infobar_view.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
1a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch// Use of this source code is governed by a BSD-style license that can be 3a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch// found in the LICENSE file. 4a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch 5a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "chrome/browser/ui/views/infobars/infobar_view.h" 6a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch 75c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu#if defined(OS_WIN) 8a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include <shellapi.h> 9a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#endif 10a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch 11a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include <algorithm> 12a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch 13a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "base/memory/scoped_ptr.h" 14a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "base/utf_string_conversions.h" 15a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "chrome/browser/infobars/infobar_delegate.h" 16a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "chrome/browser/ui/views/infobars/infobar_background.h" 17a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "chrome/browser/ui/views/infobars/infobar_button_border.h" 18a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "chrome/browser/ui/views/infobars/infobar_label_button_border.h" 19a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "grit/generated_resources.h" 20a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "grit/theme_resources.h" 21a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "grit/ui_resources.h" 22a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "third_party/skia/include/effects/SkGradientShader.h" 23a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "ui/base/accessibility/accessible_view_state.h" 24a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "ui/base/l10n/l10n_util.h" 25a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "ui/base/resource/resource_bundle.h" 26a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "ui/gfx/canvas.h" 27a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "ui/gfx/image/image.h" 28a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "ui/views/controls/button/image_button.h" 29a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "ui/views/controls/button/label_button.h" 30a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "ui/views/controls/button/menu_button.h" 31a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "ui/views/controls/image_view.h" 32a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "ui/views/controls/label.h" 33a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "ui/views/controls/link.h" 34a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "ui/views/controls/menu/menu_runner.h" 35a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "ui/views/focus/external_focus_tracker.h" 36a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "ui/views/widget/widget.h" 37#include "ui/views/window/non_client_view.h" 38 39#if defined(OS_WIN) 40#include "base/win/win_util.h" 41#include "base/win/windows_version.h" 42#include "ui/base/win/hwnd_util.h" 43#include "ui/gfx/icon_util.h" 44#endif 45 46// static 47const int InfoBar::kSeparatorLineHeight = 48 views::NonClientFrameView::kClientEdgeThickness; 49const int InfoBar::kDefaultArrowTargetHeight = 9; 50const int InfoBar::kMaximumArrowTargetHeight = 24; 51const int InfoBar::kDefaultArrowTargetHalfWidth = kDefaultArrowTargetHeight; 52const int InfoBar::kMaximumArrowTargetHalfWidth = 14; 53const int InfoBar::kDefaultBarTargetHeight = 36; 54 55const int InfoBarView::kButtonButtonSpacing = 10; 56const int InfoBarView::kEndOfLabelSpacing = 16; 57const int InfoBarView::kHorizontalPadding = 6; 58 59InfoBarView::InfoBarView(InfoBarService* owner, InfoBarDelegate* delegate) 60 : InfoBar(owner, delegate), 61 icon_(NULL), 62 close_button_(NULL) { 63 set_owned_by_client(); // InfoBar deletes itself at the appropriate time. 64 set_background(new InfoBarBackground(delegate->GetInfoBarType())); 65} 66 67InfoBarView::~InfoBarView() { 68 // We should have closed any open menus in PlatformSpecificHide(), then 69 // subclasses' RunMenu() functions should have prevented opening any new ones 70 // once we became unowned. 71 DCHECK(!menu_runner_.get()); 72} 73 74views::Label* InfoBarView::CreateLabel(const string16& text) const { 75 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 76 views::Label* label = new views::Label(text, 77 rb.GetFont(ui::ResourceBundle::MediumFont)); 78 label->SetBackgroundColor(background()->get_color()); 79 label->SetEnabledColor(SK_ColorBLACK); 80 label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 81 return label; 82} 83 84views::Link* InfoBarView::CreateLink(const string16& text, 85 views::LinkListener* listener) const { 86 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 87 views::Link* link = new views::Link; 88 link->SetText(text); 89 link->SetFont(rb.GetFont(ui::ResourceBundle::MediumFont)); 90 link->SetHorizontalAlignment(gfx::ALIGN_LEFT); 91 link->set_listener(listener); 92 link->SetBackgroundColor(background()->get_color()); 93 link->set_focusable(true); 94 return link; 95} 96 97// static 98views::MenuButton* InfoBarView::CreateMenuButton( 99 const string16& text, 100 views::MenuButtonListener* menu_button_listener) { 101 views::MenuButton* menu_button = new views::MenuButton( 102 NULL, text, menu_button_listener, true); 103 menu_button->set_border(new InfoBarButtonBorder); 104 menu_button->set_animate_on_state_change(false); 105 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 106 menu_button->set_menu_marker( 107 rb.GetImageNamed(IDR_INFOBARBUTTON_MENU_DROPARROW).ToImageSkia()); 108 menu_button->SetEnabledColor(SK_ColorBLACK); 109 menu_button->SetHoverColor(SK_ColorBLACK); 110 menu_button->SetFont(rb.GetFont(ui::ResourceBundle::MediumFont)); 111 menu_button->set_focusable(true); 112 return menu_button; 113} 114 115// static 116views::LabelButton* InfoBarView::CreateLabelButton( 117 views::ButtonListener* listener, 118 const string16& text, 119 bool needs_elevation) { 120 views::LabelButton* label_button = new views::LabelButton(listener, text); 121 label_button->set_border(new InfoBarLabelButtonBorder); 122 label_button->set_animate_on_state_change(false); 123 label_button->SetTextColor(views::Button::STATE_NORMAL, SK_ColorBLACK); 124 label_button->SetTextColor(views::Button::STATE_HOVERED, SK_ColorBLACK); 125 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 126 label_button->SetFont(rb.GetFont(ui::ResourceBundle::MediumFont)); 127#if defined(OS_WIN) 128 if (needs_elevation && 129 (base::win::GetVersion() >= base::win::VERSION_VISTA) && 130 base::win::UserAccountControlIsEnabled()) { 131 SHSTOCKICONINFO icon_info = { sizeof(SHSTOCKICONINFO) }; 132 // Even with the runtime guard above, we have to use GetProcAddress() here, 133 // because otherwise the loader will try to resolve the function address on 134 // startup, which will break on XP. 135 typedef HRESULT (STDAPICALLTYPE *GetStockIconInfo)(SHSTOCKICONID, UINT, 136 SHSTOCKICONINFO*); 137 GetStockIconInfo func = reinterpret_cast<GetStockIconInfo>( 138 GetProcAddress(GetModuleHandle(L"shell32.dll"), "SHGetStockIconInfo")); 139 if (SUCCEEDED((*func)(SIID_SHIELD, SHGSI_ICON | SHGSI_SMALLICON, 140 &icon_info))) { 141 scoped_ptr<SkBitmap> icon(IconUtil::CreateSkBitmapFromHICON( 142 icon_info.hIcon, gfx::Size(GetSystemMetrics(SM_CXSMICON), 143 GetSystemMetrics(SM_CYSMICON)))); 144 if (icon.get()) { 145 label_button->SetImage(views::Button::STATE_NORMAL, 146 gfx::ImageSkia::CreateFrom1xBitmap(*icon)); 147 } 148 DestroyIcon(icon_info.hIcon); 149 } 150 } 151#endif 152 label_button->set_focusable(true); 153 return label_button; 154} 155 156void InfoBarView::Layout() { 157 // Calculate the fill and stroke paths. We do this here, rather than in 158 // PlatformSpecificRecalculateHeight(), because this is also reached when our 159 // width is changed, which affects both paths. 160 stroke_path_.rewind(); 161 fill_path_.rewind(); 162 const InfoBarContainer::Delegate* delegate = container_delegate(); 163 if (delegate) { 164 static_cast<InfoBarBackground*>(background())->set_separator_color( 165 delegate->GetInfoBarSeparatorColor()); 166 int arrow_x; 167 SkScalar arrow_fill_height = 168 SkIntToScalar(std::max(arrow_height() - kSeparatorLineHeight, 0)); 169 SkScalar arrow_fill_half_width = SkIntToScalar(arrow_half_width()); 170 SkScalar separator_height = SkIntToScalar(kSeparatorLineHeight); 171 if (delegate->DrawInfoBarArrows(&arrow_x) && arrow_fill_height) { 172 // Skia pixel centers are at the half-values, so the arrow is horizontally 173 // centered at |arrow_x| + 0.5. Vertically, the stroke path is the center 174 // of the separator, while the fill path is a closed path that extends up 175 // through the entire height of the separator and down to the bottom of 176 // the arrow where it joins the bar. 177 stroke_path_.moveTo( 178 SkIntToScalar(arrow_x) + SK_ScalarHalf - arrow_fill_half_width, 179 SkIntToScalar(arrow_height()) - (separator_height * SK_ScalarHalf)); 180 stroke_path_.rLineTo(arrow_fill_half_width, -arrow_fill_height); 181 stroke_path_.rLineTo(arrow_fill_half_width, arrow_fill_height); 182 183 fill_path_ = stroke_path_; 184 // Move the top of the fill path up to the top of the separator and then 185 // extend it down all the way through. 186 fill_path_.offset(0, -separator_height * SK_ScalarHalf); 187 // This 0.01 hack prevents the fill from filling more pixels on the right 188 // edge of the arrow than on the left. 189 const SkScalar epsilon = 0.01f; 190 fill_path_.rLineTo(-epsilon, 0); 191 fill_path_.rLineTo(0, separator_height); 192 fill_path_.rLineTo(epsilon - (arrow_fill_half_width * 2), 0); 193 fill_path_.close(); 194 } 195 } 196 if (bar_height()) { 197 fill_path_.addRect(0.0, SkIntToScalar(arrow_height()), 198 SkIntToScalar(width()), SkIntToScalar(height() - kSeparatorLineHeight)); 199 } 200 201 int start_x = kHorizontalPadding; 202 if (icon_ != NULL) { 203 gfx::Size icon_size = icon_->GetPreferredSize(); 204 icon_->SetBounds(start_x, OffsetY(icon_size), icon_size.width(), 205 icon_size.height()); 206 } 207 208 gfx::Size button_size = close_button_->GetPreferredSize(); 209 close_button_->SetBounds(std::max(start_x + ContentMinimumWidth(), 210 width() - kHorizontalPadding - button_size.width()), OffsetY(button_size), 211 button_size.width(), button_size.height()); 212} 213 214void InfoBarView::ViewHierarchyChanged(bool is_add, View* parent, View* child) { 215 View::ViewHierarchyChanged(is_add, parent, child); 216 217 if (is_add && (child == this) && (close_button_ == NULL)) { 218 gfx::Image* image = delegate()->GetIcon(); 219 if (image) { 220 icon_ = new views::ImageView; 221 icon_->SetImage(image->ToImageSkia()); 222 AddChildView(icon_); 223 } 224 225 close_button_ = new views::ImageButton(this); 226 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 227 close_button_->SetImage(views::CustomButton::STATE_NORMAL, 228 rb.GetImageNamed(IDR_CLOSE_1).ToImageSkia()); 229 close_button_->SetImage(views::CustomButton::STATE_HOVERED, 230 rb.GetImageNamed(IDR_CLOSE_1_H).ToImageSkia()); 231 close_button_->SetImage(views::CustomButton::STATE_PRESSED, 232 rb.GetImageNamed(IDR_CLOSE_1_P).ToImageSkia()); 233 close_button_->SetAccessibleName( 234 l10n_util::GetStringUTF16(IDS_ACCNAME_CLOSE)); 235 close_button_->set_focusable(true); 236 AddChildView(close_button_); 237 } else if ((close_button_ != NULL) && (parent == this) && 238 (child != close_button_) && (close_button_->parent() == this) && 239 (child_at(child_count() - 1) != close_button_)) { 240 // For accessibility, ensure the close button is the last child view. 241 RemoveChildView(close_button_); 242 AddChildView(close_button_); 243 } 244 245 // Ensure the infobar is tall enough to display its contents. 246 const int kMinimumVerticalPadding = 6; 247 int height = kDefaultBarTargetHeight; 248 for (int i = 0; i < child_count(); ++i) { 249 const int child_height = child_at(i)->GetPreferredSize().height(); 250 height = std::max(height, child_height + kMinimumVerticalPadding); 251 } 252 SetBarTargetHeight(height); 253} 254 255void InfoBarView::PaintChildren(gfx::Canvas* canvas) { 256 canvas->Save(); 257 258 // TODO(scr): This really should be the |fill_path_|, but the clipPath seems 259 // broken on non-Windows platforms (crbug.com/75154). For now, just clip to 260 // the bar bounds. 261 // 262 // canvas->sk_canvas()->clipPath(fill_path_); 263 DCHECK_EQ(total_height(), height()) 264 << "Infobar piecewise heights do not match overall height"; 265 canvas->ClipRect(gfx::Rect(0, arrow_height(), width(), bar_height())); 266 views::View::PaintChildren(canvas); 267 canvas->Restore(); 268} 269 270void InfoBarView::ButtonPressed(views::Button* sender, 271 const ui::Event& event) { 272 if (!owner()) 273 return; // We're closing; don't call anything, it might access the owner. 274 if (sender == close_button_) { 275 delegate()->InfoBarDismissed(); 276 RemoveSelf(); 277 } 278} 279 280int InfoBarView::ContentMinimumWidth() const { 281 return 0; 282} 283 284int InfoBarView::StartX() const { 285 // Ensure we don't return a value greater than EndX(), so children can safely 286 // set something's width to "EndX() - StartX()" without risking that being 287 // negative. 288 return std::min(EndX(), 289 ((icon_ != NULL) ? icon_->bounds().right() : 0) + kHorizontalPadding); 290} 291 292int InfoBarView::EndX() const { 293 const int kCloseButtonSpacing = 12; 294 return close_button_->x() - kCloseButtonSpacing; 295} 296 297const InfoBarContainer::Delegate* InfoBarView::container_delegate() const { 298 const InfoBarContainer* infobar_container = container(); 299 return infobar_container ? infobar_container->delegate() : NULL; 300} 301 302void InfoBarView::RunMenuAt(ui::MenuModel* menu_model, 303 views::MenuButton* button, 304 views::MenuItemView::AnchorPosition anchor) { 305 DCHECK(owner()); // We'd better not open any menus while we're closing. 306 gfx::Point screen_point; 307 views::View::ConvertPointToScreen(button, &screen_point); 308 menu_runner_.reset(new views::MenuRunner(menu_model)); 309 // Ignore the result since we don't need to handle a deleted menu specially. 310 ignore_result(menu_runner_->RunMenuAt( 311 GetWidget(), button, gfx::Rect(screen_point, button->size()), anchor, 312 views::MenuRunner::HAS_MNEMONICS)); 313} 314 315void InfoBarView::PlatformSpecificShow(bool animate) { 316 views::FocusManager* focus_manager = GetFocusManager(); 317#if defined(OS_WIN) 318 // If we gain focus, we want to restore it to the previously-focused element 319 // when we're hidden. So when we're in a Widget, create a focus tracker so 320 // that if we gain focus we'll know what the previously-focused element was. 321 views::Widget* widget = GetWidget(); 322 if (widget) { 323 focus_tracker_.reset( 324 new views::ExternalFocusTracker(this, focus_manager)); 325 } 326#endif 327 if (focus_manager) 328 focus_manager->AddFocusChangeListener(this); 329 NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_ALERT, true); 330} 331 332void InfoBarView::PlatformSpecificHide(bool animate) { 333 // Cancel any menus we may have open. It doesn't make sense to leave them 334 // open while we're hidden, and if we're going to become unowned, we can't 335 // allow the user to choose any options and potentially call functions that 336 // try to access the owner. 337 menu_runner_.reset(); 338 339 // It's possible to be called twice (once with |animate| true and once with it 340 // false); in this case the second RemoveFocusChangeListener() call will 341 // silently no-op. 342 views::FocusManager* focus_manager = GetFocusManager(); 343 if (focus_manager) 344 focus_manager->RemoveFocusChangeListener(this); 345 346#if defined(OS_WIN) && !defined(USE_AURA) 347 if (!animate || !focus_tracker_.get()) 348 return; 349 350 // Do not restore focus (and active state with it) if some other top-level 351 // window became active. 352 views::Widget* widget = GetWidget(); 353 if (!widget || ui::DoesWindowBelongToActiveWindow(widget->GetNativeView())) 354 focus_tracker_->FocusLastFocusedExternalView(); 355 focus_tracker_.reset(); 356#endif 357} 358 359void InfoBarView::PlatformSpecificOnHeightsRecalculated() { 360 // Ensure that notifying our container of our size change will result in a 361 // re-layout. 362 InvalidateLayout(); 363} 364 365void InfoBarView::GetAccessibleState(ui::AccessibleViewState* state) { 366 if (delegate()) { 367 state->name = l10n_util::GetStringUTF16( 368 (delegate()->GetInfoBarType() == InfoBarDelegate::WARNING_TYPE) ? 369 IDS_ACCNAME_INFOBAR_WARNING : IDS_ACCNAME_INFOBAR_PAGE_ACTION); 370 } 371 state->role = ui::AccessibilityTypes::ROLE_ALERT; 372} 373 374gfx::Size InfoBarView::GetPreferredSize() { 375 return gfx::Size(0, total_height()); 376} 377 378void InfoBarView::OnWillChangeFocus(View* focused_before, View* focused_now) { 379 // This will trigger some screen readers to read the entire contents of this 380 // infobar. 381 if (focused_before && focused_now && !Contains(focused_before) && 382 Contains(focused_now)) { 383 NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_ALERT, true); 384 } 385} 386 387void InfoBarView::OnDidChangeFocus(View* focused_before, View* focused_now) { 388} 389