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.
5 #include "ui/views/controls/menu/menu_item_view.h"
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/resources/grit/views_resources.h"
30 #include "ui/views/widget/widget.h"
36 // EmptyMenuMenuItem ---------------------------------------------------------
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.
42 class EmptyMenuMenuItem
: public MenuItemView
{
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
));
52 bool GetTooltipText(const gfx::Point
& p
,
53 base::string16
* tooltip
) const override
{
54 // Empty menu items shouldn't have a tooltip.
59 DISALLOW_COPY_AND_ASSIGN(EmptyMenuMenuItem
);
64 // Padding between child views.
65 static const int kChildXPadding
= 8;
67 // MenuItemView ---------------------------------------------------------------
70 const int MenuItemView::kMenuItemViewID
= 1001;
73 const int MenuItemView::kEmptyMenuItemViewID
=
74 MenuItemView::kMenuItemViewID
+ 1;
77 int MenuItemView::icon_area_width_
= 0;
80 int MenuItemView::label_start_
;
83 int MenuItemView::item_right_margin_
;
86 int MenuItemView::pref_menu_height_
;
89 const char MenuItemView::kViewClassName
[] = "MenuItemView";
91 MenuItemView::MenuItemView(MenuDelegate
* delegate
)
92 : delegate_(delegate
),
95 parent_menu_item_(NULL
),
100 has_mnemonics_(false),
101 show_mnemonics_(false),
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
113 Init(NULL
, 0, SUBMENU
, delegate
);
116 void MenuItemView::ChildPreferredSizeChanged(View
* child
) {
117 invalidate_dimensions();
118 PreferredSizeChanged();
121 bool MenuItemView::GetTooltipText(const gfx::Point
& p
,
122 base::string16
* tooltip
) const {
124 if (!tooltip
->empty())
127 if (GetType() == SEPARATOR
)
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.
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.
145 const MenuDelegate
* delegate
= GetDelegate();
147 gfx::Point
location(p
);
148 ConvertPointToScreen(this, &location
);
149 *tooltip
= delegate
->GetTooltipText(command_
, location
);
150 return !tooltip
->empty();
153 void MenuItemView::GetAccessibleState(ui::AXViewState
* state
) {
154 state
->role
= ui::AX_ROLE_MENU_ITEM
;
156 base::string16 item_text
;
158 // The first child is taking over, just use its accessible name instead of
160 View
* child
= child_at(0);
161 ui::AXViewState state
;
162 child
->GetAccessibleState(&state
);
163 item_text
= state
.name
;
167 state
->name
= GetAccessibleNameForMenuItem(item_text
, GetMinorText());
171 state
->AddStateFlag(ui::AX_STATE_HASPOPUP
);
175 if (GetDelegate()->IsItemChecked(GetCommand()))
176 state
->AddStateFlag(ui::AX_STATE_CHECKED
);
181 // No additional accessibility states currently for these menu states.
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
;
195 base::string16
MenuItemView::GetAccessibleNameForMenuItem(
196 const base::string16
& item_text
, const base::string16
& minor_text
) {
197 base::string16 accessible_name
= item_text
;
199 // Filter out the "&" for accessibility clients.
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));
207 // Special case for "&&" (escaped for "&").
208 if (accessible_name
[index
] == '&')
213 if (!minor_text
.empty()) {
214 accessible_name
.push_back(' ');
215 accessible_name
.append(minor_text
);
218 return accessible_name
;
221 void MenuItemView::Cancel() {
222 if (controller_
&& !canceled_
) {
224 controller_
->Cancel(MenuController::EXIT_ALL
);
228 MenuItemView
* MenuItemView::AddMenuItemAt(
231 const base::string16
& label
,
232 const base::string16
& sublabel
,
233 const base::string16
& minor_text
,
234 const gfx::ImageSkia
& icon
,
236 ui::MenuSeparatorType separator_style
) {
237 DCHECK_NE(type
, EMPTY
);
241 DCHECK_GE(submenu_
->child_count(), index
);
242 if (type
== SEPARATOR
) {
243 submenu_
->AddChildViewAt(new MenuSeparator(this, separator_style
), index
);
246 MenuItemView
* item
= new MenuItemView(this, item_id
, type
);
247 if (label
.empty() && GetDelegate())
248 item
->SetTitle(GetDelegate()->GetLabel(item_id
));
250 item
->SetTitle(label
);
251 item
->SetSubtitle(sublabel
);
252 item
->SetMinorText(minor_text
);
256 item
->CreateSubmenu();
257 if (GetDelegate() && !GetDelegate()->IsCommandVisible(item_id
))
258 item
->SetVisible(false);
259 submenu_
->AddChildViewAt(item
, index
);
263 void MenuItemView::RemoveMenuItemAt(int index
) {
266 DCHECK_GT(submenu_
->child_count(), index
);
268 View
* item
= submenu_
->child_at(index
);
270 submenu_
->RemoveChildView(item
);
272 // RemoveChildView() does not delete the item, which is a good thing
273 // in case a submenu is being displayed while items are being removed.
274 // Deletion will be done by ChildrenChanged() or at destruction.
275 removed_items_
.push_back(item
);
278 MenuItemView
* MenuItemView::AppendMenuItem(int item_id
,
279 const base::string16
& label
,
281 return AppendMenuItemImpl(item_id
, label
, base::string16(), base::string16(),
282 gfx::ImageSkia(), type
, ui::NORMAL_SEPARATOR
);
285 MenuItemView
* MenuItemView::AppendSubMenu(int item_id
,
286 const base::string16
& label
) {
287 return AppendMenuItemImpl(item_id
, label
, base::string16(), base::string16(),
288 gfx::ImageSkia(), SUBMENU
, ui::NORMAL_SEPARATOR
);
291 MenuItemView
* MenuItemView::AppendSubMenuWithIcon(int item_id
,
292 const base::string16
& label
,
293 const gfx::ImageSkia
& icon
) {
294 return AppendMenuItemImpl(item_id
, label
, base::string16(), base::string16(),
295 icon
, SUBMENU
, ui::NORMAL_SEPARATOR
);
298 MenuItemView
* MenuItemView::AppendMenuItemWithLabel(
300 const base::string16
& label
) {
301 return AppendMenuItem(item_id
, label
, NORMAL
);
304 MenuItemView
* MenuItemView::AppendDelegateMenuItem(int item_id
) {
305 return AppendMenuItem(item_id
, base::string16(), NORMAL
);
308 void MenuItemView::AppendSeparator() {
309 AppendMenuItemImpl(0, base::string16(), base::string16(), base::string16(),
310 gfx::ImageSkia(), SEPARATOR
, ui::NORMAL_SEPARATOR
);
313 MenuItemView
* MenuItemView::AppendMenuItemWithIcon(int item_id
,
314 const base::string16
& label
,
315 const gfx::ImageSkia
& icon
) {
316 return AppendMenuItemImpl(item_id
, label
, base::string16(), base::string16(),
317 icon
, NORMAL
, ui::NORMAL_SEPARATOR
);
320 MenuItemView
* MenuItemView::AppendMenuItemImpl(
322 const base::string16
& label
,
323 const base::string16
& sublabel
,
324 const base::string16
& minor_text
,
325 const gfx::ImageSkia
& icon
,
327 ui::MenuSeparatorType separator_style
) {
328 const int index
= submenu_
? submenu_
->child_count() : 0;
329 return AddMenuItemAt(index
, item_id
, label
, sublabel
, minor_text
, icon
, type
,
333 SubmenuView
* MenuItemView::CreateSubmenu() {
335 submenu_
= new SubmenuView(this);
339 bool MenuItemView::HasSubmenu() const {
340 return (submenu_
!= NULL
);
343 SubmenuView
* MenuItemView::GetSubmenu() const {
347 void MenuItemView::SetTitle(const base::string16
& title
) {
349 invalidate_dimensions(); // Triggers preferred size recalculation.
352 void MenuItemView::SetSubtitle(const base::string16
& subtitle
) {
353 subtitle_
= subtitle
;
354 invalidate_dimensions(); // Triggers preferred size recalculation.
357 void MenuItemView::SetMinorText(const base::string16
& minor_text
) {
358 minor_text_
= minor_text
;
359 invalidate_dimensions(); // Triggers preferred size recalculation.
362 void MenuItemView::SetSelected(bool selected
) {
363 selected_
= selected
;
367 void MenuItemView::SetTooltip(const base::string16
& tooltip
, int item_id
) {
368 MenuItemView
* item
= GetMenuItemByID(item_id
);
370 item
->tooltip_
= tooltip
;
373 void MenuItemView::SetIcon(const gfx::ImageSkia
& icon
, int item_id
) {
374 MenuItemView
* item
= GetMenuItemByID(item_id
);
379 void MenuItemView::SetIcon(const gfx::ImageSkia
& icon
) {
385 ImageView
* icon_view
= new ImageView();
386 icon_view
->SetImage(&icon
);
387 SetIconView(icon_view
);
390 void MenuItemView::SetIconView(View
* icon_view
) {
392 RemoveChildView(icon_view_
);
397 AddChildView(icon_view
);
398 icon_view_
= icon_view
;
404 void MenuItemView::OnPaint(gfx::Canvas
* canvas
) {
405 PaintButton(canvas
, PB_NORMAL
);
408 gfx::Size
MenuItemView::GetPreferredSize() const {
409 const MenuItemDimensions
& dimensions(GetDimensions());
410 return gfx::Size(dimensions
.standard_width
+ dimensions
.children_width
,
414 int MenuItemView::GetHeightForWidth(int width
) const {
415 // If this isn't a container, we can just use the preferred size's height.
417 return GetPreferredSize().height();
419 int height
= child_at(0)->GetHeightForWidth(width
);
420 if (!icon_view_
&& GetRootMenuItem()->has_icons())
421 height
= std::max(height
, GetMenuConfig().check_height
);
422 height
+= GetBottomMargin() + GetTopMargin();
427 const MenuItemView::MenuItemDimensions
& MenuItemView::GetDimensions() const {
428 if (!is_dimensions_valid())
429 dimensions_
= CalculateDimensions();
430 DCHECK(is_dimensions_valid());
434 MenuController
* MenuItemView::GetMenuController() {
435 return GetRootMenuItem()->controller_
;
438 const MenuController
* MenuItemView::GetMenuController() const {
439 return GetRootMenuItem()->controller_
;
442 MenuDelegate
* MenuItemView::GetDelegate() {
443 return GetRootMenuItem()->delegate_
;
446 const MenuDelegate
* MenuItemView::GetDelegate() const {
447 return GetRootMenuItem()->delegate_
;
450 MenuItemView
* MenuItemView::GetRootMenuItem() {
451 return const_cast<MenuItemView
*>(
452 static_cast<const MenuItemView
*>(this)->GetRootMenuItem());
455 const MenuItemView
* MenuItemView::GetRootMenuItem() const {
456 const MenuItemView
* item
= this;
457 for (const MenuItemView
* parent
= GetParentMenuItem(); parent
;
458 parent
= item
->GetParentMenuItem())
463 base::char16
MenuItemView::GetMnemonic() {
464 if (!GetRootMenuItem()->has_mnemonics_
)
469 index
= title_
.find('&', index
);
470 if (index
!= base::string16::npos
) {
471 if (index
+ 1 != title_
.size() && title_
[index
+ 1] != '&') {
472 base::char16 char_array
[] = { title_
[index
+ 1], 0 };
473 // TODO(jshin): What about Turkish locale? See http://crbug.com/81719.
474 // If the mnemonic is capital I and the UI language is Turkish,
475 // lowercasing it results in 'small dotless i', which is different
476 // from a 'dotted i'. Similar issues may exist for az and lt locales.
477 return base::i18n::ToLower(char_array
)[0];
481 } while (index
!= base::string16::npos
);
485 MenuItemView
* MenuItemView::GetMenuItemByID(int id
) {
486 if (GetCommand() == id
)
490 for (int i
= 0; i
< GetSubmenu()->child_count(); ++i
) {
491 View
* child
= GetSubmenu()->child_at(i
);
492 if (child
->id() == MenuItemView::kMenuItemViewID
) {
493 MenuItemView
* result
= static_cast<MenuItemView
*>(child
)->
502 void MenuItemView::ChildrenChanged() {
503 MenuController
* controller
= GetMenuController();
505 // Handles the case where we were empty and are no longer empty.
508 // Handles the case where we were not empty, but now are.
511 controller
->MenuChildrenChanged(this);
514 // Force a paint and layout. This handles the case of the top
515 // level window's size remaining the same, resulting in no
516 // change to the submenu's size and no layout.
518 submenu_
->SchedulePaint();
519 // Update the menu selection after layout.
520 controller
->UpdateSubmenuSelection(submenu_
);
524 STLDeleteElements(&removed_items_
);
527 void MenuItemView::Layout() {
532 View
* child
= child_at(0);
533 gfx::Size size
= child
->GetPreferredSize();
534 child
->SetBounds(0, GetTopMargin(), size
.width(), size
.height());
536 // Child views are laid out right aligned and given the full height. To
537 // right align start with the last view and progress to the first.
538 int x
= width() - (use_right_margin_
? item_right_margin_
: 0);
539 for (int i
= child_count() - 1; i
>= 0; --i
) {
540 View
* child
= child_at(i
);
541 if (icon_view_
&& (icon_view_
== child
))
543 int width
= child
->GetPreferredSize().width();
544 child
->SetBounds(x
- width
, 0, width
, height());
545 x
-= width
- kChildXPadding
;
547 // Position |icon_view|.
548 const MenuConfig
& config
= GetMenuConfig();
550 icon_view_
->SizeToPreferredSize();
551 gfx::Size size
= icon_view_
->GetPreferredSize();
552 int x
= config
.item_left_margin
+ left_icon_margin_
+
553 (icon_area_width_
- size
.width()) / 2;
554 if (type_
== CHECKBOX
|| type_
== RADIO
)
557 (height() + GetTopMargin() - GetBottomMargin() - size
.height()) / 2;
558 icon_view_
->SetPosition(gfx::Point(x
, y
));
563 void MenuItemView::SetMargins(int top_margin
, int bottom_margin
) {
564 top_margin_
= top_margin
;
565 bottom_margin_
= bottom_margin
;
567 invalidate_dimensions();
570 const MenuConfig
& MenuItemView::GetMenuConfig() const {
571 const MenuController
* controller
= GetMenuController();
573 return controller
->menu_config_
;
574 return MenuConfig::instance(NULL
);
577 MenuItemView::MenuItemView(MenuItemView
* parent
,
579 MenuItemView::Type type
)
583 parent_menu_item_(parent
),
588 has_mnemonics_(false),
589 show_mnemonics_(false),
594 left_icon_margin_(0),
595 right_icon_margin_(0),
596 requested_menu_position_(POSITION_BEST_FIT
),
597 actual_menu_position_(requested_menu_position_
),
598 use_right_margin_(true) {
599 Init(parent
, command
, type
, NULL
);
602 MenuItemView::~MenuItemView() {
604 STLDeleteElements(&removed_items_
);
607 const char* MenuItemView::GetClassName() const {
608 return kViewClassName
;
611 // Calculates all sizes that we can from the OS.
613 // This is invoked prior to Running a menu.
614 void MenuItemView::UpdateMenuPartSizes() {
615 const MenuConfig
& config
= GetMenuConfig();
617 item_right_margin_
= config
.label_to_arrow_padding
+ config
.arrow_width
+
618 config
.arrow_to_edge_padding
;
619 icon_area_width_
= config
.check_width
;
621 icon_area_width_
= std::max(icon_area_width_
, GetMaxIconViewWidth());
623 label_start_
= config
.item_left_margin
+ icon_area_width_
;
625 if (config
.always_use_icon_to_label_padding
) {
626 padding
= config
.icon_to_label_padding
;
628 padding
= (has_icons_
|| HasChecksOrRadioButtons()) ?
629 config
.icon_to_label_padding
: 0;
631 label_start_
+= padding
;
633 EmptyMenuMenuItem
menu_item(this);
634 menu_item
.set_controller(GetMenuController());
635 pref_menu_height_
= menu_item
.GetPreferredSize().height();
638 void MenuItemView::Init(MenuItemView
* parent
,
640 MenuItemView::Type type
,
641 MenuDelegate
* delegate
) {
642 delegate_
= delegate
;
645 parent_menu_item_
= parent
;
650 show_mnemonics_
= false;
651 // Assign our ID, this allows SubmenuItemView to find MenuItemViews.
652 set_id(kMenuItemViewID
);
655 // Don't request enabled status from the root menu item as it is just
656 // a container for real items. EMPTY items will be disabled.
657 MenuDelegate
* root_delegate
= GetDelegate();
658 if (parent
&& type
!= EMPTY
&& root_delegate
)
659 SetEnabled(root_delegate
->IsCommandEnabled(command
));
662 void MenuItemView::PrepareForRun(bool is_first_menu
,
664 bool show_mnemonics
) {
665 // Currently we only support showing the root.
666 DCHECK(!parent_menu_item_
);
668 // Force us to have a submenu.
670 actual_menu_position_
= requested_menu_position_
;
673 has_mnemonics_
= has_mnemonics
;
674 show_mnemonics_
= has_mnemonics
&& show_mnemonics
;
679 // Only update the menu size if there are no menus showing, otherwise
680 // things may shift around.
681 UpdateMenuPartSizes();
685 int MenuItemView::GetDrawStringFlags() {
687 if (base::i18n::IsRTL())
688 flags
|= gfx::Canvas::TEXT_ALIGN_RIGHT
;
690 flags
|= gfx::Canvas::TEXT_ALIGN_LEFT
;
692 if (GetRootMenuItem()->has_mnemonics_
) {
693 if (GetMenuConfig().show_mnemonics
|| GetRootMenuItem()->show_mnemonics_
) {
694 flags
|= gfx::Canvas::SHOW_PREFIX
;
696 flags
|= gfx::Canvas::HIDE_PREFIX
;
702 const gfx::FontList
& MenuItemView::GetFontList() const {
703 const MenuDelegate
* delegate
= GetDelegate();
705 const gfx::FontList
* font_list
= delegate
->GetLabelFontList(GetCommand());
709 return GetMenuConfig().font_list
;
712 void MenuItemView::AddEmptyMenus() {
713 DCHECK(HasSubmenu());
714 if (!submenu_
->has_children()) {
715 submenu_
->AddChildViewAt(new EmptyMenuMenuItem(this), 0);
717 for (int i
= 0, item_count
= submenu_
->GetMenuItemCount(); i
< item_count
;
719 MenuItemView
* child
= submenu_
->GetMenuItemAt(i
);
720 if (child
->HasSubmenu())
721 child
->AddEmptyMenus();
726 void MenuItemView::RemoveEmptyMenus() {
727 DCHECK(HasSubmenu());
728 // Iterate backwards as we may end up removing views, which alters the child
730 for (int i
= submenu_
->child_count() - 1; i
>= 0; --i
) {
731 View
* child
= submenu_
->child_at(i
);
732 if (child
->id() == MenuItemView::kMenuItemViewID
) {
733 MenuItemView
* menu_item
= static_cast<MenuItemView
*>(child
);
734 if (menu_item
->HasSubmenu())
735 menu_item
->RemoveEmptyMenus();
736 } else if (child
->id() == EmptyMenuMenuItem::kEmptyMenuItemViewID
) {
737 submenu_
->RemoveChildView(child
);
744 void MenuItemView::AdjustBoundsForRTLUI(gfx::Rect
* rect
) const {
745 rect
->set_x(GetMirroredXForRect(*rect
));
748 void MenuItemView::PaintButton(gfx::Canvas
* canvas
, PaintButtonMode mode
) {
749 const MenuConfig
& config
= GetMenuConfig();
750 bool render_selection
=
751 (mode
== PB_NORMAL
&& IsSelected() &&
752 parent_menu_item_
->GetSubmenu()->GetShowSelection(this) &&
753 (NonIconChildViewsCount() == 0));
755 MenuDelegate
*delegate
= GetDelegate();
756 // Render the background. As MenuScrollViewContainer draws the background, we
757 // only need the background when we want it to look different, as when we're
759 ui::NativeTheme
* native_theme
= GetNativeTheme();
760 SkColor override_color
;
761 if (delegate
&& delegate
->GetBackgroundColor(GetCommand(),
764 canvas
->DrawColor(override_color
);
765 } else if (render_selection
) {
766 gfx::Rect
item_bounds(0, 0, width(), height());
767 AdjustBoundsForRTLUI(&item_bounds
);
769 native_theme
->Paint(canvas
->sk_canvas(),
770 ui::NativeTheme::kMenuItemBackground
,
771 ui::NativeTheme::kHovered
,
773 ui::NativeTheme::ExtraParams());
776 const int icon_x
= config
.item_left_margin
+ left_icon_margin_
;
777 const int top_margin
= GetTopMargin();
778 const int bottom_margin
= GetBottomMargin();
779 const int available_height
= height() - top_margin
- bottom_margin
;
782 if (type_
== CHECKBOX
&& delegate
->IsItemChecked(GetCommand())) {
783 gfx::ImageSkia check
= GetMenuCheckImage(render_selection
);
784 // Don't use config.check_width here as it's padded
785 // to force more padding (AURA).
786 gfx::Rect
check_bounds(icon_x
,
787 top_margin
+ (available_height
- check
.height()) / 2,
790 AdjustBoundsForRTLUI(&check_bounds
);
791 canvas
->DrawImageInt(check
, check_bounds
.x(), check_bounds
.y());
792 } else if (type_
== RADIO
) {
793 gfx::ImageSkia image
=
794 GetRadioButtonImage(delegate
->IsItemChecked(GetCommand()));
795 gfx::Rect
radio_bounds(icon_x
,
796 top_margin
+ (available_height
- image
.height()) / 2,
799 AdjustBoundsForRTLUI(&radio_bounds
);
800 canvas
->DrawImageInt(image
, radio_bounds
.x(), radio_bounds
.y());
803 // Render the foreground.
804 ui::NativeTheme::ColorId color_id
;
806 color_id
= render_selection
?
807 ui::NativeTheme::kColorId_SelectedMenuItemForegroundColor
:
808 ui::NativeTheme::kColorId_EnabledMenuItemForegroundColor
;
810 bool emphasized
= delegate
&&
811 delegate
->GetShouldUseDisabledEmphasizedForegroundColor(
813 color_id
= emphasized
?
814 ui::NativeTheme::kColorId_DisabledEmphasizedMenuItemForegroundColor
:
815 ui::NativeTheme::kColorId_DisabledMenuItemForegroundColor
;
817 SkColor fg_color
= native_theme
->GetSystemColor(color_id
);
818 SkColor override_foreground_color
;
819 if (delegate
&& delegate
->GetForegroundColor(GetCommand(),
821 &override_foreground_color
))
822 fg_color
= override_foreground_color
;
824 const gfx::FontList
& font_list
= GetFontList();
825 int accel_width
= parent_menu_item_
->GetSubmenu()->max_minor_text_width();
826 int label_start
= GetLabelStartForThisItem();
828 int width
= this->width() - label_start
- accel_width
-
830 delegate
->ShouldReserveSpaceForSubmenuIndicator() ?
831 item_right_margin_
: config
.arrow_to_edge_padding
);
832 gfx::Rect
text_bounds(label_start
, top_margin
, width
,
833 subtitle_
.empty() ? available_height
834 : available_height
/ 2);
835 text_bounds
.set_x(GetMirroredXForRect(text_bounds
));
836 int flags
= GetDrawStringFlags();
837 if (mode
== PB_FOR_DRAG
)
838 flags
|= gfx::Canvas::NO_SUBPIXEL_RENDERING
;
839 canvas
->DrawStringRectWithFlags(title(), font_list
, fg_color
, text_bounds
,
841 if (!subtitle_
.empty()) {
842 canvas
->DrawStringRectWithFlags(
845 GetNativeTheme()->GetSystemColor(
846 ui::NativeTheme::kColorId_ButtonDisabledColor
),
847 text_bounds
+ gfx::Vector2d(0, font_list
.GetHeight()),
851 PaintMinorText(canvas
, render_selection
);
853 // Render the submenu indicator (arrow).
855 gfx::ImageSkia arrow
= GetSubmenuArrowImage(render_selection
);
856 gfx::Rect
arrow_bounds(this->width() - config
.arrow_width
-
857 config
.arrow_to_edge_padding
,
858 top_margin
+ (available_height
- arrow
.height()) / 2,
861 AdjustBoundsForRTLUI(&arrow_bounds
);
862 canvas
->DrawImageInt(arrow
, arrow_bounds
.x(), arrow_bounds
.y());
866 void MenuItemView::PaintMinorText(gfx::Canvas
* canvas
,
867 bool render_selection
) {
868 base::string16 minor_text
= GetMinorText();
869 if (minor_text
.empty())
872 int available_height
= height() - GetTopMargin() - GetBottomMargin();
873 int max_accel_width
=
874 parent_menu_item_
->GetSubmenu()->max_minor_text_width();
875 const MenuConfig
& config
= GetMenuConfig();
876 int accel_right_margin
= config
.align_arrow_and_shortcut
?
877 config
.arrow_to_edge_padding
: item_right_margin_
;
878 gfx::Rect
accel_bounds(width() - accel_right_margin
- max_accel_width
,
879 GetTopMargin(), max_accel_width
, available_height
);
880 accel_bounds
.set_x(GetMirroredXForRect(accel_bounds
));
881 int flags
= GetDrawStringFlags();
882 flags
&= ~(gfx::Canvas::TEXT_ALIGN_RIGHT
| gfx::Canvas::TEXT_ALIGN_LEFT
);
883 if (base::i18n::IsRTL())
884 flags
|= gfx::Canvas::TEXT_ALIGN_LEFT
;
886 flags
|= gfx::Canvas::TEXT_ALIGN_RIGHT
;
887 canvas
->DrawStringRectWithFlags(
890 GetNativeTheme()->GetSystemColor(render_selection
?
891 ui::NativeTheme::kColorId_SelectedMenuItemForegroundColor
:
892 ui::NativeTheme::kColorId_ButtonDisabledColor
),
897 void MenuItemView::DestroyAllMenuHosts() {
902 for (int i
= 0, item_count
= submenu_
->GetMenuItemCount(); i
< item_count
;
904 submenu_
->GetMenuItemAt(i
)->DestroyAllMenuHosts();
908 int MenuItemView::GetTopMargin() const {
909 if (top_margin_
>= 0)
912 const MenuItemView
* root
= GetRootMenuItem();
913 return root
&& root
->has_icons_
914 ? GetMenuConfig().item_top_margin
:
915 GetMenuConfig().item_no_icon_top_margin
;
918 int MenuItemView::GetBottomMargin() const {
919 if (bottom_margin_
>= 0)
920 return bottom_margin_
;
922 const MenuItemView
* root
= GetRootMenuItem();
923 return root
&& root
->has_icons_
924 ? GetMenuConfig().item_bottom_margin
:
925 GetMenuConfig().item_no_icon_bottom_margin
;
928 gfx::Size
MenuItemView::GetChildPreferredSize() const {
933 return child_at(0)->GetPreferredSize();
936 for (int i
= 0; i
< child_count(); ++i
) {
937 const View
* child
= child_at(i
);
938 if (icon_view_
&& (icon_view_
== child
))
941 width
+= kChildXPadding
;
942 width
+= child
->GetPreferredSize().width();
946 height
= icon_view_
->GetPreferredSize().height();
948 // If there is no icon view it returns a height of 0 to indicate that
949 // we should use the title height instead.
950 return gfx::Size(width
, height
);
953 MenuItemView::MenuItemDimensions
MenuItemView::CalculateDimensions() const {
954 gfx::Size child_size
= GetChildPreferredSize();
956 MenuItemDimensions dimensions
;
957 // Get the container height.
958 dimensions
.children_width
= child_size
.width();
959 dimensions
.height
= child_size
.height();
960 // Adjust item content height if menu has both items with and without icons.
961 // This way all menu items will have the same height.
962 if (!icon_view_
&& GetRootMenuItem()->has_icons()) {
963 dimensions
.height
= std::max(dimensions
.height
,
964 GetMenuConfig().check_height
);
966 dimensions
.height
+= GetBottomMargin() + GetTopMargin();
968 // In case of a container, only the container size needs to be filled.
972 // Determine the length of the label text.
973 const gfx::FontList
& font_list
= GetFontList();
975 // Get Icon margin overrides for this particular item.
976 const MenuDelegate
* delegate
= GetDelegate();
978 delegate
->GetHorizontalIconMargins(command_
,
981 &right_icon_margin_
);
983 left_icon_margin_
= 0;
984 right_icon_margin_
= 0;
986 int label_start
= GetLabelStartForThisItem();
988 int string_width
= gfx::GetStringWidth(title_
, font_list
);
989 if (!subtitle_
.empty()) {
990 string_width
= std::max(string_width
,
991 gfx::GetStringWidth(subtitle_
, font_list
));
994 dimensions
.standard_width
= string_width
+ label_start
+
996 // Determine the length of the right-side text.
997 base::string16 minor_text
= GetMinorText();
998 dimensions
.minor_text_width
=
999 minor_text
.empty() ? 0 : gfx::GetStringWidth(minor_text
, font_list
);
1001 // Determine the height to use.
1003 std::max(dimensions
.height
,
1004 (subtitle_
.empty() ? 0 : font_list
.GetHeight()) +
1005 font_list
.GetHeight() + GetBottomMargin() + GetTopMargin());
1006 dimensions
.height
= std::max(dimensions
.height
,
1007 GetMenuConfig().item_min_height
);
1011 int MenuItemView::GetLabelStartForThisItem() const {
1012 int label_start
= label_start_
+ left_icon_margin_
+ right_icon_margin_
;
1013 if ((type_
== CHECKBOX
|| type_
== RADIO
) && icon_view_
) {
1014 label_start
+= icon_view_
->size().width() +
1015 GetMenuConfig().icon_to_label_padding
;
1020 base::string16
MenuItemView::GetMinorText() const {
1021 if (id() == kEmptyMenuItemViewID
) {
1022 // Don't query the delegate for menus that represent no children.
1023 return base::string16();
1026 ui::Accelerator accelerator
;
1027 if (GetMenuConfig().show_accelerators
&& GetDelegate() && GetCommand() &&
1028 GetDelegate()->GetAccelerator(GetCommand(), &accelerator
)) {
1029 return accelerator
.GetShortcutText();
1035 bool MenuItemView::IsContainer() const {
1036 // Let the first child take over |this| when we only have one child and no
1038 return (NonIconChildViewsCount() == 1) && title_
.empty();
1041 int MenuItemView::NonIconChildViewsCount() const {
1042 // Note that what child_count() returns is the number of children,
1043 // not the number of menu items.
1044 return child_count() - (icon_view_
? 1 : 0);
1047 int MenuItemView::GetMaxIconViewWidth() const {
1049 for (int i
= 0; i
< submenu_
->GetMenuItemCount(); ++i
) {
1050 MenuItemView
* menu_item
= submenu_
->GetMenuItemAt(i
);
1052 if (menu_item
->GetType() == CHECKBOX
||
1053 menu_item
->GetType() == RADIO
) {
1054 // If this item has a radio or checkbox, the icon will not affect
1055 // alignment of other items.
1057 } else if (menu_item
->HasSubmenu()) {
1058 temp_width
= menu_item
->GetMaxIconViewWidth();
1059 } else if (menu_item
->icon_view()) {
1060 temp_width
= menu_item
->icon_view()->GetPreferredSize().width();
1062 width
= std::max(width
, temp_width
);
1067 bool MenuItemView::HasChecksOrRadioButtons() const {
1068 for (int i
= 0; i
< submenu_
->GetMenuItemCount(); ++i
) {
1069 MenuItemView
* menu_item
= submenu_
->GetMenuItemAt(i
);
1070 if (menu_item
->HasSubmenu()) {
1071 if (menu_item
->HasChecksOrRadioButtons())
1074 const Type
& type
= menu_item
->GetType();
1075 if (type
== CHECKBOX
|| type
== RADIO
)
1082 } // namespace views