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