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/manifest_handlers/icons_handler.h"
24 #include "content/public/browser/browser_thread.h"
25 #include "content/public/browser/web_contents.h"
26 #include "content/public/browser/web_contents_delegate.h"
27 #include "extensions/common/extension.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(base::UTF16ToUTF8(shortcut_info_
.description
));
165 std::string
title(base::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 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(menu_checkbox_
))) {
216 creation_locations
.applications_menu_location
=
217 create_in_chrome_apps_subdir_
?
218 ShellIntegration::APP_MENU_LOCATION_SUBDIR_CHROMEAPPS
:
219 ShellIntegration::APP_MENU_LOCATION_ROOT
;
221 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE
,
222 base::Bind(&CreateApplicationShortcutsDialogGtk::CreateDesktopShortcut
,
223 this, shortcut_info_
, creation_locations
));
231 void CreateApplicationShortcutsDialogGtk::OnErrorDialogResponse(
232 GtkWidget
* widget
, int response
) {
236 void CreateApplicationShortcutsDialogGtk::CreateDesktopShortcut(
237 const ShellIntegration::ShortcutInfo
& shortcut_info
,
238 const ShellIntegration::ShortcutLocations
& creation_locations
) {
239 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
240 ShellIntegrationLinux::CreateDesktopShortcut(shortcut_info
,
245 void CreateApplicationShortcutsDialogGtk::ShowErrorDialog() {
246 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
248 // Hide the create dialog so that the user can no longer interact with it.
249 gtk_widget_hide(create_dialog_
);
251 error_dialog_
= gtk_dialog_new_with_buttons(
252 l10n_util::GetStringUTF8(IDS_CREATE_SHORTCUTS_ERROR_TITLE
).c_str(),
254 (GtkDialogFlags
) (GTK_DIALOG_NO_SEPARATOR
),
258 gtk_widget_realize(error_dialog_
);
259 gtk_util::SetWindowSizeFromResources(
260 GTK_WINDOW(error_dialog_
),
261 IDS_CREATE_SHORTCUTS_ERROR_DIALOG_WIDTH_CHARS
,
262 IDS_CREATE_SHORTCUTS_ERROR_DIALOG_HEIGHT_LINES
,
264 GtkWidget
* content_area
=
265 gtk_dialog_get_content_area(GTK_DIALOG(error_dialog_
));
266 gtk_box_set_spacing(GTK_BOX(content_area
), ui::kContentAreaSpacing
);
268 GtkWidget
* vbox
= gtk_vbox_new(FALSE
, ui::kControlSpacing
);
269 gtk_container_add(GTK_CONTAINER(content_area
), vbox
);
271 // Label on top of the checkboxes.
272 GtkWidget
* description
= gtk_label_new(
273 l10n_util::GetStringUTF8(IDS_CREATE_SHORTCUTS_ERROR_LABEL
).c_str());
274 gtk_label_set_line_wrap(GTK_LABEL(description
), TRUE
);
275 gtk_misc_set_alignment(GTK_MISC(description
), 0, 0);
276 gtk_box_pack_start(GTK_BOX(vbox
), description
, FALSE
, FALSE
, 0);
278 g_signal_connect(error_dialog_
, "response",
279 G_CALLBACK(OnErrorDialogResponseThunk
), this);
280 gtk_widget_show_all(error_dialog_
);
283 void CreateApplicationShortcutsDialogGtk::OnToggleCheckbox(GtkWidget
* sender
) {
284 gboolean can_accept
= FALSE
;
286 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(desktop_checkbox_
)) ||
287 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(menu_checkbox_
))) {
291 gtk_dialog_set_response_sensitive(GTK_DIALOG(create_dialog_
),
296 CreateWebApplicationShortcutsDialogGtk::CreateWebApplicationShortcutsDialogGtk(
298 content::WebContents
* web_contents
)
299 : CreateApplicationShortcutsDialogGtk(parent
),
300 web_contents_(web_contents
) {
302 // Get shortcut information now, it's needed for our UI.
303 web_app::GetShortcutInfoForTab(web_contents
, &shortcut_info_
);
304 CreateIconPixBuf(shortcut_info_
.favicon
);
306 // Create URL app shortcuts in the top-level menu.
307 create_in_chrome_apps_subdir_
= false;
309 CreateDialogBox(parent
);
312 void CreateWebApplicationShortcutsDialogGtk::OnCreatedShortcut() {
313 Browser
* browser
= chrome::FindBrowserWithWebContents(web_contents_
);
315 chrome::ConvertTabToAppWindow(browser
, web_contents_
);
318 CreateChromeApplicationShortcutsDialogGtk::
319 CreateChromeApplicationShortcutsDialogGtk(
322 const Extension
* app
,
323 const base::Closure
& close_callback
)
324 : CreateApplicationShortcutsDialogGtk(parent
),
326 profile_path_(profile
->GetPath()),
327 close_callback_(close_callback
) {
329 // Place Chrome app shortcuts in the "Chrome Apps" submenu.
330 create_in_chrome_apps_subdir_
= true;
332 // Get shortcut information and icon now; they are needed for our UI.
333 web_app::UpdateShortcutInfoAndIconForApp(
336 &CreateChromeApplicationShortcutsDialogGtk::OnShortcutInfoLoaded
,
340 CreateChromeApplicationShortcutsDialogGtk::
341 ~CreateChromeApplicationShortcutsDialogGtk() {
342 if (!close_callback_
.is_null())
343 close_callback_
.Run();
346 // Called when the app's ShortcutInfo (with icon) is loaded.
347 void CreateChromeApplicationShortcutsDialogGtk::OnShortcutInfoLoaded(
348 const ShellIntegration::ShortcutInfo
& shortcut_info
) {
349 shortcut_info_
= shortcut_info
;
351 CreateIconPixBuf(shortcut_info_
.favicon
);
352 CreateDialogBox(parent_
);
355 void CreateChromeApplicationShortcutsDialogGtk::CreateDesktopShortcut(
356 const ShellIntegration::ShortcutInfo
& shortcut_info
,
357 const ShellIntegration::ShortcutLocations
& creation_locations
) {
358 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
360 if (web_app::CreateShortcutsOnFileThread(
361 shortcut_info
, creation_locations
,
362 web_app::SHORTCUT_CREATION_BY_USER
)) {
365 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
366 base::Bind(&CreateChromeApplicationShortcutsDialogGtk::ShowErrorDialog
,