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.
5 #include "ui/views/controls/menu/native_menu_win.h"
10 #include "base/logging.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/profiler/scoped_tracker.h"
13 #include "base/stl_util.h"
14 #include "base/strings/string_util.h"
15 #include "base/win/wrapped_window_proc.h"
16 #include "ui/base/accelerators/accelerator.h"
17 #include "ui/base/l10n/l10n_util.h"
18 #include "ui/base/l10n/l10n_util_win.h"
19 #include "ui/base/models/menu_model.h"
20 #include "ui/events/keycodes/keyboard_codes.h"
21 #include "ui/gfx/canvas.h"
22 #include "ui/gfx/font_list.h"
23 #include "ui/gfx/geometry/rect.h"
24 #include "ui/gfx/image/image.h"
25 #include "ui/gfx/image/image_skia.h"
26 #include "ui/gfx/text_utils.h"
27 #include "ui/gfx/win/hwnd_util.h"
28 #include "ui/native_theme/native_theme.h"
29 #include "ui/native_theme/native_theme_win.h"
30 #include "ui/views/controls/menu/menu_2.h"
31 #include "ui/views/controls/menu/menu_config.h"
32 #include "ui/views/controls/menu/menu_insertion_delegate_win.h"
33 #include "ui/views/controls/menu/menu_listener.h"
34 #include "ui/views/layout/layout_constants.h"
36 using ui::NativeTheme
;
40 // The width of an icon, including the pixels between the icon and
42 static const int kIconWidth
= 23;
43 // Margins between the top of the item and the label.
44 static const int kItemTopMargin
= 3;
45 // Margins between the bottom of the item and the label.
46 static const int kItemBottomMargin
= 4;
47 // Margins between the left of the item and the icon.
48 static const int kItemLeftMargin
= 4;
49 // The width for displaying the sub-menu arrow.
50 static const int kArrowWidth
= 10;
52 struct NativeMenuWin::ItemData
{
53 // The Windows API requires that whoever creates the menus must own the
54 // strings used for labels, and keep them around for the lifetime of the
55 // created menu. So be it.
58 // Someone needs to own submenus, it may as well be us.
59 scoped_ptr
<Menu2
> submenu
;
61 // We need a pointer back to the containing menu in various circumstances.
62 NativeMenuWin
* native_menu_win
;
64 // The index of the item within the menu's model.
68 // Returns the NativeMenuWin for a particular HMENU.
69 static NativeMenuWin
* GetNativeMenuWinFromHMENU(HMENU hmenu
) {
71 mi
.cbSize
= sizeof(mi
);
72 mi
.fMask
= MIM_MENUDATA
| MIM_STYLE
;
73 GetMenuInfo(hmenu
, &mi
);
74 return reinterpret_cast<NativeMenuWin
*>(mi
.dwMenuData
);
77 // A window that receives messages from Windows relevant to the native menu
78 // structure we have constructed in NativeMenuWin.
79 class NativeMenuWin::MenuHostWindow
{
81 explicit MenuHostWindow(NativeMenuWin
* parent
) : parent_(parent
) {
83 hwnd_
= CreateWindowEx(l10n_util::GetExtendedStyles(), kWindowClassName
,
84 L
"", 0, 0, 0, 0, 0, HWND_MESSAGE
, NULL
, NULL
, NULL
);
85 gfx::CheckWindowCreated(hwnd_
);
86 gfx::SetWindowUserData(hwnd_
, this);
93 HWND
hwnd() const { return hwnd_
; }
96 static const wchar_t* kWindowClassName
;
98 void RegisterClass() {
99 static bool registered
= false;
103 WNDCLASSEX window_class
;
104 base::win::InitializeWindowClass(
106 &base::win::WrappedWindowProc
<MenuHostWindowProc
>,
111 reinterpret_cast<HBRUSH
>(COLOR_WINDOW
+1),
116 ATOM clazz
= RegisterClassEx(&window_class
);
121 // Converts the WPARAM value passed to WM_MENUSELECT into an index
122 // corresponding to the menu item that was selected.
123 int GetMenuItemIndexFromWPARAM(HMENU menu
, WPARAM w_param
) const {
124 int count
= GetMenuItemCount(menu
);
125 // For normal command menu items, Windows passes a command id as the LOWORD
126 // of WPARAM for WM_MENUSELECT. We need to walk forward through the menu
127 // items to find an item with a matching ID. Ugh!
128 for (int i
= 0; i
< count
; ++i
) {
129 MENUITEMINFO mii
= {0};
130 mii
.cbSize
= sizeof(mii
);
132 GetMenuItemInfo(menu
, i
, MF_BYPOSITION
, &mii
);
133 if (mii
.wID
== w_param
)
136 // If we didn't find a matching command ID, this means a submenu has been
137 // selected instead, and rather than passing a command ID in
138 // LOWORD(w_param), Windows has actually passed us a position, so we just
143 NativeMenuWin::ItemData
* GetItemData(ULONG_PTR item_data
) {
144 return reinterpret_cast<NativeMenuWin::ItemData
*>(item_data
);
147 // Called when the user selects a specific item.
148 void OnMenuCommand(int position
, HMENU menu
) {
149 NativeMenuWin
* menu_win
= GetNativeMenuWinFromHMENU(menu
);
150 ui::MenuModel
* model
= menu_win
->model_
;
151 NativeMenuWin
* root_menu
= menu_win
;
152 while (root_menu
->parent_
)
153 root_menu
= root_menu
->parent_
;
155 // Only notify the model if it didn't already send out notification.
156 // See comment in MenuMessageHook for details.
157 if (root_menu
->menu_action_
== MenuWrapper::MENU_ACTION_NONE
)
158 model
->ActivatedAt(position
);
161 // Called as the user moves their mouse or arrows through the contents of the
163 void OnMenuSelect(WPARAM w_param
, HMENU menu
) {
165 return; // menu is null when closing on XP.
167 int position
= GetMenuItemIndexFromWPARAM(menu
, w_param
);
169 GetNativeMenuWinFromHMENU(menu
)->model_
->HighlightChangedTo(position
);
172 // Called by Windows to measure the size of an owner-drawn menu item.
173 void OnMeasureItem(WPARAM w_param
, MEASUREITEMSTRUCT
* measure_item_struct
) {
174 NativeMenuWin::ItemData
* data
= GetItemData(measure_item_struct
->itemData
);
176 gfx::FontList font_list
;
177 measure_item_struct
->itemWidth
=
178 gfx::GetStringWidth(data
->label
, font_list
) +
179 kIconWidth
+ kItemLeftMargin
+ views::kItemLabelSpacing
-
180 GetSystemMetrics(SM_CXMENUCHECK
);
181 if (data
->submenu
.get())
182 measure_item_struct
->itemWidth
+= kArrowWidth
;
183 // If the label contains an accelerator, make room for tab.
184 if (data
->label
.find(L
'\t') != base::string16::npos
)
185 measure_item_struct
->itemWidth
+= gfx::GetStringWidth(L
" ", font_list
);
186 measure_item_struct
->itemHeight
=
187 font_list
.GetHeight() + kItemBottomMargin
+ kItemTopMargin
;
189 // Measure separator size.
190 measure_item_struct
->itemHeight
= GetSystemMetrics(SM_CYMENU
) / 2;
191 measure_item_struct
->itemWidth
= 0;
195 // Called by Windows to paint an owner-drawn menu item.
196 void OnDrawItem(UINT w_param
, DRAWITEMSTRUCT
* draw_item_struct
) {
197 HDC dc
= draw_item_struct
->hDC
;
198 COLORREF prev_bg_color
, prev_text_color
;
200 // Set background color and text color
201 if (draw_item_struct
->itemState
& ODS_SELECTED
) {
202 prev_bg_color
= SetBkColor(dc
, GetSysColor(COLOR_HIGHLIGHT
));
203 prev_text_color
= SetTextColor(dc
, GetSysColor(COLOR_HIGHLIGHTTEXT
));
205 prev_bg_color
= SetBkColor(dc
, GetSysColor(COLOR_MENU
));
206 if (draw_item_struct
->itemState
& ODS_DISABLED
)
207 prev_text_color
= SetTextColor(dc
, GetSysColor(COLOR_GRAYTEXT
));
209 prev_text_color
= SetTextColor(dc
, GetSysColor(COLOR_MENUTEXT
));
212 if (draw_item_struct
->itemData
) {
213 NativeMenuWin::ItemData
* data
= GetItemData(draw_item_struct
->itemData
);
214 // Draw the background.
215 HBRUSH hbr
= CreateSolidBrush(GetBkColor(dc
));
216 FillRect(dc
, &draw_item_struct
->rcItem
, hbr
);
220 RECT rect
= draw_item_struct
->rcItem
;
221 rect
.top
+= kItemTopMargin
;
222 // Should we add kIconWidth only when icon.width() != 0 ?
223 rect
.left
+= kItemLeftMargin
+ kIconWidth
;
224 rect
.right
-= views::kItemLabelSpacing
;
225 UINT format
= DT_TOP
| DT_SINGLELINE
;
226 // Check whether the mnemonics should be underlined.
227 BOOL underline_mnemonics
;
228 SystemParametersInfo(SPI_GETKEYBOARDCUES
, 0, &underline_mnemonics
, 0);
229 if (!underline_mnemonics
)
230 format
|= DT_HIDEPREFIX
;
231 gfx::FontList font_list
;
232 HGDIOBJ old_font
= static_cast<HFONT
>(
233 SelectObject(dc
, font_list
.GetPrimaryFont().GetNativeFont()));
235 // If an accelerator is specified (with a tab delimiting the rest of the
236 // label from the accelerator), we have to justify the fist part on the
237 // left and the accelerator on the right.
238 // TODO(jungshik): This will break in RTL UI. Currently, he/ar use the
239 // window system UI font and will not hit here.
240 base::string16 label
= data
->label
;
241 base::string16 accel
;
242 base::string16::size_type tab_pos
= label
.find(L
'\t');
243 if (tab_pos
!= base::string16::npos
) {
244 accel
= label
.substr(tab_pos
);
245 label
= label
.substr(0, tab_pos
);
247 DrawTextEx(dc
, const_cast<wchar_t*>(label
.data()),
248 static_cast<int>(label
.size()), &rect
, format
| DT_LEFT
, NULL
);
250 DrawTextEx(dc
, const_cast<wchar_t*>(accel
.data()),
251 static_cast<int>(accel
.size()), &rect
,
252 format
| DT_RIGHT
, NULL
);
253 SelectObject(dc
, old_font
);
255 ui::MenuModel::ItemType type
=
256 data
->native_menu_win
->model_
->GetTypeAt(data
->model_index
);
258 // Draw the icon after the label, otherwise it would be covered
261 if (data
->native_menu_win
->model_
->GetIconAt(data
->model_index
, &icon
)) {
262 // We currently don't support items with both icons and checkboxes.
263 const gfx::ImageSkia
* skia_icon
= icon
.ToImageSkia();
264 DCHECK(type
!= ui::MenuModel::TYPE_CHECK
);
266 skia_icon
->GetRepresentation(1.0f
),
268 skia::DrawToNativeContext(
269 canvas
.sk_canvas(), dc
,
270 draw_item_struct
->rcItem
.left
+ kItemLeftMargin
,
271 draw_item_struct
->rcItem
.top
+ (draw_item_struct
->rcItem
.bottom
-
272 draw_item_struct
->rcItem
.top
- skia_icon
->height()) / 2, NULL
);
273 } else if (type
== ui::MenuModel::TYPE_CHECK
&&
274 data
->native_menu_win
->model_
->IsItemCheckedAt(
275 data
->model_index
)) {
276 // Manually render a checkbox.
277 ui::NativeThemeWin
* native_theme
= ui::NativeThemeWin::instance();
278 const MenuConfig
& config
= MenuConfig::instance(native_theme
);
279 NativeTheme::State state
;
280 if (draw_item_struct
->itemState
& ODS_DISABLED
) {
281 state
= NativeTheme::kDisabled
;
283 state
= draw_item_struct
->itemState
& ODS_SELECTED
?
284 NativeTheme::kHovered
: NativeTheme::kNormal
;
286 gfx::Canvas
canvas(gfx::Size(config
.check_width
, config
.check_height
),
289 NativeTheme::ExtraParams extra
;
290 extra
.menu_check
.is_radio
= false;
291 gfx::Rect
bounds(0, 0, config
.check_width
, config
.check_height
);
293 // Draw the background and the check.
295 canvas
.sk_canvas(), NativeTheme::kMenuCheckBackground
,
296 state
, bounds
, extra
);
298 canvas
.sk_canvas(), NativeTheme::kMenuCheck
, state
, bounds
, extra
);
300 // Draw checkbox to menu.
301 skia::DrawToNativeContext(canvas
.sk_canvas(), dc
,
302 draw_item_struct
->rcItem
.left
+ kItemLeftMargin
,
303 draw_item_struct
->rcItem
.top
+ (draw_item_struct
->rcItem
.bottom
-
304 draw_item_struct
->rcItem
.top
- config
.check_height
) / 2, NULL
);
308 // Draw the separator
309 draw_item_struct
->rcItem
.top
+=
310 (draw_item_struct
->rcItem
.bottom
- draw_item_struct
->rcItem
.top
) / 3;
311 DrawEdge(dc
, &draw_item_struct
->rcItem
, EDGE_ETCHED
, BF_TOP
);
314 SetBkColor(dc
, prev_bg_color
);
315 SetTextColor(dc
, prev_text_color
);
318 bool ProcessWindowMessage(HWND window
,
325 OnMenuCommand(w_param
, reinterpret_cast<HMENU
>(l_param
));
329 OnMenuSelect(LOWORD(w_param
), reinterpret_cast<HMENU
>(l_param
));
333 OnMeasureItem(w_param
, reinterpret_cast<MEASUREITEMSTRUCT
*>(l_param
));
337 OnDrawItem(w_param
, reinterpret_cast<DRAWITEMSTRUCT
*>(l_param
));
340 // TODO(beng): bring over owner draw from old menu system.
345 static LRESULT CALLBACK
MenuHostWindowProc(HWND window
,
349 MenuHostWindow
* host
=
350 reinterpret_cast<MenuHostWindow
*>(gfx::GetWindowUserData(window
));
351 // host is null during initial construction.
352 LRESULT l_result
= 0;
353 if (!host
|| !host
->ProcessWindowMessage(window
, message
, w_param
, l_param
,
355 return DefWindowProc(window
, message
, w_param
, l_param
);
361 NativeMenuWin
* parent_
;
363 DISALLOW_COPY_AND_ASSIGN(MenuHostWindow
);
366 struct NativeMenuWin::HighlightedMenuItemInfo
{
367 HighlightedMenuItemInfo()
377 // The menu and position. These are only set for non-disabled menu items.
383 const wchar_t* NativeMenuWin::MenuHostWindow::kWindowClassName
=
384 L
"ViewsMenuHostWindow";
386 ////////////////////////////////////////////////////////////////////////////////
387 // NativeMenuWin, public:
389 NativeMenuWin::NativeMenuWin(ui::MenuModel
* model
, HWND system_menu_for
)
392 owner_draw_(l10n_util::NeedOverrideDefaultUIFont(NULL
, NULL
) &&
394 system_menu_for_(system_menu_for
),
395 first_item_index_(0),
396 menu_action_(MENU_ACTION_NONE
),
397 menu_to_select_(NULL
),
398 position_to_select_(-1),
400 destroyed_flag_(NULL
),
401 menu_to_select_factory_(this) {
404 NativeMenuWin::~NativeMenuWin() {
406 *destroyed_flag_
= true;
407 STLDeleteContainerPointers(items_
.begin(), items_
.end());
411 ////////////////////////////////////////////////////////////////////////////////
412 // NativeMenuWin, MenuWrapper implementation:
414 void NativeMenuWin::RunMenuAt(const gfx::Point
& point
, int alignment
) {
417 UINT flags
= TPM_LEFTBUTTON
| TPM_RIGHTBUTTON
| TPM_RECURSE
;
418 flags
|= GetAlignmentFlags(alignment
);
419 menu_action_
= MENU_ACTION_NONE
;
421 // Set a hook function so we can listen for keyboard events while the
422 // menu is open, and store a pointer to this object in a static
423 // variable so the hook has access to it (ugly, but it's the
425 open_native_menu_win_
= this;
426 HHOOK hhook
= SetWindowsHookEx(WH_MSGFILTER
, MenuMessageHook
,
427 GetModuleHandle(NULL
), ::GetCurrentThreadId());
429 // Mark that any registered listeners have not been called for this particular
430 // opening of the menu.
431 listeners_called_
= false;
433 // Command dispatch is done through WM_MENUCOMMAND, handled by the host
435 menu_to_select_
= NULL
;
436 position_to_select_
= -1;
437 menu_to_select_factory_
.InvalidateWeakPtrs();
438 bool destroyed
= false;
439 destroyed_flag_
= &destroyed
;
440 model_
->MenuWillShow();
441 TrackPopupMenu(menu_
, flags
, point
.x(), point
.y(), 0, host_window_
->hwnd(),
443 UnhookWindowsHookEx(hhook
);
444 open_native_menu_win_
= NULL
;
447 destroyed_flag_
= NULL
;
448 if (menu_to_select_
) {
449 // Folks aren't too happy if we notify immediately. In particular, notifying
450 // the delegate can cause destruction leaving the stack in a weird
451 // state. Instead post a task, then notify. This mirrors what WM_MENUCOMMAND
453 menu_to_select_factory_
.InvalidateWeakPtrs();
454 base::MessageLoop::current()->PostTask(
456 base::Bind(&NativeMenuWin::DelayedSelect
,
457 menu_to_select_factory_
.GetWeakPtr()));
458 menu_action_
= MENU_ACTION_SELECTED
;
460 // Send MenuClosed after we schedule the select, otherwise MenuClosed is
461 // processed after the select (MenuClosed posts a delayed task too).
462 model_
->MenuClosed();
465 void NativeMenuWin::CancelMenu() {
469 void NativeMenuWin::Rebuild(MenuInsertionDelegateWin
* delegate
) {
473 owner_draw_
= model_
->HasIcons() || owner_draw_
;
474 first_item_index_
= delegate
? delegate
->GetInsertionIndex(menu_
) : 0;
475 for (int menu_index
= first_item_index_
;
476 menu_index
< first_item_index_
+ model_
->GetItemCount(); ++menu_index
) {
477 int model_index
= menu_index
- first_item_index_
;
478 if (model_
->GetTypeAt(model_index
) == ui::MenuModel::TYPE_SEPARATOR
)
479 AddSeparatorItemAt(menu_index
, model_index
);
481 AddMenuItemAt(menu_index
, model_index
);
485 void NativeMenuWin::UpdateStates() {
486 // A depth-first walk of the menu items, updating states.
488 std::vector
<ItemData
*>::const_iterator it
;
489 for (it
= items_
.begin(); it
!= items_
.end(); ++it
, ++model_index
) {
490 int menu_index
= model_index
+ first_item_index_
;
491 SetMenuItemState(menu_index
, model_
->IsEnabledAt(model_index
),
492 model_
->IsItemCheckedAt(model_index
), false);
493 if (model_
->IsItemDynamicAt(model_index
)) {
494 // TODO(atwilson): Update the icon as well (http://crbug.com/66508).
495 SetMenuItemLabel(menu_index
, model_index
,
496 model_
->GetLabelAt(model_index
));
498 Menu2
* submenu
= (*it
)->submenu
.get();
500 submenu
->UpdateStates();
504 HMENU
NativeMenuWin::GetNativeMenu() const {
508 NativeMenuWin::MenuAction
NativeMenuWin::GetMenuAction() const {
512 void NativeMenuWin::AddMenuListener(MenuListener
* listener
) {
513 listeners_
.AddObserver(listener
);
516 void NativeMenuWin::RemoveMenuListener(MenuListener
* listener
) {
517 listeners_
.RemoveObserver(listener
);
520 void NativeMenuWin::SetMinimumWidth(int width
) {
524 ////////////////////////////////////////////////////////////////////////////////
525 // NativeMenuWin, private:
528 NativeMenuWin
* NativeMenuWin::open_native_menu_win_
= NULL
;
530 void NativeMenuWin::DelayedSelect() {
532 menu_to_select_
->model_
->ActivatedAt(position_to_select_
);
536 bool NativeMenuWin::GetHighlightedMenuItemInfo(
538 HighlightedMenuItemInfo
* info
) {
539 for (int i
= 0; i
< ::GetMenuItemCount(menu
); i
++) {
540 UINT state
= ::GetMenuState(menu
, i
, MF_BYPOSITION
);
541 if (state
& MF_HILITE
) {
542 if (state
& MF_POPUP
) {
543 HMENU submenu
= GetSubMenu(menu
, i
);
544 if (GetHighlightedMenuItemInfo(submenu
, info
))
545 info
->has_parent
= true;
547 info
->has_submenu
= true;
548 } else if (!(state
& MF_SEPARATOR
) && !(state
& MF_DISABLED
)) {
549 info
->menu
= GetNativeMenuWinFromHMENU(menu
);
559 LRESULT CALLBACK
NativeMenuWin::MenuMessageHook(
560 int n_code
, WPARAM w_param
, LPARAM l_param
) {
561 LRESULT result
= CallNextHookEx(NULL
, n_code
, w_param
, l_param
);
563 NativeMenuWin
* this_ptr
= open_native_menu_win_
;
567 // The first time this hook is called, that means the menu has successfully
568 // opened, so call the callback function on all of our listeners.
569 if (!this_ptr
->listeners_called_
) {
570 FOR_EACH_OBSERVER(MenuListener
, this_ptr
->listeners_
, OnMenuOpened());
571 this_ptr
->listeners_called_
= true;
574 MSG
* msg
= reinterpret_cast<MSG
*>(l_param
);
575 if (msg
->message
== WM_LBUTTONUP
|| msg
->message
== WM_RBUTTONUP
) {
576 HighlightedMenuItemInfo info
;
577 if (GetHighlightedMenuItemInfo(this_ptr
->menu_
, &info
) && info
.menu
) {
578 // It appears that when running a menu by way of TrackPopupMenu(Ex) win32
579 // gets confused if the underlying window paints itself. As its very easy
580 // for the underlying window to repaint itself (especially since some menu
581 // items trigger painting of the tabstrip on mouse over) we have this
582 // workaround. When the mouse is released on a menu item we remember the
583 // menu item and end the menu. When the nested message loop returns we
584 // schedule a task to notify the model. It's still possible to get a
585 // WM_MENUCOMMAND, so we have to be careful that we don't notify the model
587 this_ptr
->menu_to_select_
= info
.menu
;
588 this_ptr
->position_to_select_
= info
.position
;
591 } else if (msg
->message
== WM_KEYDOWN
) {
592 HighlightedMenuItemInfo info
;
593 if (GetHighlightedMenuItemInfo(this_ptr
->menu_
, &info
)) {
594 if (msg
->wParam
== VK_LEFT
&& !info
.has_parent
) {
595 this_ptr
->menu_action_
= MENU_ACTION_PREVIOUS
;
597 } else if (msg
->wParam
== VK_RIGHT
&& !info
.has_parent
&&
599 this_ptr
->menu_action_
= MENU_ACTION_NEXT
;
608 bool NativeMenuWin::IsSeparatorItemAt(int menu_index
) const {
609 MENUITEMINFO mii
= {0};
610 mii
.cbSize
= sizeof(mii
);
611 mii
.fMask
= MIIM_FTYPE
;
612 GetMenuItemInfo(menu_
, menu_index
, MF_BYPOSITION
, &mii
);
613 return !!(mii
.fType
& MF_SEPARATOR
);
616 void NativeMenuWin::AddMenuItemAt(int menu_index
, int model_index
) {
617 MENUITEMINFO mii
= {0};
618 mii
.cbSize
= sizeof(mii
);
619 mii
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_DATA
;
621 mii
.fType
= MFT_STRING
;
623 mii
.fType
= MFT_OWNERDRAW
;
625 ItemData
* item_data
= new ItemData
;
626 item_data
->label
= base::string16();
627 ui::MenuModel::ItemType type
= model_
->GetTypeAt(model_index
);
628 if (type
== ui::MenuModel::TYPE_SUBMENU
) {
629 item_data
->submenu
.reset(new Menu2(model_
->GetSubmenuModelAt(model_index
)));
630 mii
.fMask
|= MIIM_SUBMENU
;
631 mii
.hSubMenu
= item_data
->submenu
->GetNativeMenu();
632 GetNativeMenuWinFromHMENU(mii
.hSubMenu
)->parent_
= this;
634 if (type
== ui::MenuModel::TYPE_RADIO
)
635 mii
.fType
|= MFT_RADIOCHECK
;
636 mii
.wID
= model_
->GetCommandIdAt(model_index
);
638 item_data
->native_menu_win
= this;
639 item_data
->model_index
= model_index
;
640 items_
.insert(items_
.begin() + model_index
, item_data
);
641 mii
.dwItemData
= reinterpret_cast<ULONG_PTR
>(item_data
);
642 UpdateMenuItemInfoForString(&mii
, model_index
,
643 model_
->GetLabelAt(model_index
));
644 InsertMenuItem(menu_
, menu_index
, TRUE
, &mii
);
647 void NativeMenuWin::AddSeparatorItemAt(int menu_index
, int model_index
) {
648 MENUITEMINFO mii
= {0};
649 mii
.cbSize
= sizeof(mii
);
650 mii
.fMask
= MIIM_FTYPE
;
651 mii
.fType
= MFT_SEPARATOR
;
652 // Insert a dummy entry into our label list so we can index directly into it
653 // using item indices if need be.
654 items_
.insert(items_
.begin() + model_index
, new ItemData
);
655 InsertMenuItem(menu_
, menu_index
, TRUE
, &mii
);
658 void NativeMenuWin::SetMenuItemState(int menu_index
, bool enabled
, bool checked
,
660 if (IsSeparatorItemAt(menu_index
))
663 UINT state
= enabled
? MFS_ENABLED
: MFS_DISABLED
;
665 state
|= MFS_CHECKED
;
667 state
|= MFS_DEFAULT
;
669 MENUITEMINFO mii
= {0};
670 mii
.cbSize
= sizeof(mii
);
671 mii
.fMask
= MIIM_STATE
;
673 SetMenuItemInfo(menu_
, menu_index
, MF_BYPOSITION
, &mii
);
676 void NativeMenuWin::SetMenuItemLabel(int menu_index
,
678 const base::string16
& label
) {
679 if (IsSeparatorItemAt(menu_index
))
682 MENUITEMINFO mii
= {0};
683 mii
.cbSize
= sizeof(mii
);
684 UpdateMenuItemInfoForString(&mii
, model_index
, label
);
685 SetMenuItemInfo(menu_
, menu_index
, MF_BYPOSITION
, &mii
);
688 void NativeMenuWin::UpdateMenuItemInfoForString(MENUITEMINFO
* mii
,
690 const base::string16
& label
) {
691 base::string16 formatted
= label
;
692 ui::MenuModel::ItemType type
= model_
->GetTypeAt(model_index
);
693 // Strip out any tabs, otherwise they get interpreted as accelerators and can
694 // lead to weird behavior.
695 base::ReplaceSubstringsAfterOffset(&formatted
, 0, L
"\t", L
" ");
696 if (type
!= ui::MenuModel::TYPE_SUBMENU
) {
697 // Add accelerator details to the label if provided.
698 ui::Accelerator
accelerator(ui::VKEY_UNKNOWN
, ui::EF_NONE
);
699 if (model_
->GetAcceleratorAt(model_index
, &accelerator
)) {
701 formatted
+= accelerator
.GetShortcutText();
705 // Update the owned string, since Windows will want us to keep this new
707 items_
[model_index
]->label
= formatted
;
709 // Give Windows a pointer to the label string.
710 mii
->fMask
|= MIIM_STRING
;
712 const_cast<wchar_t*>(items_
[model_index
]->label
.c_str());
715 UINT
NativeMenuWin::GetAlignmentFlags(int alignment
) const {
716 UINT alignment_flags
= TPM_TOPALIGN
;
717 if (alignment
== Menu2::ALIGN_TOPLEFT
)
718 alignment_flags
|= TPM_LEFTALIGN
;
719 else if (alignment
== Menu2::ALIGN_TOPRIGHT
)
720 alignment_flags
|= TPM_RIGHTALIGN
;
721 return alignment_flags
;
724 void NativeMenuWin::ResetNativeMenu() {
725 if (IsWindow(system_menu_for_
)) {
727 GetSystemMenu(system_menu_for_
, TRUE
);
728 menu_
= GetSystemMenu(system_menu_for_
, FALSE
);
732 menu_
= CreatePopupMenu();
733 // Rather than relying on the return value of TrackPopupMenuEx, which is
734 // always a command identifier, instead we tell the menu to notify us via
735 // our host window and the WM_MENUCOMMAND message.
737 mi
.cbSize
= sizeof(mi
);
738 mi
.fMask
= MIM_STYLE
| MIM_MENUDATA
;
739 mi
.dwStyle
= MNS_NOTIFYBYPOS
;
740 mi
.dwMenuData
= reinterpret_cast<ULONG_PTR
>(this);
741 SetMenuInfo(menu_
, &mi
);
745 void NativeMenuWin::CreateHostWindow() {
746 // This only gets called from RunMenuAt, and as such there is only ever one
747 // host window per menu hierarchy, no matter how many NativeMenuWin objects
748 // exist wrapping submenus.
749 if (!host_window_
.get())
750 host_window_
.reset(new MenuHostWindow(this));
753 ////////////////////////////////////////////////////////////////////////////////
754 // MenuWrapper, public:
757 MenuWrapper
* MenuWrapper::CreateWrapper(ui::MenuModel
* model
) {
758 return new NativeMenuWin(model
, NULL
);