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 "chrome/browser/ui/gtk/create_application_shortcuts_dialog_gtk.h"
6
7#include <string>
8
9#include "base/bind.h"
10#include "base/environment.h"
11#include "base/strings/utf_string_conversions.h"
12#include "chrome/browser/profiles/profile.h"
13#include "chrome/browser/shell_integration.h"
14#include "chrome/browser/shell_integration_linux.h"
15#include "chrome/browser/ui/browser.h"
16#include "chrome/browser/ui/browser_commands.h"
17#include "chrome/browser/ui/browser_dialogs.h"
18#include "chrome/browser/ui/browser_finder.h"
19#include "chrome/browser/ui/gtk/gtk_util.h"
20#include "chrome/browser/ui/web_applications/web_app_ui.h"
21#include "chrome/browser/ui/webui/extensions/extension_icon_source.h"
22#include "chrome/browser/web_applications/web_app.h"
23#include "chrome/common/extensions/extension.h"
24#include "chrome/common/extensions/manifest_handlers/icons_handler.h"
25#include "content/public/browser/browser_thread.h"
26#include "content/public/browser/web_contents.h"
27#include "content/public/browser/web_contents_delegate.h"
28#include "grit/chromium_strings.h"
29#include "grit/generated_resources.h"
30#include "grit/locale_settings.h"
31#include "grit/theme_resources.h"
32#include "ui/base/gtk/gtk_hig_constants.h"
33#include "ui/base/l10n/l10n_util.h"
34#include "ui/gfx/gtk_util.h"
35#include "ui/gfx/image/image.h"
36#include "ui/gfx/image/image_family.h"
37#include "ui/gfx/image/image_skia.h"
38
39using content::BrowserThread;
40using extensions::Extension;
41
42namespace {
43
44// Size (in pixels) of the icon preview.
45const int kIconPreviewSizePixels = 32;
46
47// Minimum width (in pixels) of the shortcut description label.
48const int kDescriptionLabelMinimumWidthPixels = 200;
49
50// Height (in lines) of the shortcut description label.
51const int kDescriptionLabelHeightLines = 3;
52
53}  // namespace
54
55namespace chrome {
56
57void ShowCreateWebAppShortcutsDialog(gfx::NativeWindow parent_window,
58                                     content::WebContents* web_contents) {
59  new CreateWebApplicationShortcutsDialogGtk(parent_window, web_contents);
60}
61
62}  // namespace chrome
63
64void CreateChromeApplicationShortcutsDialogGtk::Show(GtkWindow* parent,
65                                                     Profile* profile,
66                                                     const Extension* app) {
67  new CreateChromeApplicationShortcutsDialogGtk(parent, profile, app);
68}
69
70
71CreateApplicationShortcutsDialogGtk::CreateApplicationShortcutsDialogGtk(
72    GtkWindow* parent)
73  : parent_(parent),
74    desktop_checkbox_(NULL),
75    menu_checkbox_(NULL),
76    favicon_pixbuf_(NULL),
77    create_dialog_(NULL),
78    error_dialog_(NULL) {
79  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
80
81  // Will be balanced by Release later.
82  AddRef();
83}
84
85void CreateApplicationShortcutsDialogGtk::CreateIconPixBuf(
86    const gfx::ImageFamily& image) {
87  // Get the icon closest to the desired preview size.
88  const gfx::Image* icon = image.GetBest(kIconPreviewSizePixels,
89                                         kIconPreviewSizePixels);
90  // There must be at least one icon in the image family.
91  CHECK(icon);
92  GdkPixbuf* pixbuf = icon->CopyGdkPixbuf();
93  // Prepare the icon. Scale it to the correct size to display in the dialog.
94  int pixbuf_width = gdk_pixbuf_get_width(pixbuf);
95  int pixbuf_height = gdk_pixbuf_get_height(pixbuf);
96  if (pixbuf_width == pixbuf_height) {
97    // Only scale the pixbuf if it's a square (for simplicity).
98    // Generally it should be square, if it's a favicon or app icon.
99    // Use the highest quality interpolation.
100    favicon_pixbuf_ = gdk_pixbuf_scale_simple(pixbuf,
101                                              kIconPreviewSizePixels,
102                                              kIconPreviewSizePixels,
103                                              GDK_INTERP_HYPER);
104    g_object_unref(pixbuf);
105  } else {
106    favicon_pixbuf_ = pixbuf;
107  }
108}
109
110void CreateApplicationShortcutsDialogGtk::CreateDialogBox(GtkWindow* parent) {
111  // Build the dialog.
112  create_dialog_ = gtk_dialog_new_with_buttons(
113      l10n_util::GetStringUTF8(IDS_CREATE_SHORTCUTS_TITLE).c_str(),
114      parent,
115      (GtkDialogFlags) (GTK_DIALOG_MODAL | GTK_DIALOG_NO_SEPARATOR),
116      NULL);
117  gtk_widget_realize(create_dialog_);
118  gtk_window_set_resizable(GTK_WINDOW(create_dialog_), false);
119  gtk_util::AddButtonToDialog(create_dialog_,
120      l10n_util::GetStringUTF8(IDS_CANCEL).c_str(),
121      GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT);
122  gtk_util::AddButtonToDialog(create_dialog_,
123      l10n_util::GetStringUTF8(IDS_CREATE_SHORTCUTS_COMMIT).c_str(),
124      GTK_STOCK_APPLY, GTK_RESPONSE_ACCEPT);
125
126  GtkWidget* content_area =
127      gtk_dialog_get_content_area(GTK_DIALOG(create_dialog_));
128  gtk_box_set_spacing(GTK_BOX(content_area), ui::kContentAreaSpacing);
129
130  GtkWidget* vbox = gtk_vbox_new(FALSE, ui::kControlSpacing);
131  gtk_container_add(GTK_CONTAINER(content_area), vbox);
132
133  // Create a box containing basic information about the new shortcut: an image
134  // on the left, and a description on the right.
135  GtkWidget* hbox = gtk_hbox_new(FALSE, ui::kControlSpacing);
136  gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
137  gtk_container_set_border_width(GTK_CONTAINER(hbox),
138                                 ui::kControlSpacing);
139
140  // Put the icon preview in place.
141  GtkWidget* favicon_image = gtk_image_new_from_pixbuf(favicon_pixbuf_);
142  gtk_box_pack_start(GTK_BOX(hbox), favicon_image, FALSE, FALSE, 0);
143
144  // Create the label with application shortcut description.
145  GtkWidget* description_label = gtk_label_new(NULL);
146  gtk_box_pack_start(GTK_BOX(hbox), description_label, FALSE, FALSE, 0);
147  gtk_label_set_line_wrap(GTK_LABEL(description_label), TRUE);
148  gtk_widget_realize(description_label);
149
150  // Set the size request on the label so it knows where to line wrap. The width
151  // is the desired size of the dialog less the space reserved for padding and
152  // the image.
153  int label_width;
154  gtk_util::GetWidgetSizeFromResources(
155      description_label,
156      IDS_CREATE_SHORTCUTS_DIALOG_WIDTH_CHARS, -1, &label_width, NULL);
157  label_width -= ui::kControlSpacing * 3 +
158      gdk_pixbuf_get_width(favicon_pixbuf_);
159  // Enforce a minimum width, so that very large icons do not cause the label
160  // width to shrink to unreadable size, or become negative (which would crash).
161  if (label_width < kDescriptionLabelMinimumWidthPixels)
162    label_width = kDescriptionLabelMinimumWidthPixels;
163  gtk_util::SetLabelWidth(description_label, label_width);
164
165  std::string description(UTF16ToUTF8(shortcut_info_.description));
166  std::string title(UTF16ToUTF8(shortcut_info_.title));
167  gtk_label_set_text(GTK_LABEL(description_label),
168                     (description.empty() ? title : description).c_str());
169
170  // Label on top of the checkboxes.
171  GtkWidget* checkboxes_label = gtk_label_new(
172      l10n_util::GetStringUTF8(IDS_CREATE_SHORTCUTS_LABEL).c_str());
173  gtk_misc_set_alignment(GTK_MISC(checkboxes_label), 0, 0);
174  gtk_box_pack_start(GTK_BOX(vbox), checkboxes_label, FALSE, FALSE, 0);
175
176  // Desktop checkbox.
177  desktop_checkbox_ = gtk_check_button_new_with_label(
178      l10n_util::GetStringUTF8(IDS_CREATE_SHORTCUTS_DESKTOP_CHKBOX).c_str());
179  gtk_box_pack_start(GTK_BOX(vbox), desktop_checkbox_, FALSE, FALSE, 0);
180  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(desktop_checkbox_), true);
181  g_signal_connect(desktop_checkbox_, "toggled",
182                   G_CALLBACK(OnToggleCheckboxThunk), this);
183
184  // Menu checkbox.
185  menu_checkbox_ = gtk_check_button_new_with_label(
186      l10n_util::GetStringUTF8(IDS_CREATE_SHORTCUTS_MENU_CHKBOX).c_str());
187  gtk_box_pack_start(GTK_BOX(vbox), menu_checkbox_, FALSE, FALSE, 0);
188  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(menu_checkbox_), false);
189  g_signal_connect(menu_checkbox_, "toggled",
190                   G_CALLBACK(OnToggleCheckboxThunk), this);
191
192  g_signal_connect(create_dialog_, "response",
193                   G_CALLBACK(OnCreateDialogResponseThunk), this);
194  gtk_widget_show_all(create_dialog_);
195}
196
197CreateApplicationShortcutsDialogGtk::~CreateApplicationShortcutsDialogGtk() {
198  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
199
200  gtk_widget_destroy(create_dialog_);
201
202  if (error_dialog_)
203    gtk_widget_destroy(error_dialog_);
204
205  g_object_unref(favicon_pixbuf_);
206}
207
208void CreateApplicationShortcutsDialogGtk::OnCreateDialogResponse(
209    GtkWidget* widget, int response) {
210  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
211
212  if (response == GTK_RESPONSE_ACCEPT) {
213    ShellIntegration::ShortcutLocations creation_locations;
214    creation_locations.on_desktop =
215        gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(desktop_checkbox_));
216    creation_locations.in_applications_menu =
217        gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(menu_checkbox_));
218    creation_locations.applications_menu_subdir = shortcut_menu_subdir_;
219    BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
220        base::Bind(&CreateApplicationShortcutsDialogGtk::CreateDesktopShortcut,
221                   this, shortcut_info_, creation_locations));
222
223    OnCreatedShortcut();
224  } else {
225    Release();
226  }
227}
228
229void CreateApplicationShortcutsDialogGtk::OnErrorDialogResponse(
230    GtkWidget* widget, int response) {
231  Release();
232}
233
234void CreateApplicationShortcutsDialogGtk::CreateDesktopShortcut(
235    const ShellIntegration::ShortcutInfo& shortcut_info,
236    const ShellIntegration::ShortcutLocations& creation_locations) {
237  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
238  ShellIntegrationLinux::CreateDesktopShortcut(shortcut_info,
239                                               creation_locations);
240  Release();
241}
242
243void CreateApplicationShortcutsDialogGtk::ShowErrorDialog() {
244  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
245
246  // Hide the create dialog so that the user can no longer interact with it.
247  gtk_widget_hide(create_dialog_);
248
249  error_dialog_ = gtk_dialog_new_with_buttons(
250      l10n_util::GetStringUTF8(IDS_CREATE_SHORTCUTS_ERROR_TITLE).c_str(),
251      NULL,
252      (GtkDialogFlags) (GTK_DIALOG_NO_SEPARATOR),
253      GTK_STOCK_OK,
254      GTK_RESPONSE_ACCEPT,
255      NULL);
256  gtk_widget_realize(error_dialog_);
257  gtk_util::SetWindowSizeFromResources(
258      GTK_WINDOW(error_dialog_),
259      IDS_CREATE_SHORTCUTS_ERROR_DIALOG_WIDTH_CHARS,
260      IDS_CREATE_SHORTCUTS_ERROR_DIALOG_HEIGHT_LINES,
261      false);  // resizable
262  GtkWidget* content_area =
263      gtk_dialog_get_content_area(GTK_DIALOG(error_dialog_));
264  gtk_box_set_spacing(GTK_BOX(content_area), ui::kContentAreaSpacing);
265
266  GtkWidget* vbox = gtk_vbox_new(FALSE, ui::kControlSpacing);
267  gtk_container_add(GTK_CONTAINER(content_area), vbox);
268
269  // Label on top of the checkboxes.
270  GtkWidget* description = gtk_label_new(
271      l10n_util::GetStringUTF8(IDS_CREATE_SHORTCUTS_ERROR_LABEL).c_str());
272  gtk_label_set_line_wrap(GTK_LABEL(description), TRUE);
273  gtk_misc_set_alignment(GTK_MISC(description), 0, 0);
274  gtk_box_pack_start(GTK_BOX(vbox), description, FALSE, FALSE, 0);
275
276  g_signal_connect(error_dialog_, "response",
277                   G_CALLBACK(OnErrorDialogResponseThunk), this);
278  gtk_widget_show_all(error_dialog_);
279}
280
281void CreateApplicationShortcutsDialogGtk::OnToggleCheckbox(GtkWidget* sender) {
282  gboolean can_accept = FALSE;
283
284  if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(desktop_checkbox_)) ||
285      gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(menu_checkbox_))) {
286    can_accept = TRUE;
287  }
288
289  gtk_dialog_set_response_sensitive(GTK_DIALOG(create_dialog_),
290                                    GTK_RESPONSE_ACCEPT,
291                                    can_accept);
292}
293
294CreateWebApplicationShortcutsDialogGtk::CreateWebApplicationShortcutsDialogGtk(
295    GtkWindow* parent,
296    content::WebContents* web_contents)
297  : CreateApplicationShortcutsDialogGtk(parent),
298    web_contents_(web_contents) {
299
300  // Get shortcut information now, it's needed for our UI.
301  web_app::GetShortcutInfoForTab(web_contents, &shortcut_info_);
302  CreateIconPixBuf(shortcut_info_.favicon);
303
304  // NOTE: Leave shortcut_menu_subdir_ blank to create URL app shortcuts in the
305  // top-level menu.
306
307  CreateDialogBox(parent);
308}
309
310void CreateWebApplicationShortcutsDialogGtk::OnCreatedShortcut() {
311  Browser* browser = chrome::FindBrowserWithWebContents(web_contents_);
312  if (browser)
313    chrome::ConvertTabToAppWindow(browser, web_contents_);
314}
315
316CreateChromeApplicationShortcutsDialogGtk::
317    CreateChromeApplicationShortcutsDialogGtk(
318        GtkWindow* parent,
319        Profile* profile,
320        const Extension* app)
321      : CreateApplicationShortcutsDialogGtk(parent),
322        app_(app),
323        profile_path_(profile->GetPath())  {
324
325  // Place Chrome app shortcuts in the "Chrome Apps" submenu.
326  shortcut_menu_subdir_ = web_app::GetAppShortcutsSubdirName();
327
328  // Get shortcut information and icon now; they are needed for our UI.
329  web_app::UpdateShortcutInfoAndIconForApp(
330      *app, profile,
331      base::Bind(
332          &CreateChromeApplicationShortcutsDialogGtk::OnShortcutInfoLoaded,
333          this));
334}
335
336// Called when the app's ShortcutInfo (with icon) is loaded.
337void CreateChromeApplicationShortcutsDialogGtk::OnShortcutInfoLoaded(
338  const ShellIntegration::ShortcutInfo& shortcut_info) {
339  shortcut_info_ = shortcut_info;
340
341  CreateIconPixBuf(shortcut_info_.favicon);
342  CreateDialogBox(parent_);
343}
344
345void CreateChromeApplicationShortcutsDialogGtk::CreateDesktopShortcut(
346    const ShellIntegration::ShortcutInfo& shortcut_info,
347    const ShellIntegration::ShortcutLocations& creation_locations) {
348  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
349
350  if (web_app::CreateShortcutsOnFileThread(
351          shortcut_info, creation_locations,
352          web_app::SHORTCUT_CREATION_BY_USER)) {
353    Release();
354  } else {
355    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
356        base::Bind(&CreateChromeApplicationShortcutsDialogGtk::ShowErrorDialog,
357                   this));
358  }
359}
360