Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / gtk / extensions / extension_install_dialog_gtk.cc
blobaf9eae3999b2829dd47f8b4d6d2b9b884aaa8829
1 // Copyright (c) 2012 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.
5 #include <gtk/gtk.h>
7 #include "base/i18n/rtl.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/browser/extensions/bundle_installer.h"
11 #include "chrome/browser/extensions/extension_install_prompt.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/ui/gtk/browser_window_gtk.h"
14 #include "chrome/browser/ui/gtk/gtk_chrome_link_button.h"
15 #include "chrome/browser/ui/gtk/gtk_util.h"
16 #include "chrome/common/extensions/extension_constants.h"
17 #include "content/public/browser/page_navigator.h"
18 #include "content/public/browser/web_contents.h"
19 #include "content/public/browser/web_contents_view.h"
20 #include "extensions/common/extension.h"
21 #include "grit/generated_resources.h"
22 #include "skia/ext/image_operations.h"
23 #include "ui/base/gtk/gtk_hig_constants.h"
24 #include "ui/base/l10n/l10n_util.h"
25 #include "ui/base/resource/resource_bundle.h"
26 #include "ui/gfx/gtk_util.h"
28 using content::OpenURLParams;
29 using extensions::BundleInstaller;
31 namespace {
33 const int kLeftColumnMinWidth = 250;
34 // External installs have more text, so use a wider dialog.
35 const int kExternalInstallLeftColumnWidth = 350;
36 const int kImageSize = 69;
37 const int kDetailIndent = 20;
39 // Additional padding (beyond on ui::kControlSpacing) all sides of each
40 // permission in the permissions list.
41 const int kPermissionsPadding = 2;
42 const int kExtensionsPadding = kPermissionsPadding;
44 const double kRatingTextSize = 12.1; // 12.1px = 9pt @ 96dpi
46 // Adds a Skia image as an icon control to the given container.
47 void AddResourceIcon(const gfx::ImageSkia* icon, void* data) {
48 GtkWidget* container = static_cast<GtkWidget*>(data);
49 GdkPixbuf* icon_pixbuf = gfx::GdkPixbufFromSkBitmap(*icon->bitmap());
50 GtkWidget* icon_widget = gtk_image_new_from_pixbuf(icon_pixbuf);
51 g_object_unref(icon_pixbuf);
52 gtk_box_pack_start(GTK_BOX(container), icon_widget, FALSE, FALSE, 0);
55 // Returns an expander with the lines in |details|.
56 GtkWidget* CreateDetailsWidget(const std::vector<base::string16>& details,
57 int width,
58 bool show_bullets) {
59 GtkWidget* expander = gtk_expander_new(
60 l10n_util::GetStringUTF8(IDS_EXTENSIONS_DETAILS).c_str());
61 GtkWidget* align = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
62 gtk_container_add(GTK_CONTAINER(expander), align);
63 GtkWidget* details_vbox = gtk_vbox_new(FALSE, kPermissionsPadding);
64 gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, kDetailIndent, 0);
65 gtk_container_add(GTK_CONTAINER(align), details_vbox);
67 for (size_t i = 0; i < details.size(); ++i) {
68 std::string detail = show_bullets ?
69 l10n_util::GetStringFUTF8(IDS_EXTENSION_PERMISSION_LINE, details[0]) :
70 base::UTF16ToUTF8(details[i]);
71 GtkWidget* detail_label = gtk_label_new(detail.c_str());
72 gtk_label_set_line_wrap(GTK_LABEL(detail_label), true);
73 gtk_util::SetLabelWidth(detail_label, width - kDetailIndent);
74 gtk_box_pack_start(
75 GTK_BOX(details_vbox), detail_label, FALSE, FALSE, kPermissionsPadding);
77 return expander;
80 } // namespace
82 namespace chrome {
84 // Displays the dialog when constructed, deletes itself when dialog is
85 // dismissed. Success/failure is passed back through the
86 // ExtensionInstallPrompt::Delegate instance.
87 class ExtensionInstallDialog {
88 public:
89 ExtensionInstallDialog(const ExtensionInstallPrompt::ShowParams& show_params,
90 ExtensionInstallPrompt::Delegate* delegate,
91 const ExtensionInstallPrompt::Prompt& prompt);
92 private:
93 ~ExtensionInstallDialog();
95 CHROMEGTK_CALLBACK_1(ExtensionInstallDialog, void, OnResponse, int);
96 CHROMEGTK_CALLBACK_0(ExtensionInstallDialog, void, OnStoreLinkClick);
98 GtkWidget* CreateWidgetForIssueAdvice(
99 const IssueAdviceInfoEntry& issue_advice, int pixel_width);
101 content::PageNavigator* navigator_;
102 ExtensionInstallPrompt::Delegate* delegate_;
103 std::string extension_id_; // Set for INLINE_INSTALL_PROMPT.
104 GtkWidget* dialog_;
107 ExtensionInstallDialog::ExtensionInstallDialog(
108 const ExtensionInstallPrompt::ShowParams& show_params,
109 ExtensionInstallPrompt::Delegate *delegate,
110 const ExtensionInstallPrompt::Prompt& prompt)
111 : navigator_(show_params.navigator),
112 delegate_(delegate),
113 dialog_(NULL) {
114 bool show_permissions = prompt.ShouldShowPermissions();
115 bool show_oauth_issues = prompt.GetOAuthIssueCount() > 0;
116 bool show_retained_files = prompt.GetRetainedFileCount() > 0;
117 bool is_inline_install =
118 prompt.type() == ExtensionInstallPrompt::INLINE_INSTALL_PROMPT;
119 bool is_bundle_install =
120 prompt.type() == ExtensionInstallPrompt::BUNDLE_INSTALL_PROMPT;
121 bool is_external_install =
122 prompt.type() == ExtensionInstallPrompt::EXTERNAL_INSTALL_PROMPT;
124 if (is_inline_install)
125 extension_id_ = prompt.extension()->id();
127 // Build the dialog.
128 gfx::NativeWindow parent = show_params.parent_window;
129 dialog_ = gtk_dialog_new_with_buttons(
130 base::UTF16ToUTF8(prompt.GetDialogTitle()).c_str(),
131 parent,
132 GTK_DIALOG_MODAL,
133 NULL);
134 GtkWidget* close_button = gtk_dialog_add_button(
135 GTK_DIALOG(dialog_),
136 prompt.HasAbortButtonLabel() ?
137 base::UTF16ToUTF8(prompt.GetAbortButtonLabel()).c_str() :
138 GTK_STOCK_CANCEL,
139 GTK_RESPONSE_CLOSE);
140 if (prompt.HasAcceptButtonLabel()) {
141 gtk_dialog_add_button(
142 GTK_DIALOG(dialog_),
143 base::UTF16ToUTF8(prompt.GetAcceptButtonLabel()).c_str(),
144 GTK_RESPONSE_ACCEPT);
146 #if !GTK_CHECK_VERSION(2, 22, 0)
147 gtk_dialog_set_has_separator(GTK_DIALOG(dialog_), FALSE);
148 #endif
150 GtkWidget* scrolled_window = gtk_scrolled_window_new(NULL, NULL);
151 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
152 GTK_POLICY_NEVER,
153 GTK_POLICY_AUTOMATIC);
154 GtkWidget* content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog_));
155 gtk_box_set_spacing(GTK_BOX(content_area), ui::kContentAreaSpacing);
157 // Divide the dialog vertically (item data and icon on the top, permissions
158 // on the bottom).
159 GtkWidget* content_vbox = gtk_vbox_new(FALSE, ui::kControlSpacing);
160 gtk_container_set_border_width(GTK_CONTAINER(content_vbox),
161 ui::kContentAreaBorder);
162 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
163 content_vbox);
164 GtkWidget* viewport = gtk_bin_get_child(GTK_BIN(scrolled_window));
165 gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE);
166 gtk_box_pack_start(GTK_BOX(content_area), scrolled_window, TRUE, TRUE, 0);
168 // Create a two column layout for the top (item data on the left, icon on
169 // the right).
170 GtkWidget* top_content_hbox = gtk_hbox_new(FALSE, ui::kContentAreaSpacing);
171 gtk_box_pack_start(GTK_BOX(content_vbox), top_content_hbox, TRUE, TRUE, 0);
173 // We don't show the image for bundle installs, so let the left column take
174 // up that space.
175 int left_column_min_width = kLeftColumnMinWidth;
176 if (is_bundle_install)
177 left_column_min_width += kImageSize;
178 if (is_external_install)
179 left_column_min_width = kExternalInstallLeftColumnWidth;
181 // Create a new vbox for the left column.
182 GtkWidget* left_column_area = gtk_vbox_new(FALSE, ui::kControlSpacing);
183 gtk_box_pack_start(GTK_BOX(top_content_hbox), left_column_area,
184 TRUE, TRUE, 0);
185 gtk_widget_set_size_request(left_column_area, left_column_min_width, -1);
187 GtkWidget* heading_vbox = gtk_vbox_new(FALSE, 0);
188 // If we are not going to show anything else, vertically center the title.
189 bool center_heading = !show_permissions && !show_oauth_issues &&
190 !is_inline_install && !show_retained_files;
191 gtk_box_pack_start(GTK_BOX(left_column_area), heading_vbox, center_heading,
192 center_heading, 0);
194 // Heading
195 GtkWidget* heading_label = gtk_util::CreateBoldLabel(
196 base::UTF16ToUTF8(prompt.GetHeading().c_str()));
197 gtk_util::SetLabelWidth(heading_label, left_column_min_width);
198 gtk_box_pack_start(GTK_BOX(heading_vbox), heading_label, center_heading,
199 center_heading, 0);
201 if (is_inline_install) {
202 // Average rating (as stars) and number of ratings.
203 GtkWidget* stars_hbox = gtk_hbox_new(FALSE, 0);
204 gtk_box_pack_start(GTK_BOX(heading_vbox), stars_hbox, FALSE, FALSE, 0);
205 prompt.AppendRatingStars(AddResourceIcon, stars_hbox);
206 GtkWidget* rating_label = gtk_label_new(base::UTF16ToUTF8(
207 prompt.GetRatingCount()).c_str());
208 gtk_util::ForceFontSizePixels(rating_label, kRatingTextSize);
209 gtk_box_pack_start(GTK_BOX(stars_hbox), rating_label,
210 FALSE, FALSE, 3);
212 // User count.
213 GtkWidget* users_label = gtk_label_new(base::UTF16ToUTF8(
214 prompt.GetUserCount()).c_str());
215 gtk_util::SetLabelWidth(users_label, left_column_min_width);
216 gtk_util::SetLabelColor(users_label, &ui::kGdkGray);
217 gtk_util::ForceFontSizePixels(rating_label, kRatingTextSize);
218 gtk_box_pack_start(GTK_BOX(heading_vbox), users_label,
219 FALSE, FALSE, 0);
221 // Store link.
222 GtkWidget* store_link = gtk_chrome_link_button_new(
223 l10n_util::GetStringUTF8(IDS_EXTENSION_PROMPT_STORE_LINK).c_str());
224 gtk_util::ForceFontSizePixels(store_link, kRatingTextSize);
225 GtkWidget* store_link_hbox = gtk_hbox_new(FALSE, 0);
226 // Stick it in an hbox so it doesn't expand to the whole width.
227 gtk_box_pack_start(GTK_BOX(store_link_hbox), store_link, FALSE, FALSE, 0);
228 gtk_box_pack_start(GTK_BOX(heading_vbox), store_link_hbox, FALSE, FALSE, 0);
229 g_signal_connect(store_link, "clicked",
230 G_CALLBACK(OnStoreLinkClickThunk), this);
233 if (is_bundle_install) {
234 // Add the list of extensions to be installed.
235 GtkWidget* extensions_vbox = gtk_vbox_new(FALSE, ui::kControlSpacing);
236 gtk_box_pack_start(GTK_BOX(heading_vbox), extensions_vbox, FALSE, FALSE,
237 ui::kControlSpacing);
239 BundleInstaller::ItemList items = prompt.bundle()->GetItemsWithState(
240 BundleInstaller::Item::STATE_PENDING);
241 for (size_t i = 0; i < items.size(); ++i) {
242 GtkWidget* extension_label = gtk_label_new(base::UTF16ToUTF8(
243 items[i].GetNameForDisplay()).c_str());
244 gtk_util::SetLabelWidth(extension_label, left_column_min_width);
245 gtk_box_pack_start(GTK_BOX(extensions_vbox), extension_label,
246 FALSE, FALSE, kExtensionsPadding);
248 } else {
249 // Resize the icon if necessary.
250 SkBitmap scaled_icon = *prompt.icon().ToSkBitmap();
251 if (scaled_icon.width() > kImageSize || scaled_icon.height() > kImageSize) {
252 scaled_icon = skia::ImageOperations::Resize(
253 scaled_icon, skia::ImageOperations::RESIZE_LANCZOS3,
254 kImageSize, kImageSize);
257 // Put icon in the right column.
258 GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(scaled_icon);
259 GtkWidget* icon = gtk_image_new_from_pixbuf(pixbuf);
260 g_object_unref(pixbuf);
261 gtk_box_pack_start(GTK_BOX(top_content_hbox), icon, FALSE, FALSE, 0);
262 // Top justify the image.
263 gtk_misc_set_alignment(GTK_MISC(icon), 0.5, 0.0);
266 // Permissions are shown separated by a divider for inline installs, or
267 // directly under the heading for regular installs (where we don't have
268 // the store data)
269 if (show_permissions) {
270 GtkWidget* permissions_container;
271 if (is_inline_install) {
272 permissions_container = content_vbox;
273 gtk_box_pack_start(GTK_BOX(content_vbox), gtk_hseparator_new(),
274 FALSE, FALSE, ui::kControlSpacing);
275 } else {
276 permissions_container = left_column_area;
279 if (prompt.GetPermissionCount() > 0) {
280 GtkWidget* permissions_header = gtk_util::CreateBoldLabel(
281 base::UTF16ToUTF8(prompt.GetPermissionsHeading()).c_str());
282 gtk_util::SetLabelWidth(permissions_header, left_column_min_width);
283 gtk_box_pack_start(GTK_BOX(permissions_container), permissions_header,
284 FALSE, FALSE, 0);
286 for (size_t i = 0; i < prompt.GetPermissionCount(); ++i) {
287 GtkWidget* permission_vbox = gtk_vbox_new(FALSE, 0);
288 std::string permission = l10n_util::GetStringFUTF8(
289 IDS_EXTENSION_PERMISSION_LINE, prompt.GetPermission(i));
290 GtkWidget* permission_label = gtk_label_new(permission.c_str());
291 gtk_util::SetLabelWidth(permission_label, left_column_min_width);
292 gtk_box_pack_start(GTK_BOX(permission_vbox), permission_label,
293 FALSE, FALSE, 0);
294 if (!prompt.GetPermissionsDetails(i).empty()) {
295 std::vector<base::string16> details;
296 details.push_back(prompt.GetPermissionsDetails(i));
297 gtk_box_pack_start(
298 GTK_BOX(permission_vbox),
299 CreateDetailsWidget(details, left_column_min_width, false),
300 FALSE,
301 FALSE,
304 gtk_box_pack_start(GTK_BOX(permissions_container), permission_vbox,
305 FALSE, FALSE, kPermissionsPadding);
307 } else {
308 GtkWidget* permission_label = gtk_label_new(l10n_util::GetStringUTF8(
309 IDS_EXTENSION_NO_SPECIAL_PERMISSIONS).c_str());
310 gtk_util::SetLabelWidth(permission_label, left_column_min_width);
311 gtk_box_pack_start(GTK_BOX(permissions_container), permission_label,
312 FALSE, FALSE, kPermissionsPadding);
316 if (show_oauth_issues) {
317 // If permissions are shown, then the scopes will go below them and take
318 // up the entire width of the dialog. Otherwise the scopes will go where
319 // the permissions usually go.
320 GtkWidget* oauth_issues_container =
321 show_permissions ? content_vbox : left_column_area;
322 int pixel_width = left_column_min_width +
323 (show_permissions ? kImageSize : 0);
325 GtkWidget* oauth_issues_header = gtk_util::CreateBoldLabel(
326 base::UTF16ToUTF8(prompt.GetOAuthHeading()).c_str());
327 gtk_util::SetLabelWidth(oauth_issues_header, pixel_width);
328 gtk_box_pack_start(GTK_BOX(oauth_issues_container), oauth_issues_header,
329 FALSE, FALSE, 0);
331 for (size_t i = 0; i < prompt.GetOAuthIssueCount(); ++i) {
332 GtkWidget* issue_advice_widget =
333 CreateWidgetForIssueAdvice(prompt.GetOAuthIssue(i), pixel_width);
334 gtk_box_pack_start(GTK_BOX(oauth_issues_container), issue_advice_widget,
335 FALSE, FALSE, kPermissionsPadding);
339 if (show_retained_files) {
340 GtkWidget* retained_files_container =
341 (show_permissions || show_oauth_issues) ? content_vbox
342 : left_column_area;
343 int pixel_width =
344 left_column_min_width +
345 ((show_permissions || show_oauth_issues) ? kImageSize : 0);
347 GtkWidget* retained_files_header = gtk_util::CreateBoldLabel(
348 base::UTF16ToUTF8(prompt.GetRetainedFilesHeading()).c_str());
349 gtk_util::SetLabelWidth(retained_files_header, pixel_width);
350 gtk_box_pack_start(GTK_BOX(retained_files_container), retained_files_header,
351 FALSE, FALSE, 0);
353 std::vector<base::string16> paths;
354 for (size_t i = 0; i < prompt.GetRetainedFileCount(); ++i) {
355 paths.push_back(prompt.GetRetainedFile(i));
357 gtk_box_pack_start(GTK_BOX(retained_files_container),
358 CreateDetailsWidget(paths, pixel_width, false),
359 FALSE,
360 FALSE,
361 kPermissionsPadding);
364 g_signal_connect(dialog_, "response", G_CALLBACK(OnResponseThunk), this);
365 gtk_window_set_resizable(GTK_WINDOW(dialog_), FALSE);
367 gtk_dialog_set_default_response(GTK_DIALOG(dialog_), GTK_RESPONSE_CLOSE);
368 gtk_widget_show_all(dialog_);
370 gtk_container_set_border_width(GTK_CONTAINER(content_area), 0);
371 gtk_container_set_border_width(
372 GTK_CONTAINER(gtk_dialog_get_action_area(GTK_DIALOG(dialog_))),
373 ui::kContentAreaBorder);
374 gtk_box_set_spacing(GTK_BOX(gtk_bin_get_child(GTK_BIN(dialog_))), 0);
375 GtkRequisition requisition;
376 gtk_widget_size_request(content_vbox, &requisition);
377 gtk_widget_set_size_request(
378 scrolled_window, requisition.width, requisition.height);
379 gtk_widget_grab_focus(close_button);
382 ExtensionInstallDialog::~ExtensionInstallDialog() {
385 void ExtensionInstallDialog::OnResponse(GtkWidget* dialog, int response_id) {
386 if (response_id == GTK_RESPONSE_ACCEPT)
387 delegate_->InstallUIProceed();
388 else
389 delegate_->InstallUIAbort(true);
391 gtk_widget_destroy(dialog_);
392 delete this;
395 void ExtensionInstallDialog::OnStoreLinkClick(GtkWidget* sender) {
396 GURL store_url(
397 extension_urls::GetWebstoreItemDetailURLPrefix() + extension_id_);
398 navigator_->OpenURL(OpenURLParams(
399 store_url, content::Referrer(), NEW_FOREGROUND_TAB,
400 content::PAGE_TRANSITION_LINK, false));
402 OnResponse(dialog_, GTK_RESPONSE_CLOSE);
405 GtkWidget* ExtensionInstallDialog::CreateWidgetForIssueAdvice(
406 const IssueAdviceInfoEntry& issue_advice, int pixel_width) {
407 GtkWidget* box = gtk_vbox_new(FALSE, 0);
408 GtkWidget* label = gtk_label_new(l10n_util::GetStringFUTF8(
409 IDS_EXTENSION_PERMISSION_LINE, issue_advice.description).c_str());
410 gtk_util::SetLabelWidth(label, pixel_width);
411 gtk_box_pack_start(GTK_BOX(box), label, TRUE, TRUE, 0);
413 if (!issue_advice.details.empty()) {
414 gtk_box_pack_start(
415 GTK_BOX(box),
416 CreateDetailsWidget(issue_advice.details, pixel_width, true),
417 TRUE,
418 TRUE,
421 return box;
424 } // namespace chrome
426 namespace {
428 void ShowExtensionInstallDialogImpl(
429 const ExtensionInstallPrompt::ShowParams& show_params,
430 ExtensionInstallPrompt::Delegate* delegate,
431 const ExtensionInstallPrompt::Prompt& prompt) {
432 new chrome::ExtensionInstallDialog(show_params, delegate, prompt);
435 } // namespace
437 // static
438 ExtensionInstallPrompt::ShowDialogCallback
439 ExtensionInstallPrompt::GetDefaultShowDialogCallback() {
440 return base::Bind(&ShowExtensionInstallDialogImpl);