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