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