1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/ui/views/apps/app_info_dialog/app_info_header_panel.h" 6 7 #include "base/bind.h" 8 #include "base/strings/utf_string_conversions.h" 9 #include "chrome/browser/extensions/extension_service.h" 10 #include "chrome/browser/extensions/extension_util.h" 11 #include "chrome/browser/profiles/profile.h" 12 #include "chrome/browser/ui/browser_navigator.h" 13 #include "chrome/common/extensions/extension_constants.h" 14 #include "chrome/common/extensions/manifest_url_handler.h" 15 #include "extensions/browser/extension_system.h" 16 #include "extensions/browser/image_loader.h" 17 #include "extensions/common/constants.h" 18 #include "extensions/common/extension.h" 19 #include "extensions/common/extension_icon_set.h" 20 #include "extensions/common/extension_resource.h" 21 #include "extensions/common/manifest_handlers/icons_handler.h" 22 #include "extensions/common/manifest_handlers/shared_module_info.h" 23 #include "grit/generated_resources.h" 24 #include "net/base/url_util.h" 25 #include "third_party/skia/include/core/SkBitmap.h" 26 #include "ui/app_list/app_list_constants.h" 27 #include "ui/base/l10n/l10n_util.h" 28 #include "ui/base/resource/resource_bundle.h" 29 #include "ui/gfx/geometry/size.h" 30 #include "ui/gfx/image/image.h" 31 #include "ui/gfx/image/image_skia.h" 32 #include "ui/gfx/image/image_skia_rep.h" 33 #include "ui/gfx/native_widget_types.h" 34 #include "ui/gfx/text_constants.h" 35 #include "ui/views/controls/image_view.h" 36 #include "ui/views/controls/label.h" 37 #include "ui/views/controls/link.h" 38 #include "ui/views/layout/box_layout.h" 39 #include "ui/views/layout/grid_layout.h" 40 #include "ui/views/layout/layout_constants.h" 41 #include "ui/views/view.h" 42 #include "url/gurl.h" 43 44 namespace { 45 46 // Size of extension icon in top left of dialog. 47 const int kIconSize = 64; 48 49 // The number of pixels spacing between the app's title and version in the 50 // dialog, when both are available. 51 const int kSpacingBetweenAppTitleAndVersion = 4; 52 53 } // namespace 54 55 AppInfoHeaderPanel::AppInfoHeaderPanel(Profile* profile, 56 const extensions::Extension* app) 57 : AppInfoPanel(profile, app), 58 app_icon_(NULL), 59 app_name_label_(NULL), 60 app_version_label_(NULL), 61 view_in_store_link_(NULL), 62 licenses_link_(NULL), 63 weak_ptr_factory_(this) { 64 CreateControls(); 65 66 SetLayoutManager( 67 new views::BoxLayout(views::BoxLayout::kHorizontal, 68 views::kButtonHEdgeMargin, 69 views::kButtonVEdgeMargin, 70 views::kRelatedControlHorizontalSpacing)); 71 72 LayoutControls(); 73 } 74 75 AppInfoHeaderPanel::~AppInfoHeaderPanel() { 76 } 77 78 void AppInfoHeaderPanel::CreateControls() { 79 app_name_label_ = 80 new views::Label(base::UTF8ToUTF16(app_->name()), 81 ui::ResourceBundle::GetSharedInstance().GetFontList( 82 ui::ResourceBundle::MediumFont)); 83 app_name_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 84 85 // The version number doesn't make sense for bookmarked apps. 86 if (!app_->from_bookmark()) { 87 app_version_label_ = 88 new views::Label(base::UTF8ToUTF16(app_->VersionString()), 89 ui::ResourceBundle::GetSharedInstance().GetFontList( 90 ui::ResourceBundle::MediumFont)); 91 app_version_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 92 app_version_label_->SetEnabledColor(app_list::kDialogSubtitleColor); 93 } 94 95 app_icon_ = new views::ImageView(); 96 app_icon_->SetImageSize(gfx::Size(kIconSize, kIconSize)); 97 LoadAppImageAsync(); 98 99 if (CanShowAppInWebStore()) { 100 view_in_store_link_ = new views::Link( 101 l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_WEB_STORE_LINK)); 102 view_in_store_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 103 view_in_store_link_->set_listener(this); 104 } 105 106 if (CanDisplayLicenses()) { 107 licenses_link_ = new views::Link( 108 l10n_util::GetStringUTF16(IDS_APPLICATION_INFO_LICENSES_BUTTON_TEXT)); 109 licenses_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 110 licenses_link_->set_listener(this); 111 } 112 } 113 114 void AppInfoHeaderPanel::LayoutAppNameAndVersionInto(views::View* parent_view) { 115 views::View* view = new views::View(); 116 // We need a horizontal BoxLayout here to ensure that the GridLayout does 117 // not stretch beyond the size of its content. 118 view->SetLayoutManager( 119 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0)); 120 121 views::View* container_view = new views::View(); 122 view->AddChildView(container_view); 123 views::GridLayout* layout = new views::GridLayout(container_view); 124 container_view->SetLayoutManager(layout); 125 126 static const int kColumnId = 1; 127 views::ColumnSet* column_set = layout->AddColumnSet(kColumnId); 128 column_set->AddColumn(views::GridLayout::LEADING, 129 views::GridLayout::TRAILING, 130 1, // Stretch the title to as wide as needed 131 views::GridLayout::USE_PREF, 132 0, 133 0); 134 column_set->AddPaddingColumn(0, kSpacingBetweenAppTitleAndVersion); 135 column_set->AddColumn(views::GridLayout::LEADING, 136 views::GridLayout::TRAILING, 137 0, // Do not stretch the version 138 views::GridLayout::USE_PREF, 139 0, 140 0); 141 142 layout->StartRow(1, kColumnId); 143 layout->AddView(app_name_label_); 144 if (app_version_label_) 145 layout->AddView(app_version_label_); 146 147 parent_view->AddChildView(view); 148 } 149 150 void AppInfoHeaderPanel::LayoutControls() { 151 AddChildView(app_icon_); 152 if (!app_version_label_ && !view_in_store_link_ && !licenses_link_) { 153 // If there's no version _and_ no links, allow the app's name to take up 154 // multiple lines. 155 // TODO(sashab): Limit the number of lines to 2. 156 app_name_label_->SetMultiLine(true); 157 AddChildView(app_name_label_); 158 } else { 159 // Create a vertical container to store the app's name and info. 160 views::View* vertical_info_container = new views::View(); 161 views::BoxLayout* vertical_container_layout = 162 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0); 163 vertical_container_layout->set_main_axis_alignment( 164 views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER); 165 vertical_info_container->SetLayoutManager(vertical_container_layout); 166 167 if (!view_in_store_link_ && !licenses_link_) { 168 // If there are no links, display the version on the second line. 169 vertical_info_container->AddChildView(app_name_label_); 170 vertical_info_container->AddChildView(app_version_label_); 171 } else { 172 LayoutAppNameAndVersionInto(vertical_info_container); 173 // Create a horizontal container to store the app's links. 174 views::View* horizontal_links_container = new views::View(); 175 horizontal_links_container->SetLayoutManager( 176 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 3)); 177 if (view_in_store_link_) 178 horizontal_links_container->AddChildView(view_in_store_link_); 179 if (licenses_link_) 180 horizontal_links_container->AddChildView(licenses_link_); 181 vertical_info_container->AddChildView(horizontal_links_container); 182 } 183 AddChildView(vertical_info_container); 184 } 185 } 186 void AppInfoHeaderPanel::LinkClicked(views::Link* source, int event_flags) { 187 if (source == view_in_store_link_) { 188 ShowAppInWebStore(); 189 } else if (source == licenses_link_) { 190 DisplayLicenses(); 191 } else { 192 NOTREACHED(); 193 } 194 } 195 196 void AppInfoHeaderPanel::LoadAppImageAsync() { 197 extensions::ExtensionResource image = extensions::IconsInfo::GetIconResource( 198 app_, 199 extension_misc::EXTENSION_ICON_LARGE, 200 ExtensionIconSet::MATCH_BIGGER); 201 int pixel_size = 202 static_cast<int>(kIconSize * gfx::ImageSkia::GetMaxSupportedScale()); 203 extensions::ImageLoader::Get(profile_)->LoadImageAsync( 204 app_, 205 image, 206 gfx::Size(pixel_size, pixel_size), 207 base::Bind(&AppInfoHeaderPanel::OnAppImageLoaded, AsWeakPtr())); 208 } 209 210 void AppInfoHeaderPanel::OnAppImageLoaded(const gfx::Image& image) { 211 const SkBitmap* bitmap; 212 if (image.IsEmpty()) { 213 bitmap = &extensions::util::GetDefaultAppIcon() 214 .GetRepresentation(gfx::ImageSkia::GetMaxSupportedScale()) 215 .sk_bitmap(); 216 } else { 217 bitmap = image.ToSkBitmap(); 218 } 219 220 app_icon_->SetImage(gfx::ImageSkia::CreateFrom1xBitmap(*bitmap)); 221 } 222 223 void AppInfoHeaderPanel::ShowAppInWebStore() const { 224 DCHECK(CanShowAppInWebStore()); 225 const GURL url = extensions::ManifestURL::GetDetailsURL(app_); 226 DCHECK_NE(url, GURL::EmptyGURL()); 227 chrome::NavigateParams params( 228 profile_, 229 net::AppendQueryParameter(url, 230 extension_urls::kWebstoreSourceField, 231 extension_urls::kLaunchSourceAppListInfoDialog), 232 content::PAGE_TRANSITION_LINK); 233 chrome::Navigate(¶ms); 234 } 235 236 bool AppInfoHeaderPanel::CanShowAppInWebStore() const { 237 return app_->from_webstore(); 238 } 239 240 void AppInfoHeaderPanel::DisplayLicenses() { 241 // Find the first shared module for this app, and display it's options page 242 // as an 'about' link. 243 // TODO(sashab): Revisit UI layout once shared module usage becomes more 244 // common. 245 DCHECK(CanDisplayLicenses()); 246 ExtensionService* service = 247 extensions::ExtensionSystem::Get(profile_)->extension_service(); 248 DCHECK(service); 249 const std::vector<extensions::SharedModuleInfo::ImportInfo>& imports = 250 extensions::SharedModuleInfo::GetImports(app_); 251 const extensions::Extension* imported_module = 252 service->GetExtensionById(imports[0].extension_id, true); 253 DCHECK(imported_module); 254 GURL about_page = extensions::ManifestURL::GetAboutPage(imported_module); 255 DCHECK(about_page != GURL::EmptyGURL()); 256 chrome::NavigateParams params( 257 profile_, about_page, content::PAGE_TRANSITION_LINK); 258 chrome::Navigate(¶ms); 259 } 260 261 bool AppInfoHeaderPanel::CanDisplayLicenses() { 262 if (!extensions::SharedModuleInfo::ImportsModules(app_)) 263 return false; 264 ExtensionService* service = 265 extensions::ExtensionSystem::Get(profile_)->extension_service(); 266 DCHECK(service); 267 const std::vector<extensions::SharedModuleInfo::ImportInfo>& imports = 268 extensions::SharedModuleInfo::GetImports(app_); 269 const extensions::Extension* imported_module = 270 service->GetExtensionById(imports[0].extension_id, true); 271 DCHECK(imported_module); 272 GURL about_page = extensions::ManifestURL::GetAboutPage(imported_module); 273 if (about_page == GURL::EmptyGURL()) 274 return false; 275 return true; 276 } 277