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