Home | History | Annotate | Download | only in menu
      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