1 // Copyright (c) 2011 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/environment.h"
10 #include "base/utf_string_conversions.h"
11 #include "chrome/browser/shell_integration.h"
12 #include "chrome/browser/ui/gtk/gtk_util.h"
13 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
14 #include "chrome/browser/ui/web_applications/web_app_ui.h"
15 #include "chrome/common/extensions/extension.h"
16 #include "chrome/common/extensions/extension_resource.h"
17 #include "content/browser/browser_thread.h"
18 #include "content/browser/tab_contents/tab_contents.h"
19 #include "content/browser/tab_contents/tab_contents_delegate.h"
20 #include "grit/chromium_strings.h"
21 #include "grit/generated_resources.h"
22 #include "grit/locale_settings.h"
23 #include "ui/base/l10n/l10n_util.h"
24 #include "ui/gfx/gtk_util.h"
25
26 namespace {
27
28 // Size (in pixels) of the icon preview.
29 const int kIconPreviewSizePixels = 32;
30
31 // Height (in lines) of the shortcut description label.
32 const int kDescriptionLabelHeightLines = 3;
33
34 } // namespace
35
36 // static
Show(GtkWindow * parent,TabContentsWrapper * tab_contents)37 void CreateWebApplicationShortcutsDialogGtk::Show(
38 GtkWindow* parent, TabContentsWrapper* tab_contents) {
39 new CreateWebApplicationShortcutsDialogGtk(parent, tab_contents);
40 }
41
Show(GtkWindow * parent,const Extension * app)42 void CreateChromeApplicationShortcutsDialogGtk::Show(GtkWindow* parent,
43 const Extension* app) {
44 new CreateChromeApplicationShortcutsDialogGtk(parent, app);
45 }
46
47
CreateApplicationShortcutsDialogGtk(GtkWindow * parent)48 CreateApplicationShortcutsDialogGtk::CreateApplicationShortcutsDialogGtk(
49 GtkWindow* parent)
50 : parent_(parent),
51 desktop_checkbox_(NULL),
52 menu_checkbox_(NULL),
53 favicon_pixbuf_(NULL),
54 create_dialog_(NULL),
55 error_dialog_(NULL) {
56 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
57
58 // Will be balanced by Release later.
59 AddRef();
60 }
61
CreateIconPixBuf(const SkBitmap & bitmap)62 void CreateApplicationShortcutsDialogGtk::CreateIconPixBuf(
63 const SkBitmap& bitmap) {
64 // Prepare the icon. Try to scale it if it's too small, otherwise it would
65 // look weird.
66 GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(&shortcut_info_.favicon);
67 int pixbuf_width = gdk_pixbuf_get_width(pixbuf);
68 int pixbuf_height = gdk_pixbuf_get_height(pixbuf);
69 if (pixbuf_width == pixbuf_height && pixbuf_width < kIconPreviewSizePixels) {
70 // Only scale the pixbuf if it's a square (for simplicity).
71 // Generally it should be square, if it's a favicon or app icon.
72 // Use the highest quality interpolation. The scaling is
73 // going to have low quality anyway, because the initial image
74 // is likely small.
75 favicon_pixbuf_ = gdk_pixbuf_scale_simple(pixbuf,
76 kIconPreviewSizePixels,
77 kIconPreviewSizePixels,
78 GDK_INTERP_HYPER);
79 g_object_unref(pixbuf);
80 } else {
81 favicon_pixbuf_ = pixbuf;
82 }
83 }
84
CreateDialogBox(GtkWindow * parent)85 void CreateApplicationShortcutsDialogGtk::CreateDialogBox(GtkWindow* parent) {
86 // Build the dialog.
87 create_dialog_ = gtk_dialog_new_with_buttons(
88 l10n_util::GetStringUTF8(IDS_CREATE_SHORTCUTS_TITLE).c_str(),
89 parent,
90 (GtkDialogFlags) (GTK_DIALOG_MODAL | GTK_DIALOG_NO_SEPARATOR),
91 NULL);
92 gtk_widget_realize(create_dialog_);
93 gtk_window_set_resizable(GTK_WINDOW(create_dialog_), false);
94 gtk_util::AddButtonToDialog(create_dialog_,
95 l10n_util::GetStringUTF8(IDS_CANCEL).c_str(),
96 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT);
97 gtk_util::AddButtonToDialog(create_dialog_,
98 l10n_util::GetStringUTF8(IDS_CREATE_SHORTCUTS_COMMIT).c_str(),
99 GTK_STOCK_APPLY, GTK_RESPONSE_ACCEPT);
100
101 GtkWidget* content_area = GTK_DIALOG(create_dialog_)->vbox;
102 gtk_box_set_spacing(GTK_BOX(content_area), gtk_util::kContentAreaSpacing);
103
104 GtkWidget* vbox = gtk_vbox_new(FALSE, gtk_util::kControlSpacing);
105 gtk_container_add(GTK_CONTAINER(content_area), vbox);
106
107 // Create a box containing basic information about the new shortcut: an image
108 // on the left, and a description on the right.
109 GtkWidget* hbox = gtk_hbox_new(FALSE, gtk_util::kControlSpacing);
110 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
111 gtk_container_set_border_width(GTK_CONTAINER(hbox),
112 gtk_util::kControlSpacing);
113
114 // Put the icon preview in place.
115 GtkWidget* favicon_image = gtk_image_new_from_pixbuf(favicon_pixbuf_);
116 gtk_box_pack_start(GTK_BOX(hbox), favicon_image, FALSE, FALSE, 0);
117
118 // Create the label with application shortcut description.
119 GtkWidget* description_label = gtk_label_new(NULL);
120 gtk_box_pack_start(GTK_BOX(hbox), description_label, FALSE, FALSE, 0);
121 gtk_label_set_line_wrap(GTK_LABEL(description_label), TRUE);
122 gtk_widget_realize(description_label);
123
124 // Set the size request on the label so it knows where to line wrap. The width
125 // is the desired size of the dialog less the space reserved for padding and
126 // the image.
127 int label_width;
128 gtk_util::GetWidgetSizeFromResources(
129 description_label,
130 IDS_CREATE_SHORTCUTS_DIALOG_WIDTH_CHARS, -1, &label_width, NULL);
131 label_width -= gtk_util::kControlSpacing * 3 +
132 gdk_pixbuf_get_width(favicon_pixbuf_);
133 gtk_util::SetLabelWidth(description_label, label_width);
134
135 std::string description(UTF16ToUTF8(shortcut_info_.description));
136 std::string title(UTF16ToUTF8(shortcut_info_.title));
137 gtk_label_set_text(GTK_LABEL(description_label),
138 (description.empty() ? title : description).c_str());
139
140 // Label on top of the checkboxes.
141 GtkWidget* checkboxes_label = gtk_label_new(
142 l10n_util::GetStringUTF8(IDS_CREATE_SHORTCUTS_LABEL).c_str());
143 gtk_misc_set_alignment(GTK_MISC(checkboxes_label), 0, 0);
144 gtk_box_pack_start(GTK_BOX(vbox), checkboxes_label, FALSE, FALSE, 0);
145
146 // Desktop checkbox.
147 desktop_checkbox_ = gtk_check_button_new_with_label(
148 l10n_util::GetStringUTF8(IDS_CREATE_SHORTCUTS_DESKTOP_CHKBOX).c_str());
149 gtk_box_pack_start(GTK_BOX(vbox), desktop_checkbox_, FALSE, FALSE, 0);
150 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(desktop_checkbox_), true);
151 g_signal_connect(desktop_checkbox_, "toggled",
152 G_CALLBACK(OnToggleCheckboxThunk), this);
153
154 // Menu checkbox.
155 menu_checkbox_ = gtk_check_button_new_with_label(
156 l10n_util::GetStringUTF8(IDS_CREATE_SHORTCUTS_MENU_CHKBOX).c_str());
157 gtk_box_pack_start(GTK_BOX(vbox), menu_checkbox_, FALSE, FALSE, 0);
158 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(menu_checkbox_), false);
159 g_signal_connect(menu_checkbox_, "toggled",
160 G_CALLBACK(OnToggleCheckboxThunk), this);
161
162 g_signal_connect(create_dialog_, "response",
163 G_CALLBACK(OnCreateDialogResponseThunk), this);
164 gtk_widget_show_all(create_dialog_);
165 }
166
~CreateApplicationShortcutsDialogGtk()167 CreateApplicationShortcutsDialogGtk::~CreateApplicationShortcutsDialogGtk() {
168 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
169
170 gtk_widget_destroy(create_dialog_);
171
172 if (error_dialog_)
173 gtk_widget_destroy(error_dialog_);
174
175 g_object_unref(favicon_pixbuf_);
176 }
177
OnCreateDialogResponse(GtkWidget * widget,int response)178 void CreateApplicationShortcutsDialogGtk::OnCreateDialogResponse(
179 GtkWidget* widget, int response) {
180 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
181
182 if (response == GTK_RESPONSE_ACCEPT) {
183 shortcut_info_.create_on_desktop =
184 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(desktop_checkbox_));
185 shortcut_info_.create_in_applications_menu =
186 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(menu_checkbox_));
187 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
188 NewRunnableMethod(this,
189 &CreateApplicationShortcutsDialogGtk::CreateDesktopShortcut,
190 shortcut_info_));
191
192 OnCreatedShortcut();
193 } else {
194 Release();
195 }
196 }
197
OnErrorDialogResponse(GtkWidget * widget,int response)198 void CreateApplicationShortcutsDialogGtk::OnErrorDialogResponse(
199 GtkWidget* widget, int response) {
200 Release();
201 }
202
CreateDesktopShortcut(const ShellIntegration::ShortcutInfo & shortcut_info)203 void CreateApplicationShortcutsDialogGtk::CreateDesktopShortcut(
204 const ShellIntegration::ShortcutInfo& shortcut_info) {
205 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
206
207 scoped_ptr<base::Environment> env(base::Environment::Create());
208
209 std::string shortcut_template;
210 if (ShellIntegration::GetDesktopShortcutTemplate(env.get(),
211 &shortcut_template)) {
212 ShellIntegration::CreateDesktopShortcut(shortcut_info,
213 shortcut_template);
214 Release();
215 } else {
216 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
217 NewRunnableMethod(this,
218 &CreateApplicationShortcutsDialogGtk::ShowErrorDialog));
219 }
220 }
221
ShowErrorDialog()222 void CreateApplicationShortcutsDialogGtk::ShowErrorDialog() {
223 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
224
225 // Hide the create dialog so that the user can no longer interact with it.
226 gtk_widget_hide(create_dialog_);
227
228 error_dialog_ = gtk_dialog_new_with_buttons(
229 l10n_util::GetStringUTF8(IDS_CREATE_SHORTCUTS_ERROR_TITLE).c_str(),
230 NULL,
231 (GtkDialogFlags) (GTK_DIALOG_NO_SEPARATOR),
232 GTK_STOCK_OK,
233 GTK_RESPONSE_ACCEPT,
234 NULL);
235 gtk_widget_realize(error_dialog_);
236 gtk_util::SetWindowSizeFromResources(
237 GTK_WINDOW(error_dialog_),
238 IDS_CREATE_SHORTCUTS_ERROR_DIALOG_WIDTH_CHARS,
239 IDS_CREATE_SHORTCUTS_ERROR_DIALOG_HEIGHT_LINES,
240 false); // resizable
241 GtkWidget* content_area = GTK_DIALOG(error_dialog_)->vbox;
242 gtk_box_set_spacing(GTK_BOX(content_area), gtk_util::kContentAreaSpacing);
243
244 GtkWidget* vbox = gtk_vbox_new(FALSE, gtk_util::kControlSpacing);
245 gtk_container_add(GTK_CONTAINER(content_area), vbox);
246
247 // Label on top of the checkboxes.
248 GtkWidget* description = gtk_label_new(
249 l10n_util::GetStringFUTF8(
250 IDS_CREATE_SHORTCUTS_ERROR_LABEL,
251 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)).c_str());
252 gtk_label_set_line_wrap(GTK_LABEL(description), TRUE);
253 gtk_misc_set_alignment(GTK_MISC(description), 0, 0);
254 gtk_box_pack_start(GTK_BOX(vbox), description, FALSE, FALSE, 0);
255
256 g_signal_connect(error_dialog_, "response",
257 G_CALLBACK(OnErrorDialogResponseThunk), this);
258 gtk_widget_show_all(error_dialog_);
259 }
260
OnToggleCheckbox(GtkWidget * sender)261 void CreateApplicationShortcutsDialogGtk::OnToggleCheckbox(GtkWidget* sender) {
262 gboolean can_accept = FALSE;
263
264 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(desktop_checkbox_)) ||
265 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(menu_checkbox_))) {
266 can_accept = TRUE;
267 }
268
269 gtk_dialog_set_response_sensitive(GTK_DIALOG(create_dialog_),
270 GTK_RESPONSE_ACCEPT,
271 can_accept);
272 }
273
CreateWebApplicationShortcutsDialogGtk(GtkWindow * parent,TabContentsWrapper * tab_contents)274 CreateWebApplicationShortcutsDialogGtk::CreateWebApplicationShortcutsDialogGtk(
275 GtkWindow* parent,
276 TabContentsWrapper* tab_contents)
277 : CreateApplicationShortcutsDialogGtk(parent),
278 tab_contents_(tab_contents) {
279
280 // Get shortcut information now, it's needed for our UI.
281 web_app::GetShortcutInfoForTab(tab_contents_, &shortcut_info_);
282 CreateIconPixBuf(shortcut_info_.favicon);
283
284 CreateDialogBox(parent);
285 }
286
OnCreatedShortcut()287 void CreateWebApplicationShortcutsDialogGtk::OnCreatedShortcut() {
288 if (tab_contents_->tab_contents()->delegate())
289 tab_contents_->tab_contents()->delegate()->ConvertContentsToApplication(
290 tab_contents_->tab_contents());
291 }
292
293 CreateChromeApplicationShortcutsDialogGtk::
CreateChromeApplicationShortcutsDialogGtk(GtkWindow * parent,const Extension * app)294 CreateChromeApplicationShortcutsDialogGtk(
295 GtkWindow* parent,
296 const Extension* app)
297 : CreateApplicationShortcutsDialogGtk(parent),
298 app_(app),
299 ALLOW_THIS_IN_INITIALIZER_LIST(tracker_(this)) {
300
301 // Get shortcut information now, it's needed for our UI.
302 shortcut_info_.extension_id = app_->id();
303 shortcut_info_.url = GURL(app_->launch_web_url());
304 shortcut_info_.title = UTF8ToUTF16(app_->name());
305 shortcut_info_.description = UTF8ToUTF16(app_->description());
306
307 // Get the icon.
308 const gfx::Size max_size(kIconPreviewSizePixels, kIconPreviewSizePixels);
309 ExtensionResource icon_resource = app_->GetIconResource(
310 kIconPreviewSizePixels, ExtensionIconSet::MATCH_BIGGER);
311
312 // If no icon exists that is the desired size or larger, get the
313 // largest icon available:
314 if (icon_resource.empty())
315 icon_resource = app_->GetIconResource(
316 kIconPreviewSizePixels, ExtensionIconSet::MATCH_SMALLER);
317
318 tracker_.LoadImage(app_,
319 icon_resource,
320 max_size,
321 ImageLoadingTracker::DONT_CACHE);
322 }
323
324 // Called by tracker_ when the app's icon is loaded.
OnImageLoaded(SkBitmap * image,const ExtensionResource & resource,int index)325 void CreateChromeApplicationShortcutsDialogGtk::OnImageLoaded(
326 SkBitmap* image, const ExtensionResource& resource, int index) {
327 if (image->isNull()) {
328 NOTREACHED() << "Corrupt image in profile?";
329 return;
330 }
331 shortcut_info_.favicon = *image;
332
333 CreateIconPixBuf(*image);
334 CreateDialogBox(parent_);
335 }
336