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 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
350 tracked_objects::ScopedTracker
tracking_profile(
351 FROM_HERE_WITH_EXPLICIT_FUNCTION("440919 MenuHostWindowProc"));
353 MenuHostWindow
* host
=
354 reinterpret_cast<MenuHostWindow
*>(gfx::GetWindowUserData(window
));
355 // host is null during initial construction.
356 LRESULT l_result
= 0;
357 if (!host
|| !host
->ProcessWindowMessage(window
, message
, w_param
, l_param
,
359 return DefWindowProc(window
, message
, w_param
, l_param
);
365 NativeMenuWin
* parent_
;
367 DISALLOW_COPY_AND_ASSIGN(MenuHostWindow
);
370 struct NativeMenuWin::HighlightedMenuItemInfo
{
371 HighlightedMenuItemInfo()
381 // The menu and position. These are only set for non-disabled menu items.
387 const wchar_t* NativeMenuWin::MenuHostWindow::kWindowClassName
=
388 L
"ViewsMenuHostWindow";
390 ////////////////////////////////////////////////////////////////////////////////
391 // NativeMenuWin, public:
393 NativeMenuWin::NativeMenuWin(ui::MenuModel
* model
, HWND system_menu_for
)
396 owner_draw_(l10n_util::NeedOverrideDefaultUIFont(NULL
, NULL
) &&
398 system_menu_for_(system_menu_for
),
399 first_item_index_(0),
400 menu_action_(MENU_ACTION_NONE
),
401 menu_to_select_(NULL
),
402 position_to_select_(-1),
404 destroyed_flag_(NULL
),
405 menu_to_select_factory_(this) {
408 NativeMenuWin::~NativeMenuWin() {
410 *destroyed_flag_
= true;
411 STLDeleteContainerPointers(items_
.begin(), items_
.end());
415 ////////////////////////////////////////////////////////////////////////////////
416 // NativeMenuWin, MenuWrapper implementation:
418 void NativeMenuWin::RunMenuAt(const gfx::Point
& point
, int alignment
) {
421 UINT flags
= TPM_LEFTBUTTON
| TPM_RIGHTBUTTON
| TPM_RECURSE
;
422 flags
|= GetAlignmentFlags(alignment
);
423 menu_action_
= MENU_ACTION_NONE
;
425 // Set a hook function so we can listen for keyboard events while the
426 // menu is open, and store a pointer to this object in a static
427 // variable so the hook has access to it (ugly, but it's the
429 open_native_menu_win_
= this;
430 HHOOK hhook
= SetWindowsHookEx(WH_MSGFILTER
, MenuMessageHook
,
431 GetModuleHandle(NULL
), ::GetCurrentThreadId());
433 // Mark that any registered listeners have not been called for this particular
434 // opening of the menu.
435 listeners_called_
= false;
437 // Command dispatch is done through WM_MENUCOMMAND, handled by the host
439 menu_to_select_
= NULL
;
440 position_to_select_
= -1;
441 menu_to_select_factory_
.InvalidateWeakPtrs();
442 bool destroyed
= false;
443 destroyed_flag_
= &destroyed
;
444 model_
->MenuWillShow();
445 TrackPopupMenu(menu_
, flags
, point
.x(), point
.y(), 0, host_window_
->hwnd(),
447 UnhookWindowsHookEx(hhook
);
448 open_native_menu_win_
= NULL
;
451 destroyed_flag_
= NULL
;
452 if (menu_to_select_
) {
453 // Folks aren't too happy if we notify immediately. In particular, notifying
454 // the delegate can cause destruction leaving the stack in a weird
455 // state. Instead post a task, then notify. This mirrors what WM_MENUCOMMAND
457 menu_to_select_factory_
.InvalidateWeakPtrs();
458 base::MessageLoop::current()->PostTask(
460 base::Bind(&NativeMenuWin::DelayedSelect
,
461 menu_to_select_factory_
.GetWeakPtr()));
462 menu_action_
= MENU_ACTION_SELECTED
;
464 // Send MenuClosed after we schedule the select, otherwise MenuClosed is
465 // processed after the select (MenuClosed posts a delayed task too).
466 model_
->MenuClosed();
469 void NativeMenuWin::CancelMenu() {
473 void NativeMenuWin::Rebuild(MenuInsertionDelegateWin
* delegate
) {
477 owner_draw_
= model_
->HasIcons() || owner_draw_
;
478 first_item_index_
= delegate
? delegate
->GetInsertionIndex(menu_
) : 0;
479 for (int menu_index
= first_item_index_
;
480 menu_index
< first_item_index_
+ model_
->GetItemCount(); ++menu_index
) {
481 int model_index
= menu_index
- first_item_index_
;
482 if (model_
->GetTypeAt(model_index
) == ui::MenuModel::TYPE_SEPARATOR
)
483 AddSeparatorItemAt(menu_index
, model_index
);
485 AddMenuItemAt(menu_index
, model_index
);
489 void NativeMenuWin::UpdateStates() {
490 // A depth-first walk of the menu items, updating states.
492 std::vector
<ItemData
*>::const_iterator it
;
493 for (it
= items_
.begin(); it
!= items_
.end(); ++it
, ++model_index
) {
494 int menu_index
= model_index
+ first_item_index_
;
495 SetMenuItemState(menu_index
, model_
->IsEnabledAt(model_index
),
496 model_
->IsItemCheckedAt(model_index
), false);
497 if (model_
->IsItemDynamicAt(model_index
)) {
498 // TODO(atwilson): Update the icon as well (http://crbug.com/66508).
499 SetMenuItemLabel(menu_index
, model_index
,
500 model_
->GetLabelAt(model_index
));
502 Menu2
* submenu
= (*it
)->submenu
.get();
504 submenu
->UpdateStates();
508 HMENU
NativeMenuWin::GetNativeMenu() const {
512 NativeMenuWin::MenuAction
NativeMenuWin::GetMenuAction() const {
516 void NativeMenuWin::AddMenuListener(MenuListener
* listener
) {
517 listeners_
.AddObserver(listener
);
520 void NativeMenuWin::RemoveMenuListener(MenuListener
* listener
) {
521 listeners_
.RemoveObserver(listener
);
524 void NativeMenuWin::SetMinimumWidth(int width
) {
528 ////////////////////////////////////////////////////////////////////////////////
529 // NativeMenuWin, private:
532 NativeMenuWin
* NativeMenuWin::open_native_menu_win_
= NULL
;
534 void NativeMenuWin::DelayedSelect() {
536 menu_to_select_
->model_
->ActivatedAt(position_to_select_
);
540 bool NativeMenuWin::GetHighlightedMenuItemInfo(
542 HighlightedMenuItemInfo
* info
) {
543 for (int i
= 0; i
< ::GetMenuItemCount(menu
); i
++) {
544 UINT state
= ::GetMenuState(menu
, i
, MF_BYPOSITION
);
545 if (state
& MF_HILITE
) {
546 if (state
& MF_POPUP
) {
547 HMENU submenu
= GetSubMenu(menu
, i
);
548 if (GetHighlightedMenuItemInfo(submenu
, info
))
549 info
->has_parent
= true;
551 info
->has_submenu
= true;
552 } else if (!(state
& MF_SEPARATOR
) && !(state
& MF_DISABLED
)) {
553 info
->menu
= GetNativeMenuWinFromHMENU(menu
);
563 LRESULT CALLBACK
NativeMenuWin::MenuMessageHook(
564 int n_code
, WPARAM w_param
, LPARAM l_param
) {
565 LRESULT result
= CallNextHookEx(NULL
, n_code
, w_param
, l_param
);
567 NativeMenuWin
* this_ptr
= open_native_menu_win_
;
571 // The first time this hook is called, that means the menu has successfully
572 // opened, so call the callback function on all of our listeners.
573 if (!this_ptr
->listeners_called_
) {
574 FOR_EACH_OBSERVER(MenuListener
, this_ptr
->listeners_
, OnMenuOpened());
575 this_ptr
->listeners_called_
= true;
578 MSG
* msg
= reinterpret_cast<MSG
*>(l_param
);
579 if (msg
->message
== WM_LBUTTONUP
|| msg
->message
== WM_RBUTTONUP
) {
580 HighlightedMenuItemInfo info
;
581 if (GetHighlightedMenuItemInfo(this_ptr
->menu_
, &info
) && info
.menu
) {
582 // It appears that when running a menu by way of TrackPopupMenu(Ex) win32
583 // gets confused if the underlying window paints itself. As its very easy
584 // for the underlying window to repaint itself (especially since some menu
585 // items trigger painting of the tabstrip on mouse over) we have this
586 // workaround. When the mouse is released on a menu item we remember the
587 // menu item and end the menu. When the nested message loop returns we
588 // schedule a task to notify the model. It's still possible to get a
589 // WM_MENUCOMMAND, so we have to be careful that we don't notify the model
591 this_ptr
->menu_to_select_
= info
.menu
;
592 this_ptr
->position_to_select_
= info
.position
;
595 } else if (msg
->message
== WM_KEYDOWN
) {
596 HighlightedMenuItemInfo info
;
597 if (GetHighlightedMenuItemInfo(this_ptr
->menu_
, &info
)) {
598 if (msg
->wParam
== VK_LEFT
&& !info
.has_parent
) {
599 this_ptr
->menu_action_
= MENU_ACTION_PREVIOUS
;
601 } else if (msg
->wParam
== VK_RIGHT
&& !info
.has_parent
&&
603 this_ptr
->menu_action_
= MENU_ACTION_NEXT
;
612 bool NativeMenuWin::IsSeparatorItemAt(int menu_index
) const {
613 MENUITEMINFO mii
= {0};
614 mii
.cbSize
= sizeof(mii
);
615 mii
.fMask
= MIIM_FTYPE
;
616 GetMenuItemInfo(menu_
, menu_index
, MF_BYPOSITION
, &mii
);
617 return !!(mii
.fType
& MF_SEPARATOR
);
620 void NativeMenuWin::AddMenuItemAt(int menu_index
, int model_index
) {
621 MENUITEMINFO mii
= {0};
622 mii
.cbSize
= sizeof(mii
);
623 mii
.fMask
= MIIM_FTYPE
| MIIM_ID
| MIIM_DATA
;
625 mii
.fType
= MFT_STRING
;
627 mii
.fType
= MFT_OWNERDRAW
;
629 ItemData
* item_data
= new ItemData
;
630 item_data
->label
= base::string16();
631 ui::MenuModel::ItemType type
= model_
->GetTypeAt(model_index
);
632 if (type
== ui::MenuModel::TYPE_SUBMENU
) {
633 item_data
->submenu
.reset(new Menu2(model_
->GetSubmenuModelAt(model_index
)));
634 mii
.fMask
|= MIIM_SUBMENU
;
635 mii
.hSubMenu
= item_data
->submenu
->GetNativeMenu();
636 GetNativeMenuWinFromHMENU(mii
.hSubMenu
)->parent_
= this;
638 if (type
== ui::MenuModel::TYPE_RADIO
)
639 mii
.fType
|= MFT_RADIOCHECK
;
640 mii
.wID
= model_
->GetCommandIdAt(model_index
);
642 item_data
->native_menu_win
= this;
643 item_data
->model_index
= model_index
;
644 items_
.insert(items_
.begin() + model_index
, item_data
);
645 mii
.dwItemData
= reinterpret_cast<ULONG_PTR
>(item_data
);
646 UpdateMenuItemInfoForString(&mii
, model_index
,
647 model_
->GetLabelAt(model_index
));
648 InsertMenuItem(menu_
, menu_index
, TRUE
, &mii
);
651 void NativeMenuWin::AddSeparatorItemAt(int menu_index
, int model_index
) {
652 MENUITEMINFO mii
= {0};
653 mii
.cbSize
= sizeof(mii
);
654 mii
.fMask
= MIIM_FTYPE
;
655 mii
.fType
= MFT_SEPARATOR
;
656 // Insert a dummy entry into our label list so we can index directly into it
657 // using item indices if need be.
658 items_
.insert(items_
.begin() + model_index
, new ItemData
);
659 InsertMenuItem(menu_
, menu_index
, TRUE
, &mii
);
662 void NativeMenuWin::SetMenuItemState(int menu_index
, bool enabled
, bool checked
,
664 if (IsSeparatorItemAt(menu_index
))
667 UINT state
= enabled
? MFS_ENABLED
: MFS_DISABLED
;
669 state
|= MFS_CHECKED
;
671 state
|= MFS_DEFAULT
;
673 MENUITEMINFO mii
= {0};
674 mii
.cbSize
= sizeof(mii
);
675 mii
.fMask
= MIIM_STATE
;
677 SetMenuItemInfo(menu_
, menu_index
, MF_BYPOSITION
, &mii
);
680 void NativeMenuWin::SetMenuItemLabel(int menu_index
,
682 const base::string16
& label
) {
683 if (IsSeparatorItemAt(menu_index
))
686 MENUITEMINFO mii
= {0};
687 mii
.cbSize
= sizeof(mii
);
688 UpdateMenuItemInfoForString(&mii
, model_index
, label
);
689 SetMenuItemInfo(menu_
, menu_index
, MF_BYPOSITION
, &mii
);
692 void NativeMenuWin::UpdateMenuItemInfoForString(MENUITEMINFO
* mii
,
694 const base::string16
& label
) {
695 base::string16 formatted
= label
;
696 ui::MenuModel::ItemType type
= model_
->GetTypeAt(model_index
);
697 // Strip out any tabs, otherwise they get interpreted as accelerators and can
698 // lead to weird behavior.
699 ReplaceSubstringsAfterOffset(&formatted
, 0, L
"\t", L
" ");
700 if (type
!= ui::MenuModel::TYPE_SUBMENU
) {
701 // Add accelerator details to the label if provided.
702 ui::Accelerator
accelerator(ui::VKEY_UNKNOWN
, ui::EF_NONE
);
703 if (model_
->GetAcceleratorAt(model_index
, &accelerator
)) {
705 formatted
+= accelerator
.GetShortcutText();
709 // Update the owned string, since Windows will want us to keep this new
711 items_
[model_index
]->label
= formatted
;
713 // Give Windows a pointer to the label string.
714 mii
->fMask
|= MIIM_STRING
;
716 const_cast<wchar_t*>(items_
[model_index
]->label
.c_str());
719 UINT
NativeMenuWin::GetAlignmentFlags(int alignment
) const {
720 UINT alignment_flags
= TPM_TOPALIGN
;
721 if (alignment
== Menu2::ALIGN_TOPLEFT
)
722 alignment_flags
|= TPM_LEFTALIGN
;
723 else if (alignment
== Menu2::ALIGN_TOPRIGHT
)
724 alignment_flags
|= TPM_RIGHTALIGN
;
725 return alignment_flags
;
728 void NativeMenuWin::ResetNativeMenu() {
729 if (IsWindow(system_menu_for_
)) {
731 GetSystemMenu(system_menu_for_
, TRUE
);
732 menu_
= GetSystemMenu(system_menu_for_
, FALSE
);
736 menu_
= CreatePopupMenu();
737 // Rather than relying on the return value of TrackPopupMenuEx, which is
738 // always a command identifier, instead we tell the menu to notify us via
739 // our host window and the WM_MENUCOMMAND message.
741 mi
.cbSize
= sizeof(mi
);
742 mi
.fMask
= MIM_STYLE
| MIM_MENUDATA
;
743 mi
.dwStyle
= MNS_NOTIFYBYPOS
;
744 mi
.dwMenuData
= reinterpret_cast<ULONG_PTR
>(this);
745 SetMenuInfo(menu_
, &mi
);
749 void NativeMenuWin::CreateHostWindow() {
750 // This only gets called from RunMenuAt, and as such there is only ever one
751 // host window per menu hierarchy, no matter how many NativeMenuWin objects
752 // exist wrapping submenus.
753 if (!host_window_
.get())
754 host_window_
.reset(new MenuHostWindow(this));
757 ////////////////////////////////////////////////////////////////////////////////
758 // MenuWrapper, public:
761 MenuWrapper
* MenuWrapper::CreateWrapper(ui::MenuModel
* model
) {
762 return new NativeMenuWin(model
, NULL
);