OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this |
| 2 // source code is governed by a BSD-style license that can be found in the |
| 3 // LICENSE file. |
| 4 |
| 5 #include "views/controls/menu/native_menu_win.h" |
| 6 |
| 7 #include "app/l10n_util.h" |
| 8 #include "app/l10n_util_win.h" |
| 9 #include "base/logging.h" |
| 10 #include "base/stl_util-inl.h" |
| 11 #include "views/accelerator.h" |
| 12 #include "views/controls/menu/menu_2.h" |
| 13 |
| 14 namespace views { |
| 15 |
| 16 struct NativeMenuWin::ItemData { |
| 17 // The Windows API requires that whoever creates the menus must own the |
| 18 // strings used for labels, and keep them around for the lifetime of the |
| 19 // created menu. So be it. |
| 20 std::wstring label; |
| 21 |
| 22 // Someone needs to own submenus, it may as well be us. |
| 23 scoped_ptr<Menu2> submenu; |
| 24 }; |
| 25 |
| 26 // TODO(beng): bring over owner draw from old menu system. |
| 27 class NativeMenuWin::MenuHostWindow { |
| 28 public: |
| 29 MenuHostWindow() { |
| 30 RegisterClass(); |
| 31 hwnd_ = CreateWindowEx(l10n_util::GetExtendedStyles(), kWindowClassName, |
| 32 L"", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL); |
| 33 SetProp(hwnd_, kMenuHostWindowKey, this); |
| 34 } |
| 35 |
| 36 ~MenuHostWindow() { |
| 37 DestroyWindow(hwnd_); |
| 38 } |
| 39 |
| 40 HWND hwnd() const { return hwnd_; } |
| 41 |
| 42 private: |
| 43 static const wchar_t* kMenuHostWindowKey; |
| 44 static const wchar_t* kWindowClassName; |
| 45 |
| 46 void RegisterClass() { |
| 47 static bool registered = false; |
| 48 if (registered) |
| 49 return; |
| 50 |
| 51 WNDCLASSEX wcex = {0}; |
| 52 wcex.cbSize = sizeof(WNDCLASSEX); |
| 53 wcex.style = CS_DBLCLKS; |
| 54 wcex.lpfnWndProc = &MenuHostWindowProc; |
| 55 wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW+1); |
| 56 wcex.lpszClassName = kWindowClassName; |
| 57 ATOM clazz = RegisterClassEx(&wcex); |
| 58 DCHECK(clazz); |
| 59 registered = true; |
| 60 } |
| 61 |
| 62 bool ProcessWindowMessage(HWND window, |
| 63 UINT message, |
| 64 WPARAM w_param, |
| 65 LPARAM l_param, |
| 66 LRESULT* l_result) { |
| 67 return false; |
| 68 } |
| 69 |
| 70 static LRESULT CALLBACK MenuHostWindowProc(HWND window, |
| 71 UINT message, |
| 72 WPARAM w_param, |
| 73 LPARAM l_param) { |
| 74 MenuHostWindow* host = |
| 75 reinterpret_cast<MenuHostWindow*>(GetProp(window, kMenuHostWindowKey)); |
| 76 LRESULT l_result = 0; |
| 77 if (!host || !host->ProcessWindowMessage(window, message, w_param, l_param, |
| 78 &l_result)) { |
| 79 return DefWindowProc(window, message, w_param, l_param); |
| 80 } |
| 81 return l_result; |
| 82 } |
| 83 |
| 84 HWND hwnd_; |
| 85 |
| 86 DISALLOW_COPY_AND_ASSIGN(MenuHostWindow); |
| 87 }; |
| 88 |
| 89 // static |
| 90 const wchar_t* NativeMenuWin::MenuHostWindow::kWindowClassName = |
| 91 L"ViewsMenuHostWindow"; |
| 92 |
| 93 const wchar_t* NativeMenuWin::MenuHostWindow::kMenuHostWindowKey = |
| 94 L"__MENU_HOST_WINDOW__"; |
| 95 |
| 96 |
| 97 //////////////////////////////////////////////////////////////////////////////// |
| 98 // NativeMenuWin, public: |
| 99 |
| 100 NativeMenuWin::NativeMenuWin(Menu2Model* model, |
| 101 Menu2Delegate* delegate, |
| 102 HWND system_menu_for) |
| 103 : model_(model), |
| 104 delegate_(delegate), |
| 105 menu_(NULL), |
| 106 owner_draw_(false), |
| 107 system_menu_for_(system_menu_for), |
| 108 first_item_index_(0) { |
| 109 } |
| 110 |
| 111 NativeMenuWin::~NativeMenuWin() { |
| 112 STLDeleteContainerPointers(items_.begin(), items_.end()); |
| 113 } |
| 114 |
| 115 //////////////////////////////////////////////////////////////////////////////// |
| 116 // NativeMenuWin, MenuWrapper implementation: |
| 117 |
| 118 void NativeMenuWin::RunMenuAt(const gfx::Point& point, int alignment) { |
| 119 CreateHostWindow(); |
| 120 UpdateStates(); |
| 121 UINT flags = TPM_LEFTBUTTON | TPM_RETURNCMD | TPM_RECURSE; |
| 122 flags |= GetAlignmentFlags(alignment); |
| 123 UINT selected_command_id = TrackPopupMenuEx(menu_, flags, point.x(), |
| 124 point.y(), host_window_->hwnd(), |
| 125 NULL); |
| 126 if (selected_command_id > 0) { |
| 127 // Locate the correct delegate and model to notify about the selection. |
| 128 // See comment in GetMenuForCommandId for details. |
| 129 NativeMenuWin* menu = GetMenuForCommandId(selected_command_id); |
| 130 menu->delegate_->ExecuteCommand(menu->model_, selected_command_id); |
| 131 } |
| 132 } |
| 133 |
| 134 void NativeMenuWin::Rebuild() { |
| 135 ResetNativeMenu(); |
| 136 owner_draw_ = model_->HasIcons(); |
| 137 first_item_index_ = model_->GetFirstItemIndex(GetNativeMenu()); |
| 138 for (int menu_index = first_item_index_; |
| 139 menu_index < first_item_index_ + model_->GetItemCount(); ++menu_index) { |
| 140 int model_index = menu_index - first_item_index_; |
| 141 if (model_->GetTypeAt(model_index) == Menu2Model::TYPE_SEPARATOR) |
| 142 AddSeparatorItemAt(menu_index, model_index); |
| 143 else |
| 144 AddMenuItemAt(menu_index, model_index); |
| 145 } |
| 146 } |
| 147 |
| 148 void NativeMenuWin::UpdateStates() { |
| 149 // A depth-first walk of the menu items, updating states. |
| 150 for (int menu_index = first_item_index_; |
| 151 menu_index < first_item_index_ + model_->GetItemCount(); ++menu_index) { |
| 152 int model_index = menu_index - first_item_index_; |
| 153 SetMenuItemState(menu_index, model_->IsEnabledAt(model_index), |
| 154 model_->IsItemCheckedAt(model_index), false); |
| 155 if (model_->IsLabelDynamicAt(model_index)) { |
| 156 SetMenuItemLabel(menu_index, model_index, |
| 157 model_->GetLabelAt(model_index)); |
| 158 } |
| 159 Menu2* submenu = items_.at(model_index)->submenu.get(); |
| 160 if (submenu) |
| 161 submenu->UpdateStates(); |
| 162 } |
| 163 } |
| 164 |
| 165 gfx::NativeMenu NativeMenuWin::GetNativeMenu() const { |
| 166 return menu_; |
| 167 } |
| 168 |
| 169 //////////////////////////////////////////////////////////////////////////////// |
| 170 // NativeMenuWin, private: |
| 171 |
| 172 bool NativeMenuWin::IsSeparatorItemAt(int menu_index) const { |
| 173 MENUITEMINFO mii = {0}; |
| 174 mii.cbSize = sizeof(mii); |
| 175 mii.fMask = MIIM_FTYPE; |
| 176 GetMenuItemInfo(menu_, menu_index, MF_BYPOSITION, &mii); |
| 177 return !!(mii.fType & MF_SEPARATOR); |
| 178 } |
| 179 |
| 180 void NativeMenuWin::AddMenuItemAt(int menu_index, int model_index) { |
| 181 MENUITEMINFO mii = {0}; |
| 182 mii.cbSize = sizeof(mii); |
| 183 mii.fMask = MIIM_FTYPE | MIIM_ID | MIIM_DATA; |
| 184 if (!owner_draw_) |
| 185 mii.fType = MFT_STRING; |
| 186 else |
| 187 mii.fType = MFT_OWNERDRAW; |
| 188 mii.dwItemData = reinterpret_cast<ULONG_PTR>(this); |
| 189 |
| 190 ItemData* item_data = new ItemData; |
| 191 Menu2Model::ItemType type = model_->GetTypeAt(model_index); |
| 192 if (type == Menu2Model::TYPE_SUBMENU) { |
| 193 item_data->submenu.reset(new Menu2(model_->GetSubmenuModelAt(model_index), |
| 194 delegate_)); |
| 195 mii.fMask |= MIIM_SUBMENU; |
| 196 mii.hSubMenu = item_data->submenu->GetNativeMenu(); |
| 197 } else { |
| 198 if (type == Menu2Model::TYPE_RADIO) |
| 199 mii.fType |= MFT_RADIOCHECK; |
| 200 mii.wID = model_->GetCommandIdAt(model_index); |
| 201 } |
| 202 items_.insert(items_.begin() + model_index, item_data); |
| 203 UpdateMenuItemInfoForString(&mii, model_index, |
| 204 model_->GetLabelAt(model_index)); |
| 205 InsertMenuItem(menu_, menu_index, TRUE, &mii); |
| 206 } |
| 207 |
| 208 void NativeMenuWin::AddSeparatorItemAt(int menu_index, int model_index) { |
| 209 MENUITEMINFO mii = {0}; |
| 210 mii.cbSize = sizeof(mii); |
| 211 mii.fMask = MIIM_FTYPE; |
| 212 mii.fType = MFT_SEPARATOR; |
| 213 // Insert a dummy entry into our label list so we can index directly into it |
| 214 // using item indices if need be. |
| 215 items_.insert(items_.begin() + model_index, new ItemData); |
| 216 InsertMenuItem(menu_, menu_index, TRUE, &mii); |
| 217 } |
| 218 |
| 219 void NativeMenuWin::SetMenuItemState(int menu_index, bool enabled, bool checked, |
| 220 bool is_default) { |
| 221 if (IsSeparatorItemAt(menu_index)) |
| 222 return; |
| 223 |
| 224 UINT state = enabled ? MFS_ENABLED : MFS_DISABLED; |
| 225 if (checked) |
| 226 state |= MFS_CHECKED; |
| 227 if (is_default) |
| 228 state |= MFS_DEFAULT; |
| 229 |
| 230 MENUITEMINFO mii = {0}; |
| 231 mii.cbSize = sizeof(mii); |
| 232 mii.fMask = MIIM_STATE; |
| 233 mii.fState = state; |
| 234 SetMenuItemInfo(menu_, menu_index, MF_BYPOSITION, &mii); |
| 235 } |
| 236 |
| 237 void NativeMenuWin::SetMenuItemLabel(int menu_index, |
| 238 int model_index, |
| 239 const std::wstring& label) { |
| 240 if (IsSeparatorItemAt(menu_index)) |
| 241 return; |
| 242 |
| 243 MENUITEMINFO mii = {0}; |
| 244 mii.cbSize = sizeof(mii); |
| 245 UpdateMenuItemInfoForString(&mii, model_index, label); |
| 246 if (!owner_draw_) |
| 247 SetMenuItemInfo(menu_, menu_index, MF_BYPOSITION, &mii); |
| 248 } |
| 249 |
| 250 void NativeMenuWin::UpdateMenuItemInfoForString( |
| 251 MENUITEMINFO* mii, |
| 252 int model_index, |
| 253 const std::wstring& label) { |
| 254 std::wstring formatted = label; |
| 255 Menu2Model::ItemType type = model_->GetTypeAt(model_index); |
| 256 if (type != Menu2Model::TYPE_SUBMENU) { |
| 257 // Add accelerator details to the label if provided. |
| 258 views::Accelerator accelerator(0, false, false, false); |
| 259 if (model_->GetAcceleratorAt(model_index, &accelerator)) { |
| 260 formatted += L"\t"; |
| 261 formatted += accelerator.GetShortcutText(); |
| 262 } |
| 263 } |
| 264 |
| 265 // Update the owned string, since Windows will want us to keep this new |
| 266 // version around. |
| 267 items_[model_index]->label = formatted; |
| 268 |
| 269 // Windows only requires a pointer to the label string if it's going to be |
| 270 // doing the drawing. |
| 271 if (!owner_draw_) { |
| 272 mii->fMask |= MIIM_STRING; |
| 273 mii->dwTypeData = |
| 274 const_cast<wchar_t*>(items_.at(model_index)->label.c_str()); |
| 275 } |
| 276 } |
| 277 |
| 278 NativeMenuWin* NativeMenuWin::GetMenuForCommandId(UINT command_id) const { |
| 279 // Menus can have nested submenus. In the views Menu system, each submenu is |
| 280 // wrapped in a NativeMenu instance, which may have a different model and |
| 281 // delegate from the parent menu. The trouble is, RunMenuAt is called on the |
| 282 // parent NativeMenuWin, and so it's not possible to assume that we can just |
| 283 // dispatch the command id returned by TrackPopupMenuEx to the parent's |
| 284 // delegate. For this reason, we stow a pointer on every menu item we create |
| 285 // to the NativeMenuWin that most closely contains it. Fortunately, Windows |
| 286 // provides GetMenuItemInfo, which can walk down the menu item tree from |
| 287 // the root |menu_| to find the data for a given item even if it's in a |
| 288 // submenu. |
| 289 MENUITEMINFO mii = {0}; |
| 290 mii.cbSize = sizeof(mii); |
| 291 mii.fMask = MIIM_DATA; |
| 292 GetMenuItemInfo(menu_, command_id, FALSE, &mii); |
| 293 return reinterpret_cast<NativeMenuWin*>(mii.dwItemData); |
| 294 } |
| 295 |
| 296 UINT NativeMenuWin::GetAlignmentFlags(int alignment) const { |
| 297 bool rtl = l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT; |
| 298 UINT alignment_flags = TPM_TOPALIGN; |
| 299 if (alignment == Menu2::ALIGN_TOPLEFT) |
| 300 alignment_flags |= TPM_LEFTALIGN; |
| 301 else if (alignment == Menu2::ALIGN_TOPRIGHT) |
| 302 alignment_flags |= TPM_RIGHTALIGN; |
| 303 return alignment_flags; |
| 304 } |
| 305 |
| 306 void NativeMenuWin::ResetNativeMenu() { |
| 307 if (IsWindow(system_menu_for_)) { |
| 308 if (menu_) |
| 309 GetSystemMenu(system_menu_for_, TRUE); |
| 310 menu_ = GetSystemMenu(system_menu_for_, FALSE); |
| 311 } else { |
| 312 if (menu_) |
| 313 DestroyMenu(menu_); |
| 314 menu_ = CreatePopupMenu(); |
| 315 } |
| 316 } |
| 317 |
| 318 void NativeMenuWin::CreateHostWindow() { |
| 319 if (!host_window_.get()) |
| 320 host_window_.reset(new MenuHostWindow()); |
| 321 } |
| 322 |
| 323 //////////////////////////////////////////////////////////////////////////////// |
| 324 // SystemMenuModel: |
| 325 |
| 326 SystemMenuModel::SystemMenuModel(SimpleMenuModel::Delegate* delegate) |
| 327 : SimpleMenuModel(delegate) { |
| 328 } |
| 329 |
| 330 SystemMenuModel::~SystemMenuModel() { |
| 331 } |
| 332 |
| 333 int SystemMenuModel::GetFirstItemIndex(gfx::NativeMenu native_menu) const { |
| 334 // We allow insertions before last item (Close). |
| 335 return std::max(0, GetMenuItemCount(native_menu) - 1); |
| 336 } |
| 337 |
| 338 //////////////////////////////////////////////////////////////////////////////// |
| 339 // MenuWrapper, public: |
| 340 |
| 341 // static |
| 342 MenuWrapper* MenuWrapper::CreateWrapper(Menu2* menu) { |
| 343 return new NativeMenuWin(menu->model(), menu->delegate(), NULL); |
| 344 } |
| 345 |
| 346 } // namespace views |
OLD | NEW |