1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/views/bubble/bubble_dialog_model_host.h"
6
7 #include <utility>
8
9 #include "ui/accessibility/ax_enums.mojom.h"
10 #include "ui/base/class_property.h"
11 #include "ui/base/l10n/l10n_util.h"
12 #include "ui/base/models/combobox_model.h"
13 #include "ui/views/controls/button/checkbox.h"
14 #include "ui/views/controls/button/label_button_border.h"
15 #include "ui/views/controls/button/md_text_button.h"
16 #include "ui/views/controls/combobox/combobox.h"
17 #include "ui/views/controls/label.h"
18 #include "ui/views/controls/styled_label.h"
19 #include "ui/views/controls/textfield/textfield.h"
20 #include "ui/views/layout/box_layout.h"
21 #include "ui/views/layout/box_layout_view.h"
22 #include "ui/views/layout/fill_layout.h"
23 #include "ui/views/layout/layout_provider.h"
24 #include "ui/views/metadata/metadata_impl_macros.h"
25 #include "ui/views/view_class_properties.h"
26
27 namespace views {
28 namespace {
29
FieldTypeToContentType(ui::DialogModelField::Type type)30 DialogContentType FieldTypeToContentType(ui::DialogModelField::Type type) {
31 switch (type) {
32 case ui::DialogModelField::kButton:
33 return DialogContentType::CONTROL;
34 case ui::DialogModelField::kBodyText:
35 return DialogContentType::TEXT;
36 case ui::DialogModelField::kCheckbox:
37 return DialogContentType::CONTROL;
38 case ui::DialogModelField::kTextfield:
39 return DialogContentType::CONTROL;
40 case ui::DialogModelField::kCombobox:
41 return DialogContentType::CONTROL;
42 }
43 NOTREACHED();
44 return DialogContentType::CONTROL;
45 }
46
47 // A subclass of Checkbox that allows using an external Label/StyledLabel view
48 // instead of LabelButton's internal label. This is required for the
49 // Label/StyledLabel to be clickable, while supporting Links which requires a
50 // StyledLabel.
51 class CheckboxControl : public Checkbox {
52 public:
CheckboxControl(std::unique_ptr<View> label,int label_line_height)53 CheckboxControl(std::unique_ptr<View> label, int label_line_height)
54 : label_line_height_(label_line_height) {
55 auto* layout = SetLayoutManager(std::make_unique<BoxLayout>());
56 layout->set_between_child_spacing(LayoutProvider::Get()->GetDistanceMetric(
57 views::DISTANCE_RELATED_LABEL_HORIZONTAL));
58 layout->set_cross_axis_alignment(
59 views::BoxLayout::CrossAxisAlignment::kStart);
60
61 SetAssociatedLabel(label.get());
62
63 AddChildView(std::move(label));
64 }
65
Layout()66 void Layout() override {
67 // Skip LabelButton to use LayoutManager.
68 View::Layout();
69 }
70
CalculatePreferredSize() const71 gfx::Size CalculatePreferredSize() const override {
72 // Skip LabelButton to use LayoutManager.
73 return View::CalculatePreferredSize();
74 }
75
GetHeightForWidth(int width) const76 int GetHeightForWidth(int width) const override {
77 // Skip LabelButton to use LayoutManager.
78 return View::GetHeightForWidth(width);
79 }
80
OnThemeChanged()81 void OnThemeChanged() override {
82 Checkbox::OnThemeChanged();
83 // This offsets the image to align with the first line of text. See
84 // LabelButton::Layout().
85 image()->SetBorder(CreateEmptyBorder(gfx::Insets(
86 (label_line_height_ - image()->GetPreferredSize().height()) / 2, 0, 0,
87 0)));
88 }
89
90 const int label_line_height_;
91 };
92
93 } // namespace
94
95 class BubbleDialogModelHost::LayoutConsensusView : public View {
96 public:
LayoutConsensusView(LayoutConsensusGroup * group,std::unique_ptr<View> view)97 LayoutConsensusView(LayoutConsensusGroup* group, std::unique_ptr<View> view)
98 : group_(group) {
99 group->AddView(this);
100 SetLayoutManager(std::make_unique<FillLayout>());
101 AddChildView(std::move(view));
102 }
103
~LayoutConsensusView()104 ~LayoutConsensusView() override { group_->RemoveView(this); }
105
CalculatePreferredSize() const106 gfx::Size CalculatePreferredSize() const override {
107 const gfx::Size group_preferred_size = group_->GetMaxPreferredSize();
108 DCHECK_EQ(1u, children().size());
109 const gfx::Size child_preferred_size = children()[0]->GetPreferredSize();
110 // TODO(pbos): This uses the max width, but could be configurable to use
111 // either direction.
112 return gfx::Size(group_preferred_size.width(),
113 child_preferred_size.height());
114 }
115
GetMinimumSize() const116 gfx::Size GetMinimumSize() const override {
117 const gfx::Size group_minimum_size = group_->GetMaxMinimumSize();
118 DCHECK_EQ(1u, children().size());
119 const gfx::Size child_minimum_size = children()[0]->GetMinimumSize();
120 // TODO(pbos): This uses the max width, but could be configurable to use
121 // either direction.
122 return gfx::Size(group_minimum_size.width(), child_minimum_size.height());
123 }
124
125 private:
126 LayoutConsensusGroup* const group_;
127 };
128
129 BubbleDialogModelHost::LayoutConsensusGroup::LayoutConsensusGroup() = default;
~LayoutConsensusGroup()130 BubbleDialogModelHost::LayoutConsensusGroup::~LayoutConsensusGroup() {
131 DCHECK(children_.empty());
132 }
133
AddView(LayoutConsensusView * view)134 void BubbleDialogModelHost::LayoutConsensusGroup::AddView(
135 LayoutConsensusView* view) {
136 children_.insert(view);
137 // Because this may change the max preferred/min size, invalidate all child
138 // layouts.
139 for (auto* child : children_)
140 child->InvalidateLayout();
141 }
142
RemoveView(LayoutConsensusView * view)143 void BubbleDialogModelHost::LayoutConsensusGroup::RemoveView(
144 LayoutConsensusView* view) {
145 children_.erase(view);
146 }
147
GetMaxPreferredSize() const148 gfx::Size BubbleDialogModelHost::LayoutConsensusGroup::GetMaxPreferredSize()
149 const {
150 gfx::Size size;
151 for (auto* child : children_) {
152 DCHECK_EQ(1u, child->children().size());
153 size.SetToMax(child->children().front()->GetPreferredSize());
154 }
155 return size;
156 }
157
GetMaxMinimumSize() const158 gfx::Size BubbleDialogModelHost::LayoutConsensusGroup::GetMaxMinimumSize()
159 const {
160 gfx::Size size;
161 for (auto* child : children_) {
162 DCHECK_EQ(1u, child->children().size());
163 size.SetToMax(child->children().front()->GetMinimumSize());
164 }
165 return size;
166 }
167
BubbleDialogModelHost(std::unique_ptr<ui::DialogModel> model,View * anchor_view,BubbleBorder::Arrow arrow)168 BubbleDialogModelHost::BubbleDialogModelHost(
169 std::unique_ptr<ui::DialogModel> model,
170 View* anchor_view,
171 BubbleBorder::Arrow arrow)
172 : BubbleDialogDelegateView(anchor_view, arrow), model_(std::move(model)) {
173 model_->set_host(GetPassKey(), this);
174
175 // Note that between-child spacing is manually handled using kMarginsKey.
176 SetLayoutManager(
177 std::make_unique<BoxLayout>(BoxLayout::Orientation::kVertical));
178
179 // Dialog callbacks can safely refer to |model_|, they can't be called after
180 // Widget::Close() calls WidgetWillClose() synchronously so there shouldn't
181 // be any dangling references after model removal.
182 SetAcceptCallback(base::BindOnce(&ui::DialogModel::OnDialogAccepted,
183 base::Unretained(model_.get()),
184 GetPassKey()));
185 SetCancelCallback(base::BindOnce(&ui::DialogModel::OnDialogCancelled,
186 base::Unretained(model_.get()),
187 GetPassKey()));
188 SetCloseCallback(base::BindOnce(&ui::DialogModel::OnDialogClosed,
189 base::Unretained(model_.get()),
190 GetPassKey()));
191
192 // WindowClosingCallback happens on native widget destruction which is after
193 // |model_| reset. Hence routing this callback through |this| so that we only
194 // forward the call to DialogModel::OnWindowClosing if we haven't already been
195 // closed.
196 RegisterWindowClosingCallback(base::BindOnce(
197 &BubbleDialogModelHost::OnWindowClosing, base::Unretained(this)));
198
199 int button_mask = ui::DIALOG_BUTTON_NONE;
200 auto* ok_button = model_->ok_button(GetPassKey());
201 if (ok_button) {
202 button_mask |= ui::DIALOG_BUTTON_OK;
203 if (!ok_button->label(GetPassKey()).empty())
204 SetButtonLabel(ui::DIALOG_BUTTON_OK, ok_button->label(GetPassKey()));
205 }
206
207 auto* cancel_button = model_->cancel_button(GetPassKey());
208 if (cancel_button) {
209 button_mask |= ui::DIALOG_BUTTON_CANCEL;
210 if (!cancel_button->label(GetPassKey()).empty())
211 SetButtonLabel(ui::DIALOG_BUTTON_CANCEL,
212 cancel_button->label(GetPassKey()));
213 }
214
215 // TODO(pbos): Consider refactoring ::SetExtraView() so it can be called after
216 // the Widget is created and still be picked up. Moving this to
217 // OnDialogInitialized() will not work until then.
218 auto* extra_button = model_->extra_button(GetPassKey());
219 if (extra_button) {
220 SetExtraView(std::make_unique<MdTextButton>(
221 base::BindRepeating(&ui::DialogModelButton::OnPressed,
222 base::Unretained(extra_button), GetPassKey()),
223 extra_button->label(GetPassKey())));
224 }
225
226 SetButtons(button_mask);
227
228 SetTitle(model_->title(GetPassKey()));
229
230 if (model_->override_show_close_button(GetPassKey())) {
231 SetShowCloseButton(*model_->override_show_close_button(GetPassKey()));
232 } else {
233 SetShowCloseButton(!IsModalDialog());
234 }
235
236 if (!model_->icon(GetPassKey()).IsEmpty()) {
237 // TODO(pbos): Consider adding ImageModel support to SetIcon().
238 SetIcon(model_->icon(GetPassKey()).GetImage().AsImageSkia());
239 SetShowIcon(true);
240 }
241
242 if (model_->is_alert_dialog(GetPassKey()))
243 SetAccessibleRole(ax::mojom::Role::kAlertDialog);
244
245 set_close_on_deactivate(model_->close_on_deactivate(GetPassKey()));
246
247 set_fixed_width(LayoutProvider::Get()->GetDistanceMetric(
248 anchor_view ? views::DISTANCE_BUBBLE_PREFERRED_WIDTH
249 : views::DISTANCE_MODAL_DIALOG_PREFERRED_WIDTH));
250
251 AddInitialFields();
252 }
253
~BubbleDialogModelHost()254 BubbleDialogModelHost::~BubbleDialogModelHost() {
255 // Remove children as they may refer to the soon-to-be-destructed model.
256 RemoveAllChildViews(true);
257 }
258
CreateModal(std::unique_ptr<ui::DialogModel> model,ui::ModalType modal_type)259 std::unique_ptr<BubbleDialogModelHost> BubbleDialogModelHost::CreateModal(
260 std::unique_ptr<ui::DialogModel> model,
261 ui::ModalType modal_type) {
262 DCHECK_NE(modal_type, ui::MODAL_TYPE_NONE);
263 auto dialog = std::make_unique<BubbleDialogModelHost>(
264 std::move(model), nullptr, BubbleBorder::Arrow::NONE);
265 dialog->SetModalType(modal_type);
266 return dialog;
267 }
268
GetInitiallyFocusedView()269 View* BubbleDialogModelHost::GetInitiallyFocusedView() {
270 // TODO(pbos): Migrate this override to use
271 // WidgetDelegate::SetInitiallyFocusedView() in constructor once it exists.
272 // TODO(pbos): Try to prevent uses of GetInitiallyFocusedView() after Close()
273 // and turn this in to a DCHECK for |model_| existence. This should fix
274 // https://crbug.com/1130181 for now.
275 if (!model_)
276 return BubbleDialogDelegateView::GetInitiallyFocusedView();
277
278 base::Optional<int> unique_id = model_->initially_focused_field(GetPassKey());
279
280 if (!unique_id)
281 return BubbleDialogDelegateView::GetInitiallyFocusedView();
282
283 return GetTargetView(
284 FindDialogModelHostField(model_->GetFieldByUniqueId(unique_id.value())));
285 }
286
OnDialogInitialized()287 void BubbleDialogModelHost::OnDialogInitialized() {
288 // Dialog buttons are added on dialog initialization.
289 if (GetOkButton()) {
290 AddDialogModelHostFieldForExistingView(
291 {model_->ok_button(GetPassKey()), GetOkButton(), nullptr});
292 }
293
294 if (GetCancelButton()) {
295 AddDialogModelHostFieldForExistingView(
296 {model_->cancel_button(GetPassKey()), GetCancelButton(), nullptr});
297 }
298
299 if (model_->extra_button(GetPassKey())) {
300 DCHECK(GetExtraView());
301 AddDialogModelHostFieldForExistingView(
302 {model_->extra_button(GetPassKey()), GetExtraView(), nullptr});
303 }
304 }
305
Close()306 void BubbleDialogModelHost::Close() {
307 DCHECK(model_);
308 DCHECK(GetWidget());
309 GetWidget()->Close();
310
311 // Synchronously destroy |model_|. Widget::Close() being asynchronous should
312 // not be observable by the model or client code.
313
314 // Notify the model of window closing before destroying it (as if
315 // Widget::Close)
316 model_->OnWindowClosing(GetPassKey());
317
318 // TODO(pbos): Consider turning this into for-each-field remove field.
319 RemoveAllChildViews(true);
320 fields_.clear();
321 model_.reset();
322 }
323
SelectAllText(int unique_id)324 void BubbleDialogModelHost::SelectAllText(int unique_id) {
325 const DialogModelHostField& field_view_info =
326 FindDialogModelHostField(model_->GetFieldByUniqueId(unique_id));
327
328 DCHECK(field_view_info.focusable_view);
329 static_cast<views::Textfield*>(field_view_info.focusable_view)
330 ->SelectAll(false);
331 }
332
OnFieldAdded(ui::DialogModelField * field)333 void BubbleDialogModelHost::OnFieldAdded(ui::DialogModelField* field) {
334 switch (field->type(GetPassKey())) {
335 case ui::DialogModelField::kButton:
336 // TODO(pbos): Add support for buttons that are part of content area.
337 NOTREACHED();
338 return;
339 case ui::DialogModelField::kBodyText:
340 AddOrUpdateBodyText(field->AsBodyText(GetPassKey()));
341 break;
342 case ui::DialogModelField::kCheckbox:
343 AddOrUpdateCheckbox(field->AsCheckbox(GetPassKey()));
344 break;
345 case ui::DialogModelField::kCombobox:
346 AddOrUpdateCombobox(field->AsCombobox(GetPassKey()));
347 break;
348 case ui::DialogModelField::kTextfield:
349 AddOrUpdateTextfield(field->AsTextfield(GetPassKey()));
350 break;
351 }
352 UpdateSpacingAndMargins();
353 }
354
AddInitialFields()355 void BubbleDialogModelHost::AddInitialFields() {
356 DCHECK(children().empty()) << "This should only be called once.";
357
358 const auto& fields = model_->fields(GetPassKey());
359 for (const auto& field : fields)
360 OnFieldAdded(field.get());
361 }
362
UpdateSpacingAndMargins()363 void BubbleDialogModelHost::UpdateSpacingAndMargins() {
364 const DialogContentType first_field_content_type =
365 children().empty()
366 ? DialogContentType::CONTROL
367 : FieldTypeToContentType(FindDialogModelHostField(children().front())
368 .dialog_model_field->type(GetPassKey()));
369 DialogContentType last_field_content_type = first_field_content_type;
370 bool first_row = true;
371 for (View* const view : children()) {
372 const DialogContentType field_content_type = FieldTypeToContentType(
373 FindDialogModelHostField(view).dialog_model_field->type(GetPassKey()));
374
375 if (first_row) {
376 first_row = false;
377 view->SetProperty(kMarginsKey, gfx::Insets());
378 } else {
379 int padding_margin = LayoutProvider::Get()->GetDistanceMetric(
380 DISTANCE_UNRELATED_CONTROL_VERTICAL);
381 if (last_field_content_type == DialogContentType::CONTROL &&
382 field_content_type == DialogContentType::CONTROL) {
383 // TODO(pbos): Move DISTANCE_CONTROL_LIST_VERTICAL to
384 // views::LayoutProvider and replace "12" here.
385 padding_margin = 12;
386 }
387 view->SetProperty(kMarginsKey, gfx::Insets(padding_margin, 0, 0, 0));
388 }
389 last_field_content_type = field_content_type;
390 }
391 InvalidateLayout();
392
393 gfx::Insets margins = LayoutProvider::Get()->GetDialogInsetsForContentType(
394 first_field_content_type, last_field_content_type);
395 if (!model_->icon(GetPassKey()).IsEmpty()) {
396 // If we have a window icon, inset margins additionally to align with
397 // title label.
398 // TODO(pbos): Reconsider this. Aligning with title gives a massive gap on
399 // the left side of the dialog. This style is from
400 // ExtensionUninstallDialogView as part of refactoring it to use
401 // DialogModel.
402 margins.set_left(
403 margins.left() + model_->icon(GetPassKey()).Size().width() +
404 LayoutProvider::Get()->GetInsetsMetric(INSETS_DIALOG_TITLE).left());
405 }
406 set_margins(margins);
407 }
408
OnWindowClosing()409 void BubbleDialogModelHost::OnWindowClosing() {
410 // If the model has been removed we have already notified it of closing on the
411 // ::Close() stack.
412 if (!model_)
413 return;
414 model_->OnWindowClosing(GetPassKey());
415 }
416
AddOrUpdateBodyText(ui::DialogModelBodyText * model_field)417 void BubbleDialogModelHost::AddOrUpdateBodyText(
418 ui::DialogModelBodyText* model_field) {
419 // TODO(pbos): Handle updating existing field.
420 std::unique_ptr<View> view =
421 CreateViewForLabel(model_field->label(GetPassKey()));
422 DialogModelHostField info{model_field, view.get(), nullptr};
423 AddDialogModelHostField(std::move(view), info);
424 }
425
AddOrUpdateCheckbox(ui::DialogModelCheckbox * model_field)426 void BubbleDialogModelHost::AddOrUpdateCheckbox(
427 ui::DialogModelCheckbox* model_field) {
428 // TODO(pbos): Handle updating existing field.
429
430 std::unique_ptr<CheckboxControl> checkbox;
431 if (DialogModelLabelRequiresStyledLabel(model_field->label(GetPassKey()))) {
432 auto label =
433 CreateStyledLabelForDialogModelLabel(model_field->label(GetPassKey()));
434 const int line_height = label->GetLineHeight();
435 checkbox = std::make_unique<CheckboxControl>(std::move(label), line_height);
436 } else {
437 auto label =
438 CreateLabelForDialogModelLabel(model_field->label(GetPassKey()));
439 const int line_height = label->GetLineHeight();
440 checkbox = std::make_unique<CheckboxControl>(std::move(label), line_height);
441 }
442
443 checkbox->SetCallback(base::BindRepeating(
444 [](ui::DialogModelCheckbox* model_field,
445 util::PassKey<DialogModelHost> pass_key, Checkbox* checkbox,
446 const ui::Event& event) {
447 model_field->OnChecked(pass_key, checkbox->GetChecked());
448 },
449 model_field, GetPassKey(), checkbox.get()));
450
451 DialogModelHostField info{model_field, checkbox.get(), nullptr};
452 AddDialogModelHostField(std::move(checkbox), info);
453 }
454
AddOrUpdateCombobox(ui::DialogModelCombobox * model_field)455 void BubbleDialogModelHost::AddOrUpdateCombobox(
456 ui::DialogModelCombobox* model_field) {
457 // TODO(pbos): Handle updating existing field.
458
459 auto combobox = std::make_unique<Combobox>(model_field->combobox_model());
460 combobox->SetAccessibleName(model_field->accessible_name(GetPassKey()).empty()
461 ? model_field->label(GetPassKey())
462 : model_field->accessible_name(GetPassKey()));
463 combobox->SetCallback(base::BindRepeating(
464 [](ui::DialogModelCombobox* model_field,
465 util::PassKey<DialogModelHost> pass_key, Combobox* combobox) {
466 // TODO(pbos): This should be a subscription through the Combobox
467 // directly, but Combobox right now doesn't support listening to
468 // selected-index changes.
469 model_field->OnSelectedIndexChanged(pass_key,
470 combobox->GetSelectedIndex());
471 model_field->OnPerformAction(pass_key);
472 },
473 model_field, GetPassKey(), combobox.get()));
474
475 // TODO(pbos): Add subscription to combobox selected-index changes.
476 combobox->SetSelectedIndex(model_field->selected_index());
477 const gfx::FontList& font_list = combobox->GetFontList();
478 AddViewForLabelAndField(model_field, model_field->label(GetPassKey()),
479 std::move(combobox), font_list);
480 }
481
AddOrUpdateTextfield(ui::DialogModelTextfield * model_field)482 void BubbleDialogModelHost::AddOrUpdateTextfield(
483 ui::DialogModelTextfield* model_field) {
484 // TODO(pbos): Support updates to the existing model.
485
486 auto textfield = std::make_unique<Textfield>();
487 textfield->SetAccessibleName(
488 model_field->accessible_name(GetPassKey()).empty()
489 ? model_field->label(GetPassKey())
490 : model_field->accessible_name(GetPassKey()));
491 textfield->SetText(model_field->text());
492
493 property_changed_subscriptions_.push_back(
494 textfield->AddTextChangedCallback(base::BindRepeating(
495 [](ui::DialogModelTextfield* model_field,
496 util::PassKey<DialogModelHost> pass_key, Textfield* textfield) {
497 model_field->OnTextChanged(pass_key, textfield->GetText());
498 },
499 model_field, GetPassKey(), textfield.get())));
500
501 const gfx::FontList& font_list = textfield->GetFontList();
502 AddViewForLabelAndField(model_field, model_field->label(GetPassKey()),
503 std::move(textfield), font_list);
504 }
505
AddViewForLabelAndField(ui::DialogModelField * model_field,const base::string16 & label_text,std::unique_ptr<View> field,const gfx::FontList & field_font)506 void BubbleDialogModelHost::AddViewForLabelAndField(
507 ui::DialogModelField* model_field,
508 const base::string16& label_text,
509 std::unique_ptr<View> field,
510 const gfx::FontList& field_font) {
511 auto box_layout = std::make_unique<BoxLayoutView>();
512
513 box_layout->SetBetweenChildSpacing(LayoutProvider::Get()->GetDistanceMetric(
514 DISTANCE_RELATED_CONTROL_HORIZONTAL));
515
516 DialogModelHostField info{model_field, box_layout.get(), field.get()};
517
518 auto label = std::make_unique<Label>(label_text, style::CONTEXT_LABEL,
519 style::STYLE_PRIMARY);
520 label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
521
522 box_layout->AddChildView(std::make_unique<LayoutConsensusView>(
523 &textfield_first_column_group_, std::move(label)));
524 box_layout->SetFlexForView(
525 box_layout->AddChildView(std::make_unique<LayoutConsensusView>(
526 &textfield_second_column_group_, std::move(field))),
527 1);
528
529 AddDialogModelHostField(std::move(box_layout), info);
530 }
531
AddDialogModelHostField(std::unique_ptr<View> view,const DialogModelHostField & field_view_info)532 void BubbleDialogModelHost::AddDialogModelHostField(
533 std::unique_ptr<View> view,
534 const DialogModelHostField& field_view_info) {
535 DCHECK_EQ(view.get(), field_view_info.field_view);
536
537 AddChildView(std::move(view));
538 AddDialogModelHostFieldForExistingView(field_view_info);
539 }
540
AddDialogModelHostFieldForExistingView(const DialogModelHostField & field_view_info)541 void BubbleDialogModelHost::AddDialogModelHostFieldForExistingView(
542 const DialogModelHostField& field_view_info) {
543 DCHECK(field_view_info.dialog_model_field);
544 DCHECK(field_view_info.field_view);
545 DCHECK(Contains(field_view_info.field_view) ||
546 field_view_info.field_view == GetOkButton() ||
547 field_view_info.field_view == GetCancelButton() ||
548 field_view_info.field_view == GetExtraView());
549 #if DCHECK_IS_ON()
550 // Make sure none of the info is already in use.
551 for (const auto& info : fields_) {
552 DCHECK_NE(info.field_view, field_view_info.field_view);
553 DCHECK_NE(info.dialog_model_field, field_view_info.dialog_model_field);
554 if (info.focusable_view)
555 DCHECK_NE(info.focusable_view, field_view_info.focusable_view);
556 }
557 #endif // DCHECK_IS_ON()
558 fields_.push_back(field_view_info);
559 View* const target = GetTargetView(field_view_info);
560 for (const auto& accelerator :
561 field_view_info.dialog_model_field->accelerators(GetPassKey())) {
562 target->AddAccelerator(accelerator);
563 }
564 }
565
566 BubbleDialogModelHost::DialogModelHostField
FindDialogModelHostField(ui::DialogModelField * field)567 BubbleDialogModelHost::FindDialogModelHostField(ui::DialogModelField* field) {
568 for (const auto& info : fields_) {
569 if (info.dialog_model_field == field)
570 return info;
571 }
572 NOTREACHED();
573 return {};
574 }
575
576 BubbleDialogModelHost::DialogModelHostField
FindDialogModelHostField(View * view)577 BubbleDialogModelHost::FindDialogModelHostField(View* view) {
578 for (const auto& info : fields_) {
579 if (info.field_view == view)
580 return info;
581 }
582 NOTREACHED();
583 return {};
584 }
585
GetTargetView(const DialogModelHostField & field_view_info)586 View* BubbleDialogModelHost::GetTargetView(
587 const DialogModelHostField& field_view_info) {
588 return field_view_info.focusable_view ? field_view_info.focusable_view
589 : field_view_info.field_view;
590 }
591
DialogModelLabelRequiresStyledLabel(const ui::DialogModelLabel & dialog_label)592 bool BubbleDialogModelHost::DialogModelLabelRequiresStyledLabel(
593 const ui::DialogModelLabel& dialog_label) {
594 // Link support only exists in StyledLabel.
595 return !dialog_label.links(GetPassKey()).empty();
596 }
597
CreateViewForLabel(const ui::DialogModelLabel & dialog_label)598 std::unique_ptr<View> BubbleDialogModelHost::CreateViewForLabel(
599 const ui::DialogModelLabel& dialog_label) {
600 if (DialogModelLabelRequiresStyledLabel(dialog_label))
601 return CreateStyledLabelForDialogModelLabel(dialog_label);
602 return CreateLabelForDialogModelLabel(dialog_label);
603 }
604
605 std::unique_ptr<StyledLabel>
CreateStyledLabelForDialogModelLabel(const ui::DialogModelLabel & dialog_label)606 BubbleDialogModelHost::CreateStyledLabelForDialogModelLabel(
607 const ui::DialogModelLabel& dialog_label) {
608 DCHECK(DialogModelLabelRequiresStyledLabel(dialog_label));
609 // TODO(pbos): Make sure this works for >1 link, it uses .front() now.
610 DCHECK_EQ(dialog_label.links(GetPassKey()).size(), 1u);
611
612 size_t offset;
613 const base::string16 link_text = l10n_util::GetStringUTF16(
614 dialog_label.links(GetPassKey()).front().message_id);
615 const base::string16 text = l10n_util::GetStringFUTF16(
616 dialog_label.message_id(GetPassKey()), link_text, &offset);
617
618 auto styled_label = std::make_unique<StyledLabel>();
619 styled_label->SetText(text);
620 styled_label->AddStyleRange(
621 gfx::Range(offset, offset + link_text.length()),
622 StyledLabel::RangeStyleInfo::CreateForLink(
623 dialog_label.links(GetPassKey()).front().callback));
624
625 styled_label->SetDefaultTextStyle(dialog_label.is_secondary(GetPassKey())
626 ? style::STYLE_SECONDARY
627 : style::STYLE_PRIMARY);
628
629 return styled_label;
630 }
631
CreateLabelForDialogModelLabel(const ui::DialogModelLabel & dialog_label)632 std::unique_ptr<Label> BubbleDialogModelHost::CreateLabelForDialogModelLabel(
633 const ui::DialogModelLabel& dialog_label) {
634 DCHECK(!DialogModelLabelRequiresStyledLabel(dialog_label));
635
636 auto text_label = std::make_unique<Label>(
637 dialog_label.GetString(GetPassKey()), style::CONTEXT_DIALOG_BODY_TEXT,
638 dialog_label.is_secondary(GetPassKey()) ? style::STYLE_SECONDARY
639 : style::STYLE_PRIMARY);
640 text_label->SetMultiLine(true);
641 text_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
642 return text_label;
643 }
644
IsModalDialog() const645 bool BubbleDialogModelHost::IsModalDialog() const {
646 return GetModalType() != ui::MODAL_TYPE_NONE;
647 }
648
649 BEGIN_METADATA(BubbleDialogModelHost, BubbleDialogDelegateView)
650 END_METADATA
651
652 } // namespace views
653