diff options
author | sashab@chromium.org <sashab@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-18 06:50:23 +0000 |
---|---|---|
committer | sashab@chromium.org <sashab@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-18 06:50:23 +0000 |
commit | b42a6050117330a2c042d7509d56a0b653a3d9a2 (patch) | |
tree | be55e2802075c76ce27a93710e0722663ea9155a /chrome | |
parent | af349ed8960eba82ccce3a43d684f33ae25f3506 (diff) | |
download | chromium_src-b42a6050117330a2c042d7509d56a0b653a3d9a2.zip chromium_src-b42a6050117330a2c042d7509d56a0b653a3d9a2.tar.gz chromium_src-b42a6050117330a2c042d7509d56a0b653a3d9a2.tar.bz2 |
Re-styled the App Info Dialog according to UI feedback
Changed the layout of the App Info Dialog according to UI feedback. This
includes removing the tabs, and replacing it with a single, scrollable
dialog that has all the information in a single pane.
BUG=364681,266739
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=276557
Review URL: https://codereview.chromium.org/327743002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@277954 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
21 files changed, 1157 insertions, 1483 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 16414ed..4c7f027 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -2160,58 +2160,58 @@ Even if you have downloaded files from this website before, the website might ha <!-- "Application Information" dialog --> <if expr="not use_titlecase"> - <message name="IDS_APPLICATION_INFO_SUMMARY_TAB_TITLE" desc="Title of the first tab in the dialog, which displays general summary information about the app. "> + <message name="IDS_APPLICATION_INFO_WEB_STORE_LINK" desc="Text for the link that opens the app in the Web Store.."> + View in store + </message> + <message name="IDS_APPLICATION_INFO_UNINSTALL_BUTTON_TEXT" desc="Text for the button that removes (uninstalls) the app."> + Remove + </message> + <message name="IDS_APPLICATION_INFO_LICENSES_BUTTON_TEXT" desc="Text for the button that displays the licenses for the app."> + Licenses + </message> + <message name="IDS_APPLICATION_INFO_DESCRIPTION_TITLE" desc="Title of the first section in the dialog, which displays a general summary of the app (the app's description)."> Summary </message> - <message name="IDS_APPLICATION_INFO_PERMISSIONS_TAB_TITLE" desc="Title of the second tab in the dialog, which lists the specific permissions that the app has."> - Permissions + <message name="IDS_APPLICATION_INFO_ACTIVE_PERMISSIONS_TEXT" desc="Heading above the current permissions for the app (the permissions that the user has granted the app upon installation)."> + It can: </message> - <message name="IDS_APPLICATION_INFO_WEB_STORE_LINK" desc="Text for the link that opens the app in the Web Store."> - View in Web Store + <message name="IDS_APPLICATION_INFO_RETAINED_FILE_PERMISSIONS_TEXT" desc="Heading above the current retained files for the app, which are files the app has permanent access to (the app can read and write to these files)."> + Files + </message> + <message name="IDS_APPLICATION_INFO_REVOKE_RETAINED_FILE_PERMISSIONS_BUTTON_TEXT" desc="Text displayed in the button to remove the app's file permissions. After pressing this button, the app no longer has access to the files."> + Revoke file access </message> <message name="IDS_APPLICATION_INFO_CREATE_SHORTCUTS_BUTTON_TEXT" desc="Text for the button that opens the dialog to create shortcuts for the app."> Create shortcuts </message> - <message name="IDS_APPLICATION_INFO_UNINSTALL_BUTTON_TEXT" desc="Text for the button that uninstalls the app."> - Uninstall - </message> </if> <if expr="use_titlecase"> - <message name="IDS_APPLICATION_INFO_SUMMARY_TAB_TITLE" desc="In Title Case: Title of the first tab in the dialog, which displays general summary information about the app. "> + <message name="IDS_APPLICATION_INFO_WEB_STORE_LINK" desc="In Title Case: Text for the link that opens the app in the Web Store.."> + View in Store + </message> + <message name="IDS_APPLICATION_INFO_UNINSTALL_BUTTON_TEXT" desc="In Title Case: Text for the button that removes (uninstalls) the app."> + Remove + </message> + <message name="IDS_APPLICATION_INFO_LICENSES_BUTTON_TEXT" desc="In Title Case: Text for the button that displays the licenses for the app."> + Licenses + </message> + <message name="IDS_APPLICATION_INFO_DESCRIPTION_TITLE" desc="In Title Case: Title of the first section in the dialog, which displays a general summary of the app (the app's description)."> Summary </message> - <message name="IDS_APPLICATION_INFO_PERMISSIONS_TAB_TITLE" desc="In Title Case: Title of the second tab in the dialog, which lists the specific permissions that the app has."> - Permissions + <message name="IDS_APPLICATION_INFO_ACTIVE_PERMISSIONS_TEXT" desc="In Title Case: Heading above the current permissions for the app (the permissions that the user has granted the app upon installation)."> + It Can: </message> - <message name="IDS_APPLICATION_INFO_WEB_STORE_LINK" desc="In Title Case: Text for the link that opens the app in the Web Store.."> - View in Web Store + <message name="IDS_APPLICATION_INFO_RETAINED_FILE_PERMISSIONS_TEXT" desc="In Title Case: Heading above the current retained files for the app, which are files the app has permanent access to (the app can read and write to these files)."> + Files + </message> + <message name="IDS_APPLICATION_INFO_REVOKE_RETAINED_FILE_PERMISSIONS_BUTTON_TEXT" desc="In Title Case: Text displayed in the button to remove the app's file permissions. After pressing this button, the app no longer has access to the files."> + Revoke File Access </message> <message name="IDS_APPLICATION_INFO_CREATE_SHORTCUTS_BUTTON_TEXT" desc="In Title Case: Text for the button that opens the dialog to create shortcuts for the app."> Create Shortcuts </message> - <message name="IDS_APPLICATION_INFO_UNINSTALL_BUTTON_TEXT" desc="In Title Case: Text for the button that uninstalls the app."> - Uninstall - </message> </if> - <message name="IDS_APPLICATION_INFO_IMPORTED_MODULES_TITLE_TEXT" desc="Text for the title above the imported modules for the app. Imported modules are like shared libraries that an app has used when it was built (but are not directly accessible by a user)."> - This app imports the following modules: - </message> - <message name="IDS_APPLICATION_INFO_IMPORTED_MODULES_ABOUT_LINK_TEXT" desc="Text for the link next to each imported module that goes to the About page for that module. The About page may include things such as open-source and copyright licenses for the app."> - About - </message> - <message name="IDS_APPLICATION_INFO_REQUIRED_PERMISSIONS_TEXT" desc="Heading for the required permissions for the app (the permissions that the user must grant the app upon installation)."> - Required - </message> - <message name="IDS_APPLICATION_INFO_OPTIONAL_PERMISSIONS_TEXT" desc="Heading for the optional permissions for the app (the permissions that the user may choose to grant the app, but are not required for the basic app's functionality)."> - Optional - </message> - <message name="IDS_APPLICATION_INFO_RETAINED_FILE_PERMISSIONS_TEXT" desc="Heading for the retained files for the app, which are files the app has permanent access to (the app can read and write to these files)."> - Files - </message> - <message name="IDS_APPLICATION_INFO_REVOKE_RETAINED_FILE_PERMISSIONS_BUTTON_TEXT" desc="Text displayed in the button to remove the app's file permissions. After pressing this button, the app no longer has access to the files."> - Revoke file access - </message> - <message name="IDS_APPLICATION_INFO_NO_PERMISSIONS_TEXT" desc="Text displayed in the Permissions area of the App Info dialog if the app has no permissions of any kind."> + <message name="IDS_APPLICATION_INFO_NO_PERMISSIONS_TEXT" desc="Text displayed in the Permissions area of the dialog if the app has no permissions."> This app requires no special permissions. </message> diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views.cc index bd40ec6..d2d718f 100644 --- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views.cc +++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views.cc @@ -4,20 +4,19 @@ #include "chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views.h" -#include "chrome/browser/profiles/profile.h" +#include "base/memory/scoped_ptr.h" #include "chrome/browser/ui/app_list/app_list_controller_delegate.h" #include "chrome/browser/ui/views/app_list/app_list_dialog_contents_view.h" -#include "chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_tab.h" -#include "chrome/browser/ui/views/apps/app_info_dialog/app_info_summary_tab.h" -#include "extensions/common/extension.h" -#include "grit/generated_resources.h" -#include "ui/base/l10n/l10n_util.h" +#include "chrome/browser/ui/views/apps/app_info_dialog/app_info_header_panel.h" +#include "chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_panel.h" +#include "chrome/browser/ui/views/apps/app_info_dialog/app_info_summary_panel.h" +#include "chrome/browser/ui/views/constrained_window_views.h" #include "ui/gfx/geometry/rect.h" -#include "ui/gfx/native_widget_types.h" -#include "ui/views/controls/tabbed_pane/tabbed_pane.h" +#include "ui/gfx/geometry/size.h" +#include "ui/views/border.h" +#include "ui/views/controls/scroll_view.h" #include "ui/views/layout/box_layout.h" #include "ui/views/layout/layout_constants.h" -#include "ui/views/view.h" #include "ui/views/widget/widget.h" void ShowAppInfoDialog(AppListControllerDelegate* app_list_controller_delegate, @@ -39,21 +38,40 @@ void ShowAppInfoDialog(AppListControllerDelegate* app_list_controller_delegate, AppInfoDialog::AppInfoDialog(Profile* profile, const extensions::Extension* app) { - SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, - views::kButtonHEdgeMarginNew, - views::kButtonVEdgeMarginNew, - 0)); - - views::TabbedPane* tabbed_pane = new views::TabbedPane(); - AddChildView(tabbed_pane); - - tabbed_pane->AddTab( - l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_SUMMARY_TAB_TITLE), - new AppInfoSummaryTab(profile, app)); - tabbed_pane->AddTab( - l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_PERMISSIONS_TAB_TITLE), - new AppInfoPermissionsTab(profile, app)); - // TODO(sashab): Add the manage tab back once there is content for it. + // The width of this margin determines the spacing either side of the + // horizontal separator underneath the summary panel. + const int kHorizontalBorderSpacing = 1; + const int kHorizontalSeparatorHeight = 2; + SetLayoutManager(new views::BoxLayout( + views::BoxLayout::kVertical, kHorizontalBorderSpacing, 0, 0)); + AppInfoHeaderPanel* dialog_header = new AppInfoHeaderPanel(profile, app); + dialog_header->SetBorder(views::Border::CreateSolidSidedBorder( + 0, 0, kHorizontalSeparatorHeight, 0, SK_ColorLTGRAY)); + + // Make a vertically stacked view of all the panels we want to display in the + // dialog. + views::View* dialog_body = new views::View(); + dialog_body->SetLayoutManager( + new views::BoxLayout(views::BoxLayout::kVertical, + views::kButtonHEdgeMarginNew, + views::kPanelVertMargin, + views::kUnrelatedControlVerticalSpacing)); + dialog_body->AddChildView(new AppInfoSummaryPanel(profile, app)); + dialog_body->AddChildView(new AppInfoPermissionsPanel(profile, app)); + + // Clip the scrollable view so that the scrollbar appears. As long as this + // is larger than the height of the dialog, it will be resized to the dialog's + // actual height. + // TODO(sashab): Add ClipHeight() as a parameter-less method to + // views::ScrollView(), which mimics this behaviour. + const int kMaxDialogHeight = 1000; + views::ScrollView* dialog_body_scrollview = new views::ScrollView(); + dialog_body_scrollview->ClipHeightTo(kMaxDialogHeight, kMaxDialogHeight); + dialog_body_scrollview->SetContents(dialog_body); + + AddChildView(dialog_header); + AddChildView(dialog_body_scrollview); } -AppInfoDialog::~AppInfoDialog() {} +AppInfoDialog::~AppInfoDialog() { +} diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views.h b/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views.h index 9152d37..1f8ddaa 100644 --- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views.h +++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_views.h @@ -14,6 +14,7 @@ class Extension; } // View the information about a particular chrome application. + class AppInfoDialog : public views::View { public: AppInfoDialog(Profile* profile, const extensions::Extension* app); diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_header_panel.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_header_panel.cc new file mode 100644 index 0000000..d5f0e83 --- /dev/null +++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_header_panel.cc @@ -0,0 +1,301 @@ +// Copyright 2014 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 "chrome/browser/ui/views/apps/app_info_dialog/app_info_header_panel.h" + +#include <vector> + +#include "base/bind.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_uninstall_dialog.h" +#include "chrome/browser/extensions/extension_util.h" +#include "chrome/browser/extensions/image_loader.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser_navigator.h" +#include "chrome/common/extensions/extension_constants.h" +#include "chrome/common/extensions/manifest_url_handler.h" +#include "extensions/browser/extension_system.h" +#include "extensions/browser/management_policy.h" +#include "extensions/common/constants.h" +#include "extensions/common/extension.h" +#include "extensions/common/extension_icon_set.h" +#include "extensions/common/extension_resource.h" +#include "extensions/common/manifest_handlers/icons_handler.h" +#include "extensions/common/manifest_handlers/shared_module_info.h" +#include "grit/generated_resources.h" +#include "net/base/url_util.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/image/image_skia.h" +#include "ui/gfx/image/image_skia_rep.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/text_constants.h" +#include "ui/views/controls/image_view.h" +#include "ui/views/controls/label.h" +#include "ui/views/controls/link.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/widget/widget.h" +#include "url/gurl.h" + +// Size of extension icon in top left of dialog. +const int kIconSize = 64; + +AppInfoHeaderPanel::AppInfoHeaderPanel(Profile* profile, + const extensions::Extension* app) + : AppInfoPanel(profile, app), + app_icon_(NULL), + app_name_label_(NULL), + app_version_label_(NULL), + view_in_store_link_(NULL), + remove_link_(NULL), + licenses_link_(NULL), + weak_ptr_factory_(this) { + CreateControls(); + + SetLayoutManager( + new views::BoxLayout(views::BoxLayout::kHorizontal, + views::kButtonHEdgeMargin, + views::kButtonVEdgeMargin, + views::kRelatedControlHorizontalSpacing)); + + LayoutControls(); +} + +AppInfoHeaderPanel::~AppInfoHeaderPanel() { +} + +void AppInfoHeaderPanel::CreateControls() { + app_name_label_ = + new views::Label(base::UTF8ToUTF16(app_->name()), + ui::ResourceBundle::GetSharedInstance().GetFontList( + ui::ResourceBundle::BoldFont)); + app_name_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); + + // The version number doesn't make sense for bookmarked apps. + if (!app_->from_bookmark()) { + app_version_label_ = + new views::Label(base::UTF8ToUTF16(app_->VersionString())); + app_version_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); + } + + app_icon_ = new views::ImageView(); + app_icon_->SetImageSize(gfx::Size(kIconSize, kIconSize)); + LoadAppImageAsync(); + + if (CanShowAppInWebStore()) { + view_in_store_link_ = new views::Link( + l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_WEB_STORE_LINK)); + view_in_store_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT); + view_in_store_link_->set_listener(this); + } + + if (CanUninstallApp()) { + remove_link_ = new views::Link( + l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_UNINSTALL_BUTTON_TEXT)); + remove_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT); + remove_link_->set_listener(this); + } + + if (CanDisplayLicenses()) { + licenses_link_ = new views::Link( + l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_LICENSES_BUTTON_TEXT)); + licenses_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT); + licenses_link_->set_listener(this); + } +} + +void AppInfoHeaderPanel::LayoutAppNameAndVersionInto(views::View* parent_view) { + views::View* view = new views::View(); + // We need a horizontal BoxLayout here to ensure that the GridLayout does + // not stretch beyond the size of its content. + view->SetLayoutManager( + new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0)); + + views::View* container_view = new views::View(); + view->AddChildView(container_view); + views::GridLayout* layout = new views::GridLayout(container_view); + container_view->SetLayoutManager(layout); + + static const int kColumnId = 1; + views::ColumnSet* column_set = layout->AddColumnSet(kColumnId); + column_set->AddColumn(views::GridLayout::LEADING, + views::GridLayout::LEADING, + 1, // Stretch the title to as wide as needed + views::GridLayout::USE_PREF, + 0, + 0); + column_set->AddPaddingColumn(0, views::kRelatedControlSmallHorizontalSpacing); + column_set->AddColumn(views::GridLayout::LEADING, + views::GridLayout::LEADING, + 0, // Do not stretch the version + views::GridLayout::USE_PREF, + 0, + 0); + + layout->StartRow(1, kColumnId); + layout->AddView(app_name_label_); + if (app_version_label_) + layout->AddView(app_version_label_); + + parent_view->AddChildView(view); +} + +void AppInfoHeaderPanel::LayoutControls() { + AddChildView(app_icon_); + if (!app_version_label_ && !view_in_store_link_) { + // If there's no link to the webstore _and_ no version, allow the app's name + // to take up multiple lines. + app_name_label_->SetMultiLine(true); + AddChildView(app_name_label_); + } else { + // Create a vertical container to store the app's name and info. + views::View* vertical_info_container = new views::View(); + views::BoxLayout* vertical_container_layout = + new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0); + vertical_container_layout->set_main_axis_alignment( + views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER); + vertical_info_container->SetLayoutManager(vertical_container_layout); + // Create a horizontal container to store the app's links. + views::View* horizontal_links_container = new views::View(); + horizontal_links_container->SetLayoutManager( + new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 3)); + if (view_in_store_link_) + horizontal_links_container->AddChildView(view_in_store_link_); + if (remove_link_) + horizontal_links_container->AddChildView(remove_link_); + if (licenses_link_) + horizontal_links_container->AddChildView(licenses_link_); + // First line: title and (possibly) version. Second line: links (if any). + if (app_version_label_) { + LayoutAppNameAndVersionInto(vertical_info_container); + } else { + vertical_info_container->AddChildView(app_name_label_); + } + if (vertical_info_container->child_count() > 0) + vertical_info_container->AddChildView(horizontal_links_container); + AddChildView(vertical_info_container); + } +} +void AppInfoHeaderPanel::LinkClicked(views::Link* source, int event_flags) { + if (source == view_in_store_link_) { + ShowAppInWebStore(); + } else if (source == remove_link_) { + UninstallApp(); + } else if (source == licenses_link_) { + DisplayLicenses(); + } else { + NOTREACHED(); + } +} + +void AppInfoHeaderPanel::ExtensionUninstallAccepted() { + ExtensionService* service = + extensions::ExtensionSystem::Get(profile_)->extension_service(); + service->UninstallExtension(app_->id(), false, NULL); + // Close the App Info dialog as well (which will free the dialog too). + GetWidget()->Close(); +} +void AppInfoHeaderPanel::ExtensionUninstallCanceled() { + extension_uninstall_dialog_.reset(); +} +void AppInfoHeaderPanel::LoadAppImageAsync() { + extensions::ExtensionResource image = extensions::IconsInfo::GetIconResource( + app_, + extension_misc::EXTENSION_ICON_LARGE, + ExtensionIconSet::MATCH_BIGGER); + int pixel_size = + static_cast<int>(kIconSize * gfx::ImageSkia::GetMaxSupportedScale()); + extensions::ImageLoader::Get(profile_)->LoadImageAsync( + app_, + image, + gfx::Size(pixel_size, pixel_size), + base::Bind(&AppInfoHeaderPanel::OnAppImageLoaded, AsWeakPtr())); +} + +void AppInfoHeaderPanel::OnAppImageLoaded(const gfx::Image& image) { + const SkBitmap* bitmap; + if (image.IsEmpty()) { + bitmap = &extensions::util::GetDefaultAppIcon() + .GetRepresentation(gfx::ImageSkia::GetMaxSupportedScale()) + .sk_bitmap(); + } else { + bitmap = image.ToSkBitmap(); + } + + app_icon_->SetImage(gfx::ImageSkia::CreateFrom1xBitmap(*bitmap)); +} + +void AppInfoHeaderPanel::ShowAppInWebStore() const { + DCHECK(CanShowAppInWebStore()); + const GURL url = extensions::ManifestURL::GetDetailsURL(app_); + DCHECK_NE(url, GURL::EmptyGURL()); + chrome::NavigateParams params( + profile_, + net::AppendQueryParameter(url, + extension_urls::kWebstoreSourceField, + extension_urls::kLaunchSourceAppListInfoDialog), + content::PAGE_TRANSITION_LINK); + chrome::Navigate(¶ms); +} + +bool AppInfoHeaderPanel::CanShowAppInWebStore() const { + return app_->from_webstore(); +} + +void AppInfoHeaderPanel::UninstallApp() { + DCHECK(CanUninstallApp()); + extension_uninstall_dialog_.reset( + extensions::ExtensionUninstallDialog::Create(profile_, NULL, this)); + extension_uninstall_dialog_->ConfirmUninstall(app_); +} + +bool AppInfoHeaderPanel::CanUninstallApp() const { + return extensions::ExtensionSystem::Get(profile_) + ->management_policy() + ->UserMayModifySettings(app_, NULL); +} + +void AppInfoHeaderPanel::DisplayLicenses() { + // Find the first shared module for this app, and display it's options page + // as an 'about' link. + // TODO(sashab): Revisit UI layout once shared module usage becomes more + // common. + DCHECK(CanDisplayLicenses()); + ExtensionService* service = + extensions::ExtensionSystem::Get(profile_)->extension_service(); + DCHECK(service); + const std::vector<extensions::SharedModuleInfo::ImportInfo>& imports = + extensions::SharedModuleInfo::GetImports(app_); + const extensions::Extension* imported_module = + service->GetExtensionById(imports[0].extension_id, true); + DCHECK(imported_module); + GURL about_page = extensions::ManifestURL::GetAboutPage(imported_module); + DCHECK(about_page != GURL::EmptyGURL()); + chrome::NavigateParams params( + profile_, about_page, content::PAGE_TRANSITION_LINK); + chrome::Navigate(¶ms); +} + +bool AppInfoHeaderPanel::CanDisplayLicenses() { + if (!extensions::SharedModuleInfo::ImportsModules(app_)) + return false; + ExtensionService* service = + extensions::ExtensionSystem::Get(profile_)->extension_service(); + DCHECK(service); + const std::vector<extensions::SharedModuleInfo::ImportInfo>& imports = + extensions::SharedModuleInfo::GetImports(app_); + const extensions::Extension* imported_module = + service->GetExtensionById(imports[0].extension_id, true); + DCHECK(imported_module); + GURL about_page = extensions::ManifestURL::GetAboutPage(imported_module); + if (about_page == GURL::EmptyGURL()) + return false; + return true; +} diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_header_panel.h b/chrome/browser/ui/views/apps/app_info_dialog/app_info_header_panel.h new file mode 100644 index 0000000..e731695 --- /dev/null +++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_header_panel.h @@ -0,0 +1,87 @@ +// Copyright 2014 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. + +#ifndef CHROME_BROWSER_UI_VIEWS_APPS_APP_INFO_DIALOG_APP_INFO_HEADER_PANEL_H_ +#define CHROME_BROWSER_UI_VIEWS_APPS_APP_INFO_DIALOG_APP_INFO_HEADER_PANEL_H_ + +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "chrome/browser/extensions/extension_uninstall_dialog.h" +#include "chrome/browser/ui/views/apps/app_info_dialog/app_info_panel.h" +#include "ui/views/controls/link_listener.h" + +class Profile; + +namespace extensions { +class Extension; +} + +namespace gfx { +class Image; +} + +namespace views { +class ImageView; +class Label; +class Link; +} + +// A small summary panel with the app's name, icon, version, and various links +// that is displayed at the top of the app info dialog. +class AppInfoHeaderPanel + : public AppInfoPanel, + public views::LinkListener, + public extensions::ExtensionUninstallDialog::Delegate, + public base::SupportsWeakPtr<AppInfoHeaderPanel> { + public: + AppInfoHeaderPanel(Profile* profile, const extensions::Extension* app); + virtual ~AppInfoHeaderPanel(); + + private: + void CreateControls(); + void LayoutAppNameAndVersionInto(views::View* parent_view); + void LayoutControls(); + + // Overridden from views::LinkListener: + virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE; + + // Overridden from ExtensionUninstallDialog::Delegate: + virtual void ExtensionUninstallAccepted() OVERRIDE; + virtual void ExtensionUninstallCanceled() OVERRIDE; + + // Load the app icon asynchronously. For the response, check OnAppImageLoaded. + void LoadAppImageAsync(); + // Called when the app's icon is loaded. + void OnAppImageLoaded(const gfx::Image& image); + + // Opens the app in the web store. Must only be called if + // CanShowAppInWebStore() returns true. + void ShowAppInWebStore() const; + bool CanShowAppInWebStore() const; + + // Uninstall the app. Must only be called if CanUninstallApp() returns true. + void UninstallApp(); + bool CanUninstallApp() const; + + // Displays the licenses for the app. Must only be called if + // CanDisplayLicenses() returns true. + void DisplayLicenses(); + bool CanDisplayLicenses(); + + // UI elements on the dialog. Elements are NULL if they are not displayed. + views::ImageView* app_icon_; + views::Label* app_name_label_; + views::Label* app_version_label_; + views::Link* view_in_store_link_; + views::Link* remove_link_; + views::Link* licenses_link_; + + scoped_ptr<extensions::ExtensionUninstallDialog> extension_uninstall_dialog_; + + base::WeakPtrFactory<AppInfoHeaderPanel> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(AppInfoHeaderPanel); +}; + +#endif // CHROME_BROWSER_UI_VIEWS_APPS_APP_INFO_DIALOG_APP_INFO_HEADER_PANEL_H_ diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_manage_tab.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_manage_tab.cc deleted file mode 100644 index b145f3b..0000000 --- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_manage_tab.cc +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2014 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 "chrome/browser/ui/views/apps/app_info_dialog/app_info_manage_tab.h" - -AppInfoManageTab::AppInfoManageTab(Profile* profile, - const extensions::Extension* app) - : AppInfoTab(profile, app) { - // TODO(sashab): Populate this tab. -} - -AppInfoManageTab::~AppInfoManageTab() {} diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_manage_tab.h b/chrome/browser/ui/views/apps/app_info_dialog/app_info_manage_tab.h deleted file mode 100644 index 4746bda..0000000 --- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_manage_tab.h +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2014 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. - -#ifndef CHROME_BROWSER_UI_VIEWS_APPS_APP_INFO_DIALOG_APP_INFO_MANAGE_TAB_H_ -#define CHROME_BROWSER_UI_VIEWS_APPS_APP_INFO_DIALOG_APP_INFO_MANAGE_TAB_H_ - -#include "chrome/browser/ui/views/apps/app_info_dialog/app_info_tab.h" - -class Profile; - -namespace extensions { -class Extension; -} - -// The Manage tab of the app info dialog, which provides insight and control -// over the app's state and usage. -class AppInfoManageTab : public AppInfoTab { - public: - AppInfoManageTab(Profile* profile, const extensions::Extension* app); - virtual ~AppInfoManageTab(); - - private: - DISALLOW_COPY_AND_ASSIGN(AppInfoManageTab); -}; - -#endif // CHROME_BROWSER_UI_VIEWS_APPS_APP_INFO_DIALOG_APP_INFO_MANAGE_TAB_H_ diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_panel.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_panel.cc new file mode 100644 index 0000000..0821073 --- /dev/null +++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_panel.cc @@ -0,0 +1,35 @@ +// Copyright 2014 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 "chrome/browser/ui/views/apps/app_info_dialog/app_info_panel.h" + +#include "ui/base/resource/resource_bundle.h" +#include "ui/views/controls/label.h" +#include "ui/views/layout/box_layout.h" +#include "ui/views/layout/layout_constants.h" + +AppInfoPanel::AppInfoPanel(Profile* profile, const extensions::Extension* app) + : profile_(profile), app_(app) { +} + +AppInfoPanel::~AppInfoPanel() { +} + +views::Label* AppInfoPanel::CreateHeading(const base::string16& text) const { + views::Label* label = new views::Label(text); + label->SetHorizontalAlignment(gfx::ALIGN_LEFT); + label->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList( + ui::ResourceBundle::MediumFont)); + return label; +} + +views::View* AppInfoPanel::CreateVerticalStack() const { + views::View* vertically_stacked_view = new views::View(); + vertically_stacked_view->SetLayoutManager( + new views::BoxLayout(views::BoxLayout::kVertical, + 0, + 0, + views::kRelatedControlVerticalSpacing)); + return vertically_stacked_view; +} diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_panel.h b/chrome/browser/ui/views/apps/app_info_dialog/app_info_panel.h new file mode 100644 index 0000000..2545e6b --- /dev/null +++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_panel.h @@ -0,0 +1,42 @@ +// Copyright 2014 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. + +#ifndef CHROME_BROWSER_UI_VIEWS_APPS_APP_INFO_DIALOG_APP_INFO_PANEL_H_ +#define CHROME_BROWSER_UI_VIEWS_APPS_APP_INFO_DIALOG_APP_INFO_PANEL_H_ + +#include "ui/views/view.h" + +class Profile; + +namespace extensions { +class Extension; +} +namespace views { +class Label; +} + +// A piece of the App Info dialog that displays information for a particular +// profile and app. Panels in the App Info dialog extend this class. +class AppInfoPanel : public views::View { + public: + AppInfoPanel(Profile* profile, const extensions::Extension* app); + + virtual ~AppInfoPanel(); + + protected: + // Create a heading label with the given text. + views::Label* CreateHeading(const base::string16& text) const; + + // Create a view with a vertically-stacked box layout, which can have child + // views appended to it. + views::View* CreateVerticalStack() const; + + Profile* profile_; + const extensions::Extension* app_; + + private: + DISALLOW_COPY_AND_ASSIGN(AppInfoPanel); +}; + +#endif // CHROME_BROWSER_UI_VIEWS_APPS_APP_INFO_DIALOG_APP_INFO_PANEL_H_ diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_panel.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_panel.cc new file mode 100644 index 0000000..c638790 --- /dev/null +++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_panel.cc @@ -0,0 +1,200 @@ +// Copyright 2014 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 "chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_panel.h" + +#include <string> +#include <vector> + +#include "apps/app_load_service.h" +#include "apps/app_restore_service.h" +#include "apps/saved_files_service.h" +#include "base/files/file_path.h" +#include "extensions/common/extension.h" +#include "extensions/common/permissions/api_permission.h" +#include "extensions/common/permissions/permissions_data.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/views/controls/button/label_button.h" +#include "ui/views/controls/label.h" +#include "ui/views/layout/box_layout.h" +#include "ui/views/layout/layout_constants.h" +#include "ui/views/view.h" +#include "ui/views/widget/widget.h" + +AppInfoPermissionsPanel::AppInfoPermissionsPanel( + Profile* profile, + const extensions::Extension* app) + : AppInfoPanel(profile, app), + active_permissions_heading_(NULL), + active_permissions_list_(NULL), + retained_files_heading_(NULL), + retained_files_list_(NULL), + revoke_file_permissions_button_(NULL) { + // Create UI elements. + CreateActivePermissionsControl(); + CreateRetainedFilesControl(); + + // Layout elements. + SetLayoutManager( + new views::BoxLayout(views::BoxLayout::kVertical, + 0, + 0, + views::kUnrelatedControlVerticalSpacing)); + + LayoutActivePermissionsControl(); + LayoutRetainedFilesControl(); +} + +AppInfoPermissionsPanel::~AppInfoPermissionsPanel() { + // Destroy view children before their models. + RemoveAllChildViews(true); +} + +// Given a list of strings, returns a view containing a list of these strings +// as bulleted items. +views::View* AppInfoPermissionsPanel::CreateBulletedListView( + const std::vector<base::string16>& messages) { + const int kSpacingBetweenBulletAndStartOfText = 5; + views::View* list_view = CreateVerticalStack(); + + for (std::vector<base::string16>::const_iterator it = messages.begin(); + it != messages.end(); + ++it) { + views::Label* permission_label = new views::Label(*it); + permission_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); + permission_label->SetMultiLine(true); + + // Extract only the bullet from the IDS_EXTENSION_PERMISSION_LINE text, and + // place it in it's own view so it doesn't align vertically with the + // multilined permissions text. + views::Label* bullet_label = new views::Label(l10n_util::GetStringFUTF16( + IDS_EXTENSION_PERMISSION_LINE, base::string16())); + views::View* bullet_label_top_aligned = CreateVerticalStack(); + bullet_label_top_aligned->AddChildView(bullet_label); + + // Place the bullet and the text so all permissions line up at the bullet. + views::View* bulleted_list_item = new views::View(); + bulleted_list_item->SetLayoutManager( + new views::BoxLayout(views::BoxLayout::kHorizontal, + 0, + 0, + kSpacingBetweenBulletAndStartOfText)); + bulleted_list_item->AddChildView(bullet_label_top_aligned); + bulleted_list_item->AddChildView(permission_label); + + list_view->AddChildView(bulleted_list_item); + } + + return list_view; +} + +void AppInfoPermissionsPanel::CreateActivePermissionsControl() { + std::vector<base::string16> permission_strings = + GetActivePermissionMessages(); + if (permission_strings.empty()) { + views::Label* no_permissions_text = new views::Label( + l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_NO_PERMISSIONS_TEXT)); + no_permissions_text->SetHorizontalAlignment(gfx::ALIGN_LEFT); + active_permissions_list_ = no_permissions_text; + } else { + active_permissions_heading_ = CreateHeading(l10n_util::GetStringUTF16( + IDS_APPLICATION_INFO_ACTIVE_PERMISSIONS_TEXT)); + active_permissions_list_ = CreateBulletedListView(permission_strings); + } +} + +void AppInfoPermissionsPanel::CreateRetainedFilesControl() { + const std::vector<base::string16> retained_file_permission_messages = + GetRetainedFilePaths(); + + if (!retained_file_permission_messages.empty()) { + revoke_file_permissions_button_ = new views::LabelButton( + this, + l10n_util::GetStringUTF16( + IDS_APPLICATION_INFO_REVOKE_RETAINED_FILE_PERMISSIONS_BUTTON_TEXT)); + revoke_file_permissions_button_->SetStyle(views::Button::STYLE_BUTTON); + + retained_files_heading_ = CreateHeading(l10n_util::GetStringUTF16( + IDS_APPLICATION_INFO_RETAINED_FILE_PERMISSIONS_TEXT)); + retained_files_list_ = + CreateBulletedListView(retained_file_permission_messages); + } +} + +void AppInfoPermissionsPanel::LayoutActivePermissionsControl() { + if (active_permissions_list_) { + views::View* vertical_stack = CreateVerticalStack(); + if (active_permissions_heading_) + vertical_stack->AddChildView(active_permissions_heading_); + vertical_stack->AddChildView(active_permissions_list_); + + AddChildView(vertical_stack); + } +} + +void AppInfoPermissionsPanel::LayoutRetainedFilesControl() { + if (retained_files_list_) { + DCHECK(retained_files_heading_); + DCHECK(revoke_file_permissions_button_); + + // Add a sub-view so the revoke button is right-aligned. + views::View* right_aligned_button = new views::View(); + views::BoxLayout* right_aligned_horizontal_layout = + new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0); + right_aligned_horizontal_layout->set_main_axis_alignment( + views::BoxLayout::MAIN_AXIS_ALIGNMENT_END); + right_aligned_button->SetLayoutManager(right_aligned_horizontal_layout); + right_aligned_button->AddChildView(revoke_file_permissions_button_); + + views::View* vertical_stack = CreateVerticalStack(); + vertical_stack->AddChildView(retained_files_heading_); + vertical_stack->AddChildView(retained_files_list_); + vertical_stack->AddChildView(right_aligned_button); + + AddChildView(vertical_stack); + } +} + +void AppInfoPermissionsPanel::ButtonPressed(views::Button* sender, + const ui::Event& event) { + if (sender == revoke_file_permissions_button_) { + RevokeFilePermissions(); + } else { + NOTREACHED(); + } +} + +const std::vector<base::string16> +AppInfoPermissionsPanel::GetActivePermissionMessages() const { + return app_->permissions_data()->GetPermissionMessageStrings(); +} + +const std::vector<base::string16> +AppInfoPermissionsPanel::GetRetainedFilePaths() const { + std::vector<base::string16> retained_file_paths; + if (app_->permissions_data()->HasAPIPermission( + extensions::APIPermission::kFileSystem)) { + std::vector<apps::SavedFileEntry> retained_file_entries = + apps::SavedFilesService::Get(profile_)->GetAllFileEntries(app_->id()); + for (std::vector<apps::SavedFileEntry>::const_iterator it = + retained_file_entries.begin(); + it != retained_file_entries.end(); + ++it) { + retained_file_paths.push_back(it->path.LossyDisplayName()); + } + } + return retained_file_paths; +} + +void AppInfoPermissionsPanel::RevokeFilePermissions() { + apps::SavedFilesService::Get(profile_)->ClearQueue(app_); + + // TODO(benwells): Fix this to call something like + // AppLoadService::RestartApplicationIfRunning. + if (apps::AppRestoreService::Get(profile_)->IsAppRestorable(app_->id())) + apps::AppLoadService::Get(profile_)->RestartApplication(app_->id()); + + GetWidget()->Close(); +} diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_panel.h b/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_panel.h new file mode 100644 index 0000000..fead42f --- /dev/null +++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_panel.h @@ -0,0 +1,79 @@ +// Copyright 2014 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. + +#ifndef CHROME_BROWSER_UI_VIEWS_APPS_APP_INFO_DIALOG_APP_INFO_PERMISSIONS_PANEL_H_ +#define CHROME_BROWSER_UI_VIEWS_APPS_APP_INFO_DIALOG_APP_INFO_PERMISSIONS_PANEL_H_ + +#include <vector> + +#include "chrome/browser/ui/views/apps/app_info_dialog/app_info_panel.h" +#include "ui/views/controls/button/button.h" + +class Profile; + +namespace extensions { +class Extension; +} + +namespace ui { +class Event; +} + +namespace views { +class Label; +class LabelButton; +class View; +} + +// The summary panel of the app info dialog, which provides basic information +// and controls related to the app. +class AppInfoPermissionsPanel : public AppInfoPanel, + public views::ButtonListener { + public: + AppInfoPermissionsPanel(Profile* profile, const extensions::Extension* app); + + virtual ~AppInfoPermissionsPanel(); + + private: + FRIEND_TEST_ALL_PREFIXES(AppInfoPermissionsPanelTest, + NoPermissionsObtainedCorrectly); + FRIEND_TEST_ALL_PREFIXES(AppInfoPermissionsPanelTest, + RequiredPermissionsObtainedCorrectly); + FRIEND_TEST_ALL_PREFIXES(AppInfoPermissionsPanelTest, + OptionalPermissionsObtainedCorrectly); + FRIEND_TEST_ALL_PREFIXES(AppInfoPermissionsPanelTest, + RetainedFilePermissionsObtainedCorrectly); + + // Given a list of strings, returns a view containing a list of these strings + // as bulleted items. + views::View* CreateBulletedListView( + const std::vector<base::string16>& messages); + + // Internal initialisation methods. + void CreateActivePermissionsControl(); + void CreateRetainedFilesControl(); + + void LayoutActivePermissionsControl(); + void LayoutRetainedFilesControl(); + + // Overridden from views::ButtonListener. + virtual void ButtonPressed(views::Button* sender, + const ui::Event& event) OVERRIDE; + + const std::vector<base::string16> GetActivePermissionMessages() const; + const std::vector<base::string16> GetRetainedFilePaths() const; + void RevokeFilePermissions(); + + // UI elements on the dialog. + views::Label* active_permissions_heading_; + views::View* active_permissions_list_; + + views::Label* retained_files_heading_; + views::View* retained_files_list_; + views::LabelButton* revoke_file_permissions_button_; + + DISALLOW_COPY_AND_ASSIGN(AppInfoPermissionsPanel); +}; + +#endif // CHROME_BROWSER_UI_VIEWS_APPS_APP_INFO_DIALOG_APP_INFO_PERMISSIONS_PANEL_H_ diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_tab_unittest.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_panel_unittest.cc index a7f561b..a4d8296 100644 --- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_tab_unittest.cc +++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_panel_unittest.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_tab.h" +#include "chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_panel.h" #include "apps/saved_files_service.h" #include "base/callback.h" @@ -31,9 +31,9 @@ using base::FilePath; using testing::Contains; using testing::Eq; -class AppInfoPermissionsTabTest : public testing::Test { +class AppInfoPermissionsPanelTest : public testing::Test { protected: - AppInfoPermissionsTabTest() {} + AppInfoPermissionsPanelTest() {} scoped_ptr<base::DictionaryValue> ValidAppManifest() { return extensions::DictionaryBuilder() @@ -56,27 +56,21 @@ class AppInfoPermissionsTabTest : public testing::Test { }; // Tests that an app with no permissions is treated correctly. -TEST_F(AppInfoPermissionsTabTest, NoPermissionsObtainedCorrectly) { +TEST_F(AppInfoPermissionsPanelTest, NoPermissionsObtainedCorrectly) { scoped_refptr<const extensions::Extension> app = extensions::ExtensionBuilder() .SetManifest(ValidAppManifest()) .SetID(kTestExtensionId) .Build(); - AppInfoPermissionsTab tab(&profile_, app); + AppInfoPermissionsPanel panel(&profile_, app); - EXPECT_TRUE(tab.GetRequiredPermissions()->IsEmpty()); - EXPECT_TRUE(tab.GetRequiredPermissionMessages().empty()); - - EXPECT_TRUE(tab.GetOptionalPermissions()->IsEmpty()); - EXPECT_TRUE(tab.GetOptionalPermissionMessages().empty()); - - EXPECT_TRUE(tab.GetRetainedFilePermissions().empty()); - EXPECT_TRUE(tab.GetRetainedFilePermissionMessages().empty()); + EXPECT_TRUE(panel.GetActivePermissionMessages().empty()); + EXPECT_TRUE(panel.GetRetainedFilePaths().empty()); } // Tests that an app's required permissions are detected and converted to // messages correctly. -TEST_F(AppInfoPermissionsTabTest, RequiredPermissionsObtainedCorrectly) { +TEST_F(AppInfoPermissionsPanelTest, RequiredPermissionsObtainedCorrectly) { scoped_refptr<const extensions::Extension> app = extensions::ExtensionBuilder() .SetManifest(ValidAppManifest()) @@ -92,29 +86,21 @@ TEST_F(AppInfoPermissionsTabTest, RequiredPermissionsObtainedCorrectly) { // a message .SetID(kTestExtensionId) .Build(); - AppInfoPermissionsTab tab(&profile_, app); - - const extensions::PermissionSet* required_permissions = - tab.GetRequiredPermissions(); - EXPECT_FALSE(required_permissions->IsEmpty()); - EXPECT_EQ(3U, required_permissions->GetAPIsAsStrings().size()); + AppInfoPermissionsPanel panel(&profile_, app); - EXPECT_TRUE(tab.GetOptionalPermissions()->IsEmpty()); - EXPECT_TRUE(tab.GetRetainedFilePermissions().empty()); - - const std::vector<base::string16> required_permission_messages = - tab.GetRequiredPermissionMessages(); - ASSERT_EQ(2U, required_permission_messages.size()); + const std::vector<base::string16> permission_messages = + panel.GetActivePermissionMessages(); + ASSERT_EQ(2U, permission_messages.size()); EXPECT_EQ( l10n_util::GetStringUTF8(IDS_EXTENSION_PROMPT_WARNING_DESKTOP_CAPTURE), - base::UTF16ToUTF8(required_permission_messages[0])); + base::UTF16ToUTF8(permission_messages[0])); EXPECT_EQ(l10n_util::GetStringUTF8(IDS_EXTENSION_PROMPT_WARNING_SERIAL), - base::UTF16ToUTF8(required_permission_messages[1])); + base::UTF16ToUTF8(permission_messages[1])); } // Tests that an app's optional permissions are detected and converted to // messages correctly. -TEST_F(AppInfoPermissionsTabTest, OptionalPermissionsObtainedCorrectly) { +TEST_F(AppInfoPermissionsPanelTest, OptionalPermissionsObtainedCorrectly) { scoped_refptr<const extensions::Extension> app = extensions::ExtensionBuilder() .SetManifest(ValidAppManifest()) @@ -130,28 +116,18 @@ TEST_F(AppInfoPermissionsTabTest, OptionalPermissionsObtainedCorrectly) { // a message .SetID(kTestExtensionId) .Build(); - AppInfoPermissionsTab tab(&profile_, app); - - const extensions::PermissionSet* optional_permissions = - tab.GetOptionalPermissions(); - EXPECT_FALSE(optional_permissions->IsEmpty()); - EXPECT_EQ(3U, optional_permissions->GetAPIsAsStrings().size()); + AppInfoPermissionsPanel panel(&profile_, app); - EXPECT_TRUE(tab.GetRequiredPermissions()->IsEmpty()); - EXPECT_TRUE(tab.GetRetainedFilePermissions().empty()); - - const std::vector<base::string16> optional_permission_messages = - tab.GetOptionalPermissionMessages(); - ASSERT_EQ(2U, optional_permission_messages.size()); - EXPECT_EQ(l10n_util::GetStringUTF8(IDS_EXTENSION_PROMPT_WARNING_CLIPBOARD), - base::UTF16ToUTF8(optional_permission_messages[0])); - EXPECT_EQ(l10n_util::GetStringUTF8(IDS_EXTENSION_PROMPT_WARNING_SERIAL), - base::UTF16ToUTF8(optional_permission_messages[1])); + // Optional permissions don't appear until they are 'activated' at runtime. + // TODO(sashab): Activate the optional permissions and ensure they are + // successfully added to the dialog. + EXPECT_TRUE(panel.GetActivePermissionMessages().empty()); + EXPECT_TRUE(panel.GetRetainedFilePaths().empty()); } // Tests that an app's retained files are detected and converted to paths // correctly. -TEST_F(AppInfoPermissionsTabTest, RetainedFilePermissionsObtainedCorrectly) { +TEST_F(AppInfoPermissionsPanelTest, RetainedFilePermissionsObtainedCorrectly) { scoped_refptr<const extensions::Extension> app = extensions::ExtensionBuilder() .SetManifest(ValidAppManifest()) @@ -163,8 +139,7 @@ TEST_F(AppInfoPermissionsTabTest, RetainedFilePermissionsObtainedCorrectly) { extensions::ListBuilder().Append("retainEntries"))))) .SetID(kTestExtensionId) .Build(); - AppInfoPermissionsTab tab(&profile_, app); - + AppInfoPermissionsPanel panel(&profile_, app); apps::SavedFilesService* files_service = apps::SavedFilesService::Get(&profile_); files_service->RegisterFileEntry( @@ -174,42 +149,19 @@ TEST_F(AppInfoPermissionsTabTest, RetainedFilePermissionsObtainedCorrectly) { files_service->RegisterFileEntry( app->id(), "file_id_3", FilePath(FILE_PATH_LITERAL("file_3.ext")), false); - // There should be 2 required permissions: fileSystem and - // fileSystem.retainEntries. - const extensions::PermissionSet* required_permissions = - tab.GetRequiredPermissions(); - EXPECT_FALSE(required_permissions->IsEmpty()); - EXPECT_EQ(2U, required_permissions->GetAPIsAsStrings().size()); - - EXPECT_TRUE(tab.GetOptionalPermissions()->IsEmpty()); - - // Convert the list of FilePaths into a list of StringTypes for comparisons - // using Contains. - const std::vector<FilePath> retained_files = tab.GetRetainedFilePermissions(); - std::vector<FilePath::StringType> retained_file_paths; - for (size_t i = 0; i < retained_files.size(); ++i) - retained_file_paths.push_back(retained_files[i].value()); + const std::vector<base::string16> permission_messages = + panel.GetActivePermissionMessages(); + ASSERT_TRUE(permission_messages.empty()); // Since we have no guarantees on the order of retained files, make sure the // list is the expected length and all required entries are present. - ASSERT_EQ(3U, retained_files.size()); - EXPECT_THAT( - retained_file_paths, - Contains(Eq(FilePath::StringType(FILE_PATH_LITERAL("file_1.ext"))))); - EXPECT_THAT( - retained_file_paths, - Contains(Eq(FilePath::StringType(FILE_PATH_LITERAL("file_2.ext"))))); - EXPECT_THAT( - retained_file_paths, - Contains(Eq(FilePath::StringType(FILE_PATH_LITERAL("file_3.ext"))))); - - const std::vector<base::string16> retained_file_messages = - tab.GetRetainedFilePermissionMessages(); - ASSERT_EQ(3U, retained_file_messages.size()); - EXPECT_THAT(retained_file_messages, + const std::vector<base::string16> retained_file_paths = + panel.GetRetainedFilePaths(); + ASSERT_EQ(3U, retained_file_paths.size()); + EXPECT_THAT(retained_file_paths, Contains(Eq(base::UTF8ToUTF16("file_1.ext")))); - EXPECT_THAT(retained_file_messages, + EXPECT_THAT(retained_file_paths, Contains(Eq(base::UTF8ToUTF16("file_2.ext")))); - EXPECT_THAT(retained_file_messages, + EXPECT_THAT(retained_file_paths, Contains(Eq(base::UTF8ToUTF16("file_3.ext")))); } diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_tab.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_tab.cc deleted file mode 100644 index 8703438..0000000 --- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_tab.cc +++ /dev/null @@ -1,491 +0,0 @@ -// Copyright 2014 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 "chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_tab.h" - -#include "apps/app_load_service.h" -#include "apps/app_restore_service.h" -#include "apps/saved_files_service.h" -#include "base/files/file_path.h" -#include "base/strings/string_number_conversions.h" -#include "base/strings/utf_string_conversions.h" -#include "extensions/common/extension.h" -#include "extensions/common/manifest_handlers/permissions_parser.h" -#include "extensions/common/permissions/api_permission.h" -#include "extensions/common/permissions/permission_message_provider.h" -#include "extensions/common/permissions/permissions_data.h" -#include "grit/generated_resources.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.h" -#include "ui/gfx/animation/animation_delegate.h" -#include "ui/gfx/animation/slide_animation.h" -#include "ui/gfx/native_widget_types.h" -#include "ui/gfx/text_constants.h" -#include "ui/views/controls/button/button.h" -#include "ui/views/controls/button/image_button.h" -#include "ui/views/controls/button/label_button.h" -#include "ui/views/controls/label.h" -#include "ui/views/controls/scroll_view.h" -#include "ui/views/layout/box_layout.h" -#include "ui/views/layout/fill_layout.h" -#include "ui/views/layout/grid_layout.h" -#include "ui/views/layout/layout_constants.h" -#include "ui/views/widget/widget.h" - -namespace { - -// A view to display a title with an expandable permissions list section and an -// optional 'revoke' button below the list. -class ExpandableContainerView : public views::View, - public views::ButtonListener, - public gfx::AnimationDelegate { - public: - ExpandableContainerView( - views::View* owner, - const base::string16& title, - const std::vector<base::string16>& permission_messages, - views::Button* button); - virtual ~ExpandableContainerView(); - - // views::View: - virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE; - - // views::ButtonListener: - virtual void ButtonPressed(views::Button* sender, - const ui::Event& event) OVERRIDE; - - // gfx::AnimationDelegate: - virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE; - virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE; - - // Expand/Collapse the detail section for this ExpandableContainerView. - void ToggleDetailLevel(); - - private: - // A view which displays the permission messages as a bulleted list, with an - // optional button underneath (such as to revoke the permissions). |button| - // may be NULL. - class DetailsView : public views::View { - public: - explicit DetailsView(const std::vector<base::string16>& messages, - views::Button* button); - virtual ~DetailsView() {} - - // views::View: - virtual gfx::Size GetPreferredSize() const OVERRIDE; - - // Animates this to be a height proportional to |ratio|. - void AnimateToRatio(double ratio); - - private: - // The current state of the animation, as a decimal from 0 to 1 (0 is fully - // collapsed, 1 is fully expanded). - double visible_ratio_; - - DISALLOW_COPY_AND_ASSIGN(DetailsView); - }; - - // The dialog that owns |this|. It's also an ancestor in the View hierarchy. - views::View* owner_; - - // A view for showing |permission_messages|. - DetailsView* details_view_; - - gfx::SlideAnimation slide_animation_; - - // The up/down arrow next to the heading (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); -}; - -ExpandableContainerView::DetailsView::DetailsView( - const std::vector<base::string16>& messages, - views::Button* button) - : visible_ratio_(0) { - views::GridLayout* layout = new views::GridLayout(this); - SetLayoutManager(layout); - - // Create 2 columns: one for the bullet, one for the bullet text. Also inset - // the whole bulleted list by kPanelHorizMargin on either side. - static const int kColumnSet = 1; - views::ColumnSet* column_set = layout->AddColumnSet(kColumnSet); - column_set->AddPaddingColumn(0, views::kPanelHorizMargin); - column_set->AddColumn(views::GridLayout::LEADING, - views::GridLayout::LEADING, - 1, - views::GridLayout::USE_PREF, - 0, - 0); - column_set->AddPaddingColumn(0, 5); - column_set->AddColumn(views::GridLayout::LEADING, - views::GridLayout::LEADING, - 1, - views::GridLayout::USE_PREF, - 0, - 0); - column_set->AddPaddingColumn(0, views::kPanelHorizMargin); - - // Create a right-aligned column (just for the button at the bottom) that - // aligns all the way to the right of the view. - static const int kButtonColumnSet = 2; - views::ColumnSet* button_column_set = layout->AddColumnSet(kButtonColumnSet); - button_column_set->AddColumn(views::GridLayout::TRAILING, - views::GridLayout::LEADING, - 1, - views::GridLayout::USE_PREF, - 0, - 0); - - // Add padding above the permissions. - layout->AddPaddingRow(0, views::kPanelVertMargin); - - // Add the permissions. - for (std::vector<base::string16>::const_iterator it = messages.begin(); - it != messages.end(); - ++it) { - views::Label* permission_label = new views::Label(*it); - - permission_label->SetMultiLine(true); - permission_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); - - // Add a row of padding before every item except the first. - if (it != messages.begin()) { - layout->AddPaddingRow(0, views::kRelatedControlSmallVerticalSpacing); - } - - layout->StartRow(0, kColumnSet); - // Extract only the bullet from the IDS_EXTENSION_PERMISSION_LINE text. - layout->AddView(new views::Label(l10n_util::GetStringFUTF16( - IDS_EXTENSION_PERMISSION_LINE, base::string16()))); - // Place the text second, so multi-lined permissions line up below the - // bullet. - layout->AddView(permission_label); - } - - // Add the button, if one was provided. - if (button) { - layout->AddPaddingRow(0, views::kUnrelatedControlHorizontalSpacing); - layout->StartRow(0, kButtonColumnSet); - layout->AddView(button); - } - - // Add the bottom padding. - layout->AddPaddingRow(0, views::kPanelVertMargin); -} - -gfx::Size ExpandableContainerView::DetailsView::GetPreferredSize() const { - gfx::Size size = views::View::GetPreferredSize(); - return gfx::Size(size.width(), size.height() * visible_ratio_); -} - -void ExpandableContainerView::DetailsView::AnimateToRatio(double ratio) { - visible_ratio_ = ratio; - PreferredSizeChanged(); - SchedulePaint(); -} - -ExpandableContainerView::ExpandableContainerView( - views::View* owner, - const base::string16& title, - const std::vector<base::string16>& permission_messages, - views::Button* button) - : owner_(owner), - details_view_(NULL), - slide_animation_(this), - arrow_toggle_(NULL), - expanded_(false) { - views::GridLayout* layout = new views::GridLayout(this); - SetLayoutManager(layout); - const int kMainColumnSetId = 0; - views::ColumnSet* column_set = layout->AddColumnSet(kMainColumnSetId); - column_set->AddColumn(views::GridLayout::LEADING, - views::GridLayout::LEADING, - 1, - views::GridLayout::USE_PREF, - 0, - 0); - - // A column set that is split in half, to allow for the expand/collapse button - // image to be aligned to the right of the view. - const int kSplitColumnSetId = 1; - views::ColumnSet* split_column_set = layout->AddColumnSet(kSplitColumnSetId); - split_column_set->AddColumn(views::GridLayout::LEADING, - views::GridLayout::LEADING, - 1, - views::GridLayout::USE_PREF, - 0, - 0); - split_column_set->AddPaddingColumn(0, - views::kRelatedControlHorizontalSpacing); - split_column_set->AddColumn(views::GridLayout::TRAILING, - views::GridLayout::LEADING, - 1, - views::GridLayout::USE_PREF, - 0, - 0); - - // To display the heading and count next to each other, create a sub-view - // with a box layout that stacks them horizontally. - views::View* title_view = new views::View(); - title_view->SetLayoutManager( - new views::BoxLayout(views::BoxLayout::kHorizontal, - 0, - 0, - views::kRelatedControlSmallHorizontalSpacing)); - - ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); - - // Format the title as 'Title - number'. Unfortunately, this needs to be in - // separate views because labels only support a single font per view. - title_view->AddChildView( - new views::Label(title, rb.GetFontList(ui::ResourceBundle::BoldFont))); - title_view->AddChildView( - new views::Label(base::UTF8ToUTF16("\xe2\x80\x93"))); // En-dash. - title_view->AddChildView( - new views::Label(base::IntToString16(permission_messages.size()))); - - arrow_toggle_ = new views::ImageButton(this); - arrow_toggle_->SetImage(views::Button::STATE_NORMAL, - rb.GetImageSkiaNamed(IDR_DOWN_ARROW)); - - layout->StartRow(0, kSplitColumnSetId); - layout->AddView(title_view); - layout->AddView(arrow_toggle_); - - details_view_ = new DetailsView(permission_messages, button); - layout->StartRow(0, kMainColumnSetId); - layout->AddView(details_view_); -} - -ExpandableContainerView::~ExpandableContainerView() { -} - -void ExpandableContainerView::ButtonPressed(views::Button* sender, - const ui::Event& event) { - ToggleDetailLevel(); -} - -void ExpandableContainerView::AnimationProgressed( - const gfx::Animation* animation) { - DCHECK_EQ(&slide_animation_, animation); - if (details_view_) { - details_view_->AnimateToRatio(animation->GetCurrentValue()); - } -} - -void ExpandableContainerView::AnimationEnded(const gfx::Animation* animation) { - if (arrow_toggle_) { - 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)); - } - } -} - -void ExpandableContainerView::ChildPreferredSizeChanged(views::View* child) { - owner_->Layout(); -} - -void ExpandableContainerView::ToggleDetailLevel() { - expanded_ = !expanded_; - - if (slide_animation_.IsShowing()) - slide_animation_.Hide(); - else - slide_animation_.Show(); -} - -} // namespace - -AppInfoPermissionsTab::AppInfoPermissionsTab(Profile* profile, - const extensions::Extension* app) - : AppInfoTab(profile, app), revoke_file_permissions_button_(NULL) { - this->SetLayoutManager(new views::FillLayout); - - // Create a scrollview and add it to the tab. - views::View* scrollable_content = new views::View(); - scroll_view_ = new views::ScrollView(); - scroll_view_->SetContents(scrollable_content); - AddChildView(scroll_view_); - - // Give the inner scrollview (the 'scrollable' part) a layout. - views::GridLayout* layout = - views::GridLayout::CreatePanel(scrollable_content); - scrollable_content->SetLayoutManager(layout); - - // Main column that stretches to the width of the view. - static const int kMainColumnSetId = 0; - views::ColumnSet* main_column_set = layout->AddColumnSet(kMainColumnSetId); - main_column_set->AddColumn( - views::GridLayout::FILL, - views::GridLayout::FILL, - 1, // This column resizes to the width of the dialog. - views::GridLayout::USE_PREF, - 0, - 0); - - const std::vector<base::string16> required_permission_messages = - GetRequiredPermissionMessages(); - const std::vector<base::string16> optional_permission_messages = - GetOptionalPermissionMessages(); - const std::vector<base::string16> retained_file_permission_messages = - GetRetainedFilePermissionMessages(); - - layout->AddPaddingRow(1, views::kButtonVEdgeMargin); - if (required_permission_messages.empty() && - optional_permission_messages.empty() && - retained_file_permission_messages.empty()) { - // If there are no permissions at all, display an appropriate message. - views::Label* no_permissions_text = new views::Label( - l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_NO_PERMISSIONS_TEXT)); - no_permissions_text->SetHorizontalAlignment(gfx::ALIGN_LEFT); - - layout->StartRow(0, kMainColumnSetId); - layout->AddView(no_permissions_text); - } else { - if (!required_permission_messages.empty()) { - ExpandableContainerView* details_container = new ExpandableContainerView( - this, - l10n_util::GetStringUTF16( - IDS_APPLICATION_INFO_REQUIRED_PERMISSIONS_TEXT), - required_permission_messages, - NULL); - // Required permissions are visible by default. - details_container->ToggleDetailLevel(); - - layout->StartRow(0, kMainColumnSetId); - layout->AddView(details_container); - layout->AddPaddingRow(0, views::kRelatedControlHorizontalSpacing); - } - - if (!optional_permission_messages.empty()) { - ExpandableContainerView* details_container = new ExpandableContainerView( - this, - l10n_util::GetStringUTF16( - IDS_APPLICATION_INFO_OPTIONAL_PERMISSIONS_TEXT), - optional_permission_messages, - NULL); - - layout->StartRow(0, kMainColumnSetId); - layout->AddView(details_container); - layout->AddPaddingRow(0, views::kRelatedControlHorizontalSpacing); - } - - if (!retained_file_permission_messages.empty()) { - revoke_file_permissions_button_ = new views::LabelButton( - this, - l10n_util::GetStringUTF16( - IDS_APPLICATION_INFO_REVOKE_RETAINED_FILE_PERMISSIONS_BUTTON_TEXT)); - revoke_file_permissions_button_->SetStyle(views::Button::STYLE_BUTTON); - - ExpandableContainerView* details_container = new ExpandableContainerView( - this, - l10n_util::GetStringUTF16( - IDS_APPLICATION_INFO_RETAINED_FILE_PERMISSIONS_TEXT), - retained_file_permission_messages, - revoke_file_permissions_button_); - - layout->StartRow(0, kMainColumnSetId); - layout->AddView(details_container); - layout->AddPaddingRow(0, views::kRelatedControlHorizontalSpacing); - } - } -} - -AppInfoPermissionsTab::~AppInfoPermissionsTab() { -} - -void AppInfoPermissionsTab::Layout() { - // To avoid 'jumping' issues when the scrollbar becomes visible, size the - // scrollable area as though it always has a visible scrollbar. - views::View* contents_view = scroll_view_->contents(); - int content_width = width() - scroll_view_->GetScrollBarWidth(); - int content_height = contents_view->GetHeightForWidth(content_width); - contents_view->SetBounds(0, 0, content_width, content_height); - scroll_view_->SetBounds(0, 0, width(), height()); -} - -void AppInfoPermissionsTab::ButtonPressed(views::Button* sender, - const ui::Event& event) { - if (sender == revoke_file_permissions_button_) - RevokeFilePermissions(); - else - NOTREACHED(); -} - -void AppInfoPermissionsTab::RevokeFilePermissions() { - apps::SavedFilesService::Get(profile_)->ClearQueue(app_); - - // TODO(benwells): Fix this to call something like - // AppLoadService::RestartApplicationIfRunning. - if (apps::AppRestoreService::Get(profile_)->IsAppRestorable(app_->id())) - apps::AppLoadService::Get(profile_)->RestartApplication(app_->id()); - - GetWidget()->Close(); -} - -const extensions::PermissionSet* AppInfoPermissionsTab::GetRequiredPermissions() - const { - return extensions::PermissionsParser::GetRequiredPermissions(app_); -} - -const std::vector<base::string16> -AppInfoPermissionsTab::GetRequiredPermissionMessages() const { - return extensions::PermissionMessageProvider::Get()->GetWarningMessages( - GetRequiredPermissions(), app_->GetType()); -} - -const extensions::PermissionSet* AppInfoPermissionsTab::GetOptionalPermissions() - const { - return extensions::PermissionsParser::GetOptionalPermissions(app_); -} - -const std::vector<base::string16> -AppInfoPermissionsTab::GetOptionalPermissionMessages() const { - return extensions::PermissionMessageProvider::Get()->GetWarningMessages( - GetOptionalPermissions(), app_->GetType()); -} - -const std::vector<base::FilePath> -AppInfoPermissionsTab::GetRetainedFilePermissions() const { - std::vector<base::FilePath> retained_file_paths; - if (app_->permissions_data()->HasAPIPermission( - extensions::APIPermission::kFileSystem)) { - std::vector<apps::SavedFileEntry> retained_file_entries = - apps::SavedFilesService::Get(profile_)->GetAllFileEntries(app_->id()); - for (std::vector<apps::SavedFileEntry>::const_iterator it = - retained_file_entries.begin(); - it != retained_file_entries.end(); - ++it) { - retained_file_paths.push_back(it->path); - } - } - return retained_file_paths; -} - -const std::vector<base::string16> -AppInfoPermissionsTab::GetRetainedFilePermissionMessages() const { - const std::vector<base::FilePath> permissions = GetRetainedFilePermissions(); - std::vector<base::string16> file_permission_messages; - for (std::vector<base::FilePath>::const_iterator it = permissions.begin(); - it != permissions.end(); - ++it) { - file_permission_messages.push_back(it->LossyDisplayName()); - } - return file_permission_messages; -} diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_tab.h b/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_tab.h deleted file mode 100644 index a4d8b0f..0000000 --- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_tab.h +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2014 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. - -#ifndef CHROME_BROWSER_UI_VIEWS_APPS_APP_INFO_DIALOG_APP_INFO_PERMISSIONS_TAB_H_ -#define CHROME_BROWSER_UI_VIEWS_APPS_APP_INFO_DIALOG_APP_INFO_PERMISSIONS_TAB_H_ - -#include "chrome/browser/ui/views/apps/app_info_dialog/app_info_tab.h" -#include "ui/views/controls/button/button.h" - -class Profile; - -namespace extensions { -class Extension; -class PermissionSet; -} -namespace ui { -class Event; -} -namespace views { -class LabelButton; -class ScrollView; -} - -// The Permissions tab of the app info dialog, which provides insight and -// control over the app's various permissions. -class AppInfoPermissionsTab : public AppInfoTab, views::ButtonListener { - public: - AppInfoPermissionsTab(Profile* profile, const extensions::Extension* app); - - virtual ~AppInfoPermissionsTab(); - - private: - FRIEND_TEST_ALL_PREFIXES(AppInfoPermissionsTabTest, - NoPermissionsObtainedCorrectly); - FRIEND_TEST_ALL_PREFIXES(AppInfoPermissionsTabTest, - RequiredPermissionsObtainedCorrectly); - FRIEND_TEST_ALL_PREFIXES(AppInfoPermissionsTabTest, - OptionalPermissionsObtainedCorrectly); - FRIEND_TEST_ALL_PREFIXES(AppInfoPermissionsTabTest, - RetainedFilePermissionsObtainedCorrectly); - - // Overridden from views::View: - virtual void Layout() OVERRIDE; - - // Overridden from views::ButtonListener: - virtual void ButtonPressed(views::Button* sender, - const ui::Event& event) OVERRIDE; - - // Clears all retained file permissions for this app, restarts the app and - // closes this dialog. - void RevokeFilePermissions(); - - const extensions::PermissionSet* GetRequiredPermissions() const; - const std::vector<base::string16> GetRequiredPermissionMessages() const; - - const extensions::PermissionSet* GetOptionalPermissions() const; - const std::vector<base::string16> GetOptionalPermissionMessages() const; - - const std::vector<base::FilePath> GetRetainedFilePermissions() const; - const std::vector<base::string16> GetRetainedFilePermissionMessages() const; - - views::ScrollView* scroll_view_; - views::LabelButton* revoke_file_permissions_button_; - - DISALLOW_COPY_AND_ASSIGN(AppInfoPermissionsTab); -}; - -#endif // CHROME_BROWSER_UI_VIEWS_APPS_APP_INFO_DIALOG_APP_INFO_PERMISSIONS_TAB_H_ diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_summary_panel.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_summary_panel.cc new file mode 100644 index 0000000..1f04cba6 --- /dev/null +++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_summary_panel.cc @@ -0,0 +1,271 @@ +// Copyright 2014 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 "chrome/browser/ui/views/apps/app_info_dialog/app_info_summary_panel.h" + +#include <vector> + +#include "base/callback_forward.h" +#include "base/command_line.h" +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/launch_util.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser_dialogs.h" +#include "chrome/common/chrome_switches.h" +#include "extensions/browser/extension_prefs.h" +#include "extensions/browser/extension_system.h" +#include "extensions/common/extension.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/models/combobox_model.h" +#include "ui/views/controls/button/button.h" +#include "ui/views/controls/button/label_button.h" +#include "ui/views/controls/combobox/combobox.h" +#include "ui/views/controls/label.h" +#include "ui/views/layout/box_layout.h" +#include "ui/views/layout/layout_constants.h" +#include "ui/views/view.h" +#include "ui/views/widget/widget.h" + +// A model for a combobox selecting the launch options for a hosted app. +// Displays different options depending on the host OS. +class LaunchOptionsComboboxModel : public ui::ComboboxModel { + public: + LaunchOptionsComboboxModel(); + virtual ~LaunchOptionsComboboxModel(); + + extensions::LaunchType GetLaunchTypeAtIndex(int index) const; + int GetIndexForLaunchType(extensions::LaunchType launch_type) const; + + // Overridden from ui::ComboboxModel: + virtual int GetItemCount() const OVERRIDE; + virtual base::string16 GetItemAt(int index) OVERRIDE; + + private: + // A list of the launch types available in the combobox, in order. + std::vector<extensions::LaunchType> launch_types_; + + // A list of the messages to display in the combobox, in order. The indexes in + // this list correspond to the indexes in launch_types_. + std::vector<base::string16> launch_type_messages_; +}; + +LaunchOptionsComboboxModel::LaunchOptionsComboboxModel() { + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableStreamlinedHostedApps)) { + // Streamlined hosted apps can only toggle between LAUNCH_TYPE_WINDOW and + // LAUNCH_TYPE_REGULAR. + // TODO(sashab): Use a checkbox for this choice instead of combobox. + launch_types_.push_back(extensions::LAUNCH_TYPE_REGULAR); + launch_type_messages_.push_back( + l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_TAB)); + + // Although LAUNCH_TYPE_WINDOW doesn't work on Mac, the streamlined hosted + // apps flag isn't available on Mac, so we must be on a non-Mac OS. + launch_types_.push_back(extensions::LAUNCH_TYPE_WINDOW); + launch_type_messages_.push_back( + l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_WINDOW)); + } else { + launch_types_.push_back(extensions::LAUNCH_TYPE_REGULAR); + launch_type_messages_.push_back( + l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_REGULAR)); + + launch_types_.push_back(extensions::LAUNCH_TYPE_PINNED); + launch_type_messages_.push_back( + l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_PINNED)); + +#if defined(OS_MACOSX) + // Mac does not support standalone web app browser windows or maximize. + launch_types_.push_back(extensions::LAUNCH_TYPE_FULLSCREEN); + launch_type_messages_.push_back( + l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_FULLSCREEN)); +#else + launch_types_.push_back(extensions::LAUNCH_TYPE_WINDOW); + launch_type_messages_.push_back( + l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_WINDOW)); + + // Even though the launch type is Full Screen, it is more accurately + // described as Maximized in non-Mac OSs. + launch_types_.push_back(extensions::LAUNCH_TYPE_FULLSCREEN); + launch_type_messages_.push_back( + l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_MAXIMIZED)); +#endif + } +} + +LaunchOptionsComboboxModel::~LaunchOptionsComboboxModel() { +} + +extensions::LaunchType LaunchOptionsComboboxModel::GetLaunchTypeAtIndex( + int index) const { + return launch_types_[index]; +} + +int LaunchOptionsComboboxModel::GetIndexForLaunchType( + extensions::LaunchType launch_type) const { + for (size_t i = 0; i < launch_types_.size(); i++) { + if (launch_types_[i] == launch_type) { + return i; + } + } + // If the requested launch type is not available, just select the first one. + LOG(WARNING) << "Unavailable launch type " << launch_type << " selected."; + return 0; +} + +int LaunchOptionsComboboxModel::GetItemCount() const { + return launch_types_.size(); +} + +base::string16 LaunchOptionsComboboxModel::GetItemAt(int index) { + return launch_type_messages_[index]; +} + +AppInfoSummaryPanel::AppInfoSummaryPanel(Profile* profile, + const extensions::Extension* app) + : AppInfoPanel(profile, app), + description_heading_(NULL), + description_label_(NULL), + launch_options_combobox_(NULL) { + // Create UI elements. + CreateDescriptionControl(); + CreateLaunchOptionControl(); + CreateShortcutsButton(); + + // Layout elements. + SetLayoutManager( + new views::BoxLayout(views::BoxLayout::kVertical, + 0, + 0, + views::kUnrelatedControlVerticalSpacing)); + + LayoutDescriptionControl(); + + if (launch_options_combobox_) + AddChildView(launch_options_combobox_); + + LayoutShortcutsButton(); +} + +AppInfoSummaryPanel::~AppInfoSummaryPanel() { + // Destroy view children before their models. + RemoveAllChildViews(true); +} + +void AppInfoSummaryPanel::CreateDescriptionControl() { + if (!app_->description().empty()) { + const size_t kMaxLength = 400; + + base::string16 text = base::UTF8ToUTF16(app_->description()); + if (text.length() > kMaxLength) { + text = text.substr(0, kMaxLength); + text += base::ASCIIToUTF16(" ... "); + } + + description_heading_ = CreateHeading( + l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_DESCRIPTION_TITLE)); + description_label_ = new views::Label(text); + description_label_->SetMultiLine(true); + description_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); + } +} + +void AppInfoSummaryPanel::CreateLaunchOptionControl() { + if (CanSetLaunchType()) { + launch_options_combobox_model_.reset(new LaunchOptionsComboboxModel()); + launch_options_combobox_ = + new views::Combobox(launch_options_combobox_model_.get()); + + launch_options_combobox_->set_listener(this); + launch_options_combobox_->SetSelectedIndex( + launch_options_combobox_model_->GetIndexForLaunchType(GetLaunchType())); + } +} + +void AppInfoSummaryPanel::CreateShortcutsButton() { + if (CanCreateShortcuts()) { + create_shortcuts_button_ = new views::LabelButton( + this, + l10n_util::GetStringUTF16( + IDS_APPLICATION_INFO_CREATE_SHORTCUTS_BUTTON_TEXT)); + create_shortcuts_button_->SetStyle(views::Button::STYLE_BUTTON); + } +} + +void AppInfoSummaryPanel::LayoutDescriptionControl() { + if (description_label_) { + DCHECK(description_heading_); + views::View* vertical_stack = CreateVerticalStack(); + vertical_stack->AddChildView(description_heading_); + vertical_stack->AddChildView(description_label_); + AddChildView(vertical_stack); + } +} + +void AppInfoSummaryPanel::LayoutShortcutsButton() { + if (create_shortcuts_button_) { + // Add a sub-view so the shortcuts button is left-aligned. + views::View* left_aligned_button = new views::View(); + left_aligned_button->SetLayoutManager( + new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0)); + left_aligned_button->AddChildView(create_shortcuts_button_); + + AddChildView(left_aligned_button); + } +} + +void AppInfoSummaryPanel::OnPerformAction(views::Combobox* combobox) { + if (combobox == launch_options_combobox_) { + SetLaunchType(launch_options_combobox_model_->GetLaunchTypeAtIndex( + launch_options_combobox_->selected_index())); + } else { + NOTREACHED(); + } +} + +void AppInfoSummaryPanel::ButtonPressed(views::Button* sender, + const ui::Event& event) { + if (sender == create_shortcuts_button_) { + CreateShortcuts(); + } else { + NOTREACHED(); + } +} + +extensions::LaunchType AppInfoSummaryPanel::GetLaunchType() const { + return extensions::GetLaunchType(extensions::ExtensionPrefs::Get(profile_), + app_); +} + +void AppInfoSummaryPanel::SetLaunchType( + extensions::LaunchType launch_type) const { + DCHECK(CanSetLaunchType()); + ExtensionService* service = + extensions::ExtensionSystem::Get(profile_)->extension_service(); + extensions::SetLaunchType(service, app_->id(), launch_type); +} + +bool AppInfoSummaryPanel::CanSetLaunchType() const { + // V2 apps don't have a launch type. + return !app_->is_platform_app(); +} + +void AppInfoSummaryPanel::CreateShortcuts() { + DCHECK(CanCreateShortcuts()); + chrome::ShowCreateChromeAppShortcutsDialog(GetWidget()->GetNativeWindow(), + profile_, + app_, + base::Callback<void(bool)>()); +} + +bool AppInfoSummaryPanel::CanCreateShortcuts() const { +// ChromeOS can pin apps to the app launcher, but can't create shortcuts. +#if defined(OS_CHROMEOS) + return false; +#else + return true; +#endif +} diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_summary_tab.h b/chrome/browser/ui/views/apps/app_info_dialog/app_info_summary_panel.h index 109602ab..8861441 100644 --- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_summary_tab.h +++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_summary_panel.h @@ -2,54 +2,50 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_UI_VIEWS_APPS_APP_INFO_DIALOG_APP_INFO_SUMMARY_TAB_H_ -#define CHROME_BROWSER_UI_VIEWS_APPS_APP_INFO_DIALOG_APP_INFO_SUMMARY_TAB_H_ +#ifndef CHROME_BROWSER_UI_VIEWS_APPS_APP_INFO_DIALOG_APP_INFO_SUMMARY_PANEL_H_ +#define CHROME_BROWSER_UI_VIEWS_APPS_APP_INFO_DIALOG_APP_INFO_SUMMARY_PANEL_H_ -#include "chrome/browser/extensions/extension_uninstall_dialog.h" -#include "chrome/browser/ui/views/apps/app_info_dialog/app_info_tab.h" +#include "base/memory/scoped_ptr.h" +#include "chrome/browser/ui/views/apps/app_info_dialog/app_info_panel.h" #include "chrome/common/extensions/extension_constants.h" #include "ui/views/controls/button/button.h" #include "ui/views/controls/combobox/combobox_listener.h" +class LaunchOptionsComboboxModel; class Profile; namespace extensions { class Extension; } -namespace gfx { -class Image; -} + namespace ui { class Event; } + namespace views { class Combobox; -class ImageView; class Label; class LabelButton; } -class LaunchOptionsComboboxModel; - -// The Summary tab of the app info dialog, which provides basic information and -// controls related to the app. -class AppInfoSummaryTab - : public AppInfoTab, - public views::ComboboxListener, - public views::ButtonListener, - public extensions::ExtensionUninstallDialog::Delegate { +// The summary panel of the app info dialog, which provides basic information +// and controls related to the app. +class AppInfoSummaryPanel : public AppInfoPanel, + public views::ComboboxListener, + public views::ButtonListener { public: - AppInfoSummaryTab(Profile* profile, const extensions::Extension* app); + AppInfoSummaryPanel(Profile* profile, const extensions::Extension* app); - virtual ~AppInfoSummaryTab(); + virtual ~AppInfoSummaryPanel(); private: // Internal initialisation methods. void CreateDescriptionControl(); void CreateLaunchOptionControl(); - void CreateButtons(); + void CreateShortcutsButton(); - void LayoutButtons(); + void LayoutDescriptionControl(); + void LayoutShortcutsButton(); // Overridden from views::ComboboxListener: virtual void OnPerformAction(views::Combobox* combobox) OVERRIDE; @@ -58,10 +54,6 @@ class AppInfoSummaryTab virtual void ButtonPressed(views::Button* sender, const ui::Event& event) OVERRIDE; - // Overridden from ExtensionUninstallDialog::Delegate. - virtual void ExtensionUninstallAccepted() OVERRIDE; - virtual void ExtensionUninstallCanceled() OVERRIDE; - // Returns the launch type of the app (e.g. pinned tab, fullscreen, etc). extensions::LaunchType GetLaunchType() const; @@ -70,29 +62,21 @@ class AppInfoSummaryTab void SetLaunchType(extensions::LaunchType) const; bool CanSetLaunchType() const; - // Uninstall the app. Must only be called if CanUninstallApp() returns true. - void UninstallApp(); - bool CanUninstallApp() const; - // Create Shortcuts for the app. Must only be called if CanCreateShortcuts() // returns true. void CreateShortcuts(); bool CanCreateShortcuts() const; - bool HasImportedModules(); - // UI elements on the dialog. - views::View* app_summary_panel_; - views::Label* app_description_label_; - views::LabelButton* create_shortcuts_button_; + views::Label* description_heading_; + views::Label* description_label_; - scoped_ptr<extensions::ExtensionUninstallDialog> extension_uninstall_dialog_; - views::LabelButton* uninstall_button_; + views::LabelButton* create_shortcuts_button_; scoped_ptr<LaunchOptionsComboboxModel> launch_options_combobox_model_; views::Combobox* launch_options_combobox_; - DISALLOW_COPY_AND_ASSIGN(AppInfoSummaryTab); + DISALLOW_COPY_AND_ASSIGN(AppInfoSummaryPanel); }; -#endif // CHROME_BROWSER_UI_VIEWS_APPS_APP_INFO_DIALOG_APP_INFO_SUMMARY_TAB_H_ +#endif // CHROME_BROWSER_UI_VIEWS_APPS_APP_INFO_DIALOG_APP_INFO_SUMMARY_PANEL_H_ diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_summary_tab.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_summary_tab.cc deleted file mode 100644 index e5edc2c..0000000 --- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_summary_tab.cc +++ /dev/null @@ -1,650 +0,0 @@ -// Copyright 2014 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 "chrome/browser/ui/views/apps/app_info_dialog/app_info_summary_tab.h" - -#include <vector> - -#include "base/bind.h" -#include "base/command_line.h" -#include "base/strings/utf_string_conversions.h" -#include "chrome/browser/extensions/extension_service.h" -#include "chrome/browser/extensions/extension_util.h" -#include "chrome/browser/extensions/image_loader.h" -#include "chrome/browser/extensions/launch_util.h" -#include "chrome/browser/profiles/profile.h" -#include "chrome/browser/ui/browser_dialogs.h" -#include "chrome/browser/ui/browser_navigator.h" -#include "chrome/common/chrome_switches.h" -#include "chrome/common/extensions/manifest_url_handler.h" -#include "extensions/browser/extension_prefs.h" -#include "extensions/browser/extension_system.h" -#include "extensions/common/constants.h" -#include "extensions/common/extension.h" -#include "extensions/common/extension_icon_set.h" -#include "extensions/common/manifest_handlers/icons_handler.h" -#include "extensions/common/manifest_handlers/shared_module_info.h" -#include "grit/generated_resources.h" -#include "net/base/url_util.h" -#include "ui/base/l10n/l10n_util.h" -#include "ui/base/models/combobox_model.h" -#include "ui/base/resource/resource_bundle.h" -#include "ui/gfx/geometry/size.h" -#include "ui/gfx/image/image.h" -#include "ui/gfx/image/image_skia.h" -#include "ui/views/controls/button/label_button.h" -#include "ui/views/controls/combobox/combobox.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/layout/box_layout.h" -#include "ui/views/layout/grid_layout.h" -#include "ui/views/layout/layout_constants.h" -#include "ui/views/widget/widget.h" -#include "url/gurl.h" - -// Size of extension icon in top left of dialog. -const int kIconSize = 64; - -namespace { - -// A small summary panel with the app's name, icon, version, and a link to the -// web store (if they exist). -class AppInfoSummaryPanel : public views::View, - public views::LinkListener, - public base::SupportsWeakPtr<AppInfoSummaryPanel> { - public: - AppInfoSummaryPanel(Profile* profile, const extensions::Extension* app); - virtual ~AppInfoSummaryPanel(); - - private: - void CreateControls(); - void LayoutAppNameAndVersionInto(views::View* parent_view); - void LayoutViews(); - - // Overridden from views::LinkListener: - virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE; - - // Load the app icon asynchronously. For the response, check OnAppImageLoaded. - void LoadAppImageAsync(); - // Called when the app's icon is loaded. - void OnAppImageLoaded(const gfx::Image& image); - - // Opens the app in the web store. Must only be called if - // CanShowAppInWebStore() returns true. - void ShowAppInWebStore() const; - bool CanShowAppInWebStore() const; - - views::ImageView* app_icon_; - views::Label* app_name_label_; - views::Label* app_version_label_; - views::Link* view_in_store_link_; - - Profile* profile_; - const extensions::Extension* app_; - - base::WeakPtrFactory<AppInfoSummaryPanel> weak_ptr_factory_; - - DISALLOW_COPY_AND_ASSIGN(AppInfoSummaryPanel); -}; - -AppInfoSummaryPanel::AppInfoSummaryPanel(Profile* profile, - const extensions::Extension* app) - : app_icon_(NULL), - app_name_label_(NULL), - app_version_label_(NULL), - view_in_store_link_(NULL), - profile_(profile), - app_(app), - weak_ptr_factory_(this) { - CreateControls(); - - // Layout elements. - SetLayoutManager( - new views::BoxLayout(views::BoxLayout::kHorizontal, - 0, - 0, - views::kRelatedControlHorizontalSpacing)); - - AddChildView(app_icon_); - - if (!app_version_label_ && !view_in_store_link_) { - // If there's no link to the webstore _and_ no version, allow the app's name - // to take up multiple lines. - app_name_label_->SetMultiLine(true); - AddChildView(app_name_label_); - } else { - // Create a vertical container to store the app's name and info. - views::View* vertical_container = new views::View(); - views::BoxLayout* vertical_container_layout = - new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0); - vertical_container_layout->set_main_axis_alignment( - views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER); - vertical_container->SetLayoutManager(vertical_container_layout); - - if (app_version_label_ && view_in_store_link_) { - // First line: title and version, Second line: web store link. - LayoutAppNameAndVersionInto(vertical_container); - vertical_container->AddChildView(view_in_store_link_); - } else { - // Put the title on the first line, and whatever other information we have - // on the second line. - vertical_container->AddChildView(app_name_label_); - - if (app_version_label_) { - vertical_container->AddChildView(app_version_label_); - } else if (view_in_store_link_) { - vertical_container->AddChildView(view_in_store_link_); - } - } - - AddChildView(vertical_container); - } -} - -AppInfoSummaryPanel::~AppInfoSummaryPanel() { -} - -void AppInfoSummaryPanel::CreateControls() { - app_name_label_ = - new views::Label(base::UTF8ToUTF16(app_->name()), - ui::ResourceBundle::GetSharedInstance().GetFontList( - ui::ResourceBundle::BoldFont)); - app_name_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); - - // The version number doesn't make sense for bookmarked apps. - if (!app_->from_bookmark()) { - app_version_label_ = - new views::Label(base::UTF8ToUTF16(app_->VersionString())); - app_version_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); - } - - app_icon_ = new views::ImageView(); - app_icon_->SetImageSize(gfx::Size(kIconSize, kIconSize)); - LoadAppImageAsync(); - - if (CanShowAppInWebStore()) { - view_in_store_link_ = new views::Link( - l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_WEB_STORE_LINK)); - view_in_store_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT); - view_in_store_link_->set_listener(this); - } -} - -void AppInfoSummaryPanel::LayoutAppNameAndVersionInto( - views::View* parent_view) { - views::View* view = new views::View(); - // We need a horizontal BoxLayout here to ensure that the GridLayout does - // not stretch beyond the size of its content. - view->SetLayoutManager( - new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0)); - - views::View* container_view = new views::View(); - view->AddChildView(container_view); - views::GridLayout* layout = new views::GridLayout(container_view); - container_view->SetLayoutManager(layout); - - static const int kColumnId = 1; - views::ColumnSet* column_set = layout->AddColumnSet(kColumnId); - column_set->AddColumn(views::GridLayout::LEADING, - views::GridLayout::LEADING, - 1, // Stretch the title to as wide as needed - views::GridLayout::USE_PREF, - 0, - 0); - column_set->AddPaddingColumn(0, views::kRelatedControlSmallHorizontalSpacing); - column_set->AddColumn(views::GridLayout::LEADING, - views::GridLayout::LEADING, - 0, // Do not stretch the version - views::GridLayout::USE_PREF, - 0, - 0); - - layout->StartRow(1, kColumnId); - layout->AddView(app_name_label_); - if (app_version_label_) - layout->AddView(app_version_label_); - - parent_view->AddChildView(view); -} - -void AppInfoSummaryPanel::LinkClicked(views::Link* source, int event_flags) { - if (source == view_in_store_link_) { - ShowAppInWebStore(); - } else { - NOTREACHED(); - } -} - -void AppInfoSummaryPanel::LoadAppImageAsync() { - extensions::ExtensionResource image = extensions::IconsInfo::GetIconResource( - app_, - extension_misc::EXTENSION_ICON_LARGE, - ExtensionIconSet::MATCH_BIGGER); - int pixel_size = - static_cast<int>(kIconSize * gfx::ImageSkia::GetMaxSupportedScale()); - extensions::ImageLoader::Get(profile_)->LoadImageAsync( - app_, - image, - gfx::Size(pixel_size, pixel_size), - base::Bind(&AppInfoSummaryPanel::OnAppImageLoaded, AsWeakPtr())); -} - -void AppInfoSummaryPanel::OnAppImageLoaded(const gfx::Image& image) { - const SkBitmap* bitmap; - if (image.IsEmpty()) { - bitmap = &extensions::util::GetDefaultAppIcon() - .GetRepresentation(gfx::ImageSkia::GetMaxSupportedScale()) - .sk_bitmap(); - } else { - bitmap = image.ToSkBitmap(); - } - - app_icon_->SetImage(gfx::ImageSkia::CreateFrom1xBitmap(*bitmap)); -} - -void AppInfoSummaryPanel::ShowAppInWebStore() const { - DCHECK(CanShowAppInWebStore()); - const GURL url = extensions::ManifestURL::GetDetailsURL(app_); - DCHECK_NE(url, GURL::EmptyGURL()); - chrome::NavigateParams params( - profile_, - net::AppendQueryParameter(url, - extension_urls::kWebstoreSourceField, - extension_urls::kLaunchSourceAppListInfoDialog), - content::PAGE_TRANSITION_LINK); - chrome::Navigate(¶ms); -} - -bool AppInfoSummaryPanel::CanShowAppInWebStore() const { - return app_->from_webstore(); -} - -// A small summary panel with a list of the app's imported modules, and a link -// to each of their about pages. -class AppInfoImportedModulesPanel : public views::View, - public views::LinkListener { - public: - AppInfoImportedModulesPanel(Profile* profile, - const extensions::Extension* app); - virtual ~AppInfoImportedModulesPanel(); - - private: - // Overridden from views::LinkListener: - virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE; - - Profile* profile_; - const extensions::Extension* app_; - std::map<views::Link*, GURL> about_links_; - - DISALLOW_COPY_AND_ASSIGN(AppInfoImportedModulesPanel); -}; - -AppInfoImportedModulesPanel::AppInfoImportedModulesPanel( - Profile* profile, - const extensions::Extension* app) - : profile_(profile), app_(app) { - // Create controls. - views::Label* title = - new views::Label(l10n_util::GetStringUTF16( - IDS_APPLICATION_INFO_IMPORTED_MODULES_TITLE_TEXT), - ui::ResourceBundle::GetSharedInstance().GetFontList( - ui::ResourceBundle::BoldFont)); - title->SetHorizontalAlignment(gfx::ALIGN_LEFT); - - // Layout elements. - views::GridLayout* layout = new views::GridLayout(this); - SetLayoutManager(layout); - - static const int kTitleColumnSetId = 1; - views::ColumnSet* title_column_set = layout->AddColumnSet(kTitleColumnSetId); - title_column_set->AddColumn(views::GridLayout::LEADING, - views::GridLayout::LEADING, - 1, // Stretch the title to as wide as needed - views::GridLayout::USE_PREF, - 0, - 0); - - static const int kModulesListColumnSetId = 2; - views::ColumnSet* column_set = layout->AddColumnSet(kModulesListColumnSetId); - column_set->AddColumn(views::GridLayout::LEADING, - views::GridLayout::LEADING, - 1, // Stretch the title to as wide as needed - views::GridLayout::USE_PREF, - 0, - 0); - column_set->AddPaddingColumn(0, views::kRelatedControlSmallHorizontalSpacing); - column_set->AddColumn(views::GridLayout::LEADING, - views::GridLayout::LEADING, - 0, // Do not stretch the 'about' link - views::GridLayout::USE_PREF, - 0, - 0); - - layout->StartRow(0, kTitleColumnSetId); - layout->AddView(title); - - // Find all the shared modules for this app, and display them in a list. - // TODO(sashab): Revisit UI layout once shared module usage becomes more - // common. - ExtensionService* service = - extensions::ExtensionSystem::Get(profile_)->extension_service(); - DCHECK(service); - const std::vector<extensions::SharedModuleInfo::ImportInfo>& imports = - extensions::SharedModuleInfo::GetImports(app_); - for (size_t i = 0; i < imports.size(); ++i) { - const extensions::Extension* imported_module = - service->GetExtensionById(imports[i].extension_id, true); - DCHECK(imported_module); - views::Label* name_label = - new views::Label(base::UTF8ToUTF16(imported_module->name())); - name_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); - - layout->StartRow(0, kModulesListColumnSetId); - layout->AddView(name_label); - - // If this app has an about page, display it as an 'about' link. - GURL about_page = extensions::ManifestURL::GetAboutPage(imported_module); - if (about_page != GURL::EmptyGURL()) { - views::Link* about_link = new views::Link(l10n_util::GetStringUTF16( - IDS_APPLICATION_INFO_IMPORTED_MODULES_ABOUT_LINK_TEXT)); - about_link->set_listener(this); - about_links_[about_link] = about_page; - layout->AddView(about_link); - } - } -} - -AppInfoImportedModulesPanel::~AppInfoImportedModulesPanel() { -} - -void AppInfoImportedModulesPanel::LinkClicked(views::Link* source, - int event_flags) { - chrome::NavigateParams params( - profile_, about_links_[source], content::PAGE_TRANSITION_LINK); - chrome::Navigate(¶ms); -} - -} // namespace - -// A model for a combobox selecting the launch options for a hosted app. -// Displays different options depending on the host OS. -class LaunchOptionsComboboxModel : public ui::ComboboxModel { - public: - LaunchOptionsComboboxModel(); - virtual ~LaunchOptionsComboboxModel(); - - extensions::LaunchType GetLaunchTypeAtIndex(int index) const; - int GetIndexForLaunchType(extensions::LaunchType launch_type) const; - - // Overridden from ui::ComboboxModel: - virtual int GetItemCount() const OVERRIDE; - virtual base::string16 GetItemAt(int index) OVERRIDE; - - private: - // A list of the launch types available in the combobox, in order. - std::vector<extensions::LaunchType> launch_types_; - - // A list of the messages to display in the combobox, in order. The indexes in - // this list correspond to the indexes in launch_types_. - std::vector<base::string16> launch_type_messages_; -}; - -LaunchOptionsComboboxModel::LaunchOptionsComboboxModel() { - if (CommandLine::ForCurrentProcess()->HasSwitch( - switches::kEnableStreamlinedHostedApps)) { - // Streamlined hosted apps can only toggle between LAUNCH_TYPE_WINDOW and - // LAUNCH_TYPE_REGULAR. - // TODO(sashab): Use a checkbox for this choice instead of combobox. - launch_types_.push_back(extensions::LAUNCH_TYPE_REGULAR); - launch_type_messages_.push_back( - l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_TAB)); - - // Although LAUNCH_TYPE_WINDOW doesn't work on Mac, the streamlined hosted - // apps flag isn't available on Mac, so we must be on a non-Mac OS. - launch_types_.push_back(extensions::LAUNCH_TYPE_WINDOW); - launch_type_messages_.push_back( - l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_WINDOW)); - } else { - launch_types_.push_back(extensions::LAUNCH_TYPE_REGULAR); - launch_type_messages_.push_back( - l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_REGULAR)); - - launch_types_.push_back(extensions::LAUNCH_TYPE_PINNED); - launch_type_messages_.push_back( - l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_PINNED)); - -#if defined(OS_MACOSX) - // Mac does not support standalone web app browser windows or maximize. - launch_types_.push_back(extensions::LAUNCH_TYPE_FULLSCREEN); - launch_type_messages_.push_back( - l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_FULLSCREEN)); -#else - launch_types_.push_back(extensions::LAUNCH_TYPE_WINDOW); - launch_type_messages_.push_back( - l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_WINDOW)); - - // Even though the launch type is Full Screen, it is more accurately - // described as Maximized in non-Mac OSs. - launch_types_.push_back(extensions::LAUNCH_TYPE_FULLSCREEN); - launch_type_messages_.push_back( - l10n_util::GetStringUTF16(IDS_APP_CONTEXT_MENU_OPEN_MAXIMIZED)); -#endif - } -} - -LaunchOptionsComboboxModel::~LaunchOptionsComboboxModel() {} - -extensions::LaunchType LaunchOptionsComboboxModel::GetLaunchTypeAtIndex( - int index) const { - return launch_types_[index]; -} - -int LaunchOptionsComboboxModel::GetIndexForLaunchType( - extensions::LaunchType launch_type) const { - for (size_t i = 0; i < launch_types_.size(); i++) { - if (launch_types_[i] == launch_type) { - return i; - } - } - // If the requested launch type is not available, just select the first one. - LOG(WARNING) << "Unavailable launch type " << launch_type << " selected."; - return 0; -} - -int LaunchOptionsComboboxModel::GetItemCount() const { - return launch_types_.size(); -} - -base::string16 LaunchOptionsComboboxModel::GetItemAt(int index) { - return launch_type_messages_[index]; -} - -AppInfoSummaryTab::AppInfoSummaryTab(Profile* profile, - const extensions::Extension* app) - : AppInfoTab(profile, app), - app_summary_panel_(NULL), - app_description_label_(NULL), - create_shortcuts_button_(NULL), - uninstall_button_(NULL), - launch_options_combobox_(NULL) { - // Create UI elements. - app_summary_panel_ = new AppInfoSummaryPanel(profile_, app_); - CreateDescriptionControl(); - CreateLaunchOptionControl(); - CreateButtons(); - - // Layout elements. - SetLayoutManager( - new views::BoxLayout(views::BoxLayout::kVertical, - views::kButtonHEdgeMarginNew, - views::kPanelVertMargin, - views::kUnrelatedControlVerticalSpacing)); - - AddChildView(app_summary_panel_); - - if (app_description_label_) - AddChildView(app_description_label_); - - if (launch_options_combobox_) - AddChildView(launch_options_combobox_); - - if (HasImportedModules()) - AddChildView(new AppInfoImportedModulesPanel(profile_, app_)); - - LayoutButtons(); -} - -AppInfoSummaryTab::~AppInfoSummaryTab() { - // Destroy view children before their models. - RemoveAllChildViews(true); -} - -void AppInfoSummaryTab::CreateDescriptionControl() { - if (!app_->description().empty()) { - const size_t kMaxLength = 400; - - base::string16 text = base::UTF8ToUTF16(app_->description()); - if (text.length() > kMaxLength) { - text = text.substr(0, kMaxLength); - text += base::ASCIIToUTF16(" ... "); - } - - app_description_label_ = new views::Label(text); - app_description_label_->SetMultiLine(true); - app_description_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); - } -} - -void AppInfoSummaryTab::CreateLaunchOptionControl() { - if (CanSetLaunchType()) { - launch_options_combobox_model_.reset(new LaunchOptionsComboboxModel()); - launch_options_combobox_ = - new views::Combobox(launch_options_combobox_model_.get()); - - launch_options_combobox_->set_listener(this); - launch_options_combobox_->SetSelectedIndex( - launch_options_combobox_model_->GetIndexForLaunchType(GetLaunchType())); - } -} - -void AppInfoSummaryTab::CreateButtons() { - if (CanCreateShortcuts()) { - create_shortcuts_button_ = new views::LabelButton( - this, - l10n_util::GetStringUTF16( - IDS_APPLICATION_INFO_CREATE_SHORTCUTS_BUTTON_TEXT)); - create_shortcuts_button_->SetStyle(views::Button::STYLE_BUTTON); - } - - if (CanUninstallApp()) { - uninstall_button_ = new views::LabelButton( - this, - l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_UNINSTALL_BUTTON_TEXT)); - uninstall_button_->SetStyle(views::Button::STYLE_BUTTON); - } -} - -void AppInfoSummaryTab::LayoutButtons() { - views::View* app_buttons = new views::View(); - app_buttons->SetLayoutManager( - new views::BoxLayout(views::BoxLayout::kHorizontal, - 0, - 0, - views::kRelatedControlVerticalSpacing)); - - if (create_shortcuts_button_) - app_buttons->AddChildView(create_shortcuts_button_); - - if (uninstall_button_) - app_buttons->AddChildView(uninstall_button_); - - AddChildView(app_buttons); -} - -void AppInfoSummaryTab::OnPerformAction(views::Combobox* combobox) { - if (combobox == launch_options_combobox_) { - SetLaunchType(launch_options_combobox_model_->GetLaunchTypeAtIndex( - launch_options_combobox_->selected_index())); - } else { - NOTREACHED(); - } -} - -void AppInfoSummaryTab::ButtonPressed(views::Button* sender, - const ui::Event& event) { - if (sender == uninstall_button_) { - UninstallApp(); - } else if (sender == create_shortcuts_button_) { - CreateShortcuts(); - } else { - NOTREACHED(); - } -} - -void AppInfoSummaryTab::ExtensionUninstallAccepted() { - ExtensionService* service = - extensions::ExtensionSystem::Get(profile_)->extension_service(); - service->UninstallExtension(app_->id(), false, NULL); - - // Close the App Info dialog as well (which will free the dialog too). - GetWidget()->Close(); -} - -void AppInfoSummaryTab::ExtensionUninstallCanceled() { - extension_uninstall_dialog_.reset(); -} - -extensions::LaunchType AppInfoSummaryTab::GetLaunchType() const { - return extensions::GetLaunchType(extensions::ExtensionPrefs::Get(profile_), - app_); -} - -void AppInfoSummaryTab::SetLaunchType( - extensions::LaunchType launch_type) const { - DCHECK(CanSetLaunchType()); - ExtensionService* service = - extensions::ExtensionSystem::Get(profile_)->extension_service(); - extensions::SetLaunchType(service, app_->id(), launch_type); -} - -bool AppInfoSummaryTab::CanSetLaunchType() const { - // V2 apps don't have a launch type. - return !app_->is_platform_app(); -} - -void AppInfoSummaryTab::UninstallApp() { - DCHECK(CanUninstallApp()); - extension_uninstall_dialog_.reset( - extensions::ExtensionUninstallDialog::Create(profile_, NULL, this)); - extension_uninstall_dialog_->ConfirmUninstall(app_); -} - -bool AppInfoSummaryTab::CanUninstallApp() const { - return extensions::ExtensionSystem::Get(profile_) - ->management_policy() - ->UserMayModifySettings(app_, NULL); -} - -void AppInfoSummaryTab::CreateShortcuts() { - DCHECK(CanCreateShortcuts()); - chrome::ShowCreateChromeAppShortcutsDialog(GetWidget()->GetNativeWindow(), - profile_, - app_, - base::Callback<void(bool)>()); -} - -bool AppInfoSummaryTab::CanCreateShortcuts() const { - // ChromeOS can pin apps to the app launcher, but can't create shortcuts. -#if defined(OS_CHROMEOS) - return false; -#else - return true; -#endif -} - -bool AppInfoSummaryTab::HasImportedModules() { - return extensions::SharedModuleInfo::ImportsModules(app_); -} diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_tab.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_tab.cc deleted file mode 100644 index ba13597..0000000 --- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_tab.cc +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2014 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 "chrome/browser/ui/views/apps/app_info_dialog/app_info_tab.h" - -AppInfoTab::AppInfoTab(Profile* profile, const extensions::Extension* app) - : profile_(profile), app_(app) { -} - -AppInfoTab::~AppInfoTab() {} diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_tab.h b/chrome/browser/ui/views/apps/app_info_dialog/app_info_tab.h deleted file mode 100644 index 8a19b39..0000000 --- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_tab.h +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2014 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. - -#ifndef CHROME_BROWSER_UI_VIEWS_APPS_APP_INFO_DIALOG_APP_INFO_TAB_H_ -#define CHROME_BROWSER_UI_VIEWS_APPS_APP_INFO_DIALOG_APP_INFO_TAB_H_ - -#include "ui/views/view.h" - -class Profile; - -namespace extensions { -class Extension; -} -namespace views { -class View; -} - -// A tab in the App Info dialog that displays information for a particular -// profile and app. Tabs in the App Info dialog extend this class. -class AppInfoTab : public views::View { - public: - AppInfoTab(Profile* profile, const extensions::Extension* app); - - virtual ~AppInfoTab(); - - protected: - Profile* profile_; - const extensions::Extension* app_; - - private: - DISALLOW_COPY_AND_ASSIGN(AppInfoTab); -}; - -#endif // CHROME_BROWSER_UI_VIEWS_APPS_APP_INFO_DIALOG_APP_INFO_TAB_H_ diff --git a/chrome/chrome_browser_ui.gypi b/chrome/chrome_browser_ui.gypi index 76924f0..b537a27 100644 --- a/chrome/chrome_browser_ui.gypi +++ b/chrome/chrome_browser_ui.gypi @@ -1475,14 +1475,14 @@ 'browser/ui/views/app_list/win/app_list_win.h', 'browser/ui/views/apps/app_info_dialog/app_info_dialog_views.cc', 'browser/ui/views/apps/app_info_dialog/app_info_dialog_views.h', - 'browser/ui/views/apps/app_info_dialog/app_info_manage_tab.cc', - 'browser/ui/views/apps/app_info_dialog/app_info_manage_tab.h', - 'browser/ui/views/apps/app_info_dialog/app_info_permissions_tab.cc', - 'browser/ui/views/apps/app_info_dialog/app_info_permissions_tab.h', - 'browser/ui/views/apps/app_info_dialog/app_info_summary_tab.cc', - 'browser/ui/views/apps/app_info_dialog/app_info_summary_tab.h', - 'browser/ui/views/apps/app_info_dialog/app_info_tab.cc', - 'browser/ui/views/apps/app_info_dialog/app_info_tab.h', + 'browser/ui/views/apps/app_info_dialog/app_info_header_panel.cc', + 'browser/ui/views/apps/app_info_dialog/app_info_header_panel.h', + 'browser/ui/views/apps/app_info_dialog/app_info_panel.cc', + 'browser/ui/views/apps/app_info_dialog/app_info_panel.h', + 'browser/ui/views/apps/app_info_dialog/app_info_permissions_panel.cc', + 'browser/ui/views/apps/app_info_dialog/app_info_permissions_panel.h', + 'browser/ui/views/apps/app_info_dialog/app_info_summary_panel.cc', + 'browser/ui/views/apps/app_info_dialog/app_info_summary_panel.h', 'browser/ui/views/apps/app_window_desktop_native_widget_aura_win.cc', 'browser/ui/views/apps/app_window_desktop_native_widget_aura_win.h', 'browser/ui/views/apps/app_window_desktop_window_tree_host_win.cc', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index 023968d..b9f4ae8 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -1753,7 +1753,7 @@ 'browser/ui/views/accessibility/accessibility_event_router_views_unittest.cc', 'browser/ui/views/app_list/linux/app_list_linux_unittest.cc', 'browser/ui/views/app_list/win/app_list_win_unittest.cc', - 'browser/ui/views/apps/app_info_dialog/app_info_permissions_tab_unittest.cc', + 'browser/ui/views/apps/app_info_dialog/app_info_permissions_panel_unittest.cc', 'browser/ui/views/apps/shaped_app_window_targeter_unittest.cc', 'browser/ui/views/autofill/autofill_dialog_views_unittest.cc', 'browser/ui/views/bookmarks/bookmark_bar_view_unittest.cc', |