Revert of Add button to add new FSP services to Files app. (patchset #8 id:140001...
[chromium-blink-merge.git] / chrome / browser / ui / views / extensions / extension_install_dialog_view.cc
blob325fbdda36447a7e590f22ca48244d887aa3a692
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/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;
57 namespace {
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
74 // permissions.
75 const int kPermissionsLeftColumnWidth = 250;
77 // Width of the left column of the dialog when the extension requests no
78 // permissions.
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,
99 bool bullet_point) {
100 return bullet_point ? l10n_util::GetStringFUTF16(
101 IDS_EXTENSION_PERMISSION_LINE,
102 message) : message;
105 } // namespace
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,
115 kBulletWidth,
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(),
136 delegate,
137 prompt);
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(
151 Profile* profile,
152 content::PageNavigator* navigator,
153 ExtensionInstallPrompt::Delegate* delegate,
154 scoped_refptr<ExtensionInstallPrompt::Prompt> prompt)
155 : profile_(profile),
156 navigator_(navigator),
157 delegate_(delegate),
158 prompt_(prompt),
159 scroll_view_(NULL),
160 scrollable_(NULL),
161 handled_result_(false) {
162 InitView();
165 ExtensionInstallDialogView::~ExtensionInstallDialogView() {
166 if (!handled_result_)
167 delegate_->InstallUIAbort(true);
170 void ExtensionInstallDialogView::InitView() {
171 // Possible grid layouts:
172 // Inline install
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 // +--------------------+------+ +--------------+------+
183 // | separator |
184 // +--------------------+------+
185 // | permissions_header | |
186 // +--------------------+------+
187 // | permission1 | |
188 // +--------------------+------+
189 // | permission2 | |
190 // +--------------------+------+
192 // Regular install
193 // w/ permissions no permissions
194 // +--------------------+------+ +--------------+------+
195 // | heading | icon | | heading | icon |
196 // +--------------------| | +--------------+------+
197 // | permissions_header | |
198 // +--------------------| |
199 // | permission1 | |
200 // +--------------------| |
201 // | permission2 | |
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()) {
283 AddPermissions(
284 layout,
286 column_set_id,
287 left_column_width,
288 ExtensionInstallPrompt::PermissionsType::REGULAR_PERMISSIONS);
289 AddPermissions(
290 layout,
292 column_set_id,
293 left_column_width,
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,
321 0, // no fixed width
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,
344 base::string16(),
345 details,
346 space_for_files_and_devices,
347 false);
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,
369 base::string16(),
370 details,
371 space_for_files_and_devices,
372 false);
373 layout->AddView(issue_advice_view);
376 DCHECK(prompt_->type() >= 0);
377 UMA_HISTOGRAM_ENUMERATION("Extensions.InstallPrompt.Type",
378 prompt_->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(
384 dialog_width,
385 std::min(scrollable_size.height(), kDialogMaxHeight));
387 std::string event_name = ExperienceSamplingEvent::kExtensionInstallDialog;
388 event_name.append(
389 ExtensionInstallPrompt::PromptTypeToString(prompt_->type()));
390 sampling_event_ = ExperienceSamplingEvent::Create(event_name);
393 bool ExtensionInstallDialogView::AddPermissions(
394 views::GridLayout* layout,
395 ui::ResourceBundle& rb,
396 int column_set_id,
397 int left_column_width,
398 ExtensionInstallPrompt::PermissionsType perm_type) {
399 if (prompt_->GetPermissionCount(perm_type) == 0)
400 return false;
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
418 // of the line.
419 permissions_header =
420 new views::Label(prompt_->GetPermissionsHeading(perm_type),
421 rb.GetFontList(ui::ResourceBundle::MediumFont));
422 } else {
423 permissions_header =
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,
450 base::string16(),
451 details,
452 left_column_width,
453 true);
454 layout->AddView(details_container);
457 return true;
460 views::GridLayout* ExtensionInstallDialogView::CreateLayout(
461 views::View* parent,
462 int left_column_width,
463 int column_set_id,
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,
471 0, // no resizing
472 views::GridLayout::USE_PREF,
473 0, // no fixed width
474 left_column_width);
475 if (!is_bundle_install()) {
476 column_set->AddPaddingColumn(0, views::kPanelHorizMargin);
477 column_set->AddColumn(views::GridLayout::TRAILING,
478 views::GridLayout::LEADING,
479 0, // no resizing
480 views::GridLayout::USE_PREF,
481 0, // no fixed width
482 kIconSize);
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);
509 } else {
510 int icon_row_span = 1;
511 if (is_inline_install()) {
512 // Also span the rating, user_count and store_link rows.
513 icon_row_span = 4;
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.
523 icon_row_span = 4;
525 layout->AddView(icon, 1, icon_row_span);
528 return layout;
531 void ExtensionInstallDialogView::ContentsChanged() {
532 Layout();
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);
540 return buttons;
543 base::string16 ExtensionInstallDialogView::GetDialogButtonLabel(
544 ui::DialogButton button) const {
545 switch (button) {
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);
552 default:
553 NOTREACHED();
554 return base::string16();
558 int ExtensionInstallDialogView::GetDefaultDialogButton() const {
559 return ui::DIALOG_BUTTON_CANCEL;
562 bool ExtensionInstallDialogView::Cancel() {
563 if (handled_result_)
564 return true;
566 handled_result_ = true;
567 UpdateInstallResultHistogram(false);
568 if (sampling_event_)
569 sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kDeny);
570 delegate_->InstallUIAbort(true);
571 return true;
574 bool ExtensionInstallDialogView::Accept() {
575 DCHECK(!handled_result_);
577 handled_result_ = true;
578 UpdateInstallResultHistogram(true);
579 if (sampling_event_)
580 sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kProceed);
581 delegate_->InstallUIProceed();
582 return true;
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,
594 int event_flags) {
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,
600 false);
602 if (navigator_) {
603 navigator_->OpenURL(params);
604 } else {
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 {
618 return dialog_size_;
621 void ExtensionInstallDialogView::UpdateInstallResultHistogram(bool accepted)
622 const {
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)),
632 state_(0) {
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.
639 int padding =
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) {
667 state_ = state;
668 PreferredSizeChanged();
669 SchedulePaint();
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)
680 : owner_(owner),
681 details_view_(NULL),
682 slide_animation_(this),
683 more_details_(NULL),
684 arrow_toggle_(NULL),
685 expanded_(false) {
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));
706 if (details.empty())
707 return;
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
722 // toggle.
723 int link_col_width =
724 views::kRelatedControlHorizontalSpacing +
725 std::max(gfx::GetStringWidth(
726 l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS),
727 link->font_list()),
728 gfx::GetStringWidth(
729 l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS),
730 link->font_list()));
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,
745 link_col_width,
746 link_col_width);
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) {
775 ToggleDetailLevel();
778 void ExpandableContainerView::LinkClicked(
779 views::Link* source, int event_flags) {
780 ToggleDetailLevel();
783 void ExpandableContainerView::AnimationProgressed(
784 const gfx::Animation* animation) {
785 DCHECK_EQ(&slide_animation_, animation);
786 if (details_view_)
787 details_view_->AnimateToState(animation->GetCurrentValue());
790 void ExpandableContainerView::AnimationEnded(const gfx::Animation* animation) {
791 if (arrow_toggle_) {
792 if (animation->GetCurrentValue() != 0.0) {
793 arrow_toggle_->SetImage(
794 views::Button::STATE_NORMAL,
795 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
796 IDR_UP_ARROW));
797 } else {
798 arrow_toggle_->SetImage(
799 views::Button::STATE_NORMAL,
800 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
801 IDR_DOWN_ARROW));
804 if (more_details_) {
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();
820 else
821 slide_animation_.Show();
824 // static
825 ExtensionInstallPrompt::ShowDialogCallback
826 ExtensionInstallPrompt::GetDefaultShowDialogCallback() {
827 return base::Bind(&ShowExtensionInstallDialogImpl);