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"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/browser/ui/infobar_container_delegate.h"
12 #include "chrome/browser/ui/views/infobars/infobar_background.h"
13 #include "chrome/grit/generated_resources.h"
14 #include "components/infobars/core/infobar_delegate.h"
15 #include "grit/theme_resources.h"
16 #include "third_party/skia/include/effects/SkGradientShader.h"
17 #include "ui/accessibility/ax_view_state.h"
18 #include "ui/base/l10n/l10n_util.h"
19 #include "ui/base/resource/resource_bundle.h"
20 #include "ui/compositor/clip_transform_recorder.h"
21 #include "ui/gfx/canvas.h"
22 #include "ui/gfx/image/image.h"
23 #include "ui/resources/grit/ui_resources.h"
24 #include "ui/views/controls/button/image_button.h"
25 #include "ui/views/controls/button/label_button.h"
26 #include "ui/views/controls/button/label_button_border.h"
27 #include "ui/views/controls/button/menu_button.h"
28 #include "ui/views/controls/image_view.h"
29 #include "ui/views/controls/label.h"
30 #include "ui/views/controls/link.h"
31 #include "ui/views/controls/menu/menu_runner.h"
32 #include "ui/views/layout/layout_constants.h"
33 #include "ui/views/widget/widget.h"
34 #include "ui/views/window/non_client_view.h"
37 // Helpers --------------------------------------------------------------------
41 const int kEdgeItemPadding
= views::kRelatedControlHorizontalSpacing
;
42 const int kIconToLabelSpacing
= views::kRelatedControlHorizontalSpacing
;
43 const int kBeforeCloseButtonSpacing
= views::kUnrelatedControlHorizontalSpacing
;
45 bool SortLabelsByDecreasingWidth(views::Label
* label_1
, views::Label
* label_2
) {
46 return label_1
->GetPreferredSize().width() >
47 label_2
->GetPreferredSize().width();
53 // InfoBarView ----------------------------------------------------------------
56 const int InfoBarView::kButtonButtonSpacing
= views::kRelatedButtonHSpacing
;
57 const int InfoBarView::kEndOfLabelSpacing
= views::kItemLabelSpacing
;
59 InfoBarView::InfoBarView(scoped_ptr
<infobars::InfoBarDelegate
> delegate
)
60 : infobars::InfoBar(delegate
.Pass()),
61 views::ExternalFocusTracker(this, NULL
),
64 set_owned_by_client(); // InfoBar deletes itself at the appropriate time.
66 new InfoBarBackground(infobars::InfoBar::delegate()->GetInfoBarType()));
69 InfoBarView::~InfoBarView() {
70 // We should have closed any open menus in PlatformSpecificHide(), then
71 // subclasses' RunMenu() functions should have prevented opening any new ones
72 // once we became unowned.
73 DCHECK(!menu_runner_
.get());
76 views::Label
* InfoBarView::CreateLabel(const base::string16
& text
) const {
77 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
78 views::Label
* label
= new views::Label(
79 text
, rb
.GetFontList(ui::ResourceBundle::MediumFont
));
80 label
->SizeToPreferredSize();
81 label
->SetBackgroundColor(background()->get_color());
82 label
->SetEnabledColor(SK_ColorBLACK
);
83 label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
87 views::Link
* InfoBarView::CreateLink(const base::string16
& text
,
88 views::LinkListener
* listener
) const {
89 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
90 views::Link
* link
= new views::Link(text
);
91 link
->SetFontList(rb
.GetFontList(ui::ResourceBundle::MediumFont
));
92 link
->SizeToPreferredSize();
93 link
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
94 link
->set_listener(listener
);
95 link
->SetBackgroundColor(background()->get_color());
100 views::LabelButton
* InfoBarView::CreateLabelButton(
101 views::ButtonListener
* listener
,
102 const base::string16
& text
) {
103 scoped_ptr
<views::LabelButtonBorder
> label_button_border(
104 new views::LabelButtonBorder(views::Button::STYLE_TEXTBUTTON
));
105 const int kNormalImageSet
[] = IMAGE_GRID(IDR_INFOBARBUTTON_NORMAL
);
106 label_button_border
->SetPainter(
107 false, views::Button::STATE_NORMAL
,
108 views::Painter::CreateImageGridPainter(kNormalImageSet
));
109 const int kHoveredImageSet
[] = IMAGE_GRID(IDR_INFOBARBUTTON_HOVER
);
110 label_button_border
->SetPainter(
111 false, views::Button::STATE_HOVERED
,
112 views::Painter::CreateImageGridPainter(kHoveredImageSet
));
113 const int kPressedImageSet
[] = IMAGE_GRID(IDR_INFOBARBUTTON_PRESSED
);
114 label_button_border
->SetPainter(
115 false, views::Button::STATE_PRESSED
,
116 views::Painter::CreateImageGridPainter(kPressedImageSet
));
118 views::LabelButton
* label_button
= new views::LabelButton(listener
, text
);
119 label_button
->SetBorder(label_button_border
.Pass());
120 label_button
->set_animate_on_state_change(false);
121 label_button
->SetTextColor(views::Button::STATE_NORMAL
, SK_ColorBLACK
);
122 label_button
->SetTextColor(views::Button::STATE_HOVERED
, SK_ColorBLACK
);
123 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
124 label_button
->SetFontList(rb
.GetFontList(ui::ResourceBundle::MediumFont
));
125 label_button
->SizeToPreferredSize();
126 label_button
->SetFocusable(true);
131 void InfoBarView::AssignWidths(Labels
* labels
, int available_width
) {
132 std::sort(labels
->begin(), labels
->end(), SortLabelsByDecreasingWidth
);
133 AssignWidthsSorted(labels
, available_width
);
136 void InfoBarView::Layout() {
137 // Calculate the fill and stroke paths. We do this here, rather than in
138 // PlatformSpecificRecalculateHeight(), because this is also reached when our
139 // width is changed, which affects both paths.
140 stroke_path_
.rewind();
142 const infobars::InfoBarContainer::Delegate
* delegate
= container_delegate();
144 static_cast<InfoBarBackground
*>(background())->set_separator_color(
145 delegate
->GetInfoBarSeparatorColor());
147 SkScalar arrow_fill_height
= SkIntToScalar(std::max(
148 arrow_height() - InfoBarContainerDelegate::kSeparatorLineHeight
, 0));
149 SkScalar arrow_fill_half_width
= SkIntToScalar(arrow_half_width());
150 SkScalar separator_height
=
151 SkIntToScalar(InfoBarContainerDelegate::kSeparatorLineHeight
);
152 if (delegate
->DrawInfoBarArrows(&arrow_x
) && arrow_fill_height
) {
153 // Skia pixel centers are at the half-values, so the arrow is horizontally
154 // centered at |arrow_x| + 0.5. Vertically, the stroke path is the center
155 // of the separator, while the fill path is a closed path that extends up
156 // through the entire height of the separator and down to the bottom of
157 // the arrow where it joins the bar.
159 SkIntToScalar(arrow_x
) + SK_ScalarHalf
- arrow_fill_half_width
,
160 SkIntToScalar(arrow_height()) - (separator_height
* SK_ScalarHalf
));
161 stroke_path_
.rLineTo(arrow_fill_half_width
, -arrow_fill_height
);
162 stroke_path_
.rLineTo(arrow_fill_half_width
, arrow_fill_height
);
164 fill_path_
= stroke_path_
;
165 // Move the top of the fill path up to the top of the separator and then
166 // extend it down all the way through.
167 fill_path_
.offset(0, -separator_height
* SK_ScalarHalf
);
168 // This 0.01 hack prevents the fill from filling more pixels on the right
169 // edge of the arrow than on the left.
170 const SkScalar epsilon
= 0.01f
;
171 fill_path_
.rLineTo(-epsilon
, 0);
172 fill_path_
.rLineTo(0, separator_height
);
173 fill_path_
.rLineTo(epsilon
- (arrow_fill_half_width
* 2), 0);
179 0.0, SkIntToScalar(arrow_height()), SkIntToScalar(width()),
181 height() - InfoBarContainerDelegate::kSeparatorLineHeight
));
184 int start_x
= kEdgeItemPadding
;
186 icon_
->SetPosition(gfx::Point(start_x
, OffsetY(icon_
)));
187 start_x
= icon_
->bounds().right() + kIconToLabelSpacing
;
190 int content_minimum_width
= ContentMinimumWidth();
191 close_button_
->SetPosition(gfx::Point(
193 start_x
+ content_minimum_width
+
194 ((content_minimum_width
> 0) ? kBeforeCloseButtonSpacing
: 0),
195 width() - kEdgeItemPadding
- close_button_
->width()),
196 OffsetY(close_button_
)));
199 void InfoBarView::ViewHierarchyChanged(
200 const ViewHierarchyChangedDetails
& details
) {
201 View::ViewHierarchyChanged(details
);
203 if (details
.is_add
&& (details
.child
== this) && (close_button_
== NULL
)) {
204 gfx::Image image
= delegate()->GetIcon();
205 if (!image
.IsEmpty()) {
206 icon_
= new views::ImageView
;
207 icon_
->SetImage(image
.ToImageSkia());
208 icon_
->SizeToPreferredSize();
212 close_button_
= new views::ImageButton(this);
213 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
214 close_button_
->SetImage(views::CustomButton::STATE_NORMAL
,
215 rb
.GetImageNamed(IDR_CLOSE_1
).ToImageSkia());
216 close_button_
->SetImage(views::CustomButton::STATE_HOVERED
,
217 rb
.GetImageNamed(IDR_CLOSE_1_H
).ToImageSkia());
218 close_button_
->SetImage(views::CustomButton::STATE_PRESSED
,
219 rb
.GetImageNamed(IDR_CLOSE_1_P
).ToImageSkia());
220 close_button_
->SizeToPreferredSize();
221 close_button_
->SetAccessibleName(
222 l10n_util::GetStringUTF16(IDS_ACCNAME_CLOSE
));
223 close_button_
->SetFocusable(true);
224 AddChildView(close_button_
);
225 } else if ((close_button_
!= NULL
) && (details
.parent
== this) &&
226 (details
.child
!= close_button_
) && (close_button_
->parent() == this) &&
227 (child_at(child_count() - 1) != close_button_
)) {
228 // For accessibility, ensure the close button is the last child view.
229 RemoveChildView(close_button_
);
230 AddChildView(close_button_
);
233 // Ensure the infobar is tall enough to display its contents.
234 const int kMinimumVerticalPadding
= 6;
235 int height
= InfoBarContainerDelegate::kDefaultBarTargetHeight
;
236 for (int i
= 0; i
< child_count(); ++i
) {
237 const int child_height
= child_at(i
)->height();
238 height
= std::max(height
, child_height
+ kMinimumVerticalPadding
);
240 SetBarTargetHeight(height
);
243 void InfoBarView::PaintChildren(const ui::PaintContext
& context
) {
244 // TODO(scr): This really should be the |fill_path_|, but the clipPath seems
245 // broken on non-Windows platforms (crbug.com/75154). For now, just clip to
248 // canvas->sk_canvas()->clipPath(fill_path_);
249 DCHECK_EQ(total_height(), height())
250 << "Infobar piecewise heights do not match overall height";
251 ui::ClipTransformRecorder
clip_transform_recorder(context
);
252 clip_transform_recorder
.ClipRect(
253 gfx::Rect(0, arrow_height(), width(), bar_height()));
254 views::View::PaintChildren(context
);
257 void InfoBarView::ButtonPressed(views::Button
* sender
,
258 const ui::Event
& event
) {
260 return; // We're closing; don't call anything, it might access the owner.
261 if (sender
== close_button_
) {
262 delegate()->InfoBarDismissed();
267 int InfoBarView::ContentMinimumWidth() const {
271 int InfoBarView::StartX() const {
272 // Ensure we don't return a value greater than EndX(), so children can safely
273 // set something's width to "EndX() - StartX()" without risking that being
275 return std::min(EndX(), (icon_
!= NULL
) ?
276 (icon_
->bounds().right() + kIconToLabelSpacing
) : kEdgeItemPadding
);
279 int InfoBarView::EndX() const {
280 return close_button_
->x() - kBeforeCloseButtonSpacing
;
283 int InfoBarView::OffsetY(views::View
* view
) const {
284 return arrow_height() +
285 std::max((bar_target_height() - view
->height()) / 2, 0) -
286 (bar_target_height() - bar_height());
289 const infobars::InfoBarContainer::Delegate
* InfoBarView::container_delegate()
291 const infobars::InfoBarContainer
* infobar_container
= container();
292 return infobar_container
? infobar_container
->delegate() : NULL
;
295 void InfoBarView::RunMenuAt(ui::MenuModel
* menu_model
,
296 views::MenuButton
* button
,
297 views::MenuAnchorPosition anchor
) {
298 DCHECK(owner()); // We'd better not open any menus while we're closing.
299 gfx::Point screen_point
;
300 views::View::ConvertPointToScreen(button
, &screen_point
);
302 new views::MenuRunner(menu_model
, views::MenuRunner::HAS_MNEMONICS
));
303 // Ignore the result since we don't need to handle a deleted menu specially.
304 ignore_result(menu_runner_
->RunMenuAt(GetWidget(),
306 gfx::Rect(screen_point
, button
->size()),
308 ui::MENU_SOURCE_NONE
));
312 void InfoBarView::AssignWidthsSorted(Labels
* labels
, int available_width
) {
315 gfx::Size
back_label_size(labels
->back()->GetPreferredSize());
316 back_label_size
.set_width(
317 std::min(back_label_size
.width(),
318 available_width
/ static_cast<int>(labels
->size())));
319 labels
->back()->SetSize(back_label_size
);
321 AssignWidthsSorted(labels
, available_width
- back_label_size
.width());
324 void InfoBarView::PlatformSpecificShow(bool animate
) {
325 // If we gain focus, we want to restore it to the previously-focused element
326 // when we're hidden. So when we're in a Widget, create a focus tracker so
327 // that if we gain focus we'll know what the previously-focused element was.
328 SetFocusManager(GetFocusManager());
330 NotifyAccessibilityEvent(ui::AX_EVENT_ALERT
, true);
333 void InfoBarView::PlatformSpecificHide(bool animate
) {
334 // Cancel any menus we may have open. It doesn't make sense to leave them
335 // open while we're hidden, and if we're going to become unowned, we can't
336 // allow the user to choose any options and potentially call functions that
337 // try to access the owner.
338 menu_runner_
.reset();
340 // It's possible to be called twice (once with |animate| true and once with it
341 // false); in this case the second SetFocusManager() call will silently no-op.
342 SetFocusManager(NULL
);
347 // Do not restore focus (and active state with it) if some other top-level
348 // window became active.
349 views::Widget
* widget
= GetWidget();
350 if (!widget
|| widget
->IsActive())
351 FocusLastFocusedExternalView();
354 void InfoBarView::PlatformSpecificOnHeightsRecalculated() {
355 // Ensure that notifying our container of our size change will result in a
360 void InfoBarView::GetAccessibleState(ui::AXViewState
* state
) {
361 state
->name
= l10n_util::GetStringUTF16(
362 (delegate()->GetInfoBarType() ==
363 infobars::InfoBarDelegate::WARNING_TYPE
) ?
364 IDS_ACCNAME_INFOBAR_WARNING
: IDS_ACCNAME_INFOBAR_PAGE_ACTION
);
365 state
->role
= ui::AX_ROLE_ALERT
;
366 state
->keyboard_shortcut
= base::ASCIIToUTF16("Alt+Shift+A");
369 gfx::Size
InfoBarView::GetPreferredSize() const {
371 kEdgeItemPadding
+ (icon_
? (icon_
->width() + kIconToLabelSpacing
) : 0) +
372 ContentMinimumWidth() + kBeforeCloseButtonSpacing
+
373 close_button_
->width() + kEdgeItemPadding
,
377 void InfoBarView::OnWillChangeFocus(View
* focused_before
, View
* focused_now
) {
378 views::ExternalFocusTracker::OnWillChangeFocus(focused_before
, focused_now
);
380 // This will trigger some screen readers to read the entire contents of this
382 if (focused_before
&& focused_now
&& !Contains(focused_before
) &&
383 Contains(focused_now
)) {
384 NotifyAccessibilityEvent(ui::AX_EVENT_ALERT
, true);