- add sources.
[platform/framework/web/crosswalk.git] / src / ui / views / controls / menu / menu_item_view.cc
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 string16& minor_text,
231     const gfx::ImageSkia& icon,
232     Type type,
233     ui::MenuSeparatorType separator_style) {
234   DCHECK_NE(type, EMPTY);
235   DCHECK_LE(0, index);
236   if (!submenu_)
237     CreateSubmenu();
238   DCHECK_GE(submenu_->child_count(), index);
239   if (type == SEPARATOR) {
240     submenu_->AddChildViewAt(new MenuSeparator(this, separator_style), index);
241     return NULL;
242   }
243   MenuItemView* item = new MenuItemView(this, item_id, type);
244   if (label.empty() && GetDelegate())
245     item->SetTitle(GetDelegate()->GetLabel(item_id));
246   else
247     item->SetTitle(label);
248   item->SetSubtitle(sublabel);
249   item->SetMinorText(minor_text);
250   if (!icon.isNull())
251     item->SetIcon(icon);
252   if (type == SUBMENU)
253     item->CreateSubmenu();
254   submenu_->AddChildViewAt(item, index);
255   return item;
256 }
257
258 void MenuItemView::RemoveMenuItemAt(int index) {
259   DCHECK(submenu_);
260   DCHECK_LE(0, index);
261   DCHECK_GT(submenu_->child_count(), index);
262
263   View* item = submenu_->child_at(index);
264   DCHECK(item);
265   submenu_->RemoveChildView(item);
266
267   // RemoveChildView() does not delete the item, which is a good thing
268   // in case a submenu is being displayed while items are being removed.
269   // Deletion will be done by ChildrenChanged() or at destruction.
270   removed_items_.push_back(item);
271 }
272
273 MenuItemView* MenuItemView::AppendMenuItem(int item_id,
274                                            const string16& label,
275                                            Type type) {
276   return AppendMenuItemImpl(item_id, label, string16(), string16(),
277       gfx::ImageSkia(), type, ui::NORMAL_SEPARATOR);
278 }
279
280 MenuItemView* MenuItemView::AppendSubMenu(int item_id,
281                                           const string16& label) {
282   return AppendMenuItemImpl(item_id, label, string16(), string16(),
283       gfx::ImageSkia(), SUBMENU, ui::NORMAL_SEPARATOR);
284 }
285
286 MenuItemView* MenuItemView::AppendSubMenuWithIcon(int item_id,
287                                                   const string16& label,
288                                                   const gfx::ImageSkia& icon) {
289   return AppendMenuItemImpl(item_id, label, string16(), string16(), icon,
290       SUBMENU, ui::NORMAL_SEPARATOR);
291 }
292
293 MenuItemView* MenuItemView::AppendMenuItemWithLabel(int item_id,
294                                                     const string16& label) {
295   return AppendMenuItem(item_id, label, NORMAL);
296 }
297
298 MenuItemView* MenuItemView::AppendDelegateMenuItem(int item_id) {
299   return AppendMenuItem(item_id, string16(), NORMAL);
300 }
301
302 void MenuItemView::AppendSeparator() {
303   AppendMenuItemImpl(0, string16(), string16(), string16(), gfx::ImageSkia(),
304                      SEPARATOR, ui::NORMAL_SEPARATOR);
305 }
306
307 MenuItemView* MenuItemView::AppendMenuItemWithIcon(int item_id,
308                                                    const string16& label,
309                                                    const gfx::ImageSkia& icon) {
310   return AppendMenuItemImpl(item_id, label, string16(), string16(), icon,
311       NORMAL, ui::NORMAL_SEPARATOR);
312 }
313
314 MenuItemView* MenuItemView::AppendMenuItemImpl(
315     int item_id,
316     const string16& label,
317     const string16& sublabel,
318     const string16& minor_text,
319     const gfx::ImageSkia& icon,
320     Type type,
321     ui::MenuSeparatorType separator_style) {
322   const int index = submenu_ ? submenu_->child_count() : 0;
323   return AddMenuItemAt(index, item_id, label, sublabel, minor_text, icon, type,
324                        separator_style);
325 }
326
327 SubmenuView* MenuItemView::CreateSubmenu() {
328   if (!submenu_)
329     submenu_ = new SubmenuView(this);
330   return submenu_;
331 }
332
333 bool MenuItemView::HasSubmenu() const {
334   return (submenu_ != NULL);
335 }
336
337 SubmenuView* MenuItemView::GetSubmenu() const {
338   return submenu_;
339 }
340
341 void MenuItemView::SetTitle(const string16& title) {
342   title_ = title;
343   invalidate_dimensions();  // Triggers preferred size recalculation.
344 }
345
346 void MenuItemView::SetSubtitle(const string16& subtitle) {
347   subtitle_ = subtitle;
348   invalidate_dimensions();  // Triggers preferred size recalculation.
349 }
350
351 void MenuItemView::SetMinorText(const string16& minor_text) {
352   minor_text_ = minor_text;
353   invalidate_dimensions();  // Triggers preferred size recalculation.
354 }
355
356 void MenuItemView::SetSelected(bool selected) {
357   selected_ = selected;
358   SchedulePaint();
359 }
360
361 void MenuItemView::SetTooltip(const string16& tooltip, int item_id) {
362   MenuItemView* item = GetMenuItemByID(item_id);
363   DCHECK(item);
364   item->tooltip_ = tooltip;
365 }
366
367 void MenuItemView::SetIcon(const gfx::ImageSkia& icon, int item_id) {
368   MenuItemView* item = GetMenuItemByID(item_id);
369   DCHECK(item);
370   item->SetIcon(icon);
371 }
372
373 void MenuItemView::SetIcon(const gfx::ImageSkia& icon) {
374   if (icon.isNull()) {
375     SetIconView(NULL);
376     return;
377   }
378
379   ImageView* icon_view = new ImageView();
380   icon_view->SetImage(&icon);
381   SetIconView(icon_view);
382 }
383
384 void MenuItemView::SetIconView(View* icon_view) {
385   if (icon_view_) {
386     RemoveChildView(icon_view_);
387     delete icon_view_;
388     icon_view_ = NULL;
389   }
390   if (icon_view) {
391     AddChildView(icon_view);
392     icon_view_ = icon_view;
393   }
394   Layout();
395   SchedulePaint();
396 }
397
398 void MenuItemView::OnPaint(gfx::Canvas* canvas) {
399   PaintButton(canvas, PB_NORMAL);
400 }
401
402 gfx::Size MenuItemView::GetPreferredSize() {
403   const MenuItemDimensions& dimensions(GetDimensions());
404   return gfx::Size(dimensions.standard_width + dimensions.children_width,
405                    dimensions.height);
406 }
407
408 const MenuItemView::MenuItemDimensions& MenuItemView::GetDimensions() {
409   if (!is_dimensions_valid())
410     dimensions_ = CalculateDimensions();
411   DCHECK(is_dimensions_valid());
412   return dimensions_;
413 }
414
415 MenuController* MenuItemView::GetMenuController() {
416   return GetRootMenuItem()->controller_;
417 }
418
419 const MenuController* MenuItemView::GetMenuController() const {
420   return GetRootMenuItem()->controller_;
421 }
422
423 MenuDelegate* MenuItemView::GetDelegate() {
424   return GetRootMenuItem()->delegate_;
425 }
426
427 const MenuDelegate* MenuItemView::GetDelegate() const {
428   return GetRootMenuItem()->delegate_;
429 }
430
431 MenuItemView* MenuItemView::GetRootMenuItem() {
432   return const_cast<MenuItemView*>(
433       static_cast<const MenuItemView*>(this)->GetRootMenuItem());
434 }
435
436 const MenuItemView* MenuItemView::GetRootMenuItem() const {
437   const MenuItemView* item = this;
438   for (const MenuItemView* parent = GetParentMenuItem(); parent;
439        parent = item->GetParentMenuItem())
440     item = parent;
441   return item;
442 }
443
444 char16 MenuItemView::GetMnemonic() {
445   if (!GetRootMenuItem()->has_mnemonics_)
446     return 0;
447
448   size_t index = 0;
449   do {
450     index = title_.find('&', index);
451     if (index != string16::npos) {
452       if (index + 1 != title_.size() && title_[index + 1] != '&') {
453         char16 char_array[] = { title_[index + 1], 0 };
454         // TODO(jshin): What about Turkish locale? See http://crbug.com/81719.
455         // If the mnemonic is capital I and the UI language is Turkish,
456         // lowercasing it results in 'small dotless i', which is different
457         // from a 'dotted i'. Similar issues may exist for az and lt locales.
458         return base::i18n::ToLower(char_array)[0];
459       }
460       index++;
461     }
462   } while (index != string16::npos);
463   return 0;
464 }
465
466 MenuItemView* MenuItemView::GetMenuItemByID(int id) {
467   if (GetCommand() == id)
468     return this;
469   if (!HasSubmenu())
470     return NULL;
471   for (int i = 0; i < GetSubmenu()->child_count(); ++i) {
472     View* child = GetSubmenu()->child_at(i);
473     if (child->id() == MenuItemView::kMenuItemViewID) {
474       MenuItemView* result = static_cast<MenuItemView*>(child)->
475           GetMenuItemByID(id);
476       if (result)
477         return result;
478     }
479   }
480   return NULL;
481 }
482
483 void MenuItemView::ChildrenChanged() {
484   MenuController* controller = GetMenuController();
485   if (controller) {
486     // Handles the case where we were empty and are no longer empty.
487     RemoveEmptyMenus();
488
489     // Handles the case where we were not empty, but now are.
490     AddEmptyMenus();
491
492     controller->MenuChildrenChanged(this);
493
494     if (submenu_) {
495       // Force a paint and layout. This handles the case of the top
496       // level window's size remaining the same, resulting in no
497       // change to the submenu's size and no layout.
498       submenu_->Layout();
499       submenu_->SchedulePaint();
500       // Update the menu selection after layout.
501       controller->UpdateSubmenuSelection(submenu_);
502     }
503   }
504
505   STLDeleteElements(&removed_items_);
506 }
507
508 void MenuItemView::Layout() {
509   if (!has_children())
510     return;
511
512   if (IsContainer()) {
513     View* child = child_at(0);
514     gfx::Size size = child->GetPreferredSize();
515     child->SetBounds(0, GetTopMargin(), size.width(), size.height());
516   } else {
517     // Child views are laid out right aligned and given the full height. To
518     // right align start with the last view and progress to the first.
519     int x = width() - (use_right_margin_ ? item_right_margin_ : 0);
520     for (int i = child_count() - 1; i >= 0; --i) {
521       View* child = child_at(i);
522       if (icon_view_ && (icon_view_ == child))
523         continue;
524       int width = child->GetPreferredSize().width();
525       child->SetBounds(x - width, 0, width, height());
526       x -= width - kChildXPadding;
527     }
528     // Position |icon_view|.
529     const MenuConfig& config = GetMenuConfig();
530     if (icon_view_) {
531       icon_view_->SizeToPreferredSize();
532       gfx::Size size = icon_view_->GetPreferredSize();
533       int x = config.item_left_margin + left_icon_margin_ +
534               (icon_area_width_ - size.width()) / 2;
535       if (type_ == CHECKBOX || type_ == RADIO)
536         x = label_start_;
537       int y =
538           (height() + GetTopMargin() - GetBottomMargin() - size.height()) / 2;
539       icon_view_->SetPosition(gfx::Point(x, y));
540     }
541   }
542 }
543
544 void MenuItemView::SetMargins(int top_margin, int bottom_margin) {
545   top_margin_ = top_margin;
546   bottom_margin_ = bottom_margin;
547
548   invalidate_dimensions();
549 }
550
551 const MenuConfig& MenuItemView::GetMenuConfig() const {
552   const MenuController* controller = GetMenuController();
553   if (controller)
554     return controller->menu_config_;
555   return MenuConfig::instance(NULL);
556 }
557
558 MenuItemView::MenuItemView(MenuItemView* parent,
559                            int command,
560                            MenuItemView::Type type)
561     : delegate_(NULL),
562       controller_(NULL),
563       canceled_(false),
564       parent_menu_item_(parent),
565       type_(type),
566       selected_(false),
567       command_(command),
568       submenu_(NULL),
569       has_mnemonics_(false),
570       show_mnemonics_(false),
571       has_icons_(false),
572       icon_view_(NULL),
573       top_margin_(-1),
574       bottom_margin_(-1),
575       left_icon_margin_(0),
576       right_icon_margin_(0),
577       requested_menu_position_(POSITION_BEST_FIT),
578       actual_menu_position_(requested_menu_position_),
579       use_right_margin_(true) {
580   Init(parent, command, type, NULL);
581 }
582
583 MenuItemView::~MenuItemView() {
584   delete submenu_;
585   STLDeleteElements(&removed_items_);
586 }
587
588 const char* MenuItemView::GetClassName() const {
589   return kViewClassName;
590 }
591
592 // Calculates all sizes that we can from the OS.
593 //
594 // This is invoked prior to Running a menu.
595 void MenuItemView::UpdateMenuPartSizes() {
596   const MenuConfig& config = GetMenuConfig();
597
598   item_right_margin_ = config.label_to_arrow_padding + config.arrow_width +
599                        config.arrow_to_edge_padding;
600   icon_area_width_ = config.check_width;
601   if (has_icons_)
602     icon_area_width_ = std::max(icon_area_width_, GetMaxIconViewWidth());
603
604   label_start_ = config.item_left_margin + icon_area_width_;
605   int padding = 0;
606   if (config.always_use_icon_to_label_padding) {
607     padding = config.icon_to_label_padding;
608   } else if (config.render_gutter) {
609     padding = config.item_left_margin;
610   } else {
611     padding = (has_icons_ || HasChecksOrRadioButtons()) ?
612         config.icon_to_label_padding : 0;
613   }
614   label_start_ += padding;
615
616   if (config.render_gutter)
617     label_start_ += config.gutter_width + config.gutter_to_label;
618
619   EmptyMenuMenuItem menu_item(this);
620   menu_item.set_controller(GetMenuController());
621   pref_menu_height_ = menu_item.GetPreferredSize().height();
622 }
623
624 void MenuItemView::Init(MenuItemView* parent,
625                         int command,
626                         MenuItemView::Type type,
627                         MenuDelegate* delegate) {
628   delegate_ = delegate;
629   controller_ = NULL;
630   canceled_ = false;
631   parent_menu_item_ = parent;
632   type_ = type;
633   selected_ = false;
634   command_ = command;
635   submenu_ = NULL;
636   show_mnemonics_ = false;
637   // Assign our ID, this allows SubmenuItemView to find MenuItemViews.
638   set_id(kMenuItemViewID);
639   has_icons_ = false;
640
641   // Don't request enabled status from the root menu item as it is just
642   // a container for real items.  EMPTY items will be disabled.
643   MenuDelegate* root_delegate = GetDelegate();
644   if (parent && type != EMPTY && root_delegate)
645     SetEnabled(root_delegate->IsCommandEnabled(command));
646 }
647
648 void MenuItemView::PrepareForRun(bool is_first_menu,
649                                  bool has_mnemonics,
650                                  bool show_mnemonics) {
651   // Currently we only support showing the root.
652   DCHECK(!parent_menu_item_);
653
654   // Force us to have a submenu.
655   CreateSubmenu();
656   actual_menu_position_ = requested_menu_position_;
657   canceled_ = false;
658
659   has_mnemonics_ = has_mnemonics;
660   show_mnemonics_ = has_mnemonics && show_mnemonics;
661
662   AddEmptyMenus();
663
664   if (is_first_menu) {
665     // Only update the menu size if there are no menus showing, otherwise
666     // things may shift around.
667     UpdateMenuPartSizes();
668   }
669 }
670
671 int MenuItemView::GetDrawStringFlags() {
672   int flags = 0;
673   if (base::i18n::IsRTL())
674     flags |= gfx::Canvas::TEXT_ALIGN_RIGHT;
675   else
676     flags |= gfx::Canvas::TEXT_ALIGN_LEFT;
677
678   if (GetRootMenuItem()->has_mnemonics_) {
679     if (GetMenuConfig().show_mnemonics || GetRootMenuItem()->show_mnemonics_) {
680       flags |= gfx::Canvas::SHOW_PREFIX;
681     } else {
682       flags |= gfx::Canvas::HIDE_PREFIX;
683     }
684   }
685   return flags;
686 }
687
688 const gfx::Font& MenuItemView::GetFont() {
689   const MenuDelegate* delegate = GetDelegate();
690   if (delegate) {
691     const gfx::Font* font = delegate->GetLabelFont(GetCommand());
692     if (font)
693       return *font;
694   }
695   return GetMenuConfig().font;
696 }
697
698 void MenuItemView::AddEmptyMenus() {
699   DCHECK(HasSubmenu());
700   if (!submenu_->has_children()) {
701     submenu_->AddChildViewAt(new EmptyMenuMenuItem(this), 0);
702   } else {
703     for (int i = 0, item_count = submenu_->GetMenuItemCount(); i < item_count;
704          ++i) {
705       MenuItemView* child = submenu_->GetMenuItemAt(i);
706       if (child->HasSubmenu())
707         child->AddEmptyMenus();
708     }
709   }
710 }
711
712 void MenuItemView::RemoveEmptyMenus() {
713   DCHECK(HasSubmenu());
714   // Iterate backwards as we may end up removing views, which alters the child
715   // view count.
716   for (int i = submenu_->child_count() - 1; i >= 0; --i) {
717     View* child = submenu_->child_at(i);
718     if (child->id() == MenuItemView::kMenuItemViewID) {
719       MenuItemView* menu_item = static_cast<MenuItemView*>(child);
720       if (menu_item->HasSubmenu())
721         menu_item->RemoveEmptyMenus();
722     } else if (child->id() == EmptyMenuMenuItem::kEmptyMenuItemViewID) {
723       submenu_->RemoveChildView(child);
724       delete child;
725       child = NULL;
726     }
727   }
728 }
729
730 void MenuItemView::AdjustBoundsForRTLUI(gfx::Rect* rect) const {
731   rect->set_x(GetMirroredXForRect(*rect));
732 }
733
734 void MenuItemView::PaintButton(gfx::Canvas* canvas, PaintButtonMode mode) {
735   const MenuConfig& config = GetMenuConfig();
736   bool render_selection =
737       (mode == PB_NORMAL && IsSelected() &&
738        parent_menu_item_->GetSubmenu()->GetShowSelection(this) &&
739        (NonIconChildViewsCount() == 0));
740
741   int icon_x = config.item_left_margin + left_icon_margin_;
742   int top_margin = GetTopMargin();
743   int bottom_margin = GetBottomMargin();
744   int icon_y = top_margin + (height() - config.item_top_margin -
745                              bottom_margin - config.check_height) / 2;
746   int icon_height = config.check_height;
747   int available_height = height() - top_margin - bottom_margin;
748   MenuDelegate *delegate = GetDelegate();
749   // Render the background. As MenuScrollViewContainer draws the background, we
750   // only need the background when we want it to look different, as when we're
751   // selected.
752   ui::NativeTheme* native_theme = GetNativeTheme();
753   SkColor override_color;
754   if (delegate && delegate->GetBackgroundColor(GetCommand(),
755                                                render_selection,
756                                                &override_color)) {
757     canvas->DrawColor(override_color);
758   } else if (render_selection) {
759     gfx::Rect item_bounds(0, 0, width(), height());
760     AdjustBoundsForRTLUI(&item_bounds);
761
762     native_theme->Paint(canvas->sk_canvas(),
763                         ui::NativeTheme::kMenuItemBackground,
764                         ui::NativeTheme::kHovered,
765                         item_bounds,
766                         ui::NativeTheme::ExtraParams());
767   }
768
769   // Render the check.
770   if (type_ == CHECKBOX && delegate->IsItemChecked(GetCommand())) {
771     gfx::ImageSkia check = GetMenuCheckImage(IsSelected());
772     // Don't use config.check_width here as it's padded
773     // to force more padding (AURA).
774     gfx::Rect check_bounds(icon_x, icon_y, check.width(), icon_height);
775     AdjustBoundsForRTLUI(&check_bounds);
776     canvas->DrawImageInt(check, check_bounds.x(), check_bounds.y());
777   } else if (type_ == RADIO) {
778     gfx::ImageSkia image =
779         GetRadioButtonImage(delegate->IsItemChecked(GetCommand()));
780     gfx::Rect radio_bounds(icon_x,
781                            top_margin +
782                            (height() - top_margin - bottom_margin -
783                                image.height()) / 2,
784                            image.width(),
785                            image.height());
786     AdjustBoundsForRTLUI(&radio_bounds);
787     canvas->DrawImageInt(image, radio_bounds.x(), radio_bounds.y());
788   }
789
790   // Render the foreground.
791   ui::NativeTheme::ColorId color_id =
792       ui::NativeTheme::kColorId_DisabledMenuItemForegroundColor;
793   if (enabled()) {
794     color_id = render_selection ?
795         ui::NativeTheme::kColorId_SelectedMenuItemForegroundColor:
796         ui::NativeTheme::kColorId_EnabledMenuItemForegroundColor;
797   }
798   SkColor fg_color = native_theme->GetSystemColor(color_id);
799   SkColor override_foreground_color;
800   if (delegate && delegate->GetForegroundColor(GetCommand(),
801                                                render_selection,
802                                                &override_foreground_color))
803     fg_color = override_foreground_color;
804
805   const gfx::Font& font = GetFont();
806   int accel_width = parent_menu_item_->GetSubmenu()->max_minor_text_width();
807   int label_start = GetLabelStartForThisItem();
808
809   int width = this->width() - label_start - accel_width -
810       (!delegate ||
811        delegate->ShouldReserveSpaceForSubmenuIndicator() ?
812            item_right_margin_ : config.arrow_to_edge_padding);
813   gfx::Rect text_bounds(label_start, top_margin, width,
814                         subtitle_.empty() ? available_height
815                                           : available_height / 2);
816   text_bounds.set_x(GetMirroredXForRect(text_bounds));
817   int flags = GetDrawStringFlags();
818   if (mode == PB_FOR_DRAG)
819     flags |= gfx::Canvas::NO_SUBPIXEL_RENDERING;
820   canvas->DrawStringInt(title(), font, fg_color,
821                         text_bounds.x(), text_bounds.y(), text_bounds.width(),
822                         text_bounds.height(), flags);
823   if (!subtitle_.empty()) {
824     canvas->DrawStringInt(
825         subtitle_,
826         font,
827         GetNativeTheme()->GetSystemColor(
828             ui::NativeTheme::kColorId_ButtonDisabledColor),
829         text_bounds.x(),
830         text_bounds.y() + GetFont().GetHeight(),
831         text_bounds.width(),
832         text_bounds.height(),
833         flags);
834   }
835
836   PaintMinorText(canvas, render_selection);
837
838   // Render the submenu indicator (arrow).
839   if (HasSubmenu()) {
840     gfx::Rect arrow_bounds(this->width() - config.arrow_width -
841                                config.arrow_to_edge_padding,
842                            top_margin + (available_height -
843                                          config.arrow_width) / 2,
844                            config.arrow_width, height());
845     AdjustBoundsForRTLUI(&arrow_bounds);
846     canvas->DrawImageInt(GetSubmenuArrowImage(IsSelected()),
847                          arrow_bounds.x(), arrow_bounds.y());
848   }
849 }
850
851 void MenuItemView::PaintMinorText(gfx::Canvas* canvas,
852                                   bool render_selection) {
853   string16 minor_text = GetMinorText();
854   if (minor_text.empty())
855     return;
856
857   const gfx::Font& font = GetFont();
858   int available_height = height() - GetTopMargin() - GetBottomMargin();
859   int max_accel_width =
860       parent_menu_item_->GetSubmenu()->max_minor_text_width();
861   const MenuConfig& config = GetMenuConfig();
862   int accel_right_margin = config.align_arrow_and_shortcut ?
863                            config.arrow_to_edge_padding :  item_right_margin_;
864   gfx::Rect accel_bounds(width() - accel_right_margin - max_accel_width,
865                          GetTopMargin(), max_accel_width, available_height);
866   accel_bounds.set_x(GetMirroredXForRect(accel_bounds));
867   int flags = GetDrawStringFlags();
868   flags &= ~(gfx::Canvas::TEXT_ALIGN_RIGHT | gfx::Canvas::TEXT_ALIGN_LEFT);
869   if (base::i18n::IsRTL())
870     flags |= gfx::Canvas::TEXT_ALIGN_LEFT;
871   else
872     flags |= gfx::Canvas::TEXT_ALIGN_RIGHT;
873   canvas->DrawStringInt(
874       minor_text,
875       font,
876       GetNativeTheme()->GetSystemColor(render_selection ?
877           ui::NativeTheme::kColorId_SelectedMenuItemForegroundColor :
878           ui::NativeTheme::kColorId_ButtonDisabledColor),
879       accel_bounds.x(),
880       accel_bounds.y(),
881       accel_bounds.width(),
882       accel_bounds.height(),
883       flags);
884 }
885
886 void MenuItemView::DestroyAllMenuHosts() {
887   if (!HasSubmenu())
888     return;
889
890   submenu_->Close();
891   for (int i = 0, item_count = submenu_->GetMenuItemCount(); i < item_count;
892        ++i) {
893     submenu_->GetMenuItemAt(i)->DestroyAllMenuHosts();
894   }
895 }
896
897 int MenuItemView::GetTopMargin() {
898   if (top_margin_ >= 0)
899     return top_margin_;
900
901   MenuItemView* root = GetRootMenuItem();
902   return root && root->has_icons_
903       ? GetMenuConfig().item_top_margin :
904         GetMenuConfig().item_no_icon_top_margin;
905 }
906
907 int MenuItemView::GetBottomMargin() {
908   if (bottom_margin_ >= 0)
909     return bottom_margin_;
910
911   MenuItemView* root = GetRootMenuItem();
912   return root && root->has_icons_
913       ? GetMenuConfig().item_bottom_margin :
914         GetMenuConfig().item_no_icon_bottom_margin;
915 }
916
917 gfx::Size MenuItemView::GetChildPreferredSize() {
918   if (!has_children())
919     return gfx::Size();
920
921   if (IsContainer()) {
922     View* child = child_at(0);
923     return child->GetPreferredSize();
924   }
925
926   int width = 0;
927   for (int i = 0; i < child_count(); ++i) {
928     View* child = child_at(i);
929     if (icon_view_ && (icon_view_ == child))
930       continue;
931     if (i)
932       width += kChildXPadding;
933     width += child->GetPreferredSize().width();
934   }
935   int height = 0;
936   if (icon_view_)
937     height = icon_view_->GetPreferredSize().height();
938
939   // If there is no icon view it returns a height of 0 to indicate that
940   // we should use the title height instead.
941   return gfx::Size(width, height);
942 }
943
944 MenuItemView::MenuItemDimensions MenuItemView::CalculateDimensions() {
945   gfx::Size child_size = GetChildPreferredSize();
946
947   MenuItemDimensions dimensions;
948   // Get the container height.
949   dimensions.children_width = child_size.width();
950   dimensions.height = child_size.height();
951   // Adjust item content height if menu has both items with and without icons.
952   // This way all menu items will have the same height.
953   if (!icon_view_ && GetRootMenuItem()->has_icons()) {
954     dimensions.height = std::max(dimensions.height,
955                                  GetMenuConfig().check_height);
956   }
957   dimensions.height += GetBottomMargin() + GetTopMargin();
958
959   // In case of a container, only the container size needs to be filled.
960   if (IsContainer())
961     return dimensions;
962
963   // Determine the length of the label text.
964   const gfx::Font& font = GetFont();
965
966   // Get Icon margin overrides for this particular item.
967   const MenuDelegate* delegate = GetDelegate();
968   if (delegate) {
969     delegate->GetHorizontalIconMargins(command_,
970                                        icon_area_width_,
971                                        &left_icon_margin_,
972                                        &right_icon_margin_);
973   } else {
974     left_icon_margin_ = 0;
975     right_icon_margin_ = 0;
976   }
977   int label_start = GetLabelStartForThisItem();
978
979   int string_width = font.GetStringWidth(title_);
980   if (!subtitle_.empty())
981     string_width = std::max(string_width, font.GetStringWidth(subtitle_));
982
983   dimensions.standard_width = string_width + label_start +
984       item_right_margin_;
985   // Determine the length of the right-side text.
986   string16 minor_text = GetMinorText();
987   dimensions.minor_text_width =
988       minor_text.empty() ? 0 : GetFont().GetStringWidth(minor_text);
989
990   // Determine the height to use.
991   dimensions.height = std::max(dimensions.height,
992       (subtitle_.empty() ? 0 : font.GetHeight()) +
993       font.GetHeight() + GetBottomMargin() + GetTopMargin());
994   dimensions.height = std::max(dimensions.height,
995       GetMenuConfig().item_min_height);
996   return dimensions;
997 }
998
999 int MenuItemView::GetLabelStartForThisItem() {
1000   int label_start = label_start_ + left_icon_margin_ + right_icon_margin_;
1001   if ((type_ == CHECKBOX || type_ == RADIO) && icon_view_) {
1002     label_start += icon_view_->size().width() +
1003         GetMenuConfig().icon_to_label_padding;
1004   }
1005   return label_start;
1006 }
1007
1008 string16 MenuItemView::GetMinorText() {
1009   if (id() == kEmptyMenuItemViewID) {
1010     // Don't query the delegate for menus that represent no children.
1011     return string16();
1012   }
1013
1014   ui::Accelerator accelerator;
1015   if (GetMenuConfig().show_accelerators && GetDelegate() && GetCommand() &&
1016           GetDelegate()->GetAccelerator(GetCommand(), &accelerator)) {
1017     return accelerator.GetShortcutText();
1018   }
1019
1020   return minor_text_;
1021 }
1022
1023 bool MenuItemView::IsContainer() const {
1024   // Let the first child take over |this| when we only have one child and no
1025   // title.
1026   return (NonIconChildViewsCount() == 1) && title_.empty();
1027 }
1028
1029 int MenuItemView::NonIconChildViewsCount() const {
1030   // Note that what child_count() returns is the number of children,
1031   // not the number of menu items.
1032   return child_count() - (icon_view_ ? 1 : 0);
1033 }
1034
1035 int MenuItemView::GetMaxIconViewWidth() const {
1036   int width = 0;
1037   for (int i = 0; i < submenu_->GetMenuItemCount(); ++i) {
1038     MenuItemView* menu_item = submenu_->GetMenuItemAt(i);
1039     int temp_width = 0;
1040     if (menu_item->GetType() == CHECKBOX ||
1041         menu_item->GetType() == RADIO) {
1042       // If this item has a radio or checkbox, the icon will not affect
1043       // alignment of other items.
1044       continue;
1045     } else if (menu_item->HasSubmenu()) {
1046       temp_width = menu_item->GetMaxIconViewWidth();
1047     } else if (menu_item->icon_view()) {
1048       temp_width = menu_item->icon_view()->GetPreferredSize().width();
1049     }
1050     width = std::max(width, temp_width);
1051   }
1052   return width;
1053 }
1054
1055 bool MenuItemView::HasChecksOrRadioButtons() const {
1056   for (int i = 0; i < submenu_->GetMenuItemCount(); ++i) {
1057     MenuItemView* menu_item = submenu_->GetMenuItemAt(i);
1058     if (menu_item->HasSubmenu()) {
1059       if (menu_item->HasChecksOrRadioButtons())
1060         return true;
1061     } else {
1062       const Type& type = menu_item->GetType();
1063       if (type == CHECKBOX || type == RADIO)
1064         return true;
1065     }
1066   }
1067   return false;
1068 }
1069
1070 }  // namespace views