1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/ui/views/infobars/infobar_view.h" 6 7 #include "base/message_loop.h" 8 #include "base/utf_string_conversions.h" 9 #include "chrome/browser/tab_contents/infobar_delegate.h" 10 #include "chrome/browser/ui/views/infobars/infobar_background.h" 11 #include "chrome/browser/ui/views/infobars/infobar_button_border.h" 12 #include "grit/generated_resources.h" 13 #include "grit/theme_resources.h" 14 #include "third_party/skia/include/effects/SkGradientShader.h" 15 #include "ui/base/accessibility/accessible_view_state.h" 16 #include "ui/base/animation/slide_animation.h" 17 #include "ui/base/l10n/l10n_util.h" 18 #include "ui/base/resource/resource_bundle.h" 19 #include "ui/gfx/canvas_skia_paint.h" 20 #include "views/controls/button/image_button.h" 21 #include "views/controls/button/menu_button.h" 22 #include "views/controls/button/text_button.h" 23 #include "views/controls/image_view.h" 24 #include "views/controls/label.h" 25 #include "views/controls/link.h" 26 #include "views/focus/external_focus_tracker.h" 27 #include "views/widget/widget.h" 28 #include "views/window/non_client_view.h" 29 30 #if defined(OS_WIN) 31 #include <shellapi.h> 32 33 #include "base/win/win_util.h" 34 #include "base/win/windows_version.h" 35 #include "ui/base/win/hwnd_util.h" 36 #include "ui/gfx/icon_util.h" 37 #endif 38 39 // static 40 const int InfoBar::kSeparatorLineHeight = 41 views::NonClientFrameView::kClientEdgeThickness; 42 const int InfoBar::kDefaultArrowTargetHeight = 9; 43 const int InfoBar::kMaximumArrowTargetHeight = 24; 44 const int InfoBar::kDefaultArrowTargetHalfWidth = kDefaultArrowTargetHeight; 45 const int InfoBar::kMaximumArrowTargetHalfWidth = 14; 46 const int InfoBar::kDefaultBarTargetHeight = 36; 47 48 const int InfoBarView::kButtonButtonSpacing = 10; 49 const int InfoBarView::kEndOfLabelSpacing = 16; 50 const int InfoBarView::kHorizontalPadding = 6; 51 52 InfoBarView::InfoBarView(InfoBarDelegate* delegate) 53 : InfoBar(delegate), 54 icon_(NULL), 55 close_button_(NULL), 56 ALLOW_THIS_IN_INITIALIZER_LIST(delete_factory_(this)), 57 fill_path_(new SkPath), 58 stroke_path_(new SkPath) { 59 set_parent_owned(false); // InfoBar deletes itself at the appropriate time. 60 set_background(new InfoBarBackground(delegate->GetInfoBarType())); 61 } 62 63 InfoBarView::~InfoBarView() { 64 } 65 66 // static 67 views::Label* InfoBarView::CreateLabel(const string16& text) { 68 views::Label* label = new views::Label(UTF16ToWideHack(text), 69 ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::MediumFont)); 70 label->SetColor(SK_ColorBLACK); 71 label->SetHorizontalAlignment(views::Label::ALIGN_LEFT); 72 return label; 73 } 74 75 // static 76 views::Link* InfoBarView::CreateLink(const string16& text, 77 views::LinkController* controller, 78 const SkColor& background_color) { 79 views::Link* link = new views::Link; 80 link->SetText(UTF16ToWideHack(text)); 81 link->SetFont( 82 ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::MediumFont)); 83 link->SetHorizontalAlignment(views::Label::ALIGN_LEFT); 84 link->SetController(controller); 85 link->MakeReadableOverBackgroundColor(background_color); 86 return link; 87 } 88 89 // static 90 views::MenuButton* InfoBarView::CreateMenuButton( 91 const string16& text, 92 bool normal_has_border, 93 views::ViewMenuDelegate* menu_delegate) { 94 views::MenuButton* menu_button = 95 new views::MenuButton(NULL, UTF16ToWideHack(text), menu_delegate, true); 96 menu_button->set_border(new InfoBarButtonBorder); 97 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 98 menu_button->set_menu_marker( 99 rb.GetBitmapNamed(IDR_INFOBARBUTTON_MENU_DROPARROW)); 100 if (normal_has_border) { 101 menu_button->SetNormalHasBorder(true); 102 menu_button->SetAnimationDuration(0); 103 } 104 menu_button->SetEnabledColor(SK_ColorBLACK); 105 menu_button->SetHighlightColor(SK_ColorBLACK); 106 menu_button->SetHoverColor(SK_ColorBLACK); 107 menu_button->SetFont(rb.GetFont(ResourceBundle::MediumFont)); 108 return menu_button; 109 } 110 111 // static 112 views::TextButton* InfoBarView::CreateTextButton( 113 views::ButtonListener* listener, 114 const string16& text, 115 bool needs_elevation) { 116 views::TextButton* text_button = 117 new views::TextButton(listener, UTF16ToWideHack(text)); 118 text_button->set_border(new InfoBarButtonBorder); 119 text_button->SetNormalHasBorder(true); 120 text_button->SetAnimationDuration(0); 121 text_button->SetEnabledColor(SK_ColorBLACK); 122 text_button->SetHighlightColor(SK_ColorBLACK); 123 text_button->SetHoverColor(SK_ColorBLACK); 124 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 125 text_button->SetFont(rb.GetFont(ResourceBundle::MediumFont)); 126 #if defined(OS_WIN) 127 if (needs_elevation && 128 (base::win::GetVersion() >= base::win::VERSION_VISTA) && 129 base::win::UserAccountControlIsEnabled()) { 130 SHSTOCKICONINFO icon_info = { sizeof SHSTOCKICONINFO }; 131 // Even with the runtime guard above, we have to use GetProcAddress() here, 132 // because otherwise the loader will try to resolve the function address on 133 // startup, which will break on XP. 134 typedef HRESULT (STDAPICALLTYPE *GetStockIconInfo)(SHSTOCKICONID, UINT, 135 SHSTOCKICONINFO*); 136 GetStockIconInfo func = reinterpret_cast<GetStockIconInfo>( 137 GetProcAddress(GetModuleHandle(L"shell32.dll"), "SHGetStockIconInfo")); 138 (*func)(SIID_SHIELD, SHGSI_ICON | SHGSI_SMALLICON, &icon_info); 139 text_button->SetIcon(*IconUtil::CreateSkBitmapFromHICON(icon_info.hIcon, 140 gfx::Size(GetSystemMetrics(SM_CXSMICON), 141 GetSystemMetrics(SM_CYSMICON)))); 142 } 143 #endif 144 return text_button; 145 } 146 147 void InfoBarView::Layout() { 148 // Calculate the fill and stroke paths. We do this here, rather than in 149 // PlatformSpecificRecalculateHeight(), because this is also reached when our 150 // width is changed, which affects both paths. 151 stroke_path_->rewind(); 152 fill_path_->rewind(); 153 const InfoBarContainer::Delegate* delegate = container_delegate(); 154 if (delegate) { 155 static_cast<InfoBarBackground*>(background())->set_separator_color( 156 delegate->GetInfoBarSeparatorColor()); 157 int arrow_x; 158 SkScalar arrow_fill_height = 159 SkIntToScalar(std::max(arrow_height() - kSeparatorLineHeight, 0)); 160 SkScalar arrow_fill_half_width = SkIntToScalar(arrow_half_width()); 161 SkScalar separator_height = SkIntToScalar(kSeparatorLineHeight); 162 if (delegate->DrawInfoBarArrows(&arrow_x) && arrow_fill_height) { 163 // Skia pixel centers are at the half-values, so the arrow is horizontally 164 // centered at |arrow_x| + 0.5. Vertically, the stroke path is the center 165 // of the separator, while the fill path is a closed path that extends up 166 // through the entire height of the separator and down to the bottom of 167 // the arrow where it joins the bar. 168 stroke_path_->moveTo( 169 SkIntToScalar(arrow_x) + SK_ScalarHalf - arrow_fill_half_width, 170 SkIntToScalar(arrow_height()) - (separator_height * SK_ScalarHalf)); 171 stroke_path_->rLineTo(arrow_fill_half_width, -arrow_fill_height); 172 stroke_path_->rLineTo(arrow_fill_half_width, arrow_fill_height); 173 174 *fill_path_ = *stroke_path_; 175 // Move the top of the fill path up to the top of the separator and then 176 // extend it down all the way through. 177 fill_path_->offset(0, -separator_height * SK_ScalarHalf); 178 // This 0.01 hack prevents the fill from filling more pixels on the right 179 // edge of the arrow than on the left. 180 const SkScalar epsilon = 0.01f; 181 fill_path_->rLineTo(-epsilon, 0); 182 fill_path_->rLineTo(0, separator_height); 183 fill_path_->rLineTo(epsilon - (arrow_fill_half_width * 2), 0); 184 fill_path_->close(); 185 } 186 } 187 if (bar_height()) { 188 fill_path_->addRect(0.0, SkIntToScalar(arrow_height()), 189 SkIntToScalar(width()), SkIntToScalar(height() - kSeparatorLineHeight)); 190 } 191 192 int start_x = kHorizontalPadding; 193 if (icon_ != NULL) { 194 gfx::Size icon_size = icon_->GetPreferredSize(); 195 icon_->SetBounds(start_x, OffsetY(icon_size), icon_size.width(), 196 icon_size.height()); 197 } 198 199 gfx::Size button_size = close_button_->GetPreferredSize(); 200 close_button_->SetBounds(std::max(start_x + ContentMinimumWidth(), 201 width() - kHorizontalPadding - button_size.width()), OffsetY(button_size), 202 button_size.width(), button_size.height()); 203 } 204 205 void InfoBarView::ViewHierarchyChanged(bool is_add, View* parent, View* child) { 206 View::ViewHierarchyChanged(is_add, parent, child); 207 208 if (child == this) { 209 if (is_add) { 210 #if defined(OS_WIN) 211 // When we're added to a view hierarchy within a widget, we create an 212 // external focus tracker to track what was focused in case we obtain 213 // focus so that we can restore focus when we're removed. 214 views::Widget* widget = GetWidget(); 215 if (widget) { 216 focus_tracker_.reset( 217 new views::ExternalFocusTracker(this, GetFocusManager())); 218 } 219 #endif 220 if (GetFocusManager()) 221 GetFocusManager()->AddFocusChangeListener(this); 222 if (GetWidget()) { 223 GetWidget()->NotifyAccessibilityEvent( 224 this, ui::AccessibilityTypes::EVENT_ALERT, true); 225 } 226 227 if (close_button_ == NULL) { 228 SkBitmap* image = delegate()->GetIcon(); 229 if (image) { 230 icon_ = new views::ImageView; 231 icon_->SetImage(image); 232 AddChildView(icon_); 233 } 234 235 close_button_ = new views::ImageButton(this); 236 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 237 close_button_->SetImage(views::CustomButton::BS_NORMAL, 238 rb.GetBitmapNamed(IDR_CLOSE_BAR)); 239 close_button_->SetImage(views::CustomButton::BS_HOT, 240 rb.GetBitmapNamed(IDR_CLOSE_BAR_H)); 241 close_button_->SetImage(views::CustomButton::BS_PUSHED, 242 rb.GetBitmapNamed(IDR_CLOSE_BAR_P)); 243 close_button_->SetAccessibleName( 244 l10n_util::GetStringUTF16(IDS_ACCNAME_CLOSE)); 245 close_button_->SetFocusable(true); 246 AddChildView(close_button_); 247 } 248 } else { 249 DestroyFocusTracker(false); 250 animation()->Stop(); 251 // Finally, clean ourselves up when we're removed from the view hierarchy 252 // since no-one refers to us now. 253 MessageLoop::current()->PostTask(FROM_HERE, 254 delete_factory_.NewRunnableMethod(&InfoBarView::DeleteSelf)); 255 if (GetFocusManager()) 256 GetFocusManager()->RemoveFocusChangeListener(this); 257 } 258 } 259 260 // For accessibility, ensure the close button is the last child view. 261 if ((close_button_ != NULL) && (parent == this) && (child != close_button_) && 262 (close_button_->parent() == this) && 263 (GetChildViewAt(child_count() - 1) != close_button_)) { 264 RemoveChildView(close_button_); 265 AddChildView(close_button_); 266 } 267 } 268 269 void InfoBarView::PaintChildren(gfx::Canvas* canvas) { 270 canvas->Save(); 271 272 // TODO(scr): This really should be the |fill_path_|, but the clipPath seems 273 // broken on non-Windows platforms (crbug.com/75154). For now, just clip to 274 // the bar bounds. 275 // 276 // gfx::CanvasSkia* canvas_skia = canvas->AsCanvasSkia(); 277 // canvas_skia->clipPath(*fill_path_); 278 DCHECK_EQ(total_height(), height()) 279 << "Infobar piecewise heights do not match overall height"; 280 canvas->ClipRectInt(0, arrow_height(), width(), bar_height()); 281 views::View::PaintChildren(canvas); 282 canvas->Restore(); 283 } 284 285 void InfoBarView::ButtonPressed(views::Button* sender, 286 const views::Event& event) { 287 if (sender == close_button_) { 288 if (delegate()) 289 delegate()->InfoBarDismissed(); 290 RemoveInfoBar(); 291 } 292 } 293 294 int InfoBarView::ContentMinimumWidth() const { 295 return 0; 296 } 297 298 int InfoBarView::StartX() const { 299 // Ensure we don't return a value greater than EndX(), so children can safely 300 // set something's width to "EndX() - StartX()" without risking that being 301 // negative. 302 return std::min(EndX(), 303 ((icon_ != NULL) ? icon_->bounds().right() : 0) + kHorizontalPadding); 304 } 305 306 int InfoBarView::EndX() const { 307 const int kCloseButtonSpacing = 12; 308 return close_button_->x() - kCloseButtonSpacing; 309 } 310 311 const InfoBarContainer::Delegate* InfoBarView::container_delegate() const { 312 const InfoBarContainer* infobar_container = container(); 313 return infobar_container ? infobar_container->delegate() : NULL; 314 } 315 316 void InfoBarView::PlatformSpecificHide(bool animate) { 317 if (!animate) 318 return; 319 320 bool restore_focus = true; 321 #if defined(OS_WIN) 322 // Do not restore focus (and active state with it) on Windows if some other 323 // top-level window became active. 324 if (GetWidget() && 325 !ui::DoesWindowBelongToActiveWindow(GetWidget()->GetNativeView())) 326 restore_focus = false; 327 #endif // defined(OS_WIN) 328 DestroyFocusTracker(restore_focus); 329 } 330 331 void InfoBarView::PlatformSpecificOnHeightsRecalculated() { 332 // Ensure that notifying our container of our size change will result in a 333 // re-layout. 334 InvalidateLayout(); 335 } 336 337 void InfoBarView::GetAccessibleState(ui::AccessibleViewState* state) { 338 if (delegate()) { 339 state->name = l10n_util::GetStringUTF16( 340 (delegate()->GetInfoBarType() == InfoBarDelegate::WARNING_TYPE) ? 341 IDS_ACCNAME_INFOBAR_WARNING : IDS_ACCNAME_INFOBAR_PAGE_ACTION); 342 } 343 state->role = ui::AccessibilityTypes::ROLE_ALERT; 344 } 345 346 gfx::Size InfoBarView::GetPreferredSize() { 347 return gfx::Size(0, total_height()); 348 } 349 350 void InfoBarView::FocusWillChange(View* focused_before, View* focused_now) { 351 // This will trigger some screen readers to read the entire contents of this 352 // infobar. 353 if (focused_before && focused_now && !this->Contains(focused_before) && 354 this->Contains(focused_now) && GetWidget()) { 355 GetWidget()->NotifyAccessibilityEvent( 356 this, ui::AccessibilityTypes::EVENT_ALERT, true); 357 } 358 } 359 360 void InfoBarView::DestroyFocusTracker(bool restore_focus) { 361 if (focus_tracker_ != NULL) { 362 if (restore_focus) 363 focus_tracker_->FocusLastFocusedExternalView(); 364 focus_tracker_->SetFocusManager(NULL); 365 focus_tracker_.reset(); 366 } 367 } 368 369 void InfoBarView::DeleteSelf() { 370 delete this; 371 } 372