#include "ui/views/bubble/bubble_dialog_model_host.h"
#include <utility>
#include "base/memory/raw_ptr.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/base/class_property.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/combobox_model.h"
#include "ui/base/models/dialog_model.h"
#include "ui/base/models/dialog_model_field.h"
#include "ui/base/ui_base_types.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/border.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/controls/button/checkbox.h"
#include "ui/views/controls/button/label_button_border.h"
#include "ui/views/controls/button/md_text_button.h"
#include "ui/views/controls/combobox/combobox.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/separator.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/controls/theme_tracking_image_view.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/box_layout_view.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/style/typography.h"
#include "ui/views/view_class_properties.h"
namespace views {
namespace {
constexpr int kScrollViewVerticalMargin = 2;
BubbleDialogModelHost::ContentsView* SetAndGetContentsView(
BubbleDialogModelHost* parent,
ui::ModalType modal_type) {
const bool is_modal_dialog = modal_type != ui::MODAL_TYPE_NONE;
auto contents_view_unique =
std::make_unique<BubbleDialogModelHost::ContentsView>(parent,
is_modal_dialog);
BubbleDialogModelHost::ContentsView* contents_view =
contents_view_unique.get();
if (is_modal_dialog) {
constexpr int kMaxDialogHeight = 448;
auto scroll_view = std::make_unique<views::ScrollView>();
scroll_view->ClipHeightTo(0, kMaxDialogHeight);
scroll_view->SetHorizontalScrollBarMode(
views::ScrollView::ScrollBarMode::kDisabled);
scroll_view->SetContents(std::move(contents_view_unique));
parent->SetContentsView(std::move(scroll_view));
} else {
parent->SetContentsView(std::move(contents_view_unique));
}
return contents_view;
}
BubbleDialogModelHost::FieldType GetFieldTypeForField(
ui::DialogModelField* field,
base::PassKey<ui::DialogModelHost> pass_key) {
DCHECK(field);
switch (field->type(pass_key)) {
case ui::DialogModelField::kButton:
return BubbleDialogModelHost::FieldType::kControl;
case ui::DialogModelField::kParagraph:
return BubbleDialogModelHost::FieldType::kText;
case ui::DialogModelField::kCheckbox:
return BubbleDialogModelHost::FieldType::kControl;
case ui::DialogModelField::kTextfield:
return BubbleDialogModelHost::FieldType::kControl;
case ui::DialogModelField::kCombobox:
return BubbleDialogModelHost::FieldType::kControl;
case ui::DialogModelField::kMenuItem:
return BubbleDialogModelHost::FieldType::kMenuItem;
case ui::DialogModelField::kSeparator:
return BubbleDialogModelHost::FieldType::kMenuItem;
case ui::DialogModelField::kCustom:
return static_cast<BubbleDialogModelHost::CustomView*>(
field->AsCustomField(pass_key)->field(pass_key))
->field_type();
}
}
int GetFieldTopMargin(LayoutProvider* layout_provider,
const BubbleDialogModelHost::FieldType& field_type,
const BubbleDialogModelHost::FieldType& last_field_type) {
DCHECK(layout_provider);
if (field_type == BubbleDialogModelHost::FieldType::kMenuItem &&
last_field_type == BubbleDialogModelHost::FieldType::kMenuItem) {
return 0;
}
if (field_type == BubbleDialogModelHost::FieldType::kControl &&
last_field_type == BubbleDialogModelHost::FieldType::kControl) {
return 12;
}
return layout_provider->GetDistanceMetric(
DISTANCE_UNRELATED_CONTROL_VERTICAL);
}
int GetDialogTopMargins(LayoutProvider* layout_provider,
ui::DialogModelField* first_field,
base::PassKey<ui::DialogModelHost> pass_key) {
const BubbleDialogModelHost::FieldType field_type =
first_field ? GetFieldTypeForField(first_field, pass_key)
: BubbleDialogModelHost::FieldType::kControl;
switch (field_type) {
case BubbleDialogModelHost::FieldType::kMenuItem:
return 0;
case BubbleDialogModelHost::FieldType::kControl:
return layout_provider->GetDistanceMetric(
DISTANCE_DIALOG_CONTENT_MARGIN_TOP_CONTROL);
case BubbleDialogModelHost::FieldType::kText:
return layout_provider->GetDistanceMetric(
DISTANCE_DIALOG_CONTENT_MARGIN_TOP_TEXT);
}
}
int GetDialogBottomMargins(LayoutProvider* layout_provider,
ui::DialogModelField* last_field,
bool has_buttons,
base::PassKey<ui::DialogModelHost> pass_key) {
const BubbleDialogModelHost::FieldType field_type =
last_field ? GetFieldTypeForField(last_field, pass_key)
: BubbleDialogModelHost::FieldType::kControl;
switch (field_type) {
case BubbleDialogModelHost::FieldType::kMenuItem:
return has_buttons ? layout_provider->GetDistanceMetric(
DISTANCE_DIALOG_CONTENT_MARGIN_BOTTOM_CONTROL)
: 0;
case BubbleDialogModelHost::FieldType::kControl:
return layout_provider->GetDistanceMetric(
DISTANCE_DIALOG_CONTENT_MARGIN_BOTTOM_CONTROL);
case BubbleDialogModelHost::FieldType::kText:
return layout_provider->GetDistanceMetric(
DISTANCE_DIALOG_CONTENT_MARGIN_BOTTOM_TEXT);
}
}
class CheckboxControl : public Checkbox {
public:
METADATA_HEADER(CheckboxControl);
CheckboxControl(std::unique_ptr<View> label, int label_line_height)
: label_line_height_(label_line_height) {
auto* layout = SetLayoutManager(std::make_unique<BoxLayout>());
layout->set_between_child_spacing(LayoutProvider::Get()->GetDistanceMetric(
DISTANCE_RELATED_LABEL_HORIZONTAL));
layout->set_cross_axis_alignment(BoxLayout::CrossAxisAlignment::kStart);
SetAccessibleName(label.get());
AddChildView(std::move(label));
}
void Layout() override {
View::Layout();
}
gfx::Size CalculatePreferredSize() const override {
return View::CalculatePreferredSize();
}
int GetHeightForWidth(int width) const override {
return View::GetHeightForWidth(width);
}
void OnThemeChanged() override {
Checkbox::OnThemeChanged();
image()->SetBorder(CreateEmptyBorder(gfx::Insets::TLBR(
(label_line_height_ - image()->GetPreferredSize().height()) / 2, 0, 0,
0)));
}
const int label_line_height_;
};
BEGIN_METADATA(CheckboxControl, Checkbox)
END_METADATA
}
BubbleDialogModelHost::CustomView::CustomView(std::unique_ptr<View> view,
FieldType field_type)
: view_(std::move(view)), field_type_(field_type) {}
BubbleDialogModelHost::CustomView::~CustomView() = default;
std::unique_ptr<View> BubbleDialogModelHost::CustomView::TransferView() {
DCHECK(view_);
return std::move(view_);
}
class BubbleDialogModelHost::ContentsView : public BoxLayoutView {
public:
ContentsView(BubbleDialogModelHost* parent, bool is_modal_dialog)
: parent_(parent) {
SetOrientation(views::BoxLayout::Orientation::kVertical);
if (is_modal_dialog)
SetInsideBorderInsets(gfx::Insets::VH(kScrollViewVerticalMargin, 0));
}
void OnThemeChanged() override {
View::OnThemeChanged();
if (!parent_->ShouldShowWindowIcon())
return;
const ui::ImageModel dark_mode_icon =
parent_->model_->dark_mode_icon(parent_->GetPassKey());
if (!dark_mode_icon.IsEmpty() &&
color_utils::IsDark(parent_->GetBackgroundColor())) {
parent_->SetIcon(dark_mode_icon);
return;
}
parent_->SetIcon(parent_->model_->icon(GetPassKey()));
}
private:
const raw_ptr<BubbleDialogModelHost, DanglingUntriaged> parent_;
};
class BubbleDialogModelHost::LayoutConsensusView : public View {
public:
METADATA_HEADER(LayoutConsensusView);
LayoutConsensusView(LayoutConsensusGroup* group, std::unique_ptr<View> view)
: group_(group) {
group->AddView(this);
SetLayoutManager(std::make_unique<FillLayout>());
AddChildView(std::move(view));
}
~LayoutConsensusView() override { group_->RemoveView(this); }
gfx::Size CalculatePreferredSize() const override {
const gfx::Size group_preferred_size = group_->GetMaxPreferredSize();
DCHECK_EQ(1u, children().size());
const gfx::Size child_preferred_size = children()[0]->GetPreferredSize();
return gfx::Size(group_preferred_size.width(),
child_preferred_size.height());
}
gfx::Size GetMinimumSize() const override {
const gfx::Size group_minimum_size = group_->GetMaxMinimumSize();
DCHECK_EQ(1u, children().size());
const gfx::Size child_minimum_size = children()[0]->GetMinimumSize();
return gfx::Size(group_minimum_size.width(), child_minimum_size.height());
}
private:
const raw_ptr<LayoutConsensusGroup> group_;
};
BEGIN_METADATA(BubbleDialogModelHost, LayoutConsensusView, View)
END_METADATA
BubbleDialogModelHost::LayoutConsensusGroup::LayoutConsensusGroup() = default;
BubbleDialogModelHost::LayoutConsensusGroup::~LayoutConsensusGroup() {
DCHECK(children_.empty());
}
void BubbleDialogModelHost::LayoutConsensusGroup::AddView(
LayoutConsensusView* view) {
children_.insert(view);
for (auto* child : children_)
child->InvalidateLayout();
}
void BubbleDialogModelHost::LayoutConsensusGroup::RemoveView(
LayoutConsensusView* view) {
children_.erase(view);
}
gfx::Size BubbleDialogModelHost::LayoutConsensusGroup::GetMaxPreferredSize()
const {
gfx::Size size;
for (auto* child : children_) {
DCHECK_EQ(1u, child->children().size());
size.SetToMax(child->children().front()->GetPreferredSize());
}
return size;
}
gfx::Size BubbleDialogModelHost::LayoutConsensusGroup::GetMaxMinimumSize()
const {
gfx::Size size;
for (auto* child : children_) {
DCHECK_EQ(1u, child->children().size());
size.SetToMax(child->children().front()->GetMinimumSize());
}
return size;
}
BubbleDialogModelHost::BubbleDialogModelHost(
std::unique_ptr<ui::DialogModel> model,
View* anchor_view,
BubbleBorder::Arrow arrow)
: BubbleDialogModelHost(base::PassKey<BubbleDialogModelHost>(),
std::move(model),
anchor_view,
arrow,
ui::ModalType::MODAL_TYPE_NONE) {}
BubbleDialogModelHost::BubbleDialogModelHost(
base::PassKey<BubbleDialogModelHost>,
std::unique_ptr<ui::DialogModel> model,
View* anchor_view,
BubbleBorder::Arrow arrow,
ui::ModalType modal_type)
: BubbleDialogDelegate(anchor_view, arrow),
model_(std::move(model)),
contents_view_(SetAndGetContentsView(this, modal_type)) {
model_->set_host(GetPassKey(), this);
SetModalType(modal_type);
SetAcceptCallback(base::BindOnce(&ui::DialogModel::OnDialogAcceptAction,
base::Unretained(model_.get()),
GetPassKey()));
SetCancelCallback(base::BindOnce(&ui::DialogModel::OnDialogCancelAction,
base::Unretained(model_.get()),
GetPassKey()));
SetCloseCallback(base::BindOnce(&ui::DialogModel::OnDialogCloseAction,
base::Unretained(model_.get()),
GetPassKey()));
RegisterWindowClosingCallback(base::BindOnce(
&BubbleDialogModelHost::OnWindowClosing, base::Unretained(this)));
int button_mask = ui::DIALOG_BUTTON_NONE;
auto* ok_button = model_->ok_button(GetPassKey());
if (ok_button) {
button_mask |= ui::DIALOG_BUTTON_OK;
if (!ok_button->label(GetPassKey()).empty())
SetButtonLabel(ui::DIALOG_BUTTON_OK, ok_button->label(GetPassKey()));
}
auto* cancel_button = model_->cancel_button(GetPassKey());
if (cancel_button) {
button_mask |= ui::DIALOG_BUTTON_CANCEL;
if (!cancel_button->label(GetPassKey()).empty())
SetButtonLabel(ui::DIALOG_BUTTON_CANCEL,
cancel_button->label(GetPassKey()));
}
if (ui::DialogModelButton* extra_button =
model_->extra_button(GetPassKey())) {
DCHECK(!model_->extra_link(GetPassKey()));
SetExtraView(std::make_unique<MdTextButton>(
base::BindRepeating(&ui::DialogModelButton::OnPressed,
base::Unretained(extra_button), GetPassKey()),
extra_button->label(GetPassKey())));
} else if (ui::DialogModelLabel::TextReplacement* extra_link =
model_->extra_link(GetPassKey())) {
DCHECK(extra_link->callback().has_value());
auto link = std::make_unique<views::Link>(extra_link->text());
link->SetCallback(extra_link->callback().value());
SetExtraView(std::move(link));
}
SetButtons(button_mask);
if (model_->override_default_button(GetPassKey()))
SetDefaultButton(model_->override_default_button(GetPassKey()).value());
SetTitle(model_->title(GetPassKey()));
if (!model_->accessible_title(GetPassKey()).empty()) {
SetAccessibleTitle(model_->accessible_title(GetPassKey()));
}
SetSubtitle(model_->subtitle(GetPassKey()));
if (!model_->main_image(GetPassKey()).IsEmpty())
SetMainImage(model_->main_image(GetPassKey()));
if (model_->override_show_close_button(GetPassKey())) {
SetShowCloseButton(*model_->override_show_close_button(GetPassKey()));
} else {
SetShowCloseButton(!IsModalDialog());
}
if (!model_->icon(GetPassKey()).IsEmpty()) {
SetIcon(model_->icon(GetPassKey()));
SetShowIcon(true);
}
if (model_->is_alert_dialog(GetPassKey())) {
#if BUILDFLAG(IS_WIN)
SetAccessibleWindowRole(ax::mojom::Role::kAlert);
#else
SetAccessibleWindowRole(ax::mojom::Role::kAlertDialog);
#endif
}
set_internal_name(model_->internal_name(GetPassKey()));
set_close_on_deactivate(model_->close_on_deactivate(GetPassKey()));
set_fixed_width(LayoutProvider::Get()->GetDistanceMetric(
anchor_view ? DISTANCE_BUBBLE_PREFERRED_WIDTH
: DISTANCE_MODAL_DIALOG_PREFERRED_WIDTH));
AddInitialFields();
}
BubbleDialogModelHost::~BubbleDialogModelHost() {
contents_view_->RemoveAllChildViews();
}
std::unique_ptr<BubbleDialogModelHost> BubbleDialogModelHost::CreateModal(
std::unique_ptr<ui::DialogModel> model,
ui::ModalType modal_type) {
DCHECK_NE(modal_type, ui::MODAL_TYPE_NONE);
return std::make_unique<BubbleDialogModelHost>(
base::PassKey<BubbleDialogModelHost>(), std::move(model), nullptr,
BubbleBorder::Arrow::NONE, modal_type);
}
View* BubbleDialogModelHost::GetInitiallyFocusedView() {
if (!model_)
return BubbleDialogDelegate::GetInitiallyFocusedView();
ui::ElementIdentifier unique_id =
model_->initially_focused_field(GetPassKey());
if (!unique_id)
return BubbleDialogDelegate::GetInitiallyFocusedView();
return GetTargetView(
FindDialogModelHostField(model_->GetFieldByUniqueId(unique_id)));
}
void BubbleDialogModelHost::OnWidgetInitialized() {
if (GetOkButton()) {
AddDialogModelHostFieldForExistingView(
{model_->ok_button(GetPassKey()), GetOkButton(), nullptr});
}
if (GetCancelButton()) {
AddDialogModelHostFieldForExistingView(
{model_->cancel_button(GetPassKey()), GetCancelButton(), nullptr});
}
if (model_->extra_button(GetPassKey())) {
DCHECK(GetExtraView());
AddDialogModelHostFieldForExistingView(
{model_->extra_button(GetPassKey()), GetExtraView(), nullptr});
}
if (const ui::ImageModel& banner = model_->banner(GetPassKey());
!banner.IsEmpty()) {
const ui::ImageModel& dark_mode_banner =
model_->dark_mode_banner(GetPassKey());
auto banner_view = std::make_unique<ThemeTrackingImageView>(
banner.Rasterize(contents_view_->GetColorProvider()),
(dark_mode_banner.IsEmpty() ? banner : dark_mode_banner)
.Rasterize(contents_view_->GetColorProvider()),
base::BindRepeating(&views::BubbleDialogDelegate::GetBackgroundColor,
base::Unretained(this)));
banner_view->GetViewAccessibility().OverrideIsIgnored(true);
GetBubbleFrameView()->SetHeaderView(std::move(banner_view));
SizeToContents();
}
}
View* BubbleDialogModelHost::GetContentsViewForTesting() {
return contents_view_;
}
void BubbleDialogModelHost::Close() {
DCHECK(model_);
DCHECK(GetWidget());
GetWidget()->Close();
model_->OnDialogDestroying(GetPassKey());
contents_view_->RemoveAllChildViews();
fields_.clear();
model_.reset();
}
void BubbleDialogModelHost::OnFieldAdded(ui::DialogModelField* field) {
switch (field->type(GetPassKey())) {
case ui::DialogModelField::kButton:
NOTREACHED_NORETURN();
case ui::DialogModelField::kParagraph:
AddOrUpdateParagraph(field->AsParagraph(GetPassKey()));
break;
case ui::DialogModelField::kCheckbox:
AddOrUpdateCheckbox(field->AsCheckbox(GetPassKey()));
break;
case ui::DialogModelField::kCombobox:
AddOrUpdateCombobox(field->AsCombobox(GetPassKey()));
break;
case ui::DialogModelField::kMenuItem:
AddOrUpdateMenuItem(field->AsMenuItem(GetPassKey()));
break;
case ui::DialogModelField::kSeparator:
AddOrUpdateSeparator(field);
break;
case ui::DialogModelField::kTextfield:
AddOrUpdateTextfield(field->AsTextfield(GetPassKey()));
break;
case ui::DialogModelField::kCustom:
std::unique_ptr<View> view =
static_cast<CustomView*>(
field->AsCustomField(GetPassKey())->field(GetPassKey()))
->TransferView();
DCHECK(view);
view->SetProperty(kElementIdentifierKey, field->id(GetPassKey()));
DialogModelHostField info{field, view.get(), nullptr};
AddDialogModelHostField(std::move(view), info);
break;
}
UpdateSpacingAndMargins();
if (GetBubbleFrameView())
SizeToContents();
}
void BubbleDialogModelHost::AddInitialFields() {
DCHECK(contents_view_->children().empty())
<< "This should only be called once.";
const auto& fields = model_->fields(GetPassKey());
for (const auto& field : fields)
OnFieldAdded(field.get());
}
void BubbleDialogModelHost::UpdateSpacingAndMargins() {
LayoutProvider* const layout_provider = LayoutProvider::Get();
gfx::Insets dialog_side_insets =
layout_provider->GetInsetsMetric(InsetsMetric::INSETS_DIALOG);
dialog_side_insets.set_top(0);
dialog_side_insets.set_bottom(0);
ui::DialogModelField* first_field = nullptr;
ui::DialogModelField* last_field = nullptr;
auto* scroll_view =
views::ScrollView::GetScrollViewForContents(contents_view_);
const views::View::Views& children = scroll_view
? scroll_view->contents()->children()
: contents_view_->children();
for (View* const view : children) {
ui::DialogModelField* const field =
FindDialogModelHostField(view).dialog_model_field;
FieldType field_type = GetFieldTypeForField(field, GetPassKey());
gfx::Insets side_insets =
field_type == FieldType::kMenuItem ? gfx::Insets() : dialog_side_insets;
if (!first_field) {
first_field = field;
view->SetProperty(kMarginsKey, side_insets);
} else {
DCHECK(last_field);
FieldType last_field_type =
GetFieldTypeForField(last_field, GetPassKey());
side_insets.set_top(
GetFieldTopMargin(layout_provider, field_type, last_field_type));
view->SetProperty(kMarginsKey, side_insets);
}
last_field = field;
}
contents_view_->InvalidateLayout();
const int extra_margin = scroll_view ? kScrollViewVerticalMargin : 0;
const int top_margin =
GetDialogTopMargins(layout_provider, first_field, GetPassKey()) -
extra_margin;
const int bottom_margin =
GetDialogBottomMargins(layout_provider, last_field,
GetDialogButtons() != ui::DIALOG_BUTTON_NONE,
GetPassKey()) -
extra_margin;
set_margins(gfx::Insets::TLBR(top_margin >= 0 ? top_margin : 0, 0,
bottom_margin >= 0 ? bottom_margin : 0, 0));
}
void BubbleDialogModelHost::OnWindowClosing() {
if (!model_)
return;
model_->OnDialogDestroying(GetPassKey());
}
void BubbleDialogModelHost::AddOrUpdateParagraph(
ui::DialogModelParagraph* model_field) {
std::unique_ptr<View> view =
model_field->header(GetPassKey()).empty()
? CreateViewForLabel(model_field->label(GetPassKey()))
: CreateViewForParagraphWithHeader(model_field->label(GetPassKey()),
model_field->header(GetPassKey()));
DialogModelHostField info{model_field, view.get(), nullptr};
view->SetProperty(kElementIdentifierKey, model_field->id(GetPassKey()));
AddDialogModelHostField(std::move(view), info);
}
void BubbleDialogModelHost::AddOrUpdateCheckbox(
ui::DialogModelCheckbox* model_field) {
std::unique_ptr<CheckboxControl> checkbox;
if (DialogModelLabelRequiresStyledLabel(model_field->label(GetPassKey()))) {
auto label =
CreateStyledLabelForDialogModelLabel(model_field->label(GetPassKey()));
const int line_height = label->GetLineHeight();
checkbox = std::make_unique<CheckboxControl>(std::move(label), line_height);
} else {
auto label =
CreateLabelForDialogModelLabel(model_field->label(GetPassKey()));
const int line_height = label->GetLineHeight();
checkbox = std::make_unique<CheckboxControl>(std::move(label), line_height);
}
checkbox->SetChecked(model_field->is_checked());
checkbox->SetCallback(base::BindRepeating(
[](ui::DialogModelCheckbox* model_field,
base::PassKey<DialogModelHost> pass_key, Checkbox* checkbox,
const ui::Event& event) {
model_field->OnChecked(pass_key, checkbox->GetChecked());
},
model_field, GetPassKey(), checkbox.get()));
DialogModelHostField info{model_field, checkbox.get(), nullptr};
AddDialogModelHostField(std::move(checkbox), info);
}
void BubbleDialogModelHost::AddOrUpdateCombobox(
ui::DialogModelCombobox* model_field) {
auto combobox = std::make_unique<Combobox>(model_field->combobox_model());
combobox->SetAccessibleName(model_field->accessible_name(GetPassKey()).empty()
? model_field->label(GetPassKey())
: model_field->accessible_name(GetPassKey()));
combobox->SetCallback(base::BindRepeating(
[](ui::DialogModelCombobox* model_field,
base::PassKey<DialogModelHost> pass_key, Combobox* combobox) {
model_field->OnPerformAction(pass_key);
},
model_field, GetPassKey(), combobox.get()));
combobox->SetSelectedIndex(model_field->selected_index());
property_changed_subscriptions_.push_back(
combobox->AddSelectedIndexChangedCallback(base::BindRepeating(
[](ui::DialogModelCombobox* model_field,
base::PassKey<DialogModelHost> pass_key, Combobox* combobox) {
model_field->OnSelectedIndexChanged(
pass_key, combobox->GetSelectedIndex().value());
},
model_field, GetPassKey(), combobox.get())));
const gfx::FontList& font_list = combobox->GetFontList();
AddViewForLabelAndField(model_field, model_field->label(GetPassKey()),
std::move(combobox), font_list);
}
void BubbleDialogModelHost::AddOrUpdateMenuItem(
ui::DialogModelMenuItem* model_field) {
DCHECK(!model_field->is_enabled(GetPassKey()));
auto item = std::make_unique<LabelButton>(
base::BindRepeating(
[](base::PassKey<ui::DialogModelHost> pass_key,
ui::DialogModelMenuItem* model_field, const ui::Event& event) {
model_field->OnActivated(pass_key, event.flags());
},
GetPassKey(), model_field),
model_field->label(GetPassKey()));
item->SetImageModel(Button::STATE_NORMAL, model_field->icon(GetPassKey()));
item->SetBorder(views::CreateEmptyBorder(
gfx::Insets::VH(12 / 2, LayoutProvider::Get()->GetDistanceMetric(
DISTANCE_BUTTON_HORIZONTAL_PADDING))));
item->SetEnabled(model_field->is_enabled(GetPassKey()));
item->SetProperty(kElementIdentifierKey, model_field->id(GetPassKey()));
DialogModelHostField info{model_field, item.get(), nullptr};
AddDialogModelHostField(std::move(item), info);
}
void BubbleDialogModelHost::AddOrUpdateSeparator(
ui::DialogModelField* model_field) {
DCHECK_EQ(ui::DialogModelField::Type::kSeparator,
model_field->type(GetPassKey()));
auto separator = std::make_unique<Separator>();
DialogModelHostField info{model_field, separator.get(), nullptr};
AddDialogModelHostField(std::move(separator), info);
}
void BubbleDialogModelHost::AddOrUpdateTextfield(
ui::DialogModelTextfield* model_field) {
auto textfield = std::make_unique<Textfield>();
textfield->SetAccessibleName(
model_field->accessible_name(GetPassKey()).empty()
? model_field->label(GetPassKey())
: model_field->accessible_name(GetPassKey()));
textfield->SetText(model_field->text());
ui::ElementIdentifier initially_focused_field_id =
model_->initially_focused_field(GetPassKey());
if (initially_focused_field_id &&
model_field->id(GetPassKey()) == initially_focused_field_id) {
textfield->SelectAll(true);
}
property_changed_subscriptions_.push_back(
textfield->AddTextChangedCallback(base::BindRepeating(
[](ui::DialogModelTextfield* model_field,
base::PassKey<DialogModelHost> pass_key, Textfield* textfield) {
model_field->OnTextChanged(pass_key, textfield->GetText());
},
model_field, GetPassKey(), textfield.get())));
const gfx::FontList& font_list = textfield->GetFontList();
AddViewForLabelAndField(model_field, model_field->label(GetPassKey()),
std::move(textfield), font_list);
}
void BubbleDialogModelHost::AddViewForLabelAndField(
ui::DialogModelField* model_field,
const std::u16string& label_text,
std::unique_ptr<View> field,
const gfx::FontList& field_font) {
auto box_layout = std::make_unique<BoxLayoutView>();
box_layout->SetBetweenChildSpacing(LayoutProvider::Get()->GetDistanceMetric(
DISTANCE_RELATED_CONTROL_HORIZONTAL));
DialogModelHostField info{model_field, box_layout.get(), field.get()};
auto label = std::make_unique<Label>(label_text, style::CONTEXT_LABEL,
style::STYLE_PRIMARY);
label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
box_layout->AddChildView(std::make_unique<LayoutConsensusView>(
&textfield_first_column_group_, std::move(label)));
box_layout->SetFlexForView(
box_layout->AddChildView(std::make_unique<LayoutConsensusView>(
&textfield_second_column_group_, std::move(field))),
1);
AddDialogModelHostField(std::move(box_layout), info);
}
void BubbleDialogModelHost::AddDialogModelHostField(
std::unique_ptr<View> view,
const DialogModelHostField& field_view_info) {
DCHECK_EQ(view.get(), field_view_info.field_view);
contents_view_->AddChildView(std::move(view));
AddDialogModelHostFieldForExistingView(field_view_info);
}
void BubbleDialogModelHost::AddDialogModelHostFieldForExistingView(
const DialogModelHostField& field_view_info) {
DCHECK(field_view_info.dialog_model_field);
DCHECK(field_view_info.field_view);
DCHECK(contents_view_->Contains(field_view_info.field_view) ||
field_view_info.field_view == GetOkButton() ||
field_view_info.field_view == GetCancelButton() ||
field_view_info.field_view == GetExtraView());
#if DCHECK_IS_ON()
for (const auto& info : fields_) {
DCHECK_NE(info.field_view, field_view_info.field_view);
DCHECK_NE(info.dialog_model_field, field_view_info.dialog_model_field);
if (info.focusable_view)
DCHECK_NE(info.focusable_view, field_view_info.focusable_view);
}
#endif
fields_.push_back(field_view_info);
View* const target = GetTargetView(field_view_info);
target->SetProperty(kElementIdentifierKey,
field_view_info.dialog_model_field->id(GetPassKey()));
for (const auto& accelerator :
field_view_info.dialog_model_field->accelerators(GetPassKey())) {
target->AddAccelerator(accelerator);
}
}
BubbleDialogModelHost::DialogModelHostField
BubbleDialogModelHost::FindDialogModelHostField(ui::DialogModelField* field) {
for (const auto& info : fields_) {
if (info.dialog_model_field == field)
return info;
}
return {};
}
BubbleDialogModelHost::DialogModelHostField
BubbleDialogModelHost::FindDialogModelHostField(View* view) {
for (const auto& info : fields_) {
if (info.field_view == view)
return info;
}
NOTREACHED_NORETURN();
}
View* BubbleDialogModelHost::GetTargetView(
const DialogModelHostField& field_view_info) {
return field_view_info.focusable_view ? field_view_info.focusable_view.get()
: field_view_info.field_view.get();
}
bool BubbleDialogModelHost::DialogModelLabelRequiresStyledLabel(
const ui::DialogModelLabel& dialog_label) {
return !dialog_label.replacements(GetPassKey()).empty();
}
std::unique_ptr<View> BubbleDialogModelHost::CreateViewForLabel(
const ui::DialogModelLabel& dialog_label) {
if (DialogModelLabelRequiresStyledLabel(dialog_label))
return CreateStyledLabelForDialogModelLabel(dialog_label);
return CreateLabelForDialogModelLabel(dialog_label);
}
std::unique_ptr<StyledLabel>
BubbleDialogModelHost::CreateStyledLabelForDialogModelLabel(
const ui::DialogModelLabel& dialog_label) {
DCHECK(DialogModelLabelRequiresStyledLabel(dialog_label));
const std::vector<ui::DialogModelLabel::TextReplacement>& replacements =
dialog_label.replacements(GetPassKey());
std::vector<std::u16string> string_replacements;
for (auto replacement : replacements) {
string_replacements.push_back(replacement.text());
}
std::vector<size_t> offsets;
const std::u16string text = l10n_util::GetStringFUTF16(
dialog_label.message_id(GetPassKey()), string_replacements, &offsets);
auto styled_label = std::make_unique<StyledLabel>();
styled_label->SetText(text);
styled_label->SetDefaultTextStyle(dialog_label.is_secondary(GetPassKey())
? style::STYLE_SECONDARY
: style::STYLE_PRIMARY);
DCHECK_EQ(string_replacements.size(), offsets.size());
for (size_t i = 0; i < replacements.size(); ++i) {
auto replacement = replacements[i];
if (!replacement.callback().has_value() && !replacement.is_emphasized())
continue;
StyledLabel::RangeStyleInfo style_info;
if (replacement.callback().has_value()) {
style_info = StyledLabel::RangeStyleInfo::CreateForLink(
replacement.callback().value());
style_info.accessible_name = replacement.accessible_name().value();
} else if (replacement.is_emphasized()) {
style_info.text_style = views::style::STYLE_EMPHASIZED;
}
auto offset = offsets[i];
styled_label->AddStyleRange(
gfx::Range(offset, offset + replacement.text().length()), style_info);
}
return styled_label;
}
std::unique_ptr<Label> BubbleDialogModelHost::CreateLabelForDialogModelLabel(
const ui::DialogModelLabel& dialog_label) {
DCHECK(!DialogModelLabelRequiresStyledLabel(dialog_label));
auto text_label = std::make_unique<Label>(
dialog_label.GetString(GetPassKey()), style::CONTEXT_DIALOG_BODY_TEXT,
dialog_label.is_secondary(GetPassKey()) ? style::STYLE_SECONDARY
: style::STYLE_PRIMARY);
text_label->SetMultiLine(true);
text_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
return text_label;
}
std::unique_ptr<View> BubbleDialogModelHost::CreateViewForParagraphWithHeader(
const ui::DialogModelLabel& dialog_label,
const std::u16string header) {
auto view = std::make_unique<BoxLayoutView>();
view->SetOrientation(BoxLayout::Orientation::kVertical);
auto* header_label = view->AddChildView(std::make_unique<Label>(
header, style::CONTEXT_DIALOG_BODY_TEXT, style::STYLE_PRIMARY));
header_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
view->AddChildView(CreateViewForLabel(dialog_label));
return view;
}
bool BubbleDialogModelHost::IsModalDialog() const {
return GetModalType() != ui::MODAL_TYPE_NONE;
}
}