| // 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_field.h" |
| |
| #include <string> |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/models/dialog_model.h" |
| |
| namespace ui { |
| |
| DialogModelLabel::TextReplacement::TextReplacement(std::u16string text, |
| bool is_emphasized) |
| : text_(text), is_emphasized_(is_emphasized) {} |
| DialogModelLabel::TextReplacement::TextReplacement( |
| int message_id, |
| Callback callback, |
| std::u16string accessible_name) |
| : text_(l10n_util::GetStringUTF16(message_id)), |
| is_emphasized_(false), |
| callback_(callback), |
| accessible_name_(accessible_name) { |
| // Emphasized links are not supported, at least for now. |
| } |
| DialogModelLabel::TextReplacement::TextReplacement(const TextReplacement&) = |
| default; |
| DialogModelLabel::TextReplacement::~TextReplacement() = default; |
| |
| DialogModelLabel::DialogModelLabel(int message_id) |
| : message_id_(message_id), |
| string_(l10n_util::GetStringUTF16(message_id_)) {} |
| |
| DialogModelLabel::DialogModelLabel(int message_id, |
| std::vector<TextReplacement> replacements) |
| : message_id_(message_id), replacements_(std::move(replacements)) { |
| // Note that this constructor does not set `string_` which is invalid for |
| // labels with `replacements_`. |
| } |
| |
| DialogModelLabel::DialogModelLabel(std::u16string fixed_string) |
| : message_id_(-1), string_(std::move(fixed_string)) {} |
| |
| const std::u16string& DialogModelLabel::GetString() const { |
| CHECK(replacements_.empty()); |
| return string_; |
| } |
| |
| DialogModelLabel::DialogModelLabel(const DialogModelLabel&) = default; |
| |
| DialogModelLabel::~DialogModelLabel() = default; |
| |
| DialogModelLabel DialogModelLabel::CreateWithReplacement( |
| int message_id, |
| TextReplacement replacement) { |
| return CreateWithReplacements(message_id, {std::move(replacement)}); |
| } |
| |
| DialogModelLabel DialogModelLabel::CreateWithReplacements( |
| int message_id, |
| std::vector<TextReplacement> replacements) { |
| return DialogModelLabel(message_id, std::move(replacements)); |
| } |
| |
| DialogModelLabel::TextReplacement DialogModelLabel::CreateLink( |
| int message_id, |
| base::RepeatingClosure closure, |
| std::u16string accessible_name) { |
| return CreateLink( |
| message_id, |
| base::BindRepeating([](base::RepeatingClosure closure, |
| const Event& event) { closure.Run(); }, |
| std::move(closure)), |
| accessible_name); |
| } |
| |
| DialogModelLabel::TextReplacement DialogModelLabel::CreateLink( |
| int message_id, |
| Callback callback, |
| std::u16string accessible_name) { |
| return TextReplacement(message_id, callback, accessible_name); |
| } |
| |
| DialogModelLabel::TextReplacement DialogModelLabel::CreatePlainText( |
| std::u16string text) { |
| return TextReplacement(text); |
| } |
| |
| DialogModelLabel::TextReplacement DialogModelLabel::CreateEmphasizedText( |
| std::u16string text) { |
| return TextReplacement(text, true); |
| } |
| |
| DialogModelField::DialogModelField(Type type, |
| ElementIdentifier id, |
| base::flat_set<Accelerator> accelerators, |
| const DialogModelField::Params& params) |
| : type_(type), |
| id_(id), |
| accelerators_(std::move(accelerators)), |
| is_visible_(params.is_visible_) {} |
| |
| DialogModelField::~DialogModelField() = default; |
| |
| base::CallbackListSubscription DialogModelField::AddOnFieldChangedCallback( |
| base::RepeatingClosure on_field_changed) { |
| return on_field_changed_.Add(std::move(on_field_changed)); |
| } |
| |
| void DialogModelField::SetVisible(bool visible) { |
| is_visible_ = visible; |
| NotifyOnFieldChanged(); |
| } |
| |
| DialogModelParagraph* DialogModelField::AsParagraph() { |
| CHECK_EQ(type_, kParagraph); |
| return static_cast<DialogModelParagraph*>(this); |
| } |
| |
| DialogModelCheckbox* DialogModelField::AsCheckbox() { |
| CHECK_EQ(type_, kCheckbox); |
| return static_cast<DialogModelCheckbox*>(this); |
| } |
| |
| DialogModelCombobox* DialogModelField::AsCombobox() { |
| CHECK_EQ(type_, kCombobox); |
| return static_cast<DialogModelCombobox*>(this); |
| } |
| |
| DialogModelMenuItem* DialogModelField::AsMenuItem() { |
| return const_cast<DialogModelMenuItem*>(std::as_const(*this).AsMenuItem()); |
| } |
| |
| const DialogModelMenuItem* DialogModelField::AsMenuItem() const { |
| CHECK_EQ(type_, kMenuItem); |
| return static_cast<const DialogModelMenuItem*>(this); |
| } |
| |
| const DialogModelTitleItem* DialogModelField::AsTitleItem() const { |
| CHECK_EQ(type_, kTitleItem); |
| return static_cast<const DialogModelTitleItem*>(this); |
| } |
| |
| DialogModelTextfield* DialogModelField::AsTextfield() { |
| CHECK_EQ(type_, kTextfield); |
| return static_cast<DialogModelTextfield*>(this); |
| } |
| |
| DialogModelPasswordField* DialogModelField::AsPasswordField() { |
| CHECK_EQ(type_, kPasswordField); |
| return static_cast<DialogModelPasswordField*>(this); |
| } |
| |
| DialogModelCustomField* DialogModelField::AsCustomField() { |
| CHECK_EQ(type_, kCustom); |
| return static_cast<DialogModelCustomField*>(this); |
| } |
| |
| void DialogModelField::NotifyOnFieldChanged() { |
| on_field_changed_.Notify(); |
| } |
| |
| DialogModelParagraph::DialogModelParagraph(const DialogModelLabel& label, |
| std::u16string header, |
| ElementIdentifier id) |
| : DialogModelField(kParagraph, id, {}, DialogModelField::Params()), |
| label_(label), |
| header_(header) {} |
| |
| DialogModelParagraph::~DialogModelParagraph() = default; |
| |
| DialogModelCheckbox::DialogModelCheckbox( |
| ElementIdentifier id, |
| const DialogModelLabel& label, |
| const DialogModelCheckbox::Params& params) |
| : DialogModelField(kCheckbox, id, {}, params), |
| label_(label), |
| is_checked_(params.is_checked_) {} |
| |
| DialogModelCheckbox::~DialogModelCheckbox() = default; |
| |
| void DialogModelCheckbox::OnChecked(base::PassKey<DialogModelFieldHost>, |
| bool is_checked) { |
| is_checked_ = is_checked; |
| } |
| |
| DialogModelCombobox::Params::Params() = default; |
| DialogModelCombobox::Params::~Params() = default; |
| |
| DialogModelCombobox::Params& DialogModelCombobox::Params::SetCallback( |
| base::RepeatingClosure callback) { |
| callback_ = std::move(callback); |
| return *this; |
| } |
| |
| DialogModelCombobox::Params& DialogModelCombobox::Params::AddAccelerator( |
| Accelerator accelerator) { |
| accelerators_.insert(std::move(accelerator)); |
| return *this; |
| } |
| |
| DialogModelCombobox::DialogModelCombobox( |
| ElementIdentifier id, |
| std::u16string label, |
| std::unique_ptr<ui::ComboboxModel> combobox_model, |
| const DialogModelCombobox::Params& params) |
| : DialogModelField(kCombobox, id, params.accelerators_, params), |
| label_(std::move(label)), |
| accessible_name_(params.accessible_name_), |
| selected_index_(combobox_model->GetDefaultIndex().value()), |
| combobox_model_(std::move(combobox_model)), |
| callback_(params.callback_) {} |
| |
| DialogModelCombobox::~DialogModelCombobox() = default; |
| |
| void DialogModelCombobox::OnSelectedIndexChanged( |
| base::PassKey<DialogModelFieldHost>, |
| size_t selected_index) { |
| selected_index_ = selected_index; |
| } |
| |
| void DialogModelCombobox::OnPerformAction(base::PassKey<DialogModelFieldHost>) { |
| if (callback_) |
| callback_.Run(); |
| } |
| |
| DialogModelMenuItem::Params::Params() = default; |
| DialogModelMenuItem::Params::~Params() = default; |
| |
| DialogModelMenuItem::Params& DialogModelMenuItem::Params::SetIsEnabled( |
| bool is_enabled) { |
| is_enabled_ = is_enabled; |
| return *this; |
| } |
| |
| DialogModelMenuItem::Params& DialogModelMenuItem::Params::SetId( |
| ElementIdentifier id) { |
| CHECK(!id_); |
| CHECK(id); |
| id_ = id; |
| return *this; |
| } |
| |
| DialogModelMenuItem::DialogModelMenuItem( |
| ImageModel icon, |
| std::u16string label, |
| base::RepeatingCallback<void(int)> callback, |
| const DialogModelMenuItem::Params& params) |
| : DialogModelField(kMenuItem, params.id_, {}, params), |
| icon_(std::move(icon)), |
| label_(std::move(label)), |
| is_enabled_(params.is_enabled_), |
| callback_(std::move(callback)) {} |
| |
| DialogModelMenuItem::~DialogModelMenuItem() = default; |
| |
| void DialogModelMenuItem::OnActivated(base::PassKey<DialogModelFieldHost>, |
| int event_flags) { |
| CHECK(callback_); |
| callback_.Run(event_flags); |
| } |
| |
| DialogModelSeparator::DialogModelSeparator() |
| : DialogModelField(kSeparator, |
| ElementIdentifier(), |
| {}, |
| DialogModelField::Params()) {} |
| |
| DialogModelSeparator::~DialogModelSeparator() = default; |
| |
| DialogModelTitleItem::DialogModelTitleItem(std::u16string label, |
| ElementIdentifier id) |
| : DialogModelField(kTitleItem, id, {}, DialogModelField::Params()), |
| label_(std::move(label)) {} |
| |
| DialogModelTitleItem::~DialogModelTitleItem() = default; |
| |
| DialogModelTextfield::Params::Params() = default; |
| DialogModelTextfield::Params::~Params() = default; |
| |
| DialogModelTextfield::Params& DialogModelTextfield::Params::AddAccelerator( |
| Accelerator accelerator) { |
| accelerators_.insert(std::move(accelerator)); |
| return *this; |
| } |
| |
| DialogModelTextfield::DialogModelTextfield( |
| ElementIdentifier id, |
| std::u16string label, |
| std::u16string text, |
| const ui::DialogModelTextfield::Params& params) |
| : DialogModelField(kTextfield, id, params.accelerators_, params), |
| label_(label), |
| accessible_name_(params.accessible_name_), |
| text_(std::move(text)) { |
| // Textfields need either an accessible name or label or the screenreader will |
| // not be able to announce anything sensible. |
| CHECK(!label_.empty() || !accessible_name_.empty()); |
| } |
| |
| DialogModelTextfield::~DialogModelTextfield() = default; |
| |
| void DialogModelTextfield::OnTextChanged(base::PassKey<DialogModelFieldHost>, |
| std::u16string_view text) { |
| if (text == text_) { |
| return; |
| } |
| text_ = std::u16string(text); |
| NotifyOnFieldChanged(); |
| } |
| |
| DialogModelPasswordField::DialogModelPasswordField( |
| ElementIdentifier id, |
| std::u16string label, |
| std::u16string accessible_name, |
| std::u16string incorrect_password_text, |
| const DialogModelField::Params& params) |
| : DialogModelField(kPasswordField, id, /*accelerators=*/{}, params), |
| label_(std::move(label)), |
| accessible_name_(std::move(accessible_name)), |
| incorrect_password_text_(std::move(incorrect_password_text)) {} |
| |
| DialogModelPasswordField::~DialogModelPasswordField() = default; |
| |
| void DialogModelPasswordField::Invalidate() { |
| on_invalidate_closures_.Notify(); |
| } |
| |
| void DialogModelPasswordField::OnTextChanged( |
| base::PassKey<DialogModelFieldHost>, |
| std::u16string_view text) { |
| if (text == text_) { |
| return; |
| } |
| text_ = std::u16string(text); |
| NotifyOnFieldChanged(); |
| } |
| |
| base::CallbackListSubscription |
| DialogModelPasswordField::AddOnInvalidateCallback( |
| base::PassKey<DialogModelFieldHost>, |
| base::RepeatingClosure closure) { |
| return on_invalidate_closures_.Add(std::move(closure)); |
| } |
| |
| DialogModelCustomField::Field::~Field() = default; |
| |
| DialogModelCustomField::DialogModelCustomField( |
| ElementIdentifier id, |
| std::unique_ptr<DialogModelCustomField::Field> field) |
| : DialogModelField(kCustom, id, {}, DialogModelField::Params()), |
| field_(std::move(field)) {} |
| |
| DialogModelCustomField::~DialogModelCustomField() = default; |
| |
| DialogModelSection::DialogModelSection() |
| : DialogModelField(kSection, |
| ElementIdentifier(), |
| {}, |
| DialogModelField::Params()) {} |
| |
| DialogModelSection::~DialogModelSection() = default; |
| |
| DialogModelSection::Builder::Builder() |
| : section_(std::make_unique<DialogModelSection>()) {} |
| |
| DialogModelSection::Builder::~Builder() { |
| CHECK(!section_) << "DialogModelSection should've been built."; |
| } |
| |
| std::unique_ptr<DialogModelSection> DialogModelSection::Builder::Build() { |
| CHECK(section_); |
| return std::move(section_); |
| } |
| |
| base::CallbackListSubscription DialogModelSection::AddOnFieldAddedCallback( |
| base::RepeatingCallback<void(DialogModelField*)> on_field_added) { |
| return on_field_added_.Add(std::move(on_field_added)); |
| } |
| |
| base::CallbackListSubscription DialogModelSection::AddOnFieldChangedCallback( |
| base::RepeatingCallback<void(DialogModelField*)> on_field_changed) { |
| return on_field_changed_.Add(std::move(on_field_changed)); |
| } |
| |
| DialogModelField* DialogModelSection::GetFieldByUniqueId(ElementIdentifier id) { |
| // Assert that there are not duplicate fields corresponding to `id`. There |
| // could be no matches in `fields_` if `id` corresponds to a button. |
| CHECK_EQ(static_cast<int>(std::ranges::count_if( |
| fields_, |
| [id](auto& field) { |
| // TODO(pbos): This does not |
| // work recursively yet. |
| CHECK_NE(field->type(), DialogModelField::kSection); |
| return field->id() == id; |
| })), |
| 1); |
| |
| for (auto& field : fields_) { |
| if (field->id() == id) { |
| return field.get(); |
| } |
| } |
| |
| NOTREACHED(); |
| } |
| |
| DialogModelCheckbox* DialogModelSection::GetCheckboxByUniqueId( |
| ElementIdentifier id) { |
| return GetFieldByUniqueId(id)->AsCheckbox(); |
| } |
| |
| DialogModelCombobox* DialogModelSection::GetComboboxByUniqueId( |
| ElementIdentifier id) { |
| return GetFieldByUniqueId(id)->AsCombobox(); |
| } |
| |
| DialogModelTextfield* DialogModelSection::GetTextfieldByUniqueId( |
| ElementIdentifier id) { |
| return GetFieldByUniqueId(id)->AsTextfield(); |
| } |
| |
| DialogModelPasswordField* DialogModelSection::GetPasswordFieldByUniqueId( |
| ElementIdentifier id) { |
| return GetFieldByUniqueId(id)->AsPasswordField(); |
| } |
| |
| void DialogModelSection::AddParagraph(const DialogModelLabel& label, |
| std::u16string header, |
| ElementIdentifier id) { |
| AddField(std::make_unique<DialogModelParagraph>(label, header, id)); |
| } |
| |
| void DialogModelSection::AddCheckbox( |
| ElementIdentifier id, |
| const DialogModelLabel& label, |
| const DialogModelCheckbox::Params& params) { |
| AddField(std::make_unique<DialogModelCheckbox>(id, label, params)); |
| } |
| |
| void DialogModelSection::AddCombobox( |
| ElementIdentifier id, |
| std::u16string label, |
| std::unique_ptr<ui::ComboboxModel> combobox_model, |
| const DialogModelCombobox::Params& params) { |
| AddField(std::make_unique<DialogModelCombobox>( |
| id, std::move(label), std::move(combobox_model), params)); |
| } |
| |
| void DialogModelSection::AddSeparator() { |
| AddField(std::make_unique<DialogModelSeparator>()); |
| } |
| |
| void DialogModelSection::AddMenuItem( |
| ImageModel icon, |
| std::u16string label, |
| base::RepeatingCallback<void(int)> callback, |
| const DialogModelMenuItem::Params& params) { |
| AddField(std::make_unique<DialogModelMenuItem>( |
| std::move(icon), std::move(label), std::move(callback), params)); |
| } |
| |
| void DialogModelSection::AddTitleItem(std::u16string label, |
| ElementIdentifier id) { |
| AddField(std::make_unique<DialogModelTitleItem>(std::move(label), id)); |
| } |
| |
| void DialogModelSection::AddTextfield( |
| ElementIdentifier id, |
| std::u16string label, |
| std::u16string text, |
| const DialogModelTextfield::Params& params) { |
| AddField(std::make_unique<DialogModelTextfield>(id, std::move(label), |
| std::move(text), params)); |
| } |
| |
| void DialogModelSection::AddPasswordField( |
| ElementIdentifier id, |
| std::u16string label, |
| std::u16string accessible_text, |
| std::u16string incorrect_password_text, |
| const DialogModelField::Params& params) { |
| AddField(std::make_unique<DialogModelPasswordField>( |
| id, std::move(label), std::move(accessible_text), |
| std::move(incorrect_password_text), params)); |
| } |
| |
| void DialogModelSection::AddCustomField( |
| std::unique_ptr<DialogModelCustomField::Field> field, |
| ElementIdentifier id) { |
| AddField(std::make_unique<DialogModelCustomField>(id, std::move(field))); |
| } |
| |
| void DialogModelSection::AddField(std::unique_ptr<DialogModelField> field) { |
| CHECK(field); |
| |
| // This probably needs to be updated for recursive fields. CHECK that we don't |
| // add recursive sections until we've thought through how the updates are |
| // communicated. |
| CHECK_NE(field->type(), DialogModelField::kSection); |
| auto* const field_ptr = field.get(); |
| field_subscriptions_.push_back(field->AddOnFieldChangedCallback( |
| base::BindRepeating(&DialogModelSection::OnFieldChanged, |
| base::Unretained(this), field_ptr))); |
| fields_.push_back(std::move(field)); |
| on_field_added_.Notify(field_ptr); |
| } |
| |
| void DialogModelSection::OnFieldChanged(DialogModelField* field) { |
| on_field_changed_.Notify(field); |
| } |
| |
| } // namespace ui |