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