app_info_header_panel.cc revision 6d86b77056ed63eb6871182f42a9fd5f07550f90
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
44namespace {
45
46// Size of extension icon in top left of dialog.
47const 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.
51const int kSpacingBetweenAppTitleAndVersion = 4;
52
53}  // namespace
54
55AppInfoHeaderPanel::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
75AppInfoHeaderPanel::~AppInfoHeaderPanel() {
76}
77
78void 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
114void 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
150void 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}
186void 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
196void 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
210void 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
223void 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(&params);
234}
235
236bool AppInfoHeaderPanel::CanShowAppInWebStore() const {
237  return app_->from_webstore();
238}
239
240void 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(&params);
259}
260
261bool 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