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