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 |