Add ENABLE_MEDIA_ROUTER define to builds other than Android and iOS.
[chromium-blink-merge.git] / chrome / browser / ui / libgtk2ui / print_dialog_gtk2.cc
bloba6f6ae1a256c067278e371dfdd3d3be2a4f198d9
1 // Copyright 2014 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.
5 #include "chrome/browser/ui/libgtk2ui/print_dialog_gtk2.h"
7 #include <gtk/gtkunixprint.h>
9 #include <algorithm>
10 #include <cmath>
11 #include <string>
12 #include <vector>
14 #include "base/bind.h"
15 #include "base/files/file_util.h"
16 #include "base/files/file_util_proxy.h"
17 #include "base/lazy_instance.h"
18 #include "base/logging.h"
19 #include "base/message_loop/message_loop_proxy.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "base/values.h"
22 #include "chrome/browser/ui/libgtk2ui/gtk2_util.h"
23 #include "chrome/browser/ui/libgtk2ui/printing_gtk2_util.h"
24 #include "printing/metafile.h"
25 #include "printing/print_job_constants.h"
26 #include "printing/print_settings.h"
27 #include "ui/aura/window.h"
28 #include "ui/views/widget/desktop_aura/x11_desktop_handler.h"
30 using content::BrowserThread;
31 using printing::PageRanges;
32 using printing::PrintSettings;
34 namespace {
36 // CUPS Duplex attribute and values.
37 const char kCUPSDuplex[] = "cups-Duplex";
38 const char kDuplexNone[] = "None";
39 const char kDuplexTumble[] = "DuplexTumble";
40 const char kDuplexNoTumble[] = "DuplexNoTumble";
42 int kPaperSizeTresholdMicrons = 100;
43 int kMicronsInMm = 1000;
45 // Checks whether gtk_paper_size can be used to represent user selected media.
46 // In fuzzy match mode checks that paper sizes are "close enough" (less than
47 // 1mm difference). In the exact mode, looks for the paper with the same PPD
48 // name and "close enough" size.
49 bool PaperSizeMatch(GtkPaperSize* gtk_paper_size,
50 const PrintSettings::RequestedMedia& media,
51 bool fuzzy_match) {
52 if (!gtk_paper_size) {
53 return false;
55 gfx::Size paper_size_microns(
56 static_cast<int>(gtk_paper_size_get_width(gtk_paper_size, GTK_UNIT_MM) *
57 kMicronsInMm + 0.5),
58 static_cast<int>(gtk_paper_size_get_height(gtk_paper_size, GTK_UNIT_MM) *
59 kMicronsInMm + 0.5));
60 int diff = std::max(
61 std::abs(paper_size_microns.width() - media.size_microns.width()),
62 std::abs(paper_size_microns.height() - media.size_microns.height()));
63 if (fuzzy_match) {
64 return diff <= kPaperSizeTresholdMicrons;
66 return !media.vendor_id.empty() &&
67 media.vendor_id == gtk_paper_size_get_ppd_name(gtk_paper_size) &&
68 diff <= kPaperSizeTresholdMicrons;
71 // Looks up a paper size matching (in terms of PaperSizeMatch) the user selected
72 // media in the paper size list reported by GTK. Returns NULL if there's no
73 // match found.
74 GtkPaperSize* FindPaperSizeMatch(GList* gtk_paper_sizes,
75 const PrintSettings::RequestedMedia& media) {
76 GtkPaperSize* first_fuzzy_match = NULL;
77 for (GList* p = gtk_paper_sizes; p && p->data; p = g_list_next(p)) {
78 GtkPaperSize* gtk_paper_size = static_cast<GtkPaperSize*>(p->data);
79 if (PaperSizeMatch(gtk_paper_size, media, false)) {
80 return gtk_paper_size;
82 if (!first_fuzzy_match && PaperSizeMatch(gtk_paper_size, media, true)) {
83 first_fuzzy_match = gtk_paper_size;
86 return first_fuzzy_match;
89 class StickyPrintSettingGtk {
90 public:
91 StickyPrintSettingGtk() : last_used_settings_(gtk_print_settings_new()) {
93 ~StickyPrintSettingGtk() {
94 NOTREACHED(); // Intended to be used with a Leaky LazyInstance.
97 GtkPrintSettings* settings() {
98 return last_used_settings_;
101 void SetLastUsedSettings(GtkPrintSettings* settings) {
102 DCHECK(last_used_settings_);
103 g_object_unref(last_used_settings_);
104 last_used_settings_ = gtk_print_settings_copy(settings);
107 private:
108 GtkPrintSettings* last_used_settings_;
110 DISALLOW_COPY_AND_ASSIGN(StickyPrintSettingGtk);
113 base::LazyInstance<StickyPrintSettingGtk>::Leaky g_last_used_settings =
114 LAZY_INSTANCE_INITIALIZER;
116 // Helper class to track GTK printers.
117 class GtkPrinterList {
118 public:
119 GtkPrinterList() : default_printer_(NULL) {
120 gtk_enumerate_printers(SetPrinter, this, NULL, TRUE);
123 ~GtkPrinterList() {
124 for (std::vector<GtkPrinter*>::iterator it = printers_.begin();
125 it < printers_.end(); ++it) {
126 g_object_unref(*it);
130 // Can return NULL if there's no default printer. E.g. Printer on a laptop
131 // is "home_printer", but the laptop is at work.
132 GtkPrinter* default_printer() {
133 return default_printer_;
136 // Can return NULL if the printer cannot be found due to:
137 // - Printer list out of sync with printer dialog UI.
138 // - Querying for non-existant printers like 'Print to PDF'.
139 GtkPrinter* GetPrinterWithName(const std::string& name) {
140 if (name.empty())
141 return NULL;
143 for (std::vector<GtkPrinter*>::iterator it = printers_.begin();
144 it < printers_.end(); ++it) {
145 if (gtk_printer_get_name(*it) == name) {
146 return *it;
150 return NULL;
153 private:
154 // Callback function used by gtk_enumerate_printers() to get all printer.
155 static gboolean SetPrinter(GtkPrinter* printer, gpointer data) {
156 GtkPrinterList* printer_list = reinterpret_cast<GtkPrinterList*>(data);
157 if (gtk_printer_is_default(printer))
158 printer_list->default_printer_ = printer;
160 g_object_ref(printer);
161 printer_list->printers_.push_back(printer);
163 return FALSE;
166 std::vector<GtkPrinter*> printers_;
167 GtkPrinter* default_printer_;
170 } // namespace
172 // static
173 printing::PrintDialogGtkInterface* PrintDialogGtk2::CreatePrintDialog(
174 PrintingContextLinux* context) {
175 DCHECK_CURRENTLY_ON(BrowserThread::UI);
176 return new PrintDialogGtk2(context);
179 PrintDialogGtk2::PrintDialogGtk2(PrintingContextLinux* context)
180 : context_(context),
181 dialog_(NULL),
182 gtk_settings_(NULL),
183 page_setup_(NULL),
184 printer_(NULL) {
187 PrintDialogGtk2::~PrintDialogGtk2() {
188 DCHECK_CURRENTLY_ON(BrowserThread::UI);
190 if (dialog_) {
191 aura::Window* parent = libgtk2ui::GetAuraTransientParent(dialog_);
192 if (parent) {
193 parent->RemoveObserver(this);
194 libgtk2ui::ClearAuraTransientParent(dialog_);
196 gtk_widget_destroy(dialog_);
197 dialog_ = NULL;
199 if (gtk_settings_) {
200 g_object_unref(gtk_settings_);
201 gtk_settings_ = NULL;
203 if (page_setup_) {
204 g_object_unref(page_setup_);
205 page_setup_ = NULL;
207 if (printer_) {
208 g_object_unref(printer_);
209 printer_ = NULL;
213 void PrintDialogGtk2::UseDefaultSettings() {
214 DCHECK(!page_setup_);
215 DCHECK(!printer_);
217 // |gtk_settings_| is a new copy.
218 gtk_settings_ =
219 gtk_print_settings_copy(g_last_used_settings.Get().settings());
220 page_setup_ = gtk_page_setup_new();
222 PrintSettings settings;
223 InitPrintSettings(&settings);
226 bool PrintDialogGtk2::UpdateSettings(printing::PrintSettings* settings) {
227 if (!gtk_settings_) {
228 gtk_settings_ =
229 gtk_print_settings_copy(g_last_used_settings.Get().settings());
232 scoped_ptr<GtkPrinterList> printer_list(new GtkPrinterList);
233 printer_ = printer_list->GetPrinterWithName(
234 base::UTF16ToUTF8(settings->device_name()));
235 if (printer_) {
236 g_object_ref(printer_);
237 gtk_print_settings_set_printer(gtk_settings_,
238 gtk_printer_get_name(printer_));
239 if (!page_setup_) {
240 page_setup_ = gtk_printer_get_default_page_size(printer_);
244 gtk_print_settings_set_n_copies(gtk_settings_, settings->copies());
245 gtk_print_settings_set_collate(gtk_settings_, settings->collate());
247 #if defined(USE_CUPS)
248 std::string color_value;
249 std::string color_setting_name;
250 printing::GetColorModelForMode(settings->color(), &color_setting_name,
251 &color_value);
252 gtk_print_settings_set(gtk_settings_, color_setting_name.c_str(),
253 color_value.c_str());
255 if (settings->duplex_mode() != printing::UNKNOWN_DUPLEX_MODE) {
256 const char* cups_duplex_mode = NULL;
257 switch (settings->duplex_mode()) {
258 case printing::LONG_EDGE:
259 cups_duplex_mode = kDuplexNoTumble;
260 break;
261 case printing::SHORT_EDGE:
262 cups_duplex_mode = kDuplexTumble;
263 break;
264 case printing::SIMPLEX:
265 cups_duplex_mode = kDuplexNone;
266 break;
267 default: // UNKNOWN_DUPLEX_MODE
268 NOTREACHED();
269 break;
271 gtk_print_settings_set(gtk_settings_, kCUPSDuplex, cups_duplex_mode);
273 #endif
274 if (!page_setup_)
275 page_setup_ = gtk_page_setup_new();
277 if (page_setup_ && !settings->requested_media().IsDefault()) {
278 const PrintSettings::RequestedMedia& requested_media =
279 settings->requested_media();
280 GtkPaperSize* gtk_current_paper_size =
281 gtk_page_setup_get_paper_size(page_setup_);
282 if (!PaperSizeMatch(gtk_current_paper_size, requested_media,
283 true /*fuzzy_match*/)) {
284 GList* gtk_paper_sizes =
285 gtk_paper_size_get_paper_sizes(false /*include_custom*/);
286 if (gtk_paper_sizes) {
287 GtkPaperSize* matching_gtk_paper_size =
288 FindPaperSizeMatch(gtk_paper_sizes, requested_media);
289 if (matching_gtk_paper_size) {
290 VLOG(1) << "Using listed paper size";
291 gtk_page_setup_set_paper_size(page_setup_, matching_gtk_paper_size);
292 } else {
293 VLOG(1) << "Using custom paper size";
294 GtkPaperSize* custom_size = gtk_paper_size_new_custom(
295 requested_media.vendor_id.c_str(),
296 requested_media.vendor_id.c_str(),
297 requested_media.size_microns.width() / kMicronsInMm,
298 requested_media.size_microns.height() / kMicronsInMm,
299 GTK_UNIT_MM);
300 gtk_page_setup_set_paper_size(page_setup_, custom_size);
301 gtk_paper_size_free(custom_size);
303 #if GTK_CHECK_VERSION(2,28,0)
304 g_list_free_full(gtk_paper_sizes,
305 reinterpret_cast<GDestroyNotify>(gtk_paper_size_free));
306 #else
307 g_list_foreach(gtk_paper_sizes,
308 reinterpret_cast<GFunc>(gtk_paper_size_free), NULL);
309 g_list_free(gtk_paper_sizes);
310 #endif
312 } else {
313 VLOG(1) << "Using default paper size";
317 gtk_print_settings_set_orientation(
318 gtk_settings_,
319 settings->landscape() ? GTK_PAGE_ORIENTATION_LANDSCAPE :
320 GTK_PAGE_ORIENTATION_PORTRAIT);
322 InitPrintSettings(settings);
323 return true;
326 void PrintDialogGtk2::ShowDialog(
327 gfx::NativeView parent_view,
328 bool has_selection,
329 const PrintingContextLinux::PrintSettingsCallback& callback) {
330 callback_ = callback;
332 dialog_ = gtk_print_unix_dialog_new(NULL, NULL);
333 libgtk2ui::SetGtkTransientForAura(dialog_, parent_view);
334 if (parent_view)
335 parent_view->AddObserver(this);
336 g_signal_connect(dialog_, "delete-event",
337 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
340 // Set modal so user cannot focus the same tab and press print again.
341 gtk_window_set_modal(GTK_WINDOW(dialog_), TRUE);
343 // Since we only generate PDF, only show printers that support PDF.
344 // TODO(thestig) Add more capabilities to support?
345 GtkPrintCapabilities cap = static_cast<GtkPrintCapabilities>(
346 GTK_PRINT_CAPABILITY_GENERATE_PDF |
347 GTK_PRINT_CAPABILITY_PAGE_SET |
348 GTK_PRINT_CAPABILITY_COPIES |
349 GTK_PRINT_CAPABILITY_COLLATE |
350 GTK_PRINT_CAPABILITY_REVERSE);
351 gtk_print_unix_dialog_set_manual_capabilities(GTK_PRINT_UNIX_DIALOG(dialog_),
352 cap);
353 gtk_print_unix_dialog_set_embed_page_setup(GTK_PRINT_UNIX_DIALOG(dialog_),
354 TRUE);
355 gtk_print_unix_dialog_set_support_selection(GTK_PRINT_UNIX_DIALOG(dialog_),
356 TRUE);
357 gtk_print_unix_dialog_set_has_selection(GTK_PRINT_UNIX_DIALOG(dialog_),
358 has_selection);
359 gtk_print_unix_dialog_set_settings(GTK_PRINT_UNIX_DIALOG(dialog_),
360 gtk_settings_);
361 g_signal_connect(dialog_, "response", G_CALLBACK(OnResponseThunk), this);
362 gtk_widget_show(dialog_);
364 // We need to call gtk_window_present after making the widgets visible to make
365 // sure window gets correctly raised and gets focus.
366 int time = views::X11DesktopHandler::get()->wm_user_time_ms();
367 gtk_window_present_with_time(GTK_WINDOW(dialog_), time);
370 void PrintDialogGtk2::PrintDocument(const printing::MetafilePlayer& metafile,
371 const base::string16& document_name) {
372 // This runs on the print worker thread, does not block the UI thread.
373 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
375 // The document printing tasks can outlive the PrintingContext that created
376 // this dialog.
377 AddRef();
379 bool success = base::CreateTemporaryFile(&path_to_pdf_);
381 if (success) {
382 base::File file;
383 file.Initialize(path_to_pdf_,
384 base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
385 success = metafile.SaveTo(&file);
386 file.Close();
387 if (!success)
388 base::DeleteFile(path_to_pdf_, false);
391 if (!success) {
392 LOG(ERROR) << "Saving metafile failed";
393 // Matches AddRef() above.
394 Release();
395 return;
398 // No errors, continue printing.
399 BrowserThread::PostTask(
400 BrowserThread::UI,
401 FROM_HERE,
402 base::Bind(&PrintDialogGtk2::SendDocumentToPrinter, this, document_name));
405 void PrintDialogGtk2::AddRefToDialog() {
406 AddRef();
409 void PrintDialogGtk2::ReleaseDialog() {
410 Release();
413 void PrintDialogGtk2::OnResponse(GtkWidget* dialog, int response_id) {
414 int num_matched_handlers = g_signal_handlers_disconnect_by_func(
415 dialog_, reinterpret_cast<gpointer>(&OnResponseThunk), this);
416 CHECK_EQ(1, num_matched_handlers);
418 gtk_widget_hide(dialog_);
420 switch (response_id) {
421 case GTK_RESPONSE_OK: {
422 if (gtk_settings_)
423 g_object_unref(gtk_settings_);
424 gtk_settings_ = gtk_print_unix_dialog_get_settings(
425 GTK_PRINT_UNIX_DIALOG(dialog_));
427 if (printer_)
428 g_object_unref(printer_);
429 printer_ = gtk_print_unix_dialog_get_selected_printer(
430 GTK_PRINT_UNIX_DIALOG(dialog_));
431 g_object_ref(printer_);
433 if (page_setup_)
434 g_object_unref(page_setup_);
435 page_setup_ = gtk_print_unix_dialog_get_page_setup(
436 GTK_PRINT_UNIX_DIALOG(dialog_));
437 g_object_ref(page_setup_);
439 // Handle page ranges.
440 PageRanges ranges_vector;
441 gint num_ranges;
442 bool print_selection_only = false;
443 switch (gtk_print_settings_get_print_pages(gtk_settings_)) {
444 case GTK_PRINT_PAGES_RANGES: {
445 GtkPageRange* gtk_range =
446 gtk_print_settings_get_page_ranges(gtk_settings_, &num_ranges);
447 if (gtk_range) {
448 for (int i = 0; i < num_ranges; ++i) {
449 printing::PageRange range;
450 range.from = gtk_range[i].start;
451 range.to = gtk_range[i].end;
452 ranges_vector.push_back(range);
454 g_free(gtk_range);
456 break;
458 case GTK_PRINT_PAGES_SELECTION:
459 print_selection_only = true;
460 break;
461 case GTK_PRINT_PAGES_ALL:
462 // Leave |ranges_vector| empty to indicate print all pages.
463 break;
464 case GTK_PRINT_PAGES_CURRENT:
465 default:
466 NOTREACHED();
467 break;
470 PrintSettings settings;
471 settings.set_ranges(ranges_vector);
472 settings.set_selection_only(print_selection_only);
473 InitPrintSettingsGtk(gtk_settings_, page_setup_, &settings);
474 context_->InitWithSettings(settings);
475 callback_.Run(PrintingContextLinux::OK);
476 callback_.Reset();
477 return;
479 case GTK_RESPONSE_DELETE_EVENT: // Fall through.
480 case GTK_RESPONSE_CANCEL: {
481 callback_.Run(PrintingContextLinux::CANCEL);
482 callback_.Reset();
483 return;
485 case GTK_RESPONSE_APPLY:
486 default: {
487 NOTREACHED();
492 void PrintDialogGtk2::SendDocumentToPrinter(
493 const base::string16& document_name) {
494 DCHECK_CURRENTLY_ON(BrowserThread::UI);
496 // If |printer_| is NULL then somehow the GTK printer list changed out under
497 // us. In which case, just bail out.
498 if (!printer_) {
499 // Matches AddRef() in PrintDocument();
500 Release();
501 return;
504 // Save the settings for next time.
505 g_last_used_settings.Get().SetLastUsedSettings(gtk_settings_);
507 GtkPrintJob* print_job = gtk_print_job_new(
508 base::UTF16ToUTF8(document_name).c_str(),
509 printer_,
510 gtk_settings_,
511 page_setup_);
512 gtk_print_job_set_source_file(print_job, path_to_pdf_.value().c_str(), NULL);
513 gtk_print_job_send(print_job, OnJobCompletedThunk, this, NULL);
516 // static
517 void PrintDialogGtk2::OnJobCompletedThunk(GtkPrintJob* print_job,
518 gpointer user_data,
519 GError* error) {
520 static_cast<PrintDialogGtk2*>(user_data)->OnJobCompleted(print_job, error);
523 void PrintDialogGtk2::OnJobCompleted(GtkPrintJob* print_job, GError* error) {
524 if (error)
525 LOG(ERROR) << "Printing failed: " << error->message;
526 if (print_job)
527 g_object_unref(print_job);
528 base::FileUtilProxy::DeleteFile(
529 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE).get(),
530 path_to_pdf_,
531 false,
532 base::FileUtilProxy::StatusCallback());
533 // Printing finished. Matches AddRef() in PrintDocument();
534 Release();
537 void PrintDialogGtk2::InitPrintSettings(PrintSettings* settings) {
538 InitPrintSettingsGtk(gtk_settings_, page_setup_, settings);
539 context_->InitWithSettings(*settings);
542 void PrintDialogGtk2::OnWindowDestroying(aura::Window* window) {
543 DCHECK_EQ(libgtk2ui::GetAuraTransientParent(dialog_), window);
545 libgtk2ui::ClearAuraTransientParent(dialog_);
546 window->RemoveObserver(this);
547 Release();