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
InfoBarView(InfoBarDelegate * delegate)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
~InfoBarView()63 InfoBarView::~InfoBarView() {
64 }
65
66 // static
CreateLabel(const string16 & text)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
CreateLink(const string16 & text,views::LinkController * controller,const SkColor & background_color)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
CreateMenuButton(const string16 & text,bool normal_has_border,views::ViewMenuDelegate * menu_delegate)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
CreateTextButton(views::ButtonListener * listener,const string16 & text,bool needs_elevation)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
Layout()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
ViewHierarchyChanged(bool is_add,View * parent,View * child)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
PaintChildren(gfx::Canvas * canvas)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
ButtonPressed(views::Button * sender,const views::Event & event)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
ContentMinimumWidth() const294 int InfoBarView::ContentMinimumWidth() const {
295 return 0;
296 }
297
StartX() const298 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
EndX() const306 int InfoBarView::EndX() const {
307 const int kCloseButtonSpacing = 12;
308 return close_button_->x() - kCloseButtonSpacing;
309 }
310
container_delegate() const311 const InfoBarContainer::Delegate* InfoBarView::container_delegate() const {
312 const InfoBarContainer* infobar_container = container();
313 return infobar_container ? infobar_container->delegate() : NULL;
314 }
315
PlatformSpecificHide(bool animate)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
PlatformSpecificOnHeightsRecalculated()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
GetAccessibleState(ui::AccessibleViewState * state)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
GetPreferredSize()346 gfx::Size InfoBarView::GetPreferredSize() {
347 return gfx::Size(0, total_height());
348 }
349
FocusWillChange(View * focused_before,View * focused_now)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
DestroyFocusTracker(bool restore_focus)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
DeleteSelf()369 void InfoBarView::DeleteSelf() {
370 delete this;
371 }
372