Home | History | Annotate | Download | only in menu
      1 // Copyright (c) 2012 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 "ui/views/controls/menu/menu_item_view.h"
      6 
      7 #include "base/i18n/case_conversion.h"
      8 #include "base/stl_util.h"
      9 #include "base/strings/utf_string_conversions.h"
     10 #include "grit/ui_resources.h"
     11 #include "grit/ui_strings.h"
     12 #include "ui/base/accessibility/accessible_view_state.h"
     13 #include "ui/base/l10n/l10n_util.h"
     14 #include "ui/base/models/menu_model.h"
     15 #include "ui/base/resource/resource_bundle.h"
     16 #include "ui/gfx/canvas.h"
     17 #include "ui/gfx/image/image.h"
     18 #include "ui/native_theme/common_theme.h"
     19 #include "ui/views/controls/button/menu_button.h"
     20 #include "ui/views/controls/image_view.h"
     21 #include "ui/views/controls/menu/menu_config.h"
     22 #include "ui/views/controls/menu/menu_controller.h"
     23 #include "ui/views/controls/menu/menu_image_util.h"
     24 #include "ui/views/controls/menu/menu_scroll_view_container.h"
     25 #include "ui/views/controls/menu/menu_separator.h"
     26 #include "ui/views/controls/menu/submenu_view.h"
     27 #include "ui/views/widget/widget.h"
     28 
     29 namespace views {
     30 
     31 namespace {
     32 
     33 // EmptyMenuMenuItem ---------------------------------------------------------
     34 
     35 // EmptyMenuMenuItem is used when a menu has no menu items. EmptyMenuMenuItem
     36 // is itself a MenuItemView, but it uses a different ID so that it isn't
     37 // identified as a MenuItemView.
     38 
     39 class EmptyMenuMenuItem : public MenuItemView {
     40  public:
     41   explicit EmptyMenuMenuItem(MenuItemView* parent)
     42       : MenuItemView(parent, 0, EMPTY) {
     43     // Set this so that we're not identified as a normal menu item.
     44     set_id(kEmptyMenuItemViewID);
     45     SetTitle(l10n_util::GetStringUTF16(IDS_APP_MENU_EMPTY_SUBMENU));
     46     SetEnabled(false);
     47   }
     48 
     49   virtual bool GetTooltipText(const gfx::Point& p,
     50                               string16* tooltip) const OVERRIDE {
     51     // Empty menu items shouldn't have a tooltip.
     52     return false;
     53   }
     54 
     55  private:
     56   DISALLOW_COPY_AND_ASSIGN(EmptyMenuMenuItem);
     57 };
     58 
     59 }  // namespace
     60 
     61 // Padding between child views.
     62 static const int kChildXPadding = 8;
     63 
     64 // MenuItemView ---------------------------------------------------------------
     65 
     66 // static
     67 const int MenuItemView::kMenuItemViewID = 1001;
     68 
     69 // static
     70 const int MenuItemView::kEmptyMenuItemViewID =
     71     MenuItemView::kMenuItemViewID + 1;
     72 
     73 // static
     74 int MenuItemView::icon_area_width_ = 0;
     75 
     76 // static
     77 int MenuItemView::label_start_;
     78 
     79 // static
     80 int MenuItemView::item_right_margin_;
     81 
     82 // static
     83 int MenuItemView::pref_menu_height_;
     84 
     85 // static
     86 const char MenuItemView::kViewClassName[] = "MenuItemView";
     87 
     88 MenuItemView::MenuItemView(MenuDelegate* delegate)
     89     : delegate_(delegate),
     90       controller_(NULL),
     91       canceled_(false),
     92       parent_menu_item_(NULL),
     93       type_(SUBMENU),
     94       selected_(false),
     95       command_(0),
     96       submenu_(NULL),
     97       has_mnemonics_(false),
     98       show_mnemonics_(false),
     99       has_icons_(false),
    100       icon_view_(NULL),
    101       top_margin_(-1),
    102       bottom_margin_(-1),
    103       left_icon_margin_(0),
    104       right_icon_margin_(0),
    105       requested_menu_position_(POSITION_BEST_FIT),
    106       actual_menu_position_(requested_menu_position_),
    107       use_right_margin_(true) {
    108   // NOTE: don't check the delegate for NULL, UpdateMenuPartSizes() supplies a
    109   // NULL delegate.
    110   Init(NULL, 0, SUBMENU, delegate);
    111 }
    112 
    113 void MenuItemView::ChildPreferredSizeChanged(View* child) {
    114   invalidate_dimensions();
    115   PreferredSizeChanged();
    116 }
    117 
    118 bool MenuItemView::GetTooltipText(const gfx::Point& p,
    119                                   string16* tooltip) const {
    120   *tooltip = tooltip_;
    121   if (!tooltip->empty())
    122     return true;
    123 
    124   if (GetType() == SEPARATOR)
    125     return false;
    126 
    127   const MenuController* controller = GetMenuController();
    128   if (!controller || controller->exit_type() != MenuController::EXIT_NONE) {
    129     // Either the menu has been closed or we're in the process of closing the
    130     // menu. Don't attempt to query the delegate as it may no longer be valid.
    131     return false;
    132   }
    133 
    134   const MenuItemView* root_menu_item = GetRootMenuItem();
    135   if (root_menu_item->canceled_) {
    136     // TODO(sky): if |canceled_| is true, controller->exit_type() should be
    137     // something other than EXIT_NONE, but crash reports seem to indicate
    138     // otherwise. Figure out why this is needed.
    139     return false;
    140   }
    141 
    142   const MenuDelegate* delegate = GetDelegate();
    143   CHECK(delegate);
    144   gfx::Point location(p);
    145   ConvertPointToScreen(this, &location);
    146   *tooltip = delegate->GetTooltipText(command_, location);
    147   return !tooltip->empty();
    148 }
    149 
    150 void MenuItemView::GetAccessibleState(ui::AccessibleViewState* state) {
    151   state->role = ui::AccessibilityTypes::ROLE_MENUITEM;
    152 
    153   string16 item_text;
    154   if (IsContainer()) {
    155     // The first child is taking over, just use its accessible name instead of
    156     // |title_|.
    157     View* child = child_at(0);
    158     ui::AccessibleViewState state;
    159     child->GetAccessibleState(&state);
    160     item_text = state.name;
    161   } else {
    162     item_text = title_;
    163   }
    164   state->name = GetAccessibleNameForMenuItem(item_text, GetMinorText());
    165 
    166   switch (GetType()) {
    167     case SUBMENU:
    168       state->state |= ui::AccessibilityTypes::STATE_HASPOPUP;
    169       break;
    170     case CHECKBOX:
    171     case RADIO:
    172       state->state |= GetDelegate()->IsItemChecked(GetCommand()) ?
    173           ui::AccessibilityTypes::STATE_CHECKED : 0;
    174       break;
    175     case NORMAL:
    176     case SEPARATOR:
    177     case EMPTY:
    178       // No additional accessibility states currently for these menu states.
    179       break;
    180   }
    181 }
    182 
    183 // static
    184 bool MenuItemView::IsBubble(MenuItemView::AnchorPosition anchor) {
    185   return anchor == MenuItemView::BUBBLE_LEFT ||
    186          anchor == MenuItemView::BUBBLE_RIGHT ||
    187          anchor == MenuItemView::BUBBLE_ABOVE ||
    188          anchor == MenuItemView::BUBBLE_BELOW;
    189 }
    190 
    191 // static
    192 string16 MenuItemView::GetAccessibleNameForMenuItem(
    193       const string16& item_text, const string16& minor_text) {
    194   string16 accessible_name = item_text;
    195 
    196   // Filter out the "&" for accessibility clients.
    197   size_t index = 0;
    198   const char16 amp = '&';
    199   while ((index = accessible_name.find(amp, index)) != string16::npos &&
    200          index + 1 < accessible_name.length()) {
    201     accessible_name.replace(index, accessible_name.length() - index,
    202                             accessible_name.substr(index + 1));
    203 
    204     // Special case for "&&" (escaped for "&").
    205     if (accessible_name[index] == '&')
    206       ++index;
    207   }
    208 
    209   // Append subtext.
    210   if (!minor_text.empty()) {
    211     accessible_name.push_back(' ');
    212     accessible_name.append(minor_text);
    213   }
    214 
    215   return accessible_name;
    216 }
    217 
    218 void MenuItemView::Cancel() {
    219   if (controller_ && !canceled_) {
    220     canceled_ = true;
    221     controller_->Cancel(MenuController::EXIT_ALL);
    222   }
    223 }
    224 
    225 MenuItemView* MenuItemView::AddMenuItemAt(
    226     int index,
    227     int item_id,
    228     const string16& label,
    229     const string16& sublabel,
    230     const gfx::ImageSkia& icon,
    231     Type type,
    232     ui::MenuSeparatorType separator_style) {
    233   DCHECK_NE(type, EMPTY);
    234   DCHECK_LE(0, index);
    235   if (!submenu_)
    236     CreateSubmenu();
    237   DCHECK_GE(submenu_->child_count(), index);
    238   if (type == SEPARATOR) {
    239     submenu_->AddChildViewAt(new MenuSeparator(this, separator_style), index);
    240     return NULL;
    241   }
    242   MenuItemView* item = new MenuItemView(this, item_id, type);
    243   if (label.empty() && GetDelegate())
    244     item->SetTitle(GetDelegate()->GetLabel(item_id));
    245   else
    246     item->SetTitle(label);
    247   item->SetSubtitle(sublabel);
    248   if (!icon.isNull())
    249     item->SetIcon(icon);
    250   if (type == SUBMENU)
    251     item->CreateSubmenu();
    252   submenu_->AddChildViewAt(item, index);
    253   return item;
    254 }
    255 
    256 void MenuItemView::RemoveMenuItemAt(int index) {
    257   DCHECK(submenu_);
    258   DCHECK_LE(0, index);
    259   DCHECK_GT(submenu_->child_count(), index);
    260 
    261   View* item = submenu_->child_at(index);
    262   DCHECK(item);
    263   submenu_->RemoveChildView(item);
    264 
    265   // RemoveChildView() does not delete the item, which is a good thing
    266   // in case a submenu is being displayed while items are being removed.
    267   // Deletion will be done by ChildrenChanged() or at destruction.
    268   removed_items_.push_back(item);
    269 }
    270 
    271 MenuItemView* MenuItemView::AppendMenuItem(int item_id,
    272                                            const string16& label,
    273                                            Type type) {
    274   return AppendMenuItemImpl(item_id, label, string16(), gfx::ImageSkia(), type,
    275       ui::NORMAL_SEPARATOR);
    276 }
    277 
    278 MenuItemView* MenuItemView::AppendSubMenu(int item_id,
    279                                           const string16& label) {
    280   return AppendMenuItemImpl(item_id, label, string16(), gfx::ImageSkia(),
    281       SUBMENU, ui::NORMAL_SEPARATOR);
    282 }
    283 
    284 MenuItemView* MenuItemView::AppendSubMenuWithIcon(int item_id,
    285                                                   const string16& label,
    286                                                   const gfx::ImageSkia& icon) {
    287   return AppendMenuItemImpl(
    288       item_id, label, string16(), icon, SUBMENU, ui::NORMAL_SEPARATOR);
    289 }
    290 
    291 MenuItemView* MenuItemView::AppendMenuItemWithLabel(int item_id,
    292                                                     const string16& label) {
    293   return AppendMenuItem(item_id, label, NORMAL);
    294 }
    295 
    296 MenuItemView* MenuItemView::AppendDelegateMenuItem(int item_id) {
    297   return AppendMenuItem(item_id, string16(), NORMAL);
    298 }
    299 
    300 void MenuItemView::AppendSeparator() {
    301   AppendMenuItemImpl(0, string16(), string16(), gfx::ImageSkia(), SEPARATOR,
    302                      ui::NORMAL_SEPARATOR);
    303 }
    304 
    305 MenuItemView* MenuItemView::AppendMenuItemWithIcon(int item_id,
    306                                                    const string16& label,
    307                                                    const gfx::ImageSkia& icon) {
    308   return AppendMenuItemImpl(
    309       item_id, label, string16(), icon, NORMAL, ui::NORMAL_SEPARATOR);
    310 }
    311 
    312 MenuItemView* MenuItemView::AppendMenuItemFromModel(ui::MenuModel* model,
    313                                                     int index,
    314                                                     int id) {
    315   gfx::Image icon;
    316   model->GetIconAt(index, &icon);
    317   string16 label, sublabel;
    318   ui::MenuSeparatorType separator_style = ui::NORMAL_SEPARATOR;
    319   MenuItemView::Type type;
    320   ui::MenuModel::ItemType menu_type = model->GetTypeAt(index);
    321   switch (menu_type) {
    322     case ui::MenuModel::TYPE_COMMAND:
    323       type = MenuItemView::NORMAL;
    324       label = model->GetLabelAt(index);
    325       sublabel = model->GetSublabelAt(index);
    326       break;
    327     case ui::MenuModel::TYPE_CHECK:
    328       type = MenuItemView::CHECKBOX;
    329       label = model->GetLabelAt(index);
    330       sublabel = model->GetSublabelAt(index);
    331       break;
    332     case ui::MenuModel::TYPE_RADIO:
    333       type = MenuItemView::RADIO;
    334       label = model->GetLabelAt(index);
    335       sublabel = model->GetSublabelAt(index);
    336       break;
    337     case ui::MenuModel::TYPE_SEPARATOR:
    338       icon = gfx::Image();
    339       type = MenuItemView::SEPARATOR;
    340       separator_style = model->GetSeparatorTypeAt(index);
    341       break;
    342     case ui::MenuModel::TYPE_SUBMENU:
    343       type = MenuItemView::SUBMENU;
    344       label = model->GetLabelAt(index);
    345       sublabel = model->GetSublabelAt(index);
    346       break;
    347     default:
    348       NOTREACHED();
    349       type = MenuItemView::NORMAL;
    350       break;
    351   }
    352 
    353   return AppendMenuItemImpl(id,
    354       label,
    355       sublabel,
    356       icon.IsEmpty() ? gfx::ImageSkia() : *icon.ToImageSkia(),
    357       type,
    358       separator_style);
    359 }
    360 
    361 MenuItemView* MenuItemView::AppendMenuItemImpl(
    362     int item_id,
    363     const string16& label,
    364     const string16& sublabel,
    365     const gfx::ImageSkia& icon,
    366     Type type,
    367     ui::MenuSeparatorType separator_style) {
    368   const int index = submenu_ ? submenu_->child_count() : 0;
    369   return AddMenuItemAt(index, item_id, label, sublabel, icon, type,
    370                        separator_style);
    371 }
    372 
    373 SubmenuView* MenuItemView::CreateSubmenu() {
    374   if (!submenu_)
    375     submenu_ = new SubmenuView(this);
    376   return submenu_;
    377 }
    378 
    379 bool MenuItemView::HasSubmenu() const {
    380   return (submenu_ != NULL);
    381 }
    382 
    383 SubmenuView* MenuItemView::GetSubmenu() const {
    384   return submenu_;
    385 }
    386 
    387 void MenuItemView::SetTitle(const string16& title) {
    388   title_ = title;
    389   invalidate_dimensions();  // Triggers preferred size recalculation.
    390 }
    391 
    392 void MenuItemView::SetSubtitle(const string16& subtitle) {
    393   subtitle_ = subtitle;
    394   invalidate_dimensions();  // Triggers preferred size recalculation.
    395 }
    396 
    397 void MenuItemView::SetSelected(bool selected) {
    398   selected_ = selected;
    399   SchedulePaint();
    400 }
    401 
    402 void MenuItemView::SetTooltip(const string16& tooltip, int item_id) {
    403   MenuItemView* item = GetMenuItemByID(item_id);
    404   DCHECK(item);
    405   item->tooltip_ = tooltip;
    406 }
    407 
    408 void MenuItemView::SetIcon(const gfx::ImageSkia& icon, int item_id) {
    409   MenuItemView* item = GetMenuItemByID(item_id);
    410   DCHECK(item);
    411   item->SetIcon(icon);
    412 }
    413 
    414 void MenuItemView::SetIcon(const gfx::ImageSkia& icon) {
    415   if (icon.isNull()) {
    416     SetIconView(NULL);
    417     return;
    418   }
    419 
    420   ImageView* icon_view = new ImageView();
    421   icon_view->SetImage(&icon);
    422   SetIconView(icon_view);
    423 }
    424 
    425 void MenuItemView::SetIconView(View* icon_view) {
    426   if (icon_view_) {
    427     RemoveChildView(icon_view_);
    428     delete icon_view_;
    429     icon_view_ = NULL;
    430   }
    431   if (icon_view) {
    432     AddChildView(icon_view);
    433     icon_view_ = icon_view;
    434   }
    435   Layout();
    436   SchedulePaint();
    437 }
    438 
    439 void MenuItemView::OnPaint(gfx::Canvas* canvas) {
    440   PaintButton(canvas, PB_NORMAL);
    441 }
    442 
    443 gfx::Size MenuItemView::GetPreferredSize() {
    444   const MenuItemDimensions& dimensions(GetDimensions());
    445   return gfx::Size(dimensions.standard_width + dimensions.children_width,
    446                    dimensions.height);
    447 }
    448 
    449 const MenuItemView::MenuItemDimensions& MenuItemView::GetDimensions() {
    450   if (!is_dimensions_valid())
    451     dimensions_ = CalculateDimensions();
    452   DCHECK(is_dimensions_valid());
    453   return dimensions_;
    454 }
    455 
    456 MenuController* MenuItemView::GetMenuController() {
    457   return GetRootMenuItem()->controller_;
    458 }
    459 
    460 const MenuController* MenuItemView::GetMenuController() const {
    461   return GetRootMenuItem()->controller_;
    462 }
    463 
    464 MenuDelegate* MenuItemView::GetDelegate() {
    465   return GetRootMenuItem()->delegate_;
    466 }
    467 
    468 const MenuDelegate* MenuItemView::GetDelegate() const {
    469   return GetRootMenuItem()->delegate_;
    470 }
    471 
    472 MenuItemView* MenuItemView::GetRootMenuItem() {
    473   return const_cast<MenuItemView*>(
    474       static_cast<const MenuItemView*>(this)->GetRootMenuItem());
    475 }
    476 
    477 const MenuItemView* MenuItemView::GetRootMenuItem() const {
    478   const MenuItemView* item = this;
    479   for (const MenuItemView* parent = GetParentMenuItem(); parent;
    480        parent = item->GetParentMenuItem())
    481     item = parent;
    482   return item;
    483 }
    484 
    485 char16 MenuItemView::GetMnemonic() {
    486   if (!GetRootMenuItem()->has_mnemonics_)
    487     return 0;
    488 
    489   size_t index = 0;
    490   do {
    491     index = title_.find('&', index);
    492     if (index != string16::npos) {
    493       if (index + 1 != title_.size() && title_[index + 1] != '&') {
    494         char16 char_array[] = { title_[index + 1], 0 };
    495         // TODO(jshin): What about Turkish locale? See http://crbug.com/81719.
    496         // If the mnemonic is capital I and the UI language is Turkish,
    497         // lowercasing it results in 'small dotless i', which is different
    498         // from a 'dotted i'. Similar issues may exist for az and lt locales.
    499         return base::i18n::ToLower(char_array)[0];
    500       }
    501       index++;
    502     }
    503   } while (index != string16::npos);
    504   return 0;
    505 }
    506 
    507 MenuItemView* MenuItemView::GetMenuItemByID(int id) {
    508   if (GetCommand() == id)
    509     return this;
    510   if (!HasSubmenu())
    511     return NULL;
    512   for (int i = 0; i < GetSubmenu()->child_count(); ++i) {
    513     View* child = GetSubmenu()->child_at(i);
    514     if (child->id() == MenuItemView::kMenuItemViewID) {
    515       MenuItemView* result = static_cast<MenuItemView*>(child)->
    516           GetMenuItemByID(id);
    517       if (result)
    518         return result;
    519     }
    520   }
    521   return NULL;
    522 }
    523 
    524 void MenuItemView::ChildrenChanged() {
    525   MenuController* controller = GetMenuController();
    526   if (controller) {
    527     // Handles the case where we were empty and are no longer empty.
    528     RemoveEmptyMenus();
    529 
    530     // Handles the case where we were not empty, but now are.
    531     AddEmptyMenus();
    532 
    533     controller->MenuChildrenChanged(this);
    534 
    535     if (submenu_) {
    536       // Force a paint and layout. This handles the case of the top
    537       // level window's size remaining the same, resulting in no
    538       // change to the submenu's size and no layout.
    539       submenu_->Layout();
    540       submenu_->SchedulePaint();
    541       // Update the menu selection after layout.
    542       controller->UpdateSubmenuSelection(submenu_);
    543     }
    544   }
    545 
    546   STLDeleteElements(&removed_items_);
    547 }
    548 
    549 void MenuItemView::Layout() {
    550   if (!has_children())
    551     return;
    552 
    553   if (IsContainer()) {
    554     View* child = child_at(0);
    555     gfx::Size size = child->GetPreferredSize();
    556     child->SetBounds(0, GetTopMargin(), size.width(), size.height());
    557   } else {
    558     // Child views are laid out right aligned and given the full height. To
    559     // right align start with the last view and progress to the first.
    560     int x = width() - (use_right_margin_ ? item_right_margin_ : 0);
    561     for (int i = child_count() - 1; i >= 0; --i) {
    562       View* child = child_at(i);
    563       if (icon_view_ && (icon_view_ == child))
    564         continue;
    565       int width = child->GetPreferredSize().width();
    566       child->SetBounds(x - width, 0, width, height());
    567       x -= width - kChildXPadding;
    568     }
    569     // Position |icon_view|.
    570     const MenuConfig& config = GetMenuConfig();
    571     if (icon_view_) {
    572       icon_view_->SizeToPreferredSize();
    573       gfx::Size size = icon_view_->GetPreferredSize();
    574       int x = config.item_left_margin + left_icon_margin_ +
    575               (icon_area_width_ - size.width()) / 2;
    576       if (type_ == CHECKBOX || type_ == RADIO)
    577         x = label_start_;
    578       int y =
    579           (height() + GetTopMargin() - GetBottomMargin() - size.height()) / 2;
    580       icon_view_->SetPosition(gfx::Point(x, y));
    581     }
    582   }
    583 }
    584 
    585 void MenuItemView::SetMargins(int top_margin, int bottom_margin) {
    586   top_margin_ = top_margin;
    587   bottom_margin_ = bottom_margin;
    588 
    589   invalidate_dimensions();
    590 }
    591 
    592 const MenuConfig& MenuItemView::GetMenuConfig() const {
    593   const MenuController* controller = GetMenuController();
    594   if (controller)
    595     return controller->menu_config_;
    596   return MenuConfig::instance(NULL);
    597 }
    598 
    599 MenuItemView::MenuItemView(MenuItemView* parent,
    600                            int command,
    601                            MenuItemView::Type type)
    602     : delegate_(NULL),
    603       controller_(NULL),
    604       canceled_(false),
    605       parent_menu_item_(parent),
    606       type_(type),
    607       selected_(false),
    608       command_(command),
    609       submenu_(NULL),
    610       has_mnemonics_(false),
    611       show_mnemonics_(false),
    612       has_icons_(false),
    613       icon_view_(NULL),
    614       top_margin_(-1),
    615       bottom_margin_(-1),
    616       left_icon_margin_(0),
    617       right_icon_margin_(0),
    618       requested_menu_position_(POSITION_BEST_FIT),
    619       actual_menu_position_(requested_menu_position_),
    620       use_right_margin_(true) {
    621   Init(parent, command, type, NULL);
    622 }
    623 
    624 MenuItemView::~MenuItemView() {
    625   delete submenu_;
    626   STLDeleteElements(&removed_items_);
    627 }
    628 
    629 const char* MenuItemView::GetClassName() const {
    630   return kViewClassName;
    631 }
    632 
    633 // Calculates all sizes that we can from the OS.
    634 //
    635 // This is invoked prior to Running a menu.
    636 void MenuItemView::UpdateMenuPartSizes() {
    637   const MenuConfig& config = GetMenuConfig();
    638 
    639   item_right_margin_ = config.label_to_arrow_padding + config.arrow_width +
    640                        config.arrow_to_edge_padding;
    641   icon_area_width_ = config.check_width;
    642   if (has_icons_)
    643     icon_area_width_ = std::max(icon_area_width_, GetMaxIconViewWidth());
    644 
    645   label_start_ = config.item_left_margin + icon_area_width_;
    646   int padding = 0;
    647   if (config.always_use_icon_to_label_padding) {
    648     padding = config.icon_to_label_padding;
    649   } else if (config.render_gutter) {
    650     padding = config.item_left_margin;
    651   } else {
    652     padding = (has_icons_ || HasChecksOrRadioButtons()) ?
    653         config.icon_to_label_padding : 0;
    654   }
    655   label_start_ += padding;
    656 
    657   if (config.render_gutter)
    658     label_start_ += config.gutter_width + config.gutter_to_label;
    659 
    660   EmptyMenuMenuItem menu_item(this);
    661   menu_item.set_controller(GetMenuController());
    662   pref_menu_height_ = menu_item.GetPreferredSize().height();
    663 }
    664 
    665 void MenuItemView::Init(MenuItemView* parent,
    666                         int command,
    667                         MenuItemView::Type type,
    668                         MenuDelegate* delegate) {
    669   delegate_ = delegate;
    670   controller_ = NULL;
    671   canceled_ = false;
    672   parent_menu_item_ = parent;
    673   type_ = type;
    674   selected_ = false;
    675   command_ = command;
    676   submenu_ = NULL;
    677   show_mnemonics_ = false;
    678   // Assign our ID, this allows SubmenuItemView to find MenuItemViews.
    679   set_id(kMenuItemViewID);
    680   has_icons_ = false;
    681 
    682   // Don't request enabled status from the root menu item as it is just
    683   // a container for real items.  EMPTY items will be disabled.
    684   MenuDelegate* root_delegate = GetDelegate();
    685   if (parent && type != EMPTY && root_delegate)
    686     SetEnabled(root_delegate->IsCommandEnabled(command));
    687 }
    688 
    689 void MenuItemView::PrepareForRun(bool is_first_menu,
    690                                  bool has_mnemonics,
    691                                  bool show_mnemonics) {
    692   // Currently we only support showing the root.
    693   DCHECK(!parent_menu_item_);
    694 
    695   // Force us to have a submenu.
    696   CreateSubmenu();
    697   actual_menu_position_ = requested_menu_position_;
    698   canceled_ = false;
    699 
    700   has_mnemonics_ = has_mnemonics;
    701   show_mnemonics_ = has_mnemonics && show_mnemonics;
    702 
    703   AddEmptyMenus();
    704 
    705   if (is_first_menu) {
    706     // Only update the menu size if there are no menus showing, otherwise
    707     // things may shift around.
    708     UpdateMenuPartSizes();
    709   }
    710 }
    711 
    712 int MenuItemView::GetDrawStringFlags() {
    713   int flags = 0;
    714   if (base::i18n::IsRTL())
    715     flags |= gfx::Canvas::TEXT_ALIGN_RIGHT;
    716   else
    717     flags |= gfx::Canvas::TEXT_ALIGN_LEFT;
    718 
    719   if (GetRootMenuItem()->has_mnemonics_) {
    720     if (GetMenuConfig().show_mnemonics || GetRootMenuItem()->show_mnemonics_) {
    721       flags |= gfx::Canvas::SHOW_PREFIX;
    722     } else {
    723       flags |= gfx::Canvas::HIDE_PREFIX;
    724     }
    725   }
    726   return flags;
    727 }
    728 
    729 const gfx::Font& MenuItemView::GetFont() {
    730   const MenuDelegate* delegate = GetDelegate();
    731   if (delegate) {
    732     const gfx::Font* font = delegate->GetLabelFont(GetCommand());
    733     if (font)
    734       return *font;
    735   }
    736   return GetMenuConfig().font;
    737 }
    738 
    739 void MenuItemView::AddEmptyMenus() {
    740   DCHECK(HasSubmenu());
    741   if (!submenu_->has_children()) {
    742     submenu_->AddChildViewAt(new EmptyMenuMenuItem(this), 0);
    743   } else {
    744     for (int i = 0, item_count = submenu_->GetMenuItemCount(); i < item_count;
    745          ++i) {
    746       MenuItemView* child = submenu_->GetMenuItemAt(i);
    747       if (child->HasSubmenu())
    748         child->AddEmptyMenus();
    749     }
    750   }
    751 }
    752 
    753 void MenuItemView::RemoveEmptyMenus() {
    754   DCHECK(HasSubmenu());
    755   // Iterate backwards as we may end up removing views, which alters the child
    756   // view count.
    757   for (int i = submenu_->child_count() - 1; i >= 0; --i) {
    758     View* child = submenu_->child_at(i);
    759     if (child->id() == MenuItemView::kMenuItemViewID) {
    760       MenuItemView* menu_item = static_cast<MenuItemView*>(child);
    761       if (menu_item->HasSubmenu())
    762         menu_item->RemoveEmptyMenus();
    763     } else if (child->id() == EmptyMenuMenuItem::kEmptyMenuItemViewID) {
    764       submenu_->RemoveChildView(child);
    765       delete child;
    766       child = NULL;
    767     }
    768   }
    769 }
    770 
    771 void MenuItemView::AdjustBoundsForRTLUI(gfx::Rect* rect) const {
    772   rect->set_x(GetMirroredXForRect(*rect));
    773 }
    774 
    775 void MenuItemView::PaintButton(gfx::Canvas* canvas, PaintButtonMode mode) {
    776   const MenuConfig& config = GetMenuConfig();
    777   bool render_selection =
    778       (mode == PB_NORMAL && IsSelected() &&
    779        parent_menu_item_->GetSubmenu()->GetShowSelection(this) &&
    780        (NonIconChildViewsCount() == 0));
    781 
    782   int icon_x = config.item_left_margin + left_icon_margin_;
    783   int top_margin = GetTopMargin();
    784   int bottom_margin = GetBottomMargin();
    785   int icon_y = top_margin + (height() - config.item_top_margin -
    786                              bottom_margin - config.check_height) / 2;
    787   int icon_height = config.check_height;
    788   int available_height = height() - top_margin - bottom_margin;
    789   MenuDelegate *delegate = GetDelegate();
    790   // Render the background. As MenuScrollViewContainer draws the background, we
    791   // only need the background when we want it to look different, as when we're
    792   // selected.
    793   ui::NativeTheme* native_theme = GetNativeTheme();
    794   SkColor override_color;
    795   if (delegate && delegate->GetBackgroundColor(GetCommand(),
    796                                                render_selection,
    797                                                &override_color)) {
    798     canvas->DrawColor(override_color);
    799   } else if (render_selection) {
    800     gfx::Rect item_bounds(0, 0, width(), height());
    801     AdjustBoundsForRTLUI(&item_bounds);
    802 
    803     native_theme->Paint(canvas->sk_canvas(),
    804                         ui::NativeTheme::kMenuItemBackground,
    805                         ui::NativeTheme::kHovered,
    806                         item_bounds,
    807                         ui::NativeTheme::ExtraParams());
    808   }
    809 
    810   // Render the check.
    811   if (type_ == CHECKBOX && delegate->IsItemChecked(GetCommand())) {
    812     const gfx::ImageSkia* check = GetMenuCheckImage();
    813     // Don't use config.check_width here as it's padded
    814     // to force more padding (AURA).
    815     gfx::Rect check_bounds(icon_x, icon_y, check->width(), icon_height);
    816     AdjustBoundsForRTLUI(&check_bounds);
    817     canvas->DrawImageInt(*check, check_bounds.x(), check_bounds.y());
    818   } else if (type_ == RADIO) {
    819     const gfx::ImageSkia* image =
    820         GetRadioButtonImage(delegate->IsItemChecked(GetCommand()));
    821     gfx::Rect radio_bounds(icon_x,
    822                            top_margin +
    823                            (height() - top_margin - bottom_margin -
    824                             image->height()) / 2,
    825                            image->width(),
    826                            image->height());
    827     AdjustBoundsForRTLUI(&radio_bounds);
    828     canvas->DrawImageInt(*image, radio_bounds.x(), radio_bounds.y());
    829   }
    830 
    831   // Render the foreground.
    832   ui::NativeTheme::ColorId color_id =
    833       ui::NativeTheme::kColorId_DisabledMenuItemForegroundColor;
    834   if (enabled()) {
    835     color_id = render_selection ?
    836         ui::NativeTheme::kColorId_SelectedMenuItemForegroundColor:
    837         ui::NativeTheme::kColorId_EnabledMenuItemForegroundColor;
    838   }
    839   SkColor fg_color = native_theme->GetSystemColor(color_id);
    840   SkColor override_foreground_color;
    841   if (delegate && delegate->GetForegroundColor(GetCommand(),
    842                                                render_selection,
    843                                                &override_foreground_color))
    844     fg_color = override_foreground_color;
    845 
    846   const gfx::Font& font = GetFont();
    847   int accel_width = parent_menu_item_->GetSubmenu()->max_minor_text_width();
    848   int label_start = GetLabelStartForThisItem();
    849 
    850   int width = this->width() - label_start - accel_width -
    851       (!delegate ||
    852        delegate->ShouldReserveSpaceForSubmenuIndicator() ?
    853            item_right_margin_ : config.arrow_to_edge_padding);
    854   gfx::Rect text_bounds(label_start, top_margin, width, available_height);
    855   text_bounds.set_x(GetMirroredXForRect(text_bounds));
    856   int flags = GetDrawStringFlags();
    857   if (mode == PB_FOR_DRAG)
    858     flags |= gfx::Canvas::NO_SUBPIXEL_RENDERING;
    859   canvas->DrawStringInt(title(), font, fg_color,
    860                         text_bounds.x(), text_bounds.y(), text_bounds.width(),
    861                         text_bounds.height(), flags);
    862 
    863   PaintMinorText(canvas, render_selection);
    864 
    865   // Render the submenu indicator (arrow).
    866   if (HasSubmenu()) {
    867     gfx::Rect arrow_bounds(this->width() - config.arrow_width -
    868                                config.arrow_to_edge_padding,
    869                            top_margin + (available_height -
    870                                          config.arrow_width) / 2,
    871                            config.arrow_width, height());
    872     AdjustBoundsForRTLUI(&arrow_bounds);
    873     canvas->DrawImageInt(*GetSubmenuArrowImage(),
    874                          arrow_bounds.x(), arrow_bounds.y());
    875   }
    876 }
    877 
    878 void MenuItemView::PaintMinorText(gfx::Canvas* canvas,
    879                                   bool render_selection) {
    880   string16 minor_text = GetMinorText();
    881   if (minor_text.empty())
    882     return;
    883 
    884   const gfx::Font& font = GetFont();
    885   int available_height = height() - GetTopMargin() - GetBottomMargin();
    886   int max_accel_width =
    887       parent_menu_item_->GetSubmenu()->max_minor_text_width();
    888   const MenuConfig& config = GetMenuConfig();
    889   int accel_right_margin = config.align_arrow_and_shortcut ?
    890                            config.arrow_to_edge_padding :  item_right_margin_;
    891   gfx::Rect accel_bounds(width() - accel_right_margin - max_accel_width,
    892                          GetTopMargin(), max_accel_width, available_height);
    893   accel_bounds.set_x(GetMirroredXForRect(accel_bounds));
    894   int flags = GetDrawStringFlags();
    895   flags &= ~(gfx::Canvas::TEXT_ALIGN_RIGHT | gfx::Canvas::TEXT_ALIGN_LEFT);
    896   if (base::i18n::IsRTL())
    897     flags |= gfx::Canvas::TEXT_ALIGN_LEFT;
    898   else
    899     flags |= gfx::Canvas::TEXT_ALIGN_RIGHT;
    900   canvas->DrawStringInt(
    901       minor_text,
    902       font,
    903       GetNativeTheme()->GetSystemColor(render_selection ?
    904           ui::NativeTheme::kColorId_SelectedMenuItemForegroundColor :
    905           ui::NativeTheme::kColorId_ButtonDisabledColor),
    906       accel_bounds.x(),
    907       accel_bounds.y(),
    908       accel_bounds.width(),
    909       accel_bounds.height(),
    910       flags);
    911 }
    912 
    913 void MenuItemView::DestroyAllMenuHosts() {
    914   if (!HasSubmenu())
    915     return;
    916 
    917   submenu_->Close();
    918   for (int i = 0, item_count = submenu_->GetMenuItemCount(); i < item_count;
    919        ++i) {
    920     submenu_->GetMenuItemAt(i)->DestroyAllMenuHosts();
    921   }
    922 }
    923 
    924 int MenuItemView::GetTopMargin() {
    925   if (top_margin_ >= 0)
    926     return top_margin_;
    927 
    928   MenuItemView* root = GetRootMenuItem();
    929   return root && root->has_icons_
    930       ? GetMenuConfig().item_top_margin :
    931         GetMenuConfig().item_no_icon_top_margin;
    932 }
    933 
    934 int MenuItemView::GetBottomMargin() {
    935   if (bottom_margin_ >= 0)
    936     return bottom_margin_;
    937 
    938   MenuItemView* root = GetRootMenuItem();
    939   return root && root->has_icons_
    940       ? GetMenuConfig().item_bottom_margin :
    941         GetMenuConfig().item_no_icon_bottom_margin;
    942 }
    943 
    944 gfx::Size MenuItemView::GetChildPreferredSize() {
    945   if (!has_children())
    946     return gfx::Size();
    947 
    948   if (IsContainer()) {
    949     View* child = child_at(0);
    950     return child->GetPreferredSize();
    951   }
    952 
    953   int width = 0;
    954   for (int i = 0; i < child_count(); ++i) {
    955     View* child = child_at(i);
    956     if (icon_view_ && (icon_view_ == child))
    957       continue;
    958     if (i)
    959       width += kChildXPadding;
    960     width += child->GetPreferredSize().width();
    961   }
    962   int height = 0;
    963   if (icon_view_)
    964     height = icon_view_->GetPreferredSize().height();
    965 
    966   // If there is no icon view it returns a height of 0 to indicate that
    967   // we should use the title height instead.
    968   return gfx::Size(width, height);
    969 }
    970 
    971 MenuItemView::MenuItemDimensions MenuItemView::CalculateDimensions() {
    972   gfx::Size child_size = GetChildPreferredSize();
    973 
    974   MenuItemDimensions dimensions;
    975   // Get the container height.
    976   dimensions.children_width = child_size.width();
    977   dimensions.height = child_size.height();
    978   // Adjust item content height if menu has both items with and without icons.
    979   // This way all menu items will have the same height.
    980   if (!icon_view_ && GetRootMenuItem()->has_icons()) {
    981     dimensions.height = std::max(dimensions.height,
    982                                  GetMenuConfig().check_height);
    983   }
    984   dimensions.height += GetBottomMargin() + GetTopMargin();
    985 
    986   // In case of a container, only the container size needs to be filled.
    987   if (IsContainer())
    988     return dimensions;
    989 
    990   // Determine the length of the label text.
    991   const gfx::Font& font = GetFont();
    992 
    993   // Get Icon margin overrides for this particular item.
    994   const MenuDelegate* delegate = GetDelegate();
    995   if (delegate) {
    996     delegate->GetHorizontalIconMargins(command_,
    997                                        icon_area_width_,
    998                                        &left_icon_margin_,
    999                                        &right_icon_margin_);
   1000   } else {
   1001     left_icon_margin_ = 0;
   1002     right_icon_margin_ = 0;
   1003   }
   1004   int label_start = GetLabelStartForThisItem();
   1005 
   1006   dimensions.standard_width = font.GetStringWidth(title_) + label_start +
   1007       item_right_margin_;
   1008   // Determine the length of the right-side text.
   1009   string16 minor_text = GetMinorText();
   1010   dimensions.minor_text_width =
   1011       minor_text.empty() ? 0 : GetFont().GetStringWidth(minor_text);
   1012 
   1013   // Determine the height to use.
   1014   dimensions.height = std::max(dimensions.height,
   1015       font.GetHeight() + GetBottomMargin() + GetTopMargin());
   1016   dimensions.height = std::max(dimensions.height,
   1017       GetMenuConfig().item_min_height);
   1018   return dimensions;
   1019 }
   1020 
   1021 int MenuItemView::GetLabelStartForThisItem() {
   1022   int label_start = label_start_ + left_icon_margin_ + right_icon_margin_;
   1023   if ((type_ == CHECKBOX || type_ == RADIO) && icon_view_) {
   1024     label_start += icon_view_->size().width() +
   1025         GetMenuConfig().icon_to_label_padding;
   1026   }
   1027   return label_start;
   1028 }
   1029 
   1030 string16 MenuItemView::GetMinorText() {
   1031   if (id() == kEmptyMenuItemViewID) {
   1032     // Don't query the delegate for menus that represent no children.
   1033     return string16();
   1034   }
   1035 
   1036   ui::Accelerator accelerator;
   1037   if (GetMenuConfig().show_accelerators && GetDelegate() && GetCommand() &&
   1038           GetDelegate()->GetAccelerator(GetCommand(), &accelerator)) {
   1039     return accelerator.GetShortcutText();
   1040   }
   1041 
   1042   return subtitle_;
   1043 }
   1044 
   1045 bool MenuItemView::IsContainer() const {
   1046   // Let the first child take over |this| when we only have one child and no
   1047   // title.
   1048   return (NonIconChildViewsCount() == 1) && title_.empty();
   1049 }
   1050 
   1051 int MenuItemView::NonIconChildViewsCount() const {
   1052   // Note that what child_count() returns is the number of children,
   1053   // not the number of menu items.
   1054   return child_count() - (icon_view_ ? 1 : 0);
   1055 }
   1056 
   1057 int MenuItemView::GetMaxIconViewWidth() const {
   1058   int width = 0;
   1059   for (int i = 0; i < submenu_->GetMenuItemCount(); ++i) {
   1060     MenuItemView* menu_item = submenu_->GetMenuItemAt(i);
   1061     int temp_width = 0;
   1062     if (menu_item->HasSubmenu()) {
   1063       temp_width = menu_item->GetMaxIconViewWidth();
   1064     } else if (menu_item->icon_view()) {
   1065       temp_width = menu_item->icon_view()->GetPreferredSize().width();
   1066     }
   1067     width = std::max(width, temp_width);
   1068   }
   1069   return width;
   1070 }
   1071 
   1072 bool MenuItemView::HasChecksOrRadioButtons() const {
   1073   for (int i = 0; i < submenu_->GetMenuItemCount(); ++i) {
   1074     MenuItemView* menu_item = submenu_->GetMenuItemAt(i);
   1075     if (menu_item->HasSubmenu()) {
   1076       if (menu_item->HasChecksOrRadioButtons())
   1077         return true;
   1078     } else {
   1079       const Type& type = menu_item->GetType();
   1080       if (type == CHECKBOX || type == RADIO)
   1081         return true;
   1082     }
   1083   }
   1084   return false;
   1085 }
   1086 
   1087 }  // namespace views
   1088