910e62b5创建于 1月15日历史提交
// 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