1 // Copyright (c) 2012 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.
5 #include "chrome/browser/ui/views/infobars/infobar_view.h"
13 #include "base/memory/scoped_ptr.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "chrome/browser/infobars/infobar_delegate.h"
16 #include "chrome/browser/ui/views/infobars/infobar_background.h"
17 #include "grit/generated_resources.h"
18 #include "grit/theme_resources.h"
19 #include "grit/ui_resources.h"
20 #include "third_party/skia/include/effects/SkGradientShader.h"
21 #include "ui/base/accessibility/accessible_view_state.h"
22 #include "ui/base/l10n/l10n_util.h"
23 #include "ui/base/resource/resource_bundle.h"
24 #include "ui/gfx/canvas.h"
25 #include "ui/gfx/image/image.h"
26 #include "ui/views/controls/button/image_button.h"
27 #include "ui/views/controls/button/label_button.h"
28 #include "ui/views/controls/button/label_button_border.h"
29 #include "ui/views/controls/button/menu_button.h"
30 #include "ui/views/controls/image_view.h"
31 #include "ui/views/controls/label.h"
32 #include "ui/views/controls/link.h"
33 #include "ui/views/controls/menu/menu_runner.h"
34 #include "ui/views/widget/widget.h"
35 #include "ui/views/window/non_client_view.h"
38 #include "base/win/win_util.h"
39 #include "base/win/windows_version.h"
40 #include "ui/gfx/icon_util.h"
41 #include "ui/gfx/win/hwnd_util.h"
45 // Helpers --------------------------------------------------------------------
49 bool SortLabelsByDecreasingWidth(views::Label
* label_1
, views::Label
* label_2
) {
50 return label_1
->GetPreferredSize().width() >
51 label_2
->GetPreferredSize().width();
57 // InfoBar --------------------------------------------------------------------
60 const int InfoBar::kSeparatorLineHeight
=
61 views::NonClientFrameView::kClientEdgeThickness
;
62 const int InfoBar::kDefaultArrowTargetHeight
= 9;
63 const int InfoBar::kMaximumArrowTargetHeight
= 24;
64 const int InfoBar::kDefaultArrowTargetHalfWidth
= kDefaultArrowTargetHeight
;
65 const int InfoBar::kMaximumArrowTargetHalfWidth
= 14;
66 const int InfoBar::kDefaultBarTargetHeight
= 36;
69 // InfoBarView ----------------------------------------------------------------
72 const int InfoBarView::kButtonButtonSpacing
= 10;
73 const int InfoBarView::kEndOfLabelSpacing
= 16;
74 const int InfoBarView::kHorizontalPadding
= 6;
75 const int InfoBarView::kCloseButtonSpacing
= kEndOfLabelSpacing
;
77 InfoBarView::InfoBarView(scoped_ptr
<InfoBarDelegate
> delegate
)
78 : InfoBar(delegate
.Pass()),
79 views::ExternalFocusTracker(this, NULL
),
82 set_owned_by_client(); // InfoBar deletes itself at the appropriate time.
83 set_background(new InfoBarBackground(InfoBar::delegate()->GetInfoBarType()));
86 InfoBarView::~InfoBarView() {
87 // We should have closed any open menus in PlatformSpecificHide(), then
88 // subclasses' RunMenu() functions should have prevented opening any new ones
89 // once we became unowned.
90 DCHECK(!menu_runner_
.get());
93 views::Label
* InfoBarView::CreateLabel(const base::string16
& text
) const {
94 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
95 views::Label
* label
= new views::Label(
96 text
, rb
.GetFontList(ui::ResourceBundle::MediumFont
));
97 label
->SizeToPreferredSize();
98 label
->SetBackgroundColor(background()->get_color());
99 label
->SetEnabledColor(SK_ColorBLACK
);
100 label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
104 views::Link
* InfoBarView::CreateLink(const base::string16
& text
,
105 views::LinkListener
* listener
) const {
106 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
107 views::Link
* link
= new views::Link(text
);
108 link
->SetFontList(rb
.GetFontList(ui::ResourceBundle::MediumFont
));
109 link
->SizeToPreferredSize();
110 link
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
111 link
->set_listener(listener
);
112 link
->SetBackgroundColor(background()->get_color());
117 views::MenuButton
* InfoBarView::CreateMenuButton(
118 const base::string16
& text
,
119 views::MenuButtonListener
* menu_button_listener
) {
120 scoped_ptr
<views::TextButtonDefaultBorder
> menu_button_border(
121 new views::TextButtonDefaultBorder());
122 const int kNormalImageSet
[] = IMAGE_GRID(IDR_INFOBARBUTTON_NORMAL
);
123 menu_button_border
->set_normal_painter(
124 views::Painter::CreateImageGridPainter(kNormalImageSet
));
125 const int kHotImageSet
[] = IMAGE_GRID(IDR_INFOBARBUTTON_HOVER
);
126 menu_button_border
->set_hot_painter(
127 views::Painter::CreateImageGridPainter(kHotImageSet
));
128 const int kPushedImageSet
[] = IMAGE_GRID(IDR_INFOBARBUTTON_PRESSED
);
129 menu_button_border
->set_pushed_painter(
130 views::Painter::CreateImageGridPainter(kPushedImageSet
));
132 views::MenuButton
* menu_button
= new views::MenuButton(
133 NULL
, text
, menu_button_listener
, true);
134 menu_button
->SetBorder(menu_button_border
.PassAs
<views::Border
>());
135 menu_button
->set_animate_on_state_change(false);
136 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
137 menu_button
->set_menu_marker(
138 rb
.GetImageNamed(IDR_INFOBARBUTTON_MENU_DROPARROW
).ToImageSkia());
139 menu_button
->SetEnabledColor(SK_ColorBLACK
);
140 menu_button
->SetHoverColor(SK_ColorBLACK
);
141 menu_button
->SetFontList(rb
.GetFontList(ui::ResourceBundle::MediumFont
));
142 menu_button
->SizeToPreferredSize();
143 menu_button
->SetFocusable(true);
148 views::LabelButton
* InfoBarView::CreateLabelButton(
149 views::ButtonListener
* listener
,
150 const base::string16
& text
,
151 bool needs_elevation
) {
152 scoped_ptr
<views::LabelButtonBorder
> label_button_border(
153 new views::LabelButtonBorder(views::Button::STYLE_TEXTBUTTON
));
154 const int kNormalImageSet
[] = IMAGE_GRID(IDR_INFOBARBUTTON_NORMAL
);
155 label_button_border
->SetPainter(
156 false, views::Button::STATE_NORMAL
,
157 views::Painter::CreateImageGridPainter(kNormalImageSet
));
158 const int kHoveredImageSet
[] = IMAGE_GRID(IDR_INFOBARBUTTON_HOVER
);
159 label_button_border
->SetPainter(
160 false, views::Button::STATE_HOVERED
,
161 views::Painter::CreateImageGridPainter(kHoveredImageSet
));
162 const int kPressedImageSet
[] = IMAGE_GRID(IDR_INFOBARBUTTON_PRESSED
);
163 label_button_border
->SetPainter(
164 false, views::Button::STATE_PRESSED
,
165 views::Painter::CreateImageGridPainter(kPressedImageSet
));
167 views::LabelButton
* label_button
= new views::LabelButton(listener
, text
);
168 label_button
->SetBorder(label_button_border
.PassAs
<views::Border
>());
169 label_button
->set_animate_on_state_change(false);
170 label_button
->SetTextColor(views::Button::STATE_NORMAL
, SK_ColorBLACK
);
171 label_button
->SetTextColor(views::Button::STATE_HOVERED
, SK_ColorBLACK
);
172 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
173 label_button
->SetFontList(rb
.GetFontList(ui::ResourceBundle::MediumFont
));
175 if (needs_elevation
&&
176 (base::win::GetVersion() >= base::win::VERSION_VISTA
) &&
177 base::win::UserAccountControlIsEnabled()) {
178 SHSTOCKICONINFO icon_info
= { sizeof(SHSTOCKICONINFO
) };
179 // Even with the runtime guard above, we have to use GetProcAddress() here,
180 // because otherwise the loader will try to resolve the function address on
181 // startup, which will break on XP.
182 typedef HRESULT (STDAPICALLTYPE
*GetStockIconInfo
)(SHSTOCKICONID
, UINT
,
184 GetStockIconInfo func
= reinterpret_cast<GetStockIconInfo
>(
185 GetProcAddress(GetModuleHandle(L
"shell32.dll"), "SHGetStockIconInfo"));
186 if (SUCCEEDED((*func
)(SIID_SHIELD
, SHGSI_ICON
| SHGSI_SMALLICON
,
188 scoped_ptr
<SkBitmap
> icon(IconUtil::CreateSkBitmapFromHICON(
189 icon_info
.hIcon
, gfx::Size(GetSystemMetrics(SM_CXSMICON
),
190 GetSystemMetrics(SM_CYSMICON
))));
192 label_button
->SetImage(views::Button::STATE_NORMAL
,
193 gfx::ImageSkia::CreateFrom1xBitmap(*icon
));
195 DestroyIcon(icon_info
.hIcon
);
199 label_button
->SizeToPreferredSize();
200 label_button
->SetFocusable(true);
205 void InfoBarView::AssignWidths(Labels
* labels
, int available_width
) {
206 std::sort(labels
->begin(), labels
->end(), SortLabelsByDecreasingWidth
);
207 AssignWidthsSorted(labels
, available_width
);
210 void InfoBarView::Layout() {
211 // Calculate the fill and stroke paths. We do this here, rather than in
212 // PlatformSpecificRecalculateHeight(), because this is also reached when our
213 // width is changed, which affects both paths.
214 stroke_path_
.rewind();
216 const InfoBarContainer::Delegate
* delegate
= container_delegate();
218 static_cast<InfoBarBackground
*>(background())->set_separator_color(
219 delegate
->GetInfoBarSeparatorColor());
221 SkScalar arrow_fill_height
=
222 SkIntToScalar(std::max(arrow_height() - kSeparatorLineHeight
, 0));
223 SkScalar arrow_fill_half_width
= SkIntToScalar(arrow_half_width());
224 SkScalar separator_height
= SkIntToScalar(kSeparatorLineHeight
);
225 if (delegate
->DrawInfoBarArrows(&arrow_x
) && arrow_fill_height
) {
226 // Skia pixel centers are at the half-values, so the arrow is horizontally
227 // centered at |arrow_x| + 0.5. Vertically, the stroke path is the center
228 // of the separator, while the fill path is a closed path that extends up
229 // through the entire height of the separator and down to the bottom of
230 // the arrow where it joins the bar.
232 SkIntToScalar(arrow_x
) + SK_ScalarHalf
- arrow_fill_half_width
,
233 SkIntToScalar(arrow_height()) - (separator_height
* SK_ScalarHalf
));
234 stroke_path_
.rLineTo(arrow_fill_half_width
, -arrow_fill_height
);
235 stroke_path_
.rLineTo(arrow_fill_half_width
, arrow_fill_height
);
237 fill_path_
= stroke_path_
;
238 // Move the top of the fill path up to the top of the separator and then
239 // extend it down all the way through.
240 fill_path_
.offset(0, -separator_height
* SK_ScalarHalf
);
241 // This 0.01 hack prevents the fill from filling more pixels on the right
242 // edge of the arrow than on the left.
243 const SkScalar epsilon
= 0.01f
;
244 fill_path_
.rLineTo(-epsilon
, 0);
245 fill_path_
.rLineTo(0, separator_height
);
246 fill_path_
.rLineTo(epsilon
- (arrow_fill_half_width
* 2), 0);
251 fill_path_
.addRect(0.0, SkIntToScalar(arrow_height()),
252 SkIntToScalar(width()), SkIntToScalar(height() - kSeparatorLineHeight
));
255 int start_x
= kHorizontalPadding
;
257 icon_
->SetPosition(gfx::Point(start_x
, OffsetY(icon_
)));
258 start_x
= icon_
->bounds().right() + kHorizontalPadding
;
261 int content_minimum_width
= ContentMinimumWidth();
262 close_button_
->SetPosition(gfx::Point(
263 std::max(start_x
+ content_minimum_width
+
264 ((content_minimum_width
> 0) ? kCloseButtonSpacing
: 0),
265 width() - kHorizontalPadding
- close_button_
->width()),
266 OffsetY(close_button_
)));
269 void InfoBarView::ViewHierarchyChanged(
270 const ViewHierarchyChangedDetails
& details
) {
271 View::ViewHierarchyChanged(details
);
273 if (details
.is_add
&& (details
.child
== this) && (close_button_
== NULL
)) {
274 gfx::Image image
= delegate()->GetIcon();
275 if (!image
.IsEmpty()) {
276 icon_
= new views::ImageView
;
277 icon_
->SetImage(image
.ToImageSkia());
278 icon_
->SizeToPreferredSize();
282 close_button_
= new views::ImageButton(this);
283 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
284 close_button_
->SetImage(views::CustomButton::STATE_NORMAL
,
285 rb
.GetImageNamed(IDR_CLOSE_1
).ToImageSkia());
286 close_button_
->SetImage(views::CustomButton::STATE_HOVERED
,
287 rb
.GetImageNamed(IDR_CLOSE_1_H
).ToImageSkia());
288 close_button_
->SetImage(views::CustomButton::STATE_PRESSED
,
289 rb
.GetImageNamed(IDR_CLOSE_1_P
).ToImageSkia());
290 close_button_
->SizeToPreferredSize();
291 close_button_
->SetAccessibleName(
292 l10n_util::GetStringUTF16(IDS_ACCNAME_CLOSE
));
293 close_button_
->SetFocusable(true);
294 AddChildView(close_button_
);
295 } else if ((close_button_
!= NULL
) && (details
.parent
== this) &&
296 (details
.child
!= close_button_
) && (close_button_
->parent() == this) &&
297 (child_at(child_count() - 1) != close_button_
)) {
298 // For accessibility, ensure the close button is the last child view.
299 RemoveChildView(close_button_
);
300 AddChildView(close_button_
);
303 // Ensure the infobar is tall enough to display its contents.
304 const int kMinimumVerticalPadding
= 6;
305 int height
= kDefaultBarTargetHeight
;
306 for (int i
= 0; i
< child_count(); ++i
) {
307 const int child_height
= child_at(i
)->height();
308 height
= std::max(height
, child_height
+ kMinimumVerticalPadding
);
310 SetBarTargetHeight(height
);
313 void InfoBarView::PaintChildren(gfx::Canvas
* canvas
) {
316 // TODO(scr): This really should be the |fill_path_|, but the clipPath seems
317 // broken on non-Windows platforms (crbug.com/75154). For now, just clip to
320 // canvas->sk_canvas()->clipPath(fill_path_);
321 DCHECK_EQ(total_height(), height())
322 << "Infobar piecewise heights do not match overall height";
323 canvas
->ClipRect(gfx::Rect(0, arrow_height(), width(), bar_height()));
324 views::View::PaintChildren(canvas
);
328 void InfoBarView::ButtonPressed(views::Button
* sender
,
329 const ui::Event
& event
) {
331 return; // We're closing; don't call anything, it might access the owner.
332 if (sender
== close_button_
) {
333 delegate()->InfoBarDismissed();
338 int InfoBarView::ContentMinimumWidth() {
342 int InfoBarView::StartX() const {
343 // Ensure we don't return a value greater than EndX(), so children can safely
344 // set something's width to "EndX() - StartX()" without risking that being
346 return std::min(EndX(),
347 ((icon_
!= NULL
) ? icon_
->bounds().right() : 0) + kHorizontalPadding
);
350 int InfoBarView::EndX() const {
351 return close_button_
->x() - kCloseButtonSpacing
;
354 int InfoBarView::OffsetY(views::View
* view
) const {
355 return arrow_height() +
356 std::max((bar_target_height() - view
->height()) / 2, 0) -
357 (bar_target_height() - bar_height());
360 const InfoBarContainer::Delegate
* InfoBarView::container_delegate() const {
361 const InfoBarContainer
* infobar_container
= container();
362 return infobar_container
? infobar_container
->delegate() : NULL
;
365 void InfoBarView::RunMenuAt(ui::MenuModel
* menu_model
,
366 views::MenuButton
* button
,
367 views::MenuItemView::AnchorPosition anchor
) {
368 DCHECK(owner()); // We'd better not open any menus while we're closing.
369 gfx::Point screen_point
;
370 views::View::ConvertPointToScreen(button
, &screen_point
);
371 menu_runner_
.reset(new views::MenuRunner(menu_model
));
372 // Ignore the result since we don't need to handle a deleted menu specially.
373 ignore_result(menu_runner_
->RunMenuAt(
374 GetWidget(), button
, gfx::Rect(screen_point
, button
->size()), anchor
,
375 ui::MENU_SOURCE_NONE
, views::MenuRunner::HAS_MNEMONICS
));
379 void InfoBarView::AssignWidthsSorted(Labels
* labels
, int available_width
) {
382 gfx::Size
back_label_size(labels
->back()->GetPreferredSize());
383 back_label_size
.set_width(
384 std::min(back_label_size
.width(),
385 available_width
/ static_cast<int>(labels
->size())));
386 labels
->back()->SetSize(back_label_size
);
388 AssignWidthsSorted(labels
, available_width
- back_label_size
.width());
391 void InfoBarView::PlatformSpecificShow(bool animate
) {
392 // If we gain focus, we want to restore it to the previously-focused element
393 // when we're hidden. So when we're in a Widget, create a focus tracker so
394 // that if we gain focus we'll know what the previously-focused element was.
395 SetFocusManager(GetFocusManager());
397 NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_ALERT
, true);
400 void InfoBarView::PlatformSpecificHide(bool animate
) {
401 // Cancel any menus we may have open. It doesn't make sense to leave them
402 // open while we're hidden, and if we're going to become unowned, we can't
403 // allow the user to choose any options and potentially call functions that
404 // try to access the owner.
405 menu_runner_
.reset();
407 // It's possible to be called twice (once with |animate| true and once with it
408 // false); in this case the second SetFocusManager() call will silently no-op.
409 SetFocusManager(NULL
);
414 // Do not restore focus (and active state with it) if some other top-level
415 // window became active.
416 views::Widget
* widget
= GetWidget();
417 if (!widget
|| widget
->IsActive())
418 FocusLastFocusedExternalView();
421 void InfoBarView::PlatformSpecificOnHeightsRecalculated() {
422 // Ensure that notifying our container of our size change will result in a
427 void InfoBarView::GetAccessibleState(ui::AccessibleViewState
* state
) {
428 state
->name
= l10n_util::GetStringUTF16(
429 (delegate()->GetInfoBarType() == InfoBarDelegate::WARNING_TYPE
) ?
430 IDS_ACCNAME_INFOBAR_WARNING
: IDS_ACCNAME_INFOBAR_PAGE_ACTION
);
431 state
->role
= ui::AccessibilityTypes::ROLE_ALERT
;
434 gfx::Size
InfoBarView::GetPreferredSize() {
436 kHorizontalPadding
+ (icon_
? (icon_
->width() + kHorizontalPadding
) : 0) +
437 ContentMinimumWidth() + kCloseButtonSpacing
+ close_button_
->width() +
442 void InfoBarView::OnWillChangeFocus(View
* focused_before
, View
* focused_now
) {
443 views::ExternalFocusTracker::OnWillChangeFocus(focused_before
, focused_now
);
445 // This will trigger some screen readers to read the entire contents of this
447 if (focused_before
&& focused_now
&& !Contains(focused_before
) &&
448 Contains(focused_now
)) {
449 NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_ALERT
, true);