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