diff options
author | sky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-23 17:27:08 +0000 |
---|---|---|
committer | sky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-23 17:27:08 +0000 |
commit | eccda9db41ac8285b256d59b994d64fa3760cf2b (patch) | |
tree | 2ba77a7c0660d0227da5416824b7b16ef0bca3a9 /chrome | |
parent | a658ed17827051f9a9fc16d9eb01fcc018806f2f (diff) | |
download | chromium_src-eccda9db41ac8285b256d59b994d64fa3760cf2b.zip chromium_src-eccda9db41ac8285b256d59b994d64fa3760cf2b.tar.gz chromium_src-eccda9db41ac8285b256d59b994d64fa3760cf2b.tar.bz2 |
Attempt 2 at landing the following. This differs from the first in
that I wasn't setting a field in a default: of a switch where I have
a NOTREACHED.
Moves cut/copy/paste and increment/descrement/fullscreen into buttons
on views.
BUG=45734
TEST=none
TBR=jcivelli@chromium.org
Review URL: http://codereview.chromium.org/2868029
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@50613 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/browser/views/toolbar_view.cc | 29 | ||||
-rw-r--r-- | chrome/browser/views/toolbar_view.h | 4 | ||||
-rw-r--r-- | chrome/browser/views/wrench_menu.cc | 658 | ||||
-rw-r--r-- | chrome/browser/views/wrench_menu.h | 85 | ||||
-rw-r--r-- | chrome/browser/wrench_menu_model.cc | 4 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 4 |
6 files changed, 780 insertions, 4 deletions
diff --git a/chrome/browser/views/toolbar_view.cc b/chrome/browser/views/toolbar_view.cc index 2cf5ca6..3175922 100644 --- a/chrome/browser/views/toolbar_view.cc +++ b/chrome/browser/views/toolbar_view.cc @@ -19,6 +19,7 @@ #include "chrome/browser/views/browser_actions_container.h" #include "chrome/browser/views/event_utils.h" #include "chrome/browser/views/frame/browser_view.h" +#include "chrome/browser/views/wrench_menu.h" #include "chrome/browser/wrench_menu_model.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/notification_service.h" @@ -181,6 +182,10 @@ void ToolbarView::Init(Profile* profile) { bookmark_menu_ = NULL; } + // Catch the case where the window is created after we detect a new version. + if (Singleton<UpgradeDetector>::get()->notify_upgrade()) + ShowUpgradeReminder(); + LoadImages(); // Always add children in order from left to right, for accessibility. @@ -199,10 +204,6 @@ void ToolbarView::Init(Profile* profile) { location_bar_->Init(); show_home_button_.Init(prefs::kShowHomeButton, profile->GetPrefs(), this); - // Catch the case where the window is created after we detect a new version. - if (Singleton<UpgradeDetector>::get()->notify_upgrade()) - ShowUpgradeReminder(); - SetProfile(profile); if (!app_menu_model_.get()) { if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kNewWrenchMenu)) { @@ -742,6 +743,26 @@ void ToolbarView::RunPageMenu(const gfx::Point& pt) { } void ToolbarView::RunAppMenu(const gfx::Point& pt) { + if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kNewWrenchMenu)) { + bool destroyed_flag = false; + destroyed_flag_ = &destroyed_flag; + wrench_menu_.reset(new WrenchMenu(browser_)); + wrench_menu_->Init(app_menu_model_.get()); + + for (size_t i = 0; i < menu_listeners_.size(); ++i) + menu_listeners_[i]->OnMenuOpened(); + + wrench_menu_->RunMenu(app_menu_); + + if (destroyed_flag) + return; + destroyed_flag_ = NULL; + + // Stop pulsating the upgrade reminder on the app menu, if active. + upgrade_reminder_pulse_timer_.Stop(); + return; + } + bool destroyed_flag = false; destroyed_flag_ = &destroyed_flag; diff --git a/chrome/browser/views/toolbar_view.h b/chrome/browser/views/toolbar_view.h index 75eacdb..98fc915 100644 --- a/chrome/browser/views/toolbar_view.h +++ b/chrome/browser/views/toolbar_view.h @@ -27,6 +27,7 @@ class BrowserActionsContainer; class Browser; class Profile; +class WrenchMenu; namespace views { class Menu2; @@ -209,6 +210,9 @@ class ToolbarView : public AccessibleToolbarView, scoped_ptr<views::Menu2> page_menu_menu_; scoped_ptr<views::Menu2> app_menu_menu_; + // Wrench menu. Only used if kNewWrenchMenu is set. + scoped_ptr<WrenchMenu> wrench_menu_; + // Vector of listeners to receive callbacks when the menu opens. std::vector<views::MenuListener*> menu_listeners_; diff --git a/chrome/browser/views/wrench_menu.cc b/chrome/browser/views/wrench_menu.cc new file mode 100644 index 0000000..fa56718 --- /dev/null +++ b/chrome/browser/views/wrench_menu.cc @@ -0,0 +1,658 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/views/wrench_menu.h" + +#include <cmath> + +#include "app/l10n_util.h" +#include "app/resource_bundle.h" +#include "base/utf_string_conversions.h" +#include "chrome/app/chrome_dll_resource.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/host_zoom_map.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" +#include "chrome/common/notification_source.h" +#include "chrome/common/notification_type.h" +#include "gfx/canvas.h" +#include "gfx/skia_util.h" +#include "grit/generated_resources.h" +#include "grit/theme_resources.h" +#include "third_party/skia/include/core/SkPaint.h" +#include "views/background.h" +#include "views/controls/button/image_button.h" +#include "views/controls/button/text_button.h" +#include "views/controls/label.h" +#include "views/controls/menu/menu_config.h" +#include "views/controls/menu/menu_item_view.h" +#include "views/controls/menu/menu_scroll_view_container.h" +#include "views/controls/menu/submenu_view.h" +#include "views/window/window.h" + +using menus::MenuModel; +using views::CustomButton; +using views::ImageButton; +using views::Label; +using views::MenuConfig; +using views::MenuItemView; +using views::TextButton; +using views::View; + +namespace { + +// Colors used for buttons. +const SkColor kHotBorderColor = SkColorSetARGB(72, 0, 0, 0); +const SkColor kBorderColor = SkColorSetARGB(36, 0, 0, 0); +const SkColor kPushedBorderColor = SkColorSetARGB(72, 0, 0, 0); +const SkColor kHotBackgroundColor = SkColorSetARGB(204, 255, 255, 255); +const SkColor kBackgroundColor = SkColorSetARGB(102, 255, 255, 255); +const SkColor kPushedBackgroundColor = SkColorSetARGB(13, 0, 0, 0); + +// Horizontal padding on the edges of the buttons. +const int kHorizontalPadding = 6; + +// Subclass of ImageButton whose preferred size includes the size of the border. +class FullscreenButton : public ImageButton { + public: + FullscreenButton(views::ButtonListener* listener) : ImageButton(listener) {} + + virtual gfx::Size GetPreferredSize() { + gfx::Size pref = ImageButton::GetPreferredSize(); + gfx::Insets insets; + if (border()) + border()->GetInsets(&insets); + pref.Enlarge(insets.width(), insets.height()); + return pref; + } + + private: + DISALLOW_COPY_AND_ASSIGN(FullscreenButton); +}; + +// Border for buttons contained in the menu. This is only used for getting the +// insets, the actual painting is done in MenuButtonBackground. +class MenuButtonBorder : public views::Border { + public: + MenuButtonBorder() {} + + virtual void Paint(const View& view, gfx::Canvas* canvas) const { + // Painting of border is done in MenuButtonBackground. + } + + virtual void GetInsets(gfx::Insets* insets) const { + insets->Set(MenuConfig::instance().item_no_icon_top_margin, + kHorizontalPadding, + MenuConfig::instance().item_no_icon_bottom_margin, + kHorizontalPadding); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MenuButtonBorder); +}; + +// Combination border/background for the buttons contained in the menu. The +// painting of the border/background is done here as TextButton does not always +// paint the border. +class MenuButtonBackground : public views::Background { + public: + enum ButtonType { + LEFT_BUTTON, + CENTER_BUTTON, + RIGHT_BUTTON, + SINGLE_BUTTON, + }; + + explicit MenuButtonBackground(ButtonType type) + : type_(type), + left_button_(NULL), + right_button_(NULL) {} + + // Used when the type is CENTER_BUTTON to determine if the left/right edge + // needs to be rendered selected. + void SetOtherButtons(CustomButton* left_button, CustomButton* right_button) { + left_button_ = left_button; + right_button_ = right_button; + } + + virtual void Paint(gfx::Canvas* canvas, View* view) const { + CustomButton::ButtonState state = + (view->GetClassName() == views::Label::kViewClassName) ? + CustomButton::BS_NORMAL : static_cast<CustomButton*>(view)->state(); + int w = view->width(); + int h = view->height(); + switch (type_) { + case LEFT_BUTTON: + canvas->FillRectInt(background_color(state), 1, 1, w, h - 2); + canvas->FillRectInt(border_color(state), 2, 0, w, 1); + canvas->FillRectInt(border_color(state), 1, 1, 1, 1); + canvas->FillRectInt(border_color(state), 0, 2, 1, h - 4); + canvas->FillRectInt(border_color(state), 1, h - 2, 1, 1); + canvas->FillRectInt(border_color(state), 2, h - 1, w, 1); + break; + + case CENTER_BUTTON: { + canvas->FillRectInt(background_color(state), 1, 1, w - 2, h - 2); + SkColor left_color = state != CustomButton::BS_NORMAL ? + border_color(state) : border_color(left_button_->state()); + canvas->FillRectInt(left_color, 0, 0, 1, h); + canvas->FillRectInt(border_color(state), 1, 0, w - 2, 1); + canvas->FillRectInt(border_color(state), 1, h - 1, w - 2, 1); + SkColor right_color = state != CustomButton::BS_NORMAL ? + border_color(state) : border_color(right_button_->state()); + canvas->FillRectInt(right_color, w - 1, 0, 1, h); + break; + } + + case RIGHT_BUTTON: + canvas->FillRectInt(background_color(state), 0, 1, w - 1, h - 2); + canvas->FillRectInt(border_color(state), 0, 0, w - 2, 1); + canvas->FillRectInt(border_color(state), w - 2, 1, 1, 1); + canvas->FillRectInt(border_color(state), w - 1, 2, 1, h - 4); + canvas->FillRectInt(border_color(state), w - 2, h - 2, 1, 1); + canvas->FillRectInt(border_color(state), 0, h - 1, w - 2, 1); + break; + + case SINGLE_BUTTON: + canvas->FillRectInt(background_color(state), 1, 1, w - 2, h - 2); + canvas->FillRectInt(border_color(state), 2, 0, w - 4, 1); + canvas->FillRectInt(border_color(state), 1, 1, 1, 1); + canvas->FillRectInt(border_color(state), 0, 2, 1, h - 4); + canvas->FillRectInt(border_color(state), 1, h - 2, 1, 1); + canvas->FillRectInt(border_color(state), 2, h - 1, w - 4, 1); + canvas->FillRectInt(border_color(state), w - 2, 1, 1, 1); + canvas->FillRectInt(border_color(state), w - 1, 2, 1, h - 4); + canvas->FillRectInt(border_color(state), w - 2, h - 2, 1, 1); + break; + + default: + NOTREACHED(); + break; + } + } + + private: + static SkColor border_color(CustomButton::ButtonState state) { + switch (state) { + case CustomButton::BS_HOT: return kHotBorderColor; + case CustomButton::BS_PUSHED: return kPushedBorderColor; + default: return kBorderColor; + } + } + + static SkColor background_color(CustomButton::ButtonState state) { + switch (state) { + case CustomButton::BS_HOT: return kHotBackgroundColor; + case CustomButton::BS_PUSHED: return kPushedBackgroundColor; + default: return kBackgroundColor; + } + } + + const ButtonType type_; + + // See description above setter for details. + CustomButton* left_button_; + CustomButton* right_button_; + + DISALLOW_COPY_AND_ASSIGN(MenuButtonBackground); +}; + +// A View subclass that forces SchedulePaint to paint all. Normally when the +// mouse enters/exits a button the buttons invokes SchedulePaint. As part of the +// button border (MenuButtonBackground) is rendered by the button to the +// left/right of it SchedulePaint on the the button may not be enough, so this +// forces a paint all. +class ScheduleAllView : public views::View { + public: + ScheduleAllView() {} + + virtual void SchedulePaint(const gfx::Rect& r, bool urgent) { + if (!IsVisible()) + return; + + if (GetParent()) + GetParent()->SchedulePaint(bounds(), urgent); + } + + private: + DISALLOW_COPY_AND_ASSIGN(ScheduleAllView); +}; + +TextButton* CreateAndConfigureButton(View* parent, + views::ButtonListener* listener, + int string_id, + MenuButtonBackground::ButtonType type, + MenuModel* model, + int index, + MenuButtonBackground** background) { + TextButton* button = + new TextButton(listener, l10n_util::GetString(string_id)); + button->SetFocusable(true); + button->set_request_focus_on_press(false); + button->set_tag(index); + button->SetEnabled(model->IsEnabledAt(index)); + button->set_prefix_type(TextButton::PREFIX_HIDE); + MenuButtonBackground* bg = new MenuButtonBackground(type); + button->set_background(bg); + button->SetEnabledColor(MenuConfig::instance().text_color); + if (background) + *background = bg; + button->set_border(new MenuButtonBorder()); + button->set_alignment(TextButton::ALIGN_CENTER); + button->SetShowHighlighted(true); + button->SetNormalHasBorder(true); + button->SetFont(views::MenuConfig::instance().font); + button->ClearMaxTextSize(); + parent->AddChildView(button); + return button; +} + +} // namespace + +// CutCopyPasteView ------------------------------------------------------------ + +// CutCopyPasteView is the view containing the cut/copy/paste buttons. +class WrenchMenu::CutCopyPasteView : public ScheduleAllView, + public views::ButtonListener { + public: + CutCopyPasteView(WrenchMenu* menu, + MenuModel* menu_model, + int cut_index, + int copy_index, + int paste_index) + : menu_(menu), + menu_model_(menu_model) { + TextButton* cut = CreateAndConfigureButton( + this, this, IDS_CUT, MenuButtonBackground::LEFT_BUTTON, menu_model, + cut_index, NULL); + + MenuButtonBackground* copy_background = NULL; + CreateAndConfigureButton( + this, this, IDS_COPY, MenuButtonBackground::CENTER_BUTTON, menu_model, + copy_index, ©_background); + + TextButton* paste = CreateAndConfigureButton( + this, this, IDS_PASTE, MenuButtonBackground::RIGHT_BUTTON, menu_model, + paste_index, NULL); + + copy_background->SetOtherButtons(cut, paste); + } + + gfx::Size GetPreferredSize() { + // Returned height doesn't matter as MenuItemView forces everything to the + // height of the menuitemview. + return gfx::Size(GetMaxChildViewPreferredWidth() * GetChildViewCount(), 0); + } + + void Layout() { + // All buttons are given the same width. + int width = GetMaxChildViewPreferredWidth(); + for (int i = 0; i < GetChildViewCount(); ++i) + GetChildViewAt(i)->SetBounds(i * width, 0, width, height()); + } + + // ButtonListener + virtual void ButtonPressed(views::Button* sender, const views::Event& event) { + menu_->CancelAndEvaluate(menu_model_, sender->tag()); + } + + private: + // Returns the max preferred width of all the children. + int GetMaxChildViewPreferredWidth() { + int width = 0; + for (int i = 0; i < GetChildViewCount(); ++i) + width = std::max(width, GetChildViewAt(i)->GetPreferredSize().width()); + return width; + } + + WrenchMenu* menu_; + MenuModel* menu_model_; + + DISALLOW_COPY_AND_ASSIGN(CutCopyPasteView); +}; + +// ZoomView -------------------------------------------------------------------- + +// Padding between the increment buttons and the reset button. +static const int kZoomPadding = 6; + +// ZoomView contains the various zoom controls: two buttons to increase/decrease +// the zoom, a label showing the current zoom percent, and a button to go +// full-screen. +class WrenchMenu::ZoomView : public ScheduleAllView, + public views::ButtonListener, + public NotificationObserver { + public: + ZoomView(WrenchMenu* menu, + MenuModel* menu_model, + int increment_index, + int decrement_index, + int fullscreen_index) + : menu_(menu), + menu_model_(menu_model), + fullscreen_index_(fullscreen_index), + increment_button_(NULL), + zoom_label_(NULL), + decrement_button_(NULL), + fullscreen_button_(NULL), + zoom_label_width_(0) { + increment_button_ = CreateAndConfigureButton( + this, this, IDS_ZOOM_PLUS2, MenuButtonBackground::LEFT_BUTTON, + menu_model, increment_index, NULL); + + zoom_label_ = new Label(l10n_util::GetStringF(IDS_ZOOM_PERCENT, L"100")); + zoom_label_->SetColor(MenuConfig::instance().text_color); + zoom_label_->SetHorizontalAlignment(Label::ALIGN_RIGHT); + MenuButtonBackground* center_bg = + new MenuButtonBackground(MenuButtonBackground::CENTER_BUTTON); + zoom_label_->set_background(center_bg); + zoom_label_->set_border(new MenuButtonBorder()); + zoom_label_->SetFont(MenuConfig::instance().font); + AddChildView(zoom_label_); + // Get the preferred width given 100%, we'll use this in calculating our + // preferred size. + zoom_label_width_ = zoom_label_->GetPreferredSize().width(); + + decrement_button_ = CreateAndConfigureButton( + this, this, IDS_ZOOM_MINUS2, MenuButtonBackground::RIGHT_BUTTON, + menu_model, decrement_index, NULL); + + center_bg->SetOtherButtons(increment_button_, decrement_button_); + + fullscreen_button_ = new FullscreenButton(this); + fullscreen_button_->SetImage( + ImageButton::BS_NORMAL, + ResourceBundle::GetSharedInstance().GetBitmapNamed( + IDR_FULLSCREEN_MENU_BUTTON)); + fullscreen_button_->SetFocusable(true); + fullscreen_button_->set_request_focus_on_press(false); + fullscreen_button_->set_tag(fullscreen_index); + fullscreen_button_->SetImageAlignment( + ImageButton::ALIGN_CENTER, ImageButton::ALIGN_MIDDLE); + fullscreen_button_->set_border(views::Border::CreateEmptyBorder( + 0, kHorizontalPadding, 0, kHorizontalPadding)); + fullscreen_button_->set_background( + new MenuButtonBackground(MenuButtonBackground::SINGLE_BUTTON)); + AddChildView(fullscreen_button_); + + UpdateZoomControls(); + + registrar_.Add(this, NotificationType::ZOOM_LEVEL_CHANGED, + Source<Profile>(menu->browser_->profile())); + } + + gfx::Size GetPreferredSize() { + // The increment/decrement button are forced to the same width. + int button_width = std::max(increment_button_->GetPreferredSize().width(), + decrement_button_->GetPreferredSize().width()); + int fullscreen_width = fullscreen_button_->GetPreferredSize().width(); + // Returned height doesn't matter as MenuItemView forces everything to the + // height of the menuitemview. + return gfx::Size(button_width + zoom_label_width_ + button_width + + kZoomPadding + fullscreen_width, 0); + } + + void Layout() { + int x = 0; + int button_width = std::max(increment_button_->GetPreferredSize().width(), + decrement_button_->GetPreferredSize().width()); + gfx::Rect bounds(0, 0, button_width, height()); + + increment_button_->SetBounds(bounds); + + x += bounds.width(); + bounds.set_x(x); + bounds.set_width(zoom_label_width_); + zoom_label_->SetBounds(bounds); + + x += bounds.width(); + bounds.set_x(x); + bounds.set_width(button_width); + decrement_button_->SetBounds(bounds); + + x += bounds.width() + kZoomPadding; + bounds.set_x(x); + bounds.set_width(fullscreen_button_->GetPreferredSize().width()); + fullscreen_button_->SetBounds(bounds); + } + + // ButtonListener: + virtual void ButtonPressed(views::Button* sender, const views::Event& event) { + if (sender->tag() == fullscreen_index_) { + menu_->CancelAndEvaluate(menu_model_, sender->tag()); + } else { + // Zoom buttons don't close the menu. + menu_model_->ActivatedAt(sender->tag()); + } + } + + // NotificationObserver: + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + DCHECK_EQ(NotificationType::ZOOM_LEVEL_CHANGED, type.value); + UpdateZoomControls(); + } + + private: + void UpdateZoomControls() { + bool enable_increment, enable_decrement; + int zoom_percent = + static_cast<int>(GetZoom(&enable_increment, &enable_decrement) * 100); + zoom_label_->SetText(l10n_util::GetStringF( + IDS_ZOOM_PERCENT, IntToWString(zoom_percent))); + increment_button_->SetEnabled(enable_increment); + decrement_button_->SetEnabled(enable_decrement); + } + + double GetZoom(bool* enable_increment, bool* enable_decrement) { + // TODO: move this somewhere it can be shared. + TabContents* selected_tab = menu_->browser_->GetSelectedTabContents(); + *enable_decrement = *enable_increment = false; + if (!selected_tab) + return 1; + + HostZoomMap* zoom_map = selected_tab->profile()->GetHostZoomMap(); + if (!zoom_map) + return 1; + + int zoom_level = zoom_map->GetZoomLevel(selected_tab->GetURL()); + double value = static_cast<double>( + std::max(std::min(std::pow(1.2, zoom_level), 3.0), .5)); + *enable_decrement = (value != .5); + *enable_increment = (value != 3.0); + return value; + } + + // Hosting WrenchMenu. + WrenchMenu* menu_; + + // The menu model containing the increment/decrement/reset items. + MenuModel* menu_model_; + + // Index of the fullscreen menu item in the model. + const int fullscreen_index_; + + NotificationRegistrar registrar_; + + // Button for incrementing the zoom. + TextButton* increment_button_; + + // Label showing zoom as a percent. + Label* zoom_label_; + + // Button for decrementing the zoom. + TextButton* decrement_button_; + + ImageButton* fullscreen_button_; + + // Width given to |zoom_label_|. This is the width at 100%. + int zoom_label_width_; + + DISALLOW_COPY_AND_ASSIGN(ZoomView); +}; + +// WrenchMenu ------------------------------------------------------------------ + +WrenchMenu::WrenchMenu(Browser* browser) + : browser_(browser), + selected_menu_model_(NULL), + selected_index_(0) { +} + +WrenchMenu::~WrenchMenu() { +} + +void WrenchMenu::Init(menus::MenuModel* model) { + DCHECK(!root_.get()); + root_.reset(new MenuItemView(this)); + root_->set_has_icons(true); // We have checks, radios and icons, set this + // so we get the taller menu style. + int next_id = 1; + PopulateMenu(root_.get(), model, &next_id); +} + +void WrenchMenu::RunMenu(views::View* host) { + gfx::Point screen_loc; + views::View::ConvertPointToScreen(host, &screen_loc); + // Subtract 1 from the height to make the popup flush with the button border. + gfx::Rect bounds(screen_loc.x(), screen_loc.y(), host->width(), + host->height() - 1); + root_->RunMenuAt(host->GetWindow()->GetNativeWindow(), NULL, bounds, + MenuItemView::TOPRIGHT, true); + if (selected_menu_model_) + selected_menu_model_->ActivatedAt(selected_index_); +} + +bool WrenchMenu::IsItemChecked(int id) const { + const Entry& entry = id_to_entry_.find(id)->second; + return entry.first->IsItemCheckedAt(entry.second); +} + +bool WrenchMenu::IsCommandEnabled(int id) const { + if (id == 0) + return false; // The root item. + + const Entry& entry = id_to_entry_.find(id)->second; + int command_id = entry.first->GetCommandIdAt(entry.second); + // The items representing the cut (cut/copy/paste) and zoom menu + // (increment/decrement/reset) are always enabled. The child views of these + // items enabled state updates appropriately. + return command_id == IDC_CUT || command_id == IDC_ZOOM_MENU || + entry.first->IsEnabledAt(entry.second); +} + +void WrenchMenu::ExecuteCommand(int id) { + const Entry& entry = id_to_entry_.find(id)->second; + return entry.first->ActivatedAt(entry.second); +} + +bool WrenchMenu::GetAccelerator(int id, views::Accelerator* accelerator) { + const Entry& entry = id_to_entry_.find(id)->second; + int command_id = entry.first->GetCommandIdAt(entry.second); + if (command_id == IDC_CUT || command_id == IDC_ZOOM_PLUS) { + // These have special child views; don't show the accelerator for them. + return false; + } + + menus::Accelerator menu_accelerator; + if (!entry.first->GetAcceleratorAt(entry.second, &menu_accelerator)) + return false; + + *accelerator = views::Accelerator(menu_accelerator.GetKeyCode(), + menu_accelerator.modifiers()); + return true; +} + +void WrenchMenu::PopulateMenu(MenuItemView* parent, + MenuModel* model, + int* next_id) { + int index_offset = model->GetFirstItemIndex(NULL); + for (int i = 0, max = model->GetItemCount(); i < max; ++i) { + int index = i + index_offset; + + MenuItemView* item = + AppendMenuItem(parent, model, index, model->GetTypeAt(index), next_id); + + if (model->GetTypeAt(index) == MenuModel::TYPE_SUBMENU) + PopulateMenu(item, model->GetSubmenuModelAt(index), next_id); + + if (model->GetCommandIdAt(index) == IDC_CUT) { + DCHECK_EQ(MenuModel::TYPE_COMMAND, model->GetTypeAt(index)); + DCHECK_LT(i + 2, max); + DCHECK_EQ(IDC_COPY, model->GetCommandIdAt(index + 1)); + DCHECK_EQ(IDC_PASTE, model->GetCommandIdAt(index + 2)); + item->SetTitle(l10n_util::GetString(IDS_EDIT2)); + item->AddChildView( + new CutCopyPasteView(this, model, index, index + 1, index + 2)); + i += 2; + } else if (model->GetCommandIdAt(index) == IDC_ZOOM_PLUS) { + DCHECK_EQ(MenuModel::TYPE_COMMAND, model->GetTypeAt(index)); + DCHECK_EQ(IDC_ZOOM_MINUS, model->GetCommandIdAt(index + 1)); + DCHECK_EQ(IDC_FULLSCREEN, model->GetCommandIdAt(index + 2)); + item->SetTitle(l10n_util::GetString(IDS_ZOOM_MENU2)); + item->AddChildView( + new ZoomView(this, model, index, index + 1, index + 2)); + i += 2; + } + } +} + +MenuItemView* WrenchMenu::AppendMenuItem(MenuItemView* parent, + MenuModel* model, + int index, + MenuModel::ItemType menu_type, + int* next_id) { + int id = (*next_id)++; + SkBitmap icon; + std::wstring label; + MenuItemView::Type type; + switch (menu_type) { + case MenuModel::TYPE_COMMAND: + model->GetIconAt(index, &icon); + type = MenuItemView::NORMAL; + label = UTF16ToWide(model->GetLabelAt(index)); + break; + case MenuModel::TYPE_CHECK: + type = MenuItemView::CHECKBOX; + label = UTF16ToWide(model->GetLabelAt(index)); + break; + case MenuModel::TYPE_RADIO: + type = MenuItemView::RADIO; + label = UTF16ToWide(model->GetLabelAt(index)); + break; + case MenuModel::TYPE_SEPARATOR: + type = MenuItemView::SEPARATOR; + break; + case MenuModel::TYPE_SUBMENU: + type = MenuItemView::SUBMENU; + label = UTF16ToWide(model->GetLabelAt(index)); + break; + default: + NOTREACHED(); + type = MenuItemView::NORMAL; + break; + } + + id_to_entry_[id].first = model; + id_to_entry_[id].second = index; + + MenuItemView* menu_item = parent->AppendMenuItemImpl(id, label, icon, type); + + if (menu_type == MenuModel::TYPE_COMMAND && model->HasIcons()) { + SkBitmap icon; + if (model->GetIconAt(index, &icon)) + menu_item->SetIcon(icon); + } + + return menu_item; +} + +void WrenchMenu::CancelAndEvaluate(MenuModel* model, int index) { + selected_menu_model_ = model; + selected_index_ = index; + root_->Cancel(); +} diff --git a/chrome/browser/views/wrench_menu.h b/chrome/browser/views/wrench_menu.h new file mode 100644 index 0000000..b371026 --- /dev/null +++ b/chrome/browser/views/wrench_menu.h @@ -0,0 +1,85 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_VIEWS_WRENCH_MENU_H_ +#define CHROME_BROWSER_VIEWS_WRENCH_MENU_H_ + +#include <map> +#include <utility> + +#include "app/menus/menu_model.h" +#include "base/scoped_ptr.h" +#include "views/controls/menu/menu_delegate.h" + +class Browser; + +namespace views { +class MenuItemView; +class View; +} // namespace views + +// WrenchMenu adapts the WrenchMenuModel to view's menu related classes. +class WrenchMenu : public views::MenuDelegate { + public: + explicit WrenchMenu(Browser* browser); + ~WrenchMenu(); + + void Init(menus::MenuModel* model); + + // Shows the menu relative to the specified view. + void RunMenu(views::View* host); + + // MenuDelegate overrides: + virtual bool IsItemChecked(int id) const; + virtual bool IsCommandEnabled(int id) const; + virtual void ExecuteCommand(int id); + virtual bool GetAccelerator(int id, views::Accelerator* accelerator); + + private: + class CutCopyPasteView; + class ZoomView; + + typedef std::pair<menus::MenuModel*,int> Entry; + typedef std::map<int,Entry> IDToEntry; + + // Populates |parent| with all the child menus in |model|. Recursively invokes + // |PopulateMenu| for any submenu. |next_id| is incremented for every menu + // that is created. + void PopulateMenu(views::MenuItemView* parent, + menus::MenuModel* model, + int* next_id); + + // Adds a new menu to |parent| to represent the MenuModel/index pair passed + // in. + views::MenuItemView* AppendMenuItem(views::MenuItemView* parent, + menus::MenuModel* model, + int index, + menus::MenuModel::ItemType menu_type, + int* next_id); + + // Invoked from the cut/copy/paste menus. Cancels the current active menu and + // activates the menu item in |model| at |index|. + void CancelAndEvaluate(menus::MenuModel* model, int index); + + // The views menu. + scoped_ptr<views::MenuItemView> root_; + + // Maps from the ID as understood by MenuItemView to the model/index pair the + // item came from. + IDToEntry id_to_entry_; + + // Browser the menu is being shown for. + Browser* browser_; + + // |CancelAndEvaluate| sets |selected_menu_model_| and |selected_index_|. + // If |selected_menu_model_| is non-null after the menu completes + // ActivatedAt is invoked. This is done so that ActivatedAt isn't invoked + // while the message loop is nested. + menus::MenuModel* selected_menu_model_; + int selected_index_; + + DISALLOW_COPY_AND_ASSIGN(WrenchMenu); +}; + +#endif // CHROME_BROWSER_VIEWS_WRENCH_MENU_H_ diff --git a/chrome/browser/wrench_menu_model.cc b/chrome/browser/wrench_menu_model.cc index c5afa70..6af1064 100644 --- a/chrome/browser/wrench_menu_model.cc +++ b/chrome/browser/wrench_menu_model.cc @@ -148,12 +148,16 @@ void WrenchMenuModel::Build() { } void WrenchMenuModel::CreateCutCopyPaste() { + // WARNING: views/wrench_menu assumes these items are added in this order. If + // you change the order you'll need to update wrench_menu as well. AddItemWithStringId(IDC_CUT, IDS_CUT); AddItemWithStringId(IDC_COPY, IDS_COPY); AddItemWithStringId(IDC_PASTE, IDS_PASTE); } void WrenchMenuModel::CreateZoomFullscreen() { + // WARNING: views/wrench_menu assumes these items are added in this order. If + // you change the order you'll need to update wrench_menu as well. AddItemWithStringId(IDC_ZOOM_PLUS, IDS_ZOOM_PLUS); AddItemWithStringId(IDC_ZOOM_MINUS, IDS_ZOOM_MINUS); AddItemWithStringId(IDC_FULLSCREEN, IDS_FULLSCREEN); diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 4027cfa..c01b309 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -2660,6 +2660,8 @@ 'browser/views/update_recommended_message_box.h', 'browser/views/user_data_dir_dialog.cc', 'browser/views/user_data_dir_dialog.h', + 'browser/views/wrench_menu.cc', + 'browser/views/wrench_menu.h', 'browser/visitedlink_master.cc', 'browser/visitedlink_master.h', 'browser/visitedlink_event_listener.cc', @@ -3223,6 +3225,8 @@ ['include', '^browser/views/unhandled_keyboard_event_handler.h'], ['include', '^browser/views/view_text_utils.cc'], ['include', '^browser/views/view_text_utils.h'], + ['include', '^browser/views/wrench_menu.cc'], + ['include', '^browser/views/wrench_menu.h'], ['include', '^browser/window_sizer.cc'], ['include', '^browser/window_sizer.h'], # Exclude all of browser/gtk, then include the things we want. |