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 "chrome/browser/ui/gtk/create_application_shortcuts_dialog_gtk.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"
39 using content::BrowserThread;
40 using extensions::Extension;
44 // Size (in pixels) of the icon preview.
45 const int kIconPreviewSizePixels = 32;
47 // Minimum width (in pixels) of the shortcut description label.
48 const int kDescriptionLabelMinimumWidthPixels = 200;
54 void ShowCreateWebAppShortcutsDialog(gfx::NativeWindow parent_window,
55 content::WebContents* web_contents) {
56 new CreateWebApplicationShortcutsDialogGtk(parent_window, web_contents);
59 void ShowCreateChromeAppShortcutsDialog(
60 gfx::NativeWindow parent_window,
62 const extensions::Extension* app,
63 const base::Closure& close_callback) {
64 new CreateChromeApplicationShortcutsDialogGtk(parent_window, profile, app,
70 CreateApplicationShortcutsDialogGtk::CreateApplicationShortcutsDialogGtk(
73 desktop_checkbox_(NULL),
75 favicon_pixbuf_(NULL),
78 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
80 // Will be balanced by Release later.
84 void CreateApplicationShortcutsDialogGtk::CreateIconPixBuf(
85 const gfx::ImageFamily& image) {
86 // Get the icon closest to the desired preview size.
87 const gfx::Image* icon = image.GetBest(kIconPreviewSizePixels,
88 kIconPreviewSizePixels);
89 // There must be at least one icon in the image family.
91 GdkPixbuf* pixbuf = icon->CopyGdkPixbuf();
92 // Prepare the icon. Scale it to the correct size to display in the dialog.
93 int pixbuf_width = gdk_pixbuf_get_width(pixbuf);
94 int pixbuf_height = gdk_pixbuf_get_height(pixbuf);
95 if (pixbuf_width == pixbuf_height) {
96 // Only scale the pixbuf if it's a square (for simplicity).
97 // Generally it should be square, if it's a favicon or app icon.
98 // Use the highest quality interpolation.
99 favicon_pixbuf_ = gdk_pixbuf_scale_simple(pixbuf,
100 kIconPreviewSizePixels,
101 kIconPreviewSizePixels,
103 g_object_unref(pixbuf);
105 favicon_pixbuf_ = pixbuf;
109 void CreateApplicationShortcutsDialogGtk::CreateDialogBox(GtkWindow* parent) {
111 create_dialog_ = gtk_dialog_new_with_buttons(
112 l10n_util::GetStringUTF8(IDS_CREATE_SHORTCUTS_TITLE).c_str(),
114 (GtkDialogFlags) (GTK_DIALOG_MODAL | GTK_DIALOG_NO_SEPARATOR),
116 gtk_widget_realize(create_dialog_);
117 gtk_window_set_resizable(GTK_WINDOW(create_dialog_), false);
118 gtk_util::AddButtonToDialog(create_dialog_,
119 l10n_util::GetStringUTF8(IDS_CANCEL).c_str(),
120 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT);
121 gtk_util::AddButtonToDialog(create_dialog_,
122 l10n_util::GetStringUTF8(IDS_CREATE_SHORTCUTS_COMMIT).c_str(),
123 GTK_STOCK_APPLY, GTK_RESPONSE_ACCEPT);
125 GtkWidget* content_area =
126 gtk_dialog_get_content_area(GTK_DIALOG(create_dialog_));
127 gtk_box_set_spacing(GTK_BOX(content_area), ui::kContentAreaSpacing);
129 GtkWidget* vbox = gtk_vbox_new(FALSE, ui::kControlSpacing);
130 gtk_container_add(GTK_CONTAINER(content_area), vbox);
132 // Create a box containing basic information about the new shortcut: an image
133 // on the left, and a description on the right.
134 GtkWidget* hbox = gtk_hbox_new(FALSE, ui::kControlSpacing);
135 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
136 gtk_container_set_border_width(GTK_CONTAINER(hbox),
137 ui::kControlSpacing);
139 // Put the icon preview in place.
140 GtkWidget* favicon_image = gtk_image_new_from_pixbuf(favicon_pixbuf_);
141 gtk_box_pack_start(GTK_BOX(hbox), favicon_image, FALSE, FALSE, 0);
143 // Create the label with application shortcut description.
144 GtkWidget* description_label = gtk_label_new(NULL);
145 gtk_box_pack_start(GTK_BOX(hbox), description_label, FALSE, FALSE, 0);
146 gtk_label_set_line_wrap(GTK_LABEL(description_label), TRUE);
147 gtk_widget_realize(description_label);
149 // Set the size request on the label so it knows where to line wrap. The width
150 // is the desired size of the dialog less the space reserved for padding and
153 gtk_util::GetWidgetSizeFromResources(
155 IDS_CREATE_SHORTCUTS_DIALOG_WIDTH_CHARS, -1, &label_width, NULL);
156 label_width -= ui::kControlSpacing * 3 +
157 gdk_pixbuf_get_width(favicon_pixbuf_);
158 // Enforce a minimum width, so that very large icons do not cause the label
159 // width to shrink to unreadable size, or become negative (which would crash).
160 if (label_width < kDescriptionLabelMinimumWidthPixels)
161 label_width = kDescriptionLabelMinimumWidthPixels;
162 gtk_util::SetLabelWidth(description_label, label_width);
164 std::string description(UTF16ToUTF8(shortcut_info_.description));
165 std::string title(UTF16ToUTF8(shortcut_info_.title));
166 gtk_label_set_text(GTK_LABEL(description_label),
167 (description.empty() ? title : description).c_str());
169 // Label on top of the checkboxes.
170 GtkWidget* checkboxes_label = gtk_label_new(
171 l10n_util::GetStringUTF8(IDS_CREATE_SHORTCUTS_LABEL).c_str());
172 gtk_misc_set_alignment(GTK_MISC(checkboxes_label), 0, 0);
173 gtk_box_pack_start(GTK_BOX(vbox), checkboxes_label, FALSE, FALSE, 0);
176 desktop_checkbox_ = gtk_check_button_new_with_label(
177 l10n_util::GetStringUTF8(IDS_CREATE_SHORTCUTS_DESKTOP_CHKBOX).c_str());
178 gtk_box_pack_start(GTK_BOX(vbox), desktop_checkbox_, FALSE, FALSE, 0);
179 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(desktop_checkbox_), true);
180 g_signal_connect(desktop_checkbox_, "toggled",
181 G_CALLBACK(OnToggleCheckboxThunk), this);
184 menu_checkbox_ = gtk_check_button_new_with_label(
185 l10n_util::GetStringUTF8(IDS_CREATE_SHORTCUTS_MENU_CHKBOX).c_str());
186 gtk_box_pack_start(GTK_BOX(vbox), menu_checkbox_, FALSE, FALSE, 0);
187 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(menu_checkbox_), false);
188 g_signal_connect(menu_checkbox_, "toggled",
189 G_CALLBACK(OnToggleCheckboxThunk), this);
191 g_signal_connect(create_dialog_, "response",
192 G_CALLBACK(OnCreateDialogResponseThunk), this);
193 gtk_widget_show_all(create_dialog_);
196 CreateApplicationShortcutsDialogGtk::~CreateApplicationShortcutsDialogGtk() {
197 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
199 gtk_widget_destroy(create_dialog_);
202 gtk_widget_destroy(error_dialog_);
204 g_object_unref(favicon_pixbuf_);
207 void CreateApplicationShortcutsDialogGtk::OnCreateDialogResponse(
208 GtkWidget* widget, int response) {
209 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
211 if (response == GTK_RESPONSE_ACCEPT) {
212 ShellIntegration::ShortcutLocations creation_locations;
213 creation_locations.on_desktop =
214 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(desktop_checkbox_));
215 creation_locations.in_applications_menu =
216 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(menu_checkbox_));
217 creation_locations.applications_menu_subdir = shortcut_menu_subdir_;
218 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
219 base::Bind(&CreateApplicationShortcutsDialogGtk::CreateDesktopShortcut,
220 this, shortcut_info_, creation_locations));
228 void CreateApplicationShortcutsDialogGtk::OnErrorDialogResponse(
229 GtkWidget* widget, int response) {
233 void CreateApplicationShortcutsDialogGtk::CreateDesktopShortcut(
234 const ShellIntegration::ShortcutInfo& shortcut_info,
235 const ShellIntegration::ShortcutLocations& creation_locations) {
236 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
237 ShellIntegrationLinux::CreateDesktopShortcut(shortcut_info,
242 void CreateApplicationShortcutsDialogGtk::ShowErrorDialog() {
243 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
245 // Hide the create dialog so that the user can no longer interact with it.
246 gtk_widget_hide(create_dialog_);
248 error_dialog_ = gtk_dialog_new_with_buttons(
249 l10n_util::GetStringUTF8(IDS_CREATE_SHORTCUTS_ERROR_TITLE).c_str(),
251 (GtkDialogFlags) (GTK_DIALOG_NO_SEPARATOR),
255 gtk_widget_realize(error_dialog_);
256 gtk_util::SetWindowSizeFromResources(
257 GTK_WINDOW(error_dialog_),
258 IDS_CREATE_SHORTCUTS_ERROR_DIALOG_WIDTH_CHARS,
259 IDS_CREATE_SHORTCUTS_ERROR_DIALOG_HEIGHT_LINES,
261 GtkWidget* content_area =
262 gtk_dialog_get_content_area(GTK_DIALOG(error_dialog_));
263 gtk_box_set_spacing(GTK_BOX(content_area), ui::kContentAreaSpacing);
265 GtkWidget* vbox = gtk_vbox_new(FALSE, ui::kControlSpacing);
266 gtk_container_add(GTK_CONTAINER(content_area), vbox);
268 // Label on top of the checkboxes.
269 GtkWidget* description = gtk_label_new(
270 l10n_util::GetStringUTF8(IDS_CREATE_SHORTCUTS_ERROR_LABEL).c_str());
271 gtk_label_set_line_wrap(GTK_LABEL(description), TRUE);
272 gtk_misc_set_alignment(GTK_MISC(description), 0, 0);
273 gtk_box_pack_start(GTK_BOX(vbox), description, FALSE, FALSE, 0);
275 g_signal_connect(error_dialog_, "response",
276 G_CALLBACK(OnErrorDialogResponseThunk), this);
277 gtk_widget_show_all(error_dialog_);
280 void CreateApplicationShortcutsDialogGtk::OnToggleCheckbox(GtkWidget* sender) {
281 gboolean can_accept = FALSE;
283 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(desktop_checkbox_)) ||
284 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(menu_checkbox_))) {
288 gtk_dialog_set_response_sensitive(GTK_DIALOG(create_dialog_),
293 CreateWebApplicationShortcutsDialogGtk::CreateWebApplicationShortcutsDialogGtk(
295 content::WebContents* web_contents)
296 : CreateApplicationShortcutsDialogGtk(parent),
297 web_contents_(web_contents) {
299 // Get shortcut information now, it's needed for our UI.
300 web_app::GetShortcutInfoForTab(web_contents, &shortcut_info_);
301 CreateIconPixBuf(shortcut_info_.favicon);
303 // NOTE: Leave shortcut_menu_subdir_ blank to create URL app shortcuts in the
306 CreateDialogBox(parent);
309 void CreateWebApplicationShortcutsDialogGtk::OnCreatedShortcut() {
310 Browser* browser = chrome::FindBrowserWithWebContents(web_contents_);
312 chrome::ConvertTabToAppWindow(browser, web_contents_);
315 CreateChromeApplicationShortcutsDialogGtk::
316 CreateChromeApplicationShortcutsDialogGtk(
319 const Extension* app,
320 const base::Closure& close_callback)
321 : CreateApplicationShortcutsDialogGtk(parent),
323 profile_path_(profile->GetPath()),
324 close_callback_(close_callback) {
326 // Place Chrome app shortcuts in the "Chrome Apps" submenu.
327 shortcut_menu_subdir_ = web_app::GetAppShortcutsSubdirName();
329 // Get shortcut information and icon now; they are needed for our UI.
330 web_app::UpdateShortcutInfoAndIconForApp(
333 &CreateChromeApplicationShortcutsDialogGtk::OnShortcutInfoLoaded,
337 CreateChromeApplicationShortcutsDialogGtk::
338 ~CreateChromeApplicationShortcutsDialogGtk() {
339 if (!close_callback_.is_null())
340 close_callback_.Run();
343 // Called when the app's ShortcutInfo (with icon) is loaded.
344 void CreateChromeApplicationShortcutsDialogGtk::OnShortcutInfoLoaded(
345 const ShellIntegration::ShortcutInfo& shortcut_info) {
346 shortcut_info_ = shortcut_info;
348 CreateIconPixBuf(shortcut_info_.favicon);
349 CreateDialogBox(parent_);
352 void CreateChromeApplicationShortcutsDialogGtk::CreateDesktopShortcut(
353 const ShellIntegration::ShortcutInfo& shortcut_info,
354 const ShellIntegration::ShortcutLocations& creation_locations) {
355 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
357 if (web_app::CreateShortcutsOnFileThread(
358 shortcut_info, creation_locations,
359 web_app::SHORTCUT_CREATION_BY_USER)) {
362 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
363 base::Bind(&CreateChromeApplicationShortcutsDialogGtk::ShowErrorDialog,