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 #ifndef UI_VIEWS_CONTROLS_MENU_MENU_ITEM_VIEW_H_
6 #define UI_VIEWS_CONTROLS_MENU_MENU_ITEM_VIEW_H_
7 
8 #include <string>
9 #include <vector>
10 
11 #include "base/compiler_specific.h"
12 #include "base/logging.h"
13 #include "base/macros.h"
14 #include "base/memory/weak_ptr.h"
15 #include "base/optional.h"
16 #include "base/strings/string16.h"
17 #include "build/build_config.h"
18 #include "ui/base/models/menu_separator_types.h"
19 #include "ui/gfx/geometry/insets.h"
20 #include "ui/gfx/image/image_skia.h"
21 #include "ui/native_theme/themed_vector_icon.h"
22 #include "ui/views/controls/menu/menu_controller.h"
23 #include "ui/views/controls/menu/menu_types.h"
24 #include "ui/views/view.h"
25 
26 #if defined(OS_WIN)
27 #include <windows.h>
28 #endif
29 
30 namespace views {
31 
32 namespace internal {
33 class MenuRunnerImpl;
34 }
35 
36 namespace test {
37 class TestMenuItemViewShown;
38 class TestMenuItemViewNotShown;
39 }  // namespace test
40 
41 class ImageView;
42 class MenuController;
43 class MenuDelegate;
44 class Separator;
45 class TestMenuItemView;
46 class SubmenuView;
47 
48 // MenuItemView --------------------------------------------------------------
49 
50 // MenuItemView represents a single menu item with a label and optional icon.
51 // Each MenuItemView may also contain a submenu, which in turn may contain
52 // any number of child MenuItemViews.
53 //
54 // To use a menu create an initial MenuItemView using the constructor that
55 // takes a MenuDelegate, then create any number of child menu items by way
56 // of the various AddXXX methods.
57 //
58 // MenuItemView is itself a View, which means you can add Views to each
59 // MenuItemView. This is normally NOT want you want, rather add other child
60 // Views to the submenu of the MenuItemView. Any child views of the MenuItemView
61 // that are focusable can be navigated to by way of the up/down arrow and can be
62 // activated by way of space/return keys. Activating a focusable child results
63 // in |AcceleratorPressed| being invoked. Note, that as menus try not to steal
64 // focus from the hosting window child views do not actually get focus. Instead
65 // |SetHotTracked| is used as the user navigates around.
66 //
67 // To show the menu use MenuRunner. See MenuRunner for details on how to run
68 // (show) the menu as well as for details on the life time of the menu.
69 
70 class VIEWS_EXPORT MenuItemView : public View {
71  public:
72   METADATA_HEADER(MenuItemView);
73 
74   friend class MenuController;
75 
76   // ID used to identify menu items.
77   static const int kMenuItemViewID;
78 
79   // ID used to identify empty menu items.
80   static const int kEmptyMenuItemViewID;
81 
82   // Different types of menu items.
83   enum class Type {
84     kNormal,             // Performs an action when selected.
85     kSubMenu,            // Presents a submenu within another menu.
86     kActionableSubMenu,  // A SubMenu that is also a COMMAND.
87     kCheckbox,           // Can be selected/checked to toggle a boolean state.
88     kRadio,              // Can be selected/checked among a group of choices.
89     kSeparator,          // Shows a horizontal line separator.
90     kHighlighted,        // Performs an action when selected, and has a
91                          // different colored background that merges with the
92                          // menu's rounded corners when placed at the bottom.
93     kTitle,              // Title text, does not perform any action.
94     kEmpty,              // kEmpty is a special type for empty menus that is
95                          // only used internally.
96   };
97 
98   // Where the menu should be drawn, above or below the bounds (when
99   // the bounds is non-empty).  MenuPosition::kBestFit (default) positions
100   // the menu below the bounds unless the menu does not fit on the
101   // screen and the re is more space above.
102   enum class MenuPosition { kBestFit, kAboveBounds, kBelowBounds };
103 
104   // The data structure which is used for the menu size
105   struct MenuItemDimensions {
106     MenuItemDimensions() = default;
107 
108     // Width of everything except the accelerator and children views.
109     int standard_width = 0;
110     // The width of all contained views of the item.
111     int children_width = 0;
112     // The amount of space needed to accommodate the subtext.
113     int minor_text_width = 0;
114     // The height of the menu item.
115     int height = 0;
116   };
117 
118   // Constructor for use with the top level menu item. This menu is never
119   // shown to the user, rather its use as the parent for all menu items.
120   explicit MenuItemView(MenuDelegate* delegate);
121 
122   // Overridden from View:
123   base::string16 GetTooltipText(const gfx::Point& p) const override;
124   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
125   bool HandleAccessibleAction(const ui::AXActionData& action_data) override;
126 
127   // Returns the preferred height of menu items. This is only valid when the
128   // menu is about to be shown.
pref_menu_height()129   static int pref_menu_height() { return pref_menu_height_; }
130 
131   // X-coordinate of where the label starts.
label_start()132   static int label_start() { return label_start_; }
133 
134   // Returns if a given |anchor| is a bubble or not.
135   static bool IsBubble(MenuAnchorPosition anchor);
136 
137   // Returns the accessible name to be used with screen readers. Mnemonics are
138   // removed and the menu item accelerator text is appended.
139   static base::string16 GetAccessibleNameForMenuItem(
140       const base::string16& item_text,
141       const base::string16& accelerator_text);
142 
143   // Hides and cancels the menu. This does nothing if the menu is not open.
144   void Cancel();
145 
146   // Add an item to the menu at a specified index.  ChildrenChanged() should
147   // called after adding menu items if the menu may be active.
148   MenuItemView* AddMenuItemAt(int index,
149                               int item_id,
150                               const base::string16& label,
151                               const base::string16& minor_text,
152                               const ui::ThemedVectorIcon& minor_icon,
153                               const gfx::ImageSkia& icon,
154                               const ui::ThemedVectorIcon& vector_icon,
155                               Type type,
156                               ui::MenuSeparatorType separator_style);
157 
158   // Remove the specified item from the menu. |item| will be deleted when
159   // ChildrenChanged() is invoked.
160   void RemoveMenuItem(View* item);
161 
162   // Removes all items from the menu.  The removed MenuItemViews are deleted
163   // when ChildrenChanged() is invoked.
164   void RemoveAllMenuItems();
165 
166   // Appends a normal item to this menu.
167   // item_id    The id of the item, used to identify it in delegate callbacks
168   //            or (if delegate is NULL) to identify the command associated
169   //            with this item with the controller specified in the ctor. Note
170   //            that this value should not be 0 as this has a special meaning
171   //            ("NULL command, no item selected")
172   // label      The text label shown.
173   // icon       The icon.
174   MenuItemView* AppendMenuItem(int item_id,
175                                const base::string16& label = base::string16(),
176                                const gfx::ImageSkia& icon = gfx::ImageSkia());
177 
178   // Append a submenu to this menu.
179   // The returned pointer is owned by this menu.
180   MenuItemView* AppendSubMenu(int item_id,
181                               const base::string16& label,
182                               const gfx::ImageSkia& icon = gfx::ImageSkia());
183 
184   // Adds a separator to this menu
185   void AppendSeparator();
186 
187   // Adds a separator to this menu at the specified position.
188   void AddSeparatorAt(int index);
189 
190   // All the AppendXXX methods funnel into this.
191   MenuItemView* AppendMenuItemImpl(int item_id,
192                                    const base::string16& label,
193                                    const gfx::ImageSkia& icon,
194                                    Type type);
195 
196   // Returns the view that contains child menu items. If the submenu has
197   // not been created, this creates it.
198   SubmenuView* CreateSubmenu();
199 
200   // Returns true if this menu item has a submenu.
201   bool HasSubmenu() const;
202 
203   // Returns the view containing child menu items.
204   SubmenuView* GetSubmenu() const;
205 
206   // Returns true if this menu item has a submenu and it is showing
207   bool SubmenuIsShowing() const;
208 
209   // Returns the parent menu item.
GetParentMenuItem()210   MenuItemView* GetParentMenuItem() { return parent_menu_item_; }
GetParentMenuItem()211   const MenuItemView* GetParentMenuItem() const { return parent_menu_item_; }
212 
213   // Sets/Gets the title.
214   void SetTitle(const base::string16& title);
title()215   const base::string16& title() const { return title_; }
216 
217   // Sets the minor text.
218   void SetMinorText(const base::string16& minor_text);
219 
220   // Sets the minor icon.
221   void SetMinorIcon(const ui::ThemedVectorIcon& minor_icon);
222 
223   // Returns the type of this menu.
GetType()224   const Type& GetType() const { return type_; }
225 
226   // Sets whether this item is selected. This is invoked as the user moves
227   // the mouse around the menu while open.
228   void SetSelected(bool selected);
229 
230   // Returns true if the item is selected.
IsSelected()231   bool IsSelected() const { return selected_; }
232 
233   // Sets whether the submenu area of an ACTIONABLE_SUBMENU is selected.
234   void SetSelectionOfActionableSubmenu(
235       bool submenu_area_of_actionable_submenu_selected);
236 
237   // Whether the submenu area of an ACTIONABLE_SUBMENU is selected.
IsSubmenuAreaOfActionableSubmenuSelected()238   bool IsSubmenuAreaOfActionableSubmenuSelected() const {
239     return submenu_area_of_actionable_submenu_selected_;
240   }
241 
242   // Sets the |tooltip| for a menu item view with |item_id| identifier.
243   void SetTooltip(const base::string16& tooltip, int item_id);
244 
245   // Sets the icon for the descendant identified by item_id.
246   void SetIcon(const gfx::ImageSkia& icon, int item_id);
247 
248   // Sets the icon of this menu item.
249   void SetIcon(const gfx::ImageSkia& icon);
250 
251   // Sets the icon as a vector icon which gets its color from the NativeTheme or
252   // the included color.
253   void SetIcon(const ui::ThemedVectorIcon& icon);
254 
255   // Sets the view used to render the icon. This clobbers any icon set via
256   // SetIcon(). MenuItemView takes ownership of |icon_view|.
257   void SetIconView(std::unique_ptr<ImageView> icon_view);
258 
259   void UpdateIconViewFromVectorIconAndTheme();
260 
261   // Sets the command id of this menu item.
SetCommand(int command)262   void SetCommand(int command) { command_ = command; }
263 
264   // Returns the command id of this item.
GetCommand()265   int GetCommand() const { return command_; }
266 
267   // Paints the menu item.
268   void OnPaint(gfx::Canvas* canvas) override;
269 
270   // Returns the preferred size of this item.
271   gfx::Size CalculatePreferredSize() const override;
272 
273   // Gets the preferred height for the given |width|. This is only different
274   // from GetPreferredSize().width() if the item has a child view with flexible
275   // dimensions.
276   int GetHeightForWidth(int width) const override;
277 
278   void OnThemeChanged() override;
279 
280   // Returns the bounds of the submenu part of the ACTIONABLE_SUBMENU.
281   gfx::Rect GetSubmenuAreaOfActionableSubmenu() const;
282 
283   // Return the preferred dimensions of the item in pixel.
284   const MenuItemDimensions& GetDimensions() const;
285 
286   // Returns the object responsible for controlling showing the menu.
287   MenuController* GetMenuController();
288   const MenuController* GetMenuController() const;
289 
290   // Returns the delegate. This returns the delegate of the root menu item.
291   MenuDelegate* GetDelegate();
292   const MenuDelegate* GetDelegate() const;
set_delegate(MenuDelegate * delegate)293   void set_delegate(MenuDelegate* delegate) { delegate_ = delegate; }
294 
295   // Returns the root parent, or this if this has no parent.
296   MenuItemView* GetRootMenuItem();
297   const MenuItemView* GetRootMenuItem() const;
298 
299   // Returns the mnemonic for this MenuItemView, or 0 if this MenuItemView
300   // doesn't have a mnemonic.
301   base::char16 GetMnemonic();
302 
303   // Do we have icons? This only has effect on the top menu. Turning this on
304   // makes the menus slightly wider and taller.
set_has_icons(bool has_icons)305   void set_has_icons(bool has_icons) { has_icons_ = has_icons; }
has_icons()306   bool has_icons() const { return has_icons_; }
307 
308   // Returns the descendant with the specified command.
309   MenuItemView* GetMenuItemByID(int id);
310 
311   // Invoke if you remove/add children to the menu while it's showing. This
312   // recalculates the bounds.
313   void ChildrenChanged();
314 
315   // Sizes any child views.
316   void Layout() override;
317 
318   // Returns true if the menu has mnemonics. This only useful on the root menu
319   // item.
has_mnemonics()320   bool has_mnemonics() const { return has_mnemonics_; }
321 
322   // Set top and bottom margins in pixels.  If no margin is set or a
323   // negative margin is specified then MenuConfig values are used.
324   void SetMargins(int top_margin, int bottom_margin);
325 
326   // Suppress the right margin if this is set to false.
set_use_right_margin(bool use_right_margin)327   void set_use_right_margin(bool use_right_margin) {
328     use_right_margin_ = use_right_margin;
329   }
330 
331   // Controls whether this menu has a forced visual selection state. This is
332   // used when animating item acceptance on Mac. Note that once this is set
333   // there's no way to unset it for this MenuItemView!
334   void SetForcedVisualSelection(bool selected);
335 
336   // For items of type HIGHLIGHTED only: sets the radius of the item's
337   // background. This makes the menu item's background fit its container's
338   // border radius, if they are both the same value.
339   void SetCornerRadius(int radius);
340 
341   // Shows an alert on this menu item. An alerted menu item is rendered
342   // differently to draw attention to it. This must be called before the menu is
343   // run.
344   void SetAlerted();
is_alerted()345   bool is_alerted() const { return is_alerted_; }
346 
347  protected:
348   // Creates a MenuItemView. This is used by the various AddXXX methods.
349   MenuItemView(MenuItemView* parent, int command, Type type);
350 
351   // MenuRunner owns MenuItemView and should be the only one deleting it.
352   ~MenuItemView() override;
353 
354   // View:
355   void ChildPreferredSizeChanged(View* child) override;
356 
357   // Returns the preferred size (and padding) of any children.
358   virtual gfx::Size GetChildPreferredSize() const;
359 
360   // Returns the various margins.
361   int GetTopMargin() const;
362   int GetBottomMargin() const;
363 
364  private:
365   friend class internal::MenuRunnerImpl;        // For access to ~MenuItemView.
366   friend class test::TestMenuItemViewShown;     // for access to |submenu_|;
367   friend class test::TestMenuItemViewNotShown;  // for access to |submenu_|;
368   friend class TestMenuItemView;  // For access to AddEmptyMenus();
369 
370   enum class PaintButtonMode { kNormal, kForDrag };
371 
372   // Calculates all sizes that we can from the OS.
373   //
374   // This is invoked prior to Running a menu.
375   void UpdateMenuPartSizes();
376 
377   // Called by the two constructors to initialize this menu item.
378   void Init(MenuItemView* parent, int command, MenuItemView::Type type);
379 
380   // The RunXXX methods call into this to set up the necessary state before
381   // running. |is_first_menu| is true if no menus are currently showing.
382   void PrepareForRun(bool is_first_menu,
383                      bool has_mnemonics,
384                      bool show_mnemonics);
385 
386   // Returns the flags passed to DrawStringRect.
387   int GetDrawStringFlags();
388 
389   // Returns the style for the menu text.
390   void GetLabelStyle(MenuDelegate::LabelStyle* style) const;
391 
392   // If this menu item has no children a child is added showing it has no
393   // children. Otherwise AddEmtpyMenus is recursively invoked on child menu
394   // items that have children.
395   void AddEmptyMenus();
396 
397   // Undoes the work of AddEmptyMenus.
398   void RemoveEmptyMenus();
399 
400   // Given bounds within our View, this helper routine mirrors the bounds if
401   // necessary.
402   void AdjustBoundsForRTLUI(gfx::Rect* rect) const;
403 
404   // Actual paint implementation. If mode is kForDrag, portions of the menu are
405   // not rendered.
406   void PaintButton(gfx::Canvas* canvas, PaintButtonMode mode);
407 
408   // Helper function for PaintButton(), draws the background for the button if
409   // appropriate.
410   void PaintBackground(gfx::Canvas* canvas,
411                        PaintButtonMode mode,
412                        bool render_selection);
413 
414   // Paints the right-side icon and text.
415   void PaintMinorIconAndText(gfx::Canvas* canvas,
416                              const MenuDelegate::LabelStyle& style);
417 
418   // Destroys the window used to display this menu and recursively destroys
419   // the windows used to display all descendants.
420   void DestroyAllMenuHosts();
421 
422   // Returns the text that should be displayed on the end (right) of the menu
423   // item. This will be the accelerator (if one exists).
424   base::string16 GetMinorText() const;
425 
426   // Returns the icon that should be displayed to the left of the minor text.
427   ui::ThemedVectorIcon GetMinorIcon() const;
428 
429   // Returns the text color for the current state.  |minor| specifies if the
430   // minor text or the normal text is desired.
431   SkColor GetTextColor(bool minor, bool render_selection) const;
432 
433   // Calculates and returns the MenuItemDimensions.
434   MenuItemDimensions CalculateDimensions() const;
435 
436   // Imposes MenuConfig's minimum sizes, if any, on the supplied
437   // dimensions and returns the new dimensions. It is guaranteed that:
438   //    ApplyMinimumDimensions(x).standard_width >= x.standard_width
439   //    ApplyMinimumDimensions(x).children_width == x.children_width
440   //    ApplyMinimumDimensions(x).minor_text_width == x.minor_text_width
441   //    ApplyMinimumDimensions(x).height >= x.height
442   void ApplyMinimumDimensions(MenuItemDimensions* dims) const;
443 
444   // Get the horizontal position at which to draw the menu item's label.
445   int GetLabelStartForThisItem() const;
446 
447   // Used by MenuController to cache the menu position in use by the
448   // active menu.
actual_menu_position()449   MenuPosition actual_menu_position() const { return actual_menu_position_; }
set_actual_menu_position(MenuPosition actual_menu_position)450   void set_actual_menu_position(MenuPosition actual_menu_position) {
451     actual_menu_position_ = actual_menu_position;
452   }
453 
set_controller(MenuController * controller)454   void set_controller(MenuController* controller) {
455     if (controller)
456       controller_ = controller->AsWeakPtr();
457     else
458       controller_.reset();
459   }
460 
461   // Returns true if this MenuItemView contains a single child
462   // that is responsible for rendering the content.
463   bool IsContainer() const;
464 
465   // Gets the child view margins. Should only be called when |IsContainer()| is
466   // true.
467   gfx::Insets GetContainerMargins() const;
468 
469   // Returns number of child views excluding icon_view.
470   int NonIconChildViewsCount() const;
471 
472   // Returns the max icon width; recurses over submenus.
473   int GetMaxIconViewWidth() const;
474 
475   // Returns true if the menu has items with a checkbox or a radio button.
476   bool HasChecksOrRadioButtons() const;
477 
invalidate_dimensions()478   void invalidate_dimensions() { dimensions_.height = 0; }
is_dimensions_valid()479   bool is_dimensions_valid() const { return dimensions_.height > 0; }
480 
481   // The delegate. This is only valid for the root menu item. You shouldn't
482   // use this directly, instead use GetDelegate() which walks the tree as
483   // as necessary.
484   MenuDelegate* delegate_ = nullptr;
485 
486   // The controller for the run operation, or NULL if the menu isn't showing.
487   base::WeakPtr<MenuController> controller_;
488 
489   // Used to detect when Cancel was invoked.
490   bool canceled_ = false;
491 
492   // Our parent.
493   MenuItemView* parent_menu_item_ = nullptr;
494 
495   // Type of menu. NOTE: MenuItemView doesn't itself represent SEPARATOR,
496   // that is handled by an entirely different view class.
497   Type type_ = Type::kSubMenu;
498 
499   // Whether we're selected.
500   bool selected_ = false;
501 
502   // Whether the submenu area of an ACTIONABLE_SUBMENU is selected.
503   bool submenu_area_of_actionable_submenu_selected_ = false;
504 
505   // Command id.
506   int command_ = 0;
507 
508   // Submenu, created via CreateSubmenu.
509   SubmenuView* submenu_ = nullptr;
510 
511   // Title.
512   base::string16 title_;
513 
514   // Minor text.
515   base::string16 minor_text_;
516 
517   // Minor icon.
518   ui::ThemedVectorIcon minor_icon_;
519 
520   // The icon used for |icon_view_| when a vector icon has been set instead of a
521   // gfx::Image.
522   ui::ThemedVectorIcon vector_icon_;
523 
524   // Does the title have a mnemonic? Only useful on the root menu item.
525   bool has_mnemonics_ = false;
526 
527   // Should we show the mnemonic? Mnemonics are shown if this is true or
528   // MenuConfig says mnemonics should be shown. Only used on the root menu item.
529   bool show_mnemonics_ = false;
530 
531   // Set if menu has icons or icon_views (applies to root menu item only).
532   bool has_icons_ = false;
533 
534   // Pointer to a view with a menu icon.
535   ImageView* icon_view_ = nullptr;
536 
537   // The tooltip to show on hover for this menu item.
538   base::string16 tooltip_;
539 
540   // Width of a menu icon area.
541   static int icon_area_width_;
542 
543   // X-coordinate of where the label starts.
544   static int label_start_;
545 
546   // Margins between the right of the item and the label.
547   static int item_right_margin_;
548 
549   // Preferred height of menu items. Reset every time a menu is run.
550   static int pref_menu_height_;
551 
552   // Cached dimensions. This is cached as text sizing calculations are quite
553   // costly.
554   mutable MenuItemDimensions dimensions_;
555 
556   // Removed items to be deleted in ChildrenChanged().
557   std::vector<View*> removed_items_;
558 
559   // Margins in pixels.
560   int top_margin_ = -1;
561   int bottom_margin_ = -1;
562 
563   // Corner radius in pixels, for HIGHLIGHTED items placed at the end of a menu.
564   int corner_radius_ = 0;
565 
566   // Horizontal icon margins in pixels, which can differ between MenuItems.
567   // These values will be set in the layout process.
568   mutable int left_icon_margin_ = 0;
569   mutable int right_icon_margin_ = 0;
570 
571   // |menu_position_| is the requested position with respect to the bounds.
572   // |actual_menu_position_| is used by the controller to cache the
573   // position of the menu being shown.
574   MenuPosition requested_menu_position_ = MenuPosition::kBestFit;
575   MenuPosition actual_menu_position_ = MenuPosition::kBestFit;
576 
577   // If set to false, the right margin will be removed for menu lines
578   // containing other elements.
579   bool use_right_margin_ = true;
580 
581   // Contains an image for the checkbox or radio icon.
582   ImageView* radio_check_image_view_ = nullptr;
583 
584   // The submenu indicator arrow icon in case the menu item has a Submenu.
585   ImageView* submenu_arrow_image_view_ = nullptr;
586 
587   // The forced visual selection state of this item, if any.
588   base::Optional<bool> forced_visual_selection_;
589 
590   // The vertical separator that separates the actionable and submenu regions of
591   // an ACTIONABLE_SUBMENU.
592   Separator* vertical_separator_ = nullptr;
593 
594   // Whether this menu item is rendered differently to draw attention to it.
595   bool is_alerted_ = false;
596 
597   DISALLOW_COPY_AND_ASSIGN(MenuItemView);
598 };
599 
600 }  // namespace views
601 
602 #endif  // UI_VIEWS_CONTROLS_MENU_MENU_ITEM_VIEW_H_
603