wrench_menu.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
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.
4
5#include "chrome/browser/ui/views/toolbar/wrench_menu.h"
6
7#include <algorithm>
8#include <cmath>
9#include <set>
10
11#include "base/strings/string_number_conversions.h"
12#include "base/strings/utf_string_conversions.h"
13#include "chrome/app/chrome_command_ids.h"
14#include "chrome/browser/bookmarks/bookmark_model_factory.h"
15#include "chrome/browser/bookmarks/bookmark_stats.h"
16#include "chrome/browser/chrome_notification_types.h"
17#include "chrome/browser/profiles/profile.h"
18#include "chrome/browser/search/search.h"
19#include "chrome/browser/ui/browser.h"
20#include "chrome/browser/ui/browser_window.h"
21#include "chrome/browser/ui/tabs/tab_strip_model.h"
22#include "chrome/browser/ui/toolbar/wrench_menu_model.h"
23#include "chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.h"
24#include "chrome/browser/ui/views/toolbar/wrench_menu_observer.h"
25#include "components/bookmarks/core/browser/bookmark_model.h"
26#include "content/public/browser/host_zoom_map.h"
27#include "content/public/browser/notification_observer.h"
28#include "content/public/browser/notification_registrar.h"
29#include "content/public/browser/notification_source.h"
30#include "content/public/browser/notification_types.h"
31#include "content/public/browser/user_metrics.h"
32#include "content/public/browser/web_contents.h"
33#include "grit/chromium_strings.h"
34#include "grit/generated_resources.h"
35#include "grit/theme_resources.h"
36#include "third_party/skia/include/core/SkCanvas.h"
37#include "third_party/skia/include/core/SkPaint.h"
38#include "ui/base/l10n/l10n_util.h"
39#include "ui/base/layout.h"
40#include "ui/base/resource/resource_bundle.h"
41#include "ui/gfx/canvas.h"
42#include "ui/gfx/font_list.h"
43#include "ui/gfx/image/image.h"
44#include "ui/gfx/image/image_skia_source.h"
45#include "ui/gfx/skia_util.h"
46#include "ui/gfx/text_utils.h"
47#include "ui/views/background.h"
48#include "ui/views/controls/button/image_button.h"
49#include "ui/views/controls/button/label_button.h"
50#include "ui/views/controls/button/menu_button.h"
51#include "ui/views/controls/label.h"
52#include "ui/views/controls/menu/menu_config.h"
53#include "ui/views/controls/menu/menu_item_view.h"
54#include "ui/views/controls/menu/menu_model_adapter.h"
55#include "ui/views/controls/menu/menu_runner.h"
56#include "ui/views/controls/menu/menu_scroll_view_container.h"
57#include "ui/views/controls/menu/submenu_view.h"
58#include "ui/views/widget/widget.h"
59
60using base::UserMetricsAction;
61using content::HostZoomMap;
62using content::WebContents;
63using ui::MenuModel;
64using views::CustomButton;
65using views::ImageButton;
66using views::Label;
67using views::LabelButton;
68using views::MenuConfig;
69using views::MenuItemView;
70using views::View;
71
72namespace {
73
74// Horizontal padding on the edges of the buttons.
75const int kHorizontalPadding = 6;
76// Horizontal padding for a touch enabled menu.
77const int kHorizontalTouchPadding = 15;
78
79// Menu items which have embedded buttons should have this height in pixel.
80const int kMenuItemContainingButtonsHeight = 43;
81
82// Returns true if |command_id| identifies a bookmark menu item.
83bool IsBookmarkCommand(int command_id) {
84  return command_id >= WrenchMenuModel::kMinBookmarkCommandId &&
85      command_id <= WrenchMenuModel::kMaxBookmarkCommandId;
86}
87
88// Returns true if |command_id| identifies a recent tabs menu item.
89bool IsRecentTabsCommand(int command_id) {
90  return command_id >= WrenchMenuModel::kMinRecentTabsCommandId &&
91      command_id <= WrenchMenuModel::kMaxRecentTabsCommandId;
92}
93
94// Subclass of ImageButton whose preferred size includes the size of the border.
95class FullscreenButton : public ImageButton {
96 public:
97  explicit FullscreenButton(views::ButtonListener* listener)
98      : ImageButton(listener) { }
99
100  // Overridden from ImageButton.
101  virtual gfx::Size GetPreferredSize() OVERRIDE {
102    gfx::Size pref = ImageButton::GetPreferredSize();
103    if (border()) {
104      gfx::Insets insets = border()->GetInsets();
105      pref.Enlarge(insets.width(), insets.height());
106    }
107    return pref;
108  }
109
110 private:
111  DISALLOW_COPY_AND_ASSIGN(FullscreenButton);
112};
113
114// Border for buttons contained in the menu. This is only used for getting the
115// insets, the actual painting is done in MenuButtonBackground.
116class MenuButtonBorder : public views::Border {
117 public:
118  MenuButtonBorder(const MenuConfig& config, bool use_new_menu)
119      : horizontal_padding_(use_new_menu ?
120                            kHorizontalTouchPadding : kHorizontalPadding),
121        insets_(config.item_top_margin, horizontal_padding_,
122                config.item_bottom_margin, horizontal_padding_) {
123  }
124
125  // Overridden from views::Border.
126  virtual void Paint(const View& view, gfx::Canvas* canvas) OVERRIDE {
127    // Painting of border is done in MenuButtonBackground.
128  }
129
130  virtual gfx::Insets GetInsets() const OVERRIDE {
131    return insets_;
132  }
133
134  virtual gfx::Size GetMinimumSize() const OVERRIDE {
135    // This size is sufficient for MenuButtonBackground::Paint() to draw any of
136    // the button types.
137    return gfx::Size(4, 4);
138  }
139
140 private:
141  // The horizontal padding dependent on the layout.
142  const int horizontal_padding_;
143
144  const gfx::Insets insets_;
145
146  DISALLOW_COPY_AND_ASSIGN(MenuButtonBorder);
147};
148
149// Combination border/background for the buttons contained in the menu. The
150// painting of the border/background is done here as TextButton does not always
151// paint the border.
152class MenuButtonBackground : public views::Background {
153 public:
154  enum ButtonType {
155    LEFT_BUTTON,
156    CENTER_BUTTON,
157    RIGHT_BUTTON,
158    SINGLE_BUTTON,
159  };
160
161  MenuButtonBackground(ButtonType type, bool use_new_menu)
162      : type_(type),
163        use_new_menu_(use_new_menu),
164        left_button_(NULL),
165        right_button_(NULL) {}
166
167  // Used when the type is CENTER_BUTTON to determine if the left/right edge
168  // needs to be rendered selected.
169  void SetOtherButtons(CustomButton* left_button, CustomButton* right_button) {
170    if (base::i18n::IsRTL()) {
171      left_button_ = right_button;
172      right_button_ = left_button;
173    } else {
174      left_button_ = left_button;
175      right_button_ = right_button;
176    }
177  }
178
179  // Overridden from views::Background.
180  virtual void Paint(gfx::Canvas* canvas, View* view) const OVERRIDE {
181    CustomButton* button = CustomButton::AsCustomButton(view);
182    views::Button::ButtonState state =
183        button ? button->state() : views::Button::STATE_NORMAL;
184    int w = view->width();
185    int h = view->height();
186    if (use_new_menu_) {
187      // Normal buttons get a border drawn on the right side and the rest gets
188      // filled in. The left button however does not get a line to combine
189      // buttons.
190      if (type_ != RIGHT_BUTTON) {
191        canvas->FillRect(gfx::Rect(0, 0, 1, h),
192                         BorderColor(view, views::Button::STATE_NORMAL));
193      }
194      gfx::Rect bounds(view->GetLocalBounds());
195      bounds.set_x(view->GetMirroredXForRect(bounds));
196      DrawBackground(canvas, view, bounds, state);
197      return;
198    }
199    const SkColor border_color = BorderColor(view, state);
200    switch (TypeAdjustedForRTL()) {
201      // TODO(pkasting): Why don't all the following use SkPaths with rounded
202      // corners?
203      case LEFT_BUTTON:
204        DrawBackground(canvas, view, gfx::Rect(1, 1, w, h - 2), state);
205        canvas->FillRect(gfx::Rect(2, 0, w, 1), border_color);
206        canvas->FillRect(gfx::Rect(1, 1, 1, 1), border_color);
207        canvas->FillRect(gfx::Rect(0, 2, 1, h - 4), border_color);
208        canvas->FillRect(gfx::Rect(1, h - 2, 1, 1), border_color);
209        canvas->FillRect(gfx::Rect(2, h - 1, w, 1), border_color);
210        break;
211
212      case CENTER_BUTTON: {
213        DrawBackground(canvas, view, gfx::Rect(1, 1, w - 2, h - 2), state);
214        SkColor left_color = state != views::Button::STATE_NORMAL ?
215            border_color : BorderColor(view, left_button_->state());
216        canvas->FillRect(gfx::Rect(0, 0, 1, h), left_color);
217        canvas->FillRect(gfx::Rect(1, 0, w - 2, 1), border_color);
218        canvas->FillRect(gfx::Rect(1, h - 1, w - 2, 1),
219                         border_color);
220        SkColor right_color = state != views::Button::STATE_NORMAL ?
221            border_color : BorderColor(view, right_button_->state());
222        canvas->FillRect(gfx::Rect(w - 1, 0, 1, h), right_color);
223        break;
224      }
225
226      case RIGHT_BUTTON:
227        DrawBackground(canvas, view, gfx::Rect(0, 1, w - 1, h - 2), state);
228        canvas->FillRect(gfx::Rect(0, 0, w - 2, 1), border_color);
229        canvas->FillRect(gfx::Rect(w - 2, 1, 1, 1), border_color);
230        canvas->FillRect(gfx::Rect(w - 1, 2, 1, h - 4), border_color);
231        canvas->FillRect(gfx::Rect(w - 2, h - 2, 1, 1), border_color);
232        canvas->FillRect(gfx::Rect(0, h - 1, w - 2, 1), border_color);
233        break;
234
235      case SINGLE_BUTTON:
236        DrawBackground(canvas, view, gfx::Rect(1, 1, w - 2, h - 2), state);
237        canvas->FillRect(gfx::Rect(2, 0, w - 4, 1), border_color);
238        canvas->FillRect(gfx::Rect(1, 1, 1, 1), border_color);
239        canvas->FillRect(gfx::Rect(0, 2, 1, h - 4), border_color);
240        canvas->FillRect(gfx::Rect(1, h - 2, 1, 1), border_color);
241        canvas->FillRect(gfx::Rect(2, h - 1, w - 4, 1), border_color);
242        canvas->FillRect(gfx::Rect(w - 2, 1, 1, 1), border_color);
243        canvas->FillRect(gfx::Rect(w - 1, 2, 1, h - 4), border_color);
244        canvas->FillRect(gfx::Rect(w - 2, h - 2, 1, 1), border_color);
245        break;
246
247      default:
248        NOTREACHED();
249        break;
250    }
251  }
252
253 private:
254  static SkColor BorderColor(View* view, views::Button::ButtonState state) {
255    ui::NativeTheme* theme = view->GetNativeTheme();
256    switch (state) {
257      case views::Button::STATE_HOVERED:
258        return theme->GetSystemColor(
259            ui::NativeTheme::kColorId_HoverMenuButtonBorderColor);
260      case views::Button::STATE_PRESSED:
261        return theme->GetSystemColor(
262            ui::NativeTheme::kColorId_FocusedMenuButtonBorderColor);
263      default:
264        return theme->GetSystemColor(
265            ui::NativeTheme::kColorId_EnabledMenuButtonBorderColor);
266    }
267  }
268
269  static SkColor BackgroundColor(const View* view,
270                                 views::Button::ButtonState state) {
271    const ui::NativeTheme* theme = view->GetNativeTheme();
272    switch (state) {
273      case views::Button::STATE_HOVERED:
274        // Hovered should be handled in DrawBackground.
275        NOTREACHED();
276        return theme->GetSystemColor(
277            ui::NativeTheme::kColorId_HoverMenuItemBackgroundColor);
278      case views::Button::STATE_PRESSED:
279        return theme->GetSystemColor(
280            ui::NativeTheme::kColorId_FocusedMenuItemBackgroundColor);
281      default:
282        return theme->GetSystemColor(
283            ui::NativeTheme::kColorId_MenuBackgroundColor);
284    }
285  }
286
287  void DrawBackground(gfx::Canvas* canvas,
288                      const views::View* view,
289                      const gfx::Rect& bounds,
290                      views::Button::ButtonState state) const {
291    if (state == views::Button::STATE_HOVERED ||
292        state == views::Button::STATE_PRESSED) {
293      view->GetNativeTheme()->Paint(canvas->sk_canvas(),
294                                    ui::NativeTheme::kMenuItemBackground,
295                                    ui::NativeTheme::kHovered,
296                                    bounds,
297                                    ui::NativeTheme::ExtraParams());
298      return;
299    }
300    if (use_new_menu_)
301      return;
302    canvas->FillRect(bounds, BackgroundColor(view, state));
303  }
304
305  ButtonType TypeAdjustedForRTL() const {
306    if (!base::i18n::IsRTL())
307      return type_;
308
309    switch (type_) {
310      case LEFT_BUTTON:   return RIGHT_BUTTON;
311      case RIGHT_BUTTON:  return LEFT_BUTTON;
312      default:            break;
313    }
314    return type_;
315  }
316
317  const ButtonType type_;
318  const bool use_new_menu_;
319
320  // See description above setter for details.
321  CustomButton* left_button_;
322  CustomButton* right_button_;
323
324  DISALLOW_COPY_AND_ASSIGN(MenuButtonBackground);
325};
326
327base::string16 GetAccessibleNameForWrenchMenuItem(
328      MenuModel* model, int item_index, int accessible_string_id) {
329  base::string16 accessible_name =
330      l10n_util::GetStringUTF16(accessible_string_id);
331  base::string16 accelerator_text;
332
333  ui::Accelerator menu_accelerator;
334  if (model->GetAcceleratorAt(item_index, &menu_accelerator)) {
335    accelerator_text =
336        ui::Accelerator(menu_accelerator.key_code(),
337                        menu_accelerator.modifiers()).GetShortcutText();
338  }
339
340  return MenuItemView::GetAccessibleNameForMenuItem(
341      accessible_name, accelerator_text);
342}
343
344// WrenchMenuView is a view that can contain label buttons.
345class WrenchMenuView : public views::View,
346                       public views::ButtonListener,
347                       public WrenchMenuObserver {
348 public:
349  WrenchMenuView(WrenchMenu* menu, MenuModel* menu_model)
350      : menu_(menu),
351        menu_model_(menu_model) {
352    menu_->AddObserver(this);
353  }
354
355  virtual ~WrenchMenuView() {
356    if (menu_)
357      menu_->RemoveObserver(this);
358  }
359
360  // Overridden from views::View.
361  virtual void SchedulePaintInRect(const gfx::Rect& r) OVERRIDE {
362    // Normally when the mouse enters/exits a button the buttons invokes
363    // SchedulePaint. As part of the button border (MenuButtonBackground) is
364    // rendered by the button to the left/right of it SchedulePaint on the the
365    // button may not be enough, so this forces a paint all.
366    View::SchedulePaintInRect(gfx::Rect(size()));
367  }
368
369  LabelButton* CreateAndConfigureButton(const ui::NativeTheme* native_theme,
370                                        int string_id,
371                                        MenuButtonBackground::ButtonType type,
372                                        int index,
373                                        MenuButtonBackground** background) {
374    return CreateButtonWithAccName(
375        native_theme, string_id, type, index, background, string_id);
376  }
377
378  LabelButton* CreateButtonWithAccName(const ui::NativeTheme* native_theme,
379                                       int string_id,
380                                       MenuButtonBackground::ButtonType type,
381                                       int index,
382                                       MenuButtonBackground** background,
383                                       int acc_string_id) {
384    // Should only be invoked during construction when |menu_| is valid.
385    DCHECK(menu_);
386    LabelButton* button = new LabelButton(this, gfx::RemoveAcceleratorChar(
387        l10n_util::GetStringUTF16(string_id), '&', NULL, NULL));
388    button->SetAccessibleName(
389        GetAccessibleNameForWrenchMenuItem(menu_model_, index, acc_string_id));
390    button->SetFocusable(true);
391    button->set_request_focus_on_press(false);
392    button->set_tag(index);
393    button->SetEnabled(menu_model_->IsEnabledAt(index));
394    MenuButtonBackground* bg =
395        new MenuButtonBackground(type, menu_->use_new_menu());
396    button->set_background(bg);
397    const MenuConfig& menu_config = menu_->GetMenuConfig();
398    if (background)
399      *background = bg;
400    button->SetBorder(scoped_ptr<views::Border>(
401        new MenuButtonBorder(menu_config, menu_->use_new_menu())));
402    button->SetHorizontalAlignment(gfx::ALIGN_CENTER);
403    button->SetFontList(menu_config.font_list);
404    button->SetTextColor(
405        views::Button::STATE_DISABLED,
406        native_theme->GetSystemColor(
407            ui::NativeTheme::kColorId_DisabledMenuItemForegroundColor));
408    button->SetTextColor(
409        views::Button::STATE_HOVERED,
410        native_theme->GetSystemColor(
411            ui::NativeTheme::kColorId_SelectedMenuItemForegroundColor));
412    button->SetTextColor(
413        views::Button::STATE_PRESSED,
414        native_theme->GetSystemColor(
415            ui::NativeTheme::kColorId_SelectedMenuItemForegroundColor));
416    button->SetTextColor(
417        views::Button::STATE_NORMAL,
418        native_theme->GetSystemColor(
419            ui::NativeTheme::kColorId_EnabledMenuItemForegroundColor));
420    AddChildView(button);
421    // all buttons on menu should must be a custom button in order for
422    // the keyboard nativigation work.
423    DCHECK(CustomButton::AsCustomButton(button));
424    return button;
425  }
426
427  // Overridden from WrenchMenuObserver:
428  virtual void WrenchMenuDestroyed() OVERRIDE {
429    menu_->RemoveObserver(this);
430    menu_ = NULL;
431    menu_model_ = NULL;
432  }
433
434 protected:
435  WrenchMenu* menu() { return menu_; }
436  MenuModel* menu_model() { return menu_model_; }
437
438 private:
439  // Hosting WrenchMenu.
440  // WARNING: this may be NULL during shutdown.
441  WrenchMenu* menu_;
442
443  // The menu model containing the increment/decrement/reset items.
444  // WARNING: this may be NULL during shutdown.
445  MenuModel* menu_model_;
446
447  DISALLOW_COPY_AND_ASSIGN(WrenchMenuView);
448};
449
450class ButtonContainerMenuItemView : public MenuItemView {
451 public:
452  // Constructor for use with button containing menu items which have a
453  // different height then normal items.
454  ButtonContainerMenuItemView(MenuItemView* parent, int command_id, int height)
455      : MenuItemView(parent, command_id, MenuItemView::NORMAL),
456        height_(height) {
457  };
458
459  // Overridden from MenuItemView.
460  virtual gfx::Size GetChildPreferredSize() OVERRIDE {
461    gfx::Size size = MenuItemView::GetChildPreferredSize();
462    // When there is a height override given, we need to deduct our spacing
463    // above and below to get to the correct height to return here for the
464    // child item.
465    int height = height_ - GetTopMargin() - GetBottomMargin();
466    if (height > size.height())
467      size.set_height(height);
468    return size;
469  }
470
471 private:
472  int height_;
473
474  DISALLOW_COPY_AND_ASSIGN(ButtonContainerMenuItemView);
475};
476
477// Generate the button image for hover state.
478class HoveredImageSource : public gfx::ImageSkiaSource {
479 public:
480  HoveredImageSource(const gfx::ImageSkia& image, SkColor color)
481      : image_(image),
482        color_(color) {
483  }
484  virtual ~HoveredImageSource() {}
485
486  virtual gfx::ImageSkiaRep GetImageForScale(float scale) OVERRIDE {
487    const gfx::ImageSkiaRep& rep = image_.GetRepresentation(scale);
488    SkBitmap bitmap = rep.sk_bitmap();
489    SkBitmap white;
490    white.setConfig(SkBitmap::kARGB_8888_Config,
491                    bitmap.width(), bitmap.height(), 0);
492    white.allocPixels();
493    white.eraseARGB(0, 0, 0, 0);
494    bitmap.lockPixels();
495    for (int y = 0; y < bitmap.height(); ++y) {
496      uint32* image_row = bitmap.getAddr32(0, y);
497      uint32* dst_row = white.getAddr32(0, y);
498      for (int x = 0; x < bitmap.width(); ++x) {
499        uint32 image_pixel = image_row[x];
500        // Fill the non transparent pixels with |color_|.
501        dst_row[x] = (image_pixel & 0xFF000000) == 0x0 ? 0x0 : color_;
502      }
503    }
504    bitmap.unlockPixels();
505    return gfx::ImageSkiaRep(white, scale);
506  }
507
508 private:
509  const gfx::ImageSkia image_;
510  const SkColor color_;
511  DISALLOW_COPY_AND_ASSIGN(HoveredImageSource);
512};
513
514}  // namespace
515
516// CutCopyPasteView ------------------------------------------------------------
517
518// CutCopyPasteView is the view containing the cut/copy/paste buttons.
519class WrenchMenu::CutCopyPasteView : public WrenchMenuView {
520 public:
521  CutCopyPasteView(WrenchMenu* menu,
522                   MenuModel* menu_model,
523                   const ui::NativeTheme* native_theme,
524                   int cut_index,
525                   int copy_index,
526                   int paste_index)
527      : WrenchMenuView(menu, menu_model) {
528    LabelButton* cut = CreateAndConfigureButton(
529        native_theme, IDS_CUT, MenuButtonBackground::LEFT_BUTTON,
530        cut_index, NULL);
531    MenuButtonBackground* copy_background = NULL;
532    CreateAndConfigureButton(
533        native_theme, IDS_COPY, MenuButtonBackground::CENTER_BUTTON,
534        copy_index, &copy_background);
535    LabelButton* paste = CreateAndConfigureButton(
536        native_theme,
537        IDS_PASTE,
538        menu->use_new_menu() && menu->supports_new_separators_ ?
539            MenuButtonBackground::CENTER_BUTTON :
540            MenuButtonBackground::RIGHT_BUTTON,
541        paste_index,
542        NULL);
543    copy_background->SetOtherButtons(cut, paste);
544  }
545
546  // Overridden from View.
547  virtual gfx::Size GetPreferredSize() OVERRIDE {
548    // Returned height doesn't matter as MenuItemView forces everything to the
549    // height of the menuitemview.
550    return gfx::Size(GetMaxChildViewPreferredWidth() * child_count(), 0);
551  }
552
553  virtual void Layout() OVERRIDE {
554    // All buttons are given the same width.
555    int width = GetMaxChildViewPreferredWidth();
556    for (int i = 0; i < child_count(); ++i)
557      child_at(i)->SetBounds(i * width, 0, width, height());
558  }
559
560  // Overridden from ButtonListener.
561  virtual void ButtonPressed(views::Button* sender,
562                             const ui::Event& event) OVERRIDE {
563    menu()->CancelAndEvaluate(menu_model(), sender->tag());
564  }
565
566 private:
567  // Returns the max preferred width of all the children.
568  int GetMaxChildViewPreferredWidth() {
569    int width = 0;
570    for (int i = 0; i < child_count(); ++i)
571      width = std::max(width, child_at(i)->GetPreferredSize().width());
572    return width;
573  }
574
575  DISALLOW_COPY_AND_ASSIGN(CutCopyPasteView);
576};
577
578// ZoomView --------------------------------------------------------------------
579
580// Padding between the increment buttons and the reset button.
581static const int kZoomPadding = 6;
582static const int kTouchZoomPadding = 14;
583
584// ZoomView contains the various zoom controls: two buttons to increase/decrease
585// the zoom, a label showing the current zoom percent, and a button to go
586// full-screen.
587class WrenchMenu::ZoomView : public WrenchMenuView {
588 public:
589  ZoomView(WrenchMenu* menu,
590           MenuModel* menu_model,
591           const ui::NativeTheme* native_theme,
592           int decrement_index,
593           int increment_index,
594           int fullscreen_index)
595      : WrenchMenuView(menu, menu_model),
596        fullscreen_index_(fullscreen_index),
597        increment_button_(NULL),
598        zoom_label_(NULL),
599        decrement_button_(NULL),
600        fullscreen_button_(NULL),
601        zoom_label_width_(0) {
602    zoom_subscription_ = HostZoomMap::GetForBrowserContext(
603        menu->browser_->profile())->AddZoomLevelChangedCallback(
604            base::Bind(&WrenchMenu::ZoomView::OnZoomLevelChanged,
605                       base::Unretained(this)));
606
607    decrement_button_ = CreateButtonWithAccName(
608        native_theme, IDS_ZOOM_MINUS2, MenuButtonBackground::LEFT_BUTTON,
609        decrement_index, NULL, IDS_ACCNAME_ZOOM_MINUS2);
610
611    zoom_label_ = new Label(
612        l10n_util::GetStringFUTF16Int(IDS_ZOOM_PERCENT, 100));
613    zoom_label_->SetAutoColorReadabilityEnabled(false);
614    zoom_label_->SetHorizontalAlignment(gfx::ALIGN_RIGHT);
615
616    MenuButtonBackground* center_bg = new MenuButtonBackground(
617        menu->use_new_menu() && menu->supports_new_separators_ ?
618            MenuButtonBackground::RIGHT_BUTTON :
619            MenuButtonBackground::CENTER_BUTTON,
620        menu->use_new_menu());
621    zoom_label_->set_background(center_bg);
622    const MenuConfig& menu_config(menu->GetMenuConfig());
623    zoom_label_->SetBorder(scoped_ptr<views::Border>(
624        new MenuButtonBorder(menu_config, menu->use_new_menu())));
625    zoom_label_->SetFontList(menu_config.font_list);
626
627    AddChildView(zoom_label_);
628    zoom_label_width_ = MaxWidthForZoomLabel();
629
630    increment_button_ = CreateButtonWithAccName(
631        native_theme, IDS_ZOOM_PLUS2, MenuButtonBackground::RIGHT_BUTTON,
632        increment_index, NULL, IDS_ACCNAME_ZOOM_PLUS2);
633
634    center_bg->SetOtherButtons(decrement_button_, increment_button_);
635
636    fullscreen_button_ = new FullscreenButton(this);
637    // all buttons on menu should must be a custom button in order for
638    // the keyboard nativigation work.
639    DCHECK(CustomButton::AsCustomButton(fullscreen_button_));
640    gfx::ImageSkia* full_screen_image =
641        ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
642            IDR_FULLSCREEN_MENU_BUTTON);
643    fullscreen_button_->SetImage(ImageButton::STATE_NORMAL, full_screen_image);
644    SkColor fg_color = native_theme->GetSystemColor(
645        ui::NativeTheme::kColorId_SelectedMenuItemForegroundColor);
646    gfx::ImageSkia hovered_fullscreen_image(
647        new HoveredImageSource(*full_screen_image, fg_color),
648        full_screen_image->size());
649    fullscreen_button_->SetImage(
650        ImageButton::STATE_HOVERED, &hovered_fullscreen_image);
651    fullscreen_button_->SetImage(
652        ImageButton::STATE_PRESSED, &hovered_fullscreen_image);
653
654    SkColor enabled_text_color = native_theme->GetSystemColor(
655        ui::NativeTheme::kColorId_EnabledMenuItemForegroundColor);
656    zoom_label_->SetEnabledColor(enabled_text_color);
657    decrement_button_->SetTextColor(views::Button::STATE_NORMAL,
658                                    enabled_text_color);
659    increment_button_->SetTextColor(views::Button::STATE_NORMAL,
660                                    enabled_text_color);
661    SkColor disabled_text_color = native_theme->GetSystemColor(
662        ui::NativeTheme::kColorId_DisabledMenuItemForegroundColor);
663    decrement_button_->SetTextColor(views::Button::STATE_DISABLED,
664                                    disabled_text_color);
665    increment_button_->SetTextColor(views::Button::STATE_DISABLED,
666                                    disabled_text_color);
667    fullscreen_button_->SetFocusable(true);
668    fullscreen_button_->set_request_focus_on_press(false);
669    fullscreen_button_->set_tag(fullscreen_index);
670    fullscreen_button_->SetImageAlignment(
671        ImageButton::ALIGN_CENTER, ImageButton::ALIGN_MIDDLE);
672    int horizontal_padding =
673        menu->use_new_menu() ? kHorizontalTouchPadding : kHorizontalPadding;
674    fullscreen_button_->SetBorder(views::Border::CreateEmptyBorder(
675        0, horizontal_padding, 0, horizontal_padding));
676    fullscreen_button_->set_background(
677        new MenuButtonBackground(MenuButtonBackground::SINGLE_BUTTON,
678                                 menu->use_new_menu()));
679    fullscreen_button_->SetAccessibleName(
680        GetAccessibleNameForWrenchMenuItem(
681            menu_model, fullscreen_index, IDS_ACCNAME_FULLSCREEN));
682    AddChildView(fullscreen_button_);
683
684    UpdateZoomControls();
685  }
686
687  virtual ~ZoomView() {}
688
689  // Overridden from View.
690  virtual gfx::Size GetPreferredSize() OVERRIDE {
691    // The increment/decrement button are forced to the same width.
692    int button_width = std::max(increment_button_->GetPreferredSize().width(),
693                                decrement_button_->GetPreferredSize().width());
694    int zoom_padding = menu()->use_new_menu() ?
695        kTouchZoomPadding : kZoomPadding;
696    int fullscreen_width = fullscreen_button_->GetPreferredSize().width() +
697                           zoom_padding;
698    // Returned height doesn't matter as MenuItemView forces everything to the
699    // height of the menuitemview. Note that we have overridden the height when
700    // constructing the menu.
701    return gfx::Size(button_width + zoom_label_width_ + button_width +
702                     fullscreen_width, 0);
703  }
704
705  virtual void Layout() OVERRIDE {
706    int x = 0;
707    int button_width = std::max(increment_button_->GetPreferredSize().width(),
708                                decrement_button_->GetPreferredSize().width());
709    gfx::Rect bounds(0, 0, button_width, height());
710
711    decrement_button_->SetBoundsRect(bounds);
712
713    x += bounds.width();
714    bounds.set_x(x);
715    bounds.set_width(zoom_label_width_);
716    zoom_label_->SetBoundsRect(bounds);
717
718    x += bounds.width();
719    bounds.set_x(x);
720    bounds.set_width(button_width);
721    increment_button_->SetBoundsRect(bounds);
722
723    x += bounds.width() + (menu()->use_new_menu() ? 0 : kZoomPadding);
724    bounds.set_x(x);
725    bounds.set_width(fullscreen_button_->GetPreferredSize().width() +
726                     (menu()->use_new_menu() ? kTouchZoomPadding : 0));
727    fullscreen_button_->SetBoundsRect(bounds);
728  }
729
730  // Overridden from ButtonListener.
731  virtual void ButtonPressed(views::Button* sender,
732                             const ui::Event& event) OVERRIDE {
733    if (sender->tag() == fullscreen_index_) {
734      menu()->CancelAndEvaluate(menu_model(), sender->tag());
735    } else {
736      // Zoom buttons don't close the menu.
737      menu_model()->ActivatedAt(sender->tag());
738    }
739  }
740
741  // Overridden from WrenchMenuObserver.
742  virtual void WrenchMenuDestroyed() OVERRIDE {
743    WrenchMenuView::WrenchMenuDestroyed();
744  }
745
746 private:
747  void OnZoomLevelChanged(const HostZoomMap::ZoomLevelChange& change) {
748    UpdateZoomControls();
749  }
750
751  void UpdateZoomControls() {
752    bool enable_increment = false;
753    bool enable_decrement = false;
754    WebContents* selected_tab =
755        menu()->browser_->tab_strip_model()->GetActiveWebContents();
756    int zoom = 100;
757    if (selected_tab)
758      zoom = selected_tab->GetZoomPercent(&enable_increment, &enable_decrement);
759    increment_button_->SetEnabled(enable_increment);
760    decrement_button_->SetEnabled(enable_decrement);
761    zoom_label_->SetText(
762        l10n_util::GetStringFUTF16Int(IDS_ZOOM_PERCENT, zoom));
763
764    zoom_label_width_ = MaxWidthForZoomLabel();
765  }
766
767  // Calculates the max width the zoom string can be.
768  int MaxWidthForZoomLabel() {
769    const gfx::FontList& font_list = zoom_label_->font_list();
770    int border_width =
771        zoom_label_->border() ? zoom_label_->border()->GetInsets().width() : 0;
772
773    int max_w = 0;
774
775    WebContents* selected_tab =
776        menu()->browser_->tab_strip_model()->GetActiveWebContents();
777    if (selected_tab) {
778      int min_percent = selected_tab->GetMinimumZoomPercent();
779      int max_percent = selected_tab->GetMaximumZoomPercent();
780
781      int step = (max_percent - min_percent) / 10;
782      for (int i = min_percent; i <= max_percent; i += step) {
783        int w = gfx::GetStringWidth(
784            l10n_util::GetStringFUTF16Int(IDS_ZOOM_PERCENT, i), font_list);
785        max_w = std::max(w, max_w);
786      }
787    } else {
788      max_w = gfx::GetStringWidth(
789          l10n_util::GetStringFUTF16Int(IDS_ZOOM_PERCENT, 100), font_list);
790    }
791
792    return max_w + border_width;
793  }
794
795  // Index of the fullscreen menu item in the model.
796  const int fullscreen_index_;
797
798  scoped_ptr<content::HostZoomMap::Subscription> zoom_subscription_;
799  content::NotificationRegistrar registrar_;
800
801  // Button for incrementing the zoom.
802  LabelButton* increment_button_;
803
804  // Label showing zoom as a percent.
805  Label* zoom_label_;
806
807  // Button for decrementing the zoom.
808  LabelButton* decrement_button_;
809
810  ImageButton* fullscreen_button_;
811
812  // Width given to |zoom_label_|. This is the width at 100%.
813  int zoom_label_width_;
814
815  DISALLOW_COPY_AND_ASSIGN(ZoomView);
816};
817
818// RecentTabsMenuModelDelegate  ------------------------------------------------
819
820// Provides the ui::MenuModelDelegate implementation for RecentTabsSubMenuModel
821// items.
822class WrenchMenu::RecentTabsMenuModelDelegate : public ui::MenuModelDelegate {
823 public:
824  RecentTabsMenuModelDelegate(WrenchMenu* wrench_menu,
825                              ui::MenuModel* model,
826                              views::MenuItemView* menu_item)
827      : wrench_menu_(wrench_menu),
828        model_(model),
829        menu_item_(menu_item) {
830    model_->SetMenuModelDelegate(this);
831  }
832
833  virtual ~RecentTabsMenuModelDelegate() {
834    model_->SetMenuModelDelegate(NULL);
835  }
836
837  // Return the specific menu width of recent tabs submenu if |menu| is the
838  // recent tabs submenu, else return -1.
839  int GetMaxWidthForMenu(views::MenuItemView* menu) {
840    if (!menu_item_->HasSubmenu())
841      return -1;
842    const int kMaxMenuItemWidth = 320;
843    return menu->GetCommand() == menu_item_->GetCommand() ?
844        kMaxMenuItemWidth : -1;
845  }
846
847  const gfx::FontList* GetLabelFontListAt(int index) const {
848    return model_->GetLabelFontListAt(index);
849  }
850
851  bool GetShouldUseDisabledEmphasizedForegroundColor(int index) const {
852    // The items for which we get a font list, should be shown in the bolded
853    // color.
854    return GetLabelFontListAt(index) ? true : false;
855  }
856
857  // ui::MenuModelDelegate implementation:
858
859  virtual void OnIconChanged(int index) OVERRIDE {
860    int command_id = model_->GetCommandIdAt(index);
861    views::MenuItemView* item = menu_item_->GetMenuItemByID(command_id);
862    DCHECK(item);
863    gfx::Image icon;
864    model_->GetIconAt(index, &icon);
865    item->SetIcon(*icon.ToImageSkia());
866  }
867
868  virtual void OnMenuStructureChanged() OVERRIDE {
869    if (menu_item_->HasSubmenu()) {
870      // Remove all menu items from submenu.
871      views::SubmenuView* submenu = menu_item_->GetSubmenu();
872      while (submenu->child_count() > 0)
873        menu_item_->RemoveMenuItemAt(submenu->child_count() - 1);
874
875      // Remove all elements in |WrenchMenu::command_id_to_entry_| that map to
876      // |model_|.
877      WrenchMenu::CommandIDToEntry::iterator iter =
878          wrench_menu_->command_id_to_entry_.begin();
879      while (iter != wrench_menu_->command_id_to_entry_.end()) {
880        if (iter->second.first == model_)
881          wrench_menu_->command_id_to_entry_.erase(iter++);
882        else
883          ++iter;
884      }
885    }
886
887    // Add all menu items from |model| to submenu.
888    for (int i = 0; i < model_->GetItemCount(); ++i) {
889      wrench_menu_->AddMenuItem(menu_item_, i, model_, i, model_->GetTypeAt(i),
890                                0);
891    }
892
893    // In case recent tabs submenu was open when items were changing, force a
894    // ChildrenChanged().
895    menu_item_->ChildrenChanged();
896  }
897
898 private:
899  WrenchMenu* wrench_menu_;
900  ui::MenuModel* model_;
901  views::MenuItemView* menu_item_;
902
903  DISALLOW_COPY_AND_ASSIGN(RecentTabsMenuModelDelegate);
904};
905
906// WrenchMenu ------------------------------------------------------------------
907
908WrenchMenu::WrenchMenu(Browser* browser,
909                       bool use_new_menu,
910                       bool supports_new_separators)
911    : root_(NULL),
912      browser_(browser),
913      selected_menu_model_(NULL),
914      selected_index_(0),
915      bookmark_menu_(NULL),
916      feedback_menu_item_(NULL),
917      use_new_menu_(use_new_menu),
918      supports_new_separators_(supports_new_separators) {
919  registrar_.Add(this, chrome::NOTIFICATION_GLOBAL_ERRORS_CHANGED,
920                 content::Source<Profile>(browser_->profile()));
921}
922
923WrenchMenu::~WrenchMenu() {
924  if (bookmark_menu_delegate_.get()) {
925    BookmarkModel* model = BookmarkModelFactory::GetForProfile(
926        browser_->profile());
927    if (model)
928      model->RemoveObserver(this);
929  }
930  FOR_EACH_OBSERVER(WrenchMenuObserver, observer_list_, WrenchMenuDestroyed());
931}
932
933void WrenchMenu::Init(ui::MenuModel* model) {
934  DCHECK(!root_);
935  root_ = new MenuItemView(this);
936  root_->set_has_icons(true);  // We have checks, radios and icons, set this
937                               // so we get the taller menu style.
938  PopulateMenu(root_, model);
939
940#if defined(DEBUG)
941  // Verify that the reserved command ID's for bookmarks menu are not used.
942  for (int i = WrenchMenuModel:kMinBookmarkCommandId;
943       i <= WrenchMenuModel::kMaxBookmarkCommandId; ++i)
944    DCHECK(command_id_to_entry_.find(i) == command_id_to_entry_.end());
945#endif  // defined(DEBUG)
946
947  menu_runner_.reset(new views::MenuRunner(root_));
948}
949
950void WrenchMenu::RunMenu(views::MenuButton* host) {
951  gfx::Point screen_loc;
952  views::View::ConvertPointToScreen(host, &screen_loc);
953  gfx::Rect bounds(screen_loc, host->size());
954  content::RecordAction(UserMetricsAction("ShowAppMenu"));
955  if (menu_runner_->RunMenuAt(host->GetWidget(),
956                              host,
957                              bounds,
958                              views::MENU_ANCHOR_TOPRIGHT,
959                              ui::MENU_SOURCE_NONE,
960                              views::MenuRunner::HAS_MNEMONICS) ==
961      views::MenuRunner::MENU_DELETED)
962    return;
963  if (bookmark_menu_delegate_.get()) {
964    BookmarkModel* model = BookmarkModelFactory::GetForProfile(
965        browser_->profile());
966    if (model)
967      model->RemoveObserver(this);
968  }
969  if (selected_menu_model_)
970    selected_menu_model_->ActivatedAt(selected_index_);
971}
972
973bool WrenchMenu::IsShowing() {
974  return menu_runner_.get() && menu_runner_->IsRunning();
975}
976
977const ui::NativeTheme* WrenchMenu::GetNativeTheme() const {
978  views::Widget* browser_widget = views::Widget::GetWidgetForNativeView(
979      browser_->window()->GetNativeWindow());
980  DCHECK(browser_widget);
981  return browser_widget->GetNativeTheme();
982}
983
984const views::MenuConfig& WrenchMenu::GetMenuConfig() const {
985  return MenuConfig::instance(GetNativeTheme());
986}
987
988void WrenchMenu::AddObserver(WrenchMenuObserver* observer) {
989  observer_list_.AddObserver(observer);
990}
991
992void WrenchMenu::RemoveObserver(WrenchMenuObserver* observer) {
993  observer_list_.RemoveObserver(observer);
994}
995
996const gfx::FontList* WrenchMenu::GetLabelFontList(int command_id) const {
997  if (IsRecentTabsCommand(command_id)) {
998    return recent_tabs_menu_model_delegate_->GetLabelFontListAt(
999        ModelIndexFromCommandId(command_id));
1000  }
1001  return NULL;
1002}
1003
1004bool WrenchMenu::GetShouldUseDisabledEmphasizedForegroundColor(
1005    int command_id) const {
1006  if (IsRecentTabsCommand(command_id)) {
1007    return recent_tabs_menu_model_delegate_->
1008        GetShouldUseDisabledEmphasizedForegroundColor(
1009            ModelIndexFromCommandId(command_id));
1010  }
1011  return false;
1012}
1013
1014base::string16 WrenchMenu::GetTooltipText(int command_id,
1015                                          const gfx::Point& p) const {
1016  return IsBookmarkCommand(command_id) ?
1017      bookmark_menu_delegate_->GetTooltipText(command_id, p) : base::string16();
1018}
1019
1020bool WrenchMenu::IsTriggerableEvent(views::MenuItemView* menu,
1021                                    const ui::Event& e) {
1022  return IsBookmarkCommand(menu->GetCommand()) ?
1023      bookmark_menu_delegate_->IsTriggerableEvent(menu, e) :
1024      MenuDelegate::IsTriggerableEvent(menu, e);
1025}
1026
1027bool WrenchMenu::GetDropFormats(
1028      MenuItemView* menu,
1029      int* formats,
1030      std::set<ui::OSExchangeData::CustomFormat>* custom_formats) {
1031  CreateBookmarkMenu();
1032  return bookmark_menu_delegate_.get() &&
1033      bookmark_menu_delegate_->GetDropFormats(menu, formats, custom_formats);
1034}
1035
1036bool WrenchMenu::AreDropTypesRequired(MenuItemView* menu) {
1037  CreateBookmarkMenu();
1038  return bookmark_menu_delegate_.get() &&
1039      bookmark_menu_delegate_->AreDropTypesRequired(menu);
1040}
1041
1042bool WrenchMenu::CanDrop(MenuItemView* menu,
1043                         const ui::OSExchangeData& data) {
1044  CreateBookmarkMenu();
1045  return bookmark_menu_delegate_.get() &&
1046      bookmark_menu_delegate_->CanDrop(menu, data);
1047}
1048
1049int WrenchMenu::GetDropOperation(
1050    MenuItemView* item,
1051    const ui::DropTargetEvent& event,
1052    DropPosition* position) {
1053  return IsBookmarkCommand(item->GetCommand()) ?
1054      bookmark_menu_delegate_->GetDropOperation(item, event, position) :
1055      ui::DragDropTypes::DRAG_NONE;
1056}
1057
1058int WrenchMenu::OnPerformDrop(MenuItemView* menu,
1059                              DropPosition position,
1060                              const ui::DropTargetEvent& event) {
1061  if (!IsBookmarkCommand(menu->GetCommand()))
1062    return ui::DragDropTypes::DRAG_NONE;
1063
1064  int result = bookmark_menu_delegate_->OnPerformDrop(menu, position, event);
1065  return result;
1066}
1067
1068bool WrenchMenu::ShowContextMenu(MenuItemView* source,
1069                                 int command_id,
1070                                 const gfx::Point& p,
1071                                 ui::MenuSourceType source_type) {
1072  return IsBookmarkCommand(command_id) ?
1073      bookmark_menu_delegate_->ShowContextMenu(source, command_id, p,
1074                                               source_type) :
1075      false;
1076}
1077
1078bool WrenchMenu::CanDrag(MenuItemView* menu) {
1079  return IsBookmarkCommand(menu->GetCommand()) ?
1080      bookmark_menu_delegate_->CanDrag(menu) : false;
1081}
1082
1083void WrenchMenu::WriteDragData(MenuItemView* sender,
1084                               ui::OSExchangeData* data) {
1085  DCHECK(IsBookmarkCommand(sender->GetCommand()));
1086  return bookmark_menu_delegate_->WriteDragData(sender, data);
1087}
1088
1089int WrenchMenu::GetDragOperations(MenuItemView* sender) {
1090  return IsBookmarkCommand(sender->GetCommand()) ?
1091      bookmark_menu_delegate_->GetDragOperations(sender) :
1092      MenuDelegate::GetDragOperations(sender);
1093}
1094
1095int WrenchMenu::GetMaxWidthForMenu(MenuItemView* menu) {
1096  if (IsBookmarkCommand(menu->GetCommand()))
1097    return bookmark_menu_delegate_->GetMaxWidthForMenu(menu);
1098  int max_width = -1;
1099  // If recent tabs menu is available, it will decide if |menu| is one of recent
1100  // tabs; if yes, it would return the menu width for recent tabs.
1101  // otherwise, it would return -1.
1102  if (recent_tabs_menu_model_delegate_.get())
1103    max_width = recent_tabs_menu_model_delegate_->GetMaxWidthForMenu(menu);
1104  if (max_width == -1)
1105    max_width = MenuDelegate::GetMaxWidthForMenu(menu);
1106  return max_width;
1107}
1108
1109bool WrenchMenu::IsItemChecked(int command_id) const {
1110  if (IsBookmarkCommand(command_id))
1111    return false;
1112
1113  const Entry& entry = command_id_to_entry_.find(command_id)->second;
1114  return entry.first->IsItemCheckedAt(entry.second);
1115}
1116
1117bool WrenchMenu::IsCommandEnabled(int command_id) const {
1118  if (IsBookmarkCommand(command_id))
1119    return true;
1120
1121  if (command_id == 0)
1122    return false;  // The root item.
1123
1124  // The items representing the cut menu (cut/copy/paste) and zoom menu
1125  // (increment/decrement/reset) are always enabled. The child views of these
1126  // items enabled state updates appropriately.
1127  if (command_id == IDC_CUT || command_id == IDC_ZOOM_MINUS)
1128    return true;
1129
1130  const Entry& entry = command_id_to_entry_.find(command_id)->second;
1131  return entry.first->IsEnabledAt(entry.second);
1132}
1133
1134void WrenchMenu::ExecuteCommand(int command_id, int mouse_event_flags) {
1135  if (IsBookmarkCommand(command_id)) {
1136    bookmark_menu_delegate_->ExecuteCommand(command_id, mouse_event_flags);
1137    return;
1138  }
1139
1140  if (command_id == IDC_CUT || command_id == IDC_ZOOM_MINUS) {
1141    // These items are represented by child views. If ExecuteCommand is invoked
1142    // it means the user clicked on the area around the buttons and we should
1143    // not do anyting.
1144    return;
1145  }
1146
1147  const Entry& entry = command_id_to_entry_.find(command_id)->second;
1148  return entry.first->ActivatedAt(entry.second, mouse_event_flags);
1149}
1150
1151bool WrenchMenu::GetAccelerator(int command_id, ui::Accelerator* accelerator) {
1152  if (IsBookmarkCommand(command_id))
1153    return false;
1154
1155  if (command_id == IDC_CUT || command_id == IDC_ZOOM_MINUS) {
1156    // These have special child views; don't show the accelerator for them.
1157    return false;
1158  }
1159
1160  CommandIDToEntry::iterator ix = command_id_to_entry_.find(command_id);
1161  const Entry& entry = ix->second;
1162  ui::Accelerator menu_accelerator;
1163  if (!entry.first->GetAcceleratorAt(entry.second, &menu_accelerator))
1164    return false;
1165
1166  *accelerator = ui::Accelerator(menu_accelerator.key_code(),
1167                                 menu_accelerator.modifiers());
1168  return true;
1169}
1170
1171void WrenchMenu::WillShowMenu(MenuItemView* menu) {
1172  if (menu == bookmark_menu_)
1173    CreateBookmarkMenu();
1174}
1175
1176void WrenchMenu::WillHideMenu(MenuItemView* menu) {
1177  // Turns off the fade out animation of the wrench menus if
1178  // |feedback_menu_item_| is selected.  This excludes the wrench menu itself
1179  // from the snapshot in the feedback UI.
1180  if (menu->HasSubmenu() && feedback_menu_item_ &&
1181      feedback_menu_item_->IsSelected()) {
1182    // It's okay to just turn off the animation and no to take care the
1183    // animation back because the menu widget will be recreated next time
1184    // it's opened. See ToolbarView::RunMenu() and Init() of this class.
1185    menu->GetSubmenu()->GetWidget()->
1186        SetVisibilityChangedAnimationsEnabled(false);
1187  }
1188}
1189
1190void WrenchMenu::BookmarkModelChanged() {
1191  DCHECK(bookmark_menu_delegate_.get());
1192  if (!bookmark_menu_delegate_->is_mutating_model())
1193    root_->Cancel();
1194}
1195
1196void WrenchMenu::Observe(int type,
1197                         const content::NotificationSource& source,
1198                         const content::NotificationDetails& details) {
1199  switch (type) {
1200    case chrome::NOTIFICATION_GLOBAL_ERRORS_CHANGED:
1201      // A change in the global errors list can add or remove items from the
1202      // menu. Close the menu to avoid have a stale menu on-screen.
1203      if (root_)
1204        root_->Cancel();
1205      break;
1206    default:
1207      NOTREACHED();
1208  }
1209}
1210
1211void WrenchMenu::PopulateMenu(MenuItemView* parent,
1212                              MenuModel* model) {
1213  for (int i = 0, max = model->GetItemCount(); i < max; ++i) {
1214    // The button container menu items have a special height which we have to
1215    // use instead of the normal height.
1216    int height = 0;
1217    if (use_new_menu_ &&
1218        (model->GetCommandIdAt(i) == IDC_CUT ||
1219         model->GetCommandIdAt(i) == IDC_ZOOM_MINUS))
1220      height = kMenuItemContainingButtonsHeight;
1221
1222    // Add the menu item at the end.
1223    int menu_index = parent->HasSubmenu() ?
1224        parent->GetSubmenu()->child_count() : 0;
1225    MenuItemView* item = AddMenuItem(
1226        parent, menu_index, model, i, model->GetTypeAt(i), height);
1227
1228    if (model->GetTypeAt(i) == MenuModel::TYPE_SUBMENU)
1229      PopulateMenu(item, model->GetSubmenuModelAt(i));
1230
1231    const ui::NativeTheme* native_theme = GetNativeTheme();
1232
1233    switch (model->GetCommandIdAt(i)) {
1234      case IDC_CUT:
1235        DCHECK_EQ(MenuModel::TYPE_COMMAND, model->GetTypeAt(i));
1236        DCHECK_LT(i + 2, max);
1237        DCHECK_EQ(IDC_COPY, model->GetCommandIdAt(i + 1));
1238        DCHECK_EQ(IDC_PASTE, model->GetCommandIdAt(i + 2));
1239        item->SetTitle(l10n_util::GetStringUTF16(IDS_EDIT2));
1240        item->AddChildView(new CutCopyPasteView(this, model, native_theme,
1241                                                i, i + 1, i + 2));
1242        i += 2;
1243        break;
1244
1245      case IDC_ZOOM_MINUS:
1246        DCHECK_EQ(MenuModel::TYPE_COMMAND, model->GetTypeAt(i));
1247        DCHECK_EQ(IDC_ZOOM_PLUS, model->GetCommandIdAt(i + 1));
1248        DCHECK_EQ(IDC_FULLSCREEN, model->GetCommandIdAt(i + 2));
1249        item->SetTitle(l10n_util::GetStringUTF16(IDS_ZOOM_MENU2));
1250        item->AddChildView(new ZoomView(this, model, native_theme,
1251                                        i, i + 1, i + 2));
1252        i += 2;
1253        break;
1254
1255      case IDC_BOOKMARKS_MENU:
1256        DCHECK(!bookmark_menu_);
1257        bookmark_menu_ = item;
1258        break;
1259
1260#if defined(GOOGLE_CHROME_BUILD)
1261      case IDC_FEEDBACK:
1262        DCHECK(!feedback_menu_item_);
1263        feedback_menu_item_ = item;
1264        break;
1265#endif
1266
1267      case IDC_RECENT_TABS_MENU:
1268        DCHECK(!recent_tabs_menu_model_delegate_.get());
1269        recent_tabs_menu_model_delegate_.reset(
1270            new RecentTabsMenuModelDelegate(this, model->GetSubmenuModelAt(i),
1271                                            item));
1272        break;
1273
1274      default:
1275        break;
1276    }
1277  }
1278}
1279
1280MenuItemView* WrenchMenu::AddMenuItem(MenuItemView* parent,
1281                                      int menu_index,
1282                                      MenuModel* model,
1283                                      int model_index,
1284                                      MenuModel::ItemType menu_type,
1285                                      int height) {
1286  int command_id = model->GetCommandIdAt(model_index);
1287  DCHECK(command_id > -1 ||
1288         (command_id == -1 &&
1289          model->GetTypeAt(model_index) == MenuModel::TYPE_SEPARATOR));
1290
1291  if (command_id > -1) {  // Don't add separators to |command_id_to_entry_|.
1292    // All command ID's should be unique except for IDC_SHOW_HISTORY which is
1293    // in both wrench menu and RecentTabs submenu,
1294    if (command_id != IDC_SHOW_HISTORY) {
1295      DCHECK(command_id_to_entry_.find(command_id) ==
1296             command_id_to_entry_.end())
1297          << "command ID " << command_id << " already exists!";
1298    }
1299    command_id_to_entry_[command_id].first = model;
1300    command_id_to_entry_[command_id].second = model_index;
1301  }
1302
1303  MenuItemView* menu_item = NULL;
1304  if (height > 0) {
1305    // For menu items with a special menu height we use our special class to be
1306    // able to modify the item height.
1307    menu_item = new ButtonContainerMenuItemView(parent, command_id, height);
1308    parent->GetSubmenu()->AddChildViewAt(menu_item, menu_index);
1309  } else {
1310    // For all other cases we use the more generic way to add menu items.
1311    menu_item = views::MenuModelAdapter::AddMenuItemFromModelAt(
1312        model, model_index, parent, menu_index, command_id);
1313  }
1314
1315  if (menu_item) {
1316    // Flush all buttons to the right side of the menu for the new menu type.
1317    menu_item->set_use_right_margin(!use_new_menu_);
1318    menu_item->SetVisible(model->IsVisibleAt(model_index));
1319
1320    if (menu_type == MenuModel::TYPE_COMMAND && model->HasIcons()) {
1321      gfx::Image icon;
1322      if (model->GetIconAt(model_index, &icon))
1323        menu_item->SetIcon(*icon.ToImageSkia());
1324    }
1325  }
1326
1327  return menu_item;
1328}
1329
1330void WrenchMenu::CancelAndEvaluate(MenuModel* model, int index) {
1331  selected_menu_model_ = model;
1332  selected_index_ = index;
1333  root_->Cancel();
1334}
1335
1336void WrenchMenu::CreateBookmarkMenu() {
1337  if (bookmark_menu_delegate_.get())
1338    return;  // Already created the menu.
1339
1340  BookmarkModel* model =
1341      BookmarkModelFactory::GetForProfile(browser_->profile());
1342  if (!model->loaded())
1343    return;
1344
1345  model->AddObserver(this);
1346
1347  // TODO(oshima): Replace with views only API.
1348  views::Widget* parent = views::Widget::GetWidgetForNativeWindow(
1349      browser_->window()->GetNativeWindow());
1350  bookmark_menu_delegate_.reset(
1351      new BookmarkMenuDelegate(browser_,
1352                               browser_,
1353                               parent,
1354                               WrenchMenuModel::kMinBookmarkCommandId,
1355                               WrenchMenuModel::kMaxBookmarkCommandId));
1356  bookmark_menu_delegate_->Init(this,
1357                                bookmark_menu_,
1358                                model->bookmark_bar_node(),
1359                                0,
1360                                BookmarkMenuDelegate::SHOW_PERMANENT_FOLDERS,
1361                                BOOKMARK_LAUNCH_LOCATION_WRENCH_MENU);
1362}
1363
1364int WrenchMenu::ModelIndexFromCommandId(int command_id) const {
1365  CommandIDToEntry::const_iterator ix = command_id_to_entry_.find(command_id);
1366  DCHECK(ix != command_id_to_entry_.end());
1367  return ix->second.second;
1368}
1369