extension_install_dialog_view.cc revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
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.
4
5#include <vector>
6
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/strings/string_util.h"
12#include "base/strings/utf_string_conversions.h"
13#include "chrome/browser/extensions/bundle_installer.h"
14#include "chrome/browser/extensions/extension_install_prompt.h"
15#include "chrome/browser/profiles/profile.h"
16#include "chrome/browser/ui/views/constrained_window_views.h"
17#include "chrome/common/chrome_switches.h"
18#include "chrome/common/extensions/extension.h"
19#include "chrome/installer/util/browser_distribution.h"
20#include "content/public/browser/page_navigator.h"
21#include "content/public/browser/web_contents.h"
22#include "grit/chromium_strings.h"
23#include "grit/generated_resources.h"
24#include "grit/google_chrome_strings.h"
25#include "grit/theme_resources.h"
26#include "ui/base/l10n/l10n_util.h"
27#include "ui/base/resource/resource_bundle.h"
28#include "ui/gfx/animation/animation_delegate.h"
29#include "ui/gfx/animation/slide_animation.h"
30#include "ui/gfx/transform.h"
31#include "ui/views/border.h"
32#include "ui/views/controls/button/image_button.h"
33#include "ui/views/controls/image_view.h"
34#include "ui/views/controls/label.h"
35#include "ui/views/controls/link.h"
36#include "ui/views/controls/link_listener.h"
37#include "ui/views/controls/scroll_view.h"
38#include "ui/views/controls/separator.h"
39#include "ui/views/layout/box_layout.h"
40#include "ui/views/layout/grid_layout.h"
41#include "ui/views/layout/layout_constants.h"
42#include "ui/views/view.h"
43#include "ui/views/widget/widget.h"
44#include "ui/views/window/dialog_delegate.h"
45
46using content::OpenURLParams;
47using content::Referrer;
48using extensions::BundleInstaller;
49
50namespace {
51
52// Size of extension icon in top left of dialog.
53const int kIconSize = 69;
54
55// The dialog width.
56const int kDialogWidth = 385;
57
58// The dialog will resize based on its content, but this sets a maximum height
59// before overflowing a scrollbar.
60const int kDialogMaxHeight = 300;
61
62// Width of the left column of the dialog when the extension requests
63// permissions.
64const int kPermissionsLeftColumnWidth = 250;
65
66// Width of the left column of the dialog when the extension requests no
67// permissions.
68const int kNoPermissionsLeftColumnWidth = 200;
69
70// Width of the left column for bundle install prompts. There's only one column
71// in this case, so make it wider than normal.
72const int kBundleLeftColumnWidth = 300;
73
74// Width of the left column for external install prompts. The text is long in
75// this case, so make it wider than normal.
76const int kExternalInstallLeftColumnWidth = 350;
77
78typedef std::vector<string16> PermissionDetails;
79
80void AddResourceIcon(const gfx::ImageSkia* skia_image, void* data) {
81  views::View* parent = static_cast<views::View*>(data);
82  views::ImageView* image_view = new views::ImageView();
83  image_view->SetImage(*skia_image);
84  parent->AddChildView(image_view);
85}
86
87// Creates a string for displaying |message| to the user. If it has to look
88// like a entry in a bullet point list, one is added.
89string16 PrepareForDisplay(const string16& message, bool bullet_point) {
90  return bullet_point ? l10n_util::GetStringFUTF16(
91      IDS_EXTENSION_PERMISSION_LINE,
92      message) : message;
93}
94
95// A custom scrollable view implementation for the dialog.
96class CustomScrollableView : public views::View {
97 public:
98  CustomScrollableView();
99  virtual ~CustomScrollableView();
100
101 private:
102  virtual void Layout() OVERRIDE;
103
104  DISALLOW_COPY_AND_ASSIGN(CustomScrollableView);
105};
106
107// Implements the extension installation dialog for TOOLKIT_VIEWS.
108class ExtensionInstallDialogView : public views::DialogDelegateView,
109                                   public views::LinkListener {
110 public:
111  ExtensionInstallDialogView(content::PageNavigator* navigator,
112                             ExtensionInstallPrompt::Delegate* delegate,
113                             const ExtensionInstallPrompt::Prompt& prompt);
114  virtual ~ExtensionInstallDialogView();
115
116  // Called when one of the child elements has expanded/collapsed.
117  void ContentsChanged();
118
119 private:
120  // views::DialogDelegateView:
121  virtual int GetDialogButtons() const OVERRIDE;
122  virtual string16 GetDialogButtonLabel(ui::DialogButton button) const OVERRIDE;
123  virtual int GetDefaultDialogButton() const OVERRIDE;
124  virtual bool Cancel() OVERRIDE;
125  virtual bool Accept() OVERRIDE;
126  virtual ui::ModalType GetModalType() const OVERRIDE;
127  virtual string16 GetWindowTitle() const OVERRIDE;
128  virtual void Layout() OVERRIDE;
129  virtual gfx::Size GetPreferredSize() OVERRIDE;
130
131  // views::WidgetDelegate
132  virtual bool ShouldShowWindowTitle() const OVERRIDE;
133  virtual bool ShouldShowCloseButton() const OVERRIDE;
134
135  // views::LinkListener:
136  virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE;
137
138  bool is_inline_install() const {
139    return prompt_.type() == ExtensionInstallPrompt::INLINE_INSTALL_PROMPT;
140  }
141
142  bool is_first_run() const {
143    return prompt_.type() ==
144        ExtensionInstallPrompt::DEFAULT_INSTALL_FIRST_RUN_PROMPT;
145  }
146
147  bool is_bundle_install() const {
148    return prompt_.type() == ExtensionInstallPrompt::BUNDLE_INSTALL_PROMPT;
149  }
150
151  bool is_external_install() const {
152    return prompt_.type() == ExtensionInstallPrompt::EXTERNAL_INSTALL_PROMPT;
153  }
154
155  content::PageNavigator* navigator_;
156  ExtensionInstallPrompt::Delegate* delegate_;
157  ExtensionInstallPrompt::Prompt prompt_;
158
159  // The scroll view containing all the details for the dialog (including all
160  // collapsible/expandable sections).
161  views::ScrollView* scroll_view_;
162
163  // The container view for the scroll view.
164  CustomScrollableView* scrollable_;
165
166  // The preferred size of the dialog.
167  gfx::Size dialog_size_;
168
169  DISALLOW_COPY_AND_ASSIGN(ExtensionInstallDialogView);
170};
171
172// A simple view that prepends a view with a bullet with the help of a grid
173// layout.
174class BulletedView : public views::View {
175 public:
176  explicit BulletedView(views::View* view);
177 private:
178  DISALLOW_COPY_AND_ASSIGN(BulletedView);
179};
180
181BulletedView::BulletedView(views::View* view) {
182  views::GridLayout* layout = new views::GridLayout(this);
183  SetLayoutManager(layout);
184  views::ColumnSet* column_set = layout->AddColumnSet(0);
185  column_set->AddColumn(views::GridLayout::LEADING,
186                        views::GridLayout::LEADING,
187                        0,
188                        views::GridLayout::USE_PREF,
189                        0, // no fixed width
190                        0);
191   column_set->AddColumn(views::GridLayout::LEADING,
192                         views::GridLayout::LEADING,
193                         0,
194                         views::GridLayout::USE_PREF,
195                         0,  // no fixed width
196                         0);
197  layout->StartRow(0, 0);
198  layout->AddView(new views::Label(PrepareForDisplay(string16(), true)));
199  layout->AddView(view);
200}
201
202// A view to display text with an expandable details section.
203class ExpandableContainerView : public views::View,
204                                public views::ButtonListener,
205                                public views::LinkListener,
206                                public gfx::AnimationDelegate {
207 public:
208  ExpandableContainerView(ExtensionInstallDialogView* owner,
209                          const string16& description,
210                          const PermissionDetails& details,
211                          int horizontal_space,
212                          bool parent_bulleted);
213  virtual ~ExpandableContainerView();
214
215  // views::View:
216  virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE;
217
218  // views::ButtonListener:
219  virtual void ButtonPressed(views::Button* sender,
220                             const ui::Event& event) OVERRIDE;
221
222  // views::LinkListener:
223  virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE;
224
225  // gfx::AnimationDelegate:
226  virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE;
227  virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE;
228
229 private:
230  // A view which displays all the details of an IssueAdviceInfoEntry.
231  class DetailsView : public views::View {
232   public:
233    explicit DetailsView(int horizontal_space, bool parent_bulleted);
234    virtual ~DetailsView() {}
235
236    // views::View:
237    virtual gfx::Size GetPreferredSize() OVERRIDE;
238
239    void AddDetail(const string16& detail);
240
241    // Animates this to be a height proportional to |state|.
242    void AnimateToState(double state);
243
244   private:
245    views::GridLayout* layout_;
246    double state_;
247
248    // Whether the parent item is showing bullets. This will determine how much
249    // extra indentation is needed.
250    bool parent_bulleted_;
251
252    DISALLOW_COPY_AND_ASSIGN(DetailsView);
253  };
254
255  // Expand/Collapse the detail section for this ExpandableContainerView.
256  void ToggleDetailLevel();
257
258  // The dialog that owns |this|. It's also an ancestor in the View hierarchy.
259  ExtensionInstallDialogView* owner_;
260
261  // A view for showing |issue_advice.details|.
262  DetailsView* details_view_;
263
264  // The '>' zippy control.
265  views::ImageView* arrow_view_;
266
267  gfx::SlideAnimation slide_animation_;
268
269  // The 'more details' link shown under the heading (changes to 'hide details'
270  // when the details section is expanded).
271  views::Link* more_details_;
272
273  // The up/down arrow next to the 'more detail' link (points up/down depending
274  // on whether the details section is expanded).
275  views::ImageButton* arrow_toggle_;
276
277  // Whether the details section is expanded.
278  bool expanded_;
279
280  DISALLOW_COPY_AND_ASSIGN(ExpandableContainerView);
281};
282
283void ShowExtensionInstallDialogImpl(
284    const ExtensionInstallPrompt::ShowParams& show_params,
285    ExtensionInstallPrompt::Delegate* delegate,
286    const ExtensionInstallPrompt::Prompt& prompt) {
287  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
288  CreateBrowserModalDialogViews(
289      new ExtensionInstallDialogView(show_params.navigator, delegate, prompt),
290      show_params.parent_window)->Show();
291}
292
293}  // namespace
294
295CustomScrollableView::CustomScrollableView() {}
296CustomScrollableView::~CustomScrollableView() {}
297
298void CustomScrollableView::Layout() {
299  SetBounds(x(), y(), width(), GetHeightForWidth(width()));
300  views::View::Layout();
301}
302
303ExtensionInstallDialogView::ExtensionInstallDialogView(
304    content::PageNavigator* navigator,
305    ExtensionInstallPrompt::Delegate* delegate,
306    const ExtensionInstallPrompt::Prompt& prompt)
307    : navigator_(navigator),
308      delegate_(delegate),
309      prompt_(prompt) {
310  // Possible grid layouts:
311  // Inline install
312  //      w/ permissions                 no permissions
313  // +--------------------+------+  +--------------+------+
314  // | heading            | icon |  | heading      | icon |
315  // +--------------------|      |  +--------------|      |
316  // | rating             |      |  | rating       |      |
317  // +--------------------|      |  +--------------+      |
318  // | user_count         |      |  | user_count   |      |
319  // +--------------------|      |  +--------------|      |
320  // | store_link         |      |  | store_link   |      |
321  // +--------------------+------+  +--------------+------+
322  // |      separator            |
323  // +--------------------+------+
324  // | permissions_header |      |
325  // +--------------------+------+
326  // | permission1        |      |
327  // +--------------------+------+
328  // | permission2        |      |
329  // +--------------------+------+
330  //
331  // Regular install
332  // w/ permissions XOR oauth issues    no permissions
333  // +--------------------+------+  +--------------+------+
334  // | heading            | icon |  | heading      | icon |
335  // +--------------------|      |  +--------------+------+
336  // | permissions_header |      |
337  // +--------------------|      |
338  // | permission1        |      |
339  // +--------------------|      |
340  // | permission2        |      |
341  // +--------------------+------+
342  //
343  // w/ permissions AND oauth issues
344  // +--------------------+------+
345  // | heading            | icon |
346  // +--------------------|      |
347  // | permissions_header |      |
348  // +--------------------|      |
349  // | permission1        |      |
350  // +--------------------|      |
351  // | permission2        |      |
352  // +--------------------+------+
353  // | oauth header              |
354  // +---------------------------+
355  // | oauth issue 1             |
356  // +---------------------------+
357  // | oauth issue 2             |
358  // +---------------------------+
359
360  scroll_view_ = new views::ScrollView();
361  scroll_view_->set_hide_horizontal_scrollbar(true);
362  AddChildView(scroll_view_);
363  scrollable_ = new CustomScrollableView();
364  scroll_view_->SetContents(scrollable_);
365
366  views::GridLayout* layout = views::GridLayout::CreatePanel(scrollable_);
367  scrollable_->SetLayoutManager(layout);
368
369  int column_set_id = 0;
370  views::ColumnSet* column_set = layout->AddColumnSet(column_set_id);
371  int left_column_width =
372      (prompt.ShouldShowPermissions() + prompt.GetOAuthIssueCount() +
373       prompt.GetRetainedFileCount()) > 0 ?
374          kPermissionsLeftColumnWidth : kNoPermissionsLeftColumnWidth;
375  if (is_bundle_install())
376    left_column_width = kBundleLeftColumnWidth;
377  if (is_external_install())
378    left_column_width = kExternalInstallLeftColumnWidth;
379
380  column_set->AddColumn(views::GridLayout::LEADING,
381                        views::GridLayout::FILL,
382                        0,  // no resizing
383                        views::GridLayout::USE_PREF,
384                        0,  // no fixed width
385                        left_column_width);
386  if (!is_bundle_install()) {
387    column_set->AddPaddingColumn(0, views::kPanelHorizMargin);
388    column_set->AddColumn(views::GridLayout::LEADING,
389                          views::GridLayout::LEADING,
390                          0,  // no resizing
391                          views::GridLayout::USE_PREF,
392                          0,  // no fixed width
393                          kIconSize);
394  }
395
396  layout->StartRow(0, column_set_id);
397
398  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
399
400  views::Label* heading = new views::Label(prompt.GetHeading());
401  heading->SetFont(rb.GetFont(ui::ResourceBundle::MediumFont));
402  heading->SetMultiLine(true);
403  heading->SetHorizontalAlignment(gfx::ALIGN_LEFT);
404  heading->SizeToFit(left_column_width);
405  layout->AddView(heading);
406
407  if (!is_bundle_install()) {
408    // Scale down to icon size, but allow smaller icons (don't scale up).
409    const gfx::ImageSkia* image = prompt.icon().ToImageSkia();
410    gfx::Size size(image->width(), image->height());
411    if (size.width() > kIconSize || size.height() > kIconSize)
412      size = gfx::Size(kIconSize, kIconSize);
413    views::ImageView* icon = new views::ImageView();
414    icon->SetImageSize(size);
415    icon->SetImage(*image);
416    icon->SetHorizontalAlignment(views::ImageView::CENTER);
417    icon->SetVerticalAlignment(views::ImageView::CENTER);
418    int icon_row_span = 1;
419    if (is_inline_install()) {
420      // Also span the rating, user_count and store_link rows.
421      icon_row_span = 4;
422    } else if (prompt.ShouldShowPermissions()) {
423      size_t permission_count = prompt.GetPermissionCount();
424      if (permission_count > 0) {
425        // Also span the permission header and each of the permission rows (all
426        // have a padding row above it).
427        icon_row_span = 3 + permission_count * 2;
428      } else {
429        // This is the 'no special permissions' case, so span the line we add
430        // (without a header) saying the extension has no special privileges.
431        icon_row_span = 4;
432      }
433    } else if (prompt.GetOAuthIssueCount()) {
434      // Also span the permission header and each of the permission rows (all
435      // have a padding row above it).
436      icon_row_span = 3 + prompt.GetOAuthIssueCount() * 2;
437    } else if (prompt.GetRetainedFileCount()) {
438      // Also span the permission header and the retained files container.
439      icon_row_span = 4;
440    }
441    layout->AddView(icon, 1, icon_row_span);
442  }
443
444  if (is_inline_install()) {
445    layout->StartRow(0, column_set_id);
446    views::View* rating = new views::View();
447    rating->SetLayoutManager(new views::BoxLayout(
448        views::BoxLayout::kHorizontal, 0, 0, 0));
449    layout->AddView(rating);
450    prompt.AppendRatingStars(AddResourceIcon, rating);
451
452    views::Label* rating_count = new views::Label(prompt.GetRatingCount());
453    rating_count->SetFont(rb.GetFont(ui::ResourceBundle::SmallFont));
454    // Add some space between the stars and the rating count.
455    rating_count->set_border(views::Border::CreateEmptyBorder(0, 2, 0, 0));
456    rating->AddChildView(rating_count);
457
458    layout->StartRow(0, column_set_id);
459    views::Label* user_count = new views::Label(prompt.GetUserCount());
460    user_count->SetAutoColorReadabilityEnabled(false);
461    user_count->SetEnabledColor(SK_ColorGRAY);
462    user_count->SetFont(rb.GetFont(ui::ResourceBundle::SmallFont));
463    layout->AddView(user_count);
464
465    layout->StartRow(0, column_set_id);
466    views::Link* store_link = new views::Link(
467        l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_STORE_LINK));
468    store_link->SetFont(rb.GetFont(ui::ResourceBundle::SmallFont));
469    store_link->set_listener(this);
470    layout->AddView(store_link);
471  }
472
473  if (is_bundle_install()) {
474    BundleInstaller::ItemList items = prompt.bundle()->GetItemsWithState(
475        BundleInstaller::Item::STATE_PENDING);
476    for (size_t i = 0; i < items.size(); ++i) {
477      string16 extension_name = UTF8ToUTF16(items[i].localized_name);
478      base::i18n::AdjustStringForLocaleDirection(&extension_name);
479      layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
480      layout->StartRow(0, column_set_id);
481      views::Label* extension_label = new views::Label(
482          PrepareForDisplay(extension_name, true));
483      extension_label->SetMultiLine(true);
484      extension_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
485      extension_label->SizeToFit(left_column_width);
486      layout->AddView(extension_label);
487    }
488  }
489
490  if (prompt.ShouldShowPermissions()) {
491    layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
492
493    if (prompt.GetPermissionCount() > 0) {
494      if (is_inline_install()) {
495        layout->StartRow(0, column_set_id);
496        layout->AddView(new views::Separator(views::Separator::HORIZONTAL),
497                        3, 1, views::GridLayout::FILL, views::GridLayout::FILL);
498        layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
499      }
500
501      layout->StartRow(0, column_set_id);
502      views::Label* permissions_header = NULL;
503      if (is_bundle_install()) {
504        // We need to pass the Font in the constructor, rather than calling
505        // SetFont later, because otherwise SizeToFit mis-judges the width
506        // of the line.
507        permissions_header = new views::Label(
508            prompt.GetPermissionsHeading(),
509            rb.GetFont(ui::ResourceBundle::MediumFont));
510      } else {
511        permissions_header = new views::Label(prompt.GetPermissionsHeading());
512      }
513      permissions_header->SetMultiLine(true);
514      permissions_header->SetHorizontalAlignment(gfx::ALIGN_LEFT);
515      permissions_header->SizeToFit(left_column_width);
516      layout->AddView(permissions_header);
517
518      for (size_t i = 0; i < prompt.GetPermissionCount(); ++i) {
519        layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
520        layout->StartRow(0, column_set_id);
521        views::Label* permission_label =
522            new views::Label(prompt.GetPermission(i));
523        permission_label->SetMultiLine(true);
524        permission_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
525        permission_label->SizeToFit(left_column_width);
526        layout->AddView(new BulletedView(permission_label));
527
528        // If we have more details to provide, show them in collapsed form.
529        if (!prompt.GetPermissionsDetails(i).empty()) {
530          layout->StartRow(0, column_set_id);
531          PermissionDetails details;
532          details.push_back(
533              PrepareForDisplay(prompt.GetPermissionsDetails(i), false));
534          ExpandableContainerView* details_container =
535              new ExpandableContainerView(
536                  this, string16(), details, left_column_width, true);
537          layout->AddView(details_container);
538        }
539      }
540    } else {
541      layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
542      layout->StartRow(0, column_set_id);
543      views::Label* permission_label = new views::Label(
544          l10n_util::GetStringUTF16(IDS_EXTENSION_NO_SPECIAL_PERMISSIONS));
545      permission_label->SetMultiLine(true);
546      permission_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
547      permission_label->SizeToFit(left_column_width);
548      layout->AddView(permission_label);
549    }
550  }
551
552  if (prompt.GetOAuthIssueCount()) {
553    // Slide in under the permissions, if there are any. If there are
554    // permissions, the OAuth prompt stretches all the way to the right of the
555    // dialog. If there are no permissions, the OAuth prompt just takes up the
556    // left column.
557    int space_for_oauth = left_column_width;
558    if (prompt.GetPermissionCount()) {
559      space_for_oauth += kIconSize;
560      column_set = layout->AddColumnSet(++column_set_id);
561      column_set->AddColumn(views::GridLayout::FILL,
562                            views::GridLayout::FILL,
563                            1,
564                            views::GridLayout::USE_PREF,
565                            0,  // no fixed width
566                            space_for_oauth);
567    }
568
569    layout->StartRowWithPadding(0, column_set_id,
570                                0, views::kRelatedControlVerticalSpacing);
571    views::Label* oauth_header = new views::Label(prompt.GetOAuthHeading());
572    oauth_header->SetMultiLine(true);
573    oauth_header->SetHorizontalAlignment(gfx::ALIGN_LEFT);
574    oauth_header->SizeToFit(left_column_width);
575    layout->AddView(oauth_header);
576
577    for (size_t i = 0; i < prompt.GetOAuthIssueCount(); ++i) {
578      layout->StartRowWithPadding(
579          0, column_set_id,
580          0, views::kRelatedControlVerticalSpacing);
581
582      PermissionDetails details;
583      const IssueAdviceInfoEntry& entry = prompt.GetOAuthIssue(i);
584      for (size_t x = 0; x < entry.details.size(); ++x)
585        details.push_back(entry.details[x]);
586      ExpandableContainerView* issue_advice_view =
587          new ExpandableContainerView(
588              this, entry.description, details, space_for_oauth, true);
589      layout->AddView(issue_advice_view);
590    }
591  }
592  if (prompt.GetRetainedFileCount()) {
593    // Slide in under the permissions or OAuth, if there are any. If there are
594    // either, the retained files prompt stretches all the way to the right of
595    // the dialog. If there are no permissions or OAuth, the retained files
596    // prompt just takes up the left column.
597    int space_for_files = left_column_width;
598    if (prompt.GetPermissionCount() || prompt.GetOAuthIssueCount()) {
599      space_for_files += kIconSize;
600      column_set = layout->AddColumnSet(++column_set_id);
601      column_set->AddColumn(views::GridLayout::FILL,
602                            views::GridLayout::FILL,
603                            1,
604                            views::GridLayout::USE_PREF,
605                            0,  // no fixed width
606                            space_for_files);
607    }
608
609    layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
610
611    layout->StartRow(0, column_set_id);
612    views::Label* retained_files_header = NULL;
613    retained_files_header =
614        new views::Label(prompt.GetRetainedFilesHeading());
615    retained_files_header->SetMultiLine(true);
616    retained_files_header->SetHorizontalAlignment(gfx::ALIGN_LEFT);
617    retained_files_header->SizeToFit(space_for_files);
618    layout->AddView(retained_files_header);
619
620    layout->StartRow(0, column_set_id);
621    PermissionDetails details;
622    for (size_t i = 0; i < prompt.GetRetainedFileCount(); ++i)
623      details.push_back(prompt.GetRetainedFile(i));
624    ExpandableContainerView* issue_advice_view =
625        new ExpandableContainerView(
626            this, string16(), details, space_for_files, false);
627    layout->AddView(issue_advice_view);
628  }
629
630  gfx::Size scrollable_size = scrollable_->GetPreferredSize();
631  scrollable_->SetBoundsRect(gfx::Rect(scrollable_size));
632  dialog_size_ = gfx::Size(
633      kDialogWidth,
634      std::min(scrollable_size.height(), kDialogMaxHeight));
635}
636
637ExtensionInstallDialogView::~ExtensionInstallDialogView() {}
638
639void ExtensionInstallDialogView::ContentsChanged() {
640  Layout();
641}
642
643int ExtensionInstallDialogView::GetDialogButtons() const {
644  int buttons = prompt_.GetDialogButtons();
645  // Simply having just an OK button is *not* supported. See comment on function
646  // GetDialogButtons in dialog_delegate.h for reasons.
647  DCHECK_GT(buttons & ui::DIALOG_BUTTON_CANCEL, 0);
648  return buttons;
649}
650
651string16 ExtensionInstallDialogView::GetDialogButtonLabel(
652    ui::DialogButton button) const {
653  switch (button) {
654    case ui::DIALOG_BUTTON_OK:
655      return prompt_.GetAcceptButtonLabel();
656    case ui::DIALOG_BUTTON_CANCEL:
657      return prompt_.HasAbortButtonLabel() ?
658          prompt_.GetAbortButtonLabel() :
659          l10n_util::GetStringUTF16(IDS_CANCEL);
660    default:
661      NOTREACHED();
662      return string16();
663  }
664}
665
666int ExtensionInstallDialogView::GetDefaultDialogButton() const {
667  if (is_first_run())
668    return ui::DIALOG_BUTTON_OK;
669  else
670    return ui::DIALOG_BUTTON_CANCEL;
671}
672
673bool ExtensionInstallDialogView::Cancel() {
674  delegate_->InstallUIAbort(true);
675  return true;
676}
677
678bool ExtensionInstallDialogView::Accept() {
679  delegate_->InstallUIProceed();
680  return true;
681}
682
683ui::ModalType ExtensionInstallDialogView::GetModalType() const {
684  return ui::MODAL_TYPE_WINDOW;
685}
686
687string16 ExtensionInstallDialogView::GetWindowTitle() const {
688  return prompt_.GetDialogTitle();
689}
690
691void ExtensionInstallDialogView::LinkClicked(views::Link* source,
692                                             int event_flags) {
693  GURL store_url(extension_urls::GetWebstoreItemDetailURLPrefix() +
694                 prompt_.extension()->id());
695  OpenURLParams params(
696      store_url, Referrer(), NEW_FOREGROUND_TAB, content::PAGE_TRANSITION_LINK,
697      false);
698  navigator_->OpenURL(params);
699  GetWidget()->Close();
700}
701
702void ExtensionInstallDialogView::Layout() {
703  scroll_view_->SetBounds(0, 0, width(), height());
704  DialogDelegateView::Layout();
705}
706
707gfx::Size ExtensionInstallDialogView::GetPreferredSize() {
708  return dialog_size_;
709}
710
711bool ExtensionInstallDialogView::ShouldShowWindowTitle() const {
712  return false;
713}
714
715bool ExtensionInstallDialogView::ShouldShowCloseButton() const {
716  return false;
717}
718
719// static
720ExtensionInstallPrompt::ShowDialogCallback
721ExtensionInstallPrompt::GetDefaultShowDialogCallback() {
722  return base::Bind(&ShowExtensionInstallDialogImpl);
723}
724
725// ExpandableContainerView::DetailsView ----------------------------------------
726
727ExpandableContainerView::DetailsView::DetailsView(int horizontal_space,
728                                                  bool parent_bulleted)
729    : layout_(new views::GridLayout(this)),
730      state_(0),
731      parent_bulleted_(parent_bulleted) {
732  SetLayoutManager(layout_);
733  views::ColumnSet* column_set = layout_->AddColumnSet(0);
734  // If the parent is using bullets for its items, then a padding of one unit
735  // will make the child item (which has no bullet) look like a sibling of its
736  // parent. Therefore increase the indentation by one more unit to show that it
737  // is in fact a child item (with no missing bullet) and not a sibling.
738  int padding =
739      views::kRelatedControlHorizontalSpacing * (parent_bulleted ? 2 : 1);
740  column_set->AddPaddingColumn(0, padding);
741  column_set->AddColumn(views::GridLayout::LEADING,
742                        views::GridLayout::LEADING,
743                        0,
744                        views::GridLayout::FIXED,
745                        horizontal_space - padding,
746                        0);
747}
748
749void ExpandableContainerView::DetailsView::AddDetail(const string16& detail) {
750  layout_->StartRowWithPadding(0, 0,
751                               0, views::kRelatedControlSmallVerticalSpacing);
752  views::Label* detail_label =
753      new views::Label(PrepareForDisplay(detail, false));
754  detail_label->SetMultiLine(true);
755  detail_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
756  layout_->AddView(detail_label);
757}
758
759gfx::Size ExpandableContainerView::DetailsView::GetPreferredSize() {
760  gfx::Size size = views::View::GetPreferredSize();
761  return gfx::Size(size.width(), size.height() * state_);
762}
763
764void ExpandableContainerView::DetailsView::AnimateToState(double state) {
765  state_ = state;
766  PreferredSizeChanged();
767  SchedulePaint();
768}
769
770// ExpandableContainerView -----------------------------------------------------
771
772ExpandableContainerView::ExpandableContainerView(
773    ExtensionInstallDialogView* owner,
774    const string16& description,
775    const PermissionDetails& details,
776    int horizontal_space,
777    bool parent_bulleted)
778    : owner_(owner),
779      details_view_(NULL),
780      arrow_view_(NULL),
781      slide_animation_(this),
782      expanded_(false) {
783  views::GridLayout* layout = new views::GridLayout(this);
784  SetLayoutManager(layout);
785  int column_set_id = 0;
786  views::ColumnSet* column_set = layout->AddColumnSet(column_set_id);
787  column_set->AddColumn(views::GridLayout::LEADING,
788                        views::GridLayout::LEADING,
789                        0,
790                        views::GridLayout::USE_PREF,
791                        0,
792                        0);
793  if (!description.empty()) {
794    layout->StartRow(0, column_set_id);
795
796    views::Label* description_label = new views::Label(description);
797    description_label->SetMultiLine(true);
798    description_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
799    description_label->SizeToFit(horizontal_space);
800    layout->AddView(new BulletedView(description_label));
801  }
802
803  if (details.empty())
804    return;
805
806  details_view_ = new DetailsView(horizontal_space, parent_bulleted);
807
808  layout->StartRow(0, column_set_id);
809  layout->AddView(details_view_);
810
811  for (size_t i = 0; i < details.size(); ++i)
812    details_view_->AddDetail(details[i]);
813
814  views::Link* link = new views::Link(
815      l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS));
816
817  // Make sure the link width column is as wide as needed for both Show and
818  // Hide details, so that the arrow doesn't shift horizontally when we toggle.
819  int link_col_width =
820      views::kRelatedControlHorizontalSpacing +
821      std::max(link->font_list().GetStringWidth(
822                   l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS)),
823               link->font_list().GetStringWidth(
824                   l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS)));
825
826  column_set = layout->AddColumnSet(++column_set_id);
827  // Padding to the left of the More Details column. If the parent is using
828  // bullets for its items, then a padding of one unit will make the child item
829  // (which has no bullet) look like a sibling of its parent. Therefore increase
830  // the indentation by one more unit to show that it is in fact a child item
831  // (with no missing bullet) and not a sibling.
832  column_set->AddPaddingColumn(
833      0, views::kRelatedControlHorizontalSpacing * (parent_bulleted ? 2 : 1));
834  // The More Details column.
835  column_set->AddColumn(views::GridLayout::LEADING,
836                        views::GridLayout::LEADING,
837                        0,
838                        views::GridLayout::FIXED,
839                        link_col_width,
840                        link_col_width);
841  // The Up/Down arrow column.
842  column_set->AddColumn(views::GridLayout::LEADING,
843                       views::GridLayout::LEADING,
844                       0,
845                       views::GridLayout::USE_PREF,
846                       0,
847                       0);
848
849  // Add the More Details link.
850  layout->StartRow(0, column_set_id);
851  more_details_ = link;
852  more_details_->set_listener(this);
853  more_details_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
854  layout->AddView(more_details_);
855
856  // Add the arrow after the More Details link.
857  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
858  arrow_toggle_ = new views::ImageButton(this);
859  arrow_toggle_->SetImage(views::Button::STATE_NORMAL,
860                          rb.GetImageSkiaNamed(IDR_DOWN_ARROW));
861  layout->AddView(arrow_toggle_);
862}
863
864ExpandableContainerView::~ExpandableContainerView() {
865}
866
867void ExpandableContainerView::ButtonPressed(
868    views::Button* sender, const ui::Event& event) {
869  ToggleDetailLevel();
870}
871
872void ExpandableContainerView::LinkClicked(
873    views::Link* source, int event_flags) {
874  ToggleDetailLevel();
875}
876
877void ExpandableContainerView::AnimationProgressed(
878    const gfx::Animation* animation) {
879  DCHECK_EQ(&slide_animation_, animation);
880  if (details_view_)
881    details_view_->AnimateToState(animation->GetCurrentValue());
882}
883
884void ExpandableContainerView::AnimationEnded(const gfx::Animation* animation) {
885  if (animation->GetCurrentValue() != 0.0) {
886    arrow_toggle_->SetImage(
887        views::Button::STATE_NORMAL,
888        ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
889            IDR_UP_ARROW));
890  } else {
891    arrow_toggle_->SetImage(
892        views::Button::STATE_NORMAL,
893        ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
894            IDR_DOWN_ARROW));
895  }
896
897  more_details_->SetText(expanded_ ?
898      l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS) :
899      l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS));
900}
901
902void ExpandableContainerView::ChildPreferredSizeChanged(views::View* child) {
903  owner_->ContentsChanged();
904}
905
906void ExpandableContainerView::ToggleDetailLevel() {
907  expanded_ = !expanded_;
908
909  if (slide_animation_.IsShowing())
910    slide_animation_.Hide();
911  else
912    slide_animation_.Show();
913}
914