blob: 9f56505d9502c685f66ab082b60eaae4011e76b7 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/menus/simple_menu_model.h"
#include <stddef.h>
#include <utility>
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/task/single_thread_task_runner.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/image_model.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/vector_icon_types.h"
namespace ui {
const int kSeparatorId = -1;
////////////////////////////////////////////////////////////////////////////////
// SimpleMenuModel::Delegate, public:
bool SimpleMenuModel::Delegate::IsCommandIdChecked(int command_id) const {
return false;
}
bool SimpleMenuModel::Delegate::IsCommandIdEnabled(int command_id) const {
return true;
}
bool SimpleMenuModel::Delegate::IsCommandIdVisible(int command_id) const {
return true;
}
bool SimpleMenuModel::Delegate::IsCommandIdAlerted(int command_id) const {
return false;
}
bool SimpleMenuModel::Delegate::IsElementIdAlerted(
ui::ElementIdentifier element_id) const {
return false;
}
bool SimpleMenuModel::Delegate::IsItemForCommandIdDynamic(
int command_id) const {
return false;
}
std::u16string SimpleMenuModel::Delegate::GetLabelForCommandId(
int command_id) const {
return std::u16string();
}
ImageModel SimpleMenuModel::Delegate::GetIconForCommandId(
int command_id) const {
return ImageModel();
}
void SimpleMenuModel::Delegate::OnMenuWillShow(SimpleMenuModel* /*source*/) {}
void SimpleMenuModel::Delegate::MenuClosed(SimpleMenuModel* /*source*/) {}
bool SimpleMenuModel::Delegate::GetAcceleratorForCommandId(
int command_id,
ui::Accelerator* accelerator) const {
return false;
}
////////////////////////////////////////////////////////////////////////////////
// SimpleMenuModel, public:
SimpleMenuModel::SimpleMenuModel(Delegate* delegate) : delegate_(delegate) {}
SimpleMenuModel::~SimpleMenuModel() = default;
void SimpleMenuModel::AddItem(int command_id, const std::u16string& label) {
AppendItem(Item(command_id, TYPE_COMMAND, label));
}
void SimpleMenuModel::AddItemWithStringId(int command_id, int string_id) {
// Prevent this dangerous pattern:
// model->AddItemWithStringId(IDS_FOO, IDS_FOO);
// This conflates string IDs with command IDs, which are separate namespaces.
// Sometimes this is an accident where this is meant:
// model->AddItemWithStringId(IDC_FOO, IDS_FOO);
// but sometimes it is deliberate, usually in situations where there is no
// matching IDC constant or the matching IDC constant is not available.
// Using IDS constants for command IDs can cause confusion elsewhere, since
// command IDs are usually either IDC values or strictly local constants.
DCHECK_NE(command_id, string_id);
AddItem(command_id, l10n_util::GetStringUTF16(string_id));
}
void SimpleMenuModel::AddItemWithIcon(int command_id,
const std::u16string& label,
const ImageModel& icon) {
Item item(command_id, TYPE_COMMAND, label);
item.icon = icon;
AppendItem(std::move(item));
}
void SimpleMenuModel::AddItemWithStringIdAndIcon(int command_id,
int string_id,
const ImageModel& icon) {
AddItemWithIcon(command_id, l10n_util::GetStringUTF16(string_id), icon);
}
void SimpleMenuModel::AddCheckItem(int command_id,
const std::u16string& label) {
AppendItem(Item(command_id, TYPE_CHECK, label));
}
void SimpleMenuModel::AddCheckItemWithStringId(int command_id, int string_id) {
AddCheckItem(command_id, l10n_util::GetStringUTF16(string_id));
}
void SimpleMenuModel::AddRadioItem(int command_id,
const std::u16string& label,
int group_id) {
Item item(command_id, TYPE_RADIO, label);
item.group_id = group_id;
AppendItem(std::move(item));
}
void SimpleMenuModel::AddRadioItemWithStringId(int command_id,
int string_id,
int group_id) {
AddRadioItem(command_id, l10n_util::GetStringUTF16(string_id), group_id);
}
void SimpleMenuModel::AddHighlightedItemWithIcon(int command_id,
const std::u16string& label,
const ImageModel& icon) {
Item item(command_id, TYPE_HIGHLIGHTED, label);
item.icon = icon;
AppendItem(std::move(item));
}
void SimpleMenuModel::AddTitle(const std::u16string& label) {
Item item(kTitleId, TYPE_TITLE, label);
// Titles are non-interactive and should not be enabled.
item.enabled = false;
AppendItem(std::move(item));
}
void SimpleMenuModel::AddTitleWithStringId(int string_id) {
AddTitle(l10n_util::GetStringUTF16(string_id));
}
void SimpleMenuModel::AddSeparator(MenuSeparatorType separator_type) {
if (items_.empty()) {
if (separator_type == NORMAL_SEPARATOR) {
return;
}
DCHECK_EQ(SPACING_SEPARATOR, separator_type);
} else {
size_t last_visible_item = items_.size();
for (auto i = items_.size(); i > 0; i--) {
if (IsVisibleAt(i - 1)) {
last_visible_item = i - 1;
break;
}
}
if (last_visible_item == items_.size()) {
// No visible items. Don't add a separator.
return;
}
if (items_.at(last_visible_item).type == TYPE_SEPARATOR) {
DCHECK_EQ(NORMAL_SEPARATOR, separator_type);
DCHECK_EQ(NORMAL_SEPARATOR, items_.at(last_visible_item).separator_type);
// The last item is already a separator. Don't add another.
return;
}
}
#if !defined(USE_AURA)
if (separator_type == SPACING_SEPARATOR) {
NOTIMPLEMENTED();
}
#endif
Item item(kSeparatorId, TYPE_SEPARATOR, std::u16string());
item.separator_type = separator_type;
AppendItem(std::move(item));
}
void SimpleMenuModel::AddButtonItem(int command_id,
ButtonMenuItemModel* model) {
Item item(command_id, TYPE_BUTTON_ITEM, std::u16string());
item.button_model = model;
AppendItem(std::move(item));
}
void SimpleMenuModel::AddSubMenu(int command_id,
const std::u16string& label,
MenuModel* model) {
Item item(command_id, TYPE_SUBMENU, label);
item.submenu = model;
AppendItem(std::move(item));
}
void SimpleMenuModel::AddSubMenuWithStringId(int command_id,
int string_id,
MenuModel* model) {
AddSubMenu(command_id, l10n_util::GetStringUTF16(string_id), model);
}
void SimpleMenuModel::AddSubMenuWithIcon(int command_id,
const std::u16string& label,
MenuModel* model,
const ImageModel& icon) {
Item item(command_id, TYPE_SUBMENU, label);
item.submenu = model;
item.icon = icon;
AppendItem(std::move(item));
}
void SimpleMenuModel::AddSubMenuWithStringIdAndIcon(int command_id,
int string_id,
MenuModel* model,
const ImageModel& icon) {
Item item(command_id, TYPE_SUBMENU, l10n_util::GetStringUTF16(string_id));
item.submenu = model;
item.icon = icon;
AppendItem(std::move(item));
}
void SimpleMenuModel::AddActionableSubMenu(int command_id,
const std::u16string& label,
MenuModel* model) {
Item item(command_id, TYPE_ACTIONABLE_SUBMENU, label);
item.submenu = model;
AppendItem(std::move(item));
}
void SimpleMenuModel::AddActionableSubmenuWithStringIdAndIcon(
int command_id,
int string_id,
MenuModel* model,
const ImageModel& icon) {
Item item(command_id, TYPE_ACTIONABLE_SUBMENU,
l10n_util::GetStringUTF16(string_id));
item.submenu = model;
item.icon = icon;
AppendItem(std::move(item));
}
void SimpleMenuModel::InsertItemAt(size_t index,
int command_id,
const std::u16string& label) {
InsertItemAtIndex(Item(command_id, TYPE_COMMAND, label), index);
}
void SimpleMenuModel::InsertItemWithStringIdAt(size_t index,
int command_id,
int string_id) {
InsertItemAt(index, command_id, l10n_util::GetStringUTF16(string_id));
}
void SimpleMenuModel::InsertCheckItemAt(size_t index,
int command_id,
const std::u16string& label) {
InsertItemAtIndex(Item(command_id, TYPE_CHECK, label), index);
}
void SimpleMenuModel::InsertCheckItemWithStringIdAt(size_t index,
int command_id,
int string_id) {
InsertCheckItemAt(index, command_id, l10n_util::GetStringUTF16(string_id));
}
void SimpleMenuModel::InsertRadioItemAt(size_t index,
int command_id,
const std::u16string& label,
int group_id) {
Item item(command_id, TYPE_RADIO, label);
item.group_id = group_id;
InsertItemAtIndex(std::move(item), index);
}
void SimpleMenuModel::InsertRadioItemWithStringIdAt(size_t index,
int command_id,
int string_id,
int group_id) {
InsertRadioItemAt(index, command_id, l10n_util::GetStringUTF16(string_id),
group_id);
}
void SimpleMenuModel::InsertTitleWithStringIdAt(size_t index, int string_id) {
Item item(kTitleId, TYPE_TITLE, l10n_util::GetStringUTF16(string_id));
// Titles are non-interactive and should not be enabled.
item.enabled = false;
InsertItemAtIndex(std::move(item), index);
}
void SimpleMenuModel::InsertSeparatorAt(size_t index,
MenuSeparatorType separator_type) {
#if !defined(USE_AURA)
if (separator_type != NORMAL_SEPARATOR) {
NOTIMPLEMENTED();
}
#endif
Item item(kSeparatorId, TYPE_SEPARATOR, std::u16string());
item.separator_type = separator_type;
InsertItemAtIndex(std::move(item), index);
}
void SimpleMenuModel::InsertSubMenuAt(size_t index,
int command_id,
const std::u16string& label,
MenuModel* model) {
Item item(command_id, TYPE_SUBMENU, label);
item.submenu = model;
InsertItemAtIndex(std::move(item), index);
}
void SimpleMenuModel::InsertSubMenuWithStringIdAt(size_t index,
int command_id,
int string_id,
MenuModel* model) {
InsertSubMenuAt(index, command_id, l10n_util::GetStringUTF16(string_id),
model);
}
void SimpleMenuModel::RemoveItemAt(size_t index) {
items_.erase(items_.begin() +
static_cast<ptrdiff_t>(ValidateItemIndex(index)));
MenuItemsChanged();
}
void SimpleMenuModel::SetIcon(size_t index, const ui::ImageModel& icon) {
items_[ValidateItemIndex(index)].icon = icon;
MenuItemsChanged();
}
void SimpleMenuModel::SetLabel(size_t index, const std::u16string& label) {
items_[ValidateItemIndex(index)].label = label;
MenuItemsChanged();
}
void SimpleMenuModel::SetAcceleratorAt(size_t index,
const ui::Accelerator& accelerator) {
items_[ValidateItemIndex(index)].accelerator = accelerator;
MenuItemsChanged();
}
void SimpleMenuModel::SetMinorText(size_t index,
const std::u16string& minor_text) {
items_[ValidateItemIndex(index)].minor_text = minor_text;
}
void SimpleMenuModel::SetMinorIcon(size_t index,
const ui::ImageModel& minor_icon) {
items_[ValidateItemIndex(index)].minor_icon = minor_icon;
}
void SimpleMenuModel::SetEnabledAt(size_t index, bool enabled) {
if (items_[ValidateItemIndex(index)].enabled == enabled) {
return;
}
items_[index].enabled = enabled;
MenuItemsChanged();
}
void SimpleMenuModel::SetVisibleAt(size_t index, bool visible) {
if (items_[ValidateItemIndex(index)].visible == visible) {
return;
}
items_[index].visible = visible;
MenuItemsChanged();
}
void SimpleMenuModel::SetIsNewFeatureAt(size_t index,
IsNewFeatureAtValue is_new_feature) {
items_[ValidateItemIndex(index)].is_new_feature = is_new_feature;
}
void SimpleMenuModel::SetMayHaveMnemonicsAt(size_t index,
bool may_have_mnemonics) {
items_[ValidateItemIndex(index)].may_have_mnemonics = may_have_mnemonics;
}
void SimpleMenuModel::SetAccessibleNameAt(size_t index,
std::u16string accessible_name) {
items_[ValidateItemIndex(index)].accessible_name = std::move(accessible_name);
}
void SimpleMenuModel::SetElementIdentifierAt(size_t index,
ElementIdentifier unique_id) {
items_[ValidateItemIndex(index)].unique_id = unique_id;
}
void SimpleMenuModel::SetExecuteCallbackAt(
size_t index,
base::RepeatingCallback<void(int)> callback) {
items_[ValidateItemIndex(index)].on_execute_callback = callback;
}
void SimpleMenuModel::Clear() {
items_.clear();
MenuItemsChanged();
}
std::optional<size_t> SimpleMenuModel::GetIndexOfCommandId(
int command_id) const {
for (auto i = items_.begin(); i != items_.end(); ++i) {
if (i->command_id == command_id) {
return static_cast<size_t>(std::distance(items_.begin(), i));
}
}
return std::nullopt;
}
void SimpleMenuModel::SetForceShowAcceleratorForItemAt(
size_t index,
bool force_show_accelerator_for_item) {
items_[ValidateItemIndex(index)].force_show_accelerator_for_item =
force_show_accelerator_for_item;
}
////////////////////////////////////////////////////////////////////////////////
// SimpleMenuModel, MenuModel implementation:
base::WeakPtr<ui::MenuModel> SimpleMenuModel::AsWeakPtr() {
return method_factory_.GetWeakPtr();
}
size_t SimpleMenuModel::GetItemCount() const {
return items_.size();
}
MenuModel::ItemType SimpleMenuModel::GetTypeAt(size_t index) const {
return items_[ValidateItemIndex(index)].type;
}
ui::MenuSeparatorType SimpleMenuModel::GetSeparatorTypeAt(size_t index) const {
return items_[ValidateItemIndex(index)].separator_type;
}
int SimpleMenuModel::GetCommandIdAt(size_t index) const {
return items_[ValidateItemIndex(index)].command_id;
}
std::u16string SimpleMenuModel::GetLabelAt(size_t index) const {
if (IsItemDynamicAt(index)) {
return delegate_->GetLabelForCommandId(GetCommandIdAt(index));
}
return items_[ValidateItemIndex(index)].label;
}
std::u16string SimpleMenuModel::GetMinorTextAt(size_t index) const {
return items_[ValidateItemIndex(index)].minor_text;
}
ImageModel SimpleMenuModel::GetMinorIconAt(size_t index) const {
return items_[ValidateItemIndex(index)].minor_icon;
}
bool SimpleMenuModel::IsItemDynamicAt(size_t index) const {
return delegate_ &&
delegate_->IsItemForCommandIdDynamic(GetCommandIdAt(index));
}
bool SimpleMenuModel::GetAcceleratorAt(size_t index,
ui::Accelerator* accelerator) const {
bool has_accelerator = false;
if (delegate_) {
has_accelerator = delegate_->GetAcceleratorForCommandId(
GetCommandIdAt(index), accelerator);
}
if (!has_accelerator) {
*accelerator = items_[ValidateItemIndex(index)].accelerator;
}
return !accelerator->IsEmpty();
}
bool SimpleMenuModel::IsItemCheckedAt(size_t index) const {
if (!delegate_) {
return false;
}
MenuModel::ItemType item_type = GetTypeAt(index);
return (item_type == TYPE_CHECK || item_type == TYPE_RADIO) &&
delegate_->IsCommandIdChecked(GetCommandIdAt(index));
}
int SimpleMenuModel::GetGroupIdAt(size_t index) const {
return items_[ValidateItemIndex(index)].group_id;
}
ImageModel SimpleMenuModel::GetIconAt(size_t index) const {
if (IsItemDynamicAt(index)) {
return delegate_->GetIconForCommandId(GetCommandIdAt(index));
}
return items_[ValidateItemIndex(index)].icon;
}
ButtonMenuItemModel* SimpleMenuModel::GetButtonMenuItemAt(size_t index) const {
return items_[ValidateItemIndex(index)].button_model;
}
bool SimpleMenuModel::IsEnabledAt(size_t index) const {
int command_id = GetCommandIdAt(index);
if (!delegate_ || command_id == kSeparatorId || command_id == kTitleId ||
GetButtonMenuItemAt(index)) {
return items_[ValidateItemIndex(index)].enabled;
}
return delegate_->IsCommandIdEnabled(command_id) &&
items_[ValidateItemIndex(index)].enabled;
}
bool SimpleMenuModel::IsVisibleAt(size_t index) const {
int command_id = GetCommandIdAt(index);
if (!delegate_ || command_id == kSeparatorId || command_id == kTitleId ||
GetButtonMenuItemAt(index)) {
return items_[ValidateItemIndex(index)].visible;
}
return delegate_->IsCommandIdVisible(command_id) &&
items_[ValidateItemIndex(index)].visible;
}
bool SimpleMenuModel::IsAlertedAt(size_t index) const {
const int command_id = GetCommandIdAt(index);
if (!delegate_ || command_id == kSeparatorId || command_id == kTitleId) {
return false;
}
// This method needs to be recursive, because if the highlighted command is
// in a submenu then the submenu item should also be highlighted.
if (auto* const submenu = GetSubmenuModelAt(index)) {
for (size_t i = 0; i < submenu->GetItemCount(); ++i) {
if (submenu->IsAlertedAt(i)) {
return true;
}
}
}
// A submenu may assign element identifiers to menu items. This
// information needs to be shared with the delegate which may want to
// highlight specific elements keyed by identifier.
const ui::ElementIdentifier element_id = GetElementIdentifierAt(index);
if (element_id && delegate_->IsElementIdAlerted(element_id)) {
return true;
}
return delegate_->IsCommandIdAlerted(command_id);
}
bool SimpleMenuModel::IsNewFeatureAt(size_t index) const {
return items_[ValidateItemIndex(index)].is_new_feature;
}
bool SimpleMenuModel::MayHaveMnemonicsAt(size_t index) const {
return items_[ValidateItemIndex(index)].may_have_mnemonics;
}
std::u16string SimpleMenuModel::GetAccessibleNameAt(size_t index) const {
return items_[ValidateItemIndex(index)].accessible_name;
}
ElementIdentifier SimpleMenuModel::GetElementIdentifierAt(size_t index) const {
return items_[ValidateItemIndex(index)].unique_id;
}
void SimpleMenuModel::ActivatedAt(size_t index) {
ActivatedAt(index, 0);
}
void SimpleMenuModel::ActivatedAt(size_t index, int event_flags) {
if (!delegate_) {
return;
}
// The delegate might be destroyed after executing the command. Hence the
// callback is temporarily copied. The delegate destruction is tested in
// MenuControllerTest.OwningDelegate.
const base::RepeatingCallback<void(int)> on_execute_callback =
items_[ValidateItemIndex(index)].on_execute_callback;
delegate_->ExecuteCommand(GetCommandIdAt(index), event_flags);
if (on_execute_callback) {
on_execute_callback.Run(event_flags);
}
}
MenuModel* SimpleMenuModel::GetSubmenuModelAt(size_t index) const {
return items_[ValidateItemIndex(index)].submenu;
}
void SimpleMenuModel::MenuWillShow() {
if (delegate_) {
delegate_->OnMenuWillShow(this);
}
}
void SimpleMenuModel::MenuWillClose() {
// Due to how menus work on the different platforms, ActivatedAt will be
// called after this. It's more convenient for the delegate to be called
// afterwards though, so post a task.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&SimpleMenuModel::OnMenuClosed,
method_factory_.GetWeakPtr()));
}
void SimpleMenuModel::OnMenuClosed() {
if (delegate_) {
delegate_->MenuClosed(this);
}
}
bool SimpleMenuModel::GetForceShowAcceleratorForItemAt(size_t index) const {
return items_[ValidateItemIndex(index)].force_show_accelerator_for_item;
}
////////////////////////////////////////////////////////////////////////////////
// SimpleMenuModel, Protected:
void SimpleMenuModel::MenuItemsChanged() {}
////////////////////////////////////////////////////////////////////////////////
// SimpleMenuModel, Private:
SimpleMenuModel::Item::Item(Item&&) = default;
SimpleMenuModel::Item::Item(int command_id, ItemType type, std::u16string label)
: command_id(command_id), type(type), label(label) {}
SimpleMenuModel::Item& SimpleMenuModel::Item::operator=(Item&&) = default;
SimpleMenuModel::Item::~Item() = default;
size_t SimpleMenuModel::ValidateItemIndex(size_t index) const {
CHECK_LT(index, items_.size());
return index;
}
void SimpleMenuModel::AppendItem(Item item) {
ValidateItem(item);
items_.push_back(std::move(item));
MenuItemsChanged();
}
void SimpleMenuModel::InsertItemAtIndex(Item item, size_t index) {
ValidateItem(item);
items_.insert(items_.begin() + static_cast<ptrdiff_t>(index),
std::move(item));
MenuItemsChanged();
}
void SimpleMenuModel::ValidateItem(const Item& item) {
#if DCHECK_IS_ON()
if (item.type == TYPE_SEPARATOR) {
DCHECK_EQ(item.command_id, kSeparatorId);
} else if (item.type == TYPE_TITLE) {
DCHECK_EQ(item.command_id, kTitleId);
} else {
DCHECK_GE(item.command_id, 0);
}
#endif // DCHECK_IS_ON()
}
} // namespace ui