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