// Copyright 2024 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/views/controls/menu/menu_runner_impl_remote_cocoa.h"
#include "components/remote_cocoa/browser/window.h"
#include "components/remote_cocoa/common/native_widget_ns_window.mojom.h"
#include "ui/base/models/image_model.h"
#include "ui/base/models/menu_model.h"
#include "ui/events/base_event_utils.h"
#include "ui/views/controls/menu/menu_controller_cocoa_delegate_params.h"
#include "ui/views/widget/widget.h"
namespace views {
MenuRunnerImplRemoteCocoa::MenuRunnerImplRemoteCocoa(
ui::MenuModel* menu_model,
base::RepeatingClosure on_menu_closed_callback)
: menu_model_(menu_model),
on_menu_closed_callback_(std::move(on_menu_closed_callback)) {}
void MenuRunnerImplRemoteCocoa::RunMenu(Widget* widget,
const gfx::Point& anchor,
uint64_t target_view_id) {
CHECK(!running_);
running_ = true;
// Reset fields from a potential previous run.
closing_event_time_ = base::TimeTicks();
host_receiver_.reset();
menu_remote_.reset();
menu_model_->MenuWillShow();
std::set<int> command_ids;
auto menu = remote_cocoa::mojom::ContextMenu::New(
ModelToMojo(*menu_model_, command_ids), anchor, target_view_id,
MenuControllerParamsForWidget(widget));
remote_cocoa::mojom::NativeWidgetNSWindow* remote_window =
remote_cocoa::GetWindowMojoInterface(widget->GetNativeWindow());
remote_window->DisplayContextMenu(std::move(menu),
host_receiver_.BindNewPipeAndPassRemote(),
menu_remote_.BindNewPipeAndPassReceiver());
}
void MenuRunnerImplRemoteCocoa::UpdateMenuItem(int command_id,
bool enabled,
bool hidden,
const std::u16string& title) {
menu_remote_->UpdateMenuItem(command_id, enabled, !hidden, title);
}
bool MenuRunnerImplRemoteCocoa::IsRunning() const {
return running_;
}
void MenuRunnerImplRemoteCocoa::Release() {
// No need to delay destroying this, as destroying will automatically make
// sure no more methods on this class or its menu model will be called.
if (IsRunning()) {
Cancel();
}
delete this;
}
void MenuRunnerImplRemoteCocoa::RunMenuAt(
Widget* parent,
MenuButtonController* button_controller,
const gfx::Rect& bounds,
MenuAnchorPosition anchor,
int32_t run_types,
gfx::NativeView native_view_for_gestures,
std::optional<gfx::RoundedCornersF> corners,
std::optional<std::string> show_menu_host_duration_histogram) {
RunMenu(parent, bounds.CenterPoint());
}
void MenuRunnerImplRemoteCocoa::Cancel() {
CHECK(IsRunning());
CHECK(menu_remote_.is_bound());
menu_remote_->Cancel();
}
base::TimeTicks MenuRunnerImplRemoteCocoa::GetClosingEventTime() const {
return closing_event_time_;
}
MenuRunnerImplRemoteCocoa::~MenuRunnerImplRemoteCocoa() = default;
void MenuRunnerImplRemoteCocoa::CommandActivated(int32_t command_id,
int32_t event_flags) {
ui::MenuModel* model = menu_model_;
size_t index = 0;
if (ui::MenuModel::GetModelAndIndexForCommandId(command_id, &model, &index)) {
model->ActivatedAt(index, event_flags);
}
}
void MenuRunnerImplRemoteCocoa::MenuClosed() {
closing_event_time_ = ui::EventTimeForNow();
running_ = false;
menu_model_->MenuWillClose();
if (on_menu_closed_callback_) {
std::move(on_menu_closed_callback_).Run();
}
}
std::vector<remote_cocoa::mojom::MenuItemPtr>
MenuRunnerImplRemoteCocoa::ModelToMojo(const ui::MenuModel& model,
std::set<int>& command_ids) {
std::vector<remote_cocoa::mojom::MenuItemPtr> result;
const size_t count = model.GetItemCount();
result.reserve(count);
for (size_t index = 0; index < count; ++index) {
if (model.GetTypeAt(index) == ui::MenuModel::TYPE_SEPARATOR) {
result.push_back(remote_cocoa::mojom::MenuItem::NewSeparator(
remote_cocoa::mojom::MenuItemCommonFields::New()));
} else {
auto common = remote_cocoa::mojom::MenuItemCommonFields::New();
common->command_id = model.GetCommandIdAt(index);
common->label = model.GetLabelAt(index);
common->may_have_mnemonics = model.MayHaveMnemonicsAt(index);
common->is_checked = model.IsItemCheckedAt(index);
ui::ImageModel icon = model.GetIconAt(index);
if (icon.IsImage()) {
common->icon = icon.GetImage().AsImageSkia();
}
common->is_enabled = model.IsEnabledAt(index);
common->is_visible = model.IsVisibleAt(index);
// Note that we don't check IsItemDynamicAt. A new mojom::MenuModelPtr is
// created every time the menu is shown, so there is no need for extra
// support for dynamic content.
common->is_alerted = model.IsAlertedAt(index);
common->is_new_feature = model.IsNewFeatureAt(index);
if (common->is_visible &&
model.GetTypeAt(index) == ui::MenuModel::TYPE_SUBMENU) {
ui::MenuModel* submenuModel = model.GetSubmenuModelAt(index);
auto children = ModelToMojo(*submenuModel, command_ids);
result.push_back(remote_cocoa::mojom::MenuItem::NewSubmenu(
remote_cocoa::mojom::SubmenuMenuItem::New(std::move(common),
std::move(children))));
} else {
CHECK(command_ids.insert(model.GetCommandIdAt(index)).second)
<< "Duplicate command Id in menu: " << model.GetCommandIdAt(index);
result.push_back(
remote_cocoa::mojom::MenuItem::NewRegular(std::move(common)));
}
}
}
return result;
}
} // namespace views