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/printing/print_dialog_gtk.h" 6 7 #include <fcntl.h> 8 #include <gtk/gtkpagesetupunixdialog.h> 9 #include <gtk/gtkprintjob.h> 10 #include <sys/stat.h> 11 #include <sys/types.h> 12 13 #include <string> 14 #include <vector> 15 16 #include "base/file_util.h" 17 #include "base/file_util_proxy.h" 18 #include "base/logging.h" 19 #include "base/synchronization/waitable_event.h" 20 #include "base/utf_string_conversions.h" 21 #include "chrome/browser/ui/browser_list.h" 22 #include "chrome/browser/ui/browser_window.h" 23 #include "printing/metafile.h" 24 #include "printing/print_job_constants.h" 25 #include "printing/print_settings_initializer_gtk.h" 26 27 using printing::PageRanges; 28 using printing::PrintSettings; 29 30 namespace { 31 32 // Helper class to track GTK printers. 33 class GtkPrinterList { 34 public: 35 GtkPrinterList() : default_printer_(NULL) { 36 gtk_enumerate_printers((GtkPrinterFunc)SetPrinter, this, NULL, TRUE); 37 } 38 39 ~GtkPrinterList() { 40 for (std::vector<GtkPrinter*>::iterator it = printers_.begin(); 41 it < printers_.end(); ++it) { 42 g_object_unref(*it); 43 } 44 } 45 46 // Can return NULL if there's no default printer. E.g. Printer on a laptop 47 // is "home_printer", but the laptop is at work. 48 GtkPrinter* default_printer() { 49 return default_printer_; 50 } 51 52 // Can return NULL if the printer cannot be found due to: 53 // - Printer list out of sync with printer dialog UI. 54 // - Querying for non-existant printers like 'Print to PDF'. 55 GtkPrinter* GetPrinterWithName(const char* name) { 56 if (!name || !*name) 57 return NULL; 58 59 for (std::vector<GtkPrinter*>::iterator it = printers_.begin(); 60 it < printers_.end(); ++it) { 61 if (strcmp(name, gtk_printer_get_name(*it)) == 0) { 62 return *it; 63 } 64 } 65 66 return NULL; 67 } 68 69 private: 70 // Callback function used by gtk_enumerate_printers() to get all printer. 71 static bool SetPrinter(GtkPrinter* printer, GtkPrinterList* printer_list) { 72 if (gtk_printer_is_default(printer)) 73 printer_list->default_printer_ = printer; 74 75 g_object_ref(printer); 76 printer_list->printers_.push_back(printer); 77 78 return false; 79 } 80 81 std::vector<GtkPrinter*> printers_; 82 GtkPrinter* default_printer_; 83 }; 84 85 } // namespace 86 87 // static 88 printing::PrintDialogGtkInterface* PrintDialogGtk::CreatePrintDialog( 89 PrintingContextCairo* context) { 90 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 91 return new PrintDialogGtk(context); 92 } 93 94 PrintDialogGtk::PrintDialogGtk(PrintingContextCairo* context) 95 : callback_(NULL), 96 context_(context), 97 dialog_(NULL), 98 gtk_settings_(NULL), 99 page_setup_(NULL), 100 printer_(NULL) { 101 } 102 103 PrintDialogGtk::~PrintDialogGtk() { 104 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 105 106 if (dialog_) { 107 gtk_widget_destroy(dialog_); 108 dialog_ = NULL; 109 } 110 if (gtk_settings_) { 111 g_object_unref(gtk_settings_); 112 gtk_settings_ = NULL; 113 } 114 if (page_setup_) { 115 g_object_unref(page_setup_); 116 page_setup_ = NULL; 117 } 118 if (printer_) { 119 g_object_unref(printer_); 120 printer_ = NULL; 121 } 122 } 123 124 void PrintDialogGtk::UseDefaultSettings() { 125 DCHECK(!save_document_event_.get()); 126 DCHECK(!page_setup_); 127 128 // |gtk_settings_| is a new object. 129 gtk_settings_ = gtk_print_settings_new(); 130 131 scoped_ptr<GtkPrinterList> printer_list(new GtkPrinterList); 132 printer_ = printer_list->default_printer(); 133 if (printer_) { 134 g_object_ref(printer_); 135 gtk_print_settings_set_printer(gtk_settings_, 136 gtk_printer_get_name(printer_)); 137 #if GTK_CHECK_VERSION(2, 14, 0) 138 page_setup_ = gtk_printer_get_default_page_size(printer_); 139 #endif 140 } 141 142 if (!page_setup_) 143 page_setup_ = gtk_page_setup_new(); 144 145 // No page range to initialize for default settings. 146 PageRanges ranges_vector; 147 InitPrintSettings(ranges_vector); 148 } 149 150 bool PrintDialogGtk::UpdateSettings(const DictionaryValue& settings, 151 const printing::PageRanges& ranges) { 152 std::string printer_name; 153 settings.GetString(printing::kSettingPrinterName, &printer_name); 154 155 scoped_ptr<GtkPrinterList> printer_list(new GtkPrinterList); 156 printer_ = printer_list->GetPrinterWithName(printer_name.c_str()); 157 if (printer_) { 158 g_object_ref(printer_); 159 gtk_print_settings_set_printer(gtk_settings_, 160 gtk_printer_get_name(printer_)); 161 } 162 163 bool landscape; 164 if (!settings.GetBoolean(printing::kSettingLandscape, &landscape)) 165 return false; 166 167 gtk_print_settings_set_orientation( 168 gtk_settings_, 169 landscape ? GTK_PAGE_ORIENTATION_LANDSCAPE : 170 GTK_PAGE_ORIENTATION_PORTRAIT); 171 172 int copies; 173 if (!settings.GetInteger(printing::kSettingCopies, &copies)) 174 return false; 175 gtk_print_settings_set_n_copies(gtk_settings_, copies); 176 177 bool collate; 178 if (!settings.GetBoolean(printing::kSettingCollate, &collate)) 179 return false; 180 gtk_print_settings_set_collate(gtk_settings_, collate); 181 182 // TODO(thestig) Color: gtk_print_settings_set_color() does not work. 183 // TODO(thestig) Duplex: gtk_print_settings_set_duplex() does not work. 184 185 InitPrintSettings(ranges); 186 return true; 187 } 188 189 void PrintDialogGtk::ShowDialog( 190 PrintingContextCairo::PrintSettingsCallback* callback) { 191 DCHECK(!save_document_event_.get()); 192 193 callback_ = callback; 194 195 GtkWindow* parent = BrowserList::GetLastActive()->window()->GetNativeHandle(); 196 // TODO(estade): We need a window title here. 197 dialog_ = gtk_print_unix_dialog_new(NULL, parent); 198 199 // Set modal so user cannot focus the same tab and press print again. 200 gtk_window_set_modal(GTK_WINDOW(dialog_), TRUE); 201 202 // Since we only generate PDF, only show printers that support PDF. 203 // TODO(thestig) Add more capabilities to support? 204 GtkPrintCapabilities cap = static_cast<GtkPrintCapabilities>( 205 GTK_PRINT_CAPABILITY_GENERATE_PDF | 206 GTK_PRINT_CAPABILITY_PAGE_SET | 207 GTK_PRINT_CAPABILITY_COPIES | 208 GTK_PRINT_CAPABILITY_COLLATE | 209 GTK_PRINT_CAPABILITY_REVERSE); 210 gtk_print_unix_dialog_set_manual_capabilities(GTK_PRINT_UNIX_DIALOG(dialog_), 211 cap); 212 #if GTK_CHECK_VERSION(2, 18, 0) 213 gtk_print_unix_dialog_set_embed_page_setup(GTK_PRINT_UNIX_DIALOG(dialog_), 214 TRUE); 215 #endif 216 g_signal_connect(dialog_, "response", G_CALLBACK(OnResponseThunk), this); 217 gtk_widget_show(dialog_); 218 } 219 220 void PrintDialogGtk::PrintDocument(const printing::Metafile* metafile, 221 const string16& document_name) { 222 // This runs on the print worker thread, does not block the UI thread. 223 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); 224 225 // The document printing tasks can outlive the PrintingContext that created 226 // this dialog. 227 AddRef(); 228 DCHECK(!save_document_event_.get()); 229 save_document_event_.reset(new base::WaitableEvent(false, false)); 230 231 BrowserThread::PostTask( 232 BrowserThread::FILE, FROM_HERE, 233 NewRunnableMethod(this, 234 &PrintDialogGtk::SaveDocumentToDisk, 235 metafile, 236 document_name)); 237 // Wait for SaveDocumentToDisk() to finish. 238 save_document_event_->Wait(); 239 } 240 241 void PrintDialogGtk::AddRefToDialog() { 242 AddRef(); 243 } 244 245 void PrintDialogGtk::ReleaseDialog() { 246 Release(); 247 } 248 249 void PrintDialogGtk::OnResponse(GtkWidget* dialog, int response_id) { 250 gtk_widget_hide(dialog_); 251 252 switch (response_id) { 253 case GTK_RESPONSE_OK: { 254 if (gtk_settings_) 255 g_object_unref(gtk_settings_); 256 gtk_settings_ = gtk_print_unix_dialog_get_settings( 257 GTK_PRINT_UNIX_DIALOG(dialog_)); 258 259 if (printer_) 260 g_object_unref(printer_); 261 printer_ = gtk_print_unix_dialog_get_selected_printer( 262 GTK_PRINT_UNIX_DIALOG(dialog_)); 263 g_object_ref(printer_); 264 265 if (page_setup_) 266 g_object_unref(page_setup_); 267 page_setup_ = gtk_print_unix_dialog_get_page_setup( 268 GTK_PRINT_UNIX_DIALOG(dialog_)); 269 g_object_ref(page_setup_); 270 271 // Handle page ranges. 272 PageRanges ranges_vector; 273 gint num_ranges; 274 GtkPageRange* gtk_range = 275 gtk_print_settings_get_page_ranges(gtk_settings_, &num_ranges); 276 if (gtk_range) { 277 for (int i = 0; i < num_ranges; ++i) { 278 printing::PageRange range; 279 range.from = gtk_range[i].start; 280 range.to = gtk_range[i].end; 281 ranges_vector.push_back(range); 282 } 283 g_free(gtk_range); 284 } 285 286 PrintSettings settings; 287 printing::PrintSettingsInitializerGtk::InitPrintSettings( 288 gtk_settings_, page_setup_, ranges_vector, false, &settings); 289 context_->InitWithSettings(settings); 290 callback_->Run(PrintingContextCairo::OK); 291 callback_ = NULL; 292 return; 293 } 294 case GTK_RESPONSE_DELETE_EVENT: // Fall through. 295 case GTK_RESPONSE_CANCEL: { 296 callback_->Run(PrintingContextCairo::CANCEL); 297 callback_ = NULL; 298 return; 299 } 300 case GTK_RESPONSE_APPLY: 301 default: { 302 NOTREACHED(); 303 } 304 } 305 } 306 307 void PrintDialogGtk::SaveDocumentToDisk(const printing::Metafile* metafile, 308 const string16& document_name) { 309 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 310 311 bool error = false; 312 if (!file_util::CreateTemporaryFile(&path_to_pdf_)) { 313 LOG(ERROR) << "Creating temporary file failed"; 314 error = true; 315 } 316 317 if (!error && !metafile->SaveTo(path_to_pdf_)) { 318 LOG(ERROR) << "Saving metafile failed"; 319 file_util::Delete(path_to_pdf_, false); 320 error = true; 321 } 322 323 // Done saving, let PrintDialogGtk::PrintDocument() continue. 324 save_document_event_->Signal(); 325 326 if (error) { 327 // Matches AddRef() in PrintDocument(); 328 Release(); 329 } else { 330 // No errors, continue printing. 331 BrowserThread::PostTask( 332 BrowserThread::UI, FROM_HERE, 333 NewRunnableMethod(this, 334 &PrintDialogGtk::SendDocumentToPrinter, 335 document_name)); 336 } 337 } 338 339 void PrintDialogGtk::SendDocumentToPrinter(const string16& document_name) { 340 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 341 342 // If |printer_| is NULL then somehow the GTK printer list changed out under 343 // us. In which case, just bail out. 344 if (!printer_) { 345 // Matches AddRef() in PrintDocument(); 346 Release(); 347 return; 348 } 349 350 GtkPrintJob* print_job = gtk_print_job_new( 351 UTF16ToUTF8(document_name).c_str(), 352 printer_, 353 gtk_settings_, 354 page_setup_); 355 gtk_print_job_set_source_file(print_job, path_to_pdf_.value().c_str(), NULL); 356 gtk_print_job_send(print_job, OnJobCompletedThunk, this, NULL); 357 } 358 359 // static 360 void PrintDialogGtk::OnJobCompletedThunk(GtkPrintJob* print_job, 361 gpointer user_data, 362 GError* error) { 363 static_cast<PrintDialogGtk*>(user_data)->OnJobCompleted(print_job, error); 364 } 365 366 void PrintDialogGtk::OnJobCompleted(GtkPrintJob* print_job, GError* error) { 367 if (error) 368 LOG(ERROR) << "Printing failed: " << error->message; 369 if (print_job) 370 g_object_unref(print_job); 371 base::FileUtilProxy::Delete( 372 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE), 373 path_to_pdf_, 374 false, 375 NULL); 376 // Printing finished. Matches AddRef() in PrintDocument(); 377 Release(); 378 } 379 380 void PrintDialogGtk::InitPrintSettings(const PageRanges& page_ranges) { 381 PrintSettings settings; 382 printing::PrintSettingsInitializerGtk::InitPrintSettings( 383 gtk_settings_, page_setup_, page_ranges, false, &settings); 384 context_->InitWithSettings(settings); 385 } 386