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