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