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