| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "ui/views/controls/menu/menu_item_view.h" | |
| 6 | |
| 7 #include "base/i18n/case_conversion.h" | |
| 8 #include "base/stl_util.h" | |
| 9 #include "base/strings/utf_string_conversions.h" | |
| 10 #include "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/widget/widget.h" | |
| 30 | |
| 31 namespace views { | |
| 32 | |
| 33 namespace { | |
| 34 | |
| 35 // EmptyMenuMenuItem --------------------------------------------------------- | |
| 36 | |
| 37 // EmptyMenuMenuItem is used when a menu has no menu items. EmptyMenuMenuItem | |
| 38 // is itself a MenuItemView, but it uses a different ID so that it isn't | |
| 39 // identified as a MenuItemView. | |
| 40 | |
| 41 class EmptyMenuMenuItem : public MenuItemView { | |
| 42 public: | |
| 43 explicit EmptyMenuMenuItem(MenuItemView* parent) | |
| 44 : MenuItemView(parent, 0, EMPTY) { | |
| 45 // Set this so that we're not identified as a normal menu item. | |
| 46 set_id(kEmptyMenuItemViewID); | |
| 47 SetTitle(l10n_util::GetStringUTF16(IDS_APP_MENU_EMPTY_SUBMENU)); | |
| 48 SetEnabled(false); | |
| 49 } | |
| 50 | |
| 51 virtual bool GetTooltipText(const gfx::Point& p, | |
| 52 base::string16* tooltip) const override { | |
| 53 // Empty menu items shouldn't have a tooltip. | |
| 54 return false; | |
| 55 } | |
| 56 | |
| 57 private: | |
| 58 DISALLOW_COPY_AND_ASSIGN(EmptyMenuMenuItem); | |
| 59 }; | |
| 60 | |
| 61 } // namespace | |
| 62 | |
| 63 // Padding between child views. | |
| 64 static const int kChildXPadding = 8; | |
| 65 | |
| 66 // MenuItemView --------------------------------------------------------------- | |
| 67 | |
| 68 // static | |
| 69 const int MenuItemView::kMenuItemViewID = 1001; | |
| 70 | |
| 71 // static | |
| 72 const int MenuItemView::kEmptyMenuItemViewID = | |
| 73 MenuItemView::kMenuItemViewID + 1; | |
| 74 | |
| 75 // static | |
| 76 int MenuItemView::icon_area_width_ = 0; | |
| 77 | |
| 78 // static | |
| 79 int MenuItemView::label_start_; | |
| 80 | |
| 81 // static | |
| 82 int MenuItemView::item_right_margin_; | |
| 83 | |
| 84 // static | |
| 85 int MenuItemView::pref_menu_height_; | |
| 86 | |
| 87 // static | |
| 88 const char MenuItemView::kViewClassName[] = "MenuItemView"; | |
| 89 | |
| 90 MenuItemView::MenuItemView(MenuDelegate* delegate) | |
| 91 : delegate_(delegate), | |
| 92 controller_(NULL), | |
| 93 canceled_(false), | |
| 94 parent_menu_item_(NULL), | |
| 95 type_(SUBMENU), | |
| 96 selected_(false), | |
| 97 command_(0), | |
| 98 submenu_(NULL), | |
| 99 has_mnemonics_(false), | |
| 100 show_mnemonics_(false), | |
| 101 has_icons_(false), | |
| 102 icon_view_(NULL), | |
| 103 top_margin_(-1), | |
| 104 bottom_margin_(-1), | |
| 105 left_icon_margin_(0), | |
| 106 right_icon_margin_(0), | |
| 107 requested_menu_position_(POSITION_BEST_FIT), | |
| 108 actual_menu_position_(requested_menu_position_), | |
| 109 use_right_margin_(true) { | |
| 110 // NOTE: don't check the delegate for NULL, UpdateMenuPartSizes() supplies a | |
| 111 // NULL delegate. | |
| 112 Init(NULL, 0, SUBMENU, delegate); | |
| 113 } | |
| 114 | |
| 115 void MenuItemView::ChildPreferredSizeChanged(View* child) { | |
| 116 invalidate_dimensions(); | |
| 117 PreferredSizeChanged(); | |
| 118 } | |
| 119 | |
| 120 bool MenuItemView::GetTooltipText(const gfx::Point& p, | |
| 121 base::string16* tooltip) const { | |
| 122 *tooltip = tooltip_; | |
| 123 if (!tooltip->empty()) | |
| 124 return true; | |
| 125 | |
| 126 if (GetType() == SEPARATOR) | |
| 127 return false; | |
| 128 | |
| 129 const MenuController* controller = GetMenuController(); | |
| 130 if (!controller || controller->exit_type() != MenuController::EXIT_NONE) { | |
| 131 // Either the menu has been closed or we're in the process of closing the | |
| 132 // menu. Don't attempt to query the delegate as it may no longer be valid. | |
| 133 return false; | |
| 134 } | |
| 135 | |
| 136 const MenuItemView* root_menu_item = GetRootMenuItem(); | |
| 137 if (root_menu_item->canceled_) { | |
| 138 // TODO(sky): if |canceled_| is true, controller->exit_type() should be | |
| 139 // something other than EXIT_NONE, but crash reports seem to indicate | |
| 140 // otherwise. Figure out why this is needed. | |
| 141 return false; | |
| 142 } | |
| 143 | |
| 144 const MenuDelegate* delegate = GetDelegate(); | |
| 145 CHECK(delegate); | |
| 146 gfx::Point location(p); | |
| 147 ConvertPointToScreen(this, &location); | |
| 148 *tooltip = delegate->GetTooltipText(command_, location); | |
| 149 return !tooltip->empty(); | |
| 150 } | |
| 151 | |
| 152 void MenuItemView::GetAccessibleState(ui::AXViewState* state) { | |
| 153 state->role = ui::AX_ROLE_MENU_ITEM; | |
| 154 | |
| 155 base::string16 item_text; | |
| 156 if (IsContainer()) { | |
| 157 // The first child is taking over, just use its accessible name instead of | |
| 158 // |title_|. | |
| 159 View* child = child_at(0); | |
| 160 ui::AXViewState state; | |
| 161 child->GetAccessibleState(&state); | |
| 162 item_text = state.name; | |
| 163 } else { | |
| 164 item_text = title_; | |
| 165 } | |
| 166 state->name = GetAccessibleNameForMenuItem(item_text, GetMinorText()); | |
| 167 | |
| 168 switch (GetType()) { | |
| 169 case SUBMENU: | |
| 170 state->AddStateFlag(ui::AX_STATE_HASPOPUP); | |
| 171 break; | |
| 172 case CHECKBOX: | |
| 173 case RADIO: | |
| 174 if (GetDelegate()->IsItemChecked(GetCommand())) | |
| 175 state->AddStateFlag(ui::AX_STATE_CHECKED); | |
| 176 break; | |
| 177 case NORMAL: | |
| 178 case SEPARATOR: | |
| 179 case EMPTY: | |
| 180 // No additional accessibility states currently for these menu states. | |
| 181 break; | |
| 182 } | |
| 183 } | |
| 184 | |
| 185 // static | |
| 186 bool MenuItemView::IsBubble(MenuAnchorPosition anchor) { | |
| 187 return anchor == MENU_ANCHOR_BUBBLE_LEFT || | |
| 188 anchor == MENU_ANCHOR_BUBBLE_RIGHT || | |
| 189 anchor == MENU_ANCHOR_BUBBLE_ABOVE || | |
| 190 anchor == MENU_ANCHOR_BUBBLE_BELOW; | |
| 191 } | |
| 192 | |
| 193 // static | |
| 194 base::string16 MenuItemView::GetAccessibleNameForMenuItem( | |
| 195 const base::string16& item_text, const base::string16& minor_text) { | |
| 196 base::string16 accessible_name = item_text; | |
| 197 | |
| 198 // Filter out the "&" for accessibility clients. | |
| 199 size_t index = 0; | |
| 200 const base::char16 amp = '&'; | |
| 201 while ((index = accessible_name.find(amp, index)) != base::string16::npos && | |
| 202 index + 1 < accessible_name.length()) { | |
| 203 accessible_name.replace(index, accessible_name.length() - index, | |
| 204 accessible_name.substr(index + 1)); | |
| 205 | |
| 206 // Special case for "&&" (escaped for "&"). | |
| 207 if (accessible_name[index] == '&') | |
| 208 ++index; | |
| 209 } | |
| 210 | |
| 211 // Append subtext. | |
| 212 if (!minor_text.empty()) { | |
| 213 accessible_name.push_back(' '); | |
| 214 accessible_name.append(minor_text); | |
| 215 } | |
| 216 | |
| 217 return accessible_name; | |
| 218 } | |
| 219 | |
| 220 void MenuItemView::Cancel() { | |
| 221 if (controller_ && !canceled_) { | |
| 222 canceled_ = true; | |
| 223 controller_->Cancel(MenuController::EXIT_ALL); | |
| 224 } | |
| 225 } | |
| 226 | |
| 227 MenuItemView* MenuItemView::AddMenuItemAt( | |
| 228 int index, | |
| 229 int item_id, | |
| 230 const base::string16& label, | |
| 231 const base::string16& sublabel, | |
| 232 const base::string16& minor_text, | |
| 233 const gfx::ImageSkia& icon, | |
| 234 Type type, | |
| 235 ui::MenuSeparatorType separator_style) { | |
| 236 DCHECK_NE(type, EMPTY); | |
| 237 DCHECK_LE(0, index); | |
| 238 if (!submenu_) | |
| 239 CreateSubmenu(); | |
| 240 DCHECK_GE(submenu_->child_count(), index); | |
| 241 if (type == SEPARATOR) { | |
| 242 submenu_->AddChildViewAt(new MenuSeparator(this, separator_style), index); | |
| 243 return NULL; | |
| 244 } | |
| 245 MenuItemView* item = new MenuItemView(this, item_id, type); | |
| 246 if (label.empty() && GetDelegate()) | |
| 247 item->SetTitle(GetDelegate()->GetLabel(item_id)); | |
| 248 else | |
| 249 item->SetTitle(label); | |
| 250 item->SetSubtitle(sublabel); | |
| 251 item->SetMinorText(minor_text); | |
| 252 if (!icon.isNull()) | |
| 253 item->SetIcon(icon); | |
| 254 if (type == SUBMENU) | |
| 255 item->CreateSubmenu(); | |
| 256 if (GetDelegate() && !GetDelegate()->IsCommandVisible(item_id)) | |
| 257 item->SetVisible(false); | |
| 258 submenu_->AddChildViewAt(item, index); | |
| 259 return item; | |
| 260 } | |
| 261 | |
| 262 void MenuItemView::RemoveMenuItemAt(int index) { | |
| 263 DCHECK(submenu_); | |
| 264 DCHECK_LE(0, index); | |
| 265 DCHECK_GT(submenu_->child_count(), index); | |
| 266 | |
| 267 View* item = submenu_->child_at(index); | |
| 268 DCHECK(item); | |
| 269 submenu_->RemoveChildView(item); | |
| 270 | |
| 271 // RemoveChildView() does not delete the item, which is a good thing | |
| 272 // in case a submenu is being displayed while items are being removed. | |
| 273 // Deletion will be done by ChildrenChanged() or at destruction. | |
| 274 removed_items_.push_back(item); | |
| 275 } | |
| 276 | |
| 277 MenuItemView* MenuItemView::AppendMenuItem(int item_id, | |
| 278 const base::string16& label, | |
| 279 Type type) { | |
| 280 return AppendMenuItemImpl(item_id, label, base::string16(), base::string16(), | |
| 281 gfx::ImageSkia(), type, ui::NORMAL_SEPARATOR); | |
| 282 } | |
| 283 | |
| 284 MenuItemView* MenuItemView::AppendSubMenu(int item_id, | |
| 285 const base::string16& label) { | |
| 286 return AppendMenuItemImpl(item_id, label, base::string16(), base::string16(), | |
| 287 gfx::ImageSkia(), SUBMENU, ui::NORMAL_SEPARATOR); | |
| 288 } | |
| 289 | |
| 290 MenuItemView* MenuItemView::AppendSubMenuWithIcon(int item_id, | |
| 291 const base::string16& label, | |
| 292 const gfx::ImageSkia& icon) { | |
| 293 return AppendMenuItemImpl(item_id, label, base::string16(), base::string16(), | |
| 294 icon, SUBMENU, ui::NORMAL_SEPARATOR); | |
| 295 } | |
| 296 | |
| 297 MenuItemView* MenuItemView::AppendMenuItemWithLabel( | |
| 298 int item_id, | |
| 299 const base::string16& label) { | |
| 300 return AppendMenuItem(item_id, label, NORMAL); | |
| 301 } | |
| 302 | |
| 303 MenuItemView* MenuItemView::AppendDelegateMenuItem(int item_id) { | |
| 304 return AppendMenuItem(item_id, base::string16(), NORMAL); | |
| 305 } | |
| 306 | |
| 307 void MenuItemView::AppendSeparator() { | |
| 308 AppendMenuItemImpl(0, base::string16(), base::string16(), base::string16(), | |
| 309 gfx::ImageSkia(), SEPARATOR, ui::NORMAL_SEPARATOR); | |
| 310 } | |
| 311 | |
| 312 MenuItemView* MenuItemView::AppendMenuItemWithIcon(int item_id, | |
| 313 const base::string16& label, | |
| 314 const gfx::ImageSkia& icon) { | |
| 315 return AppendMenuItemImpl(item_id, label, base::string16(), base::string16(), | |
| 316 icon, NORMAL, ui::NORMAL_SEPARATOR); | |
| 317 } | |
| 318 | |
| 319 MenuItemView* MenuItemView::AppendMenuItemImpl( | |
| 320 int item_id, | |
| 321 const base::string16& label, | |
| 322 const base::string16& sublabel, | |
| 323 const base::string16& minor_text, | |
| 324 const gfx::ImageSkia& icon, | |
| 325 Type type, | |
| 326 ui::MenuSeparatorType separator_style) { | |
| 327 const int index = submenu_ ? submenu_->child_count() : 0; | |
| 328 return AddMenuItemAt(index, item_id, label, sublabel, minor_text, icon, type, | |
| 329 separator_style); | |
| 330 } | |
| 331 | |
| 332 SubmenuView* MenuItemView::CreateSubmenu() { | |
| 333 if (!submenu_) | |
| 334 submenu_ = new SubmenuView(this); | |
| 335 return submenu_; | |
| 336 } | |
| 337 | |
| 338 bool MenuItemView::HasSubmenu() const { | |
| 339 return (submenu_ != NULL); | |
| 340 } | |
| 341 | |
| 342 SubmenuView* MenuItemView::GetSubmenu() const { | |
| 343 return submenu_; | |
| 344 } | |
| 345 | |
| 346 void MenuItemView::SetTitle(const base::string16& title) { | |
| 347 title_ = title; | |
| 348 invalidate_dimensions(); // Triggers preferred size recalculation. | |
| 349 } | |
| 350 | |
| 351 void MenuItemView::SetSubtitle(const base::string16& subtitle) { | |
| 352 subtitle_ = subtitle; | |
| 353 invalidate_dimensions(); // Triggers preferred size recalculation. | |
| 354 } | |
| 355 | |
| 356 void MenuItemView::SetMinorText(const base::string16& minor_text) { | |
| 357 minor_text_ = minor_text; | |
| 358 invalidate_dimensions(); // Triggers preferred size recalculation. | |
| 359 } | |
| 360 | |
| 361 void MenuItemView::SetSelected(bool selected) { | |
| 362 selected_ = selected; | |
| 363 SchedulePaint(); | |
| 364 } | |
| 365 | |
| 366 void MenuItemView::SetTooltip(const base::string16& tooltip, int item_id) { | |
| 367 MenuItemView* item = GetMenuItemByID(item_id); | |
| 368 DCHECK(item); | |
| 369 item->tooltip_ = tooltip; | |
| 370 } | |
| 371 | |
| 372 void MenuItemView::SetIcon(const gfx::ImageSkia& icon, int item_id) { | |
| 373 MenuItemView* item = GetMenuItemByID(item_id); | |
| 374 DCHECK(item); | |
| 375 item->SetIcon(icon); | |
| 376 } | |
| 377 | |
| 378 void MenuItemView::SetIcon(const gfx::ImageSkia& icon) { | |
| 379 if (icon.isNull()) { | |
| 380 SetIconView(NULL); | |
| 381 return; | |
| 382 } | |
| 383 | |
| 384 ImageView* icon_view = new ImageView(); | |
| 385 icon_view->SetImage(&icon); | |
| 386 SetIconView(icon_view); | |
| 387 } | |
| 388 | |
| 389 void MenuItemView::SetIconView(View* icon_view) { | |
| 390 if (icon_view_) { | |
| 391 RemoveChildView(icon_view_); | |
| 392 delete icon_view_; | |
| 393 icon_view_ = NULL; | |
| 394 } | |
| 395 if (icon_view) { | |
| 396 AddChildView(icon_view); | |
| 397 icon_view_ = icon_view; | |
| 398 } | |
| 399 Layout(); | |
| 400 SchedulePaint(); | |
| 401 } | |
| 402 | |
| 403 void MenuItemView::OnPaint(gfx::Canvas* canvas) { | |
| 404 PaintButton(canvas, PB_NORMAL); | |
| 405 } | |
| 406 | |
| 407 gfx::Size MenuItemView::GetPreferredSize() const { | |
| 408 const MenuItemDimensions& dimensions(GetDimensions()); | |
| 409 return gfx::Size(dimensions.standard_width + dimensions.children_width, | |
| 410 dimensions.height); | |
| 411 } | |
| 412 | |
| 413 int MenuItemView::GetHeightForWidth(int width) const { | |
| 414 // If this isn't a container, we can just use the preferred size's height. | |
| 415 if (!IsContainer()) | |
| 416 return GetPreferredSize().height(); | |
| 417 | |
| 418 int height = child_at(0)->GetHeightForWidth(width); | |
| 419 if (!icon_view_ && GetRootMenuItem()->has_icons()) | |
| 420 height = std::max(height, GetMenuConfig().check_height); | |
| 421 height += GetBottomMargin() + GetTopMargin(); | |
| 422 | |
| 423 return height; | |
| 424 } | |
| 425 | |
| 426 const MenuItemView::MenuItemDimensions& MenuItemView::GetDimensions() const { | |
| 427 if (!is_dimensions_valid()) | |
| 428 dimensions_ = CalculateDimensions(); | |
| 429 DCHECK(is_dimensions_valid()); | |
| 430 return dimensions_; | |
| 431 } | |
| 432 | |
| 433 MenuController* MenuItemView::GetMenuController() { | |
| 434 return GetRootMenuItem()->controller_; | |
| 435 } | |
| 436 | |
| 437 const MenuController* MenuItemView::GetMenuController() const { | |
| 438 return GetRootMenuItem()->controller_; | |
| 439 } | |
| 440 | |
| 441 MenuDelegate* MenuItemView::GetDelegate() { | |
| 442 return GetRootMenuItem()->delegate_; | |
| 443 } | |
| 444 | |
| 445 const MenuDelegate* MenuItemView::GetDelegate() const { | |
| 446 return GetRootMenuItem()->delegate_; | |
| 447 } | |
| 448 | |
| 449 MenuItemView* MenuItemView::GetRootMenuItem() { | |
| 450 return const_cast<MenuItemView*>( | |
| 451 static_cast<const MenuItemView*>(this)->GetRootMenuItem()); | |
| 452 } | |
| 453 | |
| 454 const MenuItemView* MenuItemView::GetRootMenuItem() const { | |
| 455 const MenuItemView* item = this; | |
| 456 for (const MenuItemView* parent = GetParentMenuItem(); parent; | |
| 457 parent = item->GetParentMenuItem()) | |
| 458 item = parent; | |
| 459 return item; | |
| 460 } | |
| 461 | |
| 462 base::char16 MenuItemView::GetMnemonic() { | |
| 463 if (!GetRootMenuItem()->has_mnemonics_) | |
| 464 return 0; | |
| 465 | |
| 466 size_t index = 0; | |
| 467 do { | |
| 468 index = title_.find('&', index); | |
| 469 if (index != base::string16::npos) { | |
| 470 if (index + 1 != title_.size() && title_[index + 1] != '&') { | |
| 471 base::char16 char_array[] = { title_[index + 1], 0 }; | |
| 472 // TODO(jshin): What about Turkish locale? See http://crbug.com/81719. | |
| 473 // If the mnemonic is capital I and the UI language is Turkish, | |
| 474 // lowercasing it results in 'small dotless i', which is different | |
| 475 // from a 'dotted i'. Similar issues may exist for az and lt locales. | |
| 476 return base::i18n::ToLower(char_array)[0]; | |
| 477 } | |
| 478 index++; | |
| 479 } | |
| 480 } while (index != base::string16::npos); | |
| 481 return 0; | |
| 482 } | |
| 483 | |
| 484 MenuItemView* MenuItemView::GetMenuItemByID(int id) { | |
| 485 if (GetCommand() == id) | |
| 486 return this; | |
| 487 if (!HasSubmenu()) | |
| 488 return NULL; | |
| 489 for (int i = 0; i < GetSubmenu()->child_count(); ++i) { | |
| 490 View* child = GetSubmenu()->child_at(i); | |
| 491 if (child->id() == MenuItemView::kMenuItemViewID) { | |
| 492 MenuItemView* result = static_cast<MenuItemView*>(child)-> | |
| 493 GetMenuItemByID(id); | |
| 494 if (result) | |
| 495 return result; | |
| 496 } | |
| 497 } | |
| 498 return NULL; | |
| 499 } | |
| 500 | |
| 501 void MenuItemView::ChildrenChanged() { | |
| 502 MenuController* controller = GetMenuController(); | |
| 503 if (controller) { | |
| 504 // Handles the case where we were empty and are no longer empty. | |
| 505 RemoveEmptyMenus(); | |
| 506 | |
| 507 // Handles the case where we were not empty, but now are. | |
| 508 AddEmptyMenus(); | |
| 509 | |
| 510 controller->MenuChildrenChanged(this); | |
| 511 | |
| 512 if (submenu_) { | |
| 513 // Force a paint and layout. This handles the case of the top | |
| 514 // level window's size remaining the same, resulting in no | |
| 515 // change to the submenu's size and no layout. | |
| 516 submenu_->Layout(); | |
| 517 submenu_->SchedulePaint(); | |
| 518 // Update the menu selection after layout. | |
| 519 controller->UpdateSubmenuSelection(submenu_); | |
| 520 } | |
| 521 } | |
| 522 | |
| 523 STLDeleteElements(&removed_items_); | |
| 524 } | |
| 525 | |
| 526 void MenuItemView::Layout() { | |
| 527 if (!has_children()) | |
| 528 return; | |
| 529 | |
| 530 if (IsContainer()) { | |
| 531 View* child = child_at(0); | |
| 532 gfx::Size size = child->GetPreferredSize(); | |
| 533 child->SetBounds(0, GetTopMargin(), size.width(), size.height()); | |
| 534 } else { | |
| 535 // Child views are laid out right aligned and given the full height. To | |
| 536 // right align start with the last view and progress to the first. | |
| 537 int x = width() - (use_right_margin_ ? item_right_margin_ : 0); | |
| 538 for (int i = child_count() - 1; i >= 0; --i) { | |
| 539 View* child = child_at(i); | |
| 540 if (icon_view_ && (icon_view_ == child)) | |
| 541 continue; | |
| 542 int width = child->GetPreferredSize().width(); | |
| 543 child->SetBounds(x - width, 0, width, height()); | |
| 544 x -= width - kChildXPadding; | |
| 545 } | |
| 546 // Position |icon_view|. | |
| 547 const MenuConfig& config = GetMenuConfig(); | |
| 548 if (icon_view_) { | |
| 549 icon_view_->SizeToPreferredSize(); | |
| 550 gfx::Size size = icon_view_->GetPreferredSize(); | |
| 551 int x = config.item_left_margin + left_icon_margin_ + | |
| 552 (icon_area_width_ - size.width()) / 2; | |
| 553 if (type_ == CHECKBOX || type_ == RADIO) | |
| 554 x = label_start_; | |
| 555 int y = | |
| 556 (height() + GetTopMargin() - GetBottomMargin() - size.height()) / 2; | |
| 557 icon_view_->SetPosition(gfx::Point(x, y)); | |
| 558 } | |
| 559 } | |
| 560 } | |
| 561 | |
| 562 void MenuItemView::SetMargins(int top_margin, int bottom_margin) { | |
| 563 top_margin_ = top_margin; | |
| 564 bottom_margin_ = bottom_margin; | |
| 565 | |
| 566 invalidate_dimensions(); | |
| 567 } | |
| 568 | |
| 569 const MenuConfig& MenuItemView::GetMenuConfig() const { | |
| 570 const MenuController* controller = GetMenuController(); | |
| 571 if (controller) | |
| 572 return controller->menu_config_; | |
| 573 return MenuConfig::instance(NULL); | |
| 574 } | |
| 575 | |
| 576 MenuItemView::MenuItemView(MenuItemView* parent, | |
| 577 int command, | |
| 578 MenuItemView::Type type) | |
| 579 : delegate_(NULL), | |
| 580 controller_(NULL), | |
| 581 canceled_(false), | |
| 582 parent_menu_item_(parent), | |
| 583 type_(type), | |
| 584 selected_(false), | |
| 585 command_(command), | |
| 586 submenu_(NULL), | |
| 587 has_mnemonics_(false), | |
| 588 show_mnemonics_(false), | |
| 589 has_icons_(false), | |
| 590 icon_view_(NULL), | |
| 591 top_margin_(-1), | |
| 592 bottom_margin_(-1), | |
| 593 left_icon_margin_(0), | |
| 594 right_icon_margin_(0), | |
| 595 requested_menu_position_(POSITION_BEST_FIT), | |
| 596 actual_menu_position_(requested_menu_position_), | |
| 597 use_right_margin_(true) { | |
| 598 Init(parent, command, type, NULL); | |
| 599 } | |
| 600 | |
| 601 MenuItemView::~MenuItemView() { | |
| 602 delete submenu_; | |
| 603 STLDeleteElements(&removed_items_); | |
| 604 } | |
| 605 | |
| 606 const char* MenuItemView::GetClassName() const { | |
| 607 return kViewClassName; | |
| 608 } | |
| 609 | |
| 610 // Calculates all sizes that we can from the OS. | |
| 611 // | |
| 612 // This is invoked prior to Running a menu. | |
| 613 void MenuItemView::UpdateMenuPartSizes() { | |
| 614 const MenuConfig& config = GetMenuConfig(); | |
| 615 | |
| 616 item_right_margin_ = config.label_to_arrow_padding + config.arrow_width + | |
| 617 config.arrow_to_edge_padding; | |
| 618 icon_area_width_ = config.check_width; | |
| 619 if (has_icons_) | |
| 620 icon_area_width_ = std::max(icon_area_width_, GetMaxIconViewWidth()); | |
| 621 | |
| 622 label_start_ = config.item_left_margin + icon_area_width_; | |
| 623 int padding = 0; | |
| 624 if (config.always_use_icon_to_label_padding) { | |
| 625 padding = config.icon_to_label_padding; | |
| 626 } else if (config.render_gutter) { | |
| 627 padding = config.item_left_margin; | |
| 628 } else { | |
| 629 padding = (has_icons_ || HasChecksOrRadioButtons()) ? | |
| 630 config.icon_to_label_padding : 0; | |
| 631 } | |
| 632 label_start_ += padding; | |
| 633 | |
| 634 if (config.render_gutter) | |
| 635 label_start_ += config.gutter_width + config.gutter_to_label; | |
| 636 | |
| 637 EmptyMenuMenuItem menu_item(this); | |
| 638 menu_item.set_controller(GetMenuController()); | |
| 639 pref_menu_height_ = menu_item.GetPreferredSize().height(); | |
| 640 } | |
| 641 | |
| 642 void MenuItemView::Init(MenuItemView* parent, | |
| 643 int command, | |
| 644 MenuItemView::Type type, | |
| 645 MenuDelegate* delegate) { | |
| 646 delegate_ = delegate; | |
| 647 controller_ = NULL; | |
| 648 canceled_ = false; | |
| 649 parent_menu_item_ = parent; | |
| 650 type_ = type; | |
| 651 selected_ = false; | |
| 652 command_ = command; | |
| 653 submenu_ = NULL; | |
| 654 show_mnemonics_ = false; | |
| 655 // Assign our ID, this allows SubmenuItemView to find MenuItemViews. | |
| 656 set_id(kMenuItemViewID); | |
| 657 has_icons_ = false; | |
| 658 | |
| 659 // Don't request enabled status from the root menu item as it is just | |
| 660 // a container for real items. EMPTY items will be disabled. | |
| 661 MenuDelegate* root_delegate = GetDelegate(); | |
| 662 if (parent && type != EMPTY && root_delegate) | |
| 663 SetEnabled(root_delegate->IsCommandEnabled(command)); | |
| 664 } | |
| 665 | |
| 666 void MenuItemView::PrepareForRun(bool is_first_menu, | |
| 667 bool has_mnemonics, | |
| 668 bool show_mnemonics) { | |
| 669 // Currently we only support showing the root. | |
| 670 DCHECK(!parent_menu_item_); | |
| 671 | |
| 672 // Force us to have a submenu. | |
| 673 CreateSubmenu(); | |
| 674 actual_menu_position_ = requested_menu_position_; | |
| 675 canceled_ = false; | |
| 676 | |
| 677 has_mnemonics_ = has_mnemonics; | |
| 678 show_mnemonics_ = has_mnemonics && show_mnemonics; | |
| 679 | |
| 680 AddEmptyMenus(); | |
| 681 | |
| 682 if (is_first_menu) { | |
| 683 // Only update the menu size if there are no menus showing, otherwise | |
| 684 // things may shift around. | |
| 685 UpdateMenuPartSizes(); | |
| 686 } | |
| 687 } | |
| 688 | |
| 689 int MenuItemView::GetDrawStringFlags() { | |
| 690 int flags = 0; | |
| 691 if (base::i18n::IsRTL()) | |
| 692 flags |= gfx::Canvas::TEXT_ALIGN_RIGHT; | |
| 693 else | |
| 694 flags |= gfx::Canvas::TEXT_ALIGN_LEFT; | |
| 695 | |
| 696 if (GetRootMenuItem()->has_mnemonics_) { | |
| 697 if (GetMenuConfig().show_mnemonics || GetRootMenuItem()->show_mnemonics_) { | |
| 698 flags |= gfx::Canvas::SHOW_PREFIX; | |
| 699 } else { | |
| 700 flags |= gfx::Canvas::HIDE_PREFIX; | |
| 701 } | |
| 702 } | |
| 703 return flags; | |
| 704 } | |
| 705 | |
| 706 const gfx::FontList& MenuItemView::GetFontList() const { | |
| 707 const MenuDelegate* delegate = GetDelegate(); | |
| 708 if (delegate) { | |
| 709 const gfx::FontList* font_list = delegate->GetLabelFontList(GetCommand()); | |
| 710 if (font_list) | |
| 711 return *font_list; | |
| 712 } | |
| 713 return GetMenuConfig().font_list; | |
| 714 } | |
| 715 | |
| 716 void MenuItemView::AddEmptyMenus() { | |
| 717 DCHECK(HasSubmenu()); | |
| 718 if (!submenu_->has_children()) { | |
| 719 submenu_->AddChildViewAt(new EmptyMenuMenuItem(this), 0); | |
| 720 } else { | |
| 721 for (int i = 0, item_count = submenu_->GetMenuItemCount(); i < item_count; | |
| 722 ++i) { | |
| 723 MenuItemView* child = submenu_->GetMenuItemAt(i); | |
| 724 if (child->HasSubmenu()) | |
| 725 child->AddEmptyMenus(); | |
| 726 } | |
| 727 } | |
| 728 } | |
| 729 | |
| 730 void MenuItemView::RemoveEmptyMenus() { | |
| 731 DCHECK(HasSubmenu()); | |
| 732 // Iterate backwards as we may end up removing views, which alters the child | |
| 733 // view count. | |
| 734 for (int i = submenu_->child_count() - 1; i >= 0; --i) { | |
| 735 View* child = submenu_->child_at(i); | |
| 736 if (child->id() == MenuItemView::kMenuItemViewID) { | |
| 737 MenuItemView* menu_item = static_cast<MenuItemView*>(child); | |
| 738 if (menu_item->HasSubmenu()) | |
| 739 menu_item->RemoveEmptyMenus(); | |
| 740 } else if (child->id() == EmptyMenuMenuItem::kEmptyMenuItemViewID) { | |
| 741 submenu_->RemoveChildView(child); | |
| 742 delete child; | |
| 743 child = NULL; | |
| 744 } | |
| 745 } | |
| 746 } | |
| 747 | |
| 748 void MenuItemView::AdjustBoundsForRTLUI(gfx::Rect* rect) const { | |
| 749 rect->set_x(GetMirroredXForRect(*rect)); | |
| 750 } | |
| 751 | |
| 752 void MenuItemView::PaintButton(gfx::Canvas* canvas, PaintButtonMode mode) { | |
| 753 const MenuConfig& config = GetMenuConfig(); | |
| 754 bool render_selection = | |
| 755 (mode == PB_NORMAL && IsSelected() && | |
| 756 parent_menu_item_->GetSubmenu()->GetShowSelection(this) && | |
| 757 (NonIconChildViewsCount() == 0)); | |
| 758 | |
| 759 MenuDelegate *delegate = GetDelegate(); | |
| 760 // Render the background. As MenuScrollViewContainer draws the background, we | |
| 761 // only need the background when we want it to look different, as when we're | |
| 762 // selected. | |
| 763 ui::NativeTheme* native_theme = GetNativeTheme(); | |
| 764 SkColor override_color; | |
| 765 if (delegate && delegate->GetBackgroundColor(GetCommand(), | |
| 766 render_selection, | |
| 767 &override_color)) { | |
| 768 canvas->DrawColor(override_color); | |
| 769 } else if (render_selection) { | |
| 770 gfx::Rect item_bounds(0, 0, width(), height()); | |
| 771 AdjustBoundsForRTLUI(&item_bounds); | |
| 772 | |
| 773 native_theme->Paint(canvas->sk_canvas(), | |
| 774 ui::NativeTheme::kMenuItemBackground, | |
| 775 ui::NativeTheme::kHovered, | |
| 776 item_bounds, | |
| 777 ui::NativeTheme::ExtraParams()); | |
| 778 } | |
| 779 | |
| 780 const int icon_x = config.item_left_margin + left_icon_margin_; | |
| 781 const int top_margin = GetTopMargin(); | |
| 782 const int bottom_margin = GetBottomMargin(); | |
| 783 const int available_height = height() - top_margin - bottom_margin; | |
| 784 | |
| 785 // Render the check. | |
| 786 if (type_ == CHECKBOX && delegate->IsItemChecked(GetCommand())) { | |
| 787 gfx::ImageSkia check = GetMenuCheckImage(render_selection); | |
| 788 // Don't use config.check_width here as it's padded | |
| 789 // to force more padding (AURA). | |
| 790 gfx::Rect check_bounds(icon_x, | |
| 791 top_margin + (available_height - check.height()) / 2, | |
| 792 check.width(), | |
| 793 check.height()); | |
| 794 AdjustBoundsForRTLUI(&check_bounds); | |
| 795 canvas->DrawImageInt(check, check_bounds.x(), check_bounds.y()); | |
| 796 } else if (type_ == RADIO) { | |
| 797 gfx::ImageSkia image = | |
| 798 GetRadioButtonImage(delegate->IsItemChecked(GetCommand())); | |
| 799 gfx::Rect radio_bounds(icon_x, | |
| 800 top_margin + (available_height - image.height()) / 2, | |
| 801 image.width(), | |
| 802 image.height()); | |
| 803 AdjustBoundsForRTLUI(&radio_bounds); | |
| 804 canvas->DrawImageInt(image, radio_bounds.x(), radio_bounds.y()); | |
| 805 } | |
| 806 | |
| 807 // Render the foreground. | |
| 808 ui::NativeTheme::ColorId color_id; | |
| 809 if (enabled()) { | |
| 810 color_id = render_selection ? | |
| 811 ui::NativeTheme::kColorId_SelectedMenuItemForegroundColor: | |
| 812 ui::NativeTheme::kColorId_EnabledMenuItemForegroundColor; | |
| 813 } else { | |
| 814 bool emphasized = delegate && | |
| 815 delegate->GetShouldUseDisabledEmphasizedForegroundColor( | |
| 816 GetCommand()); | |
| 817 color_id = emphasized ? | |
| 818 ui::NativeTheme::kColorId_DisabledEmphasizedMenuItemForegroundColor : | |
| 819 ui::NativeTheme::kColorId_DisabledMenuItemForegroundColor; | |
| 820 } | |
| 821 SkColor fg_color = native_theme->GetSystemColor(color_id); | |
| 822 SkColor override_foreground_color; | |
| 823 if (delegate && delegate->GetForegroundColor(GetCommand(), | |
| 824 render_selection, | |
| 825 &override_foreground_color)) | |
| 826 fg_color = override_foreground_color; | |
| 827 | |
| 828 const gfx::FontList& font_list = GetFontList(); | |
| 829 int accel_width = parent_menu_item_->GetSubmenu()->max_minor_text_width(); | |
| 830 int label_start = GetLabelStartForThisItem(); | |
| 831 | |
| 832 int width = this->width() - label_start - accel_width - | |
| 833 (!delegate || | |
| 834 delegate->ShouldReserveSpaceForSubmenuIndicator() ? | |
| 835 item_right_margin_ : config.arrow_to_edge_padding); | |
| 836 gfx::Rect text_bounds(label_start, top_margin, width, | |
| 837 subtitle_.empty() ? available_height | |
| 838 : available_height / 2); | |
| 839 text_bounds.set_x(GetMirroredXForRect(text_bounds)); | |
| 840 int flags = GetDrawStringFlags(); | |
| 841 if (mode == PB_FOR_DRAG) | |
| 842 flags |= gfx::Canvas::NO_SUBPIXEL_RENDERING; | |
| 843 canvas->DrawStringRectWithFlags(title(), font_list, fg_color, text_bounds, | |
| 844 flags); | |
| 845 if (!subtitle_.empty()) { | |
| 846 canvas->DrawStringRectWithFlags( | |
| 847 subtitle_, | |
| 848 font_list, | |
| 849 GetNativeTheme()->GetSystemColor( | |
| 850 ui::NativeTheme::kColorId_ButtonDisabledColor), | |
| 851 text_bounds + gfx::Vector2d(0, font_list.GetHeight()), | |
| 852 flags); | |
| 853 } | |
| 854 | |
| 855 PaintMinorText(canvas, render_selection); | |
| 856 | |
| 857 // Render the submenu indicator (arrow). | |
| 858 if (HasSubmenu()) { | |
| 859 gfx::ImageSkia arrow = GetSubmenuArrowImage(render_selection); | |
| 860 gfx::Rect arrow_bounds(this->width() - config.arrow_width - | |
| 861 config.arrow_to_edge_padding, | |
| 862 top_margin + (available_height - arrow.height()) / 2, | |
| 863 config.arrow_width, | |
| 864 arrow.height()); | |
| 865 AdjustBoundsForRTLUI(&arrow_bounds); | |
| 866 canvas->DrawImageInt(arrow, arrow_bounds.x(), arrow_bounds.y()); | |
| 867 } | |
| 868 } | |
| 869 | |
| 870 void MenuItemView::PaintMinorText(gfx::Canvas* canvas, | |
| 871 bool render_selection) { | |
| 872 base::string16 minor_text = GetMinorText(); | |
| 873 if (minor_text.empty()) | |
| 874 return; | |
| 875 | |
| 876 int available_height = height() - GetTopMargin() - GetBottomMargin(); | |
| 877 int max_accel_width = | |
| 878 parent_menu_item_->GetSubmenu()->max_minor_text_width(); | |
| 879 const MenuConfig& config = GetMenuConfig(); | |
| 880 int accel_right_margin = config.align_arrow_and_shortcut ? | |
| 881 config.arrow_to_edge_padding : item_right_margin_; | |
| 882 gfx::Rect accel_bounds(width() - accel_right_margin - max_accel_width, | |
| 883 GetTopMargin(), max_accel_width, available_height); | |
| 884 accel_bounds.set_x(GetMirroredXForRect(accel_bounds)); | |
| 885 int flags = GetDrawStringFlags(); | |
| 886 flags &= ~(gfx::Canvas::TEXT_ALIGN_RIGHT | gfx::Canvas::TEXT_ALIGN_LEFT); | |
| 887 if (base::i18n::IsRTL()) | |
| 888 flags |= gfx::Canvas::TEXT_ALIGN_LEFT; | |
| 889 else | |
| 890 flags |= gfx::Canvas::TEXT_ALIGN_RIGHT; | |
| 891 canvas->DrawStringRectWithFlags( | |
| 892 minor_text, | |
| 893 GetFontList(), | |
| 894 GetNativeTheme()->GetSystemColor(render_selection ? | |
| 895 ui::NativeTheme::kColorId_SelectedMenuItemForegroundColor : | |
| 896 ui::NativeTheme::kColorId_ButtonDisabledColor), | |
| 897 accel_bounds, | |
| 898 flags); | |
| 899 } | |
| 900 | |
| 901 void MenuItemView::DestroyAllMenuHosts() { | |
| 902 if (!HasSubmenu()) | |
| 903 return; | |
| 904 | |
| 905 submenu_->Close(); | |
| 906 for (int i = 0, item_count = submenu_->GetMenuItemCount(); i < item_count; | |
| 907 ++i) { | |
| 908 submenu_->GetMenuItemAt(i)->DestroyAllMenuHosts(); | |
| 909 } | |
| 910 } | |
| 911 | |
| 912 int MenuItemView::GetTopMargin() const { | |
| 913 if (top_margin_ >= 0) | |
| 914 return top_margin_; | |
| 915 | |
| 916 const MenuItemView* root = GetRootMenuItem(); | |
| 917 return root && root->has_icons_ | |
| 918 ? GetMenuConfig().item_top_margin : | |
| 919 GetMenuConfig().item_no_icon_top_margin; | |
| 920 } | |
| 921 | |
| 922 int MenuItemView::GetBottomMargin() const { | |
| 923 if (bottom_margin_ >= 0) | |
| 924 return bottom_margin_; | |
| 925 | |
| 926 const MenuItemView* root = GetRootMenuItem(); | |
| 927 return root && root->has_icons_ | |
| 928 ? GetMenuConfig().item_bottom_margin : | |
| 929 GetMenuConfig().item_no_icon_bottom_margin; | |
| 930 } | |
| 931 | |
| 932 gfx::Size MenuItemView::GetChildPreferredSize() const { | |
| 933 if (!has_children()) | |
| 934 return gfx::Size(); | |
| 935 | |
| 936 if (IsContainer()) | |
| 937 return child_at(0)->GetPreferredSize(); | |
| 938 | |
| 939 int width = 0; | |
| 940 for (int i = 0; i < child_count(); ++i) { | |
| 941 const View* child = child_at(i); | |
| 942 if (icon_view_ && (icon_view_ == child)) | |
| 943 continue; | |
| 944 if (i) | |
| 945 width += kChildXPadding; | |
| 946 width += child->GetPreferredSize().width(); | |
| 947 } | |
| 948 int height = 0; | |
| 949 if (icon_view_) | |
| 950 height = icon_view_->GetPreferredSize().height(); | |
| 951 | |
| 952 // If there is no icon view it returns a height of 0 to indicate that | |
| 953 // we should use the title height instead. | |
| 954 return gfx::Size(width, height); | |
| 955 } | |
| 956 | |
| 957 MenuItemView::MenuItemDimensions MenuItemView::CalculateDimensions() const { | |
| 958 gfx::Size child_size = GetChildPreferredSize(); | |
| 959 | |
| 960 MenuItemDimensions dimensions; | |
| 961 // Get the container height. | |
| 962 dimensions.children_width = child_size.width(); | |
| 963 dimensions.height = child_size.height(); | |
| 964 // Adjust item content height if menu has both items with and without icons. | |
| 965 // This way all menu items will have the same height. | |
| 966 if (!icon_view_ && GetRootMenuItem()->has_icons()) { | |
| 967 dimensions.height = std::max(dimensions.height, | |
| 968 GetMenuConfig().check_height); | |
| 969 } | |
| 970 dimensions.height += GetBottomMargin() + GetTopMargin(); | |
| 971 | |
| 972 // In case of a container, only the container size needs to be filled. | |
| 973 if (IsContainer()) | |
| 974 return dimensions; | |
| 975 | |
| 976 // Determine the length of the label text. | |
| 977 const gfx::FontList& font_list = GetFontList(); | |
| 978 | |
| 979 // Get Icon margin overrides for this particular item. | |
| 980 const MenuDelegate* delegate = GetDelegate(); | |
| 981 if (delegate) { | |
| 982 delegate->GetHorizontalIconMargins(command_, | |
| 983 icon_area_width_, | |
| 984 &left_icon_margin_, | |
| 985 &right_icon_margin_); | |
| 986 } else { | |
| 987 left_icon_margin_ = 0; | |
| 988 right_icon_margin_ = 0; | |
| 989 } | |
| 990 int label_start = GetLabelStartForThisItem(); | |
| 991 | |
| 992 int string_width = gfx::GetStringWidth(title_, font_list); | |
| 993 if (!subtitle_.empty()) { | |
| 994 string_width = std::max(string_width, | |
| 995 gfx::GetStringWidth(subtitle_, font_list)); | |
| 996 } | |
| 997 | |
| 998 dimensions.standard_width = string_width + label_start + | |
| 999 item_right_margin_; | |
| 1000 // Determine the length of the right-side text. | |
| 1001 base::string16 minor_text = GetMinorText(); | |
| 1002 dimensions.minor_text_width = | |
| 1003 minor_text.empty() ? 0 : gfx::GetStringWidth(minor_text, font_list); | |
| 1004 | |
| 1005 // Determine the height to use. | |
| 1006 dimensions.height = | |
| 1007 std::max(dimensions.height, | |
| 1008 (subtitle_.empty() ? 0 : font_list.GetHeight()) + | |
| 1009 font_list.GetHeight() + GetBottomMargin() + GetTopMargin()); | |
| 1010 dimensions.height = std::max(dimensions.height, | |
| 1011 GetMenuConfig().item_min_height); | |
| 1012 return dimensions; | |
| 1013 } | |
| 1014 | |
| 1015 int MenuItemView::GetLabelStartForThisItem() const { | |
| 1016 int label_start = label_start_ + left_icon_margin_ + right_icon_margin_; | |
| 1017 if ((type_ == CHECKBOX || type_ == RADIO) && icon_view_) { | |
| 1018 label_start += icon_view_->size().width() + | |
| 1019 GetMenuConfig().icon_to_label_padding; | |
| 1020 } | |
| 1021 return label_start; | |
| 1022 } | |
| 1023 | |
| 1024 base::string16 MenuItemView::GetMinorText() const { | |
| 1025 if (id() == kEmptyMenuItemViewID) { | |
| 1026 // Don't query the delegate for menus that represent no children. | |
| 1027 return base::string16(); | |
| 1028 } | |
| 1029 | |
| 1030 ui::Accelerator accelerator; | |
| 1031 if (GetMenuConfig().show_accelerators && GetDelegate() && GetCommand() && | |
| 1032 GetDelegate()->GetAccelerator(GetCommand(), &accelerator)) { | |
| 1033 return accelerator.GetShortcutText(); | |
| 1034 } | |
| 1035 | |
| 1036 return minor_text_; | |
| 1037 } | |
| 1038 | |
| 1039 bool MenuItemView::IsContainer() const { | |
| 1040 // Let the first child take over |this| when we only have one child and no | |
| 1041 // title. | |
| 1042 return (NonIconChildViewsCount() == 1) && title_.empty(); | |
| 1043 } | |
| 1044 | |
| 1045 int MenuItemView::NonIconChildViewsCount() const { | |
| 1046 // Note that what child_count() returns is the number of children, | |
| 1047 // not the number of menu items. | |
| 1048 return child_count() - (icon_view_ ? 1 : 0); | |
| 1049 } | |
| 1050 | |
| 1051 int MenuItemView::GetMaxIconViewWidth() const { | |
| 1052 int width = 0; | |
| 1053 for (int i = 0; i < submenu_->GetMenuItemCount(); ++i) { | |
| 1054 MenuItemView* menu_item = submenu_->GetMenuItemAt(i); | |
| 1055 int temp_width = 0; | |
| 1056 if (menu_item->GetType() == CHECKBOX || | |
| 1057 menu_item->GetType() == RADIO) { | |
| 1058 // If this item has a radio or checkbox, the icon will not affect | |
| 1059 // alignment of other items. | |
| 1060 continue; | |
| 1061 } else if (menu_item->HasSubmenu()) { | |
| 1062 temp_width = menu_item->GetMaxIconViewWidth(); | |
| 1063 } else if (menu_item->icon_view()) { | |
| 1064 temp_width = menu_item->icon_view()->GetPreferredSize().width(); | |
| 1065 } | |
| 1066 width = std::max(width, temp_width); | |
| 1067 } | |
| 1068 return width; | |
| 1069 } | |
| 1070 | |
| 1071 bool MenuItemView::HasChecksOrRadioButtons() const { | |
| 1072 for (int i = 0; i < submenu_->GetMenuItemCount(); ++i) { | |
| 1073 MenuItemView* menu_item = submenu_->GetMenuItemAt(i); | |
| 1074 if (menu_item->HasSubmenu()) { | |
| 1075 if (menu_item->HasChecksOrRadioButtons()) | |
| 1076 return true; | |
| 1077 } else { | |
| 1078 const Type& type = menu_item->GetType(); | |
| 1079 if (type == CHECKBOX || type == RADIO) | |
| 1080 return true; | |
| 1081 } | |
| 1082 } | |
| 1083 return false; | |
| 1084 } | |
| 1085 | |
| 1086 } // namespace views | |
| OLD | NEW |