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/profiles/profile_manager.h"
22 #include "chrome/browser/ui/browser.h"
23 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
24 #include "chrome/common/extensions/extension_constants.h"
25 #include "chrome/grit/generated_resources.h"
26 #include "chrome/installer/util/browser_distribution.h"
27 #include "components/constrained_window/constrained_window_views.h"
28 #include "content/public/browser/page_navigator.h"
29 #include "content/public/browser/web_contents.h"
30 #include "extensions/common/extension.h"
31 #include "extensions/common/extension_urls.h"
32 #include "grit/theme_resources.h"
33 #include "ui/base/l10n/l10n_util.h"
34 #include "ui/base/resource/resource_bundle.h"
35 #include "ui/gfx/text_utils.h"
36 #include "ui/views/background.h"
37 #include "ui/views/border.h"
38 #include "ui/views/controls/button/checkbox.h"
39 #include "ui/views/controls/button/image_button.h"
40 #include "ui/views/controls/button/label_button.h"
41 #include "ui/views/controls/image_view.h"
42 #include "ui/views/controls/label.h"
43 #include "ui/views/controls/link.h"
44 #include "ui/views/controls/scroll_view.h"
45 #include "ui/views/controls/separator.h"
46 #include "ui/views/layout/box_layout.h"
47 #include "ui/views/layout/grid_layout.h"
48 #include "ui/views/layout/layout_constants.h"
49 #include "ui/views/widget/widget.h"
50 #include "ui/views/window/dialog_client_view.h"
52 using content::OpenURLParams
;
53 using content::Referrer
;
54 using extensions::BundleInstaller
;
55 using extensions::ExperienceSamplingEvent
;
59 // Width of the bullet column in BulletedView.
60 const int kBulletWidth
= 20;
62 // Size of extension icon in top left of dialog.
63 const int kIconSize
= 64;
65 // We offset the icon a little bit from the right edge of the dialog, to make it
66 // align with the button below it.
67 const int kIconOffset
= 16;
69 // The dialog will resize based on its content, but this sets a maximum height
70 // before overflowing a scrollbar.
71 const int kDialogMaxHeight
= 300;
73 // Width of the left column of the dialog when the extension requests
75 const int kPermissionsLeftColumnWidth
= 250;
77 // Width of the left column of the dialog when the extension requests no
79 const int kNoPermissionsLeftColumnWidth
= 200;
81 // Width of the left column for bundle install prompts. There's only one column
82 // in this case, so make it wider than normal.
83 const int kBundleLeftColumnWidth
= 300;
85 // Width of the left column for external install prompts. The text is long in
86 // this case, so make it wider than normal.
87 const int kExternalInstallLeftColumnWidth
= 350;
89 void AddResourceIcon(const gfx::ImageSkia
* skia_image
, void* data
) {
90 views::View
* parent
= static_cast<views::View
*>(data
);
91 views::ImageView
* image_view
= new views::ImageView();
92 image_view
->SetImage(*skia_image
);
93 parent
->AddChildView(image_view
);
96 // Creates a string for displaying |message| to the user. If it has to look
97 // like a entry in a bullet point list, one is added.
98 base::string16
PrepareForDisplay(const base::string16
& message
,
100 return bullet_point
? l10n_util::GetStringFUTF16(
101 IDS_EXTENSION_PERMISSION_LINE
,
107 BulletedView::BulletedView(views::View
* view
) {
108 views::GridLayout
* layout
= new views::GridLayout(this);
109 SetLayoutManager(layout
);
110 views::ColumnSet
* column_set
= layout
->AddColumnSet(0);
111 column_set
->AddColumn(views::GridLayout::CENTER
,
112 views::GridLayout::LEADING
,
114 views::GridLayout::FIXED
,
117 column_set
->AddColumn(views::GridLayout::LEADING
,
118 views::GridLayout::LEADING
,
120 views::GridLayout::USE_PREF
,
121 0, // No fixed width.
123 layout
->StartRow(0, 0);
124 layout
->AddView(new views::Label(PrepareForDisplay(base::string16(), true)));
125 layout
->AddView(view
);
128 void ShowExtensionInstallDialogImpl(
129 ExtensionInstallPromptShowParams
* show_params
,
130 ExtensionInstallPrompt::Delegate
* delegate
,
131 scoped_refptr
<ExtensionInstallPrompt::Prompt
> prompt
) {
132 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
133 ExtensionInstallDialogView
* dialog
=
134 new ExtensionInstallDialogView(show_params
->profile(),
135 show_params
->GetParentWebContents(),
138 constrained_window::CreateBrowserModalDialogViews(
139 dialog
, show_params
->GetParentWindow())->Show();
142 CustomScrollableView::CustomScrollableView() {}
143 CustomScrollableView::~CustomScrollableView() {}
145 void CustomScrollableView::Layout() {
146 SetBounds(x(), y(), width(), GetHeightForWidth(width()));
147 views::View::Layout();
150 ExtensionInstallDialogView::ExtensionInstallDialogView(
152 content::PageNavigator
* navigator
,
153 ExtensionInstallPrompt::Delegate
* delegate
,
154 scoped_refptr
<ExtensionInstallPrompt::Prompt
> prompt
)
156 navigator_(navigator
),
161 handled_result_(false) {
165 ExtensionInstallDialogView::~ExtensionInstallDialogView() {
166 if (!handled_result_
)
167 delegate_
->InstallUIAbort(true);
170 void ExtensionInstallDialogView::InitView() {
171 // Possible grid layouts:
173 // w/ permissions no permissions
174 // +--------------------+------+ +--------------+------+
175 // | heading | icon | | heading | icon |
176 // +--------------------| | +--------------| |
177 // | rating | | | rating | |
178 // +--------------------| | +--------------+ |
179 // | user_count | | | user_count | |
180 // +--------------------| | +--------------| |
181 // | store_link | | | store_link | |
182 // +--------------------+------+ +--------------+------+
184 // +--------------------+------+
185 // | permissions_header | |
186 // +--------------------+------+
188 // +--------------------+------+
190 // +--------------------+------+
193 // w/ permissions no permissions
194 // +--------------------+------+ +--------------+------+
195 // | heading | icon | | heading | icon |
196 // +--------------------| | +--------------+------+
197 // | permissions_header | |
198 // +--------------------| |
200 // +--------------------| |
202 // +--------------------+------+
203 int left_column_width
=
204 (prompt_
->ShouldShowPermissions() + prompt_
->GetRetainedFileCount()) > 0
205 ? kPermissionsLeftColumnWidth
206 : kNoPermissionsLeftColumnWidth
;
207 if (is_bundle_install())
208 left_column_width
= kBundleLeftColumnWidth
;
209 if (is_external_install())
210 left_column_width
= kExternalInstallLeftColumnWidth
;
212 scroll_view_
= new views::ScrollView();
213 scroll_view_
->set_hide_horizontal_scrollbar(true);
214 AddChildView(scroll_view_
);
216 int column_set_id
= 0;
217 // Create the full scrollable view which will contain all the information
218 // including the permissions.
219 scrollable_
= new CustomScrollableView();
220 views::GridLayout
* layout
= CreateLayout(
221 scrollable_
, left_column_width
, column_set_id
, false);
222 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
224 scroll_view_
->SetContents(scrollable_
);
226 int dialog_width
= left_column_width
+ 2 * views::kPanelHorizMargin
;
227 if (!is_bundle_install())
228 dialog_width
+= views::kPanelHorizMargin
+ kIconSize
+ kIconOffset
;
230 if (prompt_
->has_webstore_data()) {
231 layout
->StartRow(0, column_set_id
);
232 views::View
* rating
= new views::View();
233 rating
->SetLayoutManager(new views::BoxLayout(
234 views::BoxLayout::kHorizontal
, 0, 0, 0));
235 layout
->AddView(rating
);
236 prompt_
->AppendRatingStars(AddResourceIcon
, rating
);
238 const gfx::FontList
& small_font_list
=
239 rb
.GetFontList(ui::ResourceBundle::SmallFont
);
240 views::Label
* rating_count
=
241 new views::Label(prompt_
->GetRatingCount(), small_font_list
);
242 // Add some space between the stars and the rating count.
243 rating_count
->SetBorder(views::Border::CreateEmptyBorder(0, 2, 0, 0));
244 rating
->AddChildView(rating_count
);
246 layout
->StartRow(0, column_set_id
);
247 views::Label
* user_count
=
248 new views::Label(prompt_
->GetUserCount(), small_font_list
);
249 user_count
->SetAutoColorReadabilityEnabled(false);
250 user_count
->SetEnabledColor(SK_ColorGRAY
);
251 layout
->AddView(user_count
);
253 layout
->StartRow(0, column_set_id
);
254 views::Link
* store_link
= new views::Link(
255 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_STORE_LINK
));
256 store_link
->SetFontList(small_font_list
);
257 store_link
->set_listener(this);
258 layout
->AddView(store_link
);
261 if (is_bundle_install()) {
262 BundleInstaller::ItemList items
= prompt_
->bundle()->GetItemsWithState(
263 BundleInstaller::Item::STATE_PENDING
);
264 for (size_t i
= 0; i
< items
.size(); ++i
) {
265 base::string16 extension_name
=
266 base::UTF8ToUTF16(items
[i
].localized_name
);
267 base::i18n::AdjustStringForLocaleDirection(&extension_name
);
268 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
269 layout
->StartRow(0, column_set_id
);
270 views::Label
* extension_label
= new views::Label(
271 PrepareForDisplay(extension_name
, true));
272 extension_label
->SetMultiLine(true);
273 extension_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
274 extension_label
->SizeToFit(left_column_width
);
275 layout
->AddView(extension_label
);
279 bool has_permissions
=
280 prompt_
->GetPermissionCount(
281 ExtensionInstallPrompt::PermissionsType::ALL_PERMISSIONS
) > 0;
282 if (prompt_
->ShouldShowPermissions()) {
288 ExtensionInstallPrompt::PermissionsType::REGULAR_PERMISSIONS
);
294 ExtensionInstallPrompt::PermissionsType::WITHHELD_PERMISSIONS
);
295 if (!has_permissions
) {
296 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
297 layout
->StartRow(0, column_set_id
);
298 views::Label
* permission_label
= new views::Label(
299 l10n_util::GetStringUTF16(IDS_EXTENSION_NO_SPECIAL_PERMISSIONS
));
300 permission_label
->SetMultiLine(true);
301 permission_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
302 permission_label
->SizeToFit(left_column_width
);
303 layout
->AddView(permission_label
);
307 int space_for_files_and_devices
= left_column_width
;
308 if (prompt_
->GetRetainedFileCount() || prompt_
->GetRetainedDeviceCount()) {
309 // Slide in under the permissions, if there are any. If there are either,
310 // the retained files and devices prompts stretch all the way to the right
311 // of the dialog. If there are no permissions, the retained files and
312 // devices prompts just take up the left column.
314 if (has_permissions
) {
315 space_for_files_and_devices
+= kIconSize
;
316 views::ColumnSet
* column_set
= layout
->AddColumnSet(++column_set_id
);
317 column_set
->AddColumn(views::GridLayout::FILL
,
318 views::GridLayout::FILL
,
320 views::GridLayout::USE_PREF
,
322 space_for_files_and_devices
);
326 if (prompt_
->GetRetainedFileCount()) {
327 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
329 layout
->StartRow(0, column_set_id
);
330 views::Label
* retained_files_header
=
331 new views::Label(prompt_
->GetRetainedFilesHeading());
332 retained_files_header
->SetMultiLine(true);
333 retained_files_header
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
334 retained_files_header
->SizeToFit(space_for_files_and_devices
);
335 layout
->AddView(retained_files_header
);
337 layout
->StartRow(0, column_set_id
);
338 PermissionDetails details
;
339 for (size_t i
= 0; i
< prompt_
->GetRetainedFileCount(); ++i
) {
340 details
.push_back(prompt_
->GetRetainedFile(i
));
342 ExpandableContainerView
* issue_advice_view
=
343 new ExpandableContainerView(this,
346 space_for_files_and_devices
,
348 layout
->AddView(issue_advice_view
);
351 if (prompt_
->GetRetainedDeviceCount()) {
352 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
354 layout
->StartRow(0, column_set_id
);
355 views::Label
* retained_devices_header
=
356 new views::Label(prompt_
->GetRetainedDevicesHeading());
357 retained_devices_header
->SetMultiLine(true);
358 retained_devices_header
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
359 retained_devices_header
->SizeToFit(space_for_files_and_devices
);
360 layout
->AddView(retained_devices_header
);
362 layout
->StartRow(0, column_set_id
);
363 PermissionDetails details
;
364 for (size_t i
= 0; i
< prompt_
->GetRetainedDeviceCount(); ++i
) {
365 details
.push_back(prompt_
->GetRetainedDeviceMessageString(i
));
367 ExpandableContainerView
* issue_advice_view
=
368 new ExpandableContainerView(this,
371 space_for_files_and_devices
,
373 layout
->AddView(issue_advice_view
);
376 DCHECK(prompt_
->type() >= 0);
377 UMA_HISTOGRAM_ENUMERATION("Extensions.InstallPrompt.Type",
379 ExtensionInstallPrompt::NUM_PROMPT_TYPES
);
381 gfx::Size scrollable_size
= scrollable_
->GetPreferredSize();
382 scrollable_
->SetBoundsRect(gfx::Rect(scrollable_size
));
383 dialog_size_
= gfx::Size(
385 std::min(scrollable_size
.height(), kDialogMaxHeight
));
387 std::string event_name
= ExperienceSamplingEvent::kExtensionInstallDialog
;
389 ExtensionInstallPrompt::PromptTypeToString(prompt_
->type()));
390 sampling_event_
= ExperienceSamplingEvent::Create(event_name
);
393 bool ExtensionInstallDialogView::AddPermissions(
394 views::GridLayout
* layout
,
395 ui::ResourceBundle
& rb
,
397 int left_column_width
,
398 ExtensionInstallPrompt::PermissionsType perm_type
) {
399 if (prompt_
->GetPermissionCount(perm_type
) == 0)
402 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
403 if (is_inline_install()) {
404 layout
->StartRow(0, column_set_id
);
405 layout
->AddView(new views::Separator(views::Separator::HORIZONTAL
),
408 views::GridLayout::FILL
,
409 views::GridLayout::FILL
);
410 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
413 layout
->StartRow(0, column_set_id
);
414 views::Label
* permissions_header
= NULL
;
415 if (is_bundle_install()) {
416 // We need to pass the FontList in the constructor, rather than calling
417 // SetFontList later, because otherwise SizeToFit mis-judges the width
420 new views::Label(prompt_
->GetPermissionsHeading(perm_type
),
421 rb
.GetFontList(ui::ResourceBundle::MediumFont
));
424 new views::Label(prompt_
->GetPermissionsHeading(perm_type
));
426 permissions_header
->SetMultiLine(true);
427 permissions_header
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
428 permissions_header
->SizeToFit(left_column_width
);
429 layout
->AddView(permissions_header
);
431 for (size_t i
= 0; i
< prompt_
->GetPermissionCount(perm_type
); ++i
) {
432 layout
->AddPaddingRow(0, views::kRelatedControlVerticalSpacing
);
433 layout
->StartRow(0, column_set_id
);
434 views::Label
* permission_label
=
435 new views::Label(prompt_
->GetPermission(i
, perm_type
));
437 permission_label
->SetMultiLine(true);
438 permission_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
439 permission_label
->SizeToFit(left_column_width
- kBulletWidth
);
440 layout
->AddView(new BulletedView(permission_label
));
442 // If we have more details to provide, show them in collapsed form.
443 if (!prompt_
->GetPermissionsDetails(i
, perm_type
).empty()) {
444 layout
->StartRow(0, column_set_id
);
445 PermissionDetails details
;
446 details
.push_back(PrepareForDisplay(
447 prompt_
->GetPermissionsDetails(i
, perm_type
), false));
448 ExpandableContainerView
* details_container
=
449 new ExpandableContainerView(this,
454 layout
->AddView(details_container
);
460 views::GridLayout
* ExtensionInstallDialogView::CreateLayout(
462 int left_column_width
,
464 bool single_detail_row
) const {
465 views::GridLayout
* layout
= views::GridLayout::CreatePanel(parent
);
466 parent
->SetLayoutManager(layout
);
468 views::ColumnSet
* column_set
= layout
->AddColumnSet(column_set_id
);
469 column_set
->AddColumn(views::GridLayout::LEADING
,
470 views::GridLayout::FILL
,
472 views::GridLayout::USE_PREF
,
475 if (!is_bundle_install()) {
476 column_set
->AddPaddingColumn(0, views::kPanelHorizMargin
);
477 column_set
->AddColumn(views::GridLayout::TRAILING
,
478 views::GridLayout::LEADING
,
480 views::GridLayout::USE_PREF
,
485 layout
->StartRow(0, column_set_id
);
487 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
489 views::Label
* heading
= new views::Label(
490 prompt_
->GetHeading(), rb
.GetFontList(ui::ResourceBundle::MediumFont
));
491 heading
->SetMultiLine(true);
492 heading
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
493 heading
->SizeToFit(left_column_width
);
494 layout
->AddView(heading
);
496 if (!is_bundle_install()) {
497 // Scale down to icon size, but allow smaller icons (don't scale up).
498 const gfx::ImageSkia
* image
= prompt_
->icon().ToImageSkia();
499 gfx::Size
size(image
->width(), image
->height());
500 if (size
.width() > kIconSize
|| size
.height() > kIconSize
)
501 size
= gfx::Size(kIconSize
, kIconSize
);
502 views::ImageView
* icon
= new views::ImageView();
503 icon
->SetImageSize(size
);
504 icon
->SetImage(*image
);
505 icon
->SetHorizontalAlignment(views::ImageView::CENTER
);
506 icon
->SetVerticalAlignment(views::ImageView::CENTER
);
507 if (single_detail_row
) {
508 layout
->AddView(icon
);
510 int icon_row_span
= 1;
511 if (is_inline_install()) {
512 // Also span the rating, user_count and store_link rows.
514 } else if (prompt_
->ShouldShowPermissions()) {
515 size_t permission_count
= prompt_
->GetPermissionCount(
516 ExtensionInstallPrompt::PermissionsType::ALL_PERMISSIONS
);
517 // Also span the permission header and each of the permission rows (all
518 // have a padding row above it). This also works for the 'no special
519 // permissions' case.
520 icon_row_span
= 3 + permission_count
* 2;
521 } else if (prompt_
->GetRetainedFileCount()) {
522 // Also span the permission header and the retained files container.
525 layout
->AddView(icon
, 1, icon_row_span
);
531 void ExtensionInstallDialogView::ContentsChanged() {
535 int ExtensionInstallDialogView::GetDialogButtons() const {
536 int buttons
= prompt_
->GetDialogButtons();
537 // Simply having just an OK button is *not* supported. See comment on function
538 // GetDialogButtons in dialog_delegate.h for reasons.
539 DCHECK_GT(buttons
& ui::DIALOG_BUTTON_CANCEL
, 0);
543 base::string16
ExtensionInstallDialogView::GetDialogButtonLabel(
544 ui::DialogButton button
) const {
546 case ui::DIALOG_BUTTON_OK
:
547 return prompt_
->GetAcceptButtonLabel();
548 case ui::DIALOG_BUTTON_CANCEL
:
549 return prompt_
->HasAbortButtonLabel()
550 ? prompt_
->GetAbortButtonLabel()
551 : l10n_util::GetStringUTF16(IDS_CANCEL
);
554 return base::string16();
558 int ExtensionInstallDialogView::GetDefaultDialogButton() const {
559 return ui::DIALOG_BUTTON_CANCEL
;
562 bool ExtensionInstallDialogView::Cancel() {
566 handled_result_
= true;
567 UpdateInstallResultHistogram(false);
569 sampling_event_
->CreateUserDecisionEvent(ExperienceSamplingEvent::kDeny
);
570 delegate_
->InstallUIAbort(true);
574 bool ExtensionInstallDialogView::Accept() {
575 DCHECK(!handled_result_
);
577 handled_result_
= true;
578 UpdateInstallResultHistogram(true);
580 sampling_event_
->CreateUserDecisionEvent(ExperienceSamplingEvent::kProceed
);
581 delegate_
->InstallUIProceed();
585 ui::ModalType
ExtensionInstallDialogView::GetModalType() const {
586 return ui::MODAL_TYPE_WINDOW
;
589 base::string16
ExtensionInstallDialogView::GetWindowTitle() const {
590 return prompt_
->GetDialogTitle();
593 void ExtensionInstallDialogView::LinkClicked(views::Link
* source
,
595 GURL
store_url(extension_urls::GetWebstoreItemDetailURLPrefix() +
596 prompt_
->extension()->id());
597 OpenURLParams
params(
598 store_url
, Referrer(), NEW_FOREGROUND_TAB
,
599 ui::PAGE_TRANSITION_LINK
,
603 navigator_
->OpenURL(params
);
605 chrome::ScopedTabbedBrowserDisplayer
displayer(
606 profile_
, chrome::GetActiveDesktop());
607 displayer
.browser()->OpenURL(params
);
609 GetWidget()->Close();
612 void ExtensionInstallDialogView::Layout() {
613 scroll_view_
->SetBounds(0, 0, width(), height());
614 DialogDelegateView::Layout();
617 gfx::Size
ExtensionInstallDialogView::GetPreferredSize() const {
621 void ExtensionInstallDialogView::UpdateInstallResultHistogram(bool accepted
)
623 if (prompt_
->type() == ExtensionInstallPrompt::INSTALL_PROMPT
)
624 UMA_HISTOGRAM_BOOLEAN("Extensions.InstallPrompt.Accepted", accepted
);
627 // ExpandableContainerView::DetailsView ----------------------------------------
629 ExpandableContainerView::DetailsView::DetailsView(int horizontal_space
,
630 bool parent_bulleted
)
631 : layout_(new views::GridLayout(this)),
633 SetLayoutManager(layout_
);
634 views::ColumnSet
* column_set
= layout_
->AddColumnSet(0);
635 // If the parent is using bullets for its items, then a padding of one unit
636 // will make the child item (which has no bullet) look like a sibling of its
637 // parent. Therefore increase the indentation by one more unit to show that it
638 // is in fact a child item (with no missing bullet) and not a sibling.
640 views::kRelatedControlHorizontalSpacing
* (parent_bulleted
? 2 : 1);
641 column_set
->AddPaddingColumn(0, padding
);
642 column_set
->AddColumn(views::GridLayout::LEADING
,
643 views::GridLayout::LEADING
,
645 views::GridLayout::FIXED
,
646 horizontal_space
- padding
,
650 void ExpandableContainerView::DetailsView::AddDetail(
651 const base::string16
& detail
) {
652 layout_
->StartRowWithPadding(0, 0,
653 0, views::kRelatedControlSmallVerticalSpacing
);
654 views::Label
* detail_label
=
655 new views::Label(PrepareForDisplay(detail
, false));
656 detail_label
->SetMultiLine(true);
657 detail_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
658 layout_
->AddView(detail_label
);
661 gfx::Size
ExpandableContainerView::DetailsView::GetPreferredSize() const {
662 gfx::Size size
= views::View::GetPreferredSize();
663 return gfx::Size(size
.width(), size
.height() * state_
);
666 void ExpandableContainerView::DetailsView::AnimateToState(double state
) {
668 PreferredSizeChanged();
672 // ExpandableContainerView -----------------------------------------------------
674 ExpandableContainerView::ExpandableContainerView(
675 ExtensionInstallDialogView
* owner
,
676 const base::string16
& description
,
677 const PermissionDetails
& details
,
678 int horizontal_space
,
679 bool parent_bulleted
)
682 slide_animation_(this),
686 views::GridLayout
* layout
= new views::GridLayout(this);
687 SetLayoutManager(layout
);
688 int column_set_id
= 0;
689 views::ColumnSet
* column_set
= layout
->AddColumnSet(column_set_id
);
690 column_set
->AddColumn(views::GridLayout::LEADING
,
691 views::GridLayout::LEADING
,
693 views::GridLayout::USE_PREF
,
696 if (!description
.empty()) {
697 layout
->StartRow(0, column_set_id
);
699 views::Label
* description_label
= new views::Label(description
);
700 description_label
->SetMultiLine(true);
701 description_label
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
702 description_label
->SizeToFit(horizontal_space
);
703 layout
->AddView(new BulletedView(description_label
));
709 details_view_
= new DetailsView(horizontal_space
, parent_bulleted
);
711 layout
->StartRow(0, column_set_id
);
712 layout
->AddView(details_view_
);
714 for (size_t i
= 0; i
< details
.size(); ++i
)
715 details_view_
->AddDetail(details
[i
]);
717 views::Link
* link
= new views::Link(
718 l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS
));
720 // Make sure the link width column is as wide as needed for both Show and
721 // Hide details, so that the arrow doesn't shift horizontally when we
724 views::kRelatedControlHorizontalSpacing
+
725 std::max(gfx::GetStringWidth(
726 l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS
),
729 l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS
),
732 column_set
= layout
->AddColumnSet(++column_set_id
);
733 // Padding to the left of the More Details column. If the parent is using
734 // bullets for its items, then a padding of one unit will make the child
735 // item (which has no bullet) look like a sibling of its parent. Therefore
736 // increase the indentation by one more unit to show that it is in fact a
737 // child item (with no missing bullet) and not a sibling.
738 column_set
->AddPaddingColumn(
739 0, views::kRelatedControlHorizontalSpacing
* (parent_bulleted
? 2 : 1));
740 // The More Details column.
741 column_set
->AddColumn(views::GridLayout::LEADING
,
742 views::GridLayout::LEADING
,
744 views::GridLayout::FIXED
,
747 // The Up/Down arrow column.
748 column_set
->AddColumn(views::GridLayout::LEADING
,
749 views::GridLayout::LEADING
,
751 views::GridLayout::USE_PREF
,
755 // Add the More Details link.
756 layout
->StartRow(0, column_set_id
);
757 more_details_
= link
;
758 more_details_
->set_listener(this);
759 more_details_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
760 layout
->AddView(more_details_
);
762 // Add the arrow after the More Details link.
763 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
764 arrow_toggle_
= new views::ImageButton(this);
765 arrow_toggle_
->SetImage(views::Button::STATE_NORMAL
,
766 rb
.GetImageSkiaNamed(IDR_DOWN_ARROW
));
767 layout
->AddView(arrow_toggle_
);
770 ExpandableContainerView::~ExpandableContainerView() {
773 void ExpandableContainerView::ButtonPressed(
774 views::Button
* sender
, const ui::Event
& event
) {
778 void ExpandableContainerView::LinkClicked(
779 views::Link
* source
, int event_flags
) {
783 void ExpandableContainerView::AnimationProgressed(
784 const gfx::Animation
* animation
) {
785 DCHECK_EQ(&slide_animation_
, animation
);
787 details_view_
->AnimateToState(animation
->GetCurrentValue());
790 void ExpandableContainerView::AnimationEnded(const gfx::Animation
* animation
) {
792 if (animation
->GetCurrentValue() != 0.0) {
793 arrow_toggle_
->SetImage(
794 views::Button::STATE_NORMAL
,
795 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
798 arrow_toggle_
->SetImage(
799 views::Button::STATE_NORMAL
,
800 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
805 more_details_
->SetText(expanded_
?
806 l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS
) :
807 l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS
));
811 void ExpandableContainerView::ChildPreferredSizeChanged(views::View
* child
) {
812 owner_
->ContentsChanged();
815 void ExpandableContainerView::ToggleDetailLevel() {
816 expanded_
= !expanded_
;
818 if (slide_animation_
.IsShowing())
819 slide_animation_
.Hide();
821 slide_animation_
.Show();
825 ExtensionInstallPrompt::ShowDialogCallback
826 ExtensionInstallPrompt::GetDefaultShowDialogCallback() {
827 return base::Bind(&ShowExtensionInstallDialogImpl
);