Index: chrome/browser/ui/views/apps/app_info_dialog/app_info_details_panel.cc |
diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_details_panel.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_details_panel.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..2e3719c48c451e704c8200933bba28fd590a721b |
--- /dev/null |
+++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_details_panel.cc |
@@ -0,0 +1,457 @@ |
+// 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_details_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/callback.h" |
+#include "base/command_line.h" |
+#include "base/files/file_path.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 "chrome/common/extensions/extension_constants.h" |
+#include "extensions/browser/extension_prefs.h" |
+#include "extensions/browser/extension_system.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/base/models/combobox_model.h" |
+#include "ui/base/resource/resource_bundle.h" |
+#include "ui/gfx/text_constants.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/grid_layout.h" |
+#include "ui/views/layout/layout_constants.h" |
+#include "ui/views/widget/widget.h" |
+ |
+namespace { |
+ |
+// Create a heading label with the given text. |
+views::Label* CreateHeading(const base::string16& text) { |
+ views::Label* label = new views::Label(text); |
+ label->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
+ label->SetFontList(ui::ResourceBundle::GetSharedInstance().GetFontList( |
+ ui::ResourceBundle::MediumFont)); |
+ return label; |
+} |
+ |
+// Create a view with a vertically-stacked box layout, which can have child |
+// views appended to it. |
+views::View* CreateVerticalStack() { |
+ 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; |
+} |
+ |
+// 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) { |
+ 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; |
+} |
+ |
+} // 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]; |
+} |
+ |
+AppInfoDetailsPanel::AppInfoDetailsPanel(gfx::NativeWindow parent_window, |
+ Profile* profile, |
+ const extensions::Extension* app) |
+ : parent_window_(parent_window), |
+ profile_(profile), |
+ app_(app), |
+ description_heading_(NULL), |
+ description_label_(NULL), |
+ active_permissions_heading_(NULL), |
+ active_permissions_list_(NULL), |
+ retained_files_heading_(NULL), |
+ retained_files_list_(NULL), |
+ revoke_file_permissions_button_(NULL), |
+ launch_options_combobox_(NULL) { |
+ // Create UI elements. |
+ CreateDescriptionControl(); |
+ CreateActivePermissionsControl(); |
+ CreateRetainedFilesControl(); |
+ CreateLaunchOptionControl(); |
+ CreateShortcutsButton(); |
+ |
+ // Layout elements. |
+ SetLayoutManager( |
+ new views::BoxLayout(views::BoxLayout::kVertical, |
+ views::kButtonHEdgeMarginNew, |
+ views::kPanelVertMargin, |
+ views::kUnrelatedControlVerticalSpacing)); |
+ |
+ LayoutDescriptionControl(); |
+ |
+ if (launch_options_combobox_) |
+ AddChildView(launch_options_combobox_); |
+ |
+ LayoutActivePermissionsControl(); |
+ LayoutRetainedFilesControl(); |
+ LayoutShortcutsButton(); |
+} |
+ |
+AppInfoDetailsPanel::~AppInfoDetailsPanel() { |
+ // Destroy view children before their models. |
+ RemoveAllChildViews(true); |
+} |
+ |
+void AppInfoDetailsPanel::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 AppInfoDetailsPanel::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 AppInfoDetailsPanel::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 AppInfoDetailsPanel::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 AppInfoDetailsPanel::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 AppInfoDetailsPanel::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 AppInfoDetailsPanel::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 AppInfoDetailsPanel::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 AppInfoDetailsPanel::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 AppInfoDetailsPanel::OnPerformAction(views::Combobox* combobox) { |
+ if (combobox == launch_options_combobox_) { |
+ SetLaunchType(launch_options_combobox_model_->GetLaunchTypeAtIndex( |
+ launch_options_combobox_->selected_index())); |
+ } else { |
+ NOTREACHED(); |
+ } |
+} |
+ |
+void AppInfoDetailsPanel::ButtonPressed(views::Button* sender, |
+ const ui::Event& event) { |
+ if (sender == revoke_file_permissions_button_) { |
+ RevokeFilePermissions(); |
+ } else if (sender == create_shortcuts_button_) { |
+ CreateShortcuts(); |
+ } else { |
+ NOTREACHED(); |
+ } |
+} |
+ |
+extensions::LaunchType AppInfoDetailsPanel::GetLaunchType() const { |
+ return extensions::GetLaunchType(extensions::ExtensionPrefs::Get(profile_), |
+ app_); |
+} |
+ |
+void AppInfoDetailsPanel::SetLaunchType( |
+ extensions::LaunchType launch_type) const { |
+ DCHECK(CanSetLaunchType()); |
+ ExtensionService* service = |
+ extensions::ExtensionSystem::Get(profile_)->extension_service(); |
+ extensions::SetLaunchType(service, app_->id(), launch_type); |
+} |
+ |
+bool AppInfoDetailsPanel::CanSetLaunchType() const { |
+ // V2 apps don't have a launch type. |
+ return !app_->is_platform_app(); |
+} |
+ |
+void AppInfoDetailsPanel::CreateShortcuts() { |
+ DCHECK(CanCreateShortcuts()); |
+ chrome::ShowCreateChromeAppShortcutsDialog( |
+ parent_window_, profile_, app_, base::Callback<void(bool)>()); |
+} |
+ |
+bool AppInfoDetailsPanel::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 |
+} |
+ |
+const std::vector<base::string16> |
+AppInfoDetailsPanel::GetActivePermissionMessages() const { |
+ return app_->permissions_data()->GetPermissionMessageStrings(); |
+} |
+ |
+const std::vector<base::string16> AppInfoDetailsPanel::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 AppInfoDetailsPanel::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(); |
+} |