blob: 01b797549ac8293d672405102b2507a658ecedd8 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UI_VIEWS_CONTROLS_MENU_MENU_ITEM_VIEW_H_
#define UI_VIEWS_CONTROLS_MENU_MENU_ITEM_VIEW_H_
#include <memory>
#include <string>
#include <vector>
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "base/strings/string16.h"
#include "build/build_config.h"
#include "ui/base/models/menu_separator_types.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/native_theme/themed_vector_icon.h"
#include "ui/views/controls/menu/menu_controller.h"
#include "ui/views/controls/menu/menu_types.h"
#include "ui/views/view.h"
#if defined(OS_WIN)
#include <windows.h>
#endif
namespace views {
namespace internal {
class MenuRunnerImpl;
}
namespace test {
class TestMenuItemViewShown;
class TestMenuItemViewNotShown;
} // namespace test
class ImageView;
class MenuController;
class MenuDelegate;
class Separator;
class TestMenuItemView;
class SubmenuView;
// MenuItemView --------------------------------------------------------------
// MenuItemView represents a single menu item with a label and optional icon.
// Each MenuItemView may also contain a submenu, which in turn may contain
// any number of child MenuItemViews.
//
// To use a menu create an initial MenuItemView using the constructor that
// takes a MenuDelegate, then create any number of child menu items by way
// of the various AddXXX methods.
//
// MenuItemView is itself a View, which means you can add Views to each
// MenuItemView. This is normally NOT want you want, rather add other child
// Views to the submenu of the MenuItemView. Any child views of the MenuItemView
// that are focusable can be navigated to by way of the up/down arrow and can be
// activated by way of space/return keys. Activating a focusable child results
// in |AcceleratorPressed| being invoked. Note, that as menus try not to steal
// focus from the hosting window child views do not actually get focus. Instead
// |SetHotTracked| is used as the user navigates around.
//
// To show the menu use MenuRunner. See MenuRunner for details on how to run
// (show) the menu as well as for details on the life time of the menu.
class VIEWS_EXPORT MenuItemView : public View {
public:
METADATA_HEADER(MenuItemView);
friend class MenuController;
// ID used to identify menu items.
static const int kMenuItemViewID;
// ID used to identify empty menu items.
static const int kEmptyMenuItemViewID;
// Different types of menu items.
enum class Type {
kNormal, // Performs an action when selected.
kSubMenu, // Presents a submenu within another menu.
kActionableSubMenu, // A SubMenu that is also a COMMAND.
kCheckbox, // Can be selected/checked to toggle a boolean state.
kRadio, // Can be selected/checked among a group of choices.
kSeparator, // Shows a horizontal line separator.
kHighlighted, // Performs an action when selected, and has a
// different colored background that merges with the
// menu's rounded corners when placed at the bottom.
kTitle, // Title text, does not perform any action.
kEmpty, // kEmpty is a special type for empty menus that is
// only used internally.
};
// Where the menu should be drawn, above or below the bounds (when
// the bounds is non-empty). MenuPosition::kBestFit (default) positions
// the menu below the bounds unless the menu does not fit on the
// screen and the re is more space above.
enum class MenuPosition { kBestFit, kAboveBounds, kBelowBounds };
// The data structure which is used for the menu size
struct MenuItemDimensions {
MenuItemDimensions() = default;
// Width of everything except the accelerator and children views.
int standard_width = 0;
// The width of all contained views of the item.
int children_width = 0;
// The amount of space needed to accommodate the subtext.
int minor_text_width = 0;
// The height of the menu item.
int height = 0;
};
// Constructor for use with the top level menu item. This menu is never
// shown to the user, rather its use as the parent for all menu items.
explicit MenuItemView(MenuDelegate* delegate);
// Overridden from View:
base::string16 GetTooltipText(const gfx::Point& p) const override;
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
bool HandleAccessibleAction(const ui::AXActionData& action_data) override;
// Returns the preferred height of menu items. This is only valid when the
// menu is about to be shown.
static int pref_menu_height() { return pref_menu_height_; }
// X-coordinate of where the label starts.
static int label_start() { return label_start_; }
// Returns if a given |anchor| is a bubble or not.
static bool IsBubble(MenuAnchorPosition anchor);
// Returns the accessible name to be used with screen readers. Mnemonics are
// removed and the menu item accelerator text is appended.
static base::string16 GetAccessibleNameForMenuItem(
const base::string16& item_text,
const base::string16& accelerator_text);
// Hides and cancels the menu. This does nothing if the menu is not open.
void Cancel();
// Add an item to the menu at a specified index. ChildrenChanged() should
// called after adding menu items if the menu may be active.
MenuItemView* AddMenuItemAt(int index,
int item_id,
const base::string16& label,
const base::string16& secondary_label,
const base::string16& minor_text,
const ui::ThemedVectorIcon& minor_icon,
const gfx::ImageSkia& icon,
const ui::ThemedVectorIcon& vector_icon,
Type type,
ui::MenuSeparatorType separator_style);
// Remove the specified item from the menu. |item| will be deleted when
// ChildrenChanged() is invoked.
void RemoveMenuItem(View* item);
// Removes all items from the menu. The removed MenuItemViews are deleted
// when ChildrenChanged() is invoked.
void RemoveAllMenuItems();
// Appends a normal item to this menu.
// item_id The id of the item, used to identify it in delegate callbacks
// or (if delegate is NULL) to identify the command associated
// with this item with the controller specified in the ctor. Note
// that this value should not be 0 as this has a special meaning
// ("NULL command, no item selected")
// label The text label shown.
// icon The icon.
MenuItemView* AppendMenuItem(int item_id,
const base::string16& label = base::string16(),
const gfx::ImageSkia& icon = gfx::ImageSkia());
// Append a submenu to this menu.
// The returned pointer is owned by this menu.
MenuItemView* AppendSubMenu(int item_id,
const base::string16& label,
const gfx::ImageSkia& icon = gfx::ImageSkia());
// Adds a separator to this menu
void AppendSeparator();
// Adds a separator to this menu at the specified position.
void AddSeparatorAt(int index);
// All the AppendXXX methods funnel into this.
MenuItemView* AppendMenuItemImpl(int item_id,
const base::string16& label,
const gfx::ImageSkia& icon,
Type type);
// Returns the view that contains child menu items. If the submenu has
// not been created, this creates it.
SubmenuView* CreateSubmenu();
// Returns true if this menu item has a submenu.
bool HasSubmenu() const;
// Returns the view containing child menu items.
SubmenuView* GetSubmenu() const;
// Returns true if this menu item has a submenu and it is showing
bool SubmenuIsShowing() const;
// Returns the parent menu item.
MenuItemView* GetParentMenuItem() { return parent_menu_item_; }
const MenuItemView* GetParentMenuItem() const { return parent_menu_item_; }
// Sets/Gets the title.
void SetTitle(const base::string16& title);
const base::string16& title() const { return title_; }
// Sets/Gets the secondary title. When not empty, they are shown in the line
// below the title.
void SetSecondaryTitle(const base::string16& secondary_title);
const base::string16& secondary_title() const { return secondary_title_; }
// Sets the minor text.
void SetMinorText(const base::string16& minor_text);
// Sets the minor icon.
void SetMinorIcon(const ui::ThemedVectorIcon& minor_icon);
// Returns the type of this menu.
const Type& GetType() const { return type_; }
// Sets whether this item is selected. This is invoked as the user moves
// the mouse around the menu while open.
void SetSelected(bool selected);
// Returns true if the item is selected.
bool IsSelected() const { return selected_; }
// Sets whether the submenu area of an ACTIONABLE_SUBMENU is selected.
void SetSelectionOfActionableSubmenu(
bool submenu_area_of_actionable_submenu_selected);
// Whether the submenu area of an ACTIONABLE_SUBMENU is selected.
bool IsSubmenuAreaOfActionableSubmenuSelected() const {
return submenu_area_of_actionable_submenu_selected_;
}
// Sets the |tooltip| for a menu item view with |item_id| identifier.
void SetTooltip(const base::string16& tooltip, int item_id);
// Sets the icon for the descendant identified by item_id.
void SetIcon(const gfx::ImageSkia& icon, int item_id);
// Sets the icon of this menu item.
void SetIcon(const gfx::ImageSkia& icon);
// Sets the icon as a vector icon which gets its color from the NativeTheme or
// the included color.
void SetIcon(const ui::ThemedVectorIcon& icon);
// Sets the view used to render the icon. This clobbers any icon set via
// SetIcon(). MenuItemView takes ownership of |icon_view|.
void SetIconView(std::unique_ptr<ImageView> icon_view);
void UpdateIconViewFromVectorIconAndTheme();
// Sets the command id of this menu item.
void SetCommand(int command) { command_ = command; }
// Returns the command id of this item.
int GetCommand() const { return command_; }
void set_is_new(bool is_new) { is_new_ = is_new; }
bool is_new() const { return is_new_; }
// Paints the menu item.
void OnPaint(gfx::Canvas* canvas) override;
// Returns the preferred size of this item.
gfx::Size CalculatePreferredSize() const override;
// Gets the preferred height for the given |width|. This is only different
// from GetPreferredSize().width() if the item has a child view with flexible
// dimensions.
int GetHeightForWidth(int width) const override;
void OnThemeChanged() override;
// Returns the bounds of the submenu part of the ACTIONABLE_SUBMENU.
gfx::Rect GetSubmenuAreaOfActionableSubmenu() const;
// Return the preferred dimensions of the item in pixel.
const MenuItemDimensions& GetDimensions() const;
// Returns the object responsible for controlling showing the menu.
MenuController* GetMenuController();
const MenuController* GetMenuController() const;
// Returns the delegate. This returns the delegate of the root menu item.
MenuDelegate* GetDelegate();
const MenuDelegate* GetDelegate() const;
void set_delegate(MenuDelegate* delegate) { delegate_ = delegate; }
// Returns the root parent, or this if this has no parent.
MenuItemView* GetRootMenuItem();
const MenuItemView* GetRootMenuItem() const;
// Returns the mnemonic for this MenuItemView, or 0 if this MenuItemView
// doesn't have a mnemonic.
base::char16 GetMnemonic();
// Do we have icons? This only has effect on the top menu. Turning this on
// makes the menus slightly wider and taller.
void set_has_icons(bool has_icons) { has_icons_ = has_icons; }
bool has_icons() const { return has_icons_; }
// Returns the descendant with the specified command.
MenuItemView* GetMenuItemByID(int id);
// Invoke if you remove/add children to the menu while it's showing. This
// recalculates the bounds.
void ChildrenChanged();
// Sizes any child views.
void Layout() override;
// Returns true if the menu has mnemonics. This only useful on the root menu
// item.
bool has_mnemonics() const { return has_mnemonics_; }
// Set top and bottom margins in pixels. If no margin is set or a
// negative margin is specified then MenuConfig values are used.
void SetMargins(int top_margin, int bottom_margin);
// Suppress the right margin if this is set to false.
void set_use_right_margin(bool use_right_margin) {
use_right_margin_ = use_right_margin;
}
// Controls whether this menu has a forced visual selection state. This is
// used when animating item acceptance on Mac. Note that once this is set
// there's no way to unset it for this MenuItemView!
void SetForcedVisualSelection(bool selected);
// For items of type HIGHLIGHTED only: sets the radius of the item's
// background. This makes the menu item's background fit its container's
// border radius, if they are both the same value.
void SetCornerRadius(int radius);
// Shows an alert on this menu item. An alerted menu item is rendered
// differently to draw attention to it. This must be called before the menu is
// run.
void SetAlerted();
bool is_alerted() const { return is_alerted_; }
protected:
// Creates a MenuItemView. This is used by the various AddXXX methods.
MenuItemView(MenuItemView* parent, int command, Type type);
// MenuRunner owns MenuItemView and should be the only one deleting it.
~MenuItemView() override;
// View:
void ChildPreferredSizeChanged(View* child) override;
// Returns the preferred size (and padding) of any children.
virtual gfx::Size GetChildPreferredSize() const;
// Returns the various margins.
int GetTopMargin() const;
int GetBottomMargin() const;
private:
friend class internal::MenuRunnerImpl; // For access to ~MenuItemView.
friend class test::TestMenuItemViewShown; // for access to |submenu_|;
friend class test::TestMenuItemViewNotShown; // for access to |submenu_|;
friend class TestMenuItemView; // For access to AddEmptyMenus();
enum class PaintButtonMode { kNormal, kForDrag };
// Calculates all sizes that we can from the OS.
//
// This is invoked prior to Running a menu.
void UpdateMenuPartSizes();
// Called by the two constructors to initialize this menu item.
void Init(MenuItemView* parent, int command, MenuItemView::Type type);
// The RunXXX methods call into this to set up the necessary state before
// running. |is_first_menu| is true if no menus are currently showing.
void PrepareForRun(bool is_first_menu,
bool has_mnemonics,
bool show_mnemonics);
// Returns the flags passed to DrawStringRect.
int GetDrawStringFlags();
// Returns the style for the menu text.
void GetLabelStyle(MenuDelegate::LabelStyle* style) const;
// If this menu item has no children a child is added showing it has no
// children. Otherwise AddEmtpyMenus is recursively invoked on child menu
// items that have children.
void AddEmptyMenus();
// Undoes the work of AddEmptyMenus.
void RemoveEmptyMenus();
// Given bounds within our View, this helper routine mirrors the bounds if
// necessary.
void AdjustBoundsForRTLUI(gfx::Rect* rect) const;
// Actual paint implementation. If mode is kForDrag, portions of the menu are
// not rendered.
void PaintButton(gfx::Canvas* canvas, PaintButtonMode mode);
// Helper function for PaintButton(), draws the background for the button if
// appropriate.
void PaintBackground(gfx::Canvas* canvas,
PaintButtonMode mode,
bool render_selection);
// Paints the right-side icon and text.
void PaintMinorIconAndText(gfx::Canvas* canvas,
const MenuDelegate::LabelStyle& style);
// Destroys the window used to display this menu and recursively destroys
// the windows used to display all descendants.
void DestroyAllMenuHosts();
// Returns the text that should be displayed on the end (right) of the menu
// item. This will be the accelerator (if one exists).
base::string16 GetMinorText() const;
// Returns the icon that should be displayed to the left of the minor text.
ui::ThemedVectorIcon GetMinorIcon() const;
// Returns the text color for the current state. |minor| specifies if the
// minor text or the normal text is desired.
SkColor GetTextColor(bool minor, bool render_selection) const;
// Calculates and returns the MenuItemDimensions.
MenuItemDimensions CalculateDimensions() const;
// Imposes MenuConfig's minimum sizes, if any, on the supplied
// dimensions and returns the new dimensions. It is guaranteed that:
// ApplyMinimumDimensions(x).standard_width >= x.standard_width
// ApplyMinimumDimensions(x).children_width == x.children_width
// ApplyMinimumDimensions(x).minor_text_width == x.minor_text_width
// ApplyMinimumDimensions(x).height >= x.height
void ApplyMinimumDimensions(MenuItemDimensions* dims) const;
// Get the horizontal position at which to draw the menu item's label.
int GetLabelStartForThisItem() const;
// Draws the "new" badge on |canvas|. |unmirrored_badge_start| is the
// upper-left corner of the badge, not mirrored for RTL.
void DrawNewBadge(gfx::Canvas* canvas,
const gfx::Point& unmirrored_badge_start,
const gfx::FontList& primary_font,
int text_render_flags);
// Used by MenuController to cache the menu position in use by the
// active menu.
MenuPosition actual_menu_position() const { return actual_menu_position_; }
void set_actual_menu_position(MenuPosition actual_menu_position) {
actual_menu_position_ = actual_menu_position;
}
void set_controller(MenuController* controller) {
if (controller)
controller_ = controller->AsWeakPtr();
else
controller_.reset();
}
// Returns true if this MenuItemView contains a single child
// that is responsible for rendering the content.
bool IsContainer() const;
// Gets the child view margins. Should only be called when |IsContainer()| is
// true.
gfx::Insets GetContainerMargins() const;
// Returns number of child views excluding icon_view.
int NonIconChildViewsCount() const;
// Returns the max icon width; recurses over submenus.
int GetMaxIconViewWidth() const;
// Returns true if the menu has items with a checkbox or a radio button.
bool HasChecksOrRadioButtons() const;
// Returns whether or not a "new" badge should be shown on this menu item.
// Takes into account whether the badging feature is enabled.
bool ShouldShowNewBadge() const;
void invalidate_dimensions() { dimensions_.height = 0; }
bool is_dimensions_valid() const { return dimensions_.height > 0; }
// The delegate. This is only valid for the root menu item. You shouldn't
// use this directly, instead use GetDelegate() which walks the tree as
// as necessary.
MenuDelegate* delegate_ = nullptr;
// The controller for the run operation, or NULL if the menu isn't showing.
base::WeakPtr<MenuController> controller_;
// Used to detect when Cancel was invoked.
bool canceled_ = false;
// Our parent.
MenuItemView* parent_menu_item_ = nullptr;
// Type of menu. NOTE: MenuItemView doesn't itself represent SEPARATOR,
// that is handled by an entirely different view class.
Type type_ = Type::kSubMenu;
// Whether we're selected.
bool selected_ = false;
// Whether the submenu area of an ACTIONABLE_SUBMENU is selected.
bool submenu_area_of_actionable_submenu_selected_ = false;
// Command id.
int command_ = 0;
// Whether the menu item should be badged as "New" (if badging is enabled) as
// a way to highlight a new feature for users.
bool is_new_ = false;
// Submenu, created via CreateSubmenu.
SubmenuView* submenu_ = nullptr;
base::string16 title_;
base::string16 secondary_title_;
base::string16 minor_text_;
ui::ThemedVectorIcon minor_icon_;
// The icon used for |icon_view_| when a vector icon has been set instead of a
// gfx::Image.
ui::ThemedVectorIcon vector_icon_;
// Does the title have a mnemonic? Only useful on the root menu item.
bool has_mnemonics_ = false;
// Should we show the mnemonic? Mnemonics are shown if this is true or
// MenuConfig says mnemonics should be shown. Only used on the root menu item.
bool show_mnemonics_ = false;
// Set if menu has icons or icon_views (applies to root menu item only).
bool has_icons_ = false;
// Pointer to a view with a menu icon.
ImageView* icon_view_ = nullptr;
// The tooltip to show on hover for this menu item.
base::string16 tooltip_;
// Width of a menu icon area.
static int icon_area_width_;
// X-coordinate of where the label starts.
static int label_start_;
// Margins between the right of the item and the label.
static int item_right_margin_;
// Preferred height of menu items. Reset every time a menu is run.
static int pref_menu_height_;
// Cached dimensions. This is cached as text sizing calculations are quite
// costly.
mutable MenuItemDimensions dimensions_;
// Removed items to be deleted in ChildrenChanged().
std::vector<View*> removed_items_;
// Margins in pixels.
int top_margin_ = -1;
int bottom_margin_ = -1;
// Corner radius in pixels, for HIGHLIGHTED items placed at the end of a menu.
int corner_radius_ = 0;
// Horizontal icon margins in pixels, which can differ between MenuItems.
// These values will be set in the layout process.
mutable int left_icon_margin_ = 0;
mutable int right_icon_margin_ = 0;
// |menu_position_| is the requested position with respect to the bounds.
// |actual_menu_position_| is used by the controller to cache the
// position of the menu being shown.
MenuPosition requested_menu_position_ = MenuPosition::kBestFit;
MenuPosition actual_menu_position_ = MenuPosition::kBestFit;
// If set to false, the right margin will be removed for menu lines
// containing other elements.
bool use_right_margin_ = true;
// Contains an image for the checkbox or radio icon.
ImageView* radio_check_image_view_ = nullptr;
// The submenu indicator arrow icon in case the menu item has a Submenu.
ImageView* submenu_arrow_image_view_ = nullptr;
// The forced visual selection state of this item, if any.
base::Optional<bool> forced_visual_selection_;
// The vertical separator that separates the actionable and submenu regions of
// an ACTIONABLE_SUBMENU.
Separator* vertical_separator_ = nullptr;
// Whether this menu item is rendered differently to draw attention to it.
bool is_alerted_ = false;
DISALLOW_COPY_AND_ASSIGN(MenuItemView);
};
} // namespace views
#endif // UI_VIEWS_CONTROLS_MENU_MENU_ITEM_VIEW_H_