• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
GtkPrinterList()35   GtkPrinterList() : default_printer_(NULL) {
36     gtk_enumerate_printers((GtkPrinterFunc)SetPrinter, this, NULL, TRUE);
37   }
38 
~GtkPrinterList()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.
default_printer()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'.
GetPrinterWithName(const char * name)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.
SetPrinter(GtkPrinter * printer,GtkPrinterList * printer_list)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
CreatePrintDialog(PrintingContextCairo * context)88 printing::PrintDialogGtkInterface* PrintDialogGtk::CreatePrintDialog(
89     PrintingContextCairo* context) {
90   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
91   return new PrintDialogGtk(context);
92 }
93 
PrintDialogGtk(PrintingContextCairo * context)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 
~PrintDialogGtk()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 
UseDefaultSettings()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 
UpdateSettings(const DictionaryValue & settings,const printing::PageRanges & ranges)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 
ShowDialog(PrintingContextCairo::PrintSettingsCallback * callback)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 
PrintDocument(const printing::Metafile * metafile,const string16 & document_name)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 
AddRefToDialog()241 void PrintDialogGtk::AddRefToDialog() {
242   AddRef();
243 }
244 
ReleaseDialog()245 void PrintDialogGtk::ReleaseDialog() {
246   Release();
247 }
248 
OnResponse(GtkWidget * dialog,int response_id)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 
SaveDocumentToDisk(const printing::Metafile * metafile,const string16 & document_name)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 
SendDocumentToPrinter(const string16 & document_name)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
OnJobCompletedThunk(GtkPrintJob * print_job,gpointer user_data,GError * error)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 
OnJobCompleted(GtkPrintJob * print_job,GError * error)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 
InitPrintSettings(const PageRanges & page_ranges)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