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