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