1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "ui/views/controls/menu/native_menu_win.h" 6 7 #include <Windowsx.h> 8 9 #include "base/bind.h" 10 #include "base/logging.h" 11 #include "base/message_loop/message_loop.h" 12 #include "base/stl_util.h" 13 #include "base/strings/string_util.h" 14 #include "base/win/wrapped_window_proc.h" 15 #include "ui/base/accelerators/accelerator.h" 16 #include "ui/base/l10n/l10n_util.h" 17 #include "ui/base/l10n/l10n_util_win.h" 18 #include "ui/base/models/menu_model.h" 19 #include "ui/events/keycodes/keyboard_codes.h" 20 #include "ui/gfx/canvas.h" 21 #include "ui/gfx/font.h" 22 #include "ui/gfx/image/image.h" 23 #include "ui/gfx/image/image_skia.h" 24 #include "ui/gfx/rect.h" 25 #include "ui/gfx/win/hwnd_util.h" 26 #include "ui/native_theme/native_theme.h" 27 #include "ui/native_theme/native_theme_win.h" 28 #include "ui/views/controls/menu/menu_2.h" 29 #include "ui/views/controls/menu/menu_config.h" 30 #include "ui/views/controls/menu/menu_insertion_delegate_win.h" 31 #include "ui/views/controls/menu/menu_listener.h" 32 #include "ui/views/layout/layout_constants.h" 33 34 using ui::NativeTheme; 35 36 namespace views { 37 38 // The width of an icon, including the pixels between the icon and 39 // the item label. 40 static const int kIconWidth = 23; 41 // Margins between the top of the item and the label. 42 static const int kItemTopMargin = 3; 43 // Margins between the bottom of the item and the label. 44 static const int kItemBottomMargin = 4; 45 // Margins between the left of the item and the icon. 46 static const int kItemLeftMargin = 4; 47 // The width for displaying the sub-menu arrow. 48 static const int kArrowWidth = 10; 49 50 struct NativeMenuWin::ItemData { 51 // The Windows API requires that whoever creates the menus must own the 52 // strings used for labels, and keep them around for the lifetime of the 53 // created menu. So be it. 54 string16 label; 55 56 // Someone needs to own submenus, it may as well be us. 57 scoped_ptr<Menu2> submenu; 58 59 // We need a pointer back to the containing menu in various circumstances. 60 NativeMenuWin* native_menu_win; 61 62 // The index of the item within the menu's model. 63 int model_index; 64 }; 65 66 // Returns the NativeMenuWin for a particular HMENU. 67 static NativeMenuWin* GetNativeMenuWinFromHMENU(HMENU hmenu) { 68 MENUINFO mi = {0}; 69 mi.cbSize = sizeof(mi); 70 mi.fMask = MIM_MENUDATA | MIM_STYLE; 71 GetMenuInfo(hmenu, &mi); 72 return reinterpret_cast<NativeMenuWin*>(mi.dwMenuData); 73 } 74 75 // A window that receives messages from Windows relevant to the native menu 76 // structure we have constructed in NativeMenuWin. 77 class NativeMenuWin::MenuHostWindow { 78 public: 79 explicit MenuHostWindow(NativeMenuWin* parent) : parent_(parent) { 80 RegisterClass(); 81 hwnd_ = CreateWindowEx(l10n_util::GetExtendedStyles(), kWindowClassName, 82 L"", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL); 83 gfx::CheckWindowCreated(hwnd_); 84 gfx::SetWindowUserData(hwnd_, this); 85 } 86 87 ~MenuHostWindow() { 88 DestroyWindow(hwnd_); 89 } 90 91 HWND hwnd() const { return hwnd_; } 92 93 private: 94 static const wchar_t* kWindowClassName; 95 96 void RegisterClass() { 97 static bool registered = false; 98 if (registered) 99 return; 100 101 WNDCLASSEX window_class; 102 base::win::InitializeWindowClass( 103 kWindowClassName, 104 &base::win::WrappedWindowProc<MenuHostWindowProc>, 105 CS_DBLCLKS, 106 0, 107 0, 108 NULL, 109 reinterpret_cast<HBRUSH>(COLOR_WINDOW+1), 110 NULL, 111 NULL, 112 NULL, 113 &window_class); 114 ATOM clazz = RegisterClassEx(&window_class); 115 CHECK(clazz); 116 registered = true; 117 } 118 119 // Converts the WPARAM value passed to WM_MENUSELECT into an index 120 // corresponding to the menu item that was selected. 121 int GetMenuItemIndexFromWPARAM(HMENU menu, WPARAM w_param) const { 122 int count = GetMenuItemCount(menu); 123 // For normal command menu items, Windows passes a command id as the LOWORD 124 // of WPARAM for WM_MENUSELECT. We need to walk forward through the menu 125 // items to find an item with a matching ID. Ugh! 126 for (int i = 0; i < count; ++i) { 127 MENUITEMINFO mii = {0}; 128 mii.cbSize = sizeof(mii); 129 mii.fMask = MIIM_ID; 130 GetMenuItemInfo(menu, i, MF_BYPOSITION, &mii); 131 if (mii.wID == w_param) 132 return i; 133 } 134 // If we didn't find a matching command ID, this means a submenu has been 135 // selected instead, and rather than passing a command ID in 136 // LOWORD(w_param), Windows has actually passed us a position, so we just 137 // return it. 138 return w_param; 139 } 140 141 NativeMenuWin::ItemData* GetItemData(ULONG_PTR item_data) { 142 return reinterpret_cast<NativeMenuWin::ItemData*>(item_data); 143 } 144 145 // Called when the user selects a specific item. 146 void OnMenuCommand(int position, HMENU menu) { 147 NativeMenuWin* menu_win = GetNativeMenuWinFromHMENU(menu); 148 ui::MenuModel* model = menu_win->model_; 149 NativeMenuWin* root_menu = menu_win; 150 while (root_menu->parent_) 151 root_menu = root_menu->parent_; 152 153 // Only notify the model if it didn't already send out notification. 154 // See comment in MenuMessageHook for details. 155 if (root_menu->menu_action_ == MenuWrapper::MENU_ACTION_NONE) 156 model->ActivatedAt(position); 157 } 158 159 // Called as the user moves their mouse or arrows through the contents of the 160 // menu. 161 void OnMenuSelect(WPARAM w_param, HMENU menu) { 162 if (!menu) 163 return; // menu is null when closing on XP. 164 165 int position = GetMenuItemIndexFromWPARAM(menu, w_param); 166 if (position >= 0) 167 GetNativeMenuWinFromHMENU(menu)->model_->HighlightChangedTo(position); 168 } 169 170 // Called by Windows to measure the size of an owner-drawn menu item. 171 void OnMeasureItem(WPARAM w_param, MEASUREITEMSTRUCT* measure_item_struct) { 172 NativeMenuWin::ItemData* data = GetItemData(measure_item_struct->itemData); 173 if (data) { 174 gfx::Font font; 175 measure_item_struct->itemWidth = font.GetStringWidth(data->label) + 176 kIconWidth + kItemLeftMargin + views::kItemLabelSpacing - 177 GetSystemMetrics(SM_CXMENUCHECK); 178 if (data->submenu.get()) 179 measure_item_struct->itemWidth += kArrowWidth; 180 // If the label contains an accelerator, make room for tab. 181 if (data->label.find(L'\t') != string16::npos) 182 measure_item_struct->itemWidth += font.GetStringWidth(L" "); 183 measure_item_struct->itemHeight = 184 font.GetHeight() + kItemBottomMargin + kItemTopMargin; 185 } else { 186 // Measure separator size. 187 measure_item_struct->itemHeight = GetSystemMetrics(SM_CYMENU) / 2; 188 measure_item_struct->itemWidth = 0; 189 } 190 } 191 192 // Called by Windows to paint an owner-drawn menu item. 193 void OnDrawItem(UINT w_param, DRAWITEMSTRUCT* draw_item_struct) { 194 HDC dc = draw_item_struct->hDC; 195 COLORREF prev_bg_color, prev_text_color; 196 197 // Set background color and text color 198 if (draw_item_struct->itemState & ODS_SELECTED) { 199 prev_bg_color = SetBkColor(dc, GetSysColor(COLOR_HIGHLIGHT)); 200 prev_text_color = SetTextColor(dc, GetSysColor(COLOR_HIGHLIGHTTEXT)); 201 } else { 202 prev_bg_color = SetBkColor(dc, GetSysColor(COLOR_MENU)); 203 if (draw_item_struct->itemState & ODS_DISABLED) 204 prev_text_color = SetTextColor(dc, GetSysColor(COLOR_GRAYTEXT)); 205 else 206 prev_text_color = SetTextColor(dc, GetSysColor(COLOR_MENUTEXT)); 207 } 208 209 if (draw_item_struct->itemData) { 210 NativeMenuWin::ItemData* data = GetItemData(draw_item_struct->itemData); 211 // Draw the background. 212 HBRUSH hbr = CreateSolidBrush(GetBkColor(dc)); 213 FillRect(dc, &draw_item_struct->rcItem, hbr); 214 DeleteObject(hbr); 215 216 // Draw the label. 217 RECT rect = draw_item_struct->rcItem; 218 rect.top += kItemTopMargin; 219 // Should we add kIconWidth only when icon.width() != 0 ? 220 rect.left += kItemLeftMargin + kIconWidth; 221 rect.right -= views::kItemLabelSpacing; 222 UINT format = DT_TOP | DT_SINGLELINE; 223 // Check whether the mnemonics should be underlined. 224 BOOL underline_mnemonics; 225 SystemParametersInfo(SPI_GETKEYBOARDCUES, 0, &underline_mnemonics, 0); 226 if (!underline_mnemonics) 227 format |= DT_HIDEPREFIX; 228 gfx::Font font; 229 HGDIOBJ old_font = 230 static_cast<HFONT>(SelectObject(dc, font.GetNativeFont())); 231 232 // If an accelerator is specified (with a tab delimiting the rest of the 233 // label from the accelerator), we have to justify the fist part on the 234 // left and the accelerator on the right. 235 // TODO(jungshik): This will break in RTL UI. Currently, he/ar use the 236 // window system UI font and will not hit here. 237 string16 label = data->label; 238 string16 accel; 239 string16::size_type tab_pos = label.find(L'\t'); 240 if (tab_pos != string16::npos) { 241 accel = label.substr(tab_pos); 242 label = label.substr(0, tab_pos); 243 } 244 DrawTextEx(dc, const_cast<wchar_t*>(label.data()), 245 static_cast<int>(label.size()), &rect, format | DT_LEFT, NULL); 246 if (!accel.empty()) 247 DrawTextEx(dc, const_cast<wchar_t*>(accel.data()), 248 static_cast<int>(accel.size()), &rect, 249 format | DT_RIGHT, NULL); 250 SelectObject(dc, old_font); 251 252 ui::MenuModel::ItemType type = 253 data->native_menu_win->model_->GetTypeAt(data->model_index); 254 255 // Draw the icon after the label, otherwise it would be covered 256 // by the label. 257 gfx::Image icon; 258 if (data->native_menu_win->model_->GetIconAt(data->model_index, &icon)) { 259 // We currently don't support items with both icons and checkboxes. 260 const gfx::ImageSkia* skia_icon = icon.ToImageSkia(); 261 DCHECK(type != ui::MenuModel::TYPE_CHECK); 262 gfx::Canvas canvas( 263 skia_icon->GetRepresentation(1.0f), 264 false); 265 skia::DrawToNativeContext( 266 canvas.sk_canvas(), dc, 267 draw_item_struct->rcItem.left + kItemLeftMargin, 268 draw_item_struct->rcItem.top + (draw_item_struct->rcItem.bottom - 269 draw_item_struct->rcItem.top - skia_icon->height()) / 2, NULL); 270 } else if (type == ui::MenuModel::TYPE_CHECK && 271 data->native_menu_win->model_->IsItemCheckedAt( 272 data->model_index)) { 273 // Manually render a checkbox. 274 ui::NativeThemeWin* native_theme = ui::NativeThemeWin::instance(); 275 const MenuConfig& config = MenuConfig::instance(native_theme); 276 NativeTheme::State state; 277 if (draw_item_struct->itemState & ODS_DISABLED) { 278 state = NativeTheme::kDisabled; 279 } else { 280 state = draw_item_struct->itemState & ODS_SELECTED ? 281 NativeTheme::kHovered : NativeTheme::kNormal; 282 } 283 int height = 284 draw_item_struct->rcItem.bottom - draw_item_struct->rcItem.top; 285 int icon_y = kItemTopMargin + 286 (height - kItemTopMargin - kItemBottomMargin - 287 config.check_height) / 2; 288 gfx::Canvas canvas(gfx::Size(config.check_width, config.check_height), 289 1.0f, 290 false); 291 NativeTheme::ExtraParams extra; 292 extra.menu_check.is_radio = false; 293 gfx::Rect bounds(0, 0, config.check_width, config.check_height); 294 295 // Draw the background and the check. 296 native_theme->Paint( 297 canvas.sk_canvas(), NativeTheme::kMenuCheckBackground, 298 state, bounds, extra); 299 native_theme->Paint( 300 canvas.sk_canvas(), NativeTheme::kMenuCheck, state, bounds, extra); 301 302 // Draw checkbox to menu. 303 skia::DrawToNativeContext(canvas.sk_canvas(), dc, 304 draw_item_struct->rcItem.left + kItemLeftMargin, 305 draw_item_struct->rcItem.top + (draw_item_struct->rcItem.bottom - 306 draw_item_struct->rcItem.top - config.check_height) / 2, NULL); 307 } 308 309 } else { 310 // Draw the separator 311 draw_item_struct->rcItem.top += 312 (draw_item_struct->rcItem.bottom - draw_item_struct->rcItem.top) / 3; 313 DrawEdge(dc, &draw_item_struct->rcItem, EDGE_ETCHED, BF_TOP); 314 } 315 316 SetBkColor(dc, prev_bg_color); 317 SetTextColor(dc, prev_text_color); 318 } 319 320 bool ProcessWindowMessage(HWND window, 321 UINT message, 322 WPARAM w_param, 323 LPARAM l_param, 324 LRESULT* l_result) { 325 switch (message) { 326 case WM_MENUCOMMAND: 327 OnMenuCommand(w_param, reinterpret_cast<HMENU>(l_param)); 328 *l_result = 0; 329 return true; 330 case WM_MENUSELECT: 331 OnMenuSelect(LOWORD(w_param), reinterpret_cast<HMENU>(l_param)); 332 *l_result = 0; 333 return true; 334 case WM_MEASUREITEM: 335 OnMeasureItem(w_param, reinterpret_cast<MEASUREITEMSTRUCT*>(l_param)); 336 *l_result = 0; 337 return true; 338 case WM_DRAWITEM: 339 OnDrawItem(w_param, reinterpret_cast<DRAWITEMSTRUCT*>(l_param)); 340 *l_result = 0; 341 return true; 342 // TODO(beng): bring over owner draw from old menu system. 343 } 344 return false; 345 } 346 347 static LRESULT CALLBACK MenuHostWindowProc(HWND window, 348 UINT message, 349 WPARAM w_param, 350 LPARAM l_param) { 351 MenuHostWindow* host = 352 reinterpret_cast<MenuHostWindow*>(gfx::GetWindowUserData(window)); 353 // host is null during initial construction. 354 LRESULT l_result = 0; 355 if (!host || !host->ProcessWindowMessage(window, message, w_param, l_param, 356 &l_result)) { 357 return DefWindowProc(window, message, w_param, l_param); 358 } 359 return l_result; 360 } 361 362 HWND hwnd_; 363 NativeMenuWin* parent_; 364 365 DISALLOW_COPY_AND_ASSIGN(MenuHostWindow); 366 }; 367 368 struct NativeMenuWin::HighlightedMenuItemInfo { 369 HighlightedMenuItemInfo() 370 : has_parent(false), 371 has_submenu(false), 372 menu(NULL), 373 position(-1) { 374 } 375 376 bool has_parent; 377 bool has_submenu; 378 379 // The menu and position. These are only set for non-disabled menu items. 380 NativeMenuWin* menu; 381 int position; 382 }; 383 384 // static 385 const wchar_t* NativeMenuWin::MenuHostWindow::kWindowClassName = 386 L"ViewsMenuHostWindow"; 387 388 //////////////////////////////////////////////////////////////////////////////// 389 // NativeMenuWin, public: 390 391 NativeMenuWin::NativeMenuWin(ui::MenuModel* model, HWND system_menu_for) 392 : model_(model), 393 menu_(NULL), 394 owner_draw_(l10n_util::NeedOverrideDefaultUIFont(NULL, NULL) && 395 !system_menu_for), 396 system_menu_for_(system_menu_for), 397 first_item_index_(0), 398 menu_action_(MENU_ACTION_NONE), 399 menu_to_select_(NULL), 400 position_to_select_(-1), 401 menu_to_select_factory_(this), 402 parent_(NULL), 403 destroyed_flag_(NULL) { 404 } 405 406 NativeMenuWin::~NativeMenuWin() { 407 if (destroyed_flag_) 408 *destroyed_flag_ = true; 409 STLDeleteContainerPointers(items_.begin(), items_.end()); 410 DestroyMenu(menu_); 411 } 412 413 //////////////////////////////////////////////////////////////////////////////// 414 // NativeMenuWin, MenuWrapper implementation: 415 416 void NativeMenuWin::RunMenuAt(const gfx::Point& point, int alignment) { 417 CreateHostWindow(); 418 UpdateStates(); 419 UINT flags = TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RECURSE; 420 flags |= GetAlignmentFlags(alignment); 421 menu_action_ = MENU_ACTION_NONE; 422 423 // Set a hook function so we can listen for keyboard events while the 424 // menu is open, and store a pointer to this object in a static 425 // variable so the hook has access to it (ugly, but it's the 426 // only way). 427 open_native_menu_win_ = this; 428 HHOOK hhook = SetWindowsHookEx(WH_MSGFILTER, MenuMessageHook, 429 GetModuleHandle(NULL), ::GetCurrentThreadId()); 430 431 // Mark that any registered listeners have not been called for this particular 432 // opening of the menu. 433 listeners_called_ = false; 434 435 // Command dispatch is done through WM_MENUCOMMAND, handled by the host 436 // window. 437 HWND hwnd = host_window_->hwnd(); 438 menu_to_select_ = NULL; 439 position_to_select_ = -1; 440 menu_to_select_factory_.InvalidateWeakPtrs(); 441 bool destroyed = false; 442 destroyed_flag_ = &destroyed; 443 model_->MenuWillShow(); 444 TrackPopupMenu(menu_, flags, point.x(), point.y(), 0, host_window_->hwnd(), 445 NULL); 446 UnhookWindowsHookEx(hhook); 447 open_native_menu_win_ = NULL; 448 if (destroyed) 449 return; 450 destroyed_flag_ = NULL; 451 if (menu_to_select_) { 452 // Folks aren't too happy if we notify immediately. In particular, notifying 453 // the delegate can cause destruction leaving the stack in a weird 454 // state. Instead post a task, then notify. This mirrors what WM_MENUCOMMAND 455 // does. 456 menu_to_select_factory_.InvalidateWeakPtrs(); 457 base::MessageLoop::current()->PostTask( 458 FROM_HERE, 459 base::Bind(&NativeMenuWin::DelayedSelect, 460 menu_to_select_factory_.GetWeakPtr())); 461 menu_action_ = MENU_ACTION_SELECTED; 462 } 463 // Send MenuClosed after we schedule the select, otherwise MenuClosed is 464 // processed after the select (MenuClosed posts a delayed task too). 465 model_->MenuClosed(); 466 } 467 468 void NativeMenuWin::CancelMenu() { 469 EndMenu(); 470 } 471 472 void NativeMenuWin::Rebuild(MenuInsertionDelegateWin* delegate) { 473 ResetNativeMenu(); 474 items_.clear(); 475 476 owner_draw_ = model_->HasIcons() || owner_draw_; 477 first_item_index_ = delegate ? delegate->GetInsertionIndex(menu_) : 0; 478 for (int menu_index = first_item_index_; 479 menu_index < first_item_index_ + model_->GetItemCount(); ++menu_index) { 480 int model_index = menu_index - first_item_index_; 481 if (model_->GetTypeAt(model_index) == ui::MenuModel::TYPE_SEPARATOR) 482 AddSeparatorItemAt(menu_index, model_index); 483 else 484 AddMenuItemAt(menu_index, model_index); 485 } 486 } 487 488 void NativeMenuWin::UpdateStates() { 489 // A depth-first walk of the menu items, updating states. 490 int model_index = 0; 491 std::vector<ItemData*>::const_iterator it; 492 for (it = items_.begin(); it != items_.end(); ++it, ++model_index) { 493 int menu_index = model_index + first_item_index_; 494 SetMenuItemState(menu_index, model_->IsEnabledAt(model_index), 495 model_->IsItemCheckedAt(model_index), false); 496 if (model_->IsItemDynamicAt(model_index)) { 497 // TODO(atwilson): Update the icon as well (http://crbug.com/66508). 498 SetMenuItemLabel(menu_index, model_index, 499 model_->GetLabelAt(model_index)); 500 } 501 Menu2* submenu = (*it)->submenu.get(); 502 if (submenu) 503 submenu->UpdateStates(); 504 } 505 } 506 507 HMENU NativeMenuWin::GetNativeMenu() const { 508 return menu_; 509 } 510 511 NativeMenuWin::MenuAction NativeMenuWin::GetMenuAction() const { 512 return menu_action_; 513 } 514 515 void NativeMenuWin::AddMenuListener(MenuListener* listener) { 516 listeners_.AddObserver(listener); 517 } 518 519 void NativeMenuWin::RemoveMenuListener(MenuListener* listener) { 520 listeners_.RemoveObserver(listener); 521 } 522 523 void NativeMenuWin::SetMinimumWidth(int width) { 524 NOTIMPLEMENTED(); 525 } 526 527 //////////////////////////////////////////////////////////////////////////////// 528 // NativeMenuWin, private: 529 530 // static 531 NativeMenuWin* NativeMenuWin::open_native_menu_win_ = NULL; 532 533 void NativeMenuWin::DelayedSelect() { 534 if (menu_to_select_) 535 menu_to_select_->model_->ActivatedAt(position_to_select_); 536 } 537 538 // static 539 bool NativeMenuWin::GetHighlightedMenuItemInfo( 540 HMENU menu, 541 HighlightedMenuItemInfo* info) { 542 for (int i = 0; i < ::GetMenuItemCount(menu); i++) { 543 UINT state = ::GetMenuState(menu, i, MF_BYPOSITION); 544 if (state & MF_HILITE) { 545 if (state & MF_POPUP) { 546 HMENU submenu = GetSubMenu(menu, i); 547 if (GetHighlightedMenuItemInfo(submenu, info)) 548 info->has_parent = true; 549 else 550 info->has_submenu = true; 551 } else if (!(state & MF_SEPARATOR) && !(state & MF_DISABLED)) { 552 info->menu = GetNativeMenuWinFromHMENU(menu); 553 info->position = i; 554 } 555 return true; 556 } 557 } 558 return false; 559 } 560 561 // static 562 LRESULT CALLBACK NativeMenuWin::MenuMessageHook( 563 int n_code, WPARAM w_param, LPARAM l_param) { 564 LRESULT result = CallNextHookEx(NULL, n_code, w_param, l_param); 565 566 NativeMenuWin* this_ptr = open_native_menu_win_; 567 if (!this_ptr) 568 return result; 569 570 // The first time this hook is called, that means the menu has successfully 571 // opened, so call the callback function on all of our listeners. 572 if (!this_ptr->listeners_called_) { 573 FOR_EACH_OBSERVER(MenuListener, this_ptr->listeners_, OnMenuOpened()); 574 this_ptr->listeners_called_ = true; 575 } 576 577 MSG* msg = reinterpret_cast<MSG*>(l_param); 578 if (msg->message == WM_LBUTTONUP || msg->message == WM_RBUTTONUP) { 579 HighlightedMenuItemInfo info; 580 if (GetHighlightedMenuItemInfo(this_ptr->menu_, &info) && info.menu) { 581 // It appears that when running a menu by way of TrackPopupMenu(Ex) win32 582 // gets confused if the underlying window paints itself. As its very easy 583 // for the underlying window to repaint itself (especially since some menu 584 // items trigger painting of the tabstrip on mouse over) we have this 585 // workaround. When the mouse is released on a menu item we remember the 586 // menu item and end the menu. When the nested message loop returns we 587 // schedule a task to notify the model. It's still possible to get a 588 // WM_MENUCOMMAND, so we have to be careful that we don't notify the model 589 // twice. 590 this_ptr->menu_to_select_ = info.menu; 591 this_ptr->position_to_select_ = info.position; 592 EndMenu(); 593 } 594 } else if (msg->message == WM_KEYDOWN) { 595 HighlightedMenuItemInfo info; 596 if (GetHighlightedMenuItemInfo(this_ptr->menu_, &info)) { 597 if (msg->wParam == VK_LEFT && !info.has_parent) { 598 this_ptr->menu_action_ = MENU_ACTION_PREVIOUS; 599 ::EndMenu(); 600 } else if (msg->wParam == VK_RIGHT && !info.has_parent && 601 !info.has_submenu) { 602 this_ptr->menu_action_ = MENU_ACTION_NEXT; 603 ::EndMenu(); 604 } 605 } 606 } 607 608 return result; 609 } 610 611 bool NativeMenuWin::IsSeparatorItemAt(int menu_index) const { 612 MENUITEMINFO mii = {0}; 613 mii.cbSize = sizeof(mii); 614 mii.fMask = MIIM_FTYPE; 615 GetMenuItemInfo(menu_, menu_index, MF_BYPOSITION, &mii); 616 return !!(mii.fType & MF_SEPARATOR); 617 } 618 619 void NativeMenuWin::AddMenuItemAt(int menu_index, int model_index) { 620 MENUITEMINFO mii = {0}; 621 mii.cbSize = sizeof(mii); 622 mii.fMask = MIIM_FTYPE | MIIM_ID | MIIM_DATA; 623 if (!owner_draw_) 624 mii.fType = MFT_STRING; 625 else 626 mii.fType = MFT_OWNERDRAW; 627 628 ItemData* item_data = new ItemData; 629 item_data->label = string16(); 630 ui::MenuModel::ItemType type = model_->GetTypeAt(model_index); 631 if (type == ui::MenuModel::TYPE_SUBMENU) { 632 item_data->submenu.reset(new Menu2(model_->GetSubmenuModelAt(model_index))); 633 mii.fMask |= MIIM_SUBMENU; 634 mii.hSubMenu = item_data->submenu->GetNativeMenu(); 635 GetNativeMenuWinFromHMENU(mii.hSubMenu)->parent_ = this; 636 } else { 637 if (type == ui::MenuModel::TYPE_RADIO) 638 mii.fType |= MFT_RADIOCHECK; 639 mii.wID = model_->GetCommandIdAt(model_index); 640 } 641 item_data->native_menu_win = this; 642 item_data->model_index = model_index; 643 items_.insert(items_.begin() + model_index, item_data); 644 mii.dwItemData = reinterpret_cast<ULONG_PTR>(item_data); 645 UpdateMenuItemInfoForString(&mii, model_index, 646 model_->GetLabelAt(model_index)); 647 InsertMenuItem(menu_, menu_index, TRUE, &mii); 648 } 649 650 void NativeMenuWin::AddSeparatorItemAt(int menu_index, int model_index) { 651 MENUITEMINFO mii = {0}; 652 mii.cbSize = sizeof(mii); 653 mii.fMask = MIIM_FTYPE; 654 mii.fType = MFT_SEPARATOR; 655 // Insert a dummy entry into our label list so we can index directly into it 656 // using item indices if need be. 657 items_.insert(items_.begin() + model_index, new ItemData); 658 InsertMenuItem(menu_, menu_index, TRUE, &mii); 659 } 660 661 void NativeMenuWin::SetMenuItemState(int menu_index, bool enabled, bool checked, 662 bool is_default) { 663 if (IsSeparatorItemAt(menu_index)) 664 return; 665 666 UINT state = enabled ? MFS_ENABLED : MFS_DISABLED; 667 if (checked) 668 state |= MFS_CHECKED; 669 if (is_default) 670 state |= MFS_DEFAULT; 671 672 MENUITEMINFO mii = {0}; 673 mii.cbSize = sizeof(mii); 674 mii.fMask = MIIM_STATE; 675 mii.fState = state; 676 SetMenuItemInfo(menu_, menu_index, MF_BYPOSITION, &mii); 677 } 678 679 void NativeMenuWin::SetMenuItemLabel(int menu_index, 680 int model_index, 681 const string16& label) { 682 if (IsSeparatorItemAt(menu_index)) 683 return; 684 685 MENUITEMINFO mii = {0}; 686 mii.cbSize = sizeof(mii); 687 UpdateMenuItemInfoForString(&mii, model_index, label); 688 SetMenuItemInfo(menu_, menu_index, MF_BYPOSITION, &mii); 689 } 690 691 void NativeMenuWin::UpdateMenuItemInfoForString(MENUITEMINFO* mii, 692 int model_index, 693 const string16& label) { 694 string16 formatted = label; 695 ui::MenuModel::ItemType type = model_->GetTypeAt(model_index); 696 // Strip out any tabs, otherwise they get interpreted as accelerators and can 697 // lead to weird behavior. 698 ReplaceSubstringsAfterOffset(&formatted, 0, L"\t", L" "); 699 if (type != ui::MenuModel::TYPE_SUBMENU) { 700 // Add accelerator details to the label if provided. 701 ui::Accelerator accelerator(ui::VKEY_UNKNOWN, ui::EF_NONE); 702 if (model_->GetAcceleratorAt(model_index, &accelerator)) { 703 formatted += L"\t"; 704 formatted += accelerator.GetShortcutText(); 705 } 706 } 707 708 // Update the owned string, since Windows will want us to keep this new 709 // version around. 710 items_[model_index]->label = formatted; 711 712 // Give Windows a pointer to the label string. 713 mii->fMask |= MIIM_STRING; 714 mii->dwTypeData = 715 const_cast<wchar_t*>(items_[model_index]->label.c_str()); 716 } 717 718 UINT NativeMenuWin::GetAlignmentFlags(int alignment) const { 719 UINT alignment_flags = TPM_TOPALIGN; 720 if (alignment == Menu2::ALIGN_TOPLEFT) 721 alignment_flags |= TPM_LEFTALIGN; 722 else if (alignment == Menu2::ALIGN_TOPRIGHT) 723 alignment_flags |= TPM_RIGHTALIGN; 724 return alignment_flags; 725 } 726 727 void NativeMenuWin::ResetNativeMenu() { 728 if (IsWindow(system_menu_for_)) { 729 if (menu_) 730 GetSystemMenu(system_menu_for_, TRUE); 731 menu_ = GetSystemMenu(system_menu_for_, FALSE); 732 } else { 733 if (menu_) 734 DestroyMenu(menu_); 735 menu_ = CreatePopupMenu(); 736 // Rather than relying on the return value of TrackPopupMenuEx, which is 737 // always a command identifier, instead we tell the menu to notify us via 738 // our host window and the WM_MENUCOMMAND message. 739 MENUINFO mi = {0}; 740 mi.cbSize = sizeof(mi); 741 mi.fMask = MIM_STYLE | MIM_MENUDATA; 742 mi.dwStyle = MNS_NOTIFYBYPOS; 743 mi.dwMenuData = reinterpret_cast<ULONG_PTR>(this); 744 SetMenuInfo(menu_, &mi); 745 } 746 } 747 748 void NativeMenuWin::CreateHostWindow() { 749 // This only gets called from RunMenuAt, and as such there is only ever one 750 // host window per menu hierarchy, no matter how many NativeMenuWin objects 751 // exist wrapping submenus. 752 if (!host_window_.get()) 753 host_window_.reset(new MenuHostWindow(this)); 754 } 755 756 //////////////////////////////////////////////////////////////////////////////// 757 // MenuWrapper, public: 758 759 // static 760 MenuWrapper* MenuWrapper::CreateWrapper(ui::MenuModel* model) { 761 return new NativeMenuWin(model, NULL); 762 } 763 764 } // namespace views 765