- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / gtk / extensions / extension_install_dialog_gtk.cc
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.
4
5 #include <gtk/gtk.h>
6
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.h"
17 #include "chrome/common/extensions/extension_constants.h"
18 #include "content/public/browser/page_navigator.h"
19 #include "content/public/browser/web_contents.h"
20 #include "content/public/browser/web_contents_view.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"
27
28 using content::OpenURLParams;
29 using extensions::BundleInstaller;
30
31 namespace {
32
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;
38
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;
43
44 const double kRatingTextSize = 12.1;  // 12.1px = 9pt @ 96dpi
45
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);
53 }
54
55 // Returns an expander with the lines in |details|.
56 GtkWidget* CreateDetailsWidget(const std::vector<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);
66
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);
76   }
77   return expander;
78 }
79
80 }  // namespace
81
82 namespace chrome {
83
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();
94
95   CHROMEGTK_CALLBACK_1(ExtensionInstallDialog, void, OnResponse, int);
96   CHROMEGTK_CALLBACK_0(ExtensionInstallDialog, void, OnStoreLinkClick);
97
98   GtkWidget* CreateWidgetForIssueAdvice(
99       const IssueAdviceInfoEntry& issue_advice, int pixel_width);
100
101   content::PageNavigator* navigator_;
102   ExtensionInstallPrompt::Delegate* delegate_;
103   std::string extension_id_;  // Set for INLINE_INSTALL_PROMPT.
104   GtkWidget* dialog_;
105 };
106
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;
123
124   if (is_inline_install)
125     extension_id_ = prompt.extension()->id();
126
127   // Build the dialog.
128   gfx::NativeWindow parent = show_params.parent_window;
129   dialog_ = gtk_dialog_new_with_buttons(
130       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           UTF16ToUTF8(prompt.GetAbortButtonLabel()).c_str() : GTK_STOCK_CANCEL,
138       GTK_RESPONSE_CLOSE);
139   if (prompt.HasAcceptButtonLabel()) {
140     gtk_dialog_add_button(
141         GTK_DIALOG(dialog_),
142         UTF16ToUTF8(prompt.GetAcceptButtonLabel()).c_str(),
143         GTK_RESPONSE_ACCEPT);
144   }
145 #if !GTK_CHECK_VERSION(2, 22, 0)
146   gtk_dialog_set_has_separator(GTK_DIALOG(dialog_), FALSE);
147 #endif
148
149   GtkWidget* scrolled_window = gtk_scrolled_window_new(NULL, NULL);
150   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
151                                  GTK_POLICY_NEVER,
152                                  GTK_POLICY_AUTOMATIC);
153   GtkWidget* content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog_));
154   gtk_box_set_spacing(GTK_BOX(content_area), ui::kContentAreaSpacing);
155
156   // Divide the dialog vertically (item data and icon on the top, permissions
157   // on the bottom).
158   GtkWidget* content_vbox = gtk_vbox_new(FALSE, ui::kControlSpacing);
159   gtk_container_set_border_width(GTK_CONTAINER(content_vbox),
160                                  ui::kContentAreaBorder);
161   gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
162                                         content_vbox);
163   GtkWidget* viewport = gtk_bin_get_child(GTK_BIN(scrolled_window));
164   gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE);
165   gtk_box_pack_start(GTK_BOX(content_area), scrolled_window, TRUE, TRUE, 0);
166
167   // Create a two column layout for the top (item data on the left, icon on
168   // the right).
169   GtkWidget* top_content_hbox = gtk_hbox_new(FALSE, ui::kContentAreaSpacing);
170   gtk_box_pack_start(GTK_BOX(content_vbox), top_content_hbox, TRUE, TRUE, 0);
171
172   // We don't show the image for bundle installs, so let the left column take
173   // up that space.
174   int left_column_min_width = kLeftColumnMinWidth;
175   if (is_bundle_install)
176     left_column_min_width += kImageSize;
177   if (is_external_install)
178     left_column_min_width = kExternalInstallLeftColumnWidth;
179
180   // Create a new vbox for the left column.
181   GtkWidget* left_column_area = gtk_vbox_new(FALSE, ui::kControlSpacing);
182   gtk_box_pack_start(GTK_BOX(top_content_hbox), left_column_area,
183                      TRUE, TRUE, 0);
184   gtk_widget_set_size_request(left_column_area, left_column_min_width, -1);
185
186   GtkWidget* heading_vbox = gtk_vbox_new(FALSE, 0);
187   // If we are not going to show anything else, vertically center the title.
188   bool center_heading = !show_permissions && !show_oauth_issues &&
189                         !is_inline_install && !show_retained_files;
190   gtk_box_pack_start(GTK_BOX(left_column_area), heading_vbox, center_heading,
191                      center_heading, 0);
192
193   // Heading
194   GtkWidget* heading_label = gtk_util::CreateBoldLabel(
195       UTF16ToUTF8(prompt.GetHeading().c_str()));
196   gtk_util::SetLabelWidth(heading_label, left_column_min_width);
197   gtk_box_pack_start(GTK_BOX(heading_vbox), heading_label, center_heading,
198                      center_heading, 0);
199
200   if (is_inline_install) {
201     // Average rating (as stars) and number of ratings.
202     GtkWidget* stars_hbox = gtk_hbox_new(FALSE, 0);
203     gtk_box_pack_start(GTK_BOX(heading_vbox), stars_hbox, FALSE, FALSE, 0);
204     prompt.AppendRatingStars(AddResourceIcon, stars_hbox);
205     GtkWidget* rating_label = gtk_label_new(UTF16ToUTF8(
206         prompt.GetRatingCount()).c_str());
207     gtk_util::ForceFontSizePixels(rating_label, kRatingTextSize);
208     gtk_box_pack_start(GTK_BOX(stars_hbox), rating_label,
209                        FALSE, FALSE, 3);
210
211     // User count.
212     GtkWidget* users_label = gtk_label_new(UTF16ToUTF8(
213         prompt.GetUserCount()).c_str());
214     gtk_util::SetLabelWidth(users_label, left_column_min_width);
215     gtk_util::SetLabelColor(users_label, &ui::kGdkGray);
216     gtk_util::ForceFontSizePixels(rating_label, kRatingTextSize);
217     gtk_box_pack_start(GTK_BOX(heading_vbox), users_label,
218                        FALSE, FALSE, 0);
219
220     // Store link.
221     GtkWidget* store_link = gtk_chrome_link_button_new(
222         l10n_util::GetStringUTF8(IDS_EXTENSION_PROMPT_STORE_LINK).c_str());
223     gtk_util::ForceFontSizePixels(store_link, kRatingTextSize);
224     GtkWidget* store_link_hbox = gtk_hbox_new(FALSE, 0);
225     // Stick it in an hbox so it doesn't expand to the whole width.
226     gtk_box_pack_start(GTK_BOX(store_link_hbox), store_link, FALSE, FALSE, 0);
227     gtk_box_pack_start(GTK_BOX(heading_vbox), store_link_hbox, FALSE, FALSE, 0);
228     g_signal_connect(store_link, "clicked",
229                      G_CALLBACK(OnStoreLinkClickThunk), this);
230   }
231
232   if (is_bundle_install) {
233     // Add the list of extensions to be installed.
234     GtkWidget* extensions_vbox = gtk_vbox_new(FALSE, ui::kControlSpacing);
235     gtk_box_pack_start(GTK_BOX(heading_vbox), extensions_vbox, FALSE, FALSE,
236                        ui::kControlSpacing);
237
238     BundleInstaller::ItemList items = prompt.bundle()->GetItemsWithState(
239         BundleInstaller::Item::STATE_PENDING);
240     for (size_t i = 0; i < items.size(); ++i) {
241       GtkWidget* extension_label = gtk_label_new(UTF16ToUTF8(
242           items[i].GetNameForDisplay()).c_str());
243       gtk_util::SetLabelWidth(extension_label, left_column_min_width);
244       gtk_box_pack_start(GTK_BOX(extensions_vbox), extension_label,
245                          FALSE, FALSE, kExtensionsPadding);
246     }
247   } else {
248     // Resize the icon if necessary.
249     SkBitmap scaled_icon = *prompt.icon().ToSkBitmap();
250     if (scaled_icon.width() > kImageSize || scaled_icon.height() > kImageSize) {
251       scaled_icon = skia::ImageOperations::Resize(
252           scaled_icon, skia::ImageOperations::RESIZE_LANCZOS3,
253           kImageSize, kImageSize);
254     }
255
256     // Put icon in the right column.
257     GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(scaled_icon);
258     GtkWidget* icon = gtk_image_new_from_pixbuf(pixbuf);
259     g_object_unref(pixbuf);
260     gtk_box_pack_start(GTK_BOX(top_content_hbox), icon, FALSE, FALSE, 0);
261     // Top justify the image.
262     gtk_misc_set_alignment(GTK_MISC(icon), 0.5, 0.0);
263   }
264
265   // Permissions are shown separated by a divider for inline installs, or
266   // directly under the heading for regular installs (where we don't have
267   // the store data)
268   if (show_permissions) {
269     GtkWidget* permissions_container;
270     if (is_inline_install) {
271       permissions_container = content_vbox;
272       gtk_box_pack_start(GTK_BOX(content_vbox), gtk_hseparator_new(),
273                          FALSE, FALSE, ui::kControlSpacing);
274     } else {
275       permissions_container = left_column_area;
276     }
277
278     if (prompt.GetPermissionCount() > 0) {
279       GtkWidget* permissions_header = gtk_util::CreateBoldLabel(
280           UTF16ToUTF8(prompt.GetPermissionsHeading()).c_str());
281       gtk_util::SetLabelWidth(permissions_header, left_column_min_width);
282       gtk_box_pack_start(GTK_BOX(permissions_container), permissions_header,
283                          FALSE, FALSE, 0);
284
285       for (size_t i = 0; i < prompt.GetPermissionCount(); ++i) {
286         GtkWidget* permission_vbox = gtk_vbox_new(FALSE, 0);
287         std::string permission = l10n_util::GetStringFUTF8(
288             IDS_EXTENSION_PERMISSION_LINE, prompt.GetPermission(i));
289         GtkWidget* permission_label = gtk_label_new(permission.c_str());
290         gtk_util::SetLabelWidth(permission_label, left_column_min_width);
291         gtk_box_pack_start(GTK_BOX(permission_vbox), permission_label,
292                            FALSE, FALSE, 0);
293         if (!prompt.GetPermissionsDetails(i).empty()) {
294           std::vector<string16> details;
295           details.push_back(prompt.GetPermissionsDetails(i));
296           gtk_box_pack_start(
297               GTK_BOX(permission_vbox),
298               CreateDetailsWidget(details, left_column_min_width, false),
299               FALSE,
300               FALSE,
301               0);
302         }
303         gtk_box_pack_start(GTK_BOX(permissions_container), permission_vbox,
304                            FALSE, FALSE, kPermissionsPadding);
305       }
306     } else {
307       GtkWidget* permission_label = gtk_label_new(l10n_util::GetStringUTF8(
308           IDS_EXTENSION_NO_SPECIAL_PERMISSIONS).c_str());
309       gtk_util::SetLabelWidth(permission_label, left_column_min_width);
310       gtk_box_pack_start(GTK_BOX(permissions_container), permission_label,
311                          FALSE, FALSE, kPermissionsPadding);
312     }
313   }
314
315   if (show_oauth_issues) {
316     // If permissions are shown, then the scopes will go below them and take
317     // up the entire width of the dialog. Otherwise the scopes will go where
318     // the permissions usually go.
319     GtkWidget* oauth_issues_container =
320         show_permissions ? content_vbox : left_column_area;
321     int pixel_width = left_column_min_width +
322         (show_permissions ? kImageSize : 0);
323
324     GtkWidget* oauth_issues_header = gtk_util::CreateBoldLabel(
325         UTF16ToUTF8(prompt.GetOAuthHeading()).c_str());
326     gtk_util::SetLabelWidth(oauth_issues_header, pixel_width);
327     gtk_box_pack_start(GTK_BOX(oauth_issues_container), oauth_issues_header,
328                        FALSE, FALSE, 0);
329
330     for (size_t i = 0; i < prompt.GetOAuthIssueCount(); ++i) {
331       GtkWidget* issue_advice_widget =
332           CreateWidgetForIssueAdvice(prompt.GetOAuthIssue(i), pixel_width);
333       gtk_box_pack_start(GTK_BOX(oauth_issues_container), issue_advice_widget,
334                          FALSE, FALSE, kPermissionsPadding);
335     }
336   }
337
338   if (show_retained_files) {
339     GtkWidget* retained_files_container =
340         (show_permissions || show_oauth_issues) ? content_vbox
341                                                 : left_column_area;
342     int pixel_width =
343         left_column_min_width +
344         ((show_permissions || show_oauth_issues) ? kImageSize : 0);
345
346     GtkWidget* retained_files_header = gtk_util::CreateBoldLabel(
347         UTF16ToUTF8(prompt.GetRetainedFilesHeading()).c_str());
348     gtk_util::SetLabelWidth(retained_files_header, pixel_width);
349     gtk_box_pack_start(GTK_BOX(retained_files_container), retained_files_header,
350                        FALSE, FALSE, 0);
351
352     std::vector<string16> paths;
353     for (size_t i = 0; i < prompt.GetRetainedFileCount(); ++i) {
354       paths.push_back(prompt.GetRetainedFile(i));
355     }
356     gtk_box_pack_start(GTK_BOX(retained_files_container),
357                        CreateDetailsWidget(paths, pixel_width, false),
358                        FALSE,
359                        FALSE,
360                        kPermissionsPadding);
361   }
362
363   g_signal_connect(dialog_, "response", G_CALLBACK(OnResponseThunk), this);
364   gtk_window_set_resizable(GTK_WINDOW(dialog_), FALSE);
365
366   gtk_dialog_set_default_response(GTK_DIALOG(dialog_), GTK_RESPONSE_CLOSE);
367   gtk_widget_show_all(dialog_);
368
369   gtk_container_set_border_width(GTK_CONTAINER(content_area), 0);
370   gtk_container_set_border_width(
371       GTK_CONTAINER(gtk_dialog_get_action_area(GTK_DIALOG(dialog_))),
372       ui::kContentAreaBorder);
373   gtk_box_set_spacing(GTK_BOX(gtk_bin_get_child(GTK_BIN(dialog_))), 0);
374   GtkRequisition requisition;
375   gtk_widget_size_request(content_vbox, &requisition);
376   gtk_widget_set_size_request(
377       scrolled_window, requisition.width, requisition.height);
378   gtk_widget_grab_focus(close_button);
379 }
380
381 ExtensionInstallDialog::~ExtensionInstallDialog() {
382 }
383
384 void ExtensionInstallDialog::OnResponse(GtkWidget* dialog, int response_id) {
385   if (response_id == GTK_RESPONSE_ACCEPT)
386     delegate_->InstallUIProceed();
387   else
388     delegate_->InstallUIAbort(true);
389
390   gtk_widget_destroy(dialog_);
391   delete this;
392 }
393
394 void ExtensionInstallDialog::OnStoreLinkClick(GtkWidget* sender) {
395   GURL store_url(
396       extension_urls::GetWebstoreItemDetailURLPrefix() + extension_id_);
397   navigator_->OpenURL(OpenURLParams(
398       store_url, content::Referrer(), NEW_FOREGROUND_TAB,
399       content::PAGE_TRANSITION_LINK, false));
400
401   OnResponse(dialog_, GTK_RESPONSE_CLOSE);
402 }
403
404 GtkWidget* ExtensionInstallDialog::CreateWidgetForIssueAdvice(
405     const IssueAdviceInfoEntry& issue_advice, int pixel_width) {
406   GtkWidget* box = gtk_vbox_new(FALSE, 0);
407   GtkWidget* label = gtk_label_new(l10n_util::GetStringFUTF8(
408       IDS_EXTENSION_PERMISSION_LINE, issue_advice.description).c_str());
409   gtk_util::SetLabelWidth(label, pixel_width);
410   gtk_box_pack_start(GTK_BOX(box), label, TRUE, TRUE, 0);
411
412   if (!issue_advice.details.empty()) {
413     gtk_box_pack_start(
414         GTK_BOX(box),
415         CreateDetailsWidget(issue_advice.details, pixel_width, true),
416         TRUE,
417         TRUE,
418         0);
419   }
420   return box;
421 }
422
423 }  // namespace chrome
424
425 namespace {
426
427 void ShowExtensionInstallDialogImpl(
428     const ExtensionInstallPrompt::ShowParams& show_params,
429     ExtensionInstallPrompt::Delegate* delegate,
430     const ExtensionInstallPrompt::Prompt& prompt) {
431   new chrome::ExtensionInstallDialog(show_params, delegate, prompt);
432 }
433
434 }  // namespace
435
436 // static
437 ExtensionInstallPrompt::ShowDialogCallback
438 ExtensionInstallPrompt::GetDefaultShowDialogCallback() {
439   return base::Bind(&ShowExtensionInstallDialogImpl);
440 }