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