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