diff options
author | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-08 20:59:19 +0000 |
---|---|---|
committer | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-08 20:59:19 +0000 |
commit | 4bd23f35c4b4b7eab1796170789a8f00e7984972 (patch) | |
tree | 7808a313bbc7c4bf7b79717175dc29fc0b8557f1 | |
parent | d09b0922f3831944386850682b584e4dccf60357 (diff) | |
download | chromium_src-4bd23f35c4b4b7eab1796170789a8f00e7984972.zip chromium_src-4bd23f35c4b4b7eab1796170789a8f00e7984972.tar.gz chromium_src-4bd23f35c4b4b7eab1796170789a8f00e7984972.tar.bz2 |
A new menu system for views.
This is all the functionality needed for the page, app menus and browser system menus.
Review URL: http://codereview.chromium.org/119237
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@17895 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | base/gfx/native_widget_types.h | 3 | ||||
-rw-r--r-- | views/controls/menu/menu_2.cc | 62 | ||||
-rw-r--r-- | views/controls/menu/menu_2.h | 148 | ||||
-rw-r--r-- | views/controls/menu/native_menu_gtk.cc | 233 | ||||
-rw-r--r-- | views/controls/menu/native_menu_gtk.h | 59 | ||||
-rw-r--r-- | views/controls/menu/native_menu_win.cc | 346 | ||||
-rw-r--r-- | views/controls/menu/native_menu_win.h | 140 | ||||
-rw-r--r-- | views/controls/menu/simple_menu_model.cc | 129 | ||||
-rw-r--r-- | views/controls/menu/simple_menu_model.h | 93 | ||||
-rw-r--r-- | views/view.cc | 2 | ||||
-rw-r--r-- | views/view.h | 2 | ||||
-rw-r--r-- | views/views.gyp | 9 |
12 files changed, 1224 insertions, 2 deletions
diff --git a/base/gfx/native_widget_types.h b/base/gfx/native_widget_types.h index b6361cf..9444a61 100644 --- a/base/gfx/native_widget_types.h +++ b/base/gfx/native_widget_types.h @@ -61,18 +61,21 @@ typedef HWND NativeWindow; typedef HWND NativeEditView; typedef HDC NativeDrawingContext; typedef HCURSOR NativeCursor; +typedef HMENU NativeMenu; #elif defined(OS_MACOSX) typedef NSView* NativeView; typedef NSWindow* NativeWindow; typedef NSTextField* NativeEditView; typedef CGContext* NativeDrawingContext; typedef void* NativeCursor; +typedef void* NativeMenu; #elif defined(OS_LINUX) typedef GtkWidget* NativeView; typedef GtkWindow* NativeWindow; typedef GtkWidget* NativeEditView; typedef cairo_surface_t* NativeDrawingContext; typedef GdkCursor* NativeCursor; +typedef GtkWidget* NativeMenu; #endif // Note: for test_shell we're packing a pointer into the NativeViewId. So, if diff --git a/views/controls/menu/menu_2.cc b/views/controls/menu/menu_2.cc new file mode 100644 index 0000000..7d2fa8a --- /dev/null +++ b/views/controls/menu/menu_2.cc @@ -0,0 +1,62 @@ +// Copyright (c) 2009 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 "views/controls/menu/menu_2.h" + +#include "base/compiler_specific.h" +#include "views/controls/menu/menu_wrapper.h" + +namespace views { + +//////////////////////////////////////////////////////////////////////////////// +// Menu2Model, public: + +// static +bool Menu2Model::GetModelAndIndexForCommandId(int command_id, + Menu2Model** model, int* index) { + int item_count = (*model)->GetItemCount(); + for (int i = 0; i < item_count; ++i) { + if ((*model)->GetTypeAt(i) == TYPE_SUBMENU) { + Menu2Model* submenu_model = (*model)->GetSubmenuModelAt(i); + if (GetModelAndIndexForCommandId(command_id, &submenu_model, index)) { + *model = submenu_model; + return true; + } + } + if ((*model)->GetCommandIdAt(i) == command_id) { + *index = i; + return true; + } + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// Menu2, public: + +Menu2::Menu2(Menu2Model* model, Menu2Delegate* delegate) + : model_(model), + delegate_(delegate), + ALLOW_THIS_IN_INITIALIZER_LIST( + wrapper_(MenuWrapper::CreateWrapper(this))) { + Rebuild(); +} + +gfx::NativeMenu Menu2::GetNativeMenu() const { + return wrapper_->GetNativeMenu(); +} + +void Menu2::RunMenuAt(const gfx::Point& point, Alignment alignment) { + wrapper_->RunMenuAt(point, alignment); +} + +void Menu2::Rebuild() { + wrapper_->Rebuild(); +} + +void Menu2::UpdateStates() { + wrapper_->UpdateStates(); +} + +} // namespace diff --git a/views/controls/menu/menu_2.h b/views/controls/menu/menu_2.h new file mode 100644 index 0000000..b50efd7 --- /dev/null +++ b/views/controls/menu/menu_2.h @@ -0,0 +1,148 @@ +// Copyright (c) 2009 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 CONTROLS_MENU_VIEWS_MENU_2_H_ +#define CONTROLS_MENU_VIEWS_MENU_2_H_ + +#include <string> + +#include "base/gfx/native_widget_types.h" + +namespace gfx { +class Point; +} +class SkBitmap; + +namespace views { + +class Accelerator; +class Menu2; +class MenuWrapper; + +// The Menu2Model is an interface implemented by an object that provides the +// content of a menu. +class Menu2Model { + public: + virtual ~Menu2Model() {} + + // The type of item. + enum ItemType { + TYPE_COMMAND, + TYPE_CHECK, + TYPE_RADIO, + TYPE_SEPARATOR, + TYPE_SUBMENU + }; + + // Returns true if any of the items within the model have icons. Not all + // platforms support icons in menus natively and so this is a hint for + // triggering a custom rendering mode. + virtual bool HasIcons() const = 0; + + // Returns the index of the first item. This is 0 for most menus except the + // system menu on Windows. |native_menu| is the menu to locate the start index + // within. It is guaranteed to be reset to a clean default state. + // IMPORTANT: If the model implementation returns something _other_ than 0 + // here, it must offset the values for |index| it passes to the + // methods below by this number - this is NOT done automatically! + virtual int GetFirstItemIndex(gfx::NativeMenu native_menu) const { return 0; } + + // Returns the number of items in the menu. + virtual int GetItemCount() const = 0; + + // Returns the type of item at the specified index. + virtual ItemType GetTypeAt(int index) const = 0; + + // Returns the command id of the item at the specified index. + virtual int GetCommandIdAt(int index) const = 0; + + // Returns the label of the item at the specified index. + virtual std::wstring GetLabelAt(int index) const = 0; + + // Returns true if the label at the specified index can change over the course + // of the menu's lifetime. If this function returns true, the label of the + // menu item will be updated each time the menu is shown. + virtual bool IsLabelDynamicAt(int index) const = 0; + + // Gets the acclerator information for the specified index, returning true if + // there is a shortcut accelerator for the item, false otherwise. + virtual bool GetAcceleratorAt(int index, + views::Accelerator* accelerator) const = 0; + + // Returns the checked state of the item at the specified index. + virtual bool IsItemCheckedAt(int index) const = 0; + + // Returns the id of the group of radio items that the item at the specified + // index belongs to. + virtual int GetGroupIdAt(int index) const = 0; + + // Gets the icon for the item at the specified index, returning true if there + // is an icon, false otherwise. + virtual bool GetIconAt(int index, SkBitmap* icon) const = 0; + + // Returns the enabled state of the item at the specified index. + virtual bool IsEnabledAt(int index) const = 0; + + // Returns the model for the submenu at the specified index. + virtual Menu2Model* GetSubmenuModelAt(int index) const = 0; + + // Retrieves the model and index that contains a specific command id. Returns + // true if an item with the specified command id is found. |model| is inout, + // and specifies the model to start searching from. + static bool GetModelAndIndexForCommandId(int command_id, Menu2Model** model, + int* index); +}; + +// The Menu2Delegate is an interface implemented by an object that performs +// tasks that the Menu2 cannot itself. +class Menu2Delegate { + public: + // Executes the command with the specified identifier. + virtual void ExecuteCommand(Menu2Model* model, int command_id) = 0; +}; + +// A menu. Populated from a model, and relies on a delegate to execute commands. +class Menu2 { + public: + Menu2(Menu2Model* model, Menu2Delegate* delegate); + virtual ~Menu2() {} + + // How the menu is aligned relative to the point it is shown at. + enum Alignment { + ALIGN_TOPLEFT, + ALIGN_TOPRIGHT + }; + + // Runs the menu at the specified point. This may or may not block, depending + // on the platform and type of menu in use. + void RunMenuAt(const gfx::Point& point, Alignment alignment); + + // Called when the model supplying data to this menu has changed, and the menu + // must be rebuilt. + void Rebuild(); + + // Called when the states of the menu items in the menu should be refreshed + // from the model. + void UpdateStates(); + + // For submenus. + gfx::NativeMenu GetNativeMenu() const; + + // Accessors. + Menu2Model* model() const { return model_; } + Menu2Delegate* delegate() const { return delegate_; } + + private: + Menu2Model* model_; + Menu2Delegate* delegate_; + + // The object that actually implements the menu. + MenuWrapper* wrapper_; + + DISALLOW_COPY_AND_ASSIGN(Menu2); +}; + +} // namespace views + +#endif // CONTROLS_MENU_VIEWS_MENU_2_H_ diff --git a/views/controls/menu/native_menu_gtk.cc b/views/controls/menu/native_menu_gtk.cc new file mode 100644 index 0000000..4221e6b --- /dev/null +++ b/views/controls/menu/native_menu_gtk.cc @@ -0,0 +1,233 @@ +// Copyright (c) 2009 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 "views/controls/menu/native_menu_gtk.h" + +#include "base/string_util.h" +#include "base/time.h" +#include "views/accelerator.h" +#include "views/controls/menu/menu_2.h" + +namespace { +// Data passed to the UpdateStateCallback from gtk_container_foreach. +struct UpdateStateData { + // The model to retrieve state from. + views::Menu2Model* model; + // The index within said model. + int index; +}; + +// Data passed to the MenuPositionFunc from gtk_menu_popup +struct Position { + // The point to run the menu at. + gfx::Point point; + // The alignment of the menu at that point. + views::Menu2::Alignment alignment; +}; + +std::string ConvertAcceleratorsFromWindowsStyle(const std::string& label) { + std::string ret; + ret.reserve(label.length()); + for (size_t i = 0; i < label.length(); ++i) { + if ('&' == label[i]) { + if (i + 1 < label.length() && '&' == label[i + 1]) { + ret.push_back(label[i]); + ++i; + } else { + ret.push_back('_'); + } + } else { + ret.push_back(label[i]); + } + } + + return ret; +} + +// Returns true if the menu item type specified can be executed as a command. +bool MenuTypeCanExecute(views::Menu2Model::ItemType type) { + return type == views::Menu2Model::TYPE_COMMAND || + type == views::Menu2Model::TYPE_CHECK || + type == views::Menu2Model::TYPE_RADIO; +} + +} // namespace + +namespace views { + +//////////////////////////////////////////////////////////////////////////////// +// NativeMenuGtk, public: + +NativeMenuGtk::NativeMenuGtk(Menu2Model* model, + Menu2Delegate* delegate) + : model_(model), + delegate_(delegate), + menu_(NULL) { +} + +NativeMenuGtk::~NativeMenuGtk() { + gtk_widget_destroy(menu_); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeMenuGtk, MenuWrapper implementation: + +void NativeMenuGtk::RunMenuAt(const gfx::Point& point, int alignment) { + Position position = { point, static_cast<Menu2::Alignment>(alignment) }; + // TODO(beng): value of '1' will not work for context menus! + gtk_menu_popup(GTK_MENU(menu_), NULL, NULL, MenuPositionFunc, &position, 1, + gtk_get_current_event_time()); +} + +void NativeMenuGtk::Rebuild() { + ResetMenu(); + + GtkRadioMenuItem* last_radio_item = NULL; + for (int i = 0; i < model_->GetItemCount(); ++i) { + Menu2Model::ItemType type = model_->GetTypeAt(i); + if (type == Menu2Model::TYPE_SEPARATOR) + AddSeparatorAt(i); + else + AddMenuItemAt(i, &last_radio_item); + } +} + +void NativeMenuGtk::UpdateStates() { + UpdateStateData data = { model_, 0 }; + gtk_container_foreach(GTK_CONTAINER(menu_), &UpdateStateCallback, &data); +} + +gfx::NativeMenu NativeMenuGtk::GetNativeMenu() const { + return menu_; +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeMenuGtk, private: + +void NativeMenuGtk::AddSeparatorAt(int index) { + GtkWidget* separator = gtk_separator_menu_item_new(); + gtk_widget_show(separator); + gtk_menu_append(menu_, separator); +} + +void NativeMenuGtk::AddMenuItemAt(int index, + GtkRadioMenuItem** last_radio_item) { + GtkWidget* menu_item = NULL; + std::string label = ConvertAcceleratorsFromWindowsStyle(WideToUTF8( + model_->GetLabelAt(index))); + + Menu2Model::ItemType type = model_->GetTypeAt(index); + switch (type) { + case Menu2Model::TYPE_CHECK: + menu_item = gtk_check_menu_item_new_with_mnemonic(label.c_str()); + break; + case Menu2Model::TYPE_RADIO: + if (*last_radio_item) { + menu_item = gtk_radio_menu_item_new_with_mnemonic_from_widget( + *last_radio_item, label.c_str()); + } else { + menu_item = gtk_radio_menu_item_new_with_mnemonic(NULL, label.c_str()); + } + break; + case Menu2Model::TYPE_SUBMENU: + case Menu2Model::TYPE_COMMAND: + menu_item = gtk_menu_item_new_with_mnemonic(label.c_str()); + break; + default: + NOTREACHED(); + break; + } + + // TODO(beng): icons + + if (type == Menu2Model::TYPE_SUBMENU) { + // TODO(beng): we're leaking these objects right now... consider some other + // arrangement. + Menu2* submenu = new Menu2(model_->GetSubmenuModelAt(index), delegate_); + g_object_set_data(G_OBJECT(menu_item), "submenu", submenu); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), + submenu->GetNativeMenu()); + } + + views::Accelerator accelerator(0, false, false, false); + if (model_->GetAcceleratorAt(index, &accelerator)) { + // TODO(beng): accelerators w/gtk_widget_add_accelerator. + } + g_object_set_data(G_OBJECT(menu_item), "position", + reinterpret_cast<void*>(index)); + g_signal_connect(G_OBJECT(menu_item), "activate", G_CALLBACK(CallActivate), + this); + gtk_widget_show(menu_item); + gtk_menu_append(menu_, menu_item); +} + +// static +void NativeMenuGtk::UpdateStateCallback(GtkWidget* menu_item, gpointer data) { + UpdateStateData* usd = reinterpret_cast<UpdateStateData*>(data); + gtk_widget_set_sensitive(menu_item, usd->model->IsEnabledAt(usd->index)); + if (GTK_IS_CHECK_MENU_ITEM(menu_item)) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_item), + usd->model->IsItemCheckedAt(usd->index)); + } + // Recurse into submenus, too. + if (GTK_IS_MENU_ITEM(menu_item)) { + if (gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item))) { + Menu2* submenu = + reinterpret_cast<Menu2*>(g_object_get_data(G_OBJECT(menu_item), + "submenu")); + if (submenu) + submenu->UpdateStates(); + } + } + ++usd->index; +} + +void NativeMenuGtk::ResetMenu() { + if (menu_) + gtk_widget_destroy(menu_); + menu_ = gtk_menu_new(); +} + +// static +void NativeMenuGtk::MenuPositionFunc(GtkMenu* menu, + int* x, + int* y, + gboolean* push_in, + void* data) { + Position* position = reinterpret_cast<Position*>(data); + // TODO(beng): RTL + *x = position->point.x(); + *y = position->point.y(); + if (position->alignment == Menu2::ALIGN_TOPRIGHT) { + GtkRequisition menu_req; + gtk_widget_size_request(GTK_WIDGET(menu), &menu_req); + *x -= menu_req.width; + } + *push_in = FALSE; +} + +void NativeMenuGtk::OnActivate(GtkMenuItem* menu_item) { + int position = reinterpret_cast<int>(g_object_get_data(G_OBJECT(menu_item), + "position")); + if (model_->IsEnabledAt(position) && + MenuTypeCanExecute(model_->GetTypeAt(position))) { + delegate_->ExecuteCommand(model_, model_->GetCommandIdAt(position)); + } +} + +// static +void NativeMenuGtk::CallActivate(GtkMenuItem* menu_item, + NativeMenuGtk* native_menu) { + native_menu->OnActivate(menu_item); +} + +//////////////////////////////////////////////////////////////////////////////// +// MenuWrapper, public: + +// static +MenuWrapper* MenuWrapper::CreateWrapper(Menu2* menu) { + return new NativeMenuGtk(menu->model(), menu->delegate()); +} + +} // namespace views diff --git a/views/controls/menu/native_menu_gtk.h b/views/controls/menu/native_menu_gtk.h new file mode 100644 index 0000000..1c311b5 --- /dev/null +++ b/views/controls/menu/native_menu_gtk.h @@ -0,0 +1,59 @@ +// Copyright (c) 2009 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 VIEWS_CONTROLS_MENU_NATIVE_MENU_GTK_H_ +#define VIEWS_CONTROLS_MENU_NATIVE_MENU_GTK_H_ + +#include <gtk/gtk.h> + +#include "views/controls/menu/menu_wrapper.h" + +namespace views { + +class Menu2Model; +class Menu2Delegate; + +// A Gtk implementation of MenuWrapper. +// TODO(beng): rename to MenuGtk once the old class is dead. +class NativeMenuGtk : public MenuWrapper { + public: + NativeMenuGtk(Menu2Model* model, + Menu2Delegate* delegate); + virtual ~NativeMenuGtk(); + + // Overridden from MenuWrapper: + virtual void RunMenuAt(const gfx::Point& point, int alignment); + virtual void Rebuild(); + virtual void UpdateStates(); + virtual gfx::NativeMenu GetNativeMenu() const; + + private: + void AddSeparatorAt(int index); + void AddMenuItemAt(int index, GtkRadioMenuItem** last_radio_item); + + static void UpdateStateCallback(GtkWidget* menu_item, gpointer data); + + void ResetMenu(); + + // Callback for gtk_menu_popup to position the menu. + static void MenuPositionFunc(GtkMenu* menu, int* x, int* y, gboolean* push_in, + void* data); + + // Event handlers: + void OnActivate(GtkMenuItem* menu_item); + + // Gtk signal handlers. + static void CallActivate(GtkMenuItem* menu_item, NativeMenuGtk* native_menu); + + Menu2Model* model_; + Menu2Delegate* delegate_; + + GtkWidget* menu_; + + DISALLOW_COPY_AND_ASSIGN(NativeMenuGtk); +}; + +} // namespace views + +#endif // VIEWS_CONTROLS_MENU_NATIVE_MENU_GTK_H_ diff --git a/views/controls/menu/native_menu_win.cc b/views/controls/menu/native_menu_win.cc new file mode 100644 index 0000000..fa2a5f7 --- /dev/null +++ b/views/controls/menu/native_menu_win.cc @@ -0,0 +1,346 @@ +// Copyright (c) 2009 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 "views/controls/menu/native_menu_win.h" + +#include "app/l10n_util.h" +#include "app/l10n_util_win.h" +#include "base/logging.h" +#include "base/stl_util-inl.h" +#include "views/accelerator.h" +#include "views/controls/menu/menu_2.h" + +namespace views { + +struct NativeMenuWin::ItemData { + // The Windows API requires that whoever creates the menus must own the + // strings used for labels, and keep them around for the lifetime of the + // created menu. So be it. + std::wstring label; + + // Someone needs to own submenus, it may as well be us. + scoped_ptr<Menu2> submenu; +}; + +// TODO(beng): bring over owner draw from old menu system. +class NativeMenuWin::MenuHostWindow { + public: + MenuHostWindow() { + RegisterClass(); + hwnd_ = CreateWindowEx(l10n_util::GetExtendedStyles(), kWindowClassName, + L"", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL); + SetProp(hwnd_, kMenuHostWindowKey, this); + } + + ~MenuHostWindow() { + DestroyWindow(hwnd_); + } + + HWND hwnd() const { return hwnd_; } + + private: + static const wchar_t* kMenuHostWindowKey; + static const wchar_t* kWindowClassName; + + void RegisterClass() { + static bool registered = false; + if (registered) + return; + + WNDCLASSEX wcex = {0}; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_DBLCLKS; + wcex.lpfnWndProc = &MenuHostWindowProc; + wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW+1); + wcex.lpszClassName = kWindowClassName; + ATOM clazz = RegisterClassEx(&wcex); + DCHECK(clazz); + registered = true; + } + + bool ProcessWindowMessage(HWND window, + UINT message, + WPARAM w_param, + LPARAM l_param, + LRESULT* l_result) { + return false; + } + + static LRESULT CALLBACK MenuHostWindowProc(HWND window, + UINT message, + WPARAM w_param, + LPARAM l_param) { + MenuHostWindow* host = + reinterpret_cast<MenuHostWindow*>(GetProp(window, kMenuHostWindowKey)); + LRESULT l_result = 0; + if (!host || !host->ProcessWindowMessage(window, message, w_param, l_param, + &l_result)) { + return DefWindowProc(window, message, w_param, l_param); + } + return l_result; + } + + HWND hwnd_; + + DISALLOW_COPY_AND_ASSIGN(MenuHostWindow); +}; + +// static +const wchar_t* NativeMenuWin::MenuHostWindow::kWindowClassName = + L"ViewsMenuHostWindow"; + +const wchar_t* NativeMenuWin::MenuHostWindow::kMenuHostWindowKey = + L"__MENU_HOST_WINDOW__"; + + +//////////////////////////////////////////////////////////////////////////////// +// NativeMenuWin, public: + +NativeMenuWin::NativeMenuWin(Menu2Model* model, + Menu2Delegate* delegate, + HWND system_menu_for) + : model_(model), + delegate_(delegate), + menu_(NULL), + owner_draw_(false), + system_menu_for_(system_menu_for), + first_item_index_(0) { +} + +NativeMenuWin::~NativeMenuWin() { + STLDeleteContainerPointers(items_.begin(), items_.end()); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeMenuWin, MenuWrapper implementation: + +void NativeMenuWin::RunMenuAt(const gfx::Point& point, int alignment) { + CreateHostWindow(); + UpdateStates(); + UINT flags = TPM_LEFTBUTTON | TPM_RETURNCMD | TPM_RECURSE; + flags |= GetAlignmentFlags(alignment); + UINT selected_command_id = TrackPopupMenuEx(menu_, flags, point.x(), + point.y(), host_window_->hwnd(), + NULL); + if (selected_command_id > 0) { + // Locate the correct delegate and model to notify about the selection. + // See comment in GetMenuForCommandId for details. + NativeMenuWin* menu = GetMenuForCommandId(selected_command_id); + menu->delegate_->ExecuteCommand(menu->model_, selected_command_id); + } +} + +void NativeMenuWin::Rebuild() { + ResetNativeMenu(); + owner_draw_ = model_->HasIcons(); + first_item_index_ = model_->GetFirstItemIndex(GetNativeMenu()); + for (int menu_index = first_item_index_; + menu_index < first_item_index_ + model_->GetItemCount(); ++menu_index) { + int model_index = menu_index - first_item_index_; + if (model_->GetTypeAt(model_index) == Menu2Model::TYPE_SEPARATOR) + AddSeparatorItemAt(menu_index, model_index); + else + AddMenuItemAt(menu_index, model_index); + } +} + +void NativeMenuWin::UpdateStates() { + // A depth-first walk of the menu items, updating states. + for (int menu_index = first_item_index_; + menu_index < first_item_index_ + model_->GetItemCount(); ++menu_index) { + int model_index = menu_index - first_item_index_; + SetMenuItemState(menu_index, model_->IsEnabledAt(model_index), + model_->IsItemCheckedAt(model_index), false); + if (model_->IsLabelDynamicAt(model_index)) { + SetMenuItemLabel(menu_index, model_index, + model_->GetLabelAt(model_index)); + } + Menu2* submenu = items_.at(model_index)->submenu.get(); + if (submenu) + submenu->UpdateStates(); + } +} + +gfx::NativeMenu NativeMenuWin::GetNativeMenu() const { + return menu_; +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeMenuWin, private: + +bool NativeMenuWin::IsSeparatorItemAt(int menu_index) const { + MENUITEMINFO mii = {0}; + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_FTYPE; + GetMenuItemInfo(menu_, menu_index, MF_BYPOSITION, &mii); + return !!(mii.fType & MF_SEPARATOR); +} + +void NativeMenuWin::AddMenuItemAt(int menu_index, int model_index) { + MENUITEMINFO mii = {0}; + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_FTYPE | MIIM_ID | MIIM_DATA; + if (!owner_draw_) + mii.fType = MFT_STRING; + else + mii.fType = MFT_OWNERDRAW; + mii.dwItemData = reinterpret_cast<ULONG_PTR>(this); + + ItemData* item_data = new ItemData; + Menu2Model::ItemType type = model_->GetTypeAt(model_index); + if (type == Menu2Model::TYPE_SUBMENU) { + item_data->submenu.reset(new Menu2(model_->GetSubmenuModelAt(model_index), + delegate_)); + mii.fMask |= MIIM_SUBMENU; + mii.hSubMenu = item_data->submenu->GetNativeMenu(); + } else { + if (type == Menu2Model::TYPE_RADIO) + mii.fType |= MFT_RADIOCHECK; + mii.wID = model_->GetCommandIdAt(model_index); + } + items_.insert(items_.begin() + model_index, item_data); + UpdateMenuItemInfoForString(&mii, model_index, + model_->GetLabelAt(model_index)); + InsertMenuItem(menu_, menu_index, TRUE, &mii); +} + +void NativeMenuWin::AddSeparatorItemAt(int menu_index, int model_index) { + MENUITEMINFO mii = {0}; + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_FTYPE; + mii.fType = MFT_SEPARATOR; + // Insert a dummy entry into our label list so we can index directly into it + // using item indices if need be. + items_.insert(items_.begin() + model_index, new ItemData); + InsertMenuItem(menu_, menu_index, TRUE, &mii); +} + +void NativeMenuWin::SetMenuItemState(int menu_index, bool enabled, bool checked, + bool is_default) { + if (IsSeparatorItemAt(menu_index)) + return; + + UINT state = enabled ? MFS_ENABLED : MFS_DISABLED; + if (checked) + state |= MFS_CHECKED; + if (is_default) + state |= MFS_DEFAULT; + + MENUITEMINFO mii = {0}; + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_STATE; + mii.fState = state; + SetMenuItemInfo(menu_, menu_index, MF_BYPOSITION, &mii); +} + +void NativeMenuWin::SetMenuItemLabel(int menu_index, + int model_index, + const std::wstring& label) { + if (IsSeparatorItemAt(menu_index)) + return; + + MENUITEMINFO mii = {0}; + mii.cbSize = sizeof(mii); + UpdateMenuItemInfoForString(&mii, model_index, label); + if (!owner_draw_) + SetMenuItemInfo(menu_, menu_index, MF_BYPOSITION, &mii); +} + +void NativeMenuWin::UpdateMenuItemInfoForString( + MENUITEMINFO* mii, + int model_index, + const std::wstring& label) { + std::wstring formatted = label; + Menu2Model::ItemType type = model_->GetTypeAt(model_index); + if (type != Menu2Model::TYPE_SUBMENU) { + // Add accelerator details to the label if provided. + views::Accelerator accelerator(0, false, false, false); + if (model_->GetAcceleratorAt(model_index, &accelerator)) { + formatted += L"\t"; + formatted += accelerator.GetShortcutText(); + } + } + + // Update the owned string, since Windows will want us to keep this new + // version around. + items_[model_index]->label = formatted; + + // Windows only requires a pointer to the label string if it's going to be + // doing the drawing. + if (!owner_draw_) { + mii->fMask |= MIIM_STRING; + mii->dwTypeData = + const_cast<wchar_t*>(items_.at(model_index)->label.c_str()); + } +} + +NativeMenuWin* NativeMenuWin::GetMenuForCommandId(UINT command_id) const { + // Menus can have nested submenus. In the views Menu system, each submenu is + // wrapped in a NativeMenu instance, which may have a different model and + // delegate from the parent menu. The trouble is, RunMenuAt is called on the + // parent NativeMenuWin, and so it's not possible to assume that we can just + // dispatch the command id returned by TrackPopupMenuEx to the parent's + // delegate. For this reason, we stow a pointer on every menu item we create + // to the NativeMenuWin that most closely contains it. Fortunately, Windows + // provides GetMenuItemInfo, which can walk down the menu item tree from + // the root |menu_| to find the data for a given item even if it's in a + // submenu. + MENUITEMINFO mii = {0}; + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_DATA; + GetMenuItemInfo(menu_, command_id, FALSE, &mii); + return reinterpret_cast<NativeMenuWin*>(mii.dwItemData); +} + +UINT NativeMenuWin::GetAlignmentFlags(int alignment) const { + bool rtl = l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT; + UINT alignment_flags = TPM_TOPALIGN; + if (alignment == Menu2::ALIGN_TOPLEFT) + alignment_flags |= TPM_LEFTALIGN; + else if (alignment == Menu2::ALIGN_TOPRIGHT) + alignment_flags |= TPM_RIGHTALIGN; + return alignment_flags; +} + +void NativeMenuWin::ResetNativeMenu() { + if (IsWindow(system_menu_for_)) { + if (menu_) + GetSystemMenu(system_menu_for_, TRUE); + menu_ = GetSystemMenu(system_menu_for_, FALSE); + } else { + if (menu_) + DestroyMenu(menu_); + menu_ = CreatePopupMenu(); + } +} + +void NativeMenuWin::CreateHostWindow() { + if (!host_window_.get()) + host_window_.reset(new MenuHostWindow()); +} + +//////////////////////////////////////////////////////////////////////////////// +// SystemMenuModel: + +SystemMenuModel::SystemMenuModel(SimpleMenuModel::Delegate* delegate) + : SimpleMenuModel(delegate) { +} + +SystemMenuModel::~SystemMenuModel() { +} + +int SystemMenuModel::GetFirstItemIndex(gfx::NativeMenu native_menu) const { + // We allow insertions before last item (Close). + return std::max(0, GetMenuItemCount(native_menu) - 1); +} + +//////////////////////////////////////////////////////////////////////////////// +// MenuWrapper, public: + +// static +MenuWrapper* MenuWrapper::CreateWrapper(Menu2* menu) { + return new NativeMenuWin(menu->model(), menu->delegate(), NULL); +} + +} // namespace views diff --git a/views/controls/menu/native_menu_win.h b/views/controls/menu/native_menu_win.h new file mode 100644 index 0000000..df0aa63 --- /dev/null +++ b/views/controls/menu/native_menu_win.h @@ -0,0 +1,140 @@ +// Copyright (c) 2009 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 VIEWS_CONTROLS_MENU_NATIVE_MENU_WIN_H_ +#define VIEWS_CONTROLS_MENU_NATIVE_MENU_WIN_H_ + +#include <vector> + +#include "base/scoped_ptr.h" +#include "views/controls/menu/menu_wrapper.h" +#include "views/controls/menu/simple_menu_model.h" + +namespace views { + +class Menu2Delegate; +class Menu2Model; + +// A Windows implementation of MenuWrapper. +// TODO(beng): rename to MenuWin once the old class is dead. +class NativeMenuWin : public MenuWrapper { + public: + // Construct a NativeMenuWin, with a model and delegate. If |system_menu_for| + // is non-NULL, the NativeMenuWin wraps the system menu for that window. + // The caller owns the model and the delegate. + NativeMenuWin(Menu2Model* model, + Menu2Delegate* delegate, + HWND system_menu_for); + virtual ~NativeMenuWin(); + + // Overridden from MenuWrapper: + virtual void RunMenuAt(const gfx::Point& point, int alignment); + virtual void Rebuild(); + virtual void UpdateStates(); + virtual gfx::NativeMenu GetNativeMenu() const; + + private: + // IMPORTANT: Note about indices. + // Functions in this class deal in two index spaces: + // 1. menu_index - the index of an item within the actual Windows + // native menu. + // 2. model_index - the index of the item within our model. + // These two are most often but not always the same value! The + // notable exception is when this object is used to wrap the + // Windows System Menu. In this instance, the model indices start + // at 0, but the insertion index into the existing menu is not. + // It is important to take this into consideration when editing the + // code in the functions in this class. + + // Returns true if the item at the specified index is a separator. + bool IsSeparatorItemAt(int menu_index) const; + + // Add items. See note above about indices. + void AddMenuItemAt(int menu_index, int model_index); + void AddSeparatorItemAt(int menu_index, int model_index); + + // Sets the state of the item at the specified index. + void SetMenuItemState(int menu_index, + bool enabled, + bool checked, + bool is_default); + + // Sets the label of the item at the specified index. + void SetMenuItemLabel(int menu_index, + int model_index, + const std::wstring& label); + + // Updates the local data structure with the correctly formatted version of + // |label| at the specified model_index, and adds string data to |mii| if + // the menu is not owner-draw. That's a mouthful. This function exists because + // of the peculiarities of the Windows menu API. + void UpdateMenuItemInfoForString(MENUITEMINFO* mii, + int model_index, + const std::wstring& label); + + // Returns the NativeMenuWin object that contains an item with the specified + // command id. This function must only be called from RunMenuAt! + NativeMenuWin* GetMenuForCommandId(UINT command_id) const; + + // Returns the alignment flags to be passed to TrackPopupMenuEx, based on the + // supplied alignment and the UI text direction. + UINT GetAlignmentFlags(int alignment) const; + + // Resets the native menu stored in |menu_| by destroying any old menu then + // creating a new empty one. + void ResetNativeMenu(); + + // Creates the host window that receives notifications from the menu. + void CreateHostWindow(); + + // Our attached model and delegate. + Menu2Model* model_; + Menu2Delegate* delegate_; + + HMENU menu_; + + // True if the contents of menu items in this menu are drawn by the menu host + // window, rather than Windows. + bool owner_draw_; + + // An object that collects all of the data associated with an individual menu + // item. + struct ItemData; + std::vector<ItemData*> items_; + + // The window that receives notifications from the menu. + class MenuHostWindow; + scoped_ptr<MenuHostWindow> host_window_; + + // The HWND this menu is the system menu for, or NULL if the menu is not a + // system menu. + HWND system_menu_for_; + + // The index of the first item in the model in the menu. + int first_item_index_; + + DISALLOW_COPY_AND_ASSIGN(NativeMenuWin); +}; + +// A SimpleMenuModel subclass that allows the system menu for a window to be +// wrapped. +class SystemMenuModel : public SimpleMenuModel { + public: + explicit SystemMenuModel(Delegate* delegate); + virtual ~SystemMenuModel(); + + // Overridden from Menu2Model: + virtual int GetFirstItemIndex(gfx::NativeMenu native_menu) const; + + protected: + // Overridden from SimpleMenuModel: + virtual int FlipIndex(int index) const { return GetItemCount() - index - 1; } + + private: + DISALLOW_COPY_AND_ASSIGN(SystemMenuModel); +}; + +} // namespace views + +#endif // VIEWS_CONTROLS_MENU_NATIVE_MENU_WIN_H_ diff --git a/views/controls/menu/simple_menu_model.cc b/views/controls/menu/simple_menu_model.cc new file mode 100644 index 0000000..243b144 --- /dev/null +++ b/views/controls/menu/simple_menu_model.cc @@ -0,0 +1,129 @@ +// Copyright (c) 2009 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 "views/controls/menu/simple_menu_model.h" + +#include "app/l10n_util.h" + +namespace views { + +//////////////////////////////////////////////////////////////////////////////// +// SimpleMenuModel, public: + +SimpleMenuModel::SimpleMenuModel(Delegate* delegate) : delegate_(delegate) { +} + +SimpleMenuModel::~SimpleMenuModel() { +} + +void SimpleMenuModel::AddItem(int command_id, const std::wstring& label) { + Item item = { command_id, label, TYPE_COMMAND, -1, NULL }; + items_.push_back(item); +} + +void SimpleMenuModel::AddItemWithStringId(int command_id, int string_id) { + AddItem(command_id, l10n_util::GetString(string_id)); +} + +void SimpleMenuModel::AddSeparator() { + Item item = { -1, std::wstring(), TYPE_SEPARATOR, -1, NULL }; + items_.push_back(item); +} + +void SimpleMenuModel::AddCheckItem(int command_id, const std::wstring& label) { + Item item = { command_id, label, TYPE_CHECK, -1, NULL }; + items_.push_back(item); +} + +void SimpleMenuModel::AddCheckItemWithStringId(int command_id, int string_id) { + AddCheckItem(command_id, l10n_util::GetString(string_id)); +} + +void SimpleMenuModel::AddRadioItem(int command_id, const std::wstring& label, + int group_id) { + Item item = { command_id, label, TYPE_RADIO, group_id, NULL }; + items_.push_back(item); +} + +void SimpleMenuModel::AddRadioItemWithStringId(int command_id, int string_id, + int group_id) { + AddRadioItem(command_id, l10n_util::GetString(string_id), group_id); +} + +void SimpleMenuModel::AddSubMenu(const std::wstring& label, Menu2Model* model) { + Item item = { -1, label, TYPE_SUBMENU, -1, model }; + items_.push_back(item); +} + +void SimpleMenuModel::AddSubMenuWithStringId(int string_id, Menu2Model* model) { + AddSubMenu(l10n_util::GetString(string_id), model); +} + +//////////////////////////////////////////////////////////////////////////////// +// SimpleMenuModel, Menu2Model implementation: + +bool SimpleMenuModel::HasIcons() const { + return false; +} + +int SimpleMenuModel::GetItemCount() const { + return static_cast<int>(items_.size()); +} + +Menu2Model::ItemType SimpleMenuModel::GetTypeAt(int index) const { + return items_.at(FlipIndex(index)).type; +} + +int SimpleMenuModel::GetCommandIdAt(int index) const { + return items_.at(FlipIndex(index)).command_id; +} + +std::wstring SimpleMenuModel::GetLabelAt(int index) const { + if (IsLabelDynamicAt(index)) + return delegate_->GetLabelForCommandId(GetCommandIdAt(index)); + return items_.at(FlipIndex(index)).label; +} + +bool SimpleMenuModel::IsLabelDynamicAt(int index) const { + if (delegate_) + return delegate_->IsLabelForCommandIdDynamic(GetCommandIdAt(index)); + return false; +} + +bool SimpleMenuModel::GetAcceleratorAt(int index, + views::Accelerator* accelerator) const { + if (delegate_) { + return delegate_->GetAcceleratorForCommandId(GetCommandIdAt(index), + accelerator); + } + return false; +} + +bool SimpleMenuModel::IsItemCheckedAt(int index) const { + if (!delegate_) + return false; + return delegate_->IsCommandIdChecked(GetCommandIdAt(index)); +} + +int SimpleMenuModel::GetGroupIdAt(int index) const { + return items_.at(FlipIndex(index)).group_id; +} + +bool SimpleMenuModel::GetIconAt(int index, SkBitmap* icon) const { + return false; +} + +bool SimpleMenuModel::IsEnabledAt(int index) const { + int command_id = GetCommandIdAt(index); + // Submenus have a command id of -1, they should always be enabled. + if (!delegate_ || command_id == -1) + return true; + return delegate_->IsCommandIdEnabled(command_id); +} + +Menu2Model* SimpleMenuModel::GetSubmenuModelAt(int index) const { + return items_.at(FlipIndex(index)).submenu; +} + +} // namespace views diff --git a/views/controls/menu/simple_menu_model.h b/views/controls/menu/simple_menu_model.h new file mode 100644 index 0000000..dbab73f --- /dev/null +++ b/views/controls/menu/simple_menu_model.h @@ -0,0 +1,93 @@ +// Copyright (c) 2009 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 CONTROLS_MENU_VIEWS_SIMPLE_MENU_MODEL_H_ +#define CONTROLS_MENU_VIEWS_SIMPLE_MENU_MODEL_H_ + +#include <vector> + +#include "views/controls/menu/menu_2.h" + +namespace views { + +// A simple Menu2Model implementation with an imperative API for adding menu +// items. This makes it easy to construct fixed menus. Menus populated by +// dynamic data sources may be better off implementing Menu2Model directly. +// The breadth of Menu2Model is not exposed through this API. +class SimpleMenuModel : public Menu2Model { + public: + class Delegate { + public: + // Methods for determining the state of specific command ids. + virtual bool IsCommandIdChecked(int command_id) const = 0; + virtual bool IsCommandIdEnabled(int command_id) const = 0; + + // Gets the accelerator for the specified command id. Returns true if the + // command id has a valid accelerator, false otherwise. + virtual bool GetAcceleratorForCommandId( + int command_id, + views::Accelerator* accelerator) = 0; + + // Some command ids have labels that change over time. + virtual bool IsLabelForCommandIdDynamic(int command_id) const = 0; + virtual std::wstring GetLabelForCommandId(int command_id) const = 0; + }; + + // The Delegate can be NULL, though if it is items can't be checked or + // disabled. + explicit SimpleMenuModel(Delegate* delegate); + virtual ~SimpleMenuModel(); + + // Methods for adding items to the model. + void AddItem(int command_id, const std::wstring& label); + void AddItemWithStringId(int command_id, int string_id); + void AddSeparator(); + void AddCheckItem(int command_id, const std::wstring& label); + void AddCheckItemWithStringId(int command_id, int string_id); + void AddRadioItem(int command_id, const std::wstring& label, int group_id); + void AddRadioItemWithStringId(int command_id, int string_id, int group_id); + void AddSubMenu(const std::wstring& label, Menu2Model* model); + void AddSubMenuWithStringId(int string_id, Menu2Model* model); + + // Overridden from Menu2Model: + virtual bool HasIcons() const; + virtual int GetItemCount() const; + virtual ItemType GetTypeAt(int index) const; + virtual int GetCommandIdAt(int index) const; + virtual std::wstring GetLabelAt(int index) const; + virtual bool IsLabelDynamicAt(int index) const; + virtual bool GetAcceleratorAt(int index, + views::Accelerator* accelerator) const; + virtual bool IsItemCheckedAt(int index) const; + virtual int GetGroupIdAt(int index) const; + virtual bool GetIconAt(int index, SkBitmap* icon) const; + virtual bool IsEnabledAt(int index) const; + virtual Menu2Model* GetSubmenuModelAt(int index) const; + + protected: + // Some variants of this model (SystemMenuModel) relies on items to be + // inserted backwards. This is counter-intuitive for the API, so rather than + // forcing customers to insert things backwards, we return the indices + // backwards instead. That's what this method is for. By default, it just + // returns what it's passed. + virtual int FlipIndex(int index) const { return index; } + + private: + struct Item { + int command_id; + std::wstring label; + ItemType type; + int group_id; + Menu2Model* submenu; + }; + std::vector<Item> items_; + + Delegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(SimpleMenuModel); +}; + +} // namespace views + +#endif // CONTROLS_MENU_VIEWS_SIMPLE_MENU_MODEL_H_ diff --git a/views/view.cc b/views/view.cc index 985064cd..0280aef 100644 --- a/views/view.cc +++ b/views/view.cc @@ -943,7 +943,7 @@ void View::RemoveAccelerator(const Accelerator& accelerator) { return; } - int index = iter - accelerators_->begin(); + size_t index = iter - accelerators_->begin(); accelerators_->erase(iter); if (index >= registered_accelerator_count_) { // The accelerator is not registered to FocusManager. diff --git a/views/view.h b/views/view.h index 967ce22..8075378 100644 --- a/views/view.h +++ b/views/view.h @@ -1180,7 +1180,7 @@ class View : public AcceleratorTarget { // [0, registered_accelerator_count_) are already registered to FocusManager, // and the rest are not yet. scoped_ptr<std::vector<Accelerator> > accelerators_; - int registered_accelerator_count_; + size_t registered_accelerator_count_; // The menu controller. ContextMenuController* context_menu_controller_; diff --git a/views/views.gyp b/views/views.gyp index 97caf78..c0e63c8 100644 --- a/views/views.gyp +++ b/views/views.gyp @@ -103,10 +103,19 @@ 'controls/menu/controller.h', 'controls/menu/menu.cc', 'controls/menu/menu.h', + 'controls/menu/menu_2.cc', + 'controls/menu/menu_2.h', 'controls/menu/menu_gtk.cc', 'controls/menu/menu_gtk.h', 'controls/menu/menu_win.cc', 'controls/menu/menu_win.h', + 'controls/menu/menu_wrapper.h', + 'controls/menu/native_menu_gtk.cc', + 'controls/menu/native_menu_gtk.h', + 'controls/menu/native_menu_win.cc', + 'controls/menu/native_menu_win.h', + 'controls/menu/simple_menu_model.cc', + 'controls/menu/simple_menu_model.h', 'controls/menu/view_menu_delegate.h', 'controls/message_box_view.cc', 'controls/message_box_view.h', |