1 // Copyright 2013 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/toolbar/wrench_menu.h"
11 #include "base/metrics/histogram.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/app/chrome_command_ids.h"
15 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
16 #include "chrome/browser/bookmarks/bookmark_stats.h"
17 #include "chrome/browser/chrome_notification_types.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/search/search.h"
20 #include "chrome/browser/ui/browser.h"
21 #include "chrome/browser/ui/browser_window.h"
22 #include "chrome/browser/ui/tabs/tab_strip_model.h"
23 #include "chrome/browser/ui/toolbar/wrench_menu_model.h"
24 #include "chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.h"
25 #include "chrome/browser/ui/views/toolbar/extension_toolbar_menu_view.h"
26 #include "chrome/browser/ui/views/toolbar/wrench_menu_observer.h"
27 #include "chrome/grit/generated_resources.h"
28 #include "components/bookmarks/browser/bookmark_model.h"
29 #include "components/ui/zoom/zoom_controller.h"
30 #include "components/ui/zoom/zoom_event_manager.h"
31 #include "content/public/browser/host_zoom_map.h"
32 #include "content/public/browser/notification_observer.h"
33 #include "content/public/browser/notification_registrar.h"
34 #include "content/public/browser/notification_source.h"
35 #include "content/public/browser/notification_types.h"
36 #include "content/public/browser/user_metrics.h"
37 #include "content/public/browser/web_contents.h"
38 #include "extensions/common/feature_switch.h"
39 #include "grit/theme_resources.h"
40 #include "third_party/skia/include/core/SkCanvas.h"
41 #include "third_party/skia/include/core/SkPaint.h"
42 #include "ui/base/l10n/l10n_util.h"
43 #include "ui/base/layout.h"
44 #include "ui/base/resource/resource_bundle.h"
45 #include "ui/gfx/canvas.h"
46 #include "ui/gfx/font_list.h"
47 #include "ui/gfx/image/image.h"
48 #include "ui/gfx/image/image_skia_source.h"
49 #include "ui/gfx/skia_util.h"
50 #include "ui/gfx/text_utils.h"
51 #include "ui/views/background.h"
52 #include "ui/views/controls/button/image_button.h"
53 #include "ui/views/controls/button/label_button.h"
54 #include "ui/views/controls/button/menu_button.h"
55 #include "ui/views/controls/label.h"
56 #include "ui/views/controls/menu/menu_config.h"
57 #include "ui/views/controls/menu/menu_item_view.h"
58 #include "ui/views/controls/menu/menu_model_adapter.h"
59 #include "ui/views/controls/menu/menu_runner.h"
60 #include "ui/views/controls/menu/menu_scroll_view_container.h"
61 #include "ui/views/controls/menu/submenu_view.h"
62 #include "ui/views/widget/widget.h"
64 using base::UserMetricsAction
;
65 using bookmarks::BookmarkModel
;
66 using content::WebContents
;
67 using ui::ButtonMenuItemModel
;
69 using views::CustomButton
;
70 using views::ImageButton
;
72 using views::LabelButton
;
73 using views::MenuConfig
;
74 using views::MenuItemView
;
79 // Horizontal padding on the edges of the in-menu buttons.
80 const int kHorizontalPadding
= 15;
82 #if defined(OS_CHROMEOS)
83 // Extra horizontal space to reserve for the fullscreen button.
84 const int kFullscreenPadding
= 74;
85 // Padding to left and right of the XX% label.
86 const int kZoomLabelHorizontalPadding
= kHorizontalPadding
;
88 const int kFullscreenPadding
= 38;
89 const int kZoomLabelHorizontalPadding
= 2;
92 // Returns true if |command_id| identifies a bookmark menu item.
93 bool IsBookmarkCommand(int command_id
) {
94 return command_id
>= IDC_FIRST_BOOKMARK_MENU
;
97 // Returns true if |command_id| identifies a recent tabs menu item.
98 bool IsRecentTabsCommand(int command_id
) {
99 return command_id
>= WrenchMenuModel::kMinRecentTabsCommandId
&&
100 command_id
<= WrenchMenuModel::kMaxRecentTabsCommandId
;
103 // Subclass of ImageButton whose preferred size includes the size of the border.
104 class FullscreenButton
: public ImageButton
{
106 explicit FullscreenButton(views::ButtonListener
* listener
)
107 : ImageButton(listener
) { }
109 // Overridden from ImageButton.
110 gfx::Size
GetPreferredSize() const override
{
111 gfx::Size pref
= ImageButton::GetPreferredSize();
113 gfx::Insets insets
= border()->GetInsets();
114 pref
.Enlarge(insets
.width(), insets
.height());
120 DISALLOW_COPY_AND_ASSIGN(FullscreenButton
);
123 // Combination border/background for the buttons contained in the menu. The
124 // painting of the border/background is done here as LabelButton does not always
126 class InMenuButtonBackground
: public views::Background
{
135 explicit InMenuButtonBackground(ButtonType type
)
136 : type_(type
), left_button_(NULL
), right_button_(NULL
) {}
138 // Used when the type is CENTER_BUTTON to determine if the left/right edge
139 // needs to be rendered selected.
140 void SetOtherButtons(const CustomButton
* left_button
,
141 const CustomButton
* right_button
) {
142 if (base::i18n::IsRTL()) {
143 left_button_
= right_button
;
144 right_button_
= left_button
;
146 left_button_
= left_button
;
147 right_button_
= right_button
;
151 // Overridden from views::Background.
152 void Paint(gfx::Canvas
* canvas
, View
* view
) const override
{
153 CustomButton
* button
= CustomButton::AsCustomButton(view
);
154 views::Button::ButtonState state
=
155 button
? button
->state() : views::Button::STATE_NORMAL
;
156 int h
= view
->height();
158 // Normal buttons get a border drawn on the right side and the rest gets
159 // filled in. The left button however does not get a line to combine
161 if (type_
!= RIGHT_BUTTON
) {
162 canvas
->FillRect(gfx::Rect(0, 0, 1, h
),
163 BorderColor(view
, views::Button::STATE_NORMAL
));
166 gfx::Rect
bounds(view
->GetLocalBounds());
167 bounds
.set_x(view
->GetMirroredXForRect(bounds
));
168 DrawBackground(canvas
, view
, bounds
, state
);
172 static SkColor
BorderColor(View
* view
, views::Button::ButtonState state
) {
173 ui::NativeTheme
* theme
= view
->GetNativeTheme();
175 case views::Button::STATE_HOVERED
:
176 return theme
->GetSystemColor(
177 ui::NativeTheme::kColorId_HoverMenuButtonBorderColor
);
178 case views::Button::STATE_PRESSED
:
179 return theme
->GetSystemColor(
180 ui::NativeTheme::kColorId_FocusedMenuButtonBorderColor
);
182 return theme
->GetSystemColor(
183 ui::NativeTheme::kColorId_EnabledMenuButtonBorderColor
);
187 static SkColor
BackgroundColor(const View
* view
,
188 views::Button::ButtonState state
) {
189 const ui::NativeTheme
* theme
= view
->GetNativeTheme();
191 case views::Button::STATE_HOVERED
:
192 // Hovered should be handled in DrawBackground.
194 return theme
->GetSystemColor(
195 ui::NativeTheme::kColorId_HoverMenuItemBackgroundColor
);
196 case views::Button::STATE_PRESSED
:
197 return theme
->GetSystemColor(
198 ui::NativeTheme::kColorId_FocusedMenuItemBackgroundColor
);
200 return theme
->GetSystemColor(
201 ui::NativeTheme::kColorId_MenuBackgroundColor
);
205 void DrawBackground(gfx::Canvas
* canvas
,
206 const views::View
* view
,
207 const gfx::Rect
& bounds
,
208 views::Button::ButtonState state
) const {
209 if (state
== views::Button::STATE_HOVERED
||
210 state
== views::Button::STATE_PRESSED
) {
211 view
->GetNativeTheme()->Paint(canvas
->sk_canvas(),
212 ui::NativeTheme::kMenuItemBackground
,
213 ui::NativeTheme::kHovered
,
215 ui::NativeTheme::ExtraParams());
219 ButtonType
TypeAdjustedForRTL() const {
220 if (!base::i18n::IsRTL())
224 case LEFT_BUTTON
: return RIGHT_BUTTON
;
225 case RIGHT_BUTTON
: return LEFT_BUTTON
;
231 const ButtonType type_
;
233 // See description above setter for details.
234 const CustomButton
* left_button_
;
235 const CustomButton
* right_button_
;
237 DISALLOW_COPY_AND_ASSIGN(InMenuButtonBackground
);
240 base::string16
GetAccessibleNameForWrenchMenuItem(
241 ButtonMenuItemModel
* model
, int item_index
, int accessible_string_id
) {
242 base::string16 accessible_name
=
243 l10n_util::GetStringUTF16(accessible_string_id
);
244 base::string16 accelerator_text
;
246 ui::Accelerator menu_accelerator
;
247 if (model
->GetAcceleratorAt(item_index
, &menu_accelerator
)) {
249 ui::Accelerator(menu_accelerator
.key_code(),
250 menu_accelerator
.modifiers()).GetShortcutText();
253 return MenuItemView::GetAccessibleNameForMenuItem(
254 accessible_name
, accelerator_text
);
257 // A button that lives inside a menu item.
258 class InMenuButton
: public LabelButton
{
260 InMenuButton(views::ButtonListener
* listener
, const base::string16
& text
)
261 : LabelButton(listener
, text
), in_menu_background_(NULL
) {}
262 ~InMenuButton() override
{}
264 void Init(InMenuButtonBackground::ButtonType type
) {
266 set_request_focus_on_press(false);
267 SetHorizontalAlignment(gfx::ALIGN_CENTER
);
269 in_menu_background_
= new InMenuButtonBackground(type
);
270 set_background(in_menu_background_
);
271 SetBorder(views::Border::CreateEmptyBorder(0, kHorizontalPadding
, 0,
272 kHorizontalPadding
));
275 void SetOtherButtons(const InMenuButton
* left
, const InMenuButton
* right
) {
276 in_menu_background_
->SetOtherButtons(left
, right
);
279 // views::LabelButton
280 void OnNativeThemeChanged(const ui::NativeTheme
* theme
) override
{
281 const MenuConfig
& menu_config
= MenuConfig::instance(theme
);
282 SetFontList(menu_config
.font_list
);
286 views::Button::STATE_DISABLED
,
287 theme
->GetSystemColor(
288 ui::NativeTheme::kColorId_DisabledMenuItemForegroundColor
));
290 views::Button::STATE_HOVERED
,
291 theme
->GetSystemColor(
292 ui::NativeTheme::kColorId_SelectedMenuItemForegroundColor
));
294 views::Button::STATE_PRESSED
,
295 theme
->GetSystemColor(
296 ui::NativeTheme::kColorId_SelectedMenuItemForegroundColor
));
298 views::Button::STATE_NORMAL
,
299 theme
->GetSystemColor(
300 ui::NativeTheme::kColorId_EnabledMenuItemForegroundColor
));
305 InMenuButtonBackground
* in_menu_background_
;
307 DISALLOW_COPY_AND_ASSIGN(InMenuButton
);
310 // WrenchMenuView is a view that can contain label buttons.
311 class WrenchMenuView
: public views::View
,
312 public views::ButtonListener
,
313 public WrenchMenuObserver
{
315 WrenchMenuView(WrenchMenu
* menu
, ButtonMenuItemModel
* menu_model
)
317 menu_model_(menu_model
) {
318 menu_
->AddObserver(this);
321 ~WrenchMenuView() override
{
323 menu_
->RemoveObserver(this);
326 // Overridden from views::View.
327 void SchedulePaintInRect(const gfx::Rect
& r
) override
{
328 // Normally when the mouse enters/exits a button the buttons invokes
329 // SchedulePaint. As part of the button border (InMenuButtonBackground) is
330 // rendered by the button to the left/right of it SchedulePaint on the the
331 // button may not be enough, so this forces a paint all.
332 View::SchedulePaintInRect(gfx::Rect(size()));
335 InMenuButton
* CreateAndConfigureButton(
337 InMenuButtonBackground::ButtonType type
,
339 return CreateButtonWithAccName(string_id
, type
, index
, string_id
);
342 InMenuButton
* CreateButtonWithAccName(int string_id
,
343 InMenuButtonBackground::ButtonType type
,
346 // Should only be invoked during construction when |menu_| is valid.
348 InMenuButton
* button
= new InMenuButton(
350 gfx::RemoveAcceleratorChar(
351 l10n_util::GetStringUTF16(string_id
), '&', NULL
, NULL
));
353 button
->SetAccessibleName(
354 GetAccessibleNameForWrenchMenuItem(menu_model_
, index
, acc_string_id
));
355 button
->set_tag(index
);
356 button
->SetEnabled(menu_model_
->IsEnabledAt(index
));
358 AddChildView(button
);
359 // all buttons on menu should must be a custom button in order for
360 // the keyboard nativigation work.
361 DCHECK(CustomButton::AsCustomButton(button
));
365 // Overridden from WrenchMenuObserver:
366 void WrenchMenuDestroyed() override
{
367 menu_
->RemoveObserver(this);
373 WrenchMenu
* menu() { return menu_
; }
374 ButtonMenuItemModel
* menu_model() { return menu_model_
; }
377 // Hosting WrenchMenu.
378 // WARNING: this may be NULL during shutdown.
381 // The menu model containing the increment/decrement/reset items.
382 // WARNING: this may be NULL during shutdown.
383 ButtonMenuItemModel
* menu_model_
;
385 DISALLOW_COPY_AND_ASSIGN(WrenchMenuView
);
388 // Generate the button image for hover state.
389 class HoveredImageSource
: public gfx::ImageSkiaSource
{
391 HoveredImageSource(const gfx::ImageSkia
& image
, SkColor color
)
395 ~HoveredImageSource() override
{}
397 gfx::ImageSkiaRep
GetImageForScale(float scale
) override
{
398 const gfx::ImageSkiaRep
& rep
= image_
.GetRepresentation(scale
);
399 SkBitmap bitmap
= rep
.sk_bitmap();
401 white
.allocN32Pixels(bitmap
.width(), bitmap
.height());
402 white
.eraseARGB(0, 0, 0, 0);
404 for (int y
= 0; y
< bitmap
.height(); ++y
) {
405 uint32
* image_row
= bitmap
.getAddr32(0, y
);
406 uint32
* dst_row
= white
.getAddr32(0, y
);
407 for (int x
= 0; x
< bitmap
.width(); ++x
) {
408 uint32 image_pixel
= image_row
[x
];
409 // Fill the non transparent pixels with |color_|.
410 dst_row
[x
] = (image_pixel
& 0xFF000000) == 0x0 ? 0x0 : color_
;
413 bitmap
.unlockPixels();
414 return gfx::ImageSkiaRep(white
, scale
);
418 const gfx::ImageSkia image_
;
419 const SkColor color_
;
420 DISALLOW_COPY_AND_ASSIGN(HoveredImageSource
);
425 // CutCopyPasteView ------------------------------------------------------------
427 // CutCopyPasteView is the view containing the cut/copy/paste buttons.
428 class WrenchMenu::CutCopyPasteView
: public WrenchMenuView
{
430 CutCopyPasteView(WrenchMenu
* menu
,
431 ButtonMenuItemModel
* menu_model
,
435 : WrenchMenuView(menu
, menu_model
) {
436 InMenuButton
* cut
= CreateAndConfigureButton(
437 IDS_CUT
, InMenuButtonBackground::LEFT_BUTTON
, cut_index
);
438 InMenuButton
* copy
= CreateAndConfigureButton(
439 IDS_COPY
, InMenuButtonBackground::CENTER_BUTTON
, copy_index
);
440 InMenuButton
* paste
= CreateAndConfigureButton(
441 IDS_PASTE
, InMenuButtonBackground::CENTER_BUTTON
, paste_index
);
442 copy
->SetOtherButtons(cut
, paste
);
445 // Overridden from View.
446 gfx::Size
GetPreferredSize() const override
{
447 // Returned height doesn't matter as MenuItemView forces everything to the
448 // height of the menuitemview.
449 return gfx::Size(GetMaxChildViewPreferredWidth() * child_count(), 0);
452 void Layout() override
{
453 // All buttons are given the same width.
454 int width
= GetMaxChildViewPreferredWidth();
455 for (int i
= 0; i
< child_count(); ++i
)
456 child_at(i
)->SetBounds(i
* width
, 0, width
, height());
459 // Overridden from ButtonListener.
460 void ButtonPressed(views::Button
* sender
, const ui::Event
& event
) override
{
461 menu()->CancelAndEvaluate(menu_model(), sender
->tag());
465 // Returns the max preferred width of all the children.
466 int GetMaxChildViewPreferredWidth() const {
468 for (int i
= 0; i
< child_count(); ++i
)
469 width
= std::max(width
, child_at(i
)->GetPreferredSize().width());
473 DISALLOW_COPY_AND_ASSIGN(CutCopyPasteView
);
476 // ZoomView --------------------------------------------------------------------
479 // ZoomView contains the various zoom controls: two buttons to increase/decrease
480 // the zoom, a label showing the current zoom percent, and a button to go
482 class WrenchMenu::ZoomView
: public WrenchMenuView
{
484 ZoomView(WrenchMenu
* menu
,
485 ButtonMenuItemModel
* menu_model
,
488 int fullscreen_index
)
489 : WrenchMenuView(menu
, menu_model
),
490 fullscreen_index_(fullscreen_index
),
491 increment_button_(NULL
),
493 decrement_button_(NULL
),
494 fullscreen_button_(NULL
),
495 zoom_label_width_(0) {
496 browser_zoom_subscription_
=
497 ui_zoom::ZoomEventManager::GetForBrowserContext(
498 menu
->browser_
->profile())
499 ->AddZoomLevelChangedCallback(
500 base::Bind(&WrenchMenu::ZoomView::OnZoomLevelChanged
,
501 base::Unretained(this)));
503 decrement_button_
= CreateButtonWithAccName(
504 IDS_ZOOM_MINUS2
, InMenuButtonBackground::LEFT_BUTTON
,
505 decrement_index
, IDS_ACCNAME_ZOOM_MINUS2
);
507 zoom_label_
= new Label(
508 l10n_util::GetStringFUTF16Int(IDS_ZOOM_PERCENT
, 100));
509 zoom_label_
->SetAutoColorReadabilityEnabled(false);
510 zoom_label_
->SetHorizontalAlignment(gfx::ALIGN_RIGHT
);
512 InMenuButtonBackground
* center_bg
=
513 new InMenuButtonBackground(InMenuButtonBackground::RIGHT_BUTTON
);
514 zoom_label_
->set_background(center_bg
);
516 AddChildView(zoom_label_
);
517 zoom_label_width_
= MaxWidthForZoomLabel();
519 increment_button_
= CreateButtonWithAccName(
520 IDS_ZOOM_PLUS2
, InMenuButtonBackground::RIGHT_BUTTON
,
521 increment_index
, IDS_ACCNAME_ZOOM_PLUS2
);
523 center_bg
->SetOtherButtons(decrement_button_
, increment_button_
);
525 fullscreen_button_
= new FullscreenButton(this);
526 // all buttons on menu should must be a custom button in order for
527 // the keyboard nativigation work.
528 DCHECK(CustomButton::AsCustomButton(fullscreen_button_
));
529 gfx::ImageSkia
* full_screen_image
=
530 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
531 IDR_FULLSCREEN_MENU_BUTTON
);
532 fullscreen_button_
->SetImage(ImageButton::STATE_NORMAL
, full_screen_image
);
534 fullscreen_button_
->SetFocusable(true);
535 fullscreen_button_
->set_request_focus_on_press(false);
536 fullscreen_button_
->set_tag(fullscreen_index
);
537 fullscreen_button_
->SetImageAlignment(
538 ImageButton::ALIGN_CENTER
, ImageButton::ALIGN_MIDDLE
);
539 fullscreen_button_
->set_background(
540 new InMenuButtonBackground(InMenuButtonBackground::SINGLE_BUTTON
));
541 fullscreen_button_
->SetAccessibleName(
542 GetAccessibleNameForWrenchMenuItem(
543 menu_model
, fullscreen_index
, IDS_ACCNAME_FULLSCREEN
));
544 AddChildView(fullscreen_button_
);
546 // Need to set a font list for the zoom label width calculations.
547 OnNativeThemeChanged(NULL
);
548 UpdateZoomControls();
551 ~ZoomView() override
{}
553 // Overridden from View.
554 gfx::Size
GetPreferredSize() const override
{
555 // The increment/decrement button are forced to the same width.
556 int button_width
= std::max(increment_button_
->GetPreferredSize().width(),
557 decrement_button_
->GetPreferredSize().width());
558 int fullscreen_width
=
559 fullscreen_button_
->GetPreferredSize().width() + kFullscreenPadding
;
560 // Returned height doesn't matter as MenuItemView forces everything to the
561 // height of the menuitemview. Note that we have overridden the height when
562 // constructing the menu.
563 return gfx::Size(button_width
+ zoom_label_width_
+ button_width
+
564 fullscreen_width
, 0);
567 void Layout() override
{
569 int button_width
= std::max(increment_button_
->GetPreferredSize().width(),
570 decrement_button_
->GetPreferredSize().width());
571 gfx::Rect
bounds(0, 0, button_width
, height());
573 decrement_button_
->SetBoundsRect(bounds
);
577 bounds
.set_width(zoom_label_width_
);
578 zoom_label_
->SetBoundsRect(bounds
);
582 bounds
.set_width(button_width
);
583 increment_button_
->SetBoundsRect(bounds
);
587 bounds
.set_width(fullscreen_button_
->GetPreferredSize().width() +
589 fullscreen_button_
->SetBoundsRect(bounds
);
592 void OnNativeThemeChanged(const ui::NativeTheme
* theme
) override
{
593 WrenchMenuView::OnNativeThemeChanged(theme
);
595 const MenuConfig
& menu_config
= MenuConfig::instance(theme
);
596 zoom_label_
->SetBorder(views::Border::CreateEmptyBorder(
597 0, kZoomLabelHorizontalPadding
, 0, kZoomLabelHorizontalPadding
));
598 zoom_label_
->SetFontList(menu_config
.font_list
);
599 zoom_label_width_
= MaxWidthForZoomLabel();
602 zoom_label_
->SetEnabledColor(theme
->GetSystemColor(
603 ui::NativeTheme::kColorId_EnabledMenuItemForegroundColor
));
604 gfx::ImageSkia
* full_screen_image
=
605 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
606 IDR_FULLSCREEN_MENU_BUTTON
);
607 SkColor fg_color
= theme
->GetSystemColor(
608 ui::NativeTheme::kColorId_SelectedMenuItemForegroundColor
);
609 gfx::ImageSkia
hovered_fullscreen_image(
610 new HoveredImageSource(*full_screen_image
, fg_color
),
611 full_screen_image
->size());
612 fullscreen_button_
->SetImage(
613 ImageButton::STATE_HOVERED
, &hovered_fullscreen_image
);
614 fullscreen_button_
->SetImage(
615 ImageButton::STATE_PRESSED
, &hovered_fullscreen_image
);
619 // Overridden from ButtonListener.
620 void ButtonPressed(views::Button
* sender
, const ui::Event
& event
) override
{
621 if (sender
->tag() == fullscreen_index_
) {
622 menu()->CancelAndEvaluate(menu_model(), sender
->tag());
624 // Zoom buttons don't close the menu.
625 menu_model()->ActivatedAt(sender
->tag());
629 // Overridden from WrenchMenuObserver.
630 void WrenchMenuDestroyed() override
{ WrenchMenuView::WrenchMenuDestroyed(); }
633 void OnZoomLevelChanged(const content::HostZoomMap::ZoomLevelChange
& change
) {
634 UpdateZoomControls();
637 void UpdateZoomControls() {
638 WebContents
* selected_tab
=
639 menu()->browser_
->tab_strip_model()->GetActiveWebContents();
642 auto zoom_controller
=
643 ui_zoom::ZoomController::FromWebContents(selected_tab
);
645 zoom
= zoom_controller
->GetZoomPercent();
646 increment_button_
->SetEnabled(zoom
<
647 selected_tab
->GetMaximumZoomPercent());
648 decrement_button_
->SetEnabled(zoom
>
649 selected_tab
->GetMinimumZoomPercent());
651 zoom_label_
->SetText(
652 l10n_util::GetStringFUTF16Int(IDS_ZOOM_PERCENT
, zoom
));
654 zoom_label_width_
= MaxWidthForZoomLabel();
657 // Calculates the max width the zoom string can be.
658 int MaxWidthForZoomLabel() {
659 const gfx::FontList
& font_list
= zoom_label_
->font_list();
661 zoom_label_
->border() ? zoom_label_
->border()->GetInsets().width() : 0;
665 WebContents
* selected_tab
=
666 menu()->browser_
->tab_strip_model()->GetActiveWebContents();
668 int min_percent
= selected_tab
->GetMinimumZoomPercent();
669 int max_percent
= selected_tab
->GetMaximumZoomPercent();
671 int step
= (max_percent
- min_percent
) / 10;
672 for (int i
= min_percent
; i
<= max_percent
; i
+= step
) {
673 int w
= gfx::GetStringWidth(
674 l10n_util::GetStringFUTF16Int(IDS_ZOOM_PERCENT
, i
), font_list
);
675 max_w
= std::max(w
, max_w
);
678 max_w
= gfx::GetStringWidth(
679 l10n_util::GetStringFUTF16Int(IDS_ZOOM_PERCENT
, 100), font_list
);
682 return max_w
+ border_width
;
685 // Index of the fullscreen menu item in the model.
686 const int fullscreen_index_
;
688 scoped_ptr
<content::HostZoomMap::Subscription
> browser_zoom_subscription_
;
689 content::NotificationRegistrar registrar_
;
691 // Button for incrementing the zoom.
692 LabelButton
* increment_button_
;
694 // Label showing zoom as a percent.
697 // Button for decrementing the zoom.
698 LabelButton
* decrement_button_
;
700 ImageButton
* fullscreen_button_
;
702 // Width given to |zoom_label_|. This is the width at 100%.
703 int zoom_label_width_
;
705 DISALLOW_COPY_AND_ASSIGN(ZoomView
);
708 // RecentTabsMenuModelDelegate ------------------------------------------------
710 // Provides the ui::MenuModelDelegate implementation for RecentTabsSubMenuModel
712 class WrenchMenu::RecentTabsMenuModelDelegate
: public ui::MenuModelDelegate
{
714 RecentTabsMenuModelDelegate(WrenchMenu
* wrench_menu
,
715 ui::MenuModel
* model
,
716 views::MenuItemView
* menu_item
)
717 : wrench_menu_(wrench_menu
),
719 menu_item_(menu_item
) {
720 model_
->SetMenuModelDelegate(this);
723 ~RecentTabsMenuModelDelegate() override
{
724 model_
->SetMenuModelDelegate(NULL
);
727 const gfx::FontList
* GetLabelFontListAt(int index
) const {
728 return model_
->GetLabelFontListAt(index
);
731 bool GetShouldUseDisabledEmphasizedForegroundColor(int index
) const {
732 // The items for which we get a font list, should be shown in the bolded
734 return GetLabelFontListAt(index
) ? true : false;
737 // ui::MenuModelDelegate implementation:
739 void OnIconChanged(int index
) override
{
740 int command_id
= model_
->GetCommandIdAt(index
);
741 views::MenuItemView
* item
= menu_item_
->GetMenuItemByID(command_id
);
744 model_
->GetIconAt(index
, &icon
);
745 item
->SetIcon(*icon
.ToImageSkia());
748 void OnMenuStructureChanged() override
{
749 if (menu_item_
->HasSubmenu()) {
750 // Remove all menu items from submenu.
751 views::SubmenuView
* submenu
= menu_item_
->GetSubmenu();
752 while (submenu
->child_count() > 0)
753 menu_item_
->RemoveMenuItemAt(submenu
->child_count() - 1);
755 // Remove all elements in |WrenchMenu::command_id_to_entry_| that map to
757 WrenchMenu::CommandIDToEntry::iterator iter
=
758 wrench_menu_
->command_id_to_entry_
.begin();
759 while (iter
!= wrench_menu_
->command_id_to_entry_
.end()) {
760 if (iter
->second
.first
== model_
)
761 wrench_menu_
->command_id_to_entry_
.erase(iter
++);
767 // Add all menu items from |model| to submenu.
768 for (int i
= 0; i
< model_
->GetItemCount(); ++i
) {
769 wrench_menu_
->AddMenuItem(menu_item_
, i
, model_
, i
, model_
->GetTypeAt(i
));
772 // In case recent tabs submenu was open when items were changing, force a
773 // ChildrenChanged().
774 menu_item_
->ChildrenChanged();
778 WrenchMenu
* wrench_menu_
;
779 ui::MenuModel
* model_
;
780 views::MenuItemView
* menu_item_
;
782 DISALLOW_COPY_AND_ASSIGN(RecentTabsMenuModelDelegate
);
785 // WrenchMenu ------------------------------------------------------------------
787 WrenchMenu::WrenchMenu(Browser
* browser
, int run_flags
)
790 selected_menu_model_(nullptr),
792 bookmark_menu_(nullptr),
793 feedback_menu_item_(nullptr),
794 screenshot_menu_item_(nullptr),
795 run_flags_(run_flags
) {
796 registrar_
.Add(this, chrome::NOTIFICATION_GLOBAL_ERRORS_CHANGED
,
797 content::Source
<Profile
>(browser_
->profile()));
800 WrenchMenu::~WrenchMenu() {
801 if (bookmark_menu_delegate_
.get()) {
802 BookmarkModel
* model
= BookmarkModelFactory::GetForProfile(
803 browser_
->profile());
805 model
->RemoveObserver(this);
807 FOR_EACH_OBSERVER(WrenchMenuObserver
, observer_list_
, WrenchMenuDestroyed());
810 void WrenchMenu::Init(ui::MenuModel
* model
) {
812 root_
= new MenuItemView(this);
813 root_
->set_has_icons(true); // We have checks, radios and icons, set this
814 // so we get the taller menu style.
815 PopulateMenu(root_
, model
);
817 int32 types
= views::MenuRunner::HAS_MNEMONICS
;
819 // We add NESTED_DRAG since currently the only operation to open the wrench
820 // menu for is an extension action drag, which is controlled by the child
821 // BrowserActionsContainer view.
822 types
|= views::MenuRunner::FOR_DROP
| views::MenuRunner::NESTED_DRAG
;
824 menu_runner_
.reset(new views::MenuRunner(root_
, types
));
827 void WrenchMenu::RunMenu(views::MenuButton
* host
) {
828 gfx::Point screen_loc
;
829 views::View::ConvertPointToScreen(host
, &screen_loc
);
830 gfx::Rect
bounds(screen_loc
, host
->size());
831 content::RecordAction(UserMetricsAction("ShowAppMenu"));
832 if (menu_runner_
->RunMenuAt(host
->GetWidget(),
835 views::MENU_ANCHOR_TOPRIGHT
,
836 ui::MENU_SOURCE_NONE
) ==
837 views::MenuRunner::MENU_DELETED
)
839 if (bookmark_menu_delegate_
.get()) {
840 BookmarkModel
* model
= BookmarkModelFactory::GetForProfile(
841 browser_
->profile());
843 model
->RemoveObserver(this);
845 if (selected_menu_model_
) {
846 selected_menu_model_
->ActivatedAt(selected_index_
);
850 void WrenchMenu::CloseMenu() {
851 if (menu_runner_
.get())
852 menu_runner_
->Cancel();
855 bool WrenchMenu::IsShowing() {
856 return menu_runner_
.get() && menu_runner_
->IsRunning();
859 void WrenchMenu::AddObserver(WrenchMenuObserver
* observer
) {
860 observer_list_
.AddObserver(observer
);
863 void WrenchMenu::RemoveObserver(WrenchMenuObserver
* observer
) {
864 observer_list_
.RemoveObserver(observer
);
867 const gfx::FontList
* WrenchMenu::GetLabelFontList(int command_id
) const {
868 if (IsRecentTabsCommand(command_id
)) {
869 return recent_tabs_menu_model_delegate_
->GetLabelFontListAt(
870 ModelIndexFromCommandId(command_id
));
875 bool WrenchMenu::GetShouldUseDisabledEmphasizedForegroundColor(
876 int command_id
) const {
877 if (IsRecentTabsCommand(command_id
)) {
878 return recent_tabs_menu_model_delegate_
->
879 GetShouldUseDisabledEmphasizedForegroundColor(
880 ModelIndexFromCommandId(command_id
));
885 base::string16
WrenchMenu::GetTooltipText(int command_id
,
886 const gfx::Point
& p
) const {
887 return IsBookmarkCommand(command_id
) ?
888 bookmark_menu_delegate_
->GetTooltipText(command_id
, p
) : base::string16();
891 bool WrenchMenu::IsTriggerableEvent(views::MenuItemView
* menu
,
892 const ui::Event
& e
) {
893 return IsBookmarkCommand(menu
->GetCommand()) ?
894 bookmark_menu_delegate_
->IsTriggerableEvent(menu
, e
) :
895 MenuDelegate::IsTriggerableEvent(menu
, e
);
898 bool WrenchMenu::GetDropFormats(
901 std::set
<ui::OSExchangeData::CustomFormat
>* custom_formats
) {
902 CreateBookmarkMenu();
903 return bookmark_menu_delegate_
.get() &&
904 bookmark_menu_delegate_
->GetDropFormats(menu
, formats
, custom_formats
);
907 bool WrenchMenu::AreDropTypesRequired(MenuItemView
* menu
) {
908 CreateBookmarkMenu();
909 return bookmark_menu_delegate_
.get() &&
910 bookmark_menu_delegate_
->AreDropTypesRequired(menu
);
913 bool WrenchMenu::CanDrop(MenuItemView
* menu
,
914 const ui::OSExchangeData
& data
) {
915 CreateBookmarkMenu();
916 return bookmark_menu_delegate_
.get() &&
917 bookmark_menu_delegate_
->CanDrop(menu
, data
);
920 int WrenchMenu::GetDropOperation(
922 const ui::DropTargetEvent
& event
,
923 DropPosition
* position
) {
924 return IsBookmarkCommand(item
->GetCommand()) ?
925 bookmark_menu_delegate_
->GetDropOperation(item
, event
, position
) :
926 ui::DragDropTypes::DRAG_NONE
;
929 int WrenchMenu::OnPerformDrop(MenuItemView
* menu
,
930 DropPosition position
,
931 const ui::DropTargetEvent
& event
) {
932 if (!IsBookmarkCommand(menu
->GetCommand()))
933 return ui::DragDropTypes::DRAG_NONE
;
935 int result
= bookmark_menu_delegate_
->OnPerformDrop(menu
, position
, event
);
939 bool WrenchMenu::ShowContextMenu(MenuItemView
* source
,
942 ui::MenuSourceType source_type
) {
943 return IsBookmarkCommand(command_id
) ?
944 bookmark_menu_delegate_
->ShowContextMenu(source
, command_id
, p
,
949 bool WrenchMenu::CanDrag(MenuItemView
* menu
) {
950 return IsBookmarkCommand(menu
->GetCommand()) ?
951 bookmark_menu_delegate_
->CanDrag(menu
) : false;
954 void WrenchMenu::WriteDragData(MenuItemView
* sender
,
955 ui::OSExchangeData
* data
) {
956 DCHECK(IsBookmarkCommand(sender
->GetCommand()));
957 return bookmark_menu_delegate_
->WriteDragData(sender
, data
);
960 int WrenchMenu::GetDragOperations(MenuItemView
* sender
) {
961 return IsBookmarkCommand(sender
->GetCommand()) ?
962 bookmark_menu_delegate_
->GetDragOperations(sender
) :
963 MenuDelegate::GetDragOperations(sender
);
966 int WrenchMenu::GetMaxWidthForMenu(MenuItemView
* menu
) {
967 if (IsBookmarkCommand(menu
->GetCommand()))
968 return bookmark_menu_delegate_
->GetMaxWidthForMenu(menu
);
969 return MenuDelegate::GetMaxWidthForMenu(menu
);
972 bool WrenchMenu::IsItemChecked(int command_id
) const {
973 if (IsBookmarkCommand(command_id
))
976 const Entry
& entry
= command_id_to_entry_
.find(command_id
)->second
;
977 return entry
.first
->IsItemCheckedAt(entry
.second
);
980 bool WrenchMenu::IsCommandEnabled(int command_id
) const {
981 if (IsBookmarkCommand(command_id
))
985 return false; // The root item.
987 if (command_id
== IDC_MORE_TOOLS_MENU
)
990 // The items representing the cut menu (cut/copy/paste), zoom menu
991 // (increment/decrement/reset) and extension toolbar view are always enabled.
992 // The child views of these items enabled state updates appropriately.
993 if (command_id
== IDC_EDIT_MENU
|| command_id
== IDC_ZOOM_MENU
||
994 command_id
== IDC_EXTENSIONS_OVERFLOW_MENU
)
997 const Entry
& entry
= command_id_to_entry_
.find(command_id
)->second
;
998 return entry
.first
->IsEnabledAt(entry
.second
);
1001 void WrenchMenu::ExecuteCommand(int command_id
, int mouse_event_flags
) {
1002 if (IsBookmarkCommand(command_id
)) {
1003 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.OpenBookmark",
1004 menu_opened_timer_
.Elapsed());
1005 UMA_HISTOGRAM_ENUMERATION("WrenchMenu.MenuAction",
1006 MENU_ACTION_BOOKMARK_OPEN
, LIMIT_MENU_ACTION
);
1007 bookmark_menu_delegate_
->ExecuteCommand(command_id
, mouse_event_flags
);
1011 if (command_id
== IDC_EDIT_MENU
|| command_id
== IDC_ZOOM_MENU
||
1012 command_id
== IDC_EXTENSIONS_OVERFLOW_MENU
) {
1013 // These items are represented by child views. If ExecuteCommand is invoked
1014 // it means the user clicked on the area around the buttons and we should
1019 const Entry
& entry
= command_id_to_entry_
.find(command_id
)->second
;
1020 return entry
.first
->ActivatedAt(entry
.second
, mouse_event_flags
);
1023 bool WrenchMenu::GetAccelerator(int command_id
,
1024 ui::Accelerator
* accelerator
) const {
1025 if (IsBookmarkCommand(command_id
))
1028 if (command_id
== IDC_EDIT_MENU
|| command_id
== IDC_ZOOM_MENU
||
1029 command_id
== IDC_EXTENSIONS_OVERFLOW_MENU
) {
1030 // These have special child views; don't show the accelerator for them.
1034 CommandIDToEntry::const_iterator ix
= command_id_to_entry_
.find(command_id
);
1035 const Entry
& entry
= ix
->second
;
1036 ui::Accelerator menu_accelerator
;
1037 if (!entry
.first
->GetAcceleratorAt(entry
.second
, &menu_accelerator
))
1040 *accelerator
= ui::Accelerator(menu_accelerator
.key_code(),
1041 menu_accelerator
.modifiers());
1045 void WrenchMenu::WillShowMenu(MenuItemView
* menu
) {
1046 if (menu
== bookmark_menu_
)
1047 CreateBookmarkMenu();
1048 else if (bookmark_menu_delegate_
)
1049 bookmark_menu_delegate_
->WillShowMenu(menu
);
1052 void WrenchMenu::WillHideMenu(MenuItemView
* menu
) {
1053 // Turns off the fade out animation of the wrench menus if
1054 // |feedback_menu_item_| or |screenshot_menu_item_| is selected. This
1055 // excludes the wrench menu itself from the screenshot.
1056 if (menu
->HasSubmenu() &&
1057 ((feedback_menu_item_
&& feedback_menu_item_
->IsSelected()) ||
1058 (screenshot_menu_item_
&& screenshot_menu_item_
->IsSelected()))) {
1059 // It's okay to just turn off the animation and not turn it back on because
1060 // the menu widget will be recreated next time it's opened. See
1061 // ToolbarView::RunMenu() and Init() of this class.
1062 menu
->GetSubmenu()->GetWidget()->
1063 SetVisibilityChangedAnimationsEnabled(false);
1067 bool WrenchMenu::ShouldCloseOnDragComplete() {
1071 void WrenchMenu::BookmarkModelChanged() {
1072 DCHECK(bookmark_menu_delegate_
.get());
1073 if (!bookmark_menu_delegate_
->is_mutating_model())
1077 void WrenchMenu::Observe(int type
,
1078 const content::NotificationSource
& source
,
1079 const content::NotificationDetails
& details
) {
1081 case chrome::NOTIFICATION_GLOBAL_ERRORS_CHANGED
:
1082 // A change in the global errors list can add or remove items from the
1083 // menu. Close the menu to avoid have a stale menu on-screen.
1092 void WrenchMenu::PopulateMenu(MenuItemView
* parent
,
1094 for (int i
= 0, max
= model
->GetItemCount(); i
< max
; ++i
) {
1095 // Add the menu item at the end.
1096 int menu_index
= parent
->HasSubmenu() ?
1097 parent
->GetSubmenu()->child_count() : 0;
1098 MenuItemView
* item
=
1099 AddMenuItem(parent
, menu_index
, model
, i
, model
->GetTypeAt(i
));
1101 if (model
->GetCommandIdAt(i
) == IDC_EDIT_MENU
||
1102 model
->GetCommandIdAt(i
) == IDC_ZOOM_MENU
) {
1103 const MenuConfig
& config
= item
->GetMenuConfig();
1104 int top_margin
= config
.item_top_margin
+ config
.separator_height
/ 2;
1106 config
.item_bottom_margin
+ config
.separator_height
/ 2;
1108 // Chromeos adds extra vertical space for the menu buttons.
1109 #if defined(OS_CHROMEOS)
1114 item
->SetMargins(top_margin
, bottom_margin
);
1117 if (model
->GetTypeAt(i
) == MenuModel::TYPE_SUBMENU
)
1118 PopulateMenu(item
, model
->GetSubmenuModelAt(i
));
1120 switch (model
->GetCommandIdAt(i
)) {
1121 case IDC_EXTENSIONS_OVERFLOW_MENU
: {
1122 scoped_ptr
<ExtensionToolbarMenuView
> extension_toolbar(
1123 new ExtensionToolbarMenuView(browser_
, this));
1124 if (extension_toolbar
->ShouldShow())
1125 item
->AddChildView(extension_toolbar
.release());
1127 item
->SetVisible(false);
1131 case IDC_EDIT_MENU
: {
1132 ui::ButtonMenuItemModel
* submodel
= model
->GetButtonMenuItemAt(i
);
1133 DCHECK_EQ(IDC_CUT
, submodel
->GetCommandIdAt(0));
1134 DCHECK_EQ(IDC_COPY
, submodel
->GetCommandIdAt(1));
1135 DCHECK_EQ(IDC_PASTE
, submodel
->GetCommandIdAt(2));
1136 item
->SetTitle(l10n_util::GetStringUTF16(IDS_EDIT2
));
1137 item
->AddChildView(new CutCopyPasteView(this, submodel
, 0, 1, 2));
1141 case IDC_ZOOM_MENU
: {
1142 ui::ButtonMenuItemModel
* submodel
= model
->GetButtonMenuItemAt(i
);
1143 DCHECK_EQ(IDC_ZOOM_MINUS
, submodel
->GetCommandIdAt(0));
1144 DCHECK_EQ(IDC_ZOOM_PLUS
, submodel
->GetCommandIdAt(1));
1145 DCHECK_EQ(IDC_FULLSCREEN
, submodel
->GetCommandIdAt(2));
1146 item
->SetTitle(l10n_util::GetStringUTF16(IDS_ZOOM_MENU2
));
1147 item
->AddChildView(new ZoomView(this, submodel
, 0, 1, 2));
1151 case IDC_BOOKMARKS_MENU
:
1152 DCHECK(!bookmark_menu_
);
1153 bookmark_menu_
= item
;
1156 #if defined(GOOGLE_CHROME_BUILD)
1158 DCHECK(!feedback_menu_item_
);
1159 feedback_menu_item_
= item
;
1163 #if defined(OS_CHROMEOS)
1164 case IDC_TAKE_SCREENSHOT
:
1165 DCHECK(!screenshot_menu_item_
);
1166 screenshot_menu_item_
= item
;
1170 case IDC_RECENT_TABS_MENU
:
1171 DCHECK(!recent_tabs_menu_model_delegate_
.get());
1172 recent_tabs_menu_model_delegate_
.reset(
1173 new RecentTabsMenuModelDelegate(this, model
->GetSubmenuModelAt(i
),
1183 MenuItemView
* WrenchMenu::AddMenuItem(MenuItemView
* parent
,
1187 MenuModel::ItemType menu_type
) {
1188 int command_id
= model
->GetCommandIdAt(model_index
);
1189 DCHECK(command_id
> -1 ||
1190 (command_id
== -1 &&
1191 model
->GetTypeAt(model_index
) == MenuModel::TYPE_SEPARATOR
));
1192 DCHECK_LT(command_id
, IDC_FIRST_BOOKMARK_MENU
);
1194 if (command_id
> -1) { // Don't add separators to |command_id_to_entry_|.
1195 // All command ID's should be unique except for IDC_SHOW_HISTORY which is
1196 // in both wrench menu and RecentTabs submenu,
1197 if (command_id
!= IDC_SHOW_HISTORY
) {
1198 DCHECK(command_id_to_entry_
.find(command_id
) ==
1199 command_id_to_entry_
.end())
1200 << "command ID " << command_id
<< " already exists!";
1202 command_id_to_entry_
[command_id
].first
= model
;
1203 command_id_to_entry_
[command_id
].second
= model_index
;
1206 MenuItemView
* menu_item
= views::MenuModelAdapter::AddMenuItemFromModelAt(
1207 model
, model_index
, parent
, menu_index
, command_id
);
1210 // Flush all buttons to the right side of the menu for the new menu type.
1211 menu_item
->set_use_right_margin(false);
1212 menu_item
->SetVisible(model
->IsVisibleAt(model_index
));
1214 if (menu_type
== MenuModel::TYPE_COMMAND
&& model
->HasIcons()) {
1216 if (model
->GetIconAt(model_index
, &icon
))
1217 menu_item
->SetIcon(*icon
.ToImageSkia());
1224 void WrenchMenu::CancelAndEvaluate(ButtonMenuItemModel
* model
, int index
) {
1225 selected_menu_model_
= model
;
1226 selected_index_
= index
;
1230 void WrenchMenu::CreateBookmarkMenu() {
1231 if (bookmark_menu_delegate_
.get())
1232 return; // Already created the menu.
1234 BookmarkModel
* model
=
1235 BookmarkModelFactory::GetForProfile(browser_
->profile());
1236 if (!model
->loaded())
1239 model
->AddObserver(this);
1241 // TODO(oshima): Replace with views only API.
1242 views::Widget
* parent
= views::Widget::GetWidgetForNativeWindow(
1243 browser_
->window()->GetNativeWindow());
1244 bookmark_menu_delegate_
.reset(
1245 new BookmarkMenuDelegate(browser_
, browser_
, parent
));
1246 bookmark_menu_delegate_
->Init(this,
1248 model
->bookmark_bar_node(),
1250 BookmarkMenuDelegate::SHOW_PERMANENT_FOLDERS
,
1251 BOOKMARK_LAUNCH_LOCATION_WRENCH_MENU
);
1254 int WrenchMenu::ModelIndexFromCommandId(int command_id
) const {
1255 CommandIDToEntry::const_iterator ix
= command_id_to_entry_
.find(command_id
);
1256 DCHECK(ix
!= command_id_to_entry_
.end());
1257 return ix
->second
.second
;