1 // Copyright 2013 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 "chrome/browser/ui/views/toolbar/wrench_menu.h" 6 7 #include <algorithm> 8 #include <cmath> 9 #include <set> 10 11 #include "base/strings/string_number_conversions.h" 12 #include "base/strings/utf_string_conversions.h" 13 #include "chrome/app/chrome_command_ids.h" 14 #include "chrome/browser/bookmarks/bookmark_model_factory.h" 15 #include "chrome/browser/bookmarks/bookmark_stats.h" 16 #include "chrome/browser/chrome_notification_types.h" 17 #include "chrome/browser/profiles/profile.h" 18 #include "chrome/browser/search/search.h" 19 #include "chrome/browser/ui/browser.h" 20 #include "chrome/browser/ui/browser_window.h" 21 #include "chrome/browser/ui/tabs/tab_strip_model.h" 22 #include "chrome/browser/ui/toolbar/wrench_menu_model.h" 23 #include "chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.h" 24 #include "chrome/browser/ui/views/toolbar/wrench_menu_observer.h" 25 #include "components/bookmarks/browser/bookmark_model.h" 26 #include "content/public/browser/host_zoom_map.h" 27 #include "content/public/browser/notification_observer.h" 28 #include "content/public/browser/notification_registrar.h" 29 #include "content/public/browser/notification_source.h" 30 #include "content/public/browser/notification_types.h" 31 #include "content/public/browser/user_metrics.h" 32 #include "content/public/browser/web_contents.h" 33 #include "grit/chromium_strings.h" 34 #include "grit/generated_resources.h" 35 #include "grit/theme_resources.h" 36 #include "third_party/skia/include/core/SkCanvas.h" 37 #include "third_party/skia/include/core/SkPaint.h" 38 #include "ui/base/l10n/l10n_util.h" 39 #include "ui/base/layout.h" 40 #include "ui/base/resource/resource_bundle.h" 41 #include "ui/gfx/canvas.h" 42 #include "ui/gfx/font_list.h" 43 #include "ui/gfx/image/image.h" 44 #include "ui/gfx/image/image_skia_source.h" 45 #include "ui/gfx/skia_util.h" 46 #include "ui/gfx/text_utils.h" 47 #include "ui/views/background.h" 48 #include "ui/views/controls/button/image_button.h" 49 #include "ui/views/controls/button/label_button.h" 50 #include "ui/views/controls/button/menu_button.h" 51 #include "ui/views/controls/label.h" 52 #include "ui/views/controls/menu/menu_config.h" 53 #include "ui/views/controls/menu/menu_item_view.h" 54 #include "ui/views/controls/menu/menu_model_adapter.h" 55 #include "ui/views/controls/menu/menu_runner.h" 56 #include "ui/views/controls/menu/menu_scroll_view_container.h" 57 #include "ui/views/controls/menu/submenu_view.h" 58 #include "ui/views/widget/widget.h" 59 60 using base::UserMetricsAction; 61 using content::HostZoomMap; 62 using content::WebContents; 63 using ui::MenuModel; 64 using views::CustomButton; 65 using views::ImageButton; 66 using views::Label; 67 using views::LabelButton; 68 using views::MenuConfig; 69 using views::MenuItemView; 70 using views::View; 71 72 namespace { 73 74 // Horizontal padding on the edges of the buttons. 75 const int kHorizontalPadding = 6; 76 // Horizontal padding for a touch enabled menu. 77 const int kHorizontalTouchPadding = 15; 78 79 // Menu items which have embedded buttons should have this height in pixel. 80 const int kMenuItemContainingButtonsHeight = 43; 81 82 // Returns true if |command_id| identifies a bookmark menu item. 83 bool IsBookmarkCommand(int command_id) { 84 return command_id >= WrenchMenuModel::kMinBookmarkCommandId && 85 command_id <= WrenchMenuModel::kMaxBookmarkCommandId; 86 } 87 88 // Returns true if |command_id| identifies a recent tabs menu item. 89 bool IsRecentTabsCommand(int command_id) { 90 return command_id >= WrenchMenuModel::kMinRecentTabsCommandId && 91 command_id <= WrenchMenuModel::kMaxRecentTabsCommandId; 92 } 93 94 // Subclass of ImageButton whose preferred size includes the size of the border. 95 class FullscreenButton : public ImageButton { 96 public: 97 explicit FullscreenButton(views::ButtonListener* listener) 98 : ImageButton(listener) { } 99 100 // Overridden from ImageButton. 101 virtual gfx::Size GetPreferredSize() const OVERRIDE { 102 gfx::Size pref = ImageButton::GetPreferredSize(); 103 if (border()) { 104 gfx::Insets insets = border()->GetInsets(); 105 pref.Enlarge(insets.width(), insets.height()); 106 } 107 return pref; 108 } 109 110 private: 111 DISALLOW_COPY_AND_ASSIGN(FullscreenButton); 112 }; 113 114 // Border for buttons contained in the menu. This is only used for getting the 115 // insets, the actual painting is done in InMenuButtonBackground. 116 class MenuButtonBorder : public views::Border { 117 public: 118 MenuButtonBorder(const MenuConfig& config, bool use_new_menu) 119 : horizontal_padding_(use_new_menu ? 120 kHorizontalTouchPadding : kHorizontalPadding), 121 insets_(config.item_top_margin, horizontal_padding_, 122 config.item_bottom_margin, horizontal_padding_) { 123 } 124 125 // Overridden from views::Border. 126 virtual void Paint(const View& view, gfx::Canvas* canvas) OVERRIDE { 127 // Painting of border is done in InMenuButtonBackground. 128 } 129 130 virtual gfx::Insets GetInsets() const OVERRIDE { 131 return insets_; 132 } 133 134 virtual gfx::Size GetMinimumSize() const OVERRIDE { 135 // This size is sufficient for InMenuButtonBackground::Paint() to draw any 136 // of the button types. 137 return gfx::Size(4, 4); 138 } 139 140 private: 141 // The horizontal padding dependent on the layout. 142 const int horizontal_padding_; 143 144 const gfx::Insets insets_; 145 146 DISALLOW_COPY_AND_ASSIGN(MenuButtonBorder); 147 }; 148 149 // Combination border/background for the buttons contained in the menu. The 150 // painting of the border/background is done here as TextButton does not always 151 // paint the border. 152 class InMenuButtonBackground : public views::Background { 153 public: 154 enum ButtonType { 155 LEFT_BUTTON, 156 CENTER_BUTTON, 157 RIGHT_BUTTON, 158 SINGLE_BUTTON, 159 }; 160 161 InMenuButtonBackground(ButtonType type, bool use_new_menu) 162 : type_(type), 163 use_new_menu_(use_new_menu), 164 left_button_(NULL), 165 right_button_(NULL) {} 166 167 // Used when the type is CENTER_BUTTON to determine if the left/right edge 168 // needs to be rendered selected. 169 void SetOtherButtons(const CustomButton* left_button, 170 const CustomButton* right_button) { 171 if (base::i18n::IsRTL()) { 172 left_button_ = right_button; 173 right_button_ = left_button; 174 } else { 175 left_button_ = left_button; 176 right_button_ = right_button; 177 } 178 } 179 180 // Overridden from views::Background. 181 virtual void Paint(gfx::Canvas* canvas, View* view) const OVERRIDE { 182 CustomButton* button = CustomButton::AsCustomButton(view); 183 views::Button::ButtonState state = 184 button ? button->state() : views::Button::STATE_NORMAL; 185 int w = view->width(); 186 int h = view->height(); 187 if (use_new_menu_) { 188 // Normal buttons get a border drawn on the right side and the rest gets 189 // filled in. The left button however does not get a line to combine 190 // buttons. 191 if (type_ != RIGHT_BUTTON) { 192 canvas->FillRect(gfx::Rect(0, 0, 1, h), 193 BorderColor(view, views::Button::STATE_NORMAL)); 194 } 195 gfx::Rect bounds(view->GetLocalBounds()); 196 bounds.set_x(view->GetMirroredXForRect(bounds)); 197 DrawBackground(canvas, view, bounds, state); 198 return; 199 } 200 const SkColor border_color = BorderColor(view, state); 201 switch (TypeAdjustedForRTL()) { 202 // TODO(pkasting): Why don't all the following use SkPaths with rounded 203 // corners? 204 case LEFT_BUTTON: 205 DrawBackground(canvas, view, gfx::Rect(1, 1, w, h - 2), state); 206 canvas->FillRect(gfx::Rect(2, 0, w, 1), border_color); 207 canvas->FillRect(gfx::Rect(1, 1, 1, 1), border_color); 208 canvas->FillRect(gfx::Rect(0, 2, 1, h - 4), border_color); 209 canvas->FillRect(gfx::Rect(1, h - 2, 1, 1), border_color); 210 canvas->FillRect(gfx::Rect(2, h - 1, w, 1), border_color); 211 break; 212 213 case CENTER_BUTTON: { 214 DrawBackground(canvas, view, gfx::Rect(1, 1, w - 2, h - 2), state); 215 SkColor left_color = state != views::Button::STATE_NORMAL ? 216 border_color : BorderColor(view, left_button_->state()); 217 canvas->FillRect(gfx::Rect(0, 0, 1, h), left_color); 218 canvas->FillRect(gfx::Rect(1, 0, w - 2, 1), border_color); 219 canvas->FillRect(gfx::Rect(1, h - 1, w - 2, 1), 220 border_color); 221 SkColor right_color = state != views::Button::STATE_NORMAL ? 222 border_color : BorderColor(view, right_button_->state()); 223 canvas->FillRect(gfx::Rect(w - 1, 0, 1, h), right_color); 224 break; 225 } 226 227 case RIGHT_BUTTON: 228 DrawBackground(canvas, view, gfx::Rect(0, 1, w - 1, h - 2), state); 229 canvas->FillRect(gfx::Rect(0, 0, w - 2, 1), border_color); 230 canvas->FillRect(gfx::Rect(w - 2, 1, 1, 1), border_color); 231 canvas->FillRect(gfx::Rect(w - 1, 2, 1, h - 4), border_color); 232 canvas->FillRect(gfx::Rect(w - 2, h - 2, 1, 1), border_color); 233 canvas->FillRect(gfx::Rect(0, h - 1, w - 2, 1), border_color); 234 break; 235 236 case SINGLE_BUTTON: 237 DrawBackground(canvas, view, gfx::Rect(1, 1, w - 2, h - 2), state); 238 canvas->FillRect(gfx::Rect(2, 0, w - 4, 1), border_color); 239 canvas->FillRect(gfx::Rect(1, 1, 1, 1), border_color); 240 canvas->FillRect(gfx::Rect(0, 2, 1, h - 4), border_color); 241 canvas->FillRect(gfx::Rect(1, h - 2, 1, 1), border_color); 242 canvas->FillRect(gfx::Rect(2, h - 1, w - 4, 1), border_color); 243 canvas->FillRect(gfx::Rect(w - 2, 1, 1, 1), border_color); 244 canvas->FillRect(gfx::Rect(w - 1, 2, 1, h - 4), border_color); 245 canvas->FillRect(gfx::Rect(w - 2, h - 2, 1, 1), border_color); 246 break; 247 248 default: 249 NOTREACHED(); 250 break; 251 } 252 } 253 254 private: 255 static SkColor BorderColor(View* view, views::Button::ButtonState state) { 256 ui::NativeTheme* theme = view->GetNativeTheme(); 257 switch (state) { 258 case views::Button::STATE_HOVERED: 259 return theme->GetSystemColor( 260 ui::NativeTheme::kColorId_HoverMenuButtonBorderColor); 261 case views::Button::STATE_PRESSED: 262 return theme->GetSystemColor( 263 ui::NativeTheme::kColorId_FocusedMenuButtonBorderColor); 264 default: 265 return theme->GetSystemColor( 266 ui::NativeTheme::kColorId_EnabledMenuButtonBorderColor); 267 } 268 } 269 270 static SkColor BackgroundColor(const View* view, 271 views::Button::ButtonState state) { 272 const ui::NativeTheme* theme = view->GetNativeTheme(); 273 switch (state) { 274 case views::Button::STATE_HOVERED: 275 // Hovered should be handled in DrawBackground. 276 NOTREACHED(); 277 return theme->GetSystemColor( 278 ui::NativeTheme::kColorId_HoverMenuItemBackgroundColor); 279 case views::Button::STATE_PRESSED: 280 return theme->GetSystemColor( 281 ui::NativeTheme::kColorId_FocusedMenuItemBackgroundColor); 282 default: 283 return theme->GetSystemColor( 284 ui::NativeTheme::kColorId_MenuBackgroundColor); 285 } 286 } 287 288 void DrawBackground(gfx::Canvas* canvas, 289 const views::View* view, 290 const gfx::Rect& bounds, 291 views::Button::ButtonState state) const { 292 if (state == views::Button::STATE_HOVERED || 293 state == views::Button::STATE_PRESSED) { 294 view->GetNativeTheme()->Paint(canvas->sk_canvas(), 295 ui::NativeTheme::kMenuItemBackground, 296 ui::NativeTheme::kHovered, 297 bounds, 298 ui::NativeTheme::ExtraParams()); 299 return; 300 } 301 if (use_new_menu_) 302 return; 303 canvas->FillRect(bounds, BackgroundColor(view, state)); 304 } 305 306 ButtonType TypeAdjustedForRTL() const { 307 if (!base::i18n::IsRTL()) 308 return type_; 309 310 switch (type_) { 311 case LEFT_BUTTON: return RIGHT_BUTTON; 312 case RIGHT_BUTTON: return LEFT_BUTTON; 313 default: break; 314 } 315 return type_; 316 } 317 318 const ButtonType type_; 319 const bool use_new_menu_; 320 321 // See description above setter for details. 322 const CustomButton* left_button_; 323 const CustomButton* right_button_; 324 325 DISALLOW_COPY_AND_ASSIGN(InMenuButtonBackground); 326 }; 327 328 base::string16 GetAccessibleNameForWrenchMenuItem( 329 MenuModel* model, int item_index, int accessible_string_id) { 330 base::string16 accessible_name = 331 l10n_util::GetStringUTF16(accessible_string_id); 332 base::string16 accelerator_text; 333 334 ui::Accelerator menu_accelerator; 335 if (model->GetAcceleratorAt(item_index, &menu_accelerator)) { 336 accelerator_text = 337 ui::Accelerator(menu_accelerator.key_code(), 338 menu_accelerator.modifiers()).GetShortcutText(); 339 } 340 341 return MenuItemView::GetAccessibleNameForMenuItem( 342 accessible_name, accelerator_text); 343 } 344 345 // A button that lives inside a menu item. 346 class InMenuButton : public LabelButton { 347 public: 348 InMenuButton(views::ButtonListener* listener, 349 const base::string16& text, 350 bool use_new_menu) 351 : LabelButton(listener, text), 352 use_new_menu_(use_new_menu), 353 in_menu_background_(NULL) {} 354 virtual ~InMenuButton() {} 355 356 void Init(InMenuButtonBackground::ButtonType type) { 357 SetFocusable(true); 358 set_request_focus_on_press(false); 359 SetHorizontalAlignment(gfx::ALIGN_CENTER); 360 361 in_menu_background_ = new InMenuButtonBackground(type, use_new_menu_); 362 set_background(in_menu_background_); 363 364 OnNativeThemeChanged(NULL); 365 } 366 367 void SetOtherButtons(const InMenuButton* left, const InMenuButton* right) { 368 in_menu_background_->SetOtherButtons(left, right); 369 } 370 371 // views::LabelButton 372 virtual void OnNativeThemeChanged(const ui::NativeTheme* theme) OVERRIDE { 373 const MenuConfig& menu_config = MenuConfig::instance(theme); 374 SetBorder(scoped_ptr<views::Border>( 375 new MenuButtonBorder(menu_config, use_new_menu_))); 376 SetFontList(menu_config.font_list); 377 378 if (theme) { 379 SetTextColor( 380 views::Button::STATE_DISABLED, 381 theme->GetSystemColor( 382 ui::NativeTheme::kColorId_DisabledMenuItemForegroundColor)); 383 SetTextColor( 384 views::Button::STATE_HOVERED, 385 theme->GetSystemColor( 386 ui::NativeTheme::kColorId_SelectedMenuItemForegroundColor)); 387 SetTextColor( 388 views::Button::STATE_PRESSED, 389 theme->GetSystemColor( 390 ui::NativeTheme::kColorId_SelectedMenuItemForegroundColor)); 391 SetTextColor( 392 views::Button::STATE_NORMAL, 393 theme->GetSystemColor( 394 ui::NativeTheme::kColorId_EnabledMenuItemForegroundColor)); 395 } 396 } 397 398 private: 399 bool use_new_menu_; 400 401 InMenuButtonBackground* in_menu_background_; 402 403 DISALLOW_COPY_AND_ASSIGN(InMenuButton); 404 }; 405 406 // WrenchMenuView is a view that can contain label buttons. 407 class WrenchMenuView : public views::View, 408 public views::ButtonListener, 409 public WrenchMenuObserver { 410 public: 411 WrenchMenuView(WrenchMenu* menu, MenuModel* menu_model) 412 : menu_(menu), 413 menu_model_(menu_model) { 414 menu_->AddObserver(this); 415 } 416 417 virtual ~WrenchMenuView() { 418 if (menu_) 419 menu_->RemoveObserver(this); 420 } 421 422 // Overridden from views::View. 423 virtual void SchedulePaintInRect(const gfx::Rect& r) OVERRIDE { 424 // Normally when the mouse enters/exits a button the buttons invokes 425 // SchedulePaint. As part of the button border (InMenuButtonBackground) is 426 // rendered by the button to the left/right of it SchedulePaint on the the 427 // button may not be enough, so this forces a paint all. 428 View::SchedulePaintInRect(gfx::Rect(size())); 429 } 430 431 InMenuButton* CreateAndConfigureButton( 432 int string_id, 433 InMenuButtonBackground::ButtonType type, 434 int index) { 435 return CreateButtonWithAccName(string_id, type, index, string_id); 436 } 437 438 InMenuButton* CreateButtonWithAccName(int string_id, 439 InMenuButtonBackground::ButtonType type, 440 int index, 441 int acc_string_id) { 442 // Should only be invoked during construction when |menu_| is valid. 443 DCHECK(menu_); 444 InMenuButton* button = new InMenuButton( 445 this, 446 gfx::RemoveAcceleratorChar(l10n_util::GetStringUTF16(string_id), 447 '&', 448 NULL, 449 NULL), 450 use_new_menu()); 451 button->Init(type); 452 button->SetAccessibleName( 453 GetAccessibleNameForWrenchMenuItem(menu_model_, index, acc_string_id)); 454 button->set_tag(index); 455 button->SetEnabled(menu_model_->IsEnabledAt(index)); 456 457 AddChildView(button); 458 // all buttons on menu should must be a custom button in order for 459 // the keyboard nativigation work. 460 DCHECK(CustomButton::AsCustomButton(button)); 461 return button; 462 } 463 464 // Overridden from WrenchMenuObserver: 465 virtual void WrenchMenuDestroyed() OVERRIDE { 466 menu_->RemoveObserver(this); 467 menu_ = NULL; 468 menu_model_ = NULL; 469 } 470 471 protected: 472 WrenchMenu* menu() { return menu_; } 473 MenuModel* menu_model() { return menu_model_; } 474 475 bool use_new_menu() const { return menu_->use_new_menu(); } 476 477 private: 478 // Hosting WrenchMenu. 479 // WARNING: this may be NULL during shutdown. 480 WrenchMenu* menu_; 481 482 // The menu model containing the increment/decrement/reset items. 483 // WARNING: this may be NULL during shutdown. 484 MenuModel* menu_model_; 485 486 DISALLOW_COPY_AND_ASSIGN(WrenchMenuView); 487 }; 488 489 class ButtonContainerMenuItemView : public MenuItemView { 490 public: 491 // Constructor for use with button containing menu items which have a 492 // different height then normal items. 493 ButtonContainerMenuItemView(MenuItemView* parent, int command_id, int height) 494 : MenuItemView(parent, command_id, MenuItemView::NORMAL), 495 height_(height) {} 496 497 // Overridden from MenuItemView. 498 virtual gfx::Size GetChildPreferredSize() const OVERRIDE { 499 gfx::Size size = MenuItemView::GetChildPreferredSize(); 500 // When there is a height override given, we need to deduct our spacing 501 // above and below to get to the correct height to return here for the 502 // child item. 503 int height = height_ - GetTopMargin() - GetBottomMargin(); 504 if (height > size.height()) 505 size.set_height(height); 506 return size; 507 } 508 509 private: 510 int height_; 511 512 DISALLOW_COPY_AND_ASSIGN(ButtonContainerMenuItemView); 513 }; 514 515 // Generate the button image for hover state. 516 class HoveredImageSource : public gfx::ImageSkiaSource { 517 public: 518 HoveredImageSource(const gfx::ImageSkia& image, SkColor color) 519 : image_(image), 520 color_(color) { 521 } 522 virtual ~HoveredImageSource() {} 523 524 virtual gfx::ImageSkiaRep GetImageForScale(float scale) OVERRIDE { 525 const gfx::ImageSkiaRep& rep = image_.GetRepresentation(scale); 526 SkBitmap bitmap = rep.sk_bitmap(); 527 SkBitmap white; 528 white.setConfig(SkBitmap::kARGB_8888_Config, 529 bitmap.width(), bitmap.height(), 0); 530 white.allocPixels(); 531 white.eraseARGB(0, 0, 0, 0); 532 bitmap.lockPixels(); 533 for (int y = 0; y < bitmap.height(); ++y) { 534 uint32* image_row = bitmap.getAddr32(0, y); 535 uint32* dst_row = white.getAddr32(0, y); 536 for (int x = 0; x < bitmap.width(); ++x) { 537 uint32 image_pixel = image_row[x]; 538 // Fill the non transparent pixels with |color_|. 539 dst_row[x] = (image_pixel & 0xFF000000) == 0x0 ? 0x0 : color_; 540 } 541 } 542 bitmap.unlockPixels(); 543 return gfx::ImageSkiaRep(white, scale); 544 } 545 546 private: 547 const gfx::ImageSkia image_; 548 const SkColor color_; 549 DISALLOW_COPY_AND_ASSIGN(HoveredImageSource); 550 }; 551 552 } // namespace 553 554 // CutCopyPasteView ------------------------------------------------------------ 555 556 // CutCopyPasteView is the view containing the cut/copy/paste buttons. 557 class WrenchMenu::CutCopyPasteView : public WrenchMenuView { 558 public: 559 CutCopyPasteView(WrenchMenu* menu, 560 MenuModel* menu_model, 561 int cut_index, 562 int copy_index, 563 int paste_index) 564 : WrenchMenuView(menu, menu_model) { 565 InMenuButton* cut = CreateAndConfigureButton( 566 IDS_CUT, InMenuButtonBackground::LEFT_BUTTON, 567 cut_index); 568 InMenuButton* copy = CreateAndConfigureButton( 569 IDS_COPY, InMenuButtonBackground::CENTER_BUTTON, 570 copy_index); 571 InMenuButton* paste = CreateAndConfigureButton( 572 IDS_PASTE, 573 menu->use_new_menu() && menu->supports_new_separators_ ? 574 InMenuButtonBackground::CENTER_BUTTON : 575 InMenuButtonBackground::RIGHT_BUTTON, 576 paste_index); 577 copy->SetOtherButtons(cut, paste); 578 } 579 580 // Overridden from View. 581 virtual gfx::Size GetPreferredSize() const OVERRIDE { 582 // Returned height doesn't matter as MenuItemView forces everything to the 583 // height of the menuitemview. 584 return gfx::Size(GetMaxChildViewPreferredWidth() * child_count(), 0); 585 } 586 587 virtual void Layout() OVERRIDE { 588 // All buttons are given the same width. 589 int width = GetMaxChildViewPreferredWidth(); 590 for (int i = 0; i < child_count(); ++i) 591 child_at(i)->SetBounds(i * width, 0, width, height()); 592 } 593 594 // Overridden from ButtonListener. 595 virtual void ButtonPressed(views::Button* sender, 596 const ui::Event& event) OVERRIDE { 597 menu()->CancelAndEvaluate(menu_model(), sender->tag()); 598 } 599 600 private: 601 // Returns the max preferred width of all the children. 602 int GetMaxChildViewPreferredWidth() const { 603 int width = 0; 604 for (int i = 0; i < child_count(); ++i) 605 width = std::max(width, child_at(i)->GetPreferredSize().width()); 606 return width; 607 } 608 609 DISALLOW_COPY_AND_ASSIGN(CutCopyPasteView); 610 }; 611 612 // ZoomView -------------------------------------------------------------------- 613 614 // Padding between the increment buttons and the reset button. 615 static const int kZoomPadding = 6; 616 static const int kTouchZoomPadding = 14; 617 618 // ZoomView contains the various zoom controls: two buttons to increase/decrease 619 // the zoom, a label showing the current zoom percent, and a button to go 620 // full-screen. 621 class WrenchMenu::ZoomView : public WrenchMenuView { 622 public: 623 ZoomView(WrenchMenu* menu, 624 MenuModel* menu_model, 625 int decrement_index, 626 int increment_index, 627 int fullscreen_index) 628 : WrenchMenuView(menu, menu_model), 629 fullscreen_index_(fullscreen_index), 630 increment_button_(NULL), 631 zoom_label_(NULL), 632 decrement_button_(NULL), 633 fullscreen_button_(NULL), 634 zoom_label_width_(0) { 635 zoom_subscription_ = HostZoomMap::GetForBrowserContext( 636 menu->browser_->profile())->AddZoomLevelChangedCallback( 637 base::Bind(&WrenchMenu::ZoomView::OnZoomLevelChanged, 638 base::Unretained(this))); 639 640 decrement_button_ = CreateButtonWithAccName( 641 IDS_ZOOM_MINUS2, InMenuButtonBackground::LEFT_BUTTON, 642 decrement_index, IDS_ACCNAME_ZOOM_MINUS2); 643 644 zoom_label_ = new Label( 645 l10n_util::GetStringFUTF16Int(IDS_ZOOM_PERCENT, 100)); 646 zoom_label_->SetAutoColorReadabilityEnabled(false); 647 zoom_label_->SetHorizontalAlignment(gfx::ALIGN_RIGHT); 648 649 InMenuButtonBackground* center_bg = new InMenuButtonBackground( 650 menu->use_new_menu() && menu->supports_new_separators_ ? 651 InMenuButtonBackground::RIGHT_BUTTON : 652 InMenuButtonBackground::CENTER_BUTTON, 653 menu->use_new_menu()); 654 zoom_label_->set_background(center_bg); 655 656 AddChildView(zoom_label_); 657 zoom_label_width_ = MaxWidthForZoomLabel(); 658 659 increment_button_ = CreateButtonWithAccName( 660 IDS_ZOOM_PLUS2, InMenuButtonBackground::RIGHT_BUTTON, 661 increment_index, IDS_ACCNAME_ZOOM_PLUS2); 662 663 center_bg->SetOtherButtons(decrement_button_, increment_button_); 664 665 fullscreen_button_ = new FullscreenButton(this); 666 // all buttons on menu should must be a custom button in order for 667 // the keyboard nativigation work. 668 DCHECK(CustomButton::AsCustomButton(fullscreen_button_)); 669 gfx::ImageSkia* full_screen_image = 670 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( 671 IDR_FULLSCREEN_MENU_BUTTON); 672 fullscreen_button_->SetImage(ImageButton::STATE_NORMAL, full_screen_image); 673 674 fullscreen_button_->SetFocusable(true); 675 fullscreen_button_->set_request_focus_on_press(false); 676 fullscreen_button_->set_tag(fullscreen_index); 677 fullscreen_button_->SetImageAlignment( 678 ImageButton::ALIGN_CENTER, ImageButton::ALIGN_MIDDLE); 679 int horizontal_padding = 680 menu->use_new_menu() ? kHorizontalTouchPadding : kHorizontalPadding; 681 fullscreen_button_->SetBorder(views::Border::CreateEmptyBorder( 682 0, horizontal_padding, 0, horizontal_padding)); 683 fullscreen_button_->set_background( 684 new InMenuButtonBackground(InMenuButtonBackground::SINGLE_BUTTON, 685 menu->use_new_menu())); 686 fullscreen_button_->SetAccessibleName( 687 GetAccessibleNameForWrenchMenuItem( 688 menu_model, fullscreen_index, IDS_ACCNAME_FULLSCREEN)); 689 AddChildView(fullscreen_button_); 690 691 // Need to set a font list for the zoom label width calculations. 692 OnNativeThemeChanged(NULL); 693 UpdateZoomControls(); 694 } 695 696 virtual ~ZoomView() {} 697 698 // Overridden from View. 699 virtual gfx::Size GetPreferredSize() const OVERRIDE { 700 // The increment/decrement button are forced to the same width. 701 int button_width = std::max(increment_button_->GetPreferredSize().width(), 702 decrement_button_->GetPreferredSize().width()); 703 int zoom_padding = use_new_menu() ? 704 kTouchZoomPadding : kZoomPadding; 705 int fullscreen_width = fullscreen_button_->GetPreferredSize().width() + 706 zoom_padding; 707 // Returned height doesn't matter as MenuItemView forces everything to the 708 // height of the menuitemview. Note that we have overridden the height when 709 // constructing the menu. 710 return gfx::Size(button_width + zoom_label_width_ + button_width + 711 fullscreen_width, 0); 712 } 713 714 virtual void Layout() OVERRIDE { 715 int x = 0; 716 int button_width = std::max(increment_button_->GetPreferredSize().width(), 717 decrement_button_->GetPreferredSize().width()); 718 gfx::Rect bounds(0, 0, button_width, height()); 719 720 decrement_button_->SetBoundsRect(bounds); 721 722 x += bounds.width(); 723 bounds.set_x(x); 724 bounds.set_width(zoom_label_width_); 725 zoom_label_->SetBoundsRect(bounds); 726 727 x += bounds.width(); 728 bounds.set_x(x); 729 bounds.set_width(button_width); 730 increment_button_->SetBoundsRect(bounds); 731 732 x += bounds.width() + (use_new_menu() ? 0 : kZoomPadding); 733 bounds.set_x(x); 734 bounds.set_width(fullscreen_button_->GetPreferredSize().width() + 735 (use_new_menu() ? kTouchZoomPadding : 0)); 736 fullscreen_button_->SetBoundsRect(bounds); 737 } 738 739 virtual void OnNativeThemeChanged(const ui::NativeTheme* theme) OVERRIDE { 740 WrenchMenuView::OnNativeThemeChanged(theme); 741 742 const MenuConfig& menu_config = MenuConfig::instance(theme); 743 zoom_label_->SetBorder(scoped_ptr<views::Border>( 744 new MenuButtonBorder(menu_config, menu()->use_new_menu()))); 745 zoom_label_->SetFontList(menu_config.font_list); 746 zoom_label_width_ = MaxWidthForZoomLabel(); 747 748 if (theme) { 749 zoom_label_->SetEnabledColor(theme->GetSystemColor( 750 ui::NativeTheme::kColorId_EnabledMenuItemForegroundColor)); 751 gfx::ImageSkia* full_screen_image = 752 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( 753 IDR_FULLSCREEN_MENU_BUTTON); 754 SkColor fg_color = theme->GetSystemColor( 755 ui::NativeTheme::kColorId_SelectedMenuItemForegroundColor); 756 gfx::ImageSkia hovered_fullscreen_image( 757 new HoveredImageSource(*full_screen_image, fg_color), 758 full_screen_image->size()); 759 fullscreen_button_->SetImage( 760 ImageButton::STATE_HOVERED, &hovered_fullscreen_image); 761 fullscreen_button_->SetImage( 762 ImageButton::STATE_PRESSED, &hovered_fullscreen_image); 763 } 764 } 765 766 // Overridden from ButtonListener. 767 virtual void ButtonPressed(views::Button* sender, 768 const ui::Event& event) OVERRIDE { 769 if (sender->tag() == fullscreen_index_) { 770 menu()->CancelAndEvaluate(menu_model(), sender->tag()); 771 } else { 772 // Zoom buttons don't close the menu. 773 menu_model()->ActivatedAt(sender->tag()); 774 } 775 } 776 777 // Overridden from WrenchMenuObserver. 778 virtual void WrenchMenuDestroyed() OVERRIDE { 779 WrenchMenuView::WrenchMenuDestroyed(); 780 } 781 782 private: 783 void OnZoomLevelChanged(const HostZoomMap::ZoomLevelChange& change) { 784 UpdateZoomControls(); 785 } 786 787 void UpdateZoomControls() { 788 bool enable_increment = false; 789 bool enable_decrement = false; 790 WebContents* selected_tab = 791 menu()->browser_->tab_strip_model()->GetActiveWebContents(); 792 int zoom = 100; 793 if (selected_tab) 794 zoom = selected_tab->GetZoomPercent(&enable_increment, &enable_decrement); 795 increment_button_->SetEnabled(enable_increment); 796 decrement_button_->SetEnabled(enable_decrement); 797 zoom_label_->SetText( 798 l10n_util::GetStringFUTF16Int(IDS_ZOOM_PERCENT, zoom)); 799 800 zoom_label_width_ = MaxWidthForZoomLabel(); 801 } 802 803 // Calculates the max width the zoom string can be. 804 int MaxWidthForZoomLabel() { 805 const gfx::FontList& font_list = zoom_label_->font_list(); 806 int border_width = 807 zoom_label_->border() ? zoom_label_->border()->GetInsets().width() : 0; 808 809 int max_w = 0; 810 811 WebContents* selected_tab = 812 menu()->browser_->tab_strip_model()->GetActiveWebContents(); 813 if (selected_tab) { 814 int min_percent = selected_tab->GetMinimumZoomPercent(); 815 int max_percent = selected_tab->GetMaximumZoomPercent(); 816 817 int step = (max_percent - min_percent) / 10; 818 for (int i = min_percent; i <= max_percent; i += step) { 819 int w = gfx::GetStringWidth( 820 l10n_util::GetStringFUTF16Int(IDS_ZOOM_PERCENT, i), font_list); 821 max_w = std::max(w, max_w); 822 } 823 } else { 824 max_w = gfx::GetStringWidth( 825 l10n_util::GetStringFUTF16Int(IDS_ZOOM_PERCENT, 100), font_list); 826 } 827 828 return max_w + border_width; 829 } 830 831 // Index of the fullscreen menu item in the model. 832 const int fullscreen_index_; 833 834 scoped_ptr<content::HostZoomMap::Subscription> zoom_subscription_; 835 content::NotificationRegistrar registrar_; 836 837 // Button for incrementing the zoom. 838 LabelButton* increment_button_; 839 840 // Label showing zoom as a percent. 841 Label* zoom_label_; 842 843 // Button for decrementing the zoom. 844 LabelButton* decrement_button_; 845 846 ImageButton* fullscreen_button_; 847 848 // Width given to |zoom_label_|. This is the width at 100%. 849 int zoom_label_width_; 850 851 DISALLOW_COPY_AND_ASSIGN(ZoomView); 852 }; 853 854 // RecentTabsMenuModelDelegate ------------------------------------------------ 855 856 // Provides the ui::MenuModelDelegate implementation for RecentTabsSubMenuModel 857 // items. 858 class WrenchMenu::RecentTabsMenuModelDelegate : public ui::MenuModelDelegate { 859 public: 860 RecentTabsMenuModelDelegate(WrenchMenu* wrench_menu, 861 ui::MenuModel* model, 862 views::MenuItemView* menu_item) 863 : wrench_menu_(wrench_menu), 864 model_(model), 865 menu_item_(menu_item) { 866 model_->SetMenuModelDelegate(this); 867 } 868 869 virtual ~RecentTabsMenuModelDelegate() { 870 model_->SetMenuModelDelegate(NULL); 871 } 872 873 // Return the specific menu width of recent tabs submenu if |menu| is the 874 // recent tabs submenu, else return -1. 875 int GetMaxWidthForMenu(views::MenuItemView* menu) { 876 if (!menu_item_->HasSubmenu()) 877 return -1; 878 const int kMaxMenuItemWidth = 320; 879 return menu->GetCommand() == menu_item_->GetCommand() ? 880 kMaxMenuItemWidth : -1; 881 } 882 883 const gfx::FontList* GetLabelFontListAt(int index) const { 884 return model_->GetLabelFontListAt(index); 885 } 886 887 bool GetShouldUseDisabledEmphasizedForegroundColor(int index) const { 888 // The items for which we get a font list, should be shown in the bolded 889 // color. 890 return GetLabelFontListAt(index) ? true : false; 891 } 892 893 // ui::MenuModelDelegate implementation: 894 895 virtual void OnIconChanged(int index) OVERRIDE { 896 int command_id = model_->GetCommandIdAt(index); 897 views::MenuItemView* item = menu_item_->GetMenuItemByID(command_id); 898 DCHECK(item); 899 gfx::Image icon; 900 model_->GetIconAt(index, &icon); 901 item->SetIcon(*icon.ToImageSkia()); 902 } 903 904 virtual void OnMenuStructureChanged() OVERRIDE { 905 if (menu_item_->HasSubmenu()) { 906 // Remove all menu items from submenu. 907 views::SubmenuView* submenu = menu_item_->GetSubmenu(); 908 while (submenu->child_count() > 0) 909 menu_item_->RemoveMenuItemAt(submenu->child_count() - 1); 910 911 // Remove all elements in |WrenchMenu::command_id_to_entry_| that map to 912 // |model_|. 913 WrenchMenu::CommandIDToEntry::iterator iter = 914 wrench_menu_->command_id_to_entry_.begin(); 915 while (iter != wrench_menu_->command_id_to_entry_.end()) { 916 if (iter->second.first == model_) 917 wrench_menu_->command_id_to_entry_.erase(iter++); 918 else 919 ++iter; 920 } 921 } 922 923 // Add all menu items from |model| to submenu. 924 for (int i = 0; i < model_->GetItemCount(); ++i) { 925 wrench_menu_->AddMenuItem(menu_item_, i, model_, i, model_->GetTypeAt(i), 926 0); 927 } 928 929 // In case recent tabs submenu was open when items were changing, force a 930 // ChildrenChanged(). 931 menu_item_->ChildrenChanged(); 932 } 933 934 private: 935 WrenchMenu* wrench_menu_; 936 ui::MenuModel* model_; 937 views::MenuItemView* menu_item_; 938 939 DISALLOW_COPY_AND_ASSIGN(RecentTabsMenuModelDelegate); 940 }; 941 942 // WrenchMenu ------------------------------------------------------------------ 943 944 WrenchMenu::WrenchMenu(Browser* browser, 945 bool use_new_menu, 946 bool supports_new_separators) 947 : root_(NULL), 948 browser_(browser), 949 selected_menu_model_(NULL), 950 selected_index_(0), 951 bookmark_menu_(NULL), 952 feedback_menu_item_(NULL), 953 use_new_menu_(use_new_menu), 954 supports_new_separators_(supports_new_separators) { 955 registrar_.Add(this, chrome::NOTIFICATION_GLOBAL_ERRORS_CHANGED, 956 content::Source<Profile>(browser_->profile())); 957 } 958 959 WrenchMenu::~WrenchMenu() { 960 if (bookmark_menu_delegate_.get()) { 961 BookmarkModel* model = BookmarkModelFactory::GetForProfile( 962 browser_->profile()); 963 if (model) 964 model->RemoveObserver(this); 965 } 966 FOR_EACH_OBSERVER(WrenchMenuObserver, observer_list_, WrenchMenuDestroyed()); 967 } 968 969 void WrenchMenu::Init(ui::MenuModel* model) { 970 DCHECK(!root_); 971 root_ = new MenuItemView(this); 972 root_->set_has_icons(true); // We have checks, radios and icons, set this 973 // so we get the taller menu style. 974 PopulateMenu(root_, model); 975 976 #if defined(DEBUG) 977 // Verify that the reserved command ID's for bookmarks menu are not used. 978 for (int i = WrenchMenuModel:kMinBookmarkCommandId; 979 i <= WrenchMenuModel::kMaxBookmarkCommandId; ++i) 980 DCHECK(command_id_to_entry_.find(i) == command_id_to_entry_.end()); 981 #endif // defined(DEBUG) 982 983 menu_runner_.reset(new views::MenuRunner(root_)); 984 } 985 986 void WrenchMenu::RunMenu(views::MenuButton* host) { 987 gfx::Point screen_loc; 988 views::View::ConvertPointToScreen(host, &screen_loc); 989 gfx::Rect bounds(screen_loc, host->size()); 990 content::RecordAction(UserMetricsAction("ShowAppMenu")); 991 if (menu_runner_->RunMenuAt(host->GetWidget(), 992 host, 993 bounds, 994 views::MENU_ANCHOR_TOPRIGHT, 995 ui::MENU_SOURCE_NONE, 996 views::MenuRunner::HAS_MNEMONICS) == 997 views::MenuRunner::MENU_DELETED) 998 return; 999 if (bookmark_menu_delegate_.get()) { 1000 BookmarkModel* model = BookmarkModelFactory::GetForProfile( 1001 browser_->profile()); 1002 if (model) 1003 model->RemoveObserver(this); 1004 } 1005 if (selected_menu_model_) 1006 selected_menu_model_->ActivatedAt(selected_index_); 1007 } 1008 1009 bool WrenchMenu::IsShowing() { 1010 return menu_runner_.get() && menu_runner_->IsRunning(); 1011 } 1012 1013 void WrenchMenu::AddObserver(WrenchMenuObserver* observer) { 1014 observer_list_.AddObserver(observer); 1015 } 1016 1017 void WrenchMenu::RemoveObserver(WrenchMenuObserver* observer) { 1018 observer_list_.RemoveObserver(observer); 1019 } 1020 1021 const gfx::FontList* WrenchMenu::GetLabelFontList(int command_id) const { 1022 if (IsRecentTabsCommand(command_id)) { 1023 return recent_tabs_menu_model_delegate_->GetLabelFontListAt( 1024 ModelIndexFromCommandId(command_id)); 1025 } 1026 return NULL; 1027 } 1028 1029 bool WrenchMenu::GetShouldUseDisabledEmphasizedForegroundColor( 1030 int command_id) const { 1031 if (IsRecentTabsCommand(command_id)) { 1032 return recent_tabs_menu_model_delegate_-> 1033 GetShouldUseDisabledEmphasizedForegroundColor( 1034 ModelIndexFromCommandId(command_id)); 1035 } 1036 return false; 1037 } 1038 1039 base::string16 WrenchMenu::GetTooltipText(int command_id, 1040 const gfx::Point& p) const { 1041 return IsBookmarkCommand(command_id) ? 1042 bookmark_menu_delegate_->GetTooltipText(command_id, p) : base::string16(); 1043 } 1044 1045 bool WrenchMenu::IsTriggerableEvent(views::MenuItemView* menu, 1046 const ui::Event& e) { 1047 return IsBookmarkCommand(menu->GetCommand()) ? 1048 bookmark_menu_delegate_->IsTriggerableEvent(menu, e) : 1049 MenuDelegate::IsTriggerableEvent(menu, e); 1050 } 1051 1052 bool WrenchMenu::GetDropFormats( 1053 MenuItemView* menu, 1054 int* formats, 1055 std::set<ui::OSExchangeData::CustomFormat>* custom_formats) { 1056 CreateBookmarkMenu(); 1057 return bookmark_menu_delegate_.get() && 1058 bookmark_menu_delegate_->GetDropFormats(menu, formats, custom_formats); 1059 } 1060 1061 bool WrenchMenu::AreDropTypesRequired(MenuItemView* menu) { 1062 CreateBookmarkMenu(); 1063 return bookmark_menu_delegate_.get() && 1064 bookmark_menu_delegate_->AreDropTypesRequired(menu); 1065 } 1066 1067 bool WrenchMenu::CanDrop(MenuItemView* menu, 1068 const ui::OSExchangeData& data) { 1069 CreateBookmarkMenu(); 1070 return bookmark_menu_delegate_.get() && 1071 bookmark_menu_delegate_->CanDrop(menu, data); 1072 } 1073 1074 int WrenchMenu::GetDropOperation( 1075 MenuItemView* item, 1076 const ui::DropTargetEvent& event, 1077 DropPosition* position) { 1078 return IsBookmarkCommand(item->GetCommand()) ? 1079 bookmark_menu_delegate_->GetDropOperation(item, event, position) : 1080 ui::DragDropTypes::DRAG_NONE; 1081 } 1082 1083 int WrenchMenu::OnPerformDrop(MenuItemView* menu, 1084 DropPosition position, 1085 const ui::DropTargetEvent& event) { 1086 if (!IsBookmarkCommand(menu->GetCommand())) 1087 return ui::DragDropTypes::DRAG_NONE; 1088 1089 int result = bookmark_menu_delegate_->OnPerformDrop(menu, position, event); 1090 return result; 1091 } 1092 1093 bool WrenchMenu::ShowContextMenu(MenuItemView* source, 1094 int command_id, 1095 const gfx::Point& p, 1096 ui::MenuSourceType source_type) { 1097 return IsBookmarkCommand(command_id) ? 1098 bookmark_menu_delegate_->ShowContextMenu(source, command_id, p, 1099 source_type) : 1100 false; 1101 } 1102 1103 bool WrenchMenu::CanDrag(MenuItemView* menu) { 1104 return IsBookmarkCommand(menu->GetCommand()) ? 1105 bookmark_menu_delegate_->CanDrag(menu) : false; 1106 } 1107 1108 void WrenchMenu::WriteDragData(MenuItemView* sender, 1109 ui::OSExchangeData* data) { 1110 DCHECK(IsBookmarkCommand(sender->GetCommand())); 1111 return bookmark_menu_delegate_->WriteDragData(sender, data); 1112 } 1113 1114 int WrenchMenu::GetDragOperations(MenuItemView* sender) { 1115 return IsBookmarkCommand(sender->GetCommand()) ? 1116 bookmark_menu_delegate_->GetDragOperations(sender) : 1117 MenuDelegate::GetDragOperations(sender); 1118 } 1119 1120 int WrenchMenu::GetMaxWidthForMenu(MenuItemView* menu) { 1121 if (IsBookmarkCommand(menu->GetCommand())) 1122 return bookmark_menu_delegate_->GetMaxWidthForMenu(menu); 1123 int max_width = -1; 1124 // If recent tabs menu is available, it will decide if |menu| is one of recent 1125 // tabs; if yes, it would return the menu width for recent tabs. 1126 // otherwise, it would return -1. 1127 if (recent_tabs_menu_model_delegate_.get()) 1128 max_width = recent_tabs_menu_model_delegate_->GetMaxWidthForMenu(menu); 1129 if (max_width == -1) 1130 max_width = MenuDelegate::GetMaxWidthForMenu(menu); 1131 return max_width; 1132 } 1133 1134 bool WrenchMenu::IsItemChecked(int command_id) const { 1135 if (IsBookmarkCommand(command_id)) 1136 return false; 1137 1138 const Entry& entry = command_id_to_entry_.find(command_id)->second; 1139 return entry.first->IsItemCheckedAt(entry.second); 1140 } 1141 1142 bool WrenchMenu::IsCommandEnabled(int command_id) const { 1143 if (IsBookmarkCommand(command_id)) 1144 return true; 1145 1146 if (command_id == 0) 1147 return false; // The root item. 1148 1149 // The items representing the cut menu (cut/copy/paste) and zoom menu 1150 // (increment/decrement/reset) are always enabled. The child views of these 1151 // items enabled state updates appropriately. 1152 if (command_id == IDC_CUT || command_id == IDC_ZOOM_MINUS) 1153 return true; 1154 1155 const Entry& entry = command_id_to_entry_.find(command_id)->second; 1156 return entry.first->IsEnabledAt(entry.second); 1157 } 1158 1159 void WrenchMenu::ExecuteCommand(int command_id, int mouse_event_flags) { 1160 if (IsBookmarkCommand(command_id)) { 1161 bookmark_menu_delegate_->ExecuteCommand(command_id, mouse_event_flags); 1162 return; 1163 } 1164 1165 if (command_id == IDC_CUT || command_id == IDC_ZOOM_MINUS) { 1166 // These items are represented by child views. If ExecuteCommand is invoked 1167 // it means the user clicked on the area around the buttons and we should 1168 // not do anyting. 1169 return; 1170 } 1171 1172 const Entry& entry = command_id_to_entry_.find(command_id)->second; 1173 return entry.first->ActivatedAt(entry.second, mouse_event_flags); 1174 } 1175 1176 bool WrenchMenu::GetAccelerator(int command_id, 1177 ui::Accelerator* accelerator) const { 1178 if (IsBookmarkCommand(command_id)) 1179 return false; 1180 1181 if (command_id == IDC_CUT || command_id == IDC_ZOOM_MINUS) { 1182 // These have special child views; don't show the accelerator for them. 1183 return false; 1184 } 1185 1186 CommandIDToEntry::const_iterator ix = command_id_to_entry_.find(command_id); 1187 const Entry& entry = ix->second; 1188 ui::Accelerator menu_accelerator; 1189 if (!entry.first->GetAcceleratorAt(entry.second, &menu_accelerator)) 1190 return false; 1191 1192 *accelerator = ui::Accelerator(menu_accelerator.key_code(), 1193 menu_accelerator.modifiers()); 1194 return true; 1195 } 1196 1197 void WrenchMenu::WillShowMenu(MenuItemView* menu) { 1198 if (menu == bookmark_menu_) 1199 CreateBookmarkMenu(); 1200 } 1201 1202 void WrenchMenu::WillHideMenu(MenuItemView* menu) { 1203 // Turns off the fade out animation of the wrench menus if 1204 // |feedback_menu_item_| is selected. This excludes the wrench menu itself 1205 // from the snapshot in the feedback UI. 1206 if (menu->HasSubmenu() && feedback_menu_item_ && 1207 feedback_menu_item_->IsSelected()) { 1208 // It's okay to just turn off the animation and no to take care the 1209 // animation back because the menu widget will be recreated next time 1210 // it's opened. See ToolbarView::RunMenu() and Init() of this class. 1211 menu->GetSubmenu()->GetWidget()-> 1212 SetVisibilityChangedAnimationsEnabled(false); 1213 } 1214 } 1215 1216 void WrenchMenu::BookmarkModelChanged() { 1217 DCHECK(bookmark_menu_delegate_.get()); 1218 if (!bookmark_menu_delegate_->is_mutating_model()) 1219 root_->Cancel(); 1220 } 1221 1222 void WrenchMenu::Observe(int type, 1223 const content::NotificationSource& source, 1224 const content::NotificationDetails& details) { 1225 switch (type) { 1226 case chrome::NOTIFICATION_GLOBAL_ERRORS_CHANGED: 1227 // A change in the global errors list can add or remove items from the 1228 // menu. Close the menu to avoid have a stale menu on-screen. 1229 if (root_) 1230 root_->Cancel(); 1231 break; 1232 default: 1233 NOTREACHED(); 1234 } 1235 } 1236 1237 void WrenchMenu::PopulateMenu(MenuItemView* parent, 1238 MenuModel* model) { 1239 for (int i = 0, max = model->GetItemCount(); i < max; ++i) { 1240 // The button container menu items have a special height which we have to 1241 // use instead of the normal height. 1242 int height = 0; 1243 if (use_new_menu_ && 1244 (model->GetCommandIdAt(i) == IDC_CUT || 1245 model->GetCommandIdAt(i) == IDC_ZOOM_MINUS)) 1246 height = kMenuItemContainingButtonsHeight; 1247 1248 // Add the menu item at the end. 1249 int menu_index = parent->HasSubmenu() ? 1250 parent->GetSubmenu()->child_count() : 0; 1251 MenuItemView* item = AddMenuItem( 1252 parent, menu_index, model, i, model->GetTypeAt(i), height); 1253 1254 if (model->GetTypeAt(i) == MenuModel::TYPE_SUBMENU) 1255 PopulateMenu(item, model->GetSubmenuModelAt(i)); 1256 1257 switch (model->GetCommandIdAt(i)) { 1258 case IDC_CUT: 1259 DCHECK_EQ(MenuModel::TYPE_COMMAND, model->GetTypeAt(i)); 1260 DCHECK_LT(i + 2, max); 1261 DCHECK_EQ(IDC_COPY, model->GetCommandIdAt(i + 1)); 1262 DCHECK_EQ(IDC_PASTE, model->GetCommandIdAt(i + 2)); 1263 item->SetTitle(l10n_util::GetStringUTF16(IDS_EDIT2)); 1264 item->AddChildView(new CutCopyPasteView(this, model, 1265 i, i + 1, i + 2)); 1266 i += 2; 1267 break; 1268 1269 case IDC_ZOOM_MINUS: 1270 DCHECK_EQ(MenuModel::TYPE_COMMAND, model->GetTypeAt(i)); 1271 DCHECK_EQ(IDC_ZOOM_PLUS, model->GetCommandIdAt(i + 1)); 1272 DCHECK_EQ(IDC_FULLSCREEN, model->GetCommandIdAt(i + 2)); 1273 item->SetTitle(l10n_util::GetStringUTF16(IDS_ZOOM_MENU2)); 1274 item->AddChildView(new ZoomView(this, model, i, i + 1, i + 2)); 1275 i += 2; 1276 break; 1277 1278 case IDC_BOOKMARKS_MENU: 1279 DCHECK(!bookmark_menu_); 1280 bookmark_menu_ = item; 1281 break; 1282 1283 #if defined(GOOGLE_CHROME_BUILD) 1284 case IDC_FEEDBACK: 1285 DCHECK(!feedback_menu_item_); 1286 feedback_menu_item_ = item; 1287 break; 1288 #endif 1289 1290 case IDC_RECENT_TABS_MENU: 1291 DCHECK(!recent_tabs_menu_model_delegate_.get()); 1292 recent_tabs_menu_model_delegate_.reset( 1293 new RecentTabsMenuModelDelegate(this, model->GetSubmenuModelAt(i), 1294 item)); 1295 break; 1296 1297 default: 1298 break; 1299 } 1300 } 1301 } 1302 1303 MenuItemView* WrenchMenu::AddMenuItem(MenuItemView* parent, 1304 int menu_index, 1305 MenuModel* model, 1306 int model_index, 1307 MenuModel::ItemType menu_type, 1308 int height) { 1309 int command_id = model->GetCommandIdAt(model_index); 1310 DCHECK(command_id > -1 || 1311 (command_id == -1 && 1312 model->GetTypeAt(model_index) == MenuModel::TYPE_SEPARATOR)); 1313 1314 if (command_id > -1) { // Don't add separators to |command_id_to_entry_|. 1315 // All command ID's should be unique except for IDC_SHOW_HISTORY which is 1316 // in both wrench menu and RecentTabs submenu, 1317 if (command_id != IDC_SHOW_HISTORY) { 1318 DCHECK(command_id_to_entry_.find(command_id) == 1319 command_id_to_entry_.end()) 1320 << "command ID " << command_id << " already exists!"; 1321 } 1322 command_id_to_entry_[command_id].first = model; 1323 command_id_to_entry_[command_id].second = model_index; 1324 } 1325 1326 MenuItemView* menu_item = NULL; 1327 if (height > 0) { 1328 // For menu items with a special menu height we use our special class to be 1329 // able to modify the item height. 1330 menu_item = new ButtonContainerMenuItemView(parent, command_id, height); 1331 parent->GetSubmenu()->AddChildViewAt(menu_item, menu_index); 1332 } else { 1333 // For all other cases we use the more generic way to add menu items. 1334 menu_item = views::MenuModelAdapter::AddMenuItemFromModelAt( 1335 model, model_index, parent, menu_index, command_id); 1336 } 1337 1338 if (menu_item) { 1339 // Flush all buttons to the right side of the menu for the new menu type. 1340 menu_item->set_use_right_margin(!use_new_menu_); 1341 menu_item->SetVisible(model->IsVisibleAt(model_index)); 1342 1343 if (menu_type == MenuModel::TYPE_COMMAND && model->HasIcons()) { 1344 gfx::Image icon; 1345 if (model->GetIconAt(model_index, &icon)) 1346 menu_item->SetIcon(*icon.ToImageSkia()); 1347 } 1348 } 1349 1350 return menu_item; 1351 } 1352 1353 void WrenchMenu::CancelAndEvaluate(MenuModel* model, int index) { 1354 selected_menu_model_ = model; 1355 selected_index_ = index; 1356 root_->Cancel(); 1357 } 1358 1359 void WrenchMenu::CreateBookmarkMenu() { 1360 if (bookmark_menu_delegate_.get()) 1361 return; // Already created the menu. 1362 1363 BookmarkModel* model = 1364 BookmarkModelFactory::GetForProfile(browser_->profile()); 1365 if (!model->loaded()) 1366 return; 1367 1368 model->AddObserver(this); 1369 1370 // TODO(oshima): Replace with views only API. 1371 views::Widget* parent = views::Widget::GetWidgetForNativeWindow( 1372 browser_->window()->GetNativeWindow()); 1373 bookmark_menu_delegate_.reset( 1374 new BookmarkMenuDelegate(browser_, 1375 browser_, 1376 parent, 1377 WrenchMenuModel::kMinBookmarkCommandId, 1378 WrenchMenuModel::kMaxBookmarkCommandId)); 1379 bookmark_menu_delegate_->Init(this, 1380 bookmark_menu_, 1381 model->bookmark_bar_node(), 1382 0, 1383 BookmarkMenuDelegate::SHOW_PERMANENT_FOLDERS, 1384 BOOKMARK_LAUNCH_LOCATION_WRENCH_MENU); 1385 } 1386 1387 int WrenchMenu::ModelIndexFromCommandId(int command_id) const { 1388 CommandIDToEntry::const_iterator ix = command_id_to_entry_.find(command_id); 1389 DCHECK(ix != command_id_to_entry_.end()); 1390 return ix->second.second; 1391 } 1392