| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <vector> |
| |
| #include "base/basictypes.h" |
| #include "base/command_line.h" |
| #include "base/compiler_specific.h" |
| #include "base/i18n/rtl.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/extensions/bundle_installer.h" |
| #include "chrome/browser/extensions/extension_install_prompt.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/views/constrained_window_views.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/extensions/extension.h" |
| #include "chrome/common/extensions/extension_constants.h" |
| #include "chrome/installer/util/browser_distribution.h" |
| #include "content/public/browser/page_navigator.h" |
| #include "content/public/browser/web_contents.h" |
| #include "grit/chromium_strings.h" |
| #include "grit/generated_resources.h" |
| #include "grit/google_chrome_strings.h" |
| #include "grit/theme_resources.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/gfx/animation/animation_delegate.h" |
| #include "ui/gfx/animation/slide_animation.h" |
| #include "ui/gfx/transform.h" |
| #include "ui/views/border.h" |
| #include "ui/views/controls/button/image_button.h" |
| #include "ui/views/controls/image_view.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/controls/link.h" |
| #include "ui/views/controls/link_listener.h" |
| #include "ui/views/controls/scroll_view.h" |
| #include "ui/views/controls/separator.h" |
| #include "ui/views/layout/box_layout.h" |
| #include "ui/views/layout/grid_layout.h" |
| #include "ui/views/layout/layout_constants.h" |
| #include "ui/views/view.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/views/window/dialog_delegate.h" |
| |
| using content::OpenURLParams; |
| using content::Referrer; |
| using extensions::BundleInstaller; |
| |
| namespace { |
| |
| // Size of extension icon in top left of dialog. |
| const int kIconSize = 69; |
| |
| // We offset the icon a little bit from the right edge of the dialog, to make it |
| // align with the button below it. |
| const int kIconOffset = 16; |
| |
| // The dialog will resize based on its content, but this sets a maximum height |
| // before overflowing a scrollbar. |
| const int kDialogMaxHeight = 300; |
| |
| // Width of the left column of the dialog when the extension requests |
| // permissions. |
| const int kPermissionsLeftColumnWidth = 250; |
| |
| // Width of the left column of the dialog when the extension requests no |
| // permissions. |
| const int kNoPermissionsLeftColumnWidth = 200; |
| |
| // Width of the left column for bundle install prompts. There's only one column |
| // in this case, so make it wider than normal. |
| const int kBundleLeftColumnWidth = 300; |
| |
| // Width of the left column for external install prompts. The text is long in |
| // this case, so make it wider than normal. |
| const int kExternalInstallLeftColumnWidth = 350; |
| |
| typedef std::vector<string16> PermissionDetails; |
| |
| void AddResourceIcon(const gfx::ImageSkia* skia_image, void* data) { |
| views::View* parent = static_cast<views::View*>(data); |
| views::ImageView* image_view = new views::ImageView(); |
| image_view->SetImage(*skia_image); |
| parent->AddChildView(image_view); |
| } |
| |
| // Creates a string for displaying |message| to the user. If it has to look |
| // like a entry in a bullet point list, one is added. |
| string16 PrepareForDisplay(const string16& message, bool bullet_point) { |
| return bullet_point ? l10n_util::GetStringFUTF16( |
| IDS_EXTENSION_PERMISSION_LINE, |
| message) : message; |
| } |
| |
| // A custom scrollable view implementation for the dialog. |
| class CustomScrollableView : public views::View { |
| public: |
| CustomScrollableView(); |
| virtual ~CustomScrollableView(); |
| |
| private: |
| virtual void Layout() OVERRIDE; |
| |
| DISALLOW_COPY_AND_ASSIGN(CustomScrollableView); |
| }; |
| |
| // Implements the extension installation dialog for TOOLKIT_VIEWS. |
| class ExtensionInstallDialogView : public views::DialogDelegateView, |
| public views::LinkListener { |
| public: |
| ExtensionInstallDialogView(content::PageNavigator* navigator, |
| ExtensionInstallPrompt::Delegate* delegate, |
| const ExtensionInstallPrompt::Prompt& prompt); |
| virtual ~ExtensionInstallDialogView(); |
| |
| // Called when one of the child elements has expanded/collapsed. |
| void ContentsChanged(); |
| |
| private: |
| // views::DialogDelegateView: |
| virtual int GetDialogButtons() const OVERRIDE; |
| virtual string16 GetDialogButtonLabel(ui::DialogButton button) const OVERRIDE; |
| virtual int GetDefaultDialogButton() const OVERRIDE; |
| virtual bool Cancel() OVERRIDE; |
| virtual bool Accept() OVERRIDE; |
| virtual ui::ModalType GetModalType() const OVERRIDE; |
| virtual string16 GetWindowTitle() const OVERRIDE; |
| virtual void Layout() OVERRIDE; |
| virtual gfx::Size GetPreferredSize() OVERRIDE; |
| |
| // views::LinkListener: |
| virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE; |
| |
| bool is_inline_install() const { |
| return prompt_.type() == ExtensionInstallPrompt::INLINE_INSTALL_PROMPT; |
| } |
| |
| bool is_bundle_install() const { |
| return prompt_.type() == ExtensionInstallPrompt::BUNDLE_INSTALL_PROMPT; |
| } |
| |
| bool is_external_install() const { |
| return prompt_.type() == ExtensionInstallPrompt::EXTERNAL_INSTALL_PROMPT; |
| } |
| |
| content::PageNavigator* navigator_; |
| ExtensionInstallPrompt::Delegate* delegate_; |
| ExtensionInstallPrompt::Prompt prompt_; |
| |
| // The scroll view containing all the details for the dialog (including all |
| // collapsible/expandable sections). |
| views::ScrollView* scroll_view_; |
| |
| // The container view for the scroll view. |
| CustomScrollableView* scrollable_; |
| |
| // The preferred size of the dialog. |
| gfx::Size dialog_size_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ExtensionInstallDialogView); |
| }; |
| |
| // A simple view that prepends a view with a bullet with the help of a grid |
| // layout. |
| class BulletedView : public views::View { |
| public: |
| explicit BulletedView(views::View* view); |
| private: |
| DISALLOW_COPY_AND_ASSIGN(BulletedView); |
| }; |
| |
| BulletedView::BulletedView(views::View* view) { |
| views::GridLayout* layout = new views::GridLayout(this); |
| SetLayoutManager(layout); |
| views::ColumnSet* column_set = layout->AddColumnSet(0); |
| column_set->AddColumn(views::GridLayout::LEADING, |
| views::GridLayout::LEADING, |
| 0, |
| views::GridLayout::USE_PREF, |
| 0, // no fixed width |
| 0); |
| column_set->AddColumn(views::GridLayout::LEADING, |
| views::GridLayout::LEADING, |
| 0, |
| views::GridLayout::USE_PREF, |
| 0, // no fixed width |
| 0); |
| layout->StartRow(0, 0); |
| layout->AddView(new views::Label(PrepareForDisplay(string16(), true))); |
| layout->AddView(view); |
| } |
| |
| // A view to display text with an expandable details section. |
| class ExpandableContainerView : public views::View, |
| public views::ButtonListener, |
| public views::LinkListener, |
| public gfx::AnimationDelegate { |
| public: |
| ExpandableContainerView(ExtensionInstallDialogView* owner, |
| const string16& description, |
| const PermissionDetails& details, |
| int horizontal_space, |
| bool parent_bulleted); |
| virtual ~ExpandableContainerView(); |
| |
| // views::View: |
| virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE; |
| |
| // views::ButtonListener: |
| virtual void ButtonPressed(views::Button* sender, |
| const ui::Event& event) OVERRIDE; |
| |
| // views::LinkListener: |
| virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE; |
| |
| // gfx::AnimationDelegate: |
| virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE; |
| virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE; |
| |
| private: |
| // A view which displays all the details of an IssueAdviceInfoEntry. |
| class DetailsView : public views::View { |
| public: |
| explicit DetailsView(int horizontal_space, bool parent_bulleted); |
| virtual ~DetailsView() {} |
| |
| // views::View: |
| virtual gfx::Size GetPreferredSize() OVERRIDE; |
| |
| void AddDetail(const string16& detail); |
| |
| // Animates this to be a height proportional to |state|. |
| void AnimateToState(double state); |
| |
| private: |
| views::GridLayout* layout_; |
| double state_; |
| |
| // Whether the parent item is showing bullets. This will determine how much |
| // extra indentation is needed. |
| bool parent_bulleted_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DetailsView); |
| }; |
| |
| // Expand/Collapse the detail section for this ExpandableContainerView. |
| void ToggleDetailLevel(); |
| |
| // The dialog that owns |this|. It's also an ancestor in the View hierarchy. |
| ExtensionInstallDialogView* owner_; |
| |
| // A view for showing |issue_advice.details|. |
| DetailsView* details_view_; |
| |
| // The '>' zippy control. |
| views::ImageView* arrow_view_; |
| |
| gfx::SlideAnimation slide_animation_; |
| |
| // The 'more details' link shown under the heading (changes to 'hide details' |
| // when the details section is expanded). |
| views::Link* more_details_; |
| |
| // The up/down arrow next to the 'more detail' link (points up/down depending |
| // on whether the details section is expanded). |
| views::ImageButton* arrow_toggle_; |
| |
| // Whether the details section is expanded. |
| bool expanded_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ExpandableContainerView); |
| }; |
| |
| void ShowExtensionInstallDialogImpl( |
| const ExtensionInstallPrompt::ShowParams& show_params, |
| ExtensionInstallPrompt::Delegate* delegate, |
| const ExtensionInstallPrompt::Prompt& prompt) { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| CreateBrowserModalDialogViews( |
| new ExtensionInstallDialogView(show_params.navigator, delegate, prompt), |
| show_params.parent_window)->Show(); |
| } |
| |
| } // namespace |
| |
| CustomScrollableView::CustomScrollableView() {} |
| CustomScrollableView::~CustomScrollableView() {} |
| |
| void CustomScrollableView::Layout() { |
| SetBounds(x(), y(), width(), GetHeightForWidth(width())); |
| views::View::Layout(); |
| } |
| |
| ExtensionInstallDialogView::ExtensionInstallDialogView( |
| content::PageNavigator* navigator, |
| ExtensionInstallPrompt::Delegate* delegate, |
| const ExtensionInstallPrompt::Prompt& prompt) |
| : navigator_(navigator), |
| delegate_(delegate), |
| prompt_(prompt) { |
| // Possible grid layouts: |
| // Inline install |
| // w/ permissions no permissions |
| // +--------------------+------+ +--------------+------+ |
| // | heading | icon | | heading | icon | |
| // +--------------------| | +--------------| | |
| // | rating | | | rating | | |
| // +--------------------| | +--------------+ | |
| // | user_count | | | user_count | | |
| // +--------------------| | +--------------| | |
| // | store_link | | | store_link | | |
| // +--------------------+------+ +--------------+------+ |
| // | separator | |
| // +--------------------+------+ |
| // | permissions_header | | |
| // +--------------------+------+ |
| // | permission1 | | |
| // +--------------------+------+ |
| // | permission2 | | |
| // +--------------------+------+ |
| // |
| // Regular install |
| // w/ permissions XOR oauth issues no permissions |
| // +--------------------+------+ +--------------+------+ |
| // | heading | icon | | heading | icon | |
| // +--------------------| | +--------------+------+ |
| // | permissions_header | | |
| // +--------------------| | |
| // | permission1 | | |
| // +--------------------| | |
| // | permission2 | | |
| // +--------------------+------+ |
| // |
| // w/ permissions AND oauth issues |
| // +--------------------+------+ |
| // | heading | icon | |
| // +--------------------| | |
| // | permissions_header | | |
| // +--------------------| | |
| // | permission1 | | |
| // +--------------------| | |
| // | permission2 | | |
| // +--------------------+------+ |
| // | oauth header | |
| // +---------------------------+ |
| // | oauth issue 1 | |
| // +---------------------------+ |
| // | oauth issue 2 | |
| // +---------------------------+ |
| |
| scroll_view_ = new views::ScrollView(); |
| scroll_view_->set_hide_horizontal_scrollbar(true); |
| AddChildView(scroll_view_); |
| scrollable_ = new CustomScrollableView(); |
| scroll_view_->SetContents(scrollable_); |
| |
| views::GridLayout* layout = views::GridLayout::CreatePanel(scrollable_); |
| scrollable_->SetLayoutManager(layout); |
| |
| int column_set_id = 0; |
| views::ColumnSet* column_set = layout->AddColumnSet(column_set_id); |
| int left_column_width = |
| (prompt.ShouldShowPermissions() + prompt.GetOAuthIssueCount() + |
| prompt.GetRetainedFileCount()) > 0 ? |
| kPermissionsLeftColumnWidth : kNoPermissionsLeftColumnWidth; |
| if (is_bundle_install()) |
| left_column_width = kBundleLeftColumnWidth; |
| if (is_external_install()) |
| left_column_width = kExternalInstallLeftColumnWidth; |
| |
| int dialog_width = left_column_width + 2 * views::kPanelHorizMargin; |
| |
| column_set->AddColumn(views::GridLayout::LEADING, |
| views::GridLayout::FILL, |
| 0, // no resizing |
| views::GridLayout::USE_PREF, |
| 0, // no fixed width |
| left_column_width); |
| if (!is_bundle_install()) { |
| column_set->AddPaddingColumn(0, views::kPanelHorizMargin); |
| column_set->AddColumn(views::GridLayout::TRAILING, |
| views::GridLayout::LEADING, |
| 0, // no resizing |
| views::GridLayout::USE_PREF, |
| 0, // no fixed width |
| kIconSize); |
| |
| dialog_width += views::kPanelHorizMargin + kIconSize + kIconOffset; |
| } |
| |
| layout->StartRow(0, column_set_id); |
| |
| ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| |
| views::Label* heading = new views::Label(prompt.GetHeading()); |
| heading->SetFont(rb.GetFont(ui::ResourceBundle::MediumFont)); |
| heading->SetMultiLine(true); |
| heading->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| heading->SizeToFit(left_column_width); |
| layout->AddView(heading); |
| |
| if (!is_bundle_install()) { |
| // Scale down to icon size, but allow smaller icons (don't scale up). |
| const gfx::ImageSkia* image = prompt.icon().ToImageSkia(); |
| gfx::Size size(image->width(), image->height()); |
| if (size.width() > kIconSize || size.height() > kIconSize) |
| size = gfx::Size(kIconSize, kIconSize); |
| views::ImageView* icon = new views::ImageView(); |
| icon->SetImageSize(size); |
| icon->SetImage(*image); |
| icon->SetHorizontalAlignment(views::ImageView::CENTER); |
| icon->SetVerticalAlignment(views::ImageView::CENTER); |
| int icon_row_span = 1; |
| if (is_inline_install()) { |
| // Also span the rating, user_count and store_link rows. |
| icon_row_span = 4; |
| } else if (prompt.ShouldShowPermissions()) { |
| size_t permission_count = prompt.GetPermissionCount(); |
| // Also span the permission header and each of the permission rows (all |
| // have a padding row above it). This also works for the 'no special |
| // permissions' case. |
| icon_row_span = 3 + permission_count * 2; |
| } else if (prompt.GetOAuthIssueCount()) { |
| // Also span the permission header and each of the permission rows (all |
| // have a padding row above it). |
| icon_row_span = 3 + prompt.GetOAuthIssueCount() * 2; |
| } else if (prompt.GetRetainedFileCount()) { |
| // Also span the permission header and the retained files container. |
| icon_row_span = 4; |
| } |
| layout->AddView(icon, 1, icon_row_span); |
| } |
| |
| if (is_inline_install()) { |
| layout->StartRow(0, column_set_id); |
| views::View* rating = new views::View(); |
| rating->SetLayoutManager(new views::BoxLayout( |
| views::BoxLayout::kHorizontal, 0, 0, 0)); |
| layout->AddView(rating); |
| prompt.AppendRatingStars(AddResourceIcon, rating); |
| |
| views::Label* rating_count = new views::Label(prompt.GetRatingCount()); |
| rating_count->SetFont(rb.GetFont(ui::ResourceBundle::SmallFont)); |
| // Add some space between the stars and the rating count. |
| rating_count->set_border(views::Border::CreateEmptyBorder(0, 2, 0, 0)); |
| rating->AddChildView(rating_count); |
| |
| layout->StartRow(0, column_set_id); |
| views::Label* user_count = new views::Label(prompt.GetUserCount()); |
| user_count->SetAutoColorReadabilityEnabled(false); |
| user_count->SetEnabledColor(SK_ColorGRAY); |
| user_count->SetFont(rb.GetFont(ui::ResourceBundle::SmallFont)); |
| layout->AddView(user_count); |
| |
| layout->StartRow(0, column_set_id); |
| views::Link* store_link = new views::Link( |
| l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_STORE_LINK)); |
| store_link->SetFont(rb.GetFont(ui::ResourceBundle::SmallFont)); |
| store_link->set_listener(this); |
| layout->AddView(store_link); |
| } |
| |
| if (is_bundle_install()) { |
| BundleInstaller::ItemList items = prompt.bundle()->GetItemsWithState( |
| BundleInstaller::Item::STATE_PENDING); |
| for (size_t i = 0; i < items.size(); ++i) { |
| string16 extension_name = UTF8ToUTF16(items[i].localized_name); |
| base::i18n::AdjustStringForLocaleDirection(&extension_name); |
| layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); |
| layout->StartRow(0, column_set_id); |
| views::Label* extension_label = new views::Label( |
| PrepareForDisplay(extension_name, true)); |
| extension_label->SetMultiLine(true); |
| extension_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| extension_label->SizeToFit(left_column_width); |
| layout->AddView(extension_label); |
| } |
| } |
| |
| if (prompt.ShouldShowPermissions()) { |
| layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); |
| |
| if (prompt.GetPermissionCount() > 0) { |
| if (is_inline_install()) { |
| layout->StartRow(0, column_set_id); |
| layout->AddView(new views::Separator(views::Separator::HORIZONTAL), |
| 3, 1, views::GridLayout::FILL, views::GridLayout::FILL); |
| layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); |
| } |
| |
| layout->StartRow(0, column_set_id); |
| views::Label* permissions_header = NULL; |
| if (is_bundle_install()) { |
| // We need to pass the Font in the constructor, rather than calling |
| // SetFont later, because otherwise SizeToFit mis-judges the width |
| // of the line. |
| permissions_header = new views::Label( |
| prompt.GetPermissionsHeading(), |
| rb.GetFont(ui::ResourceBundle::MediumFont)); |
| } else { |
| permissions_header = new views::Label(prompt.GetPermissionsHeading()); |
| } |
| permissions_header->SetMultiLine(true); |
| permissions_header->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| permissions_header->SizeToFit(left_column_width); |
| layout->AddView(permissions_header); |
| |
| for (size_t i = 0; i < prompt.GetPermissionCount(); ++i) { |
| layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); |
| layout->StartRow(0, column_set_id); |
| views::Label* permission_label = |
| new views::Label(prompt.GetPermission(i)); |
| permission_label->SetMultiLine(true); |
| permission_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| permission_label->SizeToFit(left_column_width); |
| layout->AddView(new BulletedView(permission_label)); |
| |
| // If we have more details to provide, show them in collapsed form. |
| if (!prompt.GetPermissionsDetails(i).empty()) { |
| layout->StartRow(0, column_set_id); |
| PermissionDetails details; |
| details.push_back( |
| PrepareForDisplay(prompt.GetPermissionsDetails(i), false)); |
| ExpandableContainerView* details_container = |
| new ExpandableContainerView( |
| this, string16(), details, left_column_width, true); |
| layout->AddView(details_container); |
| } |
| } |
| } else { |
| layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); |
| layout->StartRow(0, column_set_id); |
| views::Label* permission_label = new views::Label( |
| l10n_util::GetStringUTF16(IDS_EXTENSION_NO_SPECIAL_PERMISSIONS)); |
| permission_label->SetMultiLine(true); |
| permission_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| permission_label->SizeToFit(left_column_width); |
| layout->AddView(permission_label); |
| } |
| } |
| |
| if (prompt.GetOAuthIssueCount()) { |
| // Slide in under the permissions, if there are any. If there are |
| // permissions, the OAuth prompt stretches all the way to the right of the |
| // dialog. If there are no permissions, the OAuth prompt just takes up the |
| // left column. |
| int space_for_oauth = left_column_width; |
| if (prompt.GetPermissionCount()) { |
| space_for_oauth += kIconSize; |
| column_set = layout->AddColumnSet(++column_set_id); |
| column_set->AddColumn(views::GridLayout::FILL, |
| views::GridLayout::FILL, |
| 1, |
| views::GridLayout::USE_PREF, |
| 0, // no fixed width |
| space_for_oauth); |
| } |
| |
| layout->StartRowWithPadding(0, column_set_id, |
| 0, views::kRelatedControlVerticalSpacing); |
| views::Label* oauth_header = new views::Label(prompt.GetOAuthHeading()); |
| oauth_header->SetMultiLine(true); |
| oauth_header->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| oauth_header->SizeToFit(left_column_width); |
| layout->AddView(oauth_header); |
| |
| for (size_t i = 0; i < prompt.GetOAuthIssueCount(); ++i) { |
| layout->StartRowWithPadding( |
| 0, column_set_id, |
| 0, views::kRelatedControlVerticalSpacing); |
| |
| PermissionDetails details; |
| const IssueAdviceInfoEntry& entry = prompt.GetOAuthIssue(i); |
| for (size_t x = 0; x < entry.details.size(); ++x) |
| details.push_back(entry.details[x]); |
| ExpandableContainerView* issue_advice_view = |
| new ExpandableContainerView( |
| this, entry.description, details, space_for_oauth, true); |
| layout->AddView(issue_advice_view); |
| } |
| } |
| if (prompt.GetRetainedFileCount()) { |
| // Slide in under the permissions or OAuth, if there are any. If there are |
| // either, the retained files prompt stretches all the way to the right of |
| // the dialog. If there are no permissions or OAuth, the retained files |
| // prompt just takes up the left column. |
| int space_for_files = left_column_width; |
| if (prompt.GetPermissionCount() || prompt.GetOAuthIssueCount()) { |
| space_for_files += kIconSize; |
| column_set = layout->AddColumnSet(++column_set_id); |
| column_set->AddColumn(views::GridLayout::FILL, |
| views::GridLayout::FILL, |
| 1, |
| views::GridLayout::USE_PREF, |
| 0, // no fixed width |
| space_for_files); |
| } |
| |
| layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); |
| |
| layout->StartRow(0, column_set_id); |
| views::Label* retained_files_header = NULL; |
| retained_files_header = |
| new views::Label(prompt.GetRetainedFilesHeading()); |
| retained_files_header->SetMultiLine(true); |
| retained_files_header->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| retained_files_header->SizeToFit(space_for_files); |
| layout->AddView(retained_files_header); |
| |
| layout->StartRow(0, column_set_id); |
| PermissionDetails details; |
| for (size_t i = 0; i < prompt.GetRetainedFileCount(); ++i) |
| details.push_back(prompt.GetRetainedFile(i)); |
| ExpandableContainerView* issue_advice_view = |
| new ExpandableContainerView( |
| this, string16(), details, space_for_files, false); |
| layout->AddView(issue_advice_view); |
| } |
| |
| gfx::Size scrollable_size = scrollable_->GetPreferredSize(); |
| scrollable_->SetBoundsRect(gfx::Rect(scrollable_size)); |
| dialog_size_ = gfx::Size( |
| dialog_width, |
| std::min(scrollable_size.height(), kDialogMaxHeight)); |
| } |
| |
| ExtensionInstallDialogView::~ExtensionInstallDialogView() {} |
| |
| void ExtensionInstallDialogView::ContentsChanged() { |
| Layout(); |
| } |
| |
| int ExtensionInstallDialogView::GetDialogButtons() const { |
| int buttons = prompt_.GetDialogButtons(); |
| // Simply having just an OK button is *not* supported. See comment on function |
| // GetDialogButtons in dialog_delegate.h for reasons. |
| DCHECK_GT(buttons & ui::DIALOG_BUTTON_CANCEL, 0); |
| return buttons; |
| } |
| |
| string16 ExtensionInstallDialogView::GetDialogButtonLabel( |
| ui::DialogButton button) const { |
| switch (button) { |
| case ui::DIALOG_BUTTON_OK: |
| return prompt_.GetAcceptButtonLabel(); |
| case ui::DIALOG_BUTTON_CANCEL: |
| return prompt_.HasAbortButtonLabel() ? |
| prompt_.GetAbortButtonLabel() : |
| l10n_util::GetStringUTF16(IDS_CANCEL); |
| default: |
| NOTREACHED(); |
| return string16(); |
| } |
| } |
| |
| int ExtensionInstallDialogView::GetDefaultDialogButton() const { |
| return ui::DIALOG_BUTTON_CANCEL; |
| } |
| |
| bool ExtensionInstallDialogView::Cancel() { |
| delegate_->InstallUIAbort(true); |
| return true; |
| } |
| |
| bool ExtensionInstallDialogView::Accept() { |
| delegate_->InstallUIProceed(); |
| return true; |
| } |
| |
| ui::ModalType ExtensionInstallDialogView::GetModalType() const { |
| return ui::MODAL_TYPE_WINDOW; |
| } |
| |
| string16 ExtensionInstallDialogView::GetWindowTitle() const { |
| return prompt_.GetDialogTitle(); |
| } |
| |
| void ExtensionInstallDialogView::LinkClicked(views::Link* source, |
| int event_flags) { |
| GURL store_url(extension_urls::GetWebstoreItemDetailURLPrefix() + |
| prompt_.extension()->id()); |
| OpenURLParams params( |
| store_url, Referrer(), NEW_FOREGROUND_TAB, content::PAGE_TRANSITION_LINK, |
| false); |
| navigator_->OpenURL(params); |
| GetWidget()->Close(); |
| } |
| |
| void ExtensionInstallDialogView::Layout() { |
| scroll_view_->SetBounds(0, 0, width(), height()); |
| DialogDelegateView::Layout(); |
| } |
| |
| gfx::Size ExtensionInstallDialogView::GetPreferredSize() { |
| return dialog_size_; |
| } |
| |
| // static |
| ExtensionInstallPrompt::ShowDialogCallback |
| ExtensionInstallPrompt::GetDefaultShowDialogCallback() { |
| return base::Bind(&ShowExtensionInstallDialogImpl); |
| } |
| |
| // ExpandableContainerView::DetailsView ---------------------------------------- |
| |
| ExpandableContainerView::DetailsView::DetailsView(int horizontal_space, |
| bool parent_bulleted) |
| : layout_(new views::GridLayout(this)), |
| state_(0), |
| parent_bulleted_(parent_bulleted) { |
| SetLayoutManager(layout_); |
| views::ColumnSet* column_set = layout_->AddColumnSet(0); |
| // If the parent is using bullets for its items, then a padding of one unit |
| // will make the child item (which has no bullet) look like a sibling of its |
| // parent. Therefore increase the indentation by one more unit to show that it |
| // is in fact a child item (with no missing bullet) and not a sibling. |
| int padding = |
| views::kRelatedControlHorizontalSpacing * (parent_bulleted ? 2 : 1); |
| column_set->AddPaddingColumn(0, padding); |
| column_set->AddColumn(views::GridLayout::LEADING, |
| views::GridLayout::LEADING, |
| 0, |
| views::GridLayout::FIXED, |
| horizontal_space - padding, |
| 0); |
| } |
| |
| void ExpandableContainerView::DetailsView::AddDetail(const string16& detail) { |
| layout_->StartRowWithPadding(0, 0, |
| 0, views::kRelatedControlSmallVerticalSpacing); |
| views::Label* detail_label = |
| new views::Label(PrepareForDisplay(detail, false)); |
| detail_label->SetMultiLine(true); |
| detail_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| layout_->AddView(detail_label); |
| } |
| |
| gfx::Size ExpandableContainerView::DetailsView::GetPreferredSize() { |
| gfx::Size size = views::View::GetPreferredSize(); |
| return gfx::Size(size.width(), size.height() * state_); |
| } |
| |
| void ExpandableContainerView::DetailsView::AnimateToState(double state) { |
| state_ = state; |
| PreferredSizeChanged(); |
| SchedulePaint(); |
| } |
| |
| // ExpandableContainerView ----------------------------------------------------- |
| |
| ExpandableContainerView::ExpandableContainerView( |
| ExtensionInstallDialogView* owner, |
| const string16& description, |
| const PermissionDetails& details, |
| int horizontal_space, |
| bool parent_bulleted) |
| : owner_(owner), |
| details_view_(NULL), |
| arrow_view_(NULL), |
| slide_animation_(this), |
| expanded_(false) { |
| views::GridLayout* layout = new views::GridLayout(this); |
| SetLayoutManager(layout); |
| int column_set_id = 0; |
| views::ColumnSet* column_set = layout->AddColumnSet(column_set_id); |
| column_set->AddColumn(views::GridLayout::LEADING, |
| views::GridLayout::LEADING, |
| 0, |
| views::GridLayout::USE_PREF, |
| 0, |
| 0); |
| if (!description.empty()) { |
| layout->StartRow(0, column_set_id); |
| |
| views::Label* description_label = new views::Label(description); |
| description_label->SetMultiLine(true); |
| description_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| description_label->SizeToFit(horizontal_space); |
| layout->AddView(new BulletedView(description_label)); |
| } |
| |
| if (details.empty()) |
| return; |
| |
| details_view_ = new DetailsView(horizontal_space, parent_bulleted); |
| |
| layout->StartRow(0, column_set_id); |
| layout->AddView(details_view_); |
| |
| for (size_t i = 0; i < details.size(); ++i) |
| details_view_->AddDetail(details[i]); |
| |
| views::Link* link = new views::Link( |
| l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS)); |
| |
| // Make sure the link width column is as wide as needed for both Show and |
| // Hide details, so that the arrow doesn't shift horizontally when we toggle. |
| int link_col_width = |
| views::kRelatedControlHorizontalSpacing + |
| std::max(link->font_list().GetStringWidth( |
| l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS)), |
| link->font_list().GetStringWidth( |
| l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS))); |
| |
| column_set = layout->AddColumnSet(++column_set_id); |
| // Padding to the left of the More Details column. If the parent is using |
| // bullets for its items, then a padding of one unit will make the child item |
| // (which has no bullet) look like a sibling of its parent. Therefore increase |
| // the indentation by one more unit to show that it is in fact a child item |
| // (with no missing bullet) and not a sibling. |
| column_set->AddPaddingColumn( |
| 0, views::kRelatedControlHorizontalSpacing * (parent_bulleted ? 2 : 1)); |
| // The More Details column. |
| column_set->AddColumn(views::GridLayout::LEADING, |
| views::GridLayout::LEADING, |
| 0, |
| views::GridLayout::FIXED, |
| link_col_width, |
| link_col_width); |
| // The Up/Down arrow column. |
| column_set->AddColumn(views::GridLayout::LEADING, |
| views::GridLayout::LEADING, |
| 0, |
| views::GridLayout::USE_PREF, |
| 0, |
| 0); |
| |
| // Add the More Details link. |
| layout->StartRow(0, column_set_id); |
| more_details_ = link; |
| more_details_->set_listener(this); |
| more_details_->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| layout->AddView(more_details_); |
| |
| // Add the arrow after the More Details link. |
| ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| arrow_toggle_ = new views::ImageButton(this); |
| arrow_toggle_->SetImage(views::Button::STATE_NORMAL, |
| rb.GetImageSkiaNamed(IDR_DOWN_ARROW)); |
| layout->AddView(arrow_toggle_); |
| } |
| |
| ExpandableContainerView::~ExpandableContainerView() { |
| } |
| |
| void ExpandableContainerView::ButtonPressed( |
| views::Button* sender, const ui::Event& event) { |
| ToggleDetailLevel(); |
| } |
| |
| void ExpandableContainerView::LinkClicked( |
| views::Link* source, int event_flags) { |
| ToggleDetailLevel(); |
| } |
| |
| void ExpandableContainerView::AnimationProgressed( |
| const gfx::Animation* animation) { |
| DCHECK_EQ(&slide_animation_, animation); |
| if (details_view_) |
| details_view_->AnimateToState(animation->GetCurrentValue()); |
| } |
| |
| void ExpandableContainerView::AnimationEnded(const gfx::Animation* animation) { |
| if (animation->GetCurrentValue() != 0.0) { |
| arrow_toggle_->SetImage( |
| views::Button::STATE_NORMAL, |
| ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( |
| IDR_UP_ARROW)); |
| } else { |
| arrow_toggle_->SetImage( |
| views::Button::STATE_NORMAL, |
| ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( |
| IDR_DOWN_ARROW)); |
| } |
| |
| more_details_->SetText(expanded_ ? |
| l10n_util::GetStringUTF16(IDS_EXTENSIONS_HIDE_DETAILS) : |
| l10n_util::GetStringUTF16(IDS_EXTENSIONS_SHOW_DETAILS)); |
| } |
| |
| void ExpandableContainerView::ChildPreferredSizeChanged(views::View* child) { |
| owner_->ContentsChanged(); |
| } |
| |
| void ExpandableContainerView::ToggleDetailLevel() { |
| expanded_ = !expanded_; |
| |
| if (slide_animation_.IsShowing()) |
| slide_animation_.Hide(); |
| else |
| slide_animation_.Show(); |
| } |