Index: views/controls/menu/native_menu_win.cc |
=================================================================== |
--- views/controls/menu/native_menu_win.cc (revision 0) |
+++ views/controls/menu/native_menu_win.cc (revision 0) |
@@ -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 |