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 37 void CreateWebApplicationShortcutsDialogGtk::Show( 38 GtkWindow* parent, TabContentsWrapper* tab_contents) { 39 new CreateWebApplicationShortcutsDialogGtk(parent, tab_contents); 40 } 41 42 void CreateChromeApplicationShortcutsDialogGtk::Show(GtkWindow* parent, 43 const Extension* app) { 44 new CreateChromeApplicationShortcutsDialogGtk(parent, app); 45 } 46 47 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 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 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 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 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 198 void CreateApplicationShortcutsDialogGtk::OnErrorDialogResponse( 199 GtkWidget* widget, int response) { 200 Release(); 201 } 202 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 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 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 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 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:: 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. 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