1 // Copyright (c) 2012 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 "chrome/browser/ui/views/extensions/media_galleries_dialog_views.h"
6 
7 #include <stddef.h>
8 
9 #include "base/bind.h"
10 #include "base/macros.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "build/build_config.h"
13 #include "chrome/browser/ui/browser_dialogs.h"
14 #include "chrome/browser/ui/views/chrome_layout_provider.h"
15 #include "chrome/browser/ui/views/extensions/media_gallery_checkbox_view.h"
16 #include "chrome/grit/generated_resources.h"
17 #include "chrome/grit/locale_settings.h"
18 #include "components/constrained_window/constrained_window_views.h"
19 #include "components/web_modal/web_contents_modal_dialog_manager.h"
20 #include "content/public/browser/web_contents.h"
21 #include "ui/base/l10n/l10n_util.h"
22 #include "ui/gfx/geometry/insets.h"
23 #include "ui/native_theme/native_theme.h"
24 #include "ui/views/border.h"
25 #include "ui/views/controls/button/checkbox.h"
26 #include "ui/views/controls/button/image_button.h"
27 #include "ui/views/controls/button/md_text_button.h"
28 #include "ui/views/controls/label.h"
29 #include "ui/views/controls/menu/menu_runner.h"
30 #include "ui/views/controls/scroll_view.h"
31 #include "ui/views/controls/separator.h"
32 #include "ui/views/layout/box_layout.h"
33 #include "ui/views/layout/grid_layout.h"
34 #include "ui/views/view.h"
35 
36 namespace {
37 
38 const int kScrollAreaHeight = 192;
39 
40 // This container has the right Layout() impl to use within a ScrollView.
41 class ScrollableView : public views::View {
42  public:
ScrollableView()43   ScrollableView() {}
~ScrollableView()44   ~ScrollableView() override {}
45 
46   void Layout() override;
47 
48  private:
49   DISALLOW_COPY_AND_ASSIGN(ScrollableView);
50 };
51 
Layout()52 void ScrollableView::Layout() {
53   gfx::Size pref = GetPreferredSize();
54   int width = pref.width();
55   int height = pref.height();
56   if (parent()) {
57     width = parent()->width();
58     height = std::max(parent()->height(), height);
59   }
60   SetBounds(x(), y(), width, height);
61 
62   views::View::Layout();
63 }
64 
65 }  // namespace
66 
MediaGalleriesDialogViews(MediaGalleriesDialogController * controller)67 MediaGalleriesDialogViews::MediaGalleriesDialogViews(
68     MediaGalleriesDialogController* controller)
69     : controller_(controller),
70       contents_(new views::View()),
71       auxiliary_button_(nullptr),
72       confirm_available_(false),
73       accepted_(false) {
74   SetButtonLabel(ui::DIALOG_BUTTON_OK, controller_->GetAcceptButtonText());
75   SetAcceptCallback(base::BindOnce(
76       [](MediaGalleriesDialogViews* dialog) { dialog->accepted_ = true; },
77       base::Unretained(this)));
78   SetShowCloseButton(false);
79   SetTitle(controller_->GetHeader());
80 
81   base::string16 label = controller_->GetAuxiliaryButtonText();
82   if (!label.empty()) {
83     auxiliary_button_ = SetExtraView(std::make_unique<views::MdTextButton>(
84         base::BindRepeating(
85             &MediaGalleriesDialogViews::ButtonPressed, base::Unretained(this),
86             base::BindRepeating(
87                 &MediaGalleriesDialogController::DidClickAuxiliaryButton,
88                 base::Unretained(controller_))),
89         label));
90   }
91 
92   InitChildViews();
93   if (ControllerHasWebContents()) {
94     constrained_window::ShowWebModalDialogViews(this,
95                                                 controller->WebContents());
96   }
97   chrome::RecordDialogCreation(chrome::DialogIdentifier::MEDIA_GALLERIES);
98 }
99 
~MediaGalleriesDialogViews()100 MediaGalleriesDialogViews::~MediaGalleriesDialogViews() {
101   if (!ControllerHasWebContents())
102     delete contents_;
103 }
104 
AcceptDialogForTesting()105 void MediaGalleriesDialogViews::AcceptDialogForTesting() {
106   accepted_ = true;
107 
108   web_modal::WebContentsModalDialogManager* manager =
109       web_modal::WebContentsModalDialogManager::FromWebContents(
110           controller_->WebContents());
111   DCHECK(manager);
112   web_modal::WebContentsModalDialogManager::TestApi(manager).CloseAllDialogs();
113 }
114 
InitChildViews()115 void MediaGalleriesDialogViews::InitChildViews() {
116   // Outer dialog layout.
117   contents_->RemoveAllChildViews(true);
118   checkbox_map_.clear();
119 
120   ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();
121   contents_->SetBorder(views::CreateEmptyBorder(
122       provider->GetDialogInsetsForContentType(views::TEXT, views::CONTROL)));
123 
124   const int dialog_content_width = views::Widget::GetLocalizedContentsWidth(
125       IDS_MEDIA_GALLERIES_DIALOG_CONTENT_WIDTH_CHARS);
126   views::GridLayout* layout =
127       contents_->SetLayoutManager(std::make_unique<views::GridLayout>());
128 
129   int column_set_id = 0;
130   views::ColumnSet* columns = layout->AddColumnSet(column_set_id);
131   columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::LEADING,
132                      1.0, views::GridLayout::ColumnSize::kFixed,
133                      dialog_content_width, 0);
134 
135   // Message text.
136   const int vertical_padding =
137       provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_VERTICAL);
138   auto subtext = std::make_unique<views::Label>(controller_->GetSubtext());
139   subtext->SetMultiLine(true);
140   subtext->SetHorizontalAlignment(gfx::ALIGN_LEFT);
141   // Get the height here rather than inline because the order of evaluation for
142   // parameters may differ between platforms.
143   const int subtext_height = subtext->GetHeightForWidth(dialog_content_width);
144   layout->StartRow(views::GridLayout::kFixedSize, column_set_id);
145   layout->AddView(std::move(subtext), 1, 1, views::GridLayout::FILL,
146                   views::GridLayout::LEADING, dialog_content_width,
147                   subtext_height);
148   layout->AddPaddingRow(views::GridLayout::kFixedSize, vertical_padding);
149 
150   // Scrollable area for checkboxes.
151   const int small_vertical_padding =
152       provider->GetDistanceMetric(DISTANCE_RELATED_CONTROL_VERTICAL_SMALL);
153   auto scroll_container = std::make_unique<ScrollableView>();
154   scroll_container->SetLayoutManager(std::make_unique<views::BoxLayout>(
155       views::BoxLayout::Orientation::kVertical, gfx::Insets(),
156       small_vertical_padding));
157   scroll_container->SetBorder(
158       views::CreateEmptyBorder(vertical_padding, 0, vertical_padding, 0));
159 
160   std::vector<base::string16> section_headers =
161       controller_->GetSectionHeaders();
162   for (size_t i = 0; i < section_headers.size(); i++) {
163     MediaGalleriesDialogController::Entries entries =
164         controller_->GetSectionEntries(i);
165 
166     // Header and separator line.
167     if (!section_headers[i].empty() && !entries.empty()) {
168       scroll_container->AddChildView(std::make_unique<views::Separator>());
169 
170       auto header = std::make_unique<views::Label>(section_headers[i]);
171       header->SetMultiLine(true);
172       header->SetHorizontalAlignment(gfx::ALIGN_LEFT);
173       header->SetBorder(views::CreateEmptyBorder(
174           vertical_padding,
175           provider->GetInsetsMetric(views::INSETS_DIALOG).left(),
176           vertical_padding, 0));
177       scroll_container->AddChildView(std::move(header));
178     }
179 
180     // Checkboxes.
181     MediaGalleriesDialogController::Entries::const_iterator iter;
182     for (iter = entries.begin(); iter != entries.end(); ++iter) {
183       int spacing = iter + 1 == entries.end() ? small_vertical_padding : 0;
184       AddOrUpdateGallery(*iter, scroll_container.get(), spacing);
185     }
186   }
187 
188   confirm_available_ = controller_->IsAcceptAllowed();
189 
190   // Add the scrollable area to the outer dialog view. It will squeeze against
191   // the title/subtitle and buttons to occupy all available space in the dialog.
192   auto scroll_view = views::ScrollView::CreateScrollViewWithBorder();
193   scroll_view->SetContents(std::move(scroll_container));
194   layout->StartRowWithPadding(1.0, column_set_id, views::GridLayout::kFixedSize,
195                               vertical_padding);
196   layout->AddView(std::move(scroll_view), 1.0, 1.0, views::GridLayout::FILL,
197                   views::GridLayout::FILL, dialog_content_width,
198                   kScrollAreaHeight);
199 }
200 
UpdateGalleries()201 void MediaGalleriesDialogViews::UpdateGalleries() {
202   InitChildViews();
203   contents_->Layout();
204 
205   if (ControllerHasWebContents())
206     DialogModelChanged();
207 }
208 
AddOrUpdateGallery(const MediaGalleriesDialogController::Entry & gallery,views::View * container,int trailing_vertical_space)209 bool MediaGalleriesDialogViews::AddOrUpdateGallery(
210     const MediaGalleriesDialogController::Entry& gallery,
211     views::View* container,
212     int trailing_vertical_space) {
213   auto iter = checkbox_map_.find(gallery.pref_info.pref_id);
214   if (iter != checkbox_map_.end()) {
215     views::Checkbox* checkbox = iter->second->checkbox();
216     checkbox->SetChecked(gallery.selected);
217     checkbox->SetText(gallery.pref_info.GetGalleryDisplayName());
218     checkbox->SetTooltipText(gallery.pref_info.GetGalleryTooltip());
219     base::string16 details = gallery.pref_info.GetGalleryAdditionalDetails();
220     iter->second->secondary_text()->SetText(details);
221     iter->second->secondary_text()->SetVisible(details.length() > 0);
222     return false;
223   }
224 
225   auto* gallery_view =
226       container->AddChildView(std::make_unique<MediaGalleryCheckboxView>(
227           gallery.pref_info, trailing_vertical_space, this));
228   gallery_view->checkbox()->SetCallback(base::BindRepeating(
229       &MediaGalleriesDialogViews::ButtonPressed, base::Unretained(this),
230       base::BindRepeating(
231           [](MediaGalleriesDialogController* controller,
232              MediaGalleryPrefId pref_id, views::Checkbox* checkbox) {
233             controller->DidToggleEntry(pref_id, checkbox->GetChecked());
234           },
235           controller_, gallery.pref_info.pref_id, gallery_view->checkbox())));
236   gallery_view->checkbox()->SetChecked(gallery.selected);
237   checkbox_map_[gallery.pref_info.pref_id] = gallery_view;
238   return true;
239 }
240 
DeleteDelegate()241 void MediaGalleriesDialogViews::DeleteDelegate() {
242   controller_->DialogFinished(accepted_);
243 }
244 
GetWidget()245 views::Widget* MediaGalleriesDialogViews::GetWidget() {
246   return contents_->GetWidget();
247 }
248 
GetWidget() const249 const views::Widget* MediaGalleriesDialogViews::GetWidget() const {
250   return contents_->GetWidget();
251 }
252 
GetContentsView()253 views::View* MediaGalleriesDialogViews::GetContentsView() {
254   return contents_;
255 }
256 
IsDialogButtonEnabled(ui::DialogButton button) const257 bool MediaGalleriesDialogViews::IsDialogButtonEnabled(
258     ui::DialogButton button) const {
259   return button != ui::DIALOG_BUTTON_OK || confirm_available_;
260 }
261 
GetModalType() const262 ui::ModalType MediaGalleriesDialogViews::GetModalType() const {
263   return ui::MODAL_TYPE_CHILD;
264 }
265 
ShowContextMenuForViewImpl(views::View * source,const gfx::Point & point,ui::MenuSourceType source_type)266 void MediaGalleriesDialogViews::ShowContextMenuForViewImpl(
267     views::View* source,
268     const gfx::Point& point,
269     ui::MenuSourceType source_type) {
270   for (CheckboxMap::const_iterator iter = checkbox_map_.begin();
271        iter != checkbox_map_.end(); ++iter) {
272     if (iter->second->Contains(source)) {
273       ShowContextMenu(point, source_type, iter->first);
274       return;
275     }
276   }
277 }
278 
ShowContextMenu(const gfx::Point & point,ui::MenuSourceType source_type,MediaGalleryPrefId id)279 void MediaGalleriesDialogViews::ShowContextMenu(const gfx::Point& point,
280                                                 ui::MenuSourceType source_type,
281                                                 MediaGalleryPrefId id) {
282   context_menu_runner_ = std::make_unique<views::MenuRunner>(
283       controller_->GetContextMenu(id),
284       views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU,
285       base::BindRepeating(&MediaGalleriesDialogViews::OnMenuClosed,
286                           base::Unretained(this)));
287 
288   context_menu_runner_->RunMenuAt(
289       GetWidget(), nullptr,
290       gfx::Rect(point.x(), point.y(), views::GridLayout::kFixedSize, 0),
291       views::MenuAnchorPosition::kTopLeft, source_type);
292 }
293 
ControllerHasWebContents() const294 bool MediaGalleriesDialogViews::ControllerHasWebContents() const {
295   return controller_->WebContents() != nullptr;
296 }
297 
ButtonPressed(base::RepeatingClosure closure)298 void MediaGalleriesDialogViews::ButtonPressed(base::RepeatingClosure closure) {
299   confirm_available_ = true;
300 
301   if (ControllerHasWebContents())
302     DialogModelChanged();
303 
304   closure.Run();
305 }
306 
OnMenuClosed()307 void MediaGalleriesDialogViews::OnMenuClosed() {
308   context_menu_runner_.reset();
309 }
310 
311 // MediaGalleriesDialogViewsController -----------------------------------------
312 
313 // static
Create(MediaGalleriesDialogController * controller)314 MediaGalleriesDialog* MediaGalleriesDialog::Create(
315     MediaGalleriesDialogController* controller) {
316   return new MediaGalleriesDialogViews(controller);
317 }
318