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