| 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/printing/print_dialog_gtk.h" | |
| 6 | |
| 7 #include <gtk/gtkunixprint.h> | |
| 8 | |
| 9 #include <string> | |
| 10 #include <vector> | |
| 11 | |
| 12 #include "base/bind.h" | |
| 13 #include "base/file_util.h" | |
| 14 #include "base/files/file_util_proxy.h" | |
| 15 #include "base/lazy_instance.h" | |
| 16 #include "base/logging.h" | |
| 17 #include "base/message_loop/message_loop_proxy.h" | |
| 18 #include "base/strings/utf_string_conversions.h" | |
| 19 #include "base/values.h" | |
| 20 #include "printing/metafile.h" | |
| 21 #include "printing/print_job_constants.h" | |
| 22 #include "printing/print_settings.h" | |
| 23 #include "printing/print_settings_initializer_gtk.h" | |
| 24 | |
| 25 using content::BrowserThread; | |
| 26 using printing::PageRanges; | |
| 27 using printing::PrintSettings; | |
| 28 | |
| 29 namespace { | |
| 30 | |
| 31 // CUPS Duplex attribute and values. | |
| 32 const char kCUPSDuplex[] = "cups-Duplex"; | |
| 33 const char kDuplexNone[] = "None"; | |
| 34 const char kDuplexTumble[] = "DuplexTumble"; | |
| 35 const char kDuplexNoTumble[] = "DuplexNoTumble"; | |
| 36 | |
| 37 class StickyPrintSettingGtk { | |
| 38 public: | |
| 39 StickyPrintSettingGtk() : last_used_settings_(gtk_print_settings_new()) { | |
| 40 } | |
| 41 ~StickyPrintSettingGtk() { | |
| 42 NOTREACHED(); // Intended to be used with a Leaky LazyInstance. | |
| 43 } | |
| 44 | |
| 45 GtkPrintSettings* settings() { | |
| 46 return last_used_settings_; | |
| 47 } | |
| 48 | |
| 49 void SetLastUsedSettings(GtkPrintSettings* settings) { | |
| 50 DCHECK(last_used_settings_); | |
| 51 g_object_unref(last_used_settings_); | |
| 52 last_used_settings_ = gtk_print_settings_copy(settings); | |
| 53 } | |
| 54 | |
| 55 private: | |
| 56 GtkPrintSettings* last_used_settings_; | |
| 57 | |
| 58 DISALLOW_COPY_AND_ASSIGN(StickyPrintSettingGtk); | |
| 59 }; | |
| 60 | |
| 61 base::LazyInstance<StickyPrintSettingGtk>::Leaky g_last_used_settings = | |
| 62 LAZY_INSTANCE_INITIALIZER; | |
| 63 | |
| 64 // Helper class to track GTK printers. | |
| 65 class GtkPrinterList { | |
| 66 public: | |
| 67 GtkPrinterList() : default_printer_(NULL) { | |
| 68 gtk_enumerate_printers(SetPrinter, this, NULL, TRUE); | |
| 69 } | |
| 70 | |
| 71 ~GtkPrinterList() { | |
| 72 for (std::vector<GtkPrinter*>::iterator it = printers_.begin(); | |
| 73 it < printers_.end(); ++it) { | |
| 74 g_object_unref(*it); | |
| 75 } | |
| 76 } | |
| 77 | |
| 78 // Can return NULL if there's no default printer. E.g. Printer on a laptop | |
| 79 // is "home_printer", but the laptop is at work. | |
| 80 GtkPrinter* default_printer() { | |
| 81 return default_printer_; | |
| 82 } | |
| 83 | |
| 84 // Can return NULL if the printer cannot be found due to: | |
| 85 // - Printer list out of sync with printer dialog UI. | |
| 86 // - Querying for non-existant printers like 'Print to PDF'. | |
| 87 GtkPrinter* GetPrinterWithName(const std::string& name) { | |
| 88 if (name.empty()) | |
| 89 return NULL; | |
| 90 | |
| 91 for (std::vector<GtkPrinter*>::iterator it = printers_.begin(); | |
| 92 it < printers_.end(); ++it) { | |
| 93 if (gtk_printer_get_name(*it) == name) { | |
| 94 return *it; | |
| 95 } | |
| 96 } | |
| 97 | |
| 98 return NULL; | |
| 99 } | |
| 100 | |
| 101 private: | |
| 102 // Callback function used by gtk_enumerate_printers() to get all printer. | |
| 103 static gboolean SetPrinter(GtkPrinter* printer, gpointer data) { | |
| 104 GtkPrinterList* printer_list = reinterpret_cast<GtkPrinterList*>(data); | |
| 105 if (gtk_printer_is_default(printer)) | |
| 106 printer_list->default_printer_ = printer; | |
| 107 | |
| 108 g_object_ref(printer); | |
| 109 printer_list->printers_.push_back(printer); | |
| 110 | |
| 111 return FALSE; | |
| 112 } | |
| 113 | |
| 114 std::vector<GtkPrinter*> printers_; | |
| 115 GtkPrinter* default_printer_; | |
| 116 }; | |
| 117 | |
| 118 } // namespace | |
| 119 | |
| 120 // static | |
| 121 printing::PrintDialogGtkInterface* PrintDialogGtk::CreatePrintDialog( | |
| 122 PrintingContextLinux* context) { | |
| 123 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 124 return new PrintDialogGtk(context); | |
| 125 } | |
| 126 | |
| 127 PrintDialogGtk::PrintDialogGtk(PrintingContextLinux* context) | |
| 128 : context_(context), | |
| 129 dialog_(NULL), | |
| 130 gtk_settings_(NULL), | |
| 131 page_setup_(NULL), | |
| 132 printer_(NULL) { | |
| 133 } | |
| 134 | |
| 135 PrintDialogGtk::~PrintDialogGtk() { | |
| 136 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 137 | |
| 138 if (dialog_) { | |
| 139 gtk_widget_destroy(dialog_); | |
| 140 dialog_ = NULL; | |
| 141 } | |
| 142 if (gtk_settings_) { | |
| 143 g_object_unref(gtk_settings_); | |
| 144 gtk_settings_ = NULL; | |
| 145 } | |
| 146 if (page_setup_) { | |
| 147 g_object_unref(page_setup_); | |
| 148 page_setup_ = NULL; | |
| 149 } | |
| 150 if (printer_) { | |
| 151 g_object_unref(printer_); | |
| 152 printer_ = NULL; | |
| 153 } | |
| 154 } | |
| 155 | |
| 156 void PrintDialogGtk::UseDefaultSettings() { | |
| 157 DCHECK(!page_setup_); | |
| 158 DCHECK(!printer_); | |
| 159 | |
| 160 // |gtk_settings_| is a new copy. | |
| 161 gtk_settings_ = | |
| 162 gtk_print_settings_copy(g_last_used_settings.Get().settings()); | |
| 163 page_setup_ = gtk_page_setup_new(); | |
| 164 | |
| 165 PrintSettings settings; | |
| 166 InitPrintSettings(&settings); | |
| 167 } | |
| 168 | |
| 169 bool PrintDialogGtk::UpdateSettings(printing::PrintSettings* settings) { | |
| 170 if (!gtk_settings_) { | |
| 171 gtk_settings_ = | |
| 172 gtk_print_settings_copy(g_last_used_settings.Get().settings()); | |
| 173 } | |
| 174 | |
| 175 scoped_ptr<GtkPrinterList> printer_list(new GtkPrinterList); | |
| 176 printer_ = printer_list->GetPrinterWithName( | |
| 177 base::UTF16ToUTF8(settings->device_name())); | |
| 178 if (printer_) { | |
| 179 g_object_ref(printer_); | |
| 180 gtk_print_settings_set_printer(gtk_settings_, | |
| 181 gtk_printer_get_name(printer_)); | |
| 182 if (!page_setup_) { | |
| 183 page_setup_ = gtk_printer_get_default_page_size(printer_); | |
| 184 } | |
| 185 } | |
| 186 | |
| 187 gtk_print_settings_set_n_copies(gtk_settings_, settings->copies()); | |
| 188 gtk_print_settings_set_collate(gtk_settings_, settings->collate()); | |
| 189 | |
| 190 #if defined(USE_CUPS) | |
| 191 std::string color_value; | |
| 192 std::string color_setting_name; | |
| 193 printing::GetColorModelForMode(settings->color(), &color_setting_name, | |
| 194 &color_value); | |
| 195 gtk_print_settings_set(gtk_settings_, color_setting_name.c_str(), | |
| 196 color_value.c_str()); | |
| 197 | |
| 198 if (settings->duplex_mode() != printing::UNKNOWN_DUPLEX_MODE) { | |
| 199 const char* cups_duplex_mode = NULL; | |
| 200 switch (settings->duplex_mode()) { | |
| 201 case printing::LONG_EDGE: | |
| 202 cups_duplex_mode = kDuplexNoTumble; | |
| 203 break; | |
| 204 case printing::SHORT_EDGE: | |
| 205 cups_duplex_mode = kDuplexTumble; | |
| 206 break; | |
| 207 case printing::SIMPLEX: | |
| 208 cups_duplex_mode = kDuplexNone; | |
| 209 break; | |
| 210 default: // UNKNOWN_DUPLEX_MODE | |
| 211 NOTREACHED(); | |
| 212 break; | |
| 213 } | |
| 214 gtk_print_settings_set(gtk_settings_, kCUPSDuplex, cups_duplex_mode); | |
| 215 } | |
| 216 #endif | |
| 217 if (!page_setup_) | |
| 218 page_setup_ = gtk_page_setup_new(); | |
| 219 | |
| 220 gtk_print_settings_set_orientation( | |
| 221 gtk_settings_, | |
| 222 settings->landscape() ? GTK_PAGE_ORIENTATION_LANDSCAPE : | |
| 223 GTK_PAGE_ORIENTATION_PORTRAIT); | |
| 224 | |
| 225 InitPrintSettings(settings); | |
| 226 return true; | |
| 227 } | |
| 228 | |
| 229 void PrintDialogGtk::ShowDialog( | |
| 230 gfx::NativeView parent_view, | |
| 231 bool has_selection, | |
| 232 const PrintingContextLinux::PrintSettingsCallback& callback) { | |
| 233 callback_ = callback; | |
| 234 | |
| 235 GtkWindow* parent = GTK_WINDOW(gtk_widget_get_toplevel(parent_view)); | |
| 236 // TODO(estade): We need a window title here. | |
| 237 dialog_ = gtk_print_unix_dialog_new(NULL, parent); | |
| 238 g_signal_connect(dialog_, "delete-event", | |
| 239 G_CALLBACK(gtk_widget_hide_on_delete), NULL); | |
| 240 | |
| 241 | |
| 242 // Set modal so user cannot focus the same tab and press print again. | |
| 243 gtk_window_set_modal(GTK_WINDOW(dialog_), TRUE); | |
| 244 | |
| 245 // Since we only generate PDF, only show printers that support PDF. | |
| 246 // TODO(thestig) Add more capabilities to support? | |
| 247 GtkPrintCapabilities cap = static_cast<GtkPrintCapabilities>( | |
| 248 GTK_PRINT_CAPABILITY_GENERATE_PDF | | |
| 249 GTK_PRINT_CAPABILITY_PAGE_SET | | |
| 250 GTK_PRINT_CAPABILITY_COPIES | | |
| 251 GTK_PRINT_CAPABILITY_COLLATE | | |
| 252 GTK_PRINT_CAPABILITY_REVERSE); | |
| 253 gtk_print_unix_dialog_set_manual_capabilities(GTK_PRINT_UNIX_DIALOG(dialog_), | |
| 254 cap); | |
| 255 gtk_print_unix_dialog_set_embed_page_setup(GTK_PRINT_UNIX_DIALOG(dialog_), | |
| 256 TRUE); | |
| 257 gtk_print_unix_dialog_set_support_selection(GTK_PRINT_UNIX_DIALOG(dialog_), | |
| 258 TRUE); | |
| 259 gtk_print_unix_dialog_set_has_selection(GTK_PRINT_UNIX_DIALOG(dialog_), | |
| 260 has_selection); | |
| 261 gtk_print_unix_dialog_set_settings(GTK_PRINT_UNIX_DIALOG(dialog_), | |
| 262 gtk_settings_); | |
| 263 g_signal_connect(dialog_, "response", G_CALLBACK(OnResponseThunk), this); | |
| 264 gtk_widget_show(dialog_); | |
| 265 } | |
| 266 | |
| 267 void PrintDialogGtk::PrintDocument(const printing::Metafile* metafile, | |
| 268 const base::string16& document_name) { | |
| 269 // This runs on the print worker thread, does not block the UI thread. | |
| 270 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 271 | |
| 272 // The document printing tasks can outlive the PrintingContext that created | |
| 273 // this dialog. | |
| 274 AddRef(); | |
| 275 | |
| 276 bool error = false; | |
| 277 if (!base::CreateTemporaryFile(&path_to_pdf_)) { | |
| 278 LOG(ERROR) << "Creating temporary file failed"; | |
| 279 error = true; | |
| 280 } | |
| 281 | |
| 282 if (!error && !metafile->SaveTo(path_to_pdf_)) { | |
| 283 LOG(ERROR) << "Saving metafile failed"; | |
| 284 base::DeleteFile(path_to_pdf_, false); | |
| 285 error = true; | |
| 286 } | |
| 287 | |
| 288 if (error) { | |
| 289 // Matches AddRef() above. | |
| 290 Release(); | |
| 291 } else { | |
| 292 // No errors, continue printing. | |
| 293 BrowserThread::PostTask( | |
| 294 BrowserThread::UI, FROM_HERE, | |
| 295 base::Bind(&PrintDialogGtk::SendDocumentToPrinter, this, | |
| 296 document_name)); | |
| 297 } | |
| 298 } | |
| 299 | |
| 300 void PrintDialogGtk::AddRefToDialog() { | |
| 301 AddRef(); | |
| 302 } | |
| 303 | |
| 304 void PrintDialogGtk::ReleaseDialog() { | |
| 305 Release(); | |
| 306 } | |
| 307 | |
| 308 void PrintDialogGtk::OnResponse(GtkWidget* dialog, int response_id) { | |
| 309 int num_matched_handlers = g_signal_handlers_disconnect_by_func( | |
| 310 dialog_, reinterpret_cast<gpointer>(&OnResponseThunk), this); | |
| 311 CHECK_EQ(1, num_matched_handlers); | |
| 312 | |
| 313 gtk_widget_hide(dialog_); | |
| 314 | |
| 315 switch (response_id) { | |
| 316 case GTK_RESPONSE_OK: { | |
| 317 if (gtk_settings_) | |
| 318 g_object_unref(gtk_settings_); | |
| 319 gtk_settings_ = gtk_print_unix_dialog_get_settings( | |
| 320 GTK_PRINT_UNIX_DIALOG(dialog_)); | |
| 321 | |
| 322 if (printer_) | |
| 323 g_object_unref(printer_); | |
| 324 printer_ = gtk_print_unix_dialog_get_selected_printer( | |
| 325 GTK_PRINT_UNIX_DIALOG(dialog_)); | |
| 326 g_object_ref(printer_); | |
| 327 | |
| 328 if (page_setup_) | |
| 329 g_object_unref(page_setup_); | |
| 330 page_setup_ = gtk_print_unix_dialog_get_page_setup( | |
| 331 GTK_PRINT_UNIX_DIALOG(dialog_)); | |
| 332 g_object_ref(page_setup_); | |
| 333 | |
| 334 // Handle page ranges. | |
| 335 PageRanges ranges_vector; | |
| 336 gint num_ranges; | |
| 337 bool print_selection_only = false; | |
| 338 switch (gtk_print_settings_get_print_pages(gtk_settings_)) { | |
| 339 case GTK_PRINT_PAGES_RANGES: { | |
| 340 GtkPageRange* gtk_range = | |
| 341 gtk_print_settings_get_page_ranges(gtk_settings_, &num_ranges); | |
| 342 if (gtk_range) { | |
| 343 for (int i = 0; i < num_ranges; ++i) { | |
| 344 printing::PageRange range; | |
| 345 range.from = gtk_range[i].start; | |
| 346 range.to = gtk_range[i].end; | |
| 347 ranges_vector.push_back(range); | |
| 348 } | |
| 349 g_free(gtk_range); | |
| 350 } | |
| 351 break; | |
| 352 } | |
| 353 case GTK_PRINT_PAGES_SELECTION: | |
| 354 print_selection_only = true; | |
| 355 break; | |
| 356 case GTK_PRINT_PAGES_ALL: | |
| 357 // Leave |ranges_vector| empty to indicate print all pages. | |
| 358 break; | |
| 359 case GTK_PRINT_PAGES_CURRENT: | |
| 360 default: | |
| 361 NOTREACHED(); | |
| 362 break; | |
| 363 } | |
| 364 | |
| 365 PrintSettings settings; | |
| 366 settings.set_ranges(ranges_vector); | |
| 367 settings.set_selection_only(print_selection_only); | |
| 368 printing::PrintSettingsInitializerGtk::InitPrintSettings( | |
| 369 gtk_settings_, page_setup_, &settings); | |
| 370 context_->InitWithSettings(settings); | |
| 371 callback_.Run(PrintingContextLinux::OK); | |
| 372 callback_.Reset(); | |
| 373 return; | |
| 374 } | |
| 375 case GTK_RESPONSE_DELETE_EVENT: // Fall through. | |
| 376 case GTK_RESPONSE_CANCEL: { | |
| 377 callback_.Run(PrintingContextLinux::CANCEL); | |
| 378 callback_.Reset(); | |
| 379 return; | |
| 380 } | |
| 381 case GTK_RESPONSE_APPLY: | |
| 382 default: { | |
| 383 NOTREACHED(); | |
| 384 } | |
| 385 } | |
| 386 } | |
| 387 | |
| 388 void PrintDialogGtk::SendDocumentToPrinter( | |
| 389 const base::string16& document_name) { | |
| 390 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 391 | |
| 392 // If |printer_| is NULL then somehow the GTK printer list changed out under | |
| 393 // us. In which case, just bail out. | |
| 394 if (!printer_) { | |
| 395 // Matches AddRef() in PrintDocument(); | |
| 396 Release(); | |
| 397 return; | |
| 398 } | |
| 399 | |
| 400 // Save the settings for next time. | |
| 401 g_last_used_settings.Get().SetLastUsedSettings(gtk_settings_); | |
| 402 | |
| 403 GtkPrintJob* print_job = gtk_print_job_new( | |
| 404 base::UTF16ToUTF8(document_name).c_str(), | |
| 405 printer_, | |
| 406 gtk_settings_, | |
| 407 page_setup_); | |
| 408 gtk_print_job_set_source_file(print_job, path_to_pdf_.value().c_str(), NULL); | |
| 409 gtk_print_job_send(print_job, OnJobCompletedThunk, this, NULL); | |
| 410 } | |
| 411 | |
| 412 // static | |
| 413 void PrintDialogGtk::OnJobCompletedThunk(GtkPrintJob* print_job, | |
| 414 gpointer user_data, | |
| 415 GError* error) { | |
| 416 static_cast<PrintDialogGtk*>(user_data)->OnJobCompleted(print_job, error); | |
| 417 } | |
| 418 | |
| 419 void PrintDialogGtk::OnJobCompleted(GtkPrintJob* print_job, GError* error) { | |
| 420 if (error) | |
| 421 LOG(ERROR) << "Printing failed: " << error->message; | |
| 422 if (print_job) | |
| 423 g_object_unref(print_job); | |
| 424 base::FileUtilProxy::DeleteFile( | |
| 425 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE).get(), | |
| 426 path_to_pdf_, | |
| 427 false, | |
| 428 base::FileUtilProxy::StatusCallback()); | |
| 429 // Printing finished. Matches AddRef() in PrintDocument(); | |
| 430 Release(); | |
| 431 } | |
| 432 | |
| 433 void PrintDialogGtk::InitPrintSettings(PrintSettings* settings) { | |
| 434 printing::PrintSettingsInitializerGtk::InitPrintSettings( | |
| 435 gtk_settings_, page_setup_, settings); | |
| 436 context_->InitWithSettings(*settings); | |
| 437 } | |
| OLD | NEW |