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