Rename vector icon header files.
[chromium-blink-merge.git] / chrome / browser / ui / views / extensions / extension_install_dialog_view.cc
blobef87839b073052d64b731712bcf5a8b85ea6a02b
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"
7 #include <vector>
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;
54 namespace {
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
72 // permissions.
73 const int kPermissionsLeftColumnWidth = 250;
75 // Width of the left column of the dialog when the extension requests no
76 // permissions.
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,
93 bool bullet_point) {
94 return bullet_point ? l10n_util::GetStringFUTF16(
95 IDS_EXTENSION_PERMISSION_LINE,
96 message) : message;
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(),
107 delegate,
108 prompt);
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 {
115 public:
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();
124 private:
125 void Layout() override {
126 SetBounds(x(), y(), width(), GetHeightForWidth(width()));
127 views::View::Layout();
130 DISALLOW_COPY_AND_ASSIGN(CustomScrollableView);
133 } // namespace
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,
143 kBulletWidth,
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,
164 kSmallIconSize,
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(
187 Profile* profile,
188 content::PageNavigator* navigator,
189 ExtensionInstallPrompt::Delegate* delegate,
190 scoped_refptr<ExtensionInstallPrompt::Prompt> prompt)
191 : profile_(profile),
192 navigator_(navigator),
193 delegate_(delegate),
194 prompt_(prompt),
195 container_(NULL),
196 scroll_view_(NULL),
197 handled_result_(false) {
198 InitView();
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 // +---------------------+ +---------------------+
221 // | scroll_view |
222 // +---------------------+
224 // No webstore data (all other types)
225 // +--------------+------+
226 // | title | icon |
227 // +--------------| |
228 // | scroll_view | |
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
288 // files/devices.
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
299 : left_column_width;
300 scrollable_column_set->AddColumn(views::GridLayout::LEADING,
301 views::GridLayout::LEADING,
302 0, // no resizing
303 views::GridLayout::USE_PREF,
304 scrollable_width,
305 scrollable_width);
306 // Pad to the very right of the dialog, so the scrollbar will be on the edge.
307 int padding_width =
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) {
340 AddPermissions(
341 scroll_layout,
343 column_set_id,
344 scrollable_width,
345 ExtensionInstallPrompt::PermissionsType::REGULAR_PERMISSIONS);
346 AddPermissions(
347 scroll_layout,
349 column_set_id,
350 scrollable_width,
351 ExtensionInstallPrompt::PermissionsType::WITHHELD_PERMISSIONS);
352 } else {
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,
382 base::string16(),
383 details,
384 scrollable_width,
385 false);
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,
407 base::string16(),
408 details,
409 scrollable_width,
410 false);
411 scroll_layout->AddView(issue_advice_view);
414 DCHECK(prompt_->type() >= 0);
415 UMA_HISTOGRAM_ENUMERATION("Extensions.InstallPrompt.Type",
416 prompt_->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;
428 event_name.append(
429 ExtensionInstallPrompt::PromptTypeToString(prompt_->type()));
430 sampling_event_ = ExperienceSamplingEvent::Create(event_name);
433 bool ExtensionInstallDialogView::AddPermissions(
434 views::GridLayout* layout,
435 ui::ResourceBundle& rb,
436 int column_set_id,
437 int left_column_width,
438 ExtensionInstallPrompt::PermissionsType perm_type) {
439 if (prompt_->GetPermissionCount(perm_type) == 0)
440 return false;
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
449 // of the line.
450 permissions_header =
451 new views::Label(prompt_->GetPermissionsHeading(perm_type),
452 rb.GetFontList(ui::ResourceBundle::MediumFont));
453 } else {
454 permissions_header =
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,
481 base::string16(),
482 details,
483 left_column_width,
484 true);
485 layout->AddView(details_container);
488 return true;
491 views::GridLayout* ExtensionInstallDialogView::CreateLayout(
492 int left_column_width,
493 int column_set_id) {
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,
506 0, // no resizing
507 views::GridLayout::USE_PREF,
508 0, // no fixed width
509 left_column_width);
510 column_set->AddPaddingColumn(0, views::kPanelHorizMargin);
511 column_set->AddColumn(views::GridLayout::TRAILING, views::GridLayout::LEADING,
512 0, // no resizing
513 views::GridLayout::USE_PREF,
514 0, // no fixed width
515 kIconSize);
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.
540 icon_row_span += 3;
541 // Note: Do not span the permissions here, there's a separator in between!
542 } else {
543 // Also span the scrollable container with permissions, retained files etc.
544 icon_row_span += 1;
546 layout->AddView(icon, 1, icon_row_span);
548 return layout;
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);
556 return buttons;
559 base::string16 ExtensionInstallDialogView::GetDialogButtonLabel(
560 ui::DialogButton button) const {
561 switch (button) {
562 case ui::DIALOG_BUTTON_OK:
563 return prompt_->GetAcceptButtonLabel();
564 case ui::DIALOG_BUTTON_CANCEL:
565 return prompt_->GetAbortButtonLabel();
566 default:
567 NOTREACHED();
568 return base::string16();
572 int ExtensionInstallDialogView::GetDefaultDialogButton() const {
573 return ui::DIALOG_BUTTON_CANCEL;
576 bool ExtensionInstallDialogView::Cancel() {
577 if (handled_result_)
578 return true;
580 handled_result_ = true;
581 UpdateInstallResultHistogram(false);
582 if (sampling_event_)
583 sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kDeny);
584 delegate_->InstallUIAbort(true);
585 return true;
588 bool ExtensionInstallDialogView::Accept() {
589 DCHECK(!handled_result_);
591 handled_result_ = true;
592 UpdateInstallResultHistogram(true);
593 if (sampling_event_)
594 sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kProceed);
595 delegate_->InstallUIProceed();
596 return true;
599 ui::ModalType ExtensionInstallDialogView::GetModalType() const {
600 return ui::MODAL_TYPE_WINDOW;
603 void ExtensionInstallDialogView::LinkClicked(views::Link* source,
604 int event_flags) {
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,
610 false);
612 if (navigator_) {
613 navigator_->OpenURL(params);
614 } else {
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 {
628 return dialog_size_;
631 void ExtensionInstallDialogView::UpdateInstallResultHistogram(bool accepted)
632 const {
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)),
642 state_(0) {
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.
649 int padding =
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) {
677 state_ = state;
678 PreferredSizeChanged();
679 SchedulePaint();
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)
690 : owner_(owner),
691 details_view_(NULL),
692 slide_animation_(this),
693 more_details_(NULL),
694 arrow_toggle_(NULL),
695 expanded_(false) {
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));
716 if (details.empty())
717 return;
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
729 // toggle.
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,
749 link_col_width,
750 link_col_width);
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) {
777 ToggleDetailLevel();
780 void ExpandableContainerView::LinkClicked(
781 views::Link* source, int event_flags) {
782 ToggleDetailLevel();
785 void ExpandableContainerView::AnimationProgressed(
786 const gfx::Animation* animation) {
787 DCHECK_EQ(&slide_animation_, animation);
788 if (details_view_)
789 details_view_->AnimateToState(animation->GetCurrentValue());
792 void ExpandableContainerView::AnimationEnded(const gfx::Animation* animation) {
793 if (arrow_toggle_)
794 UpdateArrowToggle(animation->GetCurrentValue() != 0.0);
795 if (more_details_) {
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();
811 else
812 slide_animation_.Show();
815 void ExpandableContainerView::UpdateArrowToggle(bool expanded) {
816 SkColor icon_color;
817 ui::CommonThemeGetSystemColor(ui::NativeTheme::kColorId_ChromeIconGrey,
818 &icon_color);
819 gfx::ImageSkia icon = gfx::CreateVectorIcon(expanded ?
820 gfx::VectorIconId::FIND_PREV :
821 gfx::VectorIconId::FIND_NEXT,
823 icon_color);
824 arrow_toggle_->SetImage(
825 views::Button::STATE_NORMAL,
826 &icon);
829 // static
830 ExtensionInstallPrompt::ShowDialogCallback
831 ExtensionInstallPrompt::GetDefaultShowDialogCallback() {
832 return base::Bind(&ShowExtensionInstallDialogImpl);