blob: 7e7041511850fbd113729cb1ddf985d584afbd2c [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/views/controls/menu/native_menu_win.h"
#include <utility>
#include "base/check.h"
#include "base/strings/string_util.h"
#include "base/strings/string_util_win.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/l10n/l10n_util_win.h"
#include "ui/base/models/menu_model.h"
#include "ui/views/controls/menu/menu_insertion_delegate_win.h"
namespace views {
struct NativeMenuWin::ItemData {
// The Windows API requires that whoever creates the menus must own the
// strings used for labels, and keep them around for the lifetime of the
// created menu. So be it.
std::u16string label;
// Someone needs to own submenus, it may as well be us.
std::unique_ptr<NativeMenuWin> submenu;
// We need a pointer back to the containing menu in various circumstances.
NativeMenuWin* native_menu_win;
// The index of the item within the menu's model.
int model_index;
};
// Returns the NativeMenuWin for a particular HMENU.
static NativeMenuWin* GetNativeMenuWinFromHMENU(HMENU hmenu) {
MENUINFO mi = {0};
mi.cbSize = sizeof(mi);
mi.fMask = MIM_MENUDATA | MIM_STYLE;
GetMenuInfo(hmenu, &mi);
return reinterpret_cast<NativeMenuWin*>(mi.dwMenuData);
}
////////////////////////////////////////////////////////////////////////////////
// NativeMenuWin, public:
NativeMenuWin::NativeMenuWin(ui::MenuModel* model, HWND system_menu_for)
: model_(model),
menu_(nullptr),
owner_draw_(l10n_util::NeedOverrideDefaultUIFont(nullptr, nullptr) &&
!system_menu_for),
system_menu_for_(system_menu_for),
first_item_index_(0),
parent_(nullptr) {}
NativeMenuWin::~NativeMenuWin() {
items_.clear();
DestroyMenu(menu_);
}
////////////////////////////////////////////////////////////////////////////////
// NativeMenuWin, MenuWrapper implementation:
void NativeMenuWin::Rebuild(MenuInsertionDelegateWin* delegate) {
ResetNativeMenu();
items_.clear();
owner_draw_ = model_->HasIcons() || owner_draw_;
first_item_index_ = delegate ? delegate->GetInsertionIndex(menu_) : 0;
for (int model_index = 0; model_index < model_->GetItemCount();
++model_index) {
int menu_index = model_index + first_item_index_;
if (model_->GetTypeAt(model_index) == ui::MenuModel::TYPE_SEPARATOR)
AddSeparatorItemAt(menu_index, model_index);
else
AddMenuItemAt(menu_index, model_index);
}
}
void NativeMenuWin::UpdateStates() {
// A depth-first walk of the menu items, updating states.
int model_index = 0;
for (const auto& item : items_) {
int menu_index = model_index + first_item_index_;
SetMenuItemState(menu_index, model_->IsEnabledAt(model_index),
model_->IsItemCheckedAt(model_index), false);
if (model_->IsItemDynamicAt(model_index)) {
// TODO(atwilson): Update the icon as well (http://crbug.com/66508).
SetMenuItemLabel(menu_index, model_index,
model_->GetLabelAt(model_index));
}
NativeMenuWin* submenu = item->submenu.get();
if (submenu)
submenu->UpdateStates();
++model_index;
}
}
////////////////////////////////////////////////////////////////////////////////
// NativeMenuWin, private:
bool NativeMenuWin::IsSeparatorItemAt(int menu_index) const {
MENUITEMINFO mii = {0};
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_FTYPE;
GetMenuItemInfo(menu_, menu_index, MF_BYPOSITION, &mii);
return !!(mii.fType & MF_SEPARATOR);
}
void NativeMenuWin::AddMenuItemAt(int menu_index, int model_index) {
MENUITEMINFO mii = {0};
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_FTYPE | MIIM_ID | MIIM_DATA;
if (!owner_draw_)
mii.fType = MFT_STRING;
else
mii.fType = MFT_OWNERDRAW;
std::unique_ptr<ItemData> item_data = std::make_unique<ItemData>();
item_data->label = std::u16string();
ui::MenuModel::ItemType type = model_->GetTypeAt(model_index);
if (type == ui::MenuModel::TYPE_SUBMENU) {
item_data->submenu = std::make_unique<NativeMenuWin>(
model_->GetSubmenuModelAt(model_index), nullptr);
item_data->submenu->Rebuild(nullptr);
mii.fMask |= MIIM_SUBMENU;
mii.hSubMenu = item_data->submenu->menu_;
GetNativeMenuWinFromHMENU(mii.hSubMenu)->parent_ = this;
} else {
if (type == ui::MenuModel::TYPE_RADIO)
mii.fType |= MFT_RADIOCHECK;
mii.wID = model_->GetCommandIdAt(model_index);
}
item_data->native_menu_win = this;
item_data->model_index = model_index;
mii.dwItemData = reinterpret_cast<ULONG_PTR>(item_data.get());
items_.insert(items_.begin() + model_index, std::move(item_data));
UpdateMenuItemInfoForString(&mii, model_index,
model_->GetLabelAt(model_index));
InsertMenuItem(menu_, menu_index, TRUE, &mii);
}
void NativeMenuWin::AddSeparatorItemAt(int menu_index, int model_index) {
MENUITEMINFO mii = {0};
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_FTYPE;
mii.fType = MFT_SEPARATOR;
// Insert a dummy entry into our label list so we can index directly into it
// using item indices if need be.
items_.insert(items_.begin() + model_index, std::make_unique<ItemData>());
InsertMenuItem(menu_, menu_index, TRUE, &mii);
}
void NativeMenuWin::SetMenuItemState(int menu_index,
bool enabled,
bool checked,
bool is_default) {
if (IsSeparatorItemAt(menu_index))
return;
UINT state = enabled ? MFS_ENABLED : MFS_DISABLED;
if (checked)
state |= MFS_CHECKED;
if (is_default)
state |= MFS_DEFAULT;
MENUITEMINFO mii = {0};
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_STATE;
mii.fState = state;
SetMenuItemInfo(menu_, menu_index, MF_BYPOSITION, &mii);
}
void NativeMenuWin::SetMenuItemLabel(int menu_index,
int model_index,
const std::u16string& label) {
if (IsSeparatorItemAt(menu_index))
return;
MENUITEMINFO mii = {0};
mii.cbSize = sizeof(mii);
UpdateMenuItemInfoForString(&mii, model_index, label);
SetMenuItemInfo(menu_, menu_index, MF_BYPOSITION, &mii);
}
void NativeMenuWin::UpdateMenuItemInfoForString(MENUITEMINFO* mii,
int model_index,
const std::u16string& label) {
std::u16string formatted = label;
ui::MenuModel::ItemType type = model_->GetTypeAt(model_index);
// Strip out any tabs, otherwise they get interpreted as accelerators and can
// lead to weird behavior.
base::ReplaceSubstringsAfterOffset(&formatted, 0, u"\t", u" ");
if (type != ui::MenuModel::TYPE_SUBMENU) {
// Add accelerator details to the label if provided.
ui::Accelerator accelerator(ui::VKEY_UNKNOWN, ui::EF_NONE);
if (model_->GetAcceleratorAt(model_index, &accelerator)) {
formatted += u"\t";
formatted += accelerator.GetShortcutText();
}
}
// Update the owned string, since Windows will want us to keep this new
// version around.
items_[model_index]->label = formatted;
// Give Windows a pointer to the label string.
mii->fMask |= MIIM_STRING;
mii->dwTypeData = base::as_writable_wcstr(items_[model_index]->label);
}
void NativeMenuWin::ResetNativeMenu() {
if (IsWindow(system_menu_for_)) {
if (menu_)
GetSystemMenu(system_menu_for_, TRUE);
menu_ = GetSystemMenu(system_menu_for_, FALSE);
} else {
if (menu_)
DestroyMenu(menu_);
menu_ = CreatePopupMenu();
// Rather than relying on the return value of TrackPopupMenuEx, which is
// always a command identifier, instead we tell the menu to notify us via
// our host window and the WM_MENUCOMMAND message.
MENUINFO mi = {0};
mi.cbSize = sizeof(mi);
mi.fMask = MIM_STYLE | MIM_MENUDATA;
mi.dwStyle = MNS_NOTIFYBYPOS;
mi.dwMenuData = reinterpret_cast<ULONG_PTR>(this);
SetMenuInfo(menu_, &mi);
}
}
} // namespace views