blob: 5e574f9b4c720e735f4c13ee83fb5791bfe1810c [file] [log] [blame]
// Copyright 2020 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/base/models/dialog_model.h"
#include <algorithm>
#include <memory>
#include <variant>
#include <vector>
#include "base/functional/callback_helpers.h"
#include "base/notreached.h"
#include "third_party/abseil-cpp/absl/functional/overload.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/models/dialog_model_field.h"
#include "ui/base/mojom/dialog_button.mojom.h"
#include "ui/base/ui_base_types.h"
namespace ui {
DialogModel::Button::Params::Params() = default;
DialogModel::Button::Params::~Params() = default;
DialogModel::Button::Params& DialogModel::Button::Params::SetId(
ElementIdentifier id) {
CHECK(!id_);
CHECK(id);
id_ = id;
return *this;
}
DialogModel::Button::Params& DialogModel::Button::Params::SetLabel(
std::u16string label) {
CHECK(label_.empty());
CHECK(!label.empty());
label_ = label;
return *this;
}
DialogModel::Button::Params& DialogModel::Button::Params::SetStyle(
std::optional<ButtonStyle> style) {
CHECK(style_ != style);
style_ = style;
return *this;
}
DialogModel::Button::Params& DialogModel::Button::Params::SetEnabled(
bool is_enabled) {
is_enabled_ = is_enabled;
return *this;
}
DialogModel::Button::Params& DialogModel::Button::Params::AddAccelerator(
Accelerator accelerator) {
accelerators_.insert(std::move(accelerator));
return *this;
}
DialogModel::Button::Button(
base::RepeatingCallback<void(const Event&)> callback,
const DialogModel::Button::Params& params)
: DialogModelField(kCustom, params.id_, params.accelerators_, params),
label_(params.label_),
style_(params.style_),
is_enabled_(params.is_enabled_),
callback_(std::move(callback)) {
CHECK(callback_);
}
DialogModel::Button::~Button() = default;
void DialogModel::Button::OnPressed(base::PassKey<DialogModelHost>,
const Event& event) {
callback_.Run(event);
}
DialogModel::Builder::Builder(std::unique_ptr<DialogModelDelegate> delegate)
: model_(std::make_unique<DialogModel>(base::PassKey<Builder>(),
std::move(delegate))) {}
DialogModel::Builder::Builder() : Builder(nullptr) {}
DialogModel::Builder::~Builder() {
CHECK(!model_) << "Model should've been built.";
}
std::unique_ptr<DialogModel> DialogModel::Builder::Build() {
CHECK(model_);
return std::move(model_);
}
DialogModel::Builder& DialogModel::Builder::AddOkButton(
ButtonCallbackVariant callback,
const DialogModel::Button::Params& params) {
return AddButtonInternal(std::move(callback), params, model_->ok_button_,
model_->accept_action_callback_);
}
DialogModel::Builder& DialogModel::Builder::AddCancelButton(
ButtonCallbackVariant callback,
const DialogModel::Button::Params& params) {
return AddButtonInternal(std::move(callback), params, model_->cancel_button_,
model_->cancel_action_callback_);
}
DialogModel::Builder& DialogModel::Builder::AddButtonInternal(
ButtonCallbackVariant callback,
const DialogModel::Button::Params& params,
std::optional<ui::DialogModel::Button>& model_button,
ButtonCallbackVariant& model_callback) {
CHECK(params.is_visible_);
CHECK(!model_button.has_value());
std::visit(
absl::Overload{
[](decltype(base::DoNothing())& callback) {
// Intentional noop
},
[](base::RepeatingCallback<bool()>& callback) { CHECK(callback); },
[](base::OnceClosure& closure) { CHECK(closure); },
},
callback);
model_callback = std::move(callback);
// NOTREACHED() is used below to make sure this callback isn't used.
// DialogModelHost should be using OnDialogCanceled() instead.
model_button.emplace(base::BindRepeating([](const Event&) { NOTREACHED(); }),
params);
return *this;
}
DialogModel::Builder& DialogModel::Builder::AddExtraButton(
base::RepeatingCallback<void(const Event&)> callback,
const DialogModel::Button::Params& params) {
CHECK(params.is_visible_);
CHECK(!model_->extra_button_);
CHECK(!model_->extra_link_);
// Extra buttons are required to have labels.
CHECK(!params.label_.empty());
model_->extra_button_.emplace(std::move(callback), params);
return *this;
}
DialogModel::Builder& DialogModel::Builder::AddExtraLink(
DialogModelLabel::TextReplacement link) {
CHECK(!model_->extra_button_);
CHECK(!model_->extra_link_);
model_->extra_link_.emplace(std::move(link));
return *this;
}
DialogModel::Builder& DialogModel::Builder::OverrideDefaultButton(
mojom::DialogButton button) {
// This can only be called once.
CHECK(!model_->override_default_button_);
// Confirm the button exists.
switch (button) {
case mojom::DialogButton::kNone:
break;
case mojom::DialogButton::kOk:
CHECK(model_->ok_button_);
break;
case mojom::DialogButton::kCancel:
CHECK(model_->cancel_button_);
break;
}
model_->override_default_button_ = button;
return *this;
}
DialogModel::Builder& DialogModel::Builder::SetInitiallyFocusedField(
ElementIdentifier id) {
// This must be called with a non-null id
CHECK(id);
// This can only be called once.
CHECK(!model_->initially_focused_field_);
model_->initially_focused_field_ = id;
return *this;
}
DialogModel::DialogModel(base::PassKey<Builder>,
std::unique_ptr<DialogModelDelegate> delegate)
: delegate_(std::move(delegate)) {
if (delegate_)
delegate_->set_dialog_model(this);
}
DialogModel::~DialogModel() = default;
bool DialogModel::HasField(ElementIdentifier id) const {
return std::ranges::any_of(contents_.fields(),
[id](auto& field) {
// TODO(pbos): This does not
// work recursively yet.
CHECK_NE(field->type_,
DialogModelField::kSection);
return field->id_ == id;
}) ||
(ok_button_ && ok_button_->id_ == id) ||
(cancel_button_ && cancel_button_->id_ == id) ||
(extra_button_ && extra_button_->id_ == id);
}
DialogModelField* DialogModel::GetFieldByUniqueId(ElementIdentifier id) {
// TODO(pbos): Make sure buttons aren't accessed through GetFieldByUniqueId.
// Then make this simply forward to contents_.
if (Button* const button = MaybeGetButtonByUniqueId(id)) {
return button;
}
return contents_.GetFieldByUniqueId(id);
}
DialogModel::Button* DialogModel::GetButtonByUniqueId(ElementIdentifier id) {
Button* const button = MaybeGetButtonByUniqueId(id);
CHECK(button);
return button;
}
DialogModel::Button* DialogModel::MaybeGetButtonByUniqueId(
ElementIdentifier id) {
if (ok_button_ && ok_button_->id_ == id) {
return &ok_button_.value();
}
if (cancel_button_ && cancel_button_->id_ == id) {
return &cancel_button_.value();
}
if (extra_button_ && extra_button_->id_ == id) {
return &extra_button_.value();
}
return nullptr;
}
bool DialogModel::OnDialogAcceptAction(base::PassKey<DialogModelHost>) {
return RunButtonCallback(accept_action_callback_);
}
bool DialogModel::OnDialogCancelAction(base::PassKey<DialogModelHost>) {
return RunButtonCallback(cancel_action_callback_);
}
bool DialogModel::RunButtonCallback(ButtonCallbackVariant& callback_variant) {
return std::visit(
absl::Overload{
[](decltype(base::DoNothing())& callback) { return true; },
[](base::RepeatingCallback<bool()>& callback) {
return callback.Run();
},
[](base::OnceClosure& callback) {
CHECK(callback);
std::move(callback).Run();
return true;
},
},
callback_variant);
}
void DialogModel::OnDialogCloseAction(base::PassKey<DialogModelHost>) {
if (close_action_callback_)
std::move(close_action_callback_).Run();
}
void DialogModel::OnDialogDestroying(base::PassKey<DialogModelHost>) {
if (dialog_destroying_callback_)
std::move(dialog_destroying_callback_).Run();
}
void DialogModel::SetVisible(ElementIdentifier id, bool visible) {
// TODO(pbos): Consider a different method for dialog buttons vs. contents.
if (Button* button = MaybeGetButtonByUniqueId(id)) {
button->SetVisible(visible);
if (host_) {
host_->OnDialogButtonChanged();
}
return;
}
GetFieldByUniqueId(id)->SetVisible(visible);
}
void DialogModel::SetButtonLabel(DialogModel::Button* button,
const std::u16string& label) {
CHECK(button);
button->label_ = label;
if (host_) {
host_->OnDialogButtonChanged();
}
}
void DialogModel::SetButtonEnabled(DialogModel::Button* button, bool enabled) {
CHECK(button);
button->is_enabled_ = enabled;
if (host_) {
host_->OnDialogButtonChanged();
}
}
} // namespace ui