#include "ui/views/controls/menu/native_menu_win.h"
#include <utility>
#include "base/check.h"
#include "base/memory/raw_ptr.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 {
std::u16string label;
std::unique_ptr<NativeMenuWin> submenu;
raw_ptr<NativeMenuWin> native_menu_win;
size_t model_index;
};
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::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_);
}
void NativeMenuWin::Rebuild(MenuInsertionDelegateWin* delegate) {
ResetNativeMenu();
items_.clear();
owner_draw_ = model_->HasIcons() || owner_draw_;
first_item_index_ = delegate ? delegate->GetInsertionIndex(menu_) : size_t{0};
for (size_t model_index = 0; model_index < model_->GetItemCount();
++model_index) {
size_t 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() {
size_t model_index = 0;
for (const auto& item : items_) {
size_t menu_index = model_index + first_item_index_;
SetMenuItemState(menu_index, model_->IsEnabledAt(model_index),
model_->IsItemCheckedAt(model_index), false);
if (model_->IsItemDynamicAt(model_index)) {
SetMenuItemLabel(menu_index, model_index,
model_->GetLabelAt(model_index));
}
NativeMenuWin* submenu = item->submenu.get();
if (submenu)
submenu->UpdateStates();
++model_index;
}
}
bool NativeMenuWin::IsSeparatorItemAt(size_t 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(size_t menu_index, size_t 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 = static_cast<UINT>(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() + static_cast<ptrdiff_t>(model_index),
std::move(item_data));
UpdateMenuItemInfoForString(&mii, model_index,
model_->GetLabelAt(model_index));
InsertMenuItem(menu_, menu_index, TRUE, &mii);
}
void NativeMenuWin::AddSeparatorItemAt(size_t menu_index, size_t model_index) {
MENUITEMINFO mii = {0};
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_FTYPE;
mii.fType = MFT_SEPARATOR;
items_.insert(items_.begin() + static_cast<ptrdiff_t>(model_index),
std::make_unique<ItemData>());
InsertMenuItem(menu_, static_cast<UINT>(menu_index), TRUE, &mii);
}
void NativeMenuWin::SetMenuItemState(size_t 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_, static_cast<UINT>(menu_index), MF_BYPOSITION, &mii);
}
void NativeMenuWin::SetMenuItemLabel(size_t menu_index,
size_t 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_, static_cast<UINT>(menu_index), MF_BYPOSITION, &mii);
}
void NativeMenuWin::UpdateMenuItemInfoForString(MENUITEMINFO* mii,
size_t model_index,
const std::u16string& label) {
std::u16string formatted = label;
ui::MenuModel::ItemType type = model_->GetTypeAt(model_index);
base::ReplaceSubstringsAfterOffset(&formatted, 0, u"\t", u" ");
if (type != ui::MenuModel::TYPE_SUBMENU) {
ui::Accelerator accelerator(ui::VKEY_UNKNOWN, ui::EF_NONE);
if (model_->GetAcceleratorAt(model_index, &accelerator)) {
formatted += u"\t";
formatted += accelerator.GetShortcutText();
}
}
items_[model_index]->label = formatted;
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();
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);
}
}
}