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