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 const WrenchMenu
* menu() const { return menu_
; }
375 ButtonMenuItemModel
* menu_model() { return menu_model_
; }
378 // Hosting WrenchMenu.
379 // WARNING: this may be NULL during shutdown.
382 // The menu model containing the increment/decrement/reset items.
383 // WARNING: this may be NULL during shutdown.
384 ButtonMenuItemModel
* menu_model_
;
386 DISALLOW_COPY_AND_ASSIGN(WrenchMenuView
);
389 // Generate the button image for hover state.
390 class HoveredImageSource
: public gfx::ImageSkiaSource
{
392 HoveredImageSource(const gfx::ImageSkia
& image
, SkColor color
)
396 ~HoveredImageSource() override
{}
398 gfx::ImageSkiaRep
GetImageForScale(float scale
) override
{
399 const gfx::ImageSkiaRep
& rep
= image_
.GetRepresentation(scale
);
400 SkBitmap bitmap
= rep
.sk_bitmap();
402 white
.allocN32Pixels(bitmap
.width(), bitmap
.height());
403 white
.eraseARGB(0, 0, 0, 0);
405 for (int y
= 0; y
< bitmap
.height(); ++y
) {
406 uint32
* image_row
= bitmap
.getAddr32(0, y
);
407 uint32
* dst_row
= white
.getAddr32(0, y
);
408 for (int x
= 0; x
< bitmap
.width(); ++x
) {
409 uint32 image_pixel
= image_row
[x
];
410 // Fill the non transparent pixels with |color_|.
411 dst_row
[x
] = (image_pixel
& 0xFF000000) == 0x0 ? 0x0 : color_
;
414 bitmap
.unlockPixels();
415 return gfx::ImageSkiaRep(white
, scale
);
419 const gfx::ImageSkia image_
;
420 const SkColor color_
;
421 DISALLOW_COPY_AND_ASSIGN(HoveredImageSource
);
426 // CutCopyPasteView ------------------------------------------------------------
428 // CutCopyPasteView is the view containing the cut/copy/paste buttons.
429 class WrenchMenu::CutCopyPasteView
: public WrenchMenuView
{
431 CutCopyPasteView(WrenchMenu
* menu
,
432 ButtonMenuItemModel
* menu_model
,
436 : WrenchMenuView(menu
, menu_model
) {
437 InMenuButton
* cut
= CreateAndConfigureButton(
438 IDS_CUT
, InMenuButtonBackground::LEFT_BUTTON
, cut_index
);
439 InMenuButton
* copy
= CreateAndConfigureButton(
440 IDS_COPY
, InMenuButtonBackground::CENTER_BUTTON
, copy_index
);
441 InMenuButton
* paste
= CreateAndConfigureButton(
442 IDS_PASTE
, InMenuButtonBackground::CENTER_BUTTON
, paste_index
);
443 copy
->SetOtherButtons(cut
, paste
);
446 // Overridden from View.
447 gfx::Size
GetPreferredSize() const override
{
448 // Returned height doesn't matter as MenuItemView forces everything to the
449 // height of the menuitemview.
450 return gfx::Size(GetMaxChildViewPreferredWidth() * child_count(), 0);
453 void Layout() override
{
454 // All buttons are given the same width.
455 int width
= GetMaxChildViewPreferredWidth();
456 for (int i
= 0; i
< child_count(); ++i
)
457 child_at(i
)->SetBounds(i
* width
, 0, width
, height());
460 // Overridden from ButtonListener.
461 void ButtonPressed(views::Button
* sender
, const ui::Event
& event
) override
{
462 menu()->CancelAndEvaluate(menu_model(), sender
->tag());
466 // Returns the max preferred width of all the children.
467 int GetMaxChildViewPreferredWidth() const {
469 for (int i
= 0; i
< child_count(); ++i
)
470 width
= std::max(width
, child_at(i
)->GetPreferredSize().width());
474 DISALLOW_COPY_AND_ASSIGN(CutCopyPasteView
);
477 // ZoomView --------------------------------------------------------------------
480 // ZoomView contains the various zoom controls: two buttons to increase/decrease
481 // the zoom, a label showing the current zoom percent, and a button to go
483 class WrenchMenu::ZoomView
: public WrenchMenuView
{
485 ZoomView(WrenchMenu
* menu
,
486 ButtonMenuItemModel
* menu_model
,
489 int fullscreen_index
)
490 : WrenchMenuView(menu
, menu_model
),
491 fullscreen_index_(fullscreen_index
),
492 increment_button_(NULL
),
494 decrement_button_(NULL
),
495 fullscreen_button_(NULL
),
496 zoom_label_max_width_(0),
497 zoom_label_max_width_valid_(false) {
498 browser_zoom_subscription_
=
499 ui_zoom::ZoomEventManager::GetForBrowserContext(
500 menu
->browser_
->profile())
501 ->AddZoomLevelChangedCallback(
502 base::Bind(&WrenchMenu::ZoomView::OnZoomLevelChanged
,
503 base::Unretained(this)));
505 decrement_button_
= CreateButtonWithAccName(
506 IDS_ZOOM_MINUS2
, InMenuButtonBackground::LEFT_BUTTON
,
507 decrement_index
, IDS_ACCNAME_ZOOM_MINUS2
);
509 zoom_label_
= new Label(
510 l10n_util::GetStringFUTF16Int(IDS_ZOOM_PERCENT
, 100));
511 zoom_label_
->SetAutoColorReadabilityEnabled(false);
512 zoom_label_
->SetHorizontalAlignment(gfx::ALIGN_RIGHT
);
514 InMenuButtonBackground
* center_bg
=
515 new InMenuButtonBackground(InMenuButtonBackground::RIGHT_BUTTON
);
516 zoom_label_
->set_background(center_bg
);
518 AddChildView(zoom_label_
);
519 zoom_label_max_width_valid_
= false;
521 increment_button_
= CreateButtonWithAccName(
522 IDS_ZOOM_PLUS2
, InMenuButtonBackground::RIGHT_BUTTON
,
523 increment_index
, IDS_ACCNAME_ZOOM_PLUS2
);
525 center_bg
->SetOtherButtons(decrement_button_
, increment_button_
);
527 fullscreen_button_
= new FullscreenButton(this);
528 // all buttons on menu should must be a custom button in order for
529 // the keyboard nativigation work.
530 DCHECK(CustomButton::AsCustomButton(fullscreen_button_
));
531 gfx::ImageSkia
* full_screen_image
=
532 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
533 IDR_FULLSCREEN_MENU_BUTTON
);
534 fullscreen_button_
->SetImage(ImageButton::STATE_NORMAL
, full_screen_image
);
536 fullscreen_button_
->SetFocusable(true);
537 fullscreen_button_
->set_request_focus_on_press(false);
538 fullscreen_button_
->set_tag(fullscreen_index
);
539 fullscreen_button_
->SetImageAlignment(
540 ImageButton::ALIGN_CENTER
, ImageButton::ALIGN_MIDDLE
);
541 fullscreen_button_
->set_background(
542 new InMenuButtonBackground(InMenuButtonBackground::SINGLE_BUTTON
));
543 fullscreen_button_
->SetAccessibleName(
544 GetAccessibleNameForWrenchMenuItem(
545 menu_model
, fullscreen_index
, IDS_ACCNAME_FULLSCREEN
));
546 AddChildView(fullscreen_button_
);
548 // Need to set a font list for the zoom label width calculations.
549 OnNativeThemeChanged(NULL
);
550 UpdateZoomControls();
553 ~ZoomView() override
{}
555 // Overridden from View.
556 gfx::Size
GetPreferredSize() const override
{
557 // The increment/decrement button are forced to the same width.
558 int button_width
= std::max(increment_button_
->GetPreferredSize().width(),
559 decrement_button_
->GetPreferredSize().width());
560 int fullscreen_width
=
561 fullscreen_button_
->GetPreferredSize().width() + kFullscreenPadding
;
562 // Returned height doesn't matter as MenuItemView forces everything to the
563 // height of the menuitemview. Note that we have overridden the height when
564 // constructing the menu.
566 button_width
+ ZoomLabelMaxWidth() + button_width
+ fullscreen_width
,
570 void Layout() override
{
572 int button_width
= std::max(increment_button_
->GetPreferredSize().width(),
573 decrement_button_
->GetPreferredSize().width());
574 gfx::Rect
bounds(0, 0, button_width
, height());
576 decrement_button_
->SetBoundsRect(bounds
);
580 bounds
.set_width(ZoomLabelMaxWidth());
581 zoom_label_
->SetBoundsRect(bounds
);
585 bounds
.set_width(button_width
);
586 increment_button_
->SetBoundsRect(bounds
);
590 bounds
.set_width(fullscreen_button_
->GetPreferredSize().width() +
592 fullscreen_button_
->SetBoundsRect(bounds
);
595 void OnNativeThemeChanged(const ui::NativeTheme
* theme
) override
{
596 WrenchMenuView::OnNativeThemeChanged(theme
);
598 const MenuConfig
& menu_config
= MenuConfig::instance(theme
);
599 zoom_label_
->SetBorder(views::Border::CreateEmptyBorder(
600 0, kZoomLabelHorizontalPadding
, 0, kZoomLabelHorizontalPadding
));
601 zoom_label_
->SetFontList(menu_config
.font_list
);
602 zoom_label_max_width_valid_
= false;
605 zoom_label_
->SetEnabledColor(theme
->GetSystemColor(
606 ui::NativeTheme::kColorId_EnabledMenuItemForegroundColor
));
607 gfx::ImageSkia
* full_screen_image
=
608 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
609 IDR_FULLSCREEN_MENU_BUTTON
);
610 SkColor fg_color
= theme
->GetSystemColor(
611 ui::NativeTheme::kColorId_SelectedMenuItemForegroundColor
);
612 gfx::ImageSkia
hovered_fullscreen_image(
613 new HoveredImageSource(*full_screen_image
, fg_color
),
614 full_screen_image
->size());
615 fullscreen_button_
->SetImage(
616 ImageButton::STATE_HOVERED
, &hovered_fullscreen_image
);
617 fullscreen_button_
->SetImage(
618 ImageButton::STATE_PRESSED
, &hovered_fullscreen_image
);
622 // Overridden from ButtonListener.
623 void ButtonPressed(views::Button
* sender
, const ui::Event
& event
) override
{
624 if (sender
->tag() == fullscreen_index_
) {
625 menu()->CancelAndEvaluate(menu_model(), sender
->tag());
627 // Zoom buttons don't close the menu.
628 menu_model()->ActivatedAt(sender
->tag());
632 // Overridden from WrenchMenuObserver.
633 void WrenchMenuDestroyed() override
{ WrenchMenuView::WrenchMenuDestroyed(); }
636 void OnZoomLevelChanged(const content::HostZoomMap::ZoomLevelChange
& change
) {
637 UpdateZoomControls();
640 void UpdateZoomControls() {
641 WebContents
* selected_tab
=
642 menu()->browser_
->tab_strip_model()->GetActiveWebContents();
645 auto zoom_controller
=
646 ui_zoom::ZoomController::FromWebContents(selected_tab
);
648 zoom
= zoom_controller
->GetZoomPercent();
649 increment_button_
->SetEnabled(zoom
<
650 selected_tab
->GetMaximumZoomPercent());
651 decrement_button_
->SetEnabled(zoom
>
652 selected_tab
->GetMinimumZoomPercent());
654 zoom_label_
->SetText(
655 l10n_util::GetStringFUTF16Int(IDS_ZOOM_PERCENT
, zoom
));
657 zoom_label_max_width_valid_
= false;
660 // Returns the max width the zoom string can be.
661 int ZoomLabelMaxWidth() const {
662 if (!zoom_label_max_width_valid_
) {
663 const gfx::FontList
& font_list
= zoom_label_
->font_list();
664 int border_width
= zoom_label_
->border()
665 ? zoom_label_
->border()->GetInsets().width()
670 WebContents
* selected_tab
=
671 menu()->browser_
->tab_strip_model()->GetActiveWebContents();
673 int min_percent
= selected_tab
->GetMinimumZoomPercent();
674 int max_percent
= selected_tab
->GetMaximumZoomPercent();
676 int step
= (max_percent
- min_percent
) / 10;
677 for (int i
= min_percent
; i
<= max_percent
; i
+= step
) {
678 int w
= gfx::GetStringWidth(
679 l10n_util::GetStringFUTF16Int(IDS_ZOOM_PERCENT
, i
), font_list
);
680 max_w
= std::max(w
, max_w
);
683 max_w
= gfx::GetStringWidth(
684 l10n_util::GetStringFUTF16Int(IDS_ZOOM_PERCENT
, 100), font_list
);
686 zoom_label_max_width_
= max_w
+ border_width
;
688 zoom_label_max_width_valid_
= true;
690 return zoom_label_max_width_
;
693 // Index of the fullscreen menu item in the model.
694 const int fullscreen_index_
;
696 scoped_ptr
<content::HostZoomMap::Subscription
> browser_zoom_subscription_
;
697 content::NotificationRegistrar registrar_
;
699 // Button for incrementing the zoom.
700 LabelButton
* increment_button_
;
702 // Label showing zoom as a percent.
705 // Button for decrementing the zoom.
706 LabelButton
* decrement_button_
;
708 ImageButton
* fullscreen_button_
;
710 // Cached width of how wide the zoom label string can be. This is the width at
711 // 100%. This should not be accessed directly, use ZoomLabelMaxWidth()
712 // instead. This value is cached because is depends on multiple calls to
713 // gfx::GetStringWidth(...) which are expensive.
714 mutable int zoom_label_max_width_
;
716 // Flag tracking whether calls to ZoomLabelMaxWidth() need to re-calculate
717 // the label width, because the cached value may no longer be correct.
718 mutable bool zoom_label_max_width_valid_
;
720 DISALLOW_COPY_AND_ASSIGN(ZoomView
);
723 // RecentTabsMenuModelDelegate ------------------------------------------------
725 // Provides the ui::MenuModelDelegate implementation for RecentTabsSubMenuModel
727 class WrenchMenu::RecentTabsMenuModelDelegate
: public ui::MenuModelDelegate
{
729 RecentTabsMenuModelDelegate(WrenchMenu
* wrench_menu
,
730 ui::MenuModel
* model
,
731 views::MenuItemView
* menu_item
)
732 : wrench_menu_(wrench_menu
),
734 menu_item_(menu_item
) {
735 model_
->SetMenuModelDelegate(this);
738 ~RecentTabsMenuModelDelegate() override
{
739 model_
->SetMenuModelDelegate(NULL
);
742 const gfx::FontList
* GetLabelFontListAt(int index
) const {
743 return model_
->GetLabelFontListAt(index
);
746 bool GetShouldUseDisabledEmphasizedForegroundColor(int index
) const {
747 // The items for which we get a font list, should be shown in the bolded
749 return GetLabelFontListAt(index
) ? true : false;
752 // ui::MenuModelDelegate implementation:
754 void OnIconChanged(int index
) override
{
755 int command_id
= model_
->GetCommandIdAt(index
);
756 views::MenuItemView
* item
= menu_item_
->GetMenuItemByID(command_id
);
759 model_
->GetIconAt(index
, &icon
);
760 item
->SetIcon(*icon
.ToImageSkia());
763 void OnMenuStructureChanged() override
{
764 if (menu_item_
->HasSubmenu()) {
765 // Remove all menu items from submenu.
766 views::SubmenuView
* submenu
= menu_item_
->GetSubmenu();
767 while (submenu
->child_count() > 0)
768 menu_item_
->RemoveMenuItemAt(submenu
->child_count() - 1);
770 // Remove all elements in |WrenchMenu::command_id_to_entry_| that map to
772 WrenchMenu::CommandIDToEntry::iterator iter
=
773 wrench_menu_
->command_id_to_entry_
.begin();
774 while (iter
!= wrench_menu_
->command_id_to_entry_
.end()) {
775 if (iter
->second
.first
== model_
)
776 wrench_menu_
->command_id_to_entry_
.erase(iter
++);
782 // Add all menu items from |model| to submenu.
783 for (int i
= 0; i
< model_
->GetItemCount(); ++i
) {
784 wrench_menu_
->AddMenuItem(menu_item_
, i
, model_
, i
, model_
->GetTypeAt(i
));
787 // In case recent tabs submenu was open when items were changing, force a
788 // ChildrenChanged().
789 menu_item_
->ChildrenChanged();
793 WrenchMenu
* wrench_menu_
;
794 ui::MenuModel
* model_
;
795 views::MenuItemView
* menu_item_
;
797 DISALLOW_COPY_AND_ASSIGN(RecentTabsMenuModelDelegate
);
800 // WrenchMenu ------------------------------------------------------------------
802 WrenchMenu::WrenchMenu(Browser
* browser
, int run_flags
)
805 selected_menu_model_(nullptr),
807 bookmark_menu_(nullptr),
808 feedback_menu_item_(nullptr),
809 screenshot_menu_item_(nullptr),
810 extension_toolbar_(nullptr),
811 run_flags_(run_flags
) {
812 registrar_
.Add(this, chrome::NOTIFICATION_GLOBAL_ERRORS_CHANGED
,
813 content::Source
<Profile
>(browser_
->profile()));
816 WrenchMenu::~WrenchMenu() {
817 if (bookmark_menu_delegate_
.get()) {
818 BookmarkModel
* model
= BookmarkModelFactory::GetForProfile(
819 browser_
->profile());
821 model
->RemoveObserver(this);
823 FOR_EACH_OBSERVER(WrenchMenuObserver
, observer_list_
, WrenchMenuDestroyed());
826 void WrenchMenu::Init(ui::MenuModel
* model
) {
828 root_
= new MenuItemView(this);
829 root_
->set_has_icons(true); // We have checks, radios and icons, set this
830 // so we get the taller menu style.
831 PopulateMenu(root_
, model
);
833 int32 types
= views::MenuRunner::HAS_MNEMONICS
;
835 // We add NESTED_DRAG since currently the only operation to open the wrench
836 // menu for is an extension action drag, which is controlled by the child
837 // BrowserActionsContainer view.
838 types
|= views::MenuRunner::FOR_DROP
| views::MenuRunner::NESTED_DRAG
;
840 menu_runner_
.reset(new views::MenuRunner(root_
, types
));
843 void WrenchMenu::RunMenu(views::MenuButton
* host
) {
844 gfx::Point screen_loc
;
845 views::View::ConvertPointToScreen(host
, &screen_loc
);
846 gfx::Rect
bounds(screen_loc
, host
->size());
847 content::RecordAction(UserMetricsAction("ShowAppMenu"));
848 if (menu_runner_
->RunMenuAt(host
->GetWidget(),
851 views::MENU_ANCHOR_TOPRIGHT
,
852 ui::MENU_SOURCE_NONE
) ==
853 views::MenuRunner::MENU_DELETED
)
855 if (bookmark_menu_delegate_
.get()) {
856 BookmarkModel
* model
= BookmarkModelFactory::GetForProfile(
857 browser_
->profile());
859 model
->RemoveObserver(this);
861 if (selected_menu_model_
) {
862 selected_menu_model_
->ActivatedAt(selected_index_
);
866 void WrenchMenu::CloseMenu() {
867 if (menu_runner_
.get())
868 menu_runner_
->Cancel();
871 bool WrenchMenu::IsShowing() {
872 return menu_runner_
.get() && menu_runner_
->IsRunning();
875 void WrenchMenu::AddObserver(WrenchMenuObserver
* observer
) {
876 observer_list_
.AddObserver(observer
);
879 void WrenchMenu::RemoveObserver(WrenchMenuObserver
* observer
) {
880 observer_list_
.RemoveObserver(observer
);
883 const gfx::FontList
* WrenchMenu::GetLabelFontList(int command_id
) const {
884 if (IsRecentTabsCommand(command_id
)) {
885 return recent_tabs_menu_model_delegate_
->GetLabelFontListAt(
886 ModelIndexFromCommandId(command_id
));
891 bool WrenchMenu::GetShouldUseDisabledEmphasizedForegroundColor(
892 int command_id
) const {
893 if (IsRecentTabsCommand(command_id
)) {
894 return recent_tabs_menu_model_delegate_
->
895 GetShouldUseDisabledEmphasizedForegroundColor(
896 ModelIndexFromCommandId(command_id
));
901 base::string16
WrenchMenu::GetTooltipText(int command_id
,
902 const gfx::Point
& p
) const {
903 return IsBookmarkCommand(command_id
) ?
904 bookmark_menu_delegate_
->GetTooltipText(command_id
, p
) : base::string16();
907 bool WrenchMenu::IsTriggerableEvent(views::MenuItemView
* menu
,
908 const ui::Event
& e
) {
909 return IsBookmarkCommand(menu
->GetCommand()) ?
910 bookmark_menu_delegate_
->IsTriggerableEvent(menu
, e
) :
911 MenuDelegate::IsTriggerableEvent(menu
, e
);
914 bool WrenchMenu::GetDropFormats(
917 std::set
<ui::OSExchangeData::CustomFormat
>* custom_formats
) {
918 CreateBookmarkMenu();
919 return bookmark_menu_delegate_
.get() &&
920 bookmark_menu_delegate_
->GetDropFormats(menu
, formats
, custom_formats
);
923 bool WrenchMenu::AreDropTypesRequired(MenuItemView
* menu
) {
924 CreateBookmarkMenu();
925 return bookmark_menu_delegate_
.get() &&
926 bookmark_menu_delegate_
->AreDropTypesRequired(menu
);
929 bool WrenchMenu::CanDrop(MenuItemView
* menu
,
930 const ui::OSExchangeData
& data
) {
931 CreateBookmarkMenu();
932 return bookmark_menu_delegate_
.get() &&
933 bookmark_menu_delegate_
->CanDrop(menu
, data
);
936 int WrenchMenu::GetDropOperation(
938 const ui::DropTargetEvent
& event
,
939 DropPosition
* position
) {
940 return IsBookmarkCommand(item
->GetCommand()) ?
941 bookmark_menu_delegate_
->GetDropOperation(item
, event
, position
) :
942 ui::DragDropTypes::DRAG_NONE
;
945 int WrenchMenu::OnPerformDrop(MenuItemView
* menu
,
946 DropPosition position
,
947 const ui::DropTargetEvent
& event
) {
948 if (!IsBookmarkCommand(menu
->GetCommand()))
949 return ui::DragDropTypes::DRAG_NONE
;
951 int result
= bookmark_menu_delegate_
->OnPerformDrop(menu
, position
, event
);
955 bool WrenchMenu::ShowContextMenu(MenuItemView
* source
,
958 ui::MenuSourceType source_type
) {
959 return IsBookmarkCommand(command_id
) ?
960 bookmark_menu_delegate_
->ShowContextMenu(source
, command_id
, p
,
965 bool WrenchMenu::CanDrag(MenuItemView
* menu
) {
966 return IsBookmarkCommand(menu
->GetCommand()) ?
967 bookmark_menu_delegate_
->CanDrag(menu
) : false;
970 void WrenchMenu::WriteDragData(MenuItemView
* sender
,
971 ui::OSExchangeData
* data
) {
972 DCHECK(IsBookmarkCommand(sender
->GetCommand()));
973 return bookmark_menu_delegate_
->WriteDragData(sender
, data
);
976 int WrenchMenu::GetDragOperations(MenuItemView
* sender
) {
977 return IsBookmarkCommand(sender
->GetCommand()) ?
978 bookmark_menu_delegate_
->GetDragOperations(sender
) :
979 MenuDelegate::GetDragOperations(sender
);
982 int WrenchMenu::GetMaxWidthForMenu(MenuItemView
* menu
) {
983 if (IsBookmarkCommand(menu
->GetCommand()))
984 return bookmark_menu_delegate_
->GetMaxWidthForMenu(menu
);
985 return MenuDelegate::GetMaxWidthForMenu(menu
);
988 bool WrenchMenu::IsItemChecked(int command_id
) const {
989 if (IsBookmarkCommand(command_id
))
992 const Entry
& entry
= command_id_to_entry_
.find(command_id
)->second
;
993 return entry
.first
->IsItemCheckedAt(entry
.second
);
996 bool WrenchMenu::IsCommandEnabled(int command_id
) const {
997 if (IsBookmarkCommand(command_id
))
1000 if (command_id
== 0)
1001 return false; // The root item.
1003 if (command_id
== IDC_MORE_TOOLS_MENU
)
1006 // The items representing the cut menu (cut/copy/paste), zoom menu
1007 // (increment/decrement/reset) and extension toolbar view are always enabled.
1008 // The child views of these items enabled state updates appropriately.
1009 if (command_id
== IDC_EDIT_MENU
|| command_id
== IDC_ZOOM_MENU
||
1010 command_id
== IDC_EXTENSIONS_OVERFLOW_MENU
)
1013 const Entry
& entry
= command_id_to_entry_
.find(command_id
)->second
;
1014 return entry
.first
->IsEnabledAt(entry
.second
);
1017 void WrenchMenu::ExecuteCommand(int command_id
, int mouse_event_flags
) {
1018 if (IsBookmarkCommand(command_id
)) {
1019 UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.OpenBookmark",
1020 menu_opened_timer_
.Elapsed());
1021 UMA_HISTOGRAM_ENUMERATION("WrenchMenu.MenuAction",
1022 MENU_ACTION_BOOKMARK_OPEN
, LIMIT_MENU_ACTION
);
1023 bookmark_menu_delegate_
->ExecuteCommand(command_id
, mouse_event_flags
);
1027 if (command_id
== IDC_EDIT_MENU
|| command_id
== IDC_ZOOM_MENU
||
1028 command_id
== IDC_EXTENSIONS_OVERFLOW_MENU
) {
1029 // These items are represented by child views. If ExecuteCommand is invoked
1030 // it means the user clicked on the area around the buttons and we should
1035 const Entry
& entry
= command_id_to_entry_
.find(command_id
)->second
;
1036 return entry
.first
->ActivatedAt(entry
.second
, mouse_event_flags
);
1039 bool WrenchMenu::GetAccelerator(int command_id
,
1040 ui::Accelerator
* accelerator
) const {
1041 if (IsBookmarkCommand(command_id
))
1044 if (command_id
== IDC_EDIT_MENU
|| command_id
== IDC_ZOOM_MENU
||
1045 command_id
== IDC_EXTENSIONS_OVERFLOW_MENU
) {
1046 // These have special child views; don't show the accelerator for them.
1050 CommandIDToEntry::const_iterator ix
= command_id_to_entry_
.find(command_id
);
1051 const Entry
& entry
= ix
->second
;
1052 ui::Accelerator menu_accelerator
;
1053 if (!entry
.first
->GetAcceleratorAt(entry
.second
, &menu_accelerator
))
1056 *accelerator
= ui::Accelerator(menu_accelerator
.key_code(),
1057 menu_accelerator
.modifiers());
1061 void WrenchMenu::WillShowMenu(MenuItemView
* menu
) {
1062 if (menu
== bookmark_menu_
)
1063 CreateBookmarkMenu();
1064 else if (bookmark_menu_delegate_
)
1065 bookmark_menu_delegate_
->WillShowMenu(menu
);
1068 void WrenchMenu::WillHideMenu(MenuItemView
* menu
) {
1069 // Turns off the fade out animation of the wrench menus if
1070 // |feedback_menu_item_| or |screenshot_menu_item_| is selected. This
1071 // excludes the wrench menu itself from the screenshot.
1072 if (menu
->HasSubmenu() &&
1073 ((feedback_menu_item_
&& feedback_menu_item_
->IsSelected()) ||
1074 (screenshot_menu_item_
&& screenshot_menu_item_
->IsSelected()))) {
1075 // It's okay to just turn off the animation and not turn it back on because
1076 // the menu widget will be recreated next time it's opened. See
1077 // ToolbarView::RunMenu() and Init() of this class.
1078 menu
->GetSubmenu()->GetWidget()->
1079 SetVisibilityChangedAnimationsEnabled(false);
1083 bool WrenchMenu::ShouldCloseOnDragComplete() {
1087 void WrenchMenu::BookmarkModelChanged() {
1088 DCHECK(bookmark_menu_delegate_
.get());
1089 if (!bookmark_menu_delegate_
->is_mutating_model())
1093 void WrenchMenu::Observe(int type
,
1094 const content::NotificationSource
& source
,
1095 const content::NotificationDetails
& details
) {
1097 case chrome::NOTIFICATION_GLOBAL_ERRORS_CHANGED
:
1098 // A change in the global errors list can add or remove items from the
1099 // menu. Close the menu to avoid have a stale menu on-screen.
1108 void WrenchMenu::PopulateMenu(MenuItemView
* parent
,
1110 for (int i
= 0, max
= model
->GetItemCount(); i
< max
; ++i
) {
1111 // Add the menu item at the end.
1112 int menu_index
= parent
->HasSubmenu() ?
1113 parent
->GetSubmenu()->child_count() : 0;
1114 MenuItemView
* item
=
1115 AddMenuItem(parent
, menu_index
, model
, i
, model
->GetTypeAt(i
));
1117 if (model
->GetCommandIdAt(i
) == IDC_EDIT_MENU
||
1118 model
->GetCommandIdAt(i
) == IDC_ZOOM_MENU
) {
1119 const MenuConfig
& config
= item
->GetMenuConfig();
1120 int top_margin
= config
.item_top_margin
+ config
.separator_height
/ 2;
1122 config
.item_bottom_margin
+ config
.separator_height
/ 2;
1124 // Chromeos adds extra vertical space for the menu buttons.
1125 #if defined(OS_CHROMEOS)
1130 item
->SetMargins(top_margin
, bottom_margin
);
1133 if (model
->GetTypeAt(i
) == MenuModel::TYPE_SUBMENU
)
1134 PopulateMenu(item
, model
->GetSubmenuModelAt(i
));
1136 switch (model
->GetCommandIdAt(i
)) {
1137 case IDC_EXTENSIONS_OVERFLOW_MENU
: {
1138 scoped_ptr
<ExtensionToolbarMenuView
> extension_toolbar(
1139 new ExtensionToolbarMenuView(browser_
, this));
1140 extension_toolbar_
= extension_toolbar
.get();
1141 if (extension_toolbar
->ShouldShow())
1142 item
->AddChildView(extension_toolbar
.release());
1144 item
->SetVisible(false);
1148 case IDC_EDIT_MENU
: {
1149 ui::ButtonMenuItemModel
* submodel
= model
->GetButtonMenuItemAt(i
);
1150 DCHECK_EQ(IDC_CUT
, submodel
->GetCommandIdAt(0));
1151 DCHECK_EQ(IDC_COPY
, submodel
->GetCommandIdAt(1));
1152 DCHECK_EQ(IDC_PASTE
, submodel
->GetCommandIdAt(2));
1153 item
->SetTitle(l10n_util::GetStringUTF16(IDS_EDIT2
));
1154 item
->AddChildView(new CutCopyPasteView(this, submodel
, 0, 1, 2));
1158 case IDC_ZOOM_MENU
: {
1159 ui::ButtonMenuItemModel
* submodel
= model
->GetButtonMenuItemAt(i
);
1160 DCHECK_EQ(IDC_ZOOM_MINUS
, submodel
->GetCommandIdAt(0));
1161 DCHECK_EQ(IDC_ZOOM_PLUS
, submodel
->GetCommandIdAt(1));
1162 DCHECK_EQ(IDC_FULLSCREEN
, submodel
->GetCommandIdAt(2));
1163 item
->SetTitle(l10n_util::GetStringUTF16(IDS_ZOOM_MENU2
));
1164 item
->AddChildView(new ZoomView(this, submodel
, 0, 1, 2));
1168 case IDC_BOOKMARKS_MENU
:
1169 DCHECK(!bookmark_menu_
);
1170 bookmark_menu_
= item
;
1173 #if defined(GOOGLE_CHROME_BUILD)
1175 DCHECK(!feedback_menu_item_
);
1176 feedback_menu_item_
= item
;
1180 #if defined(OS_CHROMEOS)
1181 case IDC_TAKE_SCREENSHOT
:
1182 DCHECK(!screenshot_menu_item_
);
1183 screenshot_menu_item_
= item
;
1187 case IDC_RECENT_TABS_MENU
:
1188 DCHECK(!recent_tabs_menu_model_delegate_
.get());
1189 recent_tabs_menu_model_delegate_
.reset(
1190 new RecentTabsMenuModelDelegate(this, model
->GetSubmenuModelAt(i
),
1200 MenuItemView
* WrenchMenu::AddMenuItem(MenuItemView
* parent
,
1204 MenuModel::ItemType menu_type
) {
1205 int command_id
= model
->GetCommandIdAt(model_index
);
1206 DCHECK(command_id
> -1 ||
1207 (command_id
== -1 &&
1208 model
->GetTypeAt(model_index
) == MenuModel::TYPE_SEPARATOR
));
1209 DCHECK_LT(command_id
, IDC_FIRST_BOOKMARK_MENU
);
1211 if (command_id
> -1) { // Don't add separators to |command_id_to_entry_|.
1212 // All command ID's should be unique except for IDC_SHOW_HISTORY which is
1213 // in both wrench menu and RecentTabs submenu,
1214 if (command_id
!= IDC_SHOW_HISTORY
) {
1215 DCHECK(command_id_to_entry_
.find(command_id
) ==
1216 command_id_to_entry_
.end())
1217 << "command ID " << command_id
<< " already exists!";
1219 command_id_to_entry_
[command_id
].first
= model
;
1220 command_id_to_entry_
[command_id
].second
= model_index
;
1223 MenuItemView
* menu_item
= views::MenuModelAdapter::AddMenuItemFromModelAt(
1224 model
, model_index
, parent
, menu_index
, command_id
);
1227 // Flush all buttons to the right side of the menu for the new menu type.
1228 menu_item
->set_use_right_margin(false);
1229 menu_item
->SetVisible(model
->IsVisibleAt(model_index
));
1231 if (menu_type
== MenuModel::TYPE_COMMAND
&& model
->HasIcons()) {
1233 if (model
->GetIconAt(model_index
, &icon
))
1234 menu_item
->SetIcon(*icon
.ToImageSkia());
1241 void WrenchMenu::CancelAndEvaluate(ButtonMenuItemModel
* model
, int index
) {
1242 selected_menu_model_
= model
;
1243 selected_index_
= index
;
1247 void WrenchMenu::CreateBookmarkMenu() {
1248 if (bookmark_menu_delegate_
.get())
1249 return; // Already created the menu.
1251 BookmarkModel
* model
=
1252 BookmarkModelFactory::GetForProfile(browser_
->profile());
1253 if (!model
->loaded())
1256 model
->AddObserver(this);
1258 // TODO(oshima): Replace with views only API.
1259 views::Widget
* parent
= views::Widget::GetWidgetForNativeWindow(
1260 browser_
->window()->GetNativeWindow());
1261 bookmark_menu_delegate_
.reset(
1262 new BookmarkMenuDelegate(browser_
, browser_
, parent
));
1263 bookmark_menu_delegate_
->Init(this,
1265 model
->bookmark_bar_node(),
1267 BookmarkMenuDelegate::SHOW_PERMANENT_FOLDERS
,
1268 BOOKMARK_LAUNCH_LOCATION_WRENCH_MENU
);
1271 int WrenchMenu::ModelIndexFromCommandId(int command_id
) const {
1272 CommandIDToEntry::const_iterator ix
= command_id_to_entry_
.find(command_id
);
1273 DCHECK(ix
!= command_id_to_entry_
.end());
1274 return ix
->second
.second
;