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.
5 #include "chrome/browser/ui/views/extensions/extension_install_dialog_view.h"
9 #include "base/basictypes.h"
10 #include "base/command_line.h"
11 #include "base/compiler_specific.h"
12 #include "base/i18n/rtl.h"
13 #include "base/metrics/histogram.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "chrome/browser/browser_process.h"
17 #include "chrome/browser/extensions/api/experience_sampling_private/experience_sampling.h"
18 #include "chrome/browser/extensions/bundle_installer.h"
19 #include "chrome/browser/extensions/extension_install_prompt_show_params.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/browser/ui/browser.h"
22 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
23 #include "chrome/common/extensions/extension_constants.h"
24 #include "chrome/grit/generated_resources.h"
25 #include "chrome/installer/util/browser_distribution.h"
26 #include "components/constrained_window/constrained_window_views.h"
27 #include "content/public/browser/page_navigator.h"
28 #include "content/public/browser/web_contents.h"
29 #include "extensions/common/extension.h"
30 #include "extensions/common/extension_urls.h"
31 #include "ui/base/l10n/l10n_util.h"
32 #include "ui/base/resource/resource_bundle.h"
33 #include "ui/gfx/paint_vector_icon.h"
34 #include "ui/gfx/text_utils.h"
35 #include "ui/gfx/vector_icons_public.h"
36 #include "ui/native_theme/common_theme.h"
37 #include "ui/views/border.h"
38 #include "ui/views/controls/button/image_button.h"
39 #include "ui/views/controls/image_view.h"
40 #include "ui/views/controls/label.h"
41 #include "ui/views/controls/link.h"
42 #include "ui/views/controls/scroll_view.h"
43 #include "ui/views/controls/separator.h"
44 #include "ui/views/layout/box_layout.h"
45 #include "ui/views/layout/grid_layout.h"
46 #include "ui/views/layout/layout_constants.h"
47 #include "ui/views/widget/widget.h"
49 using content::OpenURLParams
;
50 using content::Referrer
;
51 using extensions::BundleInstaller
;
52 using extensions::ExperienceSamplingEvent
;
56 // Width of the bullet column in BulletedView.
57 const int kBulletWidth
= 20;
59 // Size of extension icon in top left of dialog.
60 const int kIconSize
= 64;
62 // Size of the icons of individual extensions for bundle installs.
63 const int kSmallIconSize
= 32;
65 // Padding between extension icon and title for bundle installs.
66 const int kSmallIconPadding
= 6;
68 // The maximum height of the scroll view before it will show a scrollbar.
69 const int kScrollViewMaxHeight
= 250;
71 // Width of the left column of the dialog when the extension requests
73 const int kPermissionsLeftColumnWidth
= 250;
75 // Width of the left column of the dialog when the extension requests no
77 const int kNoPermissionsLeftColumnWidth
= 200;
79 // Width of the left column for external install prompts. The text is long in
80 // this case, so make it wider than normal.
81 const int kExternalInstallLeftColumnWidth
= 350;
83 void AddResourceIcon(const gfx::ImageSkia
* skia_image
, void* data
) {
84 views::View
* parent
= static_cast<views::View
*>(data
);
85 views::ImageView
* image_view
= new views::ImageView();
86 image_view
->SetImage(*skia_image
);
87 parent
->AddChildView(image_view
);
90 // Creates a string for displaying |message| to the user. If it has to look
91 // like a entry in a bullet point list, one is added.
92 base::string16
PrepareForDisplay(const base::string16
& message
,
94 return bullet_point
? l10n_util::GetStringFUTF16(
95 IDS_EXTENSION_PERMISSION_LINE
,
99 void ShowExtensionInstallDialogImpl(
100 ExtensionInstallPromptShowParams
* show_params
,
101 ExtensionInstallPrompt::Delegate
* delegate
,
102 scoped_refptr
<ExtensionInstallPrompt::Prompt
> prompt
) {
103 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
104 ExtensionInstallDialogView
* dialog
=
105 new ExtensionInstallDialogView(show_params
->profile(),
106 show_params
->GetParentWebContents(),
109 constrained_window::CreateBrowserModalDialogViews(
110 dialog
, show_params
->GetParentWindow())->Show();
113 // A custom scrollable view implementation for the dialog.
114 class CustomScrollableView
: public views::View
{
116 CustomScrollableView() {}
117 ~CustomScrollableView() override
{}
119 // Called when one of the child elements has expanded/collapsed.
120 void ChildPreferredSizeChanged(views::View
* child
) override
{
121 PreferredSizeChanged();
125 void Layout() override
{
126 SetBounds(x(), y(), width(), GetHeightForWidth(width()));
127 views::View::Layout();
130 DISALLOW_COPY_AND_ASSIGN(CustomScrollableView
);
135 BulletedView::BulletedView(views::View
* view
) {
136 views::GridLayout
* layout
= new views::GridLayout(this);
137 SetLayoutManager(layout
);
138 views::ColumnSet
* column_set
= layout
->AddColumnSet(0);
139 column_set
->AddColumn(views::GridLayout::CENTER
,
140 views::GridLayout::LEADING
,
142 views::GridLayout::FIXED
,
145 column_set
->AddColumn(views::GridLayout::LEADING
,
146 views::GridLayout::LEADING
,
148 views::GridLayout::USE_PREF
,
149 0, // No fixed width.
151 layout
->StartRow(0, 0);
152 layout
->AddView(new views::Label(PrepareForDisplay(base::string16(), true)));
153 layout
->AddView(view
);
156 IconedView::IconedView(views::View
* view
, const gfx::ImageSkia
& image
) {
157 views::GridLayout
* layout
= new views::GridLayout(this);
158 SetLayoutManager(layout
);
159 views::ColumnSet
* column_set
= layout
->AddColumnSet(0);
160 column_set
->AddColumn(views::GridLayout::CENTER
,
161 views::GridLayout::LEADING
,
163 views::GridLayout::FIXED
,
166 column_set
->AddPaddingColumn(0, kSmallIconPadding
);
167 column_set
->AddColumn(views::GridLayout::LEADING
,
168 views::GridLayout::CENTER
,
170 views::GridLayout::USE_PREF
,
171 0, // No fixed width.
173 layout
->StartRow(0, 0);
175 gfx::Size
size(image
.width(), image
.height());
176 if (size
.width() > kSmallIconSize
|| size
.height() > kSmallIconSize
)
177 size
= gfx::Size(kSmallIconSize
, kSmallIconSize
);
178 views::ImageView
* image_view
= new views::ImageView
;
179 image_view
->SetImage(image
);
180 image_view
->SetImageSize(size
);
182 layout
->AddView(image_view
);
183 layout
->AddView(view
);
186 ExtensionInstallDialogView::ExtensionInstallDialogView(
188 content::PageNavigator
* navigator
,
189 ExtensionInstallPrompt::Delegate
* delegate
,
190 scoped_refptr
<ExtensionInstallPrompt::Prompt
> prompt
)
192 navigator_(navigator
),
197 handled_result_(false) {
201 ExtensionInstallDialogView::~ExtensionInstallDialogView() {
202 if (!handled_result_
)
203 delegate_
->InstallUIAbort(true);
206 void ExtensionInstallDialogView::InitView() {
207 // Possible grid layouts:
208 // With webstore data (inline install, external install, repair)
209 // w/ permissions no permissions
210 // +--------------+------+ +--------------+------+
211 // | title | icon | | title | icon |
212 // +--------------| | +--------------| |
213 // | rating | | | rating | |
214 // +--------------| | +--------------| |
215 // | user_count | | | user_count | |
216 // +--------------| | +--------------| |
217 // | store_link | | | store_link | |
218 // +--------------+------+ +--------------+------+
219 // | separator | | scroll_view (empty) |
220 // +---------------------+ +---------------------+
222 // +---------------------+
224 // No webstore data (all other types)
225 // +--------------+------+
227 // +--------------| |
229 // +--------------+------+
230 // The scroll_view contains permissions (if there are any) and retained
231 // files/devices (if there are any; post-install-permissions prompt only).
232 int left_column_width
=
233 (prompt_
->ShouldShowPermissions() || prompt_
->GetRetainedFileCount() > 0)
234 ? kPermissionsLeftColumnWidth
235 : kNoPermissionsLeftColumnWidth
;
236 if (is_external_install())
237 left_column_width
= kExternalInstallLeftColumnWidth
;
239 int column_set_id
= 0;
240 views::GridLayout
* layout
= CreateLayout(left_column_width
, column_set_id
);
242 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
244 if (prompt_
->has_webstore_data()) {
245 layout
->StartRow(0, column_set_id
);
246 views::View
* rating
= new views::View();
247 rating
->SetLayoutManager(new views::BoxLayout(
248 views::BoxLayout::kHorizontal
, 0, 0, 0));
249 layout
->AddView(rating
);
250 prompt_
->AppendRatingStars(AddResourceIcon
, rating
);
252 const gfx::FontList
& small_font_list
=
253 rb
.GetFontList(ui::ResourceBundle::SmallFont
);
254 views::Label
* rating_count
=
255 new views::Label(prompt_
->GetRatingCount(), small_font_list
);
256 // Add some space between the stars and the rating count.
257 rating_count
->SetBorder(views::Border::CreateEmptyBorder(0, 2, 0, 0));
258 rating
->AddChildView(rating_count
);
260 layout
->StartRow(0, column_set_id
);
261 views::Label
* user_count
=
262 new views::Label(prompt_
->GetUserCount(), small_font_list
);
263 user_count
->SetAutoColorReadabilityEnabled(false);
264 user_count
->SetEnabledColor(SK_ColorGRAY
);
265 layout
->AddView(user_count
);
267 layout
->StartRow(0, column_set_id
);
268 views::Link
* store_link
= new views::Link(
269 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_STORE_LINK
));
270 store_link
->SetFontList(small_font_list
);
271 store_link
->set_listener(this);
272 layout
->AddView(store_link
);
274 if (prompt_
->ShouldShowPermissions()) {
275 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
276 layout
->StartRow(0, column_set_id
);
277 layout
->AddView(new views::Separator(views::Separator::HORIZONTAL
),
280 views::GridLayout::FILL
,
281 views::GridLayout::FILL
);
285 int content_width
= left_column_width
+ views::kPanelHorizMargin
+ kIconSize
;
287 // Create the scrollable view which will contain the permissions and retained
289 CustomScrollableView
* scrollable
= new CustomScrollableView();
290 views::GridLayout
* scroll_layout
= new views::GridLayout(scrollable
);
291 scrollable
->SetLayoutManager(scroll_layout
);
293 views::ColumnSet
* scrollable_column_set
=
294 scroll_layout
->AddColumnSet(column_set_id
);
295 // If we have webstore data, there's a separator below it, so we can span the
296 // whole content width. Otherwise just use the width of the left column so
297 // that we don't overlap the icon.
298 int scrollable_width
= prompt_
->has_webstore_data() ? content_width
300 scrollable_column_set
->AddColumn(views::GridLayout::LEADING
,
301 views::GridLayout::LEADING
,
303 views::GridLayout::USE_PREF
,
306 // Pad to the very right of the dialog, so the scrollbar will be on the edge.
308 content_width
+ views::kButtonHEdgeMarginNew
- scrollable_width
;
309 scrollable_column_set
->AddPaddingColumn(0, padding_width
);
311 layout
->StartRow(0, column_set_id
);
312 scroll_view_
= new views::ScrollView();
313 scroll_view_
->set_hide_horizontal_scrollbar(true);
314 scroll_view_
->SetContents(scrollable
);
315 layout
->AddView(scroll_view_
, 4, 1);
317 if (is_bundle_install()) {
318 BundleInstaller::ItemList items
= prompt_
->bundle()->GetItemsWithState(
319 BundleInstaller::Item::STATE_PENDING
);
320 scroll_layout
->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing
);
321 for (const BundleInstaller::Item
& item
: items
) {
322 scroll_layout
->StartRow(0, column_set_id
);
323 views::Label
* extension_label
=
324 new views::Label(item
.GetNameForDisplay());
325 extension_label
->SetMultiLine(true);
326 extension_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
327 extension_label
->SizeToFit(
328 scrollable_width
- kSmallIconSize
- kSmallIconPadding
);
329 gfx::ImageSkia image
= gfx::ImageSkia::CreateFrom1xBitmap(item
.icon
);
330 scroll_layout
->AddView(new IconedView(extension_label
, image
));
332 scroll_layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
335 if (prompt_
->ShouldShowPermissions()) {
336 bool has_permissions
=
337 prompt_
->GetPermissionCount(
338 ExtensionInstallPrompt::PermissionsType::ALL_PERMISSIONS
) > 0;
339 if (has_permissions
) {
345 ExtensionInstallPrompt::PermissionsType::REGULAR_PERMISSIONS
);
351 ExtensionInstallPrompt::PermissionsType::WITHHELD_PERMISSIONS
);
353 scroll_layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
354 scroll_layout
->StartRow(0, column_set_id
);
355 views::Label
* permission_label
= new views::Label(
356 l10n_util::GetStringUTF16(IDS_EXTENSION_NO_SPECIAL_PERMISSIONS
));
357 permission_label
->SetMultiLine(true);
358 permission_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
359 permission_label
->SizeToFit(scrollable_width
);
360 scroll_layout
->AddView(permission_label
);
364 if (prompt_
->GetRetainedFileCount()) {
365 scroll_layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
367 scroll_layout
->StartRow(0, column_set_id
);
368 views::Label
* retained_files_header
=
369 new views::Label(prompt_
->GetRetainedFilesHeading());
370 retained_files_header
->SetMultiLine(true);
371 retained_files_header
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
372 retained_files_header
->SizeToFit(scrollable_width
);
373 scroll_layout
->AddView(retained_files_header
);
375 scroll_layout
->StartRow(0, column_set_id
);
376 PermissionDetails details
;
377 for (size_t i
= 0; i
< prompt_
->GetRetainedFileCount(); ++i
) {
378 details
.push_back(prompt_
->GetRetainedFile(i
));
380 ExpandableContainerView
* issue_advice_view
=
381 new ExpandableContainerView(this,
386 scroll_layout
->AddView(issue_advice_view
);
389 if (prompt_
->GetRetainedDeviceCount()) {
390 scroll_layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
392 scroll_layout
->StartRow(0, column_set_id
);
393 views::Label
* retained_devices_header
=
394 new views::Label(prompt_
->GetRetainedDevicesHeading());
395 retained_devices_header
->SetMultiLine(true);
396 retained_devices_header
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
397 retained_devices_header
->SizeToFit(scrollable_width
);
398 scroll_layout
->AddView(retained_devices_header
);
400 scroll_layout
->StartRow(0, column_set_id
);
401 PermissionDetails details
;
402 for (size_t i
= 0; i
< prompt_
->GetRetainedDeviceCount(); ++i
) {
403 details
.push_back(prompt_
->GetRetainedDeviceMessageString(i
));
405 ExpandableContainerView
* issue_advice_view
=
406 new ExpandableContainerView(this,
411 scroll_layout
->AddView(issue_advice_view
);
414 DCHECK(prompt_
->type() >= 0);
415 UMA_HISTOGRAM_ENUMERATION("Extensions.InstallPrompt.Type",
417 ExtensionInstallPrompt::NUM_PROMPT_TYPES
);
419 scroll_view_
->ClipHeightTo(
421 std::min(kScrollViewMaxHeight
, scrollable
->GetPreferredSize().height()));
423 dialog_size_
= gfx::Size(
424 content_width
+ 2 * views::kButtonHEdgeMarginNew
,
425 container_
->GetPreferredSize().height());
427 std::string event_name
= ExperienceSamplingEvent::kExtensionInstallDialog
;
429 ExtensionInstallPrompt::PromptTypeToString(prompt_
->type()));
430 sampling_event_
= ExperienceSamplingEvent::Create(event_name
);
433 bool ExtensionInstallDialogView::AddPermissions(
434 views::GridLayout
* layout
,
435 ui::ResourceBundle
& rb
,
437 int left_column_width
,
438 ExtensionInstallPrompt::PermissionsType perm_type
) {
439 if (prompt_
->GetPermissionCount(perm_type
) == 0)
442 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
444 layout
->StartRow(0, column_set_id
);
445 views::Label
* permissions_header
= NULL
;
446 if (is_bundle_install()) {
447 // We need to pass the FontList in the constructor, rather than calling
448 // SetFontList later, because otherwise SizeToFit mis-judges the width
451 new views::Label(prompt_
->GetPermissionsHeading(perm_type
),
452 rb
.GetFontList(ui::ResourceBundle::MediumFont
));
455 new views::Label(prompt_
->GetPermissionsHeading(perm_type
));
457 permissions_header
->SetMultiLine(true);
458 permissions_header
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
459 permissions_header
->SizeToFit(left_column_width
);
460 layout
->AddView(permissions_header
);
462 for (size_t i
= 0; i
< prompt_
->GetPermissionCount(perm_type
); ++i
) {
463 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
464 layout
->StartRow(0, column_set_id
);
465 views::Label
* permission_label
=
466 new views::Label(prompt_
->GetPermission(i
, perm_type
));
468 permission_label
->SetMultiLine(true);
469 permission_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
470 permission_label
->SizeToFit(left_column_width
- kBulletWidth
);
471 layout
->AddView(new BulletedView(permission_label
));
473 // If we have more details to provide, show them in collapsed form.
474 if (!prompt_
->GetPermissionsDetails(i
, perm_type
).empty()) {
475 layout
->StartRow(0, column_set_id
);
476 PermissionDetails details
;
477 details
.push_back(PrepareForDisplay(
478 prompt_
->GetPermissionsDetails(i
, perm_type
), false));
479 ExpandableContainerView
* details_container
=
480 new ExpandableContainerView(this,
485 layout
->AddView(details_container
);
491 views::GridLayout
* ExtensionInstallDialogView::CreateLayout(
492 int left_column_width
,
494 container_
= new views::View();
495 // This is basically views::GridLayout::CreatePanel, but without a top or
496 // right margin (we effectively get a top margin anyway from the empty dialog
497 // title, and we add an explicit padding column as a right margin below).
498 views::GridLayout
* layout
= new views::GridLayout(container_
);
499 layout
->SetInsets(0, views::kButtonHEdgeMarginNew
, views::kPanelVertMargin
,
501 container_
->SetLayoutManager(layout
);
502 AddChildView(container_
);
504 views::ColumnSet
* column_set
= layout
->AddColumnSet(column_set_id
);
505 column_set
->AddColumn(views::GridLayout::LEADING
, views::GridLayout::LEADING
,
507 views::GridLayout::USE_PREF
,
510 column_set
->AddPaddingColumn(0, views::kPanelHorizMargin
);
511 column_set
->AddColumn(views::GridLayout::TRAILING
, views::GridLayout::LEADING
,
513 views::GridLayout::USE_PREF
,
516 column_set
->AddPaddingColumn(0, views::kButtonHEdgeMarginNew
);
518 layout
->StartRow(0, column_set_id
);
519 views::Label
* title
=
520 new views::Label(prompt_
->GetDialogTitle(),
521 ui::ResourceBundle::GetSharedInstance().GetFontList(
522 ui::ResourceBundle::MediumFont
));
523 title
->SetMultiLine(true);
524 title
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
525 title
->SizeToFit(left_column_width
);
526 layout
->AddView(title
);
528 // Scale down to icon size, but allow smaller icons (don't scale up).
529 const gfx::ImageSkia
* image
= prompt_
->icon().ToImageSkia();
530 gfx::Size
size(image
->width(), image
->height());
531 if (size
.width() > kIconSize
|| size
.height() > kIconSize
)
532 size
= gfx::Size(kIconSize
, kIconSize
);
533 views::ImageView
* icon
= new views::ImageView();
534 icon
->SetImageSize(size
);
535 icon
->SetImage(*image
);
537 int icon_row_span
= 1; // Always span the title.
538 if (prompt_
->has_webstore_data()) {
539 // Also span the rating, user_count and store_link rows.
541 // Note: Do not span the permissions here, there's a separator in between!
543 // Also span the scrollable container with permissions, retained files etc.
546 layout
->AddView(icon
, 1, icon_row_span
);
551 int ExtensionInstallDialogView::GetDialogButtons() const {
552 int buttons
= prompt_
->GetDialogButtons();
553 // Simply having just an OK button is *not* supported. See comment on function
554 // GetDialogButtons in dialog_delegate.h for reasons.
555 DCHECK_GT(buttons
& ui::DIALOG_BUTTON_CANCEL
, 0);
559 base::string16
ExtensionInstallDialogView::GetDialogButtonLabel(
560 ui::DialogButton button
) const {
562 case ui::DIALOG_BUTTON_OK
:
563 return prompt_
->GetAcceptButtonLabel();
564 case ui::DIALOG_BUTTON_CANCEL
:
565 return prompt_
->GetAbortButtonLabel();
568 return base::string16();
572 int ExtensionInstallDialogView::GetDefaultDialogButton() const {
573 return ui::DIALOG_BUTTON_CANCEL
;
576 bool ExtensionInstallDialogView::Cancel() {
580 handled_result_
= true;
581 UpdateInstallResultHistogram(false);
583 sampling_event_
->CreateUserDecisionEvent(ExperienceSamplingEvent::kDeny
);
584 delegate_
->InstallUIAbort(true);
588 bool ExtensionInstallDialogView::Accept() {
589 DCHECK(!handled_result_
);
591 handled_result_
= true;
592 UpdateInstallResultHistogram(true);
594 sampling_event_
->CreateUserDecisionEvent(ExperienceSamplingEvent::kProceed
);
595 delegate_
->InstallUIProceed();
599 ui::ModalType
ExtensionInstallDialogView::GetModalType() const {
600 return ui::MODAL_TYPE_WINDOW
;
603 void ExtensionInstallDialogView::LinkClicked(views::Link
* source
,
605 GURL
store_url(extension_urls::GetWebstoreItemDetailURLPrefix() +
606 prompt_
->extension()->id());
607 OpenURLParams
params(
608 store_url
, Referrer(), NEW_FOREGROUND_TAB
,
609 ui::PAGE_TRANSITION_LINK
,
613 navigator_
->OpenURL(params
);
615 chrome::ScopedTabbedBrowserDisplayer
displayer(
616 profile_
, chrome::GetActiveDesktop());
617 displayer
.browser()->OpenURL(params
);
619 GetWidget()->Close();
622 void ExtensionInstallDialogView::Layout() {
623 container_
->SetBounds(0, 0, width(), height());
624 DialogDelegateView::Layout();
627 gfx::Size
ExtensionInstallDialogView::GetPreferredSize() const {
631 void ExtensionInstallDialogView::UpdateInstallResultHistogram(bool accepted
)
633 if (prompt_
->type() == ExtensionInstallPrompt::INSTALL_PROMPT
)
634 UMA_HISTOGRAM_BOOLEAN("Extensions.InstallPrompt.Accepted", accepted
);
637 // ExpandableContainerView::DetailsView ----------------------------------------
639 ExpandableContainerView::DetailsView::DetailsView(int horizontal_space
,
640 bool parent_bulleted
)
641 : layout_(new views::GridLayout(this)),
643 SetLayoutManager(layout_
);
644 views::ColumnSet
* column_set
= layout_
->AddColumnSet(0);
645 // If the parent is using bullets for its items, then a padding of one unit
646 // will make the child item (which has no bullet) look like a sibling of its
647 // parent. Therefore increase the indentation by one more unit to show that it
648 // is in fact a child item (with no missing bullet) and not a sibling.
650 views::kRelatedControlHorizontalSpacing
* (parent_bulleted
? 2 : 1);
651 column_set
->AddPaddingColumn(0, padding
);
652 column_set
->AddColumn(views::GridLayout::LEADING
,
653 views::GridLayout::LEADING
,
655 views::GridLayout::FIXED
,
656 horizontal_space
- padding
,
660 void ExpandableContainerView::DetailsView::AddDetail(
661 const base::string16
& detail
) {
662 layout_
->StartRowWithPadding(0, 0,
663 0, views::kRelatedControlSmallVerticalSpacing
);
664 views::Label
* detail_label
=
665 new views::Label(PrepareForDisplay(detail
, false));
666 detail_label
->SetMultiLine(true);
667 detail_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
668 layout_
->AddView(detail_label
);
671 gfx::Size
ExpandableContainerView::DetailsView::GetPreferredSize() const {
672 gfx::Size size
= views::View::GetPreferredSize();
673 return gfx::Size(size
.width(), size
.height() * state_
);
676 void ExpandableContainerView::DetailsView::AnimateToState(double state
) {
678 PreferredSizeChanged();
682 // ExpandableContainerView -----------------------------------------------------
684 ExpandableContainerView::ExpandableContainerView(
685 ExtensionInstallDialogView
* owner
,
686 const base::string16
& description
,
687 const PermissionDetails
& details
,
688 int horizontal_space
,
689 bool parent_bulleted
)
692 slide_animation_(this),
696 views::GridLayout
* layout
= new views::GridLayout(this);
697 SetLayoutManager(layout
);
698 int column_set_id
= 0;
699 views::ColumnSet
* column_set
= layout
->AddColumnSet(column_set_id
);
700 column_set
->AddColumn(views::GridLayout::LEADING
,
701 views::GridLayout::LEADING
,
703 views::GridLayout::USE_PREF
,
706 if (!description
.empty()) {
707 layout
->StartRow(0, column_set_id
);
709 views::Label
* description_label
= new views::Label(description
);
710 description_label
->SetMultiLine(true);
711 description_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
712 description_label
->SizeToFit(horizontal_space
);
713 layout
->AddView(new BulletedView(description_label
));
719 details_view_
= new DetailsView(horizontal_space
, parent_bulleted
);
721 layout
->StartRow(0, column_set_id
);
722 layout
->AddView(details_view_
);
724 for (size_t i
= 0; i
< details
.size(); ++i
)
725 details_view_
->AddDetail(details
[i
]);
727 // Make sure the link width column is as wide as needed for both Show and
728 // Hide details, so that the arrow doesn't shift horizontally when we
730 views::Link
* link
= new views::Link(
731 l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS
));
732 int link_col_width
= link
->GetPreferredSize().width();
733 link
->SetText(l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS
));
734 link_col_width
= std::max(link_col_width
, link
->GetPreferredSize().width());
736 column_set
= layout
->AddColumnSet(++column_set_id
);
737 // Padding to the left of the More Details column. If the parent is using
738 // bullets for its items, then a padding of one unit will make the child
739 // item (which has no bullet) look like a sibling of its parent. Therefore
740 // increase the indentation by one more unit to show that it is in fact a
741 // child item (with no missing bullet) and not a sibling.
742 column_set
->AddPaddingColumn(
743 0, views::kRelatedControlHorizontalSpacing
* (parent_bulleted
? 2 : 1));
744 // The More Details column.
745 column_set
->AddColumn(views::GridLayout::LEADING
,
746 views::GridLayout::LEADING
,
748 views::GridLayout::FIXED
,
751 // The Up/Down arrow column.
752 column_set
->AddColumn(views::GridLayout::LEADING
,
753 views::GridLayout::TRAILING
,
755 views::GridLayout::USE_PREF
,
759 // Add the More Details link.
760 layout
->StartRow(0, column_set_id
);
761 more_details_
= link
;
762 more_details_
->set_listener(this);
763 more_details_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
764 layout
->AddView(more_details_
);
766 // Add the arrow after the More Details link.
767 arrow_toggle_
= new views::ImageButton(this);
768 UpdateArrowToggle(false);
769 layout
->AddView(arrow_toggle_
);
772 ExpandableContainerView::~ExpandableContainerView() {
775 void ExpandableContainerView::ButtonPressed(
776 views::Button
* sender
, const ui::Event
& event
) {
780 void ExpandableContainerView::LinkClicked(
781 views::Link
* source
, int event_flags
) {
785 void ExpandableContainerView::AnimationProgressed(
786 const gfx::Animation
* animation
) {
787 DCHECK_EQ(&slide_animation_
, animation
);
789 details_view_
->AnimateToState(animation
->GetCurrentValue());
792 void ExpandableContainerView::AnimationEnded(const gfx::Animation
* animation
) {
794 UpdateArrowToggle(animation
->GetCurrentValue() != 0.0);
796 more_details_
->SetText(expanded_
?
797 l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS
) :
798 l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS
));
802 void ExpandableContainerView::ChildPreferredSizeChanged(views::View
* child
) {
803 PreferredSizeChanged();
806 void ExpandableContainerView::ToggleDetailLevel() {
807 expanded_
= !expanded_
;
809 if (slide_animation_
.IsShowing())
810 slide_animation_
.Hide();
812 slide_animation_
.Show();
815 void ExpandableContainerView::UpdateArrowToggle(bool expanded
) {
817 ui::CommonThemeGetSystemColor(ui::NativeTheme::kColorId_ChromeIconGrey
,
819 gfx::ImageSkia icon
= gfx::CreateVectorIcon(expanded
?
820 gfx::VectorIconId::FIND_PREV
:
821 gfx::VectorIconId::FIND_NEXT
,
824 arrow_toggle_
->SetImage(
825 views::Button::STATE_NORMAL
,
830 ExtensionInstallPrompt::ShowDialogCallback
831 ExtensionInstallPrompt::GetDefaultShowDialogCallback() {
832 return base::Bind(&ShowExtensionInstallDialogImpl
);