Adding instrumentation to locate the source of jankiness
[chromium-blink-merge.git] / chrome / browser / ui / libgtk2ui / print_dialog_gtk2.cc
blobe050372a1954e93c440cd1e3973dd1c589eddb82
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 g_list_free_full(gtk_paper_sizes,
304 reinterpret_cast<GDestroyNotify>(gtk_paper_size_free));
306 } else {
307 VLOG(1) << "Using default paper size";
311 gtk_print_settings_set_orientation(
312 gtk_settings_,
313 settings->landscape() ? GTK_PAGE_ORIENTATION_LANDSCAPE :
314 GTK_PAGE_ORIENTATION_PORTRAIT);
316 InitPrintSettings(settings);
317 return true;
320 void PrintDialogGtk2::ShowDialog(
321 gfx::NativeView parent_view,
322 bool has_selection,
323 const PrintingContextLinux::PrintSettingsCallback& callback) {
324 callback_ = callback;
326 dialog_ = gtk_print_unix_dialog_new(NULL, NULL);
327 libgtk2ui::SetGtkTransientForAura(dialog_, parent_view);
328 if (parent_view)
329 parent_view->AddObserver(this);
330 g_signal_connect(dialog_, "delete-event",
331 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
334 // Set modal so user cannot focus the same tab and press print again.
335 gtk_window_set_modal(GTK_WINDOW(dialog_), TRUE);
337 // Since we only generate PDF, only show printers that support PDF.
338 // TODO(thestig) Add more capabilities to support?
339 GtkPrintCapabilities cap = static_cast<GtkPrintCapabilities>(
340 GTK_PRINT_CAPABILITY_GENERATE_PDF |
341 GTK_PRINT_CAPABILITY_PAGE_SET |
342 GTK_PRINT_CAPABILITY_COPIES |
343 GTK_PRINT_CAPABILITY_COLLATE |
344 GTK_PRINT_CAPABILITY_REVERSE);
345 gtk_print_unix_dialog_set_manual_capabilities(GTK_PRINT_UNIX_DIALOG(dialog_),
346 cap);
347 gtk_print_unix_dialog_set_embed_page_setup(GTK_PRINT_UNIX_DIALOG(dialog_),
348 TRUE);
349 gtk_print_unix_dialog_set_support_selection(GTK_PRINT_UNIX_DIALOG(dialog_),
350 TRUE);
351 gtk_print_unix_dialog_set_has_selection(GTK_PRINT_UNIX_DIALOG(dialog_),
352 has_selection);
353 gtk_print_unix_dialog_set_settings(GTK_PRINT_UNIX_DIALOG(dialog_),
354 gtk_settings_);
355 g_signal_connect(dialog_, "response", G_CALLBACK(OnResponseThunk), this);
356 gtk_widget_show(dialog_);
358 // We need to call gtk_window_present after making the widgets visible to make
359 // sure window gets correctly raised and gets focus.
360 int time = views::X11DesktopHandler::get()->wm_user_time_ms();
361 gtk_window_present_with_time(GTK_WINDOW(dialog_), time);
364 void PrintDialogGtk2::PrintDocument(const printing::MetafilePlayer& metafile,
365 const base::string16& document_name) {
366 // This runs on the print worker thread, does not block the UI thread.
367 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
369 // The document printing tasks can outlive the PrintingContext that created
370 // this dialog.
371 AddRef();
373 bool success = base::CreateTemporaryFile(&path_to_pdf_);
375 if (success) {
376 base::File file;
377 file.Initialize(path_to_pdf_,
378 base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
379 success = metafile.SaveTo(&file);
380 file.Close();
381 if (!success)
382 base::DeleteFile(path_to_pdf_, false);
385 if (!success) {
386 LOG(ERROR) << "Saving metafile failed";
387 // Matches AddRef() above.
388 Release();
389 return;
392 // No errors, continue printing.
393 BrowserThread::PostTask(
394 BrowserThread::UI,
395 FROM_HERE,
396 base::Bind(&PrintDialogGtk2::SendDocumentToPrinter, this, document_name));
399 void PrintDialogGtk2::AddRefToDialog() {
400 AddRef();
403 void PrintDialogGtk2::ReleaseDialog() {
404 Release();
407 void PrintDialogGtk2::OnResponse(GtkWidget* dialog, int response_id) {
408 int num_matched_handlers = g_signal_handlers_disconnect_by_func(
409 dialog_, reinterpret_cast<gpointer>(&OnResponseThunk), this);
410 CHECK_EQ(1, num_matched_handlers);
412 gtk_widget_hide(dialog_);
414 switch (response_id) {
415 case GTK_RESPONSE_OK: {
416 if (gtk_settings_)
417 g_object_unref(gtk_settings_);
418 gtk_settings_ = gtk_print_unix_dialog_get_settings(
419 GTK_PRINT_UNIX_DIALOG(dialog_));
421 if (printer_)
422 g_object_unref(printer_);
423 printer_ = gtk_print_unix_dialog_get_selected_printer(
424 GTK_PRINT_UNIX_DIALOG(dialog_));
425 g_object_ref(printer_);
427 if (page_setup_)
428 g_object_unref(page_setup_);
429 page_setup_ = gtk_print_unix_dialog_get_page_setup(
430 GTK_PRINT_UNIX_DIALOG(dialog_));
431 g_object_ref(page_setup_);
433 // Handle page ranges.
434 PageRanges ranges_vector;
435 gint num_ranges;
436 bool print_selection_only = false;
437 switch (gtk_print_settings_get_print_pages(gtk_settings_)) {
438 case GTK_PRINT_PAGES_RANGES: {
439 GtkPageRange* gtk_range =
440 gtk_print_settings_get_page_ranges(gtk_settings_, &num_ranges);
441 if (gtk_range) {
442 for (int i = 0; i < num_ranges; ++i) {
443 printing::PageRange range;
444 range.from = gtk_range[i].start;
445 range.to = gtk_range[i].end;
446 ranges_vector.push_back(range);
448 g_free(gtk_range);
450 break;
452 case GTK_PRINT_PAGES_SELECTION:
453 print_selection_only = true;
454 break;
455 case GTK_PRINT_PAGES_ALL:
456 // Leave |ranges_vector| empty to indicate print all pages.
457 break;
458 case GTK_PRINT_PAGES_CURRENT:
459 default:
460 NOTREACHED();
461 break;
464 PrintSettings settings;
465 settings.set_ranges(ranges_vector);
466 settings.set_selection_only(print_selection_only);
467 InitPrintSettingsGtk(gtk_settings_, page_setup_, &settings);
468 context_->InitWithSettings(settings);
469 callback_.Run(PrintingContextLinux::OK);
470 callback_.Reset();
471 return;
473 case GTK_RESPONSE_DELETE_EVENT: // Fall through.
474 case GTK_RESPONSE_CANCEL: {
475 callback_.Run(PrintingContextLinux::CANCEL);
476 callback_.Reset();
477 return;
479 case GTK_RESPONSE_APPLY:
480 default: {
481 NOTREACHED();
486 void PrintDialogGtk2::SendDocumentToPrinter(
487 const base::string16& document_name) {
488 DCHECK_CURRENTLY_ON(BrowserThread::UI);
490 // If |printer_| is NULL then somehow the GTK printer list changed out under
491 // us. In which case, just bail out.
492 if (!printer_) {
493 // Matches AddRef() in PrintDocument();
494 Release();
495 return;
498 // Save the settings for next time.
499 g_last_used_settings.Get().SetLastUsedSettings(gtk_settings_);
501 GtkPrintJob* print_job = gtk_print_job_new(
502 base::UTF16ToUTF8(document_name).c_str(),
503 printer_,
504 gtk_settings_,
505 page_setup_);
506 gtk_print_job_set_source_file(print_job, path_to_pdf_.value().c_str(), NULL);
507 gtk_print_job_send(print_job, OnJobCompletedThunk, this, NULL);
510 // static
511 void PrintDialogGtk2::OnJobCompletedThunk(GtkPrintJob* print_job,
512 gpointer user_data,
513 GError* error) {
514 static_cast<PrintDialogGtk2*>(user_data)->OnJobCompleted(print_job, error);
517 void PrintDialogGtk2::OnJobCompleted(GtkPrintJob* print_job, GError* error) {
518 if (error)
519 LOG(ERROR) << "Printing failed: " << error->message;
520 if (print_job)
521 g_object_unref(print_job);
522 base::FileUtilProxy::DeleteFile(
523 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE).get(),
524 path_to_pdf_,
525 false,
526 base::FileUtilProxy::StatusCallback());
527 // Printing finished. Matches AddRef() in PrintDocument();
528 Release();
531 void PrintDialogGtk2::InitPrintSettings(PrintSettings* settings) {
532 InitPrintSettingsGtk(gtk_settings_, page_setup_, settings);
533 context_->InitWithSettings(*settings);
536 void PrintDialogGtk2::OnWindowDestroying(aura::Window* window) {
537 DCHECK_EQ(libgtk2ui::GetAuraTransientParent(dialog_), window);
539 libgtk2ui::ClearAuraTransientParent(dialog_);
540 window->RemoveObserver(this);
541 Release();