[Metrics] Make MetricsStateManager take a callback param to check if UMA is enabled.
[chromium-blink-merge.git] / chrome / browser / ui / views / extensions / extension_install_dialog_view.cc
blob9aa465cce8a6fc14a1328704d9c8183834668e0a
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 <vector>
7 #include "base/basictypes.h"
8 #include "base/command_line.h"
9 #include "base/compiler_specific.h"
10 #include "base/i18n/rtl.h"
11 #include "base/metrics/histogram.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/browser/extensions/bundle_installer.h"
15 #include "chrome/browser/extensions/extension_install_prompt.h"
16 #include "chrome/browser/extensions/extension_install_prompt_experiment.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/ui/views/constrained_window_views.h"
19 #include "chrome/common/chrome_switches.h"
20 #include "chrome/common/extensions/extension_constants.h"
21 #include "chrome/installer/util/browser_distribution.h"
22 #include "content/public/browser/page_navigator.h"
23 #include "content/public/browser/web_contents.h"
24 #include "extensions/common/extension.h"
25 #include "grit/chromium_strings.h"
26 #include "grit/generated_resources.h"
27 #include "grit/google_chrome_strings.h"
28 #include "grit/theme_resources.h"
29 #include "ui/base/l10n/l10n_util.h"
30 #include "ui/base/resource/resource_bundle.h"
31 #include "ui/gfx/animation/animation_delegate.h"
32 #include "ui/gfx/animation/slide_animation.h"
33 #include "ui/gfx/text_utils.h"
34 #include "ui/gfx/transform.h"
35 #include "ui/views/background.h"
36 #include "ui/views/border.h"
37 #include "ui/views/controls/button/checkbox.h"
38 #include "ui/views/controls/button/image_button.h"
39 #include "ui/views/controls/button/label_button.h"
40 #include "ui/views/controls/image_view.h"
41 #include "ui/views/controls/label.h"
42 #include "ui/views/controls/link.h"
43 #include "ui/views/controls/link_listener.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/view.h"
50 #include "ui/views/widget/widget.h"
51 #include "ui/views/window/dialog_client_view.h"
52 #include "ui/views/window/dialog_delegate.h"
54 using content::OpenURLParams;
55 using content::Referrer;
56 using extensions::BundleInstaller;
58 namespace {
60 // Size of extension icon in top left of dialog.
61 const int kIconSize = 69;
63 // We offset the icon a little bit from the right edge of the dialog, to make it
64 // align with the button below it.
65 const int kIconOffset = 16;
67 // The dialog will resize based on its content, but this sets a maximum height
68 // before overflowing a scrollbar.
69 const int kDialogMaxHeight = 300;
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 bundle install prompts. There's only one column
80 // in this case, so make it wider than normal.
81 const int kBundleLeftColumnWidth = 300;
83 // Width of the left column for external install prompts. The text is long in
84 // this case, so make it wider than normal.
85 const int kExternalInstallLeftColumnWidth = 350;
87 // Lighter color for labels.
88 const SkColor kLighterLabelColor = SkColorSetRGB(0x99, 0x99, 0x99);
90 // Represents an action on a clickable link created by the install prompt
91 // experiment. This is used to group the actions in UMA histograms named
92 // Extensions.InstallPromptExperiment.ShowDetails and
93 // Extensions.InstallPromptExperiment.ShowPermissions.
94 enum ExperimentLinkAction {
95 LINK_SHOWN = 0,
96 LINK_NOT_SHOWN,
97 LINK_CLICKED,
98 NUM_LINK_ACTIONS
101 typedef std::vector<base::string16> PermissionDetails;
102 class ExpandableContainerView;
104 void AddResourceIcon(const gfx::ImageSkia* skia_image, void* data) {
105 views::View* parent = static_cast<views::View*>(data);
106 views::ImageView* image_view = new views::ImageView();
107 image_view->SetImage(*skia_image);
108 parent->AddChildView(image_view);
111 // Creates a string for displaying |message| to the user. If it has to look
112 // like a entry in a bullet point list, one is added.
113 base::string16 PrepareForDisplay(const base::string16& message,
114 bool bullet_point) {
115 return bullet_point ? l10n_util::GetStringFUTF16(
116 IDS_EXTENSION_PERMISSION_LINE,
117 message) : message;
120 // A custom scrollable view implementation for the dialog.
121 class CustomScrollableView : public views::View {
122 public:
123 CustomScrollableView();
124 virtual ~CustomScrollableView();
126 private:
127 virtual void Layout() OVERRIDE;
129 DISALLOW_COPY_AND_ASSIGN(CustomScrollableView);
132 // Implements the extension installation dialog for TOOLKIT_VIEWS.
133 class ExtensionInstallDialogView : public views::DialogDelegateView,
134 public views::LinkListener,
135 public views::ButtonListener {
136 public:
137 ExtensionInstallDialogView(content::PageNavigator* navigator,
138 ExtensionInstallPrompt::Delegate* delegate,
139 const ExtensionInstallPrompt::Prompt& prompt);
140 virtual ~ExtensionInstallDialogView();
142 // Called when one of the child elements has expanded/collapsed.
143 void ContentsChanged();
145 private:
146 // views::DialogDelegateView:
147 virtual int GetDialogButtons() const OVERRIDE;
148 virtual base::string16 GetDialogButtonLabel(
149 ui::DialogButton button) const OVERRIDE;
150 virtual int GetDefaultDialogButton() const OVERRIDE;
151 virtual bool Cancel() OVERRIDE;
152 virtual bool Accept() OVERRIDE;
153 virtual ui::ModalType GetModalType() const OVERRIDE;
154 virtual base::string16 GetWindowTitle() const OVERRIDE;
155 virtual void Layout() OVERRIDE;
156 virtual gfx::Size GetPreferredSize() const OVERRIDE;
157 virtual void ViewHierarchyChanged(
158 const ViewHierarchyChangedDetails& details) OVERRIDE;
160 // views::LinkListener:
161 virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE;
163 // views::ButtonListener:
164 virtual void ButtonPressed(views::Button* sender,
165 const ui::Event& event) OVERRIDE;
167 // Experimental: Toggles inline permission explanations with an animation.
168 void ToggleInlineExplanations();
170 // Creates a layout consisting of dialog header, extension name and icon.
171 views::GridLayout* CreateLayout(
172 views::View* parent,
173 int left_column_width,
174 int column_set_id,
175 bool single_detail_row) const;
177 bool is_inline_install() const {
178 return prompt_.type() == ExtensionInstallPrompt::INLINE_INSTALL_PROMPT;
181 bool is_bundle_install() const {
182 return prompt_.type() == ExtensionInstallPrompt::BUNDLE_INSTALL_PROMPT;
185 bool is_external_install() const {
186 return prompt_.type() == ExtensionInstallPrompt::EXTERNAL_INSTALL_PROMPT;
189 // Updates the histogram that holds installation accepted/aborted data.
190 void UpdateInstallResultHistogram(bool accepted) const;
192 // Updates the histogram that holds data about whether "Show details" or
193 // "Show permissions" links were shown and/or clicked.
194 void UpdateLinkActionHistogram(int action_type) const;
196 content::PageNavigator* navigator_;
197 ExtensionInstallPrompt::Delegate* delegate_;
198 const ExtensionInstallPrompt::Prompt& prompt_;
200 // The scroll view containing all the details for the dialog (including all
201 // collapsible/expandable sections).
202 views::ScrollView* scroll_view_;
204 // The container view for the scroll view.
205 CustomScrollableView* scrollable_;
207 // The container for the simpler view with only the dialog header and the
208 // extension icon. Used for the experiment where the permissions are
209 // initially hidden when the dialog shows.
210 CustomScrollableView* scrollable_header_only_;
212 // The preferred size of the dialog.
213 gfx::Size dialog_size_;
215 // Experimental: "Show details" link to expand inline explanations and reveal
216 // permision dialog.
217 views::Link* show_details_link_;
219 // Experimental: Label for showing information about the checkboxes.
220 views::Label* checkbox_info_label_;
222 // Experimental: Contains pointers to inline explanation views.
223 typedef std::vector<ExpandableContainerView*> InlineExplanations;
224 InlineExplanations inline_explanations_;
226 // Experimental: Number of unchecked checkboxes in the permission list.
227 // If this becomes zero, the accept button is enabled, otherwise disabled.
228 int unchecked_boxes_;
230 DISALLOW_COPY_AND_ASSIGN(ExtensionInstallDialogView);
233 // A simple view that prepends a view with a bullet with the help of a grid
234 // layout.
235 class BulletedView : public views::View {
236 public:
237 explicit BulletedView(views::View* view);
238 private:
239 DISALLOW_COPY_AND_ASSIGN(BulletedView);
242 BulletedView::BulletedView(views::View* view) {
243 views::GridLayout* layout = new views::GridLayout(this);
244 SetLayoutManager(layout);
245 views::ColumnSet* column_set = layout->AddColumnSet(0);
246 column_set->AddColumn(views::GridLayout::LEADING,
247 views::GridLayout::LEADING,
249 views::GridLayout::USE_PREF,
250 0, // no fixed width
252 column_set->AddColumn(views::GridLayout::LEADING,
253 views::GridLayout::LEADING,
255 views::GridLayout::USE_PREF,
256 0, // no fixed width
258 layout->StartRow(0, 0);
259 layout->AddView(new views::Label(PrepareForDisplay(base::string16(), true)));
260 layout->AddView(view);
263 // A simple view that prepends a view with a checkbox with the help of a grid
264 // layout. Used for the permission experiment.
265 // TODO(meacer): Remove once the experiment is completed.
266 class CheckboxedView : public views::View {
267 public:
268 CheckboxedView(views::View* view, views::ButtonListener* listener);
269 private:
270 DISALLOW_COPY_AND_ASSIGN(CheckboxedView);
273 CheckboxedView::CheckboxedView(views::View* view,
274 views::ButtonListener* listener) {
275 views::GridLayout* layout = new views::GridLayout(this);
276 SetLayoutManager(layout);
277 views::ColumnSet* column_set = layout->AddColumnSet(0);
278 column_set->AddColumn(views::GridLayout::LEADING,
279 views::GridLayout::LEADING,
281 views::GridLayout::USE_PREF,
282 0, // no fixed width
284 column_set->AddColumn(views::GridLayout::LEADING,
285 views::GridLayout::LEADING,
287 views::GridLayout::USE_PREF,
288 0, // no fixed width
290 layout->StartRow(0, 0);
291 views::Checkbox* checkbox = new views::Checkbox(base::string16());
292 checkbox->set_listener(listener);
293 // Alignment needs to be explicitly set again here, otherwise the views are
294 // not vertically centered.
295 layout->AddView(checkbox, 1, 1,
296 views::GridLayout::LEADING, views::GridLayout::CENTER);
297 layout->AddView(view, 1, 1,
298 views::GridLayout::LEADING, views::GridLayout::CENTER);
301 // A view to display text with an expandable details section.
302 class ExpandableContainerView : public views::View,
303 public views::ButtonListener,
304 public views::LinkListener,
305 public gfx::AnimationDelegate {
306 public:
307 ExpandableContainerView(ExtensionInstallDialogView* owner,
308 const base::string16& description,
309 const PermissionDetails& details,
310 int horizontal_space,
311 bool parent_bulleted,
312 bool show_expand_link,
313 bool lighter_color_details);
314 virtual ~ExpandableContainerView();
316 // views::View:
317 virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE;
319 // views::ButtonListener:
320 virtual void ButtonPressed(views::Button* sender,
321 const ui::Event& event) OVERRIDE;
323 // views::LinkListener:
324 virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE;
326 // gfx::AnimationDelegate:
327 virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE;
328 virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE;
330 // Expand/Collapse the detail section for this ExpandableContainerView.
331 void ToggleDetailLevel();
333 // Expand the detail section without any animation.
334 // TODO(meacer): Remove once the experiment is completed.
335 void ExpandWithoutAnimation();
337 private:
338 // A view which displays all the details of an IssueAdviceInfoEntry.
339 class DetailsView : public views::View {
340 public:
341 explicit DetailsView(int horizontal_space, bool parent_bulleted,
342 bool lighter_color);
343 virtual ~DetailsView() {}
345 // views::View:
346 virtual gfx::Size GetPreferredSize() const OVERRIDE;
348 void AddDetail(const base::string16& detail);
350 // Animates this to be a height proportional to |state|.
351 void AnimateToState(double state);
353 private:
354 views::GridLayout* layout_;
355 double state_;
357 // Whether the detail text should be shown with a lighter color.
358 bool lighter_color_;
360 DISALLOW_COPY_AND_ASSIGN(DetailsView);
363 // The dialog that owns |this|. It's also an ancestor in the View hierarchy.
364 ExtensionInstallDialogView* owner_;
366 // A view for showing |issue_advice.details|.
367 DetailsView* details_view_;
369 // The 'more details' link shown under the heading (changes to 'hide details'
370 // when the details section is expanded).
371 views::Link* more_details_;
373 gfx::SlideAnimation slide_animation_;
375 // The up/down arrow next to the 'more detail' link (points up/down depending
376 // on whether the details section is expanded).
377 views::ImageButton* arrow_toggle_;
379 // Whether the details section is expanded.
380 bool expanded_;
382 DISALLOW_COPY_AND_ASSIGN(ExpandableContainerView);
385 void ShowExtensionInstallDialogImpl(
386 const ExtensionInstallPrompt::ShowParams& show_params,
387 ExtensionInstallPrompt::Delegate* delegate,
388 const ExtensionInstallPrompt::Prompt& prompt) {
389 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
390 CreateBrowserModalDialogViews(
391 new ExtensionInstallDialogView(show_params.navigator, delegate, prompt),
392 show_params.parent_window)->Show();
395 } // namespace
397 CustomScrollableView::CustomScrollableView() {}
398 CustomScrollableView::~CustomScrollableView() {}
400 void CustomScrollableView::Layout() {
401 SetBounds(x(), y(), width(), GetHeightForWidth(width()));
402 views::View::Layout();
405 ExtensionInstallDialogView::ExtensionInstallDialogView(
406 content::PageNavigator* navigator,
407 ExtensionInstallPrompt::Delegate* delegate,
408 const ExtensionInstallPrompt::Prompt& prompt)
409 : navigator_(navigator),
410 delegate_(delegate),
411 prompt_(prompt),
412 scroll_view_(NULL),
413 scrollable_(NULL),
414 scrollable_header_only_(NULL),
415 show_details_link_(NULL),
416 checkbox_info_label_(NULL),
417 unchecked_boxes_(0) {
418 // Possible grid layouts without ExtensionPermissionDialog experiment:
419 // Inline install
420 // w/ permissions no permissions
421 // +--------------------+------+ +--------------+------+
422 // | heading | icon | | heading | icon |
423 // +--------------------| | +--------------| |
424 // | rating | | | rating | |
425 // +--------------------| | +--------------+ |
426 // | user_count | | | user_count | |
427 // +--------------------| | +--------------| |
428 // | store_link | | | store_link | |
429 // +--------------------+------+ +--------------+------+
430 // | separator |
431 // +--------------------+------+
432 // | permissions_header | |
433 // +--------------------+------+
434 // | permission1 | |
435 // +--------------------+------+
436 // | permission2 | |
437 // +--------------------+------+
439 // Regular install
440 // w/ permissions XOR oauth issues no permissions
441 // +--------------------+------+ +--------------+------+
442 // | heading | icon | | heading | icon |
443 // +--------------------| | +--------------+------+
444 // | permissions_header | |
445 // +--------------------| |
446 // | permission1 | |
447 // +--------------------| |
448 // | permission2 | |
449 // +--------------------+------+
451 // w/ permissions AND oauth issues
452 // +--------------------+------+
453 // | heading | icon |
454 // +--------------------| |
455 // | permissions_header | |
456 // +--------------------| |
457 // | permission1 | |
458 // +--------------------| |
459 // | permission2 | |
460 // +--------------------+------+
461 // | oauth header |
462 // +---------------------------+
463 // | oauth issue 1 |
464 // +---------------------------+
465 // | oauth issue 2 |
466 // +---------------------------+
468 // If the ExtensionPermissionDialog is on, the layout is modified depending
469 // on the experiment group. For text only experiment, a footer is added at the
470 // bottom of the layouts. For others, inline details are added below some of
471 // the permissions.
473 // Regular install w/ permissions and footer (experiment):
474 // +--------------------+------+
475 // | heading | icon |
476 // +--------------------| |
477 // | permissions_header | |
478 // +--------------------| |
479 // | permission1 | |
480 // +--------------------| |
481 // | permission2 | |
482 // +--------------------+------+
483 // | footer text | |
484 // +--------------------+------+
486 // Regular install w/ permissions and inline explanations (experiment):
487 // +--------------------+------+
488 // | heading | icon |
489 // +--------------------| |
490 // | permissions_header | |
491 // +--------------------| |
492 // | permission1 | |
493 // +--------------------| |
494 // | explanation1 | |
495 // +--------------------| |
496 // | permission2 | |
497 // +--------------------| |
498 // | explanation2 | |
499 // +--------------------+------+
501 // Regular install w/ permissions and inline explanations (experiment):
502 // +--------------------+------+
503 // | heading | icon |
504 // +--------------------| |
505 // | permissions_header | |
506 // +--------------------| |
507 // |checkbox|permission1| |
508 // +--------------------| |
509 // |checkbox|permission2| |
510 // +--------------------+------+
512 // Additionally, links or informational text is added to non-client areas of
513 // the dialog depending on the experiment group.
515 int left_column_width =
516 (prompt.ShouldShowPermissions() + prompt.GetOAuthIssueCount() +
517 prompt.GetRetainedFileCount()) > 0 ?
518 kPermissionsLeftColumnWidth : kNoPermissionsLeftColumnWidth;
519 if (is_bundle_install())
520 left_column_width = kBundleLeftColumnWidth;
521 if (is_external_install())
522 left_column_width = kExternalInstallLeftColumnWidth;
524 scroll_view_ = new views::ScrollView();
525 scroll_view_->set_hide_horizontal_scrollbar(true);
526 AddChildView(scroll_view_);
528 int column_set_id = 0;
529 // Create the full scrollable view which will contain all the information
530 // including the permissions.
531 scrollable_ = new CustomScrollableView();
532 views::GridLayout* layout = CreateLayout(
533 scrollable_, left_column_width, column_set_id, false);
534 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
536 if (prompt.ShouldShowPermissions() &&
537 prompt.experiment()->should_show_expandable_permission_list()) {
538 // If the experiment should hide the permission list initially, create a
539 // simple layout that contains only the header, extension name and icon.
540 scrollable_header_only_ = new CustomScrollableView();
541 CreateLayout(scrollable_header_only_, left_column_width,
542 column_set_id, true);
543 scroll_view_->SetContents(scrollable_header_only_);
544 } else {
545 scroll_view_->SetContents(scrollable_);
548 int dialog_width = left_column_width + 2 * views::kPanelHorizMargin;
549 if (!is_bundle_install())
550 dialog_width += views::kPanelHorizMargin + kIconSize + kIconOffset;
552 // Widen the dialog for experiment with checkboxes so that the information
553 // label fits the area to the left of the buttons.
554 if (prompt.experiment()->show_checkboxes())
555 dialog_width += 4 * views::kPanelHorizMargin;
557 if (prompt.has_webstore_data()) {
558 layout->StartRow(0, column_set_id);
559 views::View* rating = new views::View();
560 rating->SetLayoutManager(new views::BoxLayout(
561 views::BoxLayout::kHorizontal, 0, 0, 0));
562 layout->AddView(rating);
563 prompt.AppendRatingStars(AddResourceIcon, rating);
565 const gfx::FontList& small_font_list =
566 rb.GetFontList(ui::ResourceBundle::SmallFont);
567 views::Label* rating_count =
568 new views::Label(prompt.GetRatingCount(), small_font_list);
569 // Add some space between the stars and the rating count.
570 rating_count->SetBorder(views::Border::CreateEmptyBorder(0, 2, 0, 0));
571 rating->AddChildView(rating_count);
573 layout->StartRow(0, column_set_id);
574 views::Label* user_count =
575 new views::Label(prompt.GetUserCount(), small_font_list);
576 user_count->SetAutoColorReadabilityEnabled(false);
577 user_count->SetEnabledColor(SK_ColorGRAY);
578 layout->AddView(user_count);
580 layout->StartRow(0, column_set_id);
581 views::Link* store_link = new views::Link(
582 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_STORE_LINK));
583 store_link->SetFontList(small_font_list);
584 store_link->set_listener(this);
585 layout->AddView(store_link);
588 if (is_bundle_install()) {
589 BundleInstaller::ItemList items = prompt.bundle()->GetItemsWithState(
590 BundleInstaller::Item::STATE_PENDING);
591 for (size_t i = 0; i < items.size(); ++i) {
592 base::string16 extension_name =
593 base::UTF8ToUTF16(items[i].localized_name);
594 base::i18n::AdjustStringForLocaleDirection(&extension_name);
595 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
596 layout->StartRow(0, column_set_id);
597 views::Label* extension_label = new views::Label(
598 PrepareForDisplay(extension_name, true));
599 extension_label->SetMultiLine(true);
600 extension_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
601 extension_label->SizeToFit(left_column_width);
602 layout->AddView(extension_label);
606 if (prompt.ShouldShowPermissions()) {
607 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
609 if (prompt.GetPermissionCount() > 0) {
610 if (is_inline_install()) {
611 layout->StartRow(0, column_set_id);
612 layout->AddView(new views::Separator(views::Separator::HORIZONTAL),
613 3, 1, views::GridLayout::FILL, views::GridLayout::FILL);
614 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
617 layout->StartRow(0, column_set_id);
618 views::Label* permissions_header = NULL;
619 if (is_bundle_install()) {
620 // We need to pass the FontList in the constructor, rather than calling
621 // SetFontList later, because otherwise SizeToFit mis-judges the width
622 // of the line.
623 permissions_header = new views::Label(
624 prompt.GetPermissionsHeading(),
625 rb.GetFontList(ui::ResourceBundle::MediumFont));
626 } else {
627 permissions_header = new views::Label(prompt.GetPermissionsHeading());
629 permissions_header->SetMultiLine(true);
630 permissions_header->SetHorizontalAlignment(gfx::ALIGN_LEFT);
631 permissions_header->SizeToFit(left_column_width);
632 layout->AddView(permissions_header);
634 for (size_t i = 0; i < prompt.GetPermissionCount(); ++i) {
635 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
636 layout->StartRow(0, column_set_id);
637 views::Label* permission_label =
638 new views::Label(prompt.GetPermission(i));
640 const SkColor kTextHighlight = SK_ColorRED;
641 const SkColor kBackgroundHighlight = SkColorSetRGB(0xFB, 0xF7, 0xA3);
642 if (prompt.experiment()->ShouldHighlightText(
643 prompt.GetPermission(i))) {
644 permission_label->SetAutoColorReadabilityEnabled(false);
645 permission_label->SetEnabledColor(kTextHighlight);
646 } else if (prompt.experiment()->ShouldHighlightBackground(
647 prompt.GetPermission(i))) {
648 permission_label->SetLineHeight(18);
649 permission_label->set_background(
650 views::Background::CreateSolidBackground(kBackgroundHighlight));
653 permission_label->SetMultiLine(true);
654 permission_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
655 permission_label->SizeToFit(left_column_width);
657 if (prompt.experiment()->show_checkboxes()) {
658 layout->AddView(new CheckboxedView(permission_label, this));
659 ++unchecked_boxes_;
660 } else {
661 layout->AddView(new BulletedView(permission_label));
663 // If we have more details to provide, show them in collapsed form.
664 if (!prompt.GetPermissionsDetails(i).empty()) {
665 layout->StartRow(0, column_set_id);
666 PermissionDetails details;
667 details.push_back(
668 PrepareForDisplay(prompt.GetPermissionsDetails(i), false));
669 ExpandableContainerView* details_container =
670 new ExpandableContainerView(
671 this, base::string16(), details, left_column_width,
672 true, true, false);
673 layout->AddView(details_container);
676 if (prompt.experiment()->should_show_inline_explanations()) {
677 base::string16 explanation =
678 prompt.experiment()->GetInlineExplanation(
679 prompt.GetPermission(i));
680 if (!explanation.empty()) {
681 PermissionDetails details;
682 details.push_back(explanation);
683 ExpandableContainerView* container =
684 new ExpandableContainerView(this, base::string16(), details,
685 left_column_width,
686 false, false, true);
687 // Inline explanations are expanded by default if there is
688 // no "Show details" link.
689 if (!prompt.experiment()->show_details_link())
690 container->ExpandWithoutAnimation();
691 layout->StartRow(0, column_set_id);
692 layout->AddView(container);
693 inline_explanations_.push_back(container);
697 } else {
698 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
699 layout->StartRow(0, column_set_id);
700 views::Label* permission_label = new views::Label(
701 l10n_util::GetStringUTF16(IDS_EXTENSION_NO_SPECIAL_PERMISSIONS));
702 permission_label->SetMultiLine(true);
703 permission_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
704 permission_label->SizeToFit(left_column_width);
705 layout->AddView(permission_label);
709 if (prompt.GetOAuthIssueCount()) {
710 // Slide in under the permissions, if there are any. If there are
711 // permissions, the OAuth prompt stretches all the way to the right of the
712 // dialog. If there are no permissions, the OAuth prompt just takes up the
713 // left column.
714 int space_for_oauth = left_column_width;
715 if (prompt.GetPermissionCount()) {
716 space_for_oauth += kIconSize;
717 views::ColumnSet* column_set = layout->AddColumnSet(++column_set_id);
718 column_set->AddColumn(views::GridLayout::FILL,
719 views::GridLayout::FILL,
721 views::GridLayout::USE_PREF,
722 0, // no fixed width
723 space_for_oauth);
726 layout->StartRowWithPadding(0, column_set_id,
727 0, views::kRelatedControlVerticalSpacing);
728 views::Label* oauth_header = new views::Label(prompt.GetOAuthHeading());
729 oauth_header->SetMultiLine(true);
730 oauth_header->SetHorizontalAlignment(gfx::ALIGN_LEFT);
731 oauth_header->SizeToFit(left_column_width);
732 layout->AddView(oauth_header);
734 for (size_t i = 0; i < prompt.GetOAuthIssueCount(); ++i) {
735 layout->StartRowWithPadding(
736 0, column_set_id,
737 0, views::kRelatedControlVerticalSpacing);
739 PermissionDetails details;
740 const IssueAdviceInfoEntry& entry = prompt.GetOAuthIssue(i);
741 for (size_t x = 0; x < entry.details.size(); ++x)
742 details.push_back(entry.details[x]);
743 ExpandableContainerView* issue_advice_view =
744 new ExpandableContainerView(
745 this, entry.description, details, space_for_oauth,
746 true, true, false);
747 layout->AddView(issue_advice_view);
750 if (prompt.GetRetainedFileCount()) {
751 // Slide in under the permissions or OAuth, if there are any. If there are
752 // either, the retained files prompt stretches all the way to the right of
753 // the dialog. If there are no permissions or OAuth, the retained files
754 // prompt just takes up the left column.
755 int space_for_files = left_column_width;
756 if (prompt.GetPermissionCount() || prompt.GetOAuthIssueCount()) {
757 space_for_files += kIconSize;
758 views::ColumnSet* column_set = layout->AddColumnSet(++column_set_id);
759 column_set->AddColumn(views::GridLayout::FILL,
760 views::GridLayout::FILL,
762 views::GridLayout::USE_PREF,
763 0, // no fixed width
764 space_for_files);
767 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
769 layout->StartRow(0, column_set_id);
770 views::Label* retained_files_header = NULL;
771 retained_files_header =
772 new views::Label(prompt.GetRetainedFilesHeading());
773 retained_files_header->SetMultiLine(true);
774 retained_files_header->SetHorizontalAlignment(gfx::ALIGN_LEFT);
775 retained_files_header->SizeToFit(space_for_files);
776 layout->AddView(retained_files_header);
778 layout->StartRow(0, column_set_id);
779 PermissionDetails details;
780 for (size_t i = 0; i < prompt.GetRetainedFileCount(); ++i)
781 details.push_back(prompt.GetRetainedFile(i));
782 ExpandableContainerView* issue_advice_view =
783 new ExpandableContainerView(
784 this, base::string16(), details, space_for_files,
785 false, true, false);
786 layout->AddView(issue_advice_view);
789 DCHECK(prompt.type() >= 0);
790 UMA_HISTOGRAM_ENUMERATION("Extensions.InstallPrompt.Type",
791 prompt.type(),
792 ExtensionInstallPrompt::NUM_PROMPT_TYPES);
794 if (prompt.ShouldShowPermissions()) {
795 if (prompt.ShouldShowExplanationText()) {
796 views::ColumnSet* column_set = layout->AddColumnSet(++column_set_id);
797 column_set->AddColumn(views::GridLayout::LEADING,
798 views::GridLayout::FILL,
800 views::GridLayout::USE_PREF,
803 // Add two rows of space so that the text stands out.
804 layout->AddPaddingRow(0, 2 * views::kRelatedControlVerticalSpacing);
806 layout->StartRow(0, column_set_id);
807 views::Label* explanation = new views::Label(
808 prompt.experiment()->GetExplanationText());
809 explanation->SetMultiLine(true);
810 explanation->SetHorizontalAlignment(gfx::ALIGN_LEFT);
811 explanation->SizeToFit(left_column_width + kIconSize);
812 layout->AddView(explanation);
815 if (prompt.experiment()->should_show_expandable_permission_list() ||
816 (prompt.experiment()->show_details_link() &&
817 prompt.experiment()->should_show_inline_explanations() &&
818 !inline_explanations_.empty())) {
819 // Don't show the "Show details" link if there are OAuth issues or
820 // retained files. These have their own "Show details" links and having
821 // multiple levels of links is confusing.
822 if (prompt.GetOAuthIssueCount() + prompt.GetRetainedFileCount() == 0) {
823 int text_id =
824 prompt.experiment()->should_show_expandable_permission_list() ?
825 IDS_EXTENSION_PROMPT_EXPERIMENT_SHOW_PERMISSIONS :
826 IDS_EXTENSION_PROMPT_EXPERIMENT_SHOW_DETAILS;
827 show_details_link_ = new views::Link(
828 l10n_util::GetStringUTF16(text_id));
829 show_details_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
830 show_details_link_->set_listener(this);
831 UpdateLinkActionHistogram(LINK_SHOWN);
832 } else {
833 UpdateLinkActionHistogram(LINK_NOT_SHOWN);
837 if (prompt.experiment()->show_checkboxes()) {
838 checkbox_info_label_ = new views::Label(
839 l10n_util::GetStringUTF16(
840 IDS_EXTENSION_PROMPT_EXPERIMENT_CHECKBOX_INFO));
841 checkbox_info_label_->SetMultiLine(true);
842 checkbox_info_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
843 checkbox_info_label_->SetAutoColorReadabilityEnabled(false);
844 checkbox_info_label_->SetEnabledColor(kLighterLabelColor);
848 gfx::Size scrollable_size = scrollable_->GetPreferredSize();
849 scrollable_->SetBoundsRect(gfx::Rect(scrollable_size));
850 dialog_size_ = gfx::Size(
851 dialog_width,
852 std::min(scrollable_size.height(), kDialogMaxHeight));
854 if (scrollable_header_only_) {
855 gfx::Size header_only_size = scrollable_header_only_->GetPreferredSize();
856 scrollable_header_only_->SetBoundsRect(gfx::Rect(header_only_size));
857 dialog_size_ = gfx::Size(
858 dialog_width, std::min(header_only_size.height(), kDialogMaxHeight));
862 ExtensionInstallDialogView::~ExtensionInstallDialogView() {}
864 views::GridLayout* ExtensionInstallDialogView::CreateLayout(
865 views::View* parent,
866 int left_column_width,
867 int column_set_id,
868 bool single_detail_row) const {
869 views::GridLayout* layout = views::GridLayout::CreatePanel(parent);
870 parent->SetLayoutManager(layout);
872 views::ColumnSet* column_set = layout->AddColumnSet(column_set_id);
873 column_set->AddColumn(views::GridLayout::LEADING,
874 views::GridLayout::FILL,
875 0, // no resizing
876 views::GridLayout::USE_PREF,
877 0, // no fixed width
878 left_column_width);
879 if (!is_bundle_install()) {
880 column_set->AddPaddingColumn(0, views::kPanelHorizMargin);
881 column_set->AddColumn(views::GridLayout::TRAILING,
882 views::GridLayout::LEADING,
883 0, // no resizing
884 views::GridLayout::USE_PREF,
885 0, // no fixed width
886 kIconSize);
889 layout->StartRow(0, column_set_id);
891 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
893 views::Label* heading = new views::Label(
894 prompt_.GetHeading(), rb.GetFontList(ui::ResourceBundle::MediumFont));
895 heading->SetMultiLine(true);
896 heading->SetHorizontalAlignment(gfx::ALIGN_LEFT);
897 heading->SizeToFit(left_column_width);
898 layout->AddView(heading);
900 if (!is_bundle_install()) {
901 // Scale down to icon size, but allow smaller icons (don't scale up).
902 const gfx::ImageSkia* image = prompt_.icon().ToImageSkia();
903 gfx::Size size(image->width(), image->height());
904 if (size.width() > kIconSize || size.height() > kIconSize)
905 size = gfx::Size(kIconSize, kIconSize);
906 views::ImageView* icon = new views::ImageView();
907 icon->SetImageSize(size);
908 icon->SetImage(*image);
909 icon->SetHorizontalAlignment(views::ImageView::CENTER);
910 icon->SetVerticalAlignment(views::ImageView::CENTER);
911 if (single_detail_row) {
912 layout->AddView(icon);
913 } else {
914 int icon_row_span = 1;
915 if (is_inline_install()) {
916 // Also span the rating, user_count and store_link rows.
917 icon_row_span = 4;
918 } else if (prompt_.ShouldShowPermissions()) {
919 size_t permission_count = prompt_.GetPermissionCount();
920 // Also span the permission header and each of the permission rows (all
921 // have a padding row above it). This also works for the 'no special
922 // permissions' case.
923 icon_row_span = 3 + permission_count * 2;
924 } else if (prompt_.GetOAuthIssueCount()) {
925 // Also span the permission header and each of the permission rows (all
926 // have a padding row above it).
927 icon_row_span = 3 + prompt_.GetOAuthIssueCount() * 2;
928 } else if (prompt_.GetRetainedFileCount()) {
929 // Also span the permission header and the retained files container.
930 icon_row_span = 4;
932 layout->AddView(icon, 1, icon_row_span);
935 return layout;
938 void ExtensionInstallDialogView::ContentsChanged() {
939 Layout();
942 void ExtensionInstallDialogView::ViewHierarchyChanged(
943 const ViewHierarchyChangedDetails& details) {
944 // Since we want the links to show up in the same visual row as the accept
945 // and cancel buttons, which is provided by the framework, we must add the
946 // buttons to the non-client view, which is the parent of this view.
947 // Similarly, when we're removed from the view hierarchy, we must take care
948 // to clean up those items as well.
949 if (details.child == this) {
950 if (details.is_add) {
951 if (show_details_link_)
952 details.parent->AddChildView(show_details_link_);
953 if (checkbox_info_label_)
954 details.parent->AddChildView(checkbox_info_label_);
955 } else {
956 if (show_details_link_)
957 details.parent->RemoveChildView(show_details_link_);
958 if (checkbox_info_label_)
959 details.parent->RemoveChildView(checkbox_info_label_);
964 int ExtensionInstallDialogView::GetDialogButtons() const {
965 int buttons = prompt_.GetDialogButtons();
966 // Simply having just an OK button is *not* supported. See comment on function
967 // GetDialogButtons in dialog_delegate.h for reasons.
968 DCHECK_GT(buttons & ui::DIALOG_BUTTON_CANCEL, 0);
969 return buttons;
972 base::string16 ExtensionInstallDialogView::GetDialogButtonLabel(
973 ui::DialogButton button) const {
974 switch (button) {
975 case ui::DIALOG_BUTTON_OK:
976 return prompt_.GetAcceptButtonLabel();
977 case ui::DIALOG_BUTTON_CANCEL:
978 return prompt_.HasAbortButtonLabel() ?
979 prompt_.GetAbortButtonLabel() :
980 l10n_util::GetStringUTF16(IDS_CANCEL);
981 default:
982 NOTREACHED();
983 return base::string16();
987 int ExtensionInstallDialogView::GetDefaultDialogButton() const {
988 return ui::DIALOG_BUTTON_CANCEL;
991 bool ExtensionInstallDialogView::Cancel() {
992 UpdateInstallResultHistogram(false);
993 delegate_->InstallUIAbort(true);
994 return true;
997 bool ExtensionInstallDialogView::Accept() {
998 UpdateInstallResultHistogram(true);
999 delegate_->InstallUIProceed();
1000 return true;
1003 ui::ModalType ExtensionInstallDialogView::GetModalType() const {
1004 return ui::MODAL_TYPE_WINDOW;
1007 base::string16 ExtensionInstallDialogView::GetWindowTitle() const {
1008 return prompt_.GetDialogTitle();
1011 void ExtensionInstallDialogView::LinkClicked(views::Link* source,
1012 int event_flags) {
1013 if (source == show_details_link_) {
1014 UpdateLinkActionHistogram(LINK_CLICKED);
1015 // Show details link is used to either reveal whole permission list or to
1016 // reveal inline explanations.
1017 if (prompt_.experiment()->should_show_expandable_permission_list()) {
1018 gfx::Rect bounds = GetWidget()->GetWindowBoundsInScreen();
1019 int spacing = bounds.height() -
1020 scrollable_header_only_->GetPreferredSize().height();
1021 int content_height = std::min(scrollable_->GetPreferredSize().height(),
1022 kDialogMaxHeight);
1023 bounds.set_height(spacing + content_height);
1024 scroll_view_->SetContents(scrollable_);
1025 GetWidget()->SetBoundsConstrained(bounds);
1026 ContentsChanged();
1027 } else {
1028 ToggleInlineExplanations();
1030 show_details_link_->SetVisible(false);
1031 } else {
1032 GURL store_url(extension_urls::GetWebstoreItemDetailURLPrefix() +
1033 prompt_.extension()->id());
1034 OpenURLParams params(
1035 store_url, Referrer(), NEW_FOREGROUND_TAB,
1036 content::PAGE_TRANSITION_LINK,
1037 false);
1038 navigator_->OpenURL(params);
1039 GetWidget()->Close();
1043 void ExtensionInstallDialogView::ToggleInlineExplanations() {
1044 for (InlineExplanations::iterator it = inline_explanations_.begin();
1045 it != inline_explanations_.end(); ++it)
1046 (*it)->ToggleDetailLevel();
1049 void ExtensionInstallDialogView::Layout() {
1050 scroll_view_->SetBounds(0, 0, width(), height());
1052 if (show_details_link_ || checkbox_info_label_) {
1053 views::LabelButton* cancel_button = GetDialogClientView()->cancel_button();
1054 gfx::Rect parent_bounds = parent()->GetContentsBounds();
1055 // By default, layouts have an inset of kButtonHEdgeMarginNew. In order to
1056 // align the link horizontally with the left side of the contents of the
1057 // layout, put a horizontal margin with this amount.
1058 const int horizontal_margin = views::kButtonHEdgeMarginNew;
1059 const int vertical_margin = views::kButtonVEdgeMarginNew;
1060 int y_buttons = parent_bounds.bottom() -
1061 cancel_button->GetPreferredSize().height() - vertical_margin;
1062 int max_width = dialog_size_.width() - cancel_button->width() * 2 -
1063 horizontal_margin * 2 - views::kRelatedButtonHSpacing;
1064 if (show_details_link_) {
1065 gfx::Size link_size = show_details_link_->GetPreferredSize();
1066 show_details_link_->SetBounds(
1067 horizontal_margin,
1068 y_buttons + (cancel_button->height() - link_size.height()) / 2,
1069 link_size.width(), link_size.height());
1071 if (checkbox_info_label_) {
1072 gfx::Size label_size = checkbox_info_label_->GetPreferredSize();
1073 checkbox_info_label_->SetBounds(
1074 horizontal_margin,
1075 y_buttons + (cancel_button->height() - label_size.height()) / 2,
1076 label_size.width(), label_size.height());
1077 checkbox_info_label_->SizeToFit(max_width);
1080 // Disable accept button if there are unchecked boxes and
1081 // the experiment is on.
1082 if (prompt_.experiment()->show_checkboxes())
1083 GetDialogClientView()->ok_button()->SetEnabled(unchecked_boxes_ == 0);
1085 DialogDelegateView::Layout();
1088 gfx::Size ExtensionInstallDialogView::GetPreferredSize() const {
1089 return dialog_size_;
1092 void ExtensionInstallDialogView::ButtonPressed(views::Button* sender,
1093 const ui::Event& event) {
1094 if (std::string(views::Checkbox::kViewClassName) == sender->GetClassName()) {
1095 views::Checkbox* checkbox = static_cast<views::Checkbox*>(sender);
1096 if (checkbox->checked())
1097 --unchecked_boxes_;
1098 else
1099 ++unchecked_boxes_;
1101 GetDialogClientView()->ok_button()->SetEnabled(unchecked_boxes_ == 0);
1102 checkbox_info_label_->SetVisible(unchecked_boxes_ > 0);
1106 void ExtensionInstallDialogView::UpdateInstallResultHistogram(bool accepted)
1107 const {
1108 if (prompt_.type() == ExtensionInstallPrompt::INSTALL_PROMPT)
1109 UMA_HISTOGRAM_BOOLEAN("Extensions.InstallPrompt.Accepted", accepted);
1112 void ExtensionInstallDialogView::UpdateLinkActionHistogram(int action_type)
1113 const {
1114 if (prompt_.experiment()->should_show_expandable_permission_list()) {
1115 // The clickable link in the UI is "Show Permissions".
1116 UMA_HISTOGRAM_ENUMERATION(
1117 "Extensions.InstallPromptExperiment.ShowPermissions",
1118 action_type,
1119 NUM_LINK_ACTIONS);
1120 } else {
1121 // The clickable link in the UI is "Show Details".
1122 UMA_HISTOGRAM_ENUMERATION(
1123 "Extensions.InstallPromptExperiment.ShowDetails",
1124 action_type,
1125 NUM_LINK_ACTIONS);
1129 // static
1130 ExtensionInstallPrompt::ShowDialogCallback
1131 ExtensionInstallPrompt::GetDefaultShowDialogCallback() {
1132 return base::Bind(&ShowExtensionInstallDialogImpl);
1135 // ExpandableContainerView::DetailsView ----------------------------------------
1137 ExpandableContainerView::DetailsView::DetailsView(int horizontal_space,
1138 bool parent_bulleted,
1139 bool lighter_color)
1140 : layout_(new views::GridLayout(this)),
1141 state_(0),
1142 lighter_color_(lighter_color) {
1143 SetLayoutManager(layout_);
1144 views::ColumnSet* column_set = layout_->AddColumnSet(0);
1145 // If the parent is using bullets for its items, then a padding of one unit
1146 // will make the child item (which has no bullet) look like a sibling of its
1147 // parent. Therefore increase the indentation by one more unit to show that it
1148 // is in fact a child item (with no missing bullet) and not a sibling.
1149 int padding =
1150 views::kRelatedControlHorizontalSpacing * (parent_bulleted ? 2 : 1);
1151 column_set->AddPaddingColumn(0, padding);
1152 column_set->AddColumn(views::GridLayout::LEADING,
1153 views::GridLayout::LEADING,
1155 views::GridLayout::FIXED,
1156 horizontal_space - padding,
1160 void ExpandableContainerView::DetailsView::AddDetail(
1161 const base::string16& detail) {
1162 layout_->StartRowWithPadding(0, 0,
1163 0, views::kRelatedControlSmallVerticalSpacing);
1164 views::Label* detail_label =
1165 new views::Label(PrepareForDisplay(detail, false));
1166 detail_label->SetMultiLine(true);
1167 detail_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1168 if (lighter_color_) {
1169 detail_label->SetEnabledColor(kLighterLabelColor);
1170 detail_label->SetAutoColorReadabilityEnabled(false);
1172 layout_->AddView(detail_label);
1175 gfx::Size ExpandableContainerView::DetailsView::GetPreferredSize() const {
1176 gfx::Size size = views::View::GetPreferredSize();
1177 return gfx::Size(size.width(), size.height() * state_);
1180 void ExpandableContainerView::DetailsView::AnimateToState(double state) {
1181 state_ = state;
1182 PreferredSizeChanged();
1183 SchedulePaint();
1186 // ExpandableContainerView -----------------------------------------------------
1188 ExpandableContainerView::ExpandableContainerView(
1189 ExtensionInstallDialogView* owner,
1190 const base::string16& description,
1191 const PermissionDetails& details,
1192 int horizontal_space,
1193 bool parent_bulleted,
1194 bool show_expand_link,
1195 bool lighter_color_details)
1196 : owner_(owner),
1197 details_view_(NULL),
1198 more_details_(NULL),
1199 slide_animation_(this),
1200 arrow_toggle_(NULL),
1201 expanded_(false) {
1202 views::GridLayout* layout = new views::GridLayout(this);
1203 SetLayoutManager(layout);
1204 int column_set_id = 0;
1205 views::ColumnSet* column_set = layout->AddColumnSet(column_set_id);
1206 column_set->AddColumn(views::GridLayout::LEADING,
1207 views::GridLayout::LEADING,
1209 views::GridLayout::USE_PREF,
1212 if (!description.empty()) {
1213 layout->StartRow(0, column_set_id);
1215 views::Label* description_label = new views::Label(description);
1216 description_label->SetMultiLine(true);
1217 description_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1218 description_label->SizeToFit(horizontal_space);
1219 layout->AddView(new BulletedView(description_label));
1222 if (details.empty())
1223 return;
1225 details_view_ = new DetailsView(horizontal_space, parent_bulleted,
1226 lighter_color_details);
1228 layout->StartRow(0, column_set_id);
1229 layout->AddView(details_view_);
1231 for (size_t i = 0; i < details.size(); ++i)
1232 details_view_->AddDetail(details[i]);
1234 // TODO(meacer): Remove show_expand_link when the experiment is completed.
1235 if (show_expand_link) {
1236 views::Link* link = new views::Link(
1237 l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS));
1239 // Make sure the link width column is as wide as needed for both Show and
1240 // Hide details, so that the arrow doesn't shift horizontally when we
1241 // toggle.
1242 int link_col_width =
1243 views::kRelatedControlHorizontalSpacing +
1244 std::max(gfx::GetStringWidth(
1245 l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS),
1246 link->font_list()),
1247 gfx::GetStringWidth(
1248 l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS),
1249 link->font_list()));
1251 column_set = layout->AddColumnSet(++column_set_id);
1252 // Padding to the left of the More Details column. If the parent is using
1253 // bullets for its items, then a padding of one unit will make the child
1254 // item (which has no bullet) look like a sibling of its parent. Therefore
1255 // increase the indentation by one more unit to show that it is in fact a
1256 // child item (with no missing bullet) and not a sibling.
1257 column_set->AddPaddingColumn(
1258 0, views::kRelatedControlHorizontalSpacing * (parent_bulleted ? 2 : 1));
1259 // The More Details column.
1260 column_set->AddColumn(views::GridLayout::LEADING,
1261 views::GridLayout::LEADING,
1263 views::GridLayout::FIXED,
1264 link_col_width,
1265 link_col_width);
1266 // The Up/Down arrow column.
1267 column_set->AddColumn(views::GridLayout::LEADING,
1268 views::GridLayout::LEADING,
1270 views::GridLayout::USE_PREF,
1274 // Add the More Details link.
1275 layout->StartRow(0, column_set_id);
1276 more_details_ = link;
1277 more_details_->set_listener(this);
1278 more_details_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1279 layout->AddView(more_details_);
1281 // Add the arrow after the More Details link.
1282 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1283 arrow_toggle_ = new views::ImageButton(this);
1284 arrow_toggle_->SetImage(views::Button::STATE_NORMAL,
1285 rb.GetImageSkiaNamed(IDR_DOWN_ARROW));
1286 layout->AddView(arrow_toggle_);
1290 ExpandableContainerView::~ExpandableContainerView() {
1293 void ExpandableContainerView::ButtonPressed(
1294 views::Button* sender, const ui::Event& event) {
1295 ToggleDetailLevel();
1298 void ExpandableContainerView::LinkClicked(
1299 views::Link* source, int event_flags) {
1300 ToggleDetailLevel();
1303 void ExpandableContainerView::AnimationProgressed(
1304 const gfx::Animation* animation) {
1305 DCHECK_EQ(&slide_animation_, animation);
1306 if (details_view_)
1307 details_view_->AnimateToState(animation->GetCurrentValue());
1310 void ExpandableContainerView::AnimationEnded(const gfx::Animation* animation) {
1311 if (arrow_toggle_) {
1312 if (animation->GetCurrentValue() != 0.0) {
1313 arrow_toggle_->SetImage(
1314 views::Button::STATE_NORMAL,
1315 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
1316 IDR_UP_ARROW));
1317 } else {
1318 arrow_toggle_->SetImage(
1319 views::Button::STATE_NORMAL,
1320 ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
1321 IDR_DOWN_ARROW));
1324 if (more_details_) {
1325 more_details_->SetText(expanded_ ?
1326 l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS) :
1327 l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS));
1331 void ExpandableContainerView::ChildPreferredSizeChanged(views::View* child) {
1332 owner_->ContentsChanged();
1335 void ExpandableContainerView::ToggleDetailLevel() {
1336 expanded_ = !expanded_;
1338 if (slide_animation_.IsShowing())
1339 slide_animation_.Hide();
1340 else
1341 slide_animation_.Show();
1344 void ExpandableContainerView::ExpandWithoutAnimation() {
1345 expanded_ = true;
1346 details_view_->AnimateToState(1.0);