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