blob: 7641206f3da48a1528eca33ec9f73e445d36cbb8 [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.
#ifndef UI_BASE_MODELS_DIALOG_MODEL_FIELD_H_
#define UI_BASE_MODELS_DIALOG_MODEL_FIELD_H_
#include <string>
#include "base/component_export.h"
#include "base/containers/flat_set.h"
#include "base/functional/callback.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/types/pass_key.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/models/combobox_model.h"
#include "ui/base/models/image_model.h"
namespace ui {
class DialogModel;
class DialogModelButton;
class DialogModelParagraph;
class DialogModelCheckbox;
class DialogModelCombobox;
class DialogModelCustomField;
class DialogModelHost;
class DialogModelMenuItem;
class DialogModelTextfield;
class Event;
// TODO(pbos): Move this to separate header.
// DialogModelLabel is an exception to below classes. This is not a
// DialogModelField but rather represents a text label and styling. This is used
// with DialogModelParagraph and DialogModelCheckbox for instance and has
// support for styling text replacements and showing a link.
class COMPONENT_EXPORT(UI_BASE) DialogModelLabel {
public:
// TODO(pbos): Move this definition (maybe as a ui::LinkCallback) so it can
// be reused with views::Link.
using Callback = base::RepeatingCallback<void(const Event& event)>;
class COMPONENT_EXPORT(UI_BASE) TextReplacement {
public:
TextReplacement(const TextReplacement&);
~TextReplacement();
const std::u16string& text() const { return text_; }
bool is_emphasized() const { return is_emphasized_; }
const absl::optional<Callback>& callback() const { return callback_; }
const absl::optional<std::u16string>& accessible_name() const {
return accessible_name_;
}
private:
friend class DialogModelLabel;
// Used for regular and emphasized text.
explicit TextReplacement(std::u16string text, bool is_emphasized = false);
// Used for links.
TextReplacement(int message_id,
Callback closure,
std::u16string accessible_name = std::u16string());
const std::u16string text_;
const bool is_emphasized_;
const absl::optional<Callback> callback_;
const absl::optional<std::u16string> accessible_name_;
};
explicit DialogModelLabel(int message_id);
explicit DialogModelLabel(std::u16string fixed_string);
DialogModelLabel(const DialogModelLabel&);
DialogModelLabel& operator=(const DialogModelLabel&) = delete;
~DialogModelLabel();
static DialogModelLabel CreateWithReplacement(int message_id,
TextReplacement replacement);
static DialogModelLabel CreateWithReplacements(
int message_id,
std::vector<TextReplacement> replacements);
// Builder methods for TextReplacements.
static TextReplacement CreateLink(
int message_id,
base::RepeatingClosure closure,
std::u16string accessible_name = std::u16string());
static TextReplacement CreateLink(
int message_id,
Callback callback,
std::u16string accessible_name = std::u16string());
static TextReplacement CreatePlainText(std::u16string text);
static TextReplacement CreateEmphasizedText(std::u16string text);
// Gets the string. Not for use with replacements, in which case the caller
// must use replacements() and message_id() to construct the final label. This
// is required to style the final label appropriately and support replacement
// callbacks. The caller is responsible for checking replacements().empty()
// before calling this.
const std::u16string& GetString(base::PassKey<DialogModelHost>) const;
DialogModelLabel& set_is_secondary() {
is_secondary_ = true;
return *this;
}
DialogModelLabel& set_allow_character_break() {
allow_character_break_ = true;
return *this;
}
int message_id(base::PassKey<DialogModelHost>) const { return message_id_; }
const std::vector<TextReplacement>& replacements(
base::PassKey<DialogModelHost>) const {
return replacements_;
}
bool is_secondary(base::PassKey<DialogModelHost>) const {
return is_secondary_;
}
bool allow_character_break(base::PassKey<DialogModelHost>) const {
return allow_character_break_;
}
private:
explicit DialogModelLabel(int message_id,
std::vector<TextReplacement> replacements);
const int message_id_;
const std::u16string string_;
// Set of replacements that will be added to `message_id_`.
const std::vector<TextReplacement> replacements_;
bool is_secondary_ = false;
bool allow_character_break_ = false;
};
// These "field" classes represent entries in a DialogModel. They are owned
// by the model and either created through the model or DialogModel::Builder.
// These entries can be referred to by setting the field's ElementIdentifier in
// construction parameters (::Params::SetId()). They can then
// later be acquired through DialogModel::GetFieldByUniqueId() methods.
// These fields own the data corresponding to their field. For instance, the
// text of a textfield in a model is read using DialogModelTextfield::text() and
// stays in sync with the visible dialog (through DialogModelHosts).
class COMPONENT_EXPORT(UI_BASE) DialogModelField {
public:
enum Type {
kButton,
kParagraph,
kCheckbox,
kCombobox,
kCustom,
kMenuItem,
kSeparator,
kTextfield
};
DialogModelField(const DialogModelField&) = delete;
DialogModelField& operator=(const DialogModelField&) = delete;
virtual ~DialogModelField();
// Methods with base::PassKey<DialogModelHost> are only intended to be called
// by the DialogModelHost implementation.
Type type(base::PassKey<DialogModelHost>) const { return type_; }
const base::flat_set<Accelerator>& accelerators(
base::PassKey<DialogModelHost>) const {
return accelerators_;
}
ElementIdentifier id(base::PassKey<DialogModelHost>) const { return id_; }
DialogModelButton* AsButton(base::PassKey<DialogModelHost>);
DialogModelParagraph* AsParagraph(base::PassKey<DialogModelHost>);
DialogModelCheckbox* AsCheckbox(base::PassKey<DialogModelHost>);
DialogModelCombobox* AsCombobox(base::PassKey<DialogModelHost>);
DialogModelMenuItem* AsMenuItem(base::PassKey<DialogModelHost>);
const DialogModelMenuItem* AsMenuItem(base::PassKey<DialogModelHost>) const;
DialogModelTextfield* AsTextfield(base::PassKey<DialogModelHost>);
DialogModelCustomField* AsCustomField(base::PassKey<DialogModelHost>);
protected:
// Children of this class need to be constructed through DialogModel to help
// enforce that they're added to the model.
DialogModelField(base::PassKey<DialogModel>,
DialogModel* model,
Type type,
ElementIdentifier id,
base::flat_set<Accelerator> accelerators);
DialogModelButton* AsButton();
DialogModelParagraph* AsParagraph();
DialogModelCheckbox* AsCheckbox();
DialogModelCombobox* AsCombobox();
const DialogModelMenuItem* AsMenuItem() const;
DialogModelTextfield* AsTextfield();
DialogModelCustomField* AsCustomField();
private:
friend class DialogModel;
FRIEND_TEST_ALL_PREFIXES(DialogModelButtonTest, UsesParamsUniqueId);
const raw_ptr<DialogModel> model_;
const Type type_;
const ElementIdentifier id_;
const base::flat_set<Accelerator> accelerators_;
};
// Field class representing a dialog button.
class COMPONENT_EXPORT(UI_BASE) DialogModelButton : public DialogModelField {
public:
class COMPONENT_EXPORT(UI_BASE) Params {
public:
Params();
Params(const Params&) = delete;
Params& operator=(const Params&) = delete;
~Params();
Params& SetId(ElementIdentifier id);
Params& SetLabel(std::u16string label);
Params& AddAccelerator(Accelerator accelerator);
private:
friend class DialogModel;
friend class DialogModelButton;
ElementIdentifier id_;
std::u16string label_;
base::flat_set<Accelerator> accelerators_;
};
// Note that this is constructed through a DialogModel which adds it to model
// fields.
DialogModelButton(base::PassKey<DialogModel> pass_key,
DialogModel* model,
base::RepeatingCallback<void(const Event&)> callback,
const Params& params);
DialogModelButton(const DialogModelButton&) = delete;
DialogModelButton& operator=(const DialogModelButton&) = delete;
~DialogModelButton() override;
// Methods with base::PassKey<DialogModelHost> are only intended to be called
// by the DialogModelHost implementation.
const std::u16string& label(base::PassKey<DialogModelHost>) const {
return label_;
}
void OnPressed(base::PassKey<DialogModelHost>, const Event& event);
private:
friend class DialogModel;
const std::u16string label_;
// The button callback gets called when the button is activated. Whether
// that happens on key-press, release, etc. is implementation (and platform)
// dependent.
base::RepeatingCallback<void(const Event&)> callback_;
};
// Field class representing a paragraph.
class COMPONENT_EXPORT(UI_BASE) DialogModelParagraph : public DialogModelField {
public:
// Note that this is constructed through a DialogModel which adds it to model
// fields.
DialogModelParagraph(base::PassKey<DialogModel> pass_key,
DialogModel* model,
const DialogModelLabel& label,
std::u16string header,
ElementIdentifier id);
DialogModelParagraph(const DialogModelParagraph&) = delete;
DialogModelParagraph& operator=(const DialogModelParagraph&) = delete;
~DialogModelParagraph() override;
const DialogModelLabel& label(base::PassKey<DialogModelHost>) const {
return label_;
}
const std::u16string header(base::PassKey<DialogModelHost>) const {
return header_;
}
private:
const DialogModelLabel label_;
const std::u16string header_;
};
// Field class representing a checkbox with descriptive text.
class COMPONENT_EXPORT(UI_BASE) DialogModelCheckbox : public DialogModelField {
public:
class COMPONENT_EXPORT(UI_BASE) Params {
public:
Params() = default;
Params(const Params&) = delete;
Params& operator=(const Params&) = delete;
~Params() = default;
Params& SetIsChecked(bool is_checked) {
is_checked_ = is_checked;
return *this;
}
private:
friend class DialogModelCheckbox;
bool is_checked_ = false;
};
// Note that this is constructed through a DialogModel which adds it to model
// fields.
DialogModelCheckbox(base::PassKey<DialogModel> pass_key,
DialogModel* model,
ElementIdentifier id,
const DialogModelLabel& label,
const Params& params);
DialogModelCheckbox(const DialogModelCheckbox&) = delete;
DialogModelCheckbox& operator=(const DialogModelCheckbox&) = delete;
~DialogModelCheckbox() override;
bool is_checked() const { return is_checked_; }
void OnChecked(base::PassKey<DialogModelHost>, bool is_checked);
const DialogModelLabel& label(base::PassKey<DialogModelHost>) const {
return label_;
}
private:
const DialogModelLabel label_;
bool is_checked_;
};
// Field class representing a combobox and corresponding label to describe the
// combobox:
//
// <label> [combobox]
// Ex: Folder [My Bookmarks]
class COMPONENT_EXPORT(UI_BASE) DialogModelCombobox : public DialogModelField {
public:
class COMPONENT_EXPORT(UI_BASE) Params {
public:
Params();
Params(const Params&) = delete;
Params& operator=(const Params&) = delete;
~Params();
Params& AddAccelerator(Accelerator accelerator);
Params& SetAccessibleName(std::u16string accessible_name) {
accessible_name_ = std::move(accessible_name);
return *this;
}
// The combobox callback is invoked when an item has been selected. This
// nominally happens when selecting an item in the combobox menu. The
// selection notably does not change by hovering different items in the
// combobox menu or navigating it with up/down keys as long as the menu is
// open.
Params& SetCallback(base::RepeatingClosure callback);
private:
friend class DialogModelCombobox;
ElementIdentifier id_;
std::u16string accessible_name_;
base::RepeatingClosure callback_;
base::flat_set<Accelerator> accelerators_;
};
// Note that this is constructed through a DialogModel which adds it to model
// fields.
DialogModelCombobox(base::PassKey<DialogModel> pass_key,
DialogModel* model,
ElementIdentifier id,
std::u16string label,
std::unique_ptr<ui::ComboboxModel> combobox_model,
const Params& params);
DialogModelCombobox(const DialogModelCombobox&) = delete;
DialogModelCombobox& operator=(const DialogModelCombobox&) = delete;
~DialogModelCombobox() override;
size_t selected_index() const { return selected_index_; }
ui::ComboboxModel* combobox_model() { return combobox_model_.get(); }
// Methods with base::PassKey<DialogModelHost> are only intended to be called
// by the DialogModelHost implementation.
const std::u16string& label(base::PassKey<DialogModelHost>) const {
return label_;
}
const std::u16string& accessible_name(base::PassKey<DialogModelHost>) const {
return accessible_name_;
}
void OnSelectedIndexChanged(base::PassKey<DialogModelHost>,
size_t selected_index);
void OnPerformAction(base::PassKey<DialogModelHost>);
private:
friend class DialogModel;
const std::u16string label_;
const std::u16string accessible_name_;
size_t selected_index_;
std::unique_ptr<ui::ComboboxModel> combobox_model_;
base::RepeatingClosure callback_;
};
// Field class representing a menu item:
//
// <icon> <label>
// Ex: [icon] Open URL
class COMPONENT_EXPORT(UI_BASE) DialogModelMenuItem : public DialogModelField {
public:
class COMPONENT_EXPORT(UI_BASE) Params {
public:
Params();
Params(const Params&) = delete;
Params& operator=(const Params&) = delete;
~Params();
Params& SetIsEnabled(bool is_enabled);
Params& SetId(ElementIdentifier id);
private:
friend class DialogModelMenuItem;
bool is_enabled_ = true;
ElementIdentifier id_;
};
// Note that this is constructed through a DialogModel which adds it to model
// fields.
DialogModelMenuItem(base::PassKey<DialogModel> pass_key,
DialogModel* model,
ImageModel icon,
std::u16string label,
base::RepeatingCallback<void(int)> callback,
const Params& params);
DialogModelMenuItem(const DialogModelMenuItem&) = delete;
DialogModelMenuItem& operator=(const DialogModelMenuItem&) = delete;
~DialogModelMenuItem() override;
// Methods with base::PassKey<DialogModelHost> are only intended to be called
// by the DialogModelHost implementation.
const ImageModel& icon(base::PassKey<DialogModelHost>) const { return icon_; }
const std::u16string& label(base::PassKey<DialogModelHost>) const {
return label_;
}
bool is_enabled(base::PassKey<DialogModelHost>) const { return is_enabled_; }
void OnActivated(base::PassKey<DialogModelHost>, int event_flags);
private:
const ImageModel icon_;
const std::u16string label_;
const bool is_enabled_;
base::RepeatingCallback<void(int)> callback_;
};
// Field class representing a separator.
class COMPONENT_EXPORT(UI_BASE) DialogModelSeparator : public DialogModelField {
public:
// Note that this is constructed through a DialogModel which adds it to model
// fields.
DialogModelSeparator(base::PassKey<DialogModel> pass_key, DialogModel* model);
DialogModelSeparator(const DialogModelSeparator&) = delete;
DialogModelSeparator& operator=(const DialogModelSeparator&) = delete;
~DialogModelSeparator() override;
};
// Field class representing a textfield and corresponding label to describe the
// textfield:
//
// <label> [textfield]
// Ex: Name [My email]
class COMPONENT_EXPORT(UI_BASE) DialogModelTextfield : public DialogModelField {
public:
class COMPONENT_EXPORT(UI_BASE) Params {
public:
Params();
Params(const Params&) = delete;
Params& operator=(const Params&) = delete;
~Params();
Params& AddAccelerator(Accelerator accelerator);
Params& SetAccessibleName(std::u16string accessible_name) {
accessible_name_ = std::move(accessible_name);
return *this;
}
private:
friend class DialogModelTextfield;
ElementIdentifier id_;
std::u16string accessible_name_;
base::flat_set<Accelerator> accelerators_;
};
// Note that this is constructed through a DialogModel which adds it to model
// fields.
DialogModelTextfield(base::PassKey<DialogModel> pass_key,
DialogModel* model,
ElementIdentifier id,
std::u16string label,
std::u16string text,
const Params& params);
DialogModelTextfield(const DialogModelTextfield&) = delete;
DialogModelTextfield& operator=(const DialogModelTextfield&) = delete;
~DialogModelTextfield() override;
const std::u16string& text() const { return text_; }
// Methods with base::PassKey<DialogModelHost> are only intended to be called
// by the DialogModelHost implementation.
const std::u16string& label(base::PassKey<DialogModelHost>) const {
return label_;
}
const std::u16string& accessible_name(base::PassKey<DialogModelHost>) const {
return accessible_name_;
}
void OnTextChanged(base::PassKey<DialogModelHost>, std::u16string text);
private:
friend class DialogModel;
const std::u16string label_;
const std::u16string accessible_name_;
std::u16string text_;
};
// Field base class representing a "custom" field. Used for instance to inject
// custom Views into dialogs that use DialogModel.
class COMPONENT_EXPORT(UI_BASE) DialogModelCustomField
: public DialogModelField {
public:
// Base class for fields held by DialogModelField. Calling code is responsible
// for providing the subclass expected by the DialogModelHost used.
class COMPONENT_EXPORT(UI_BASE) Field {
public:
virtual ~Field();
};
// Note that this is constructed through a DialogModel which adds it to model
// fields.
DialogModelCustomField(base::PassKey<DialogModel> pass_key,
DialogModel* model,
ElementIdentifier id,
std::unique_ptr<DialogModelCustomField::Field> field);
DialogModelCustomField(const DialogModelCustomField&) = delete;
DialogModelCustomField& operator=(const DialogModelCustomField&) = delete;
~DialogModelCustomField() override;
// Methods with base::PassKey<DialogModelHost> are only intended to be called
// by the DialogModelHost implementation.
DialogModelCustomField::Field* field(base::PassKey<DialogModelHost>) {
return field_.get();
}
private:
friend class DialogModel;
std::unique_ptr<DialogModelCustomField::Field> field_;
};
} // namespace ui
#endif // UI_BASE_MODELS_DIALOG_MODEL_FIELD_H_