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
25using content::BrowserThread;
26using printing::PageRanges;
27using printing::PrintSettings;
28
29namespace {
30
31// CUPS Duplex attribute and values.
32const char kCUPSDuplex[] = "cups-Duplex";
33const char kDuplexNone[] = "None";
34const char kDuplexTumble[] = "DuplexTumble";
35const char kDuplexNoTumble[] = "DuplexNoTumble";
36
37class 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
61base::LazyInstance<StickyPrintSettingGtk>::Leaky g_last_used_settings =
62    LAZY_INSTANCE_INITIALIZER;
63
64// Helper class to track GTK printers.
65class 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
121printing::PrintDialogGtkInterface* PrintDialogGtk::CreatePrintDialog(
122    PrintingContextGtk* context) {
123  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
124  return new PrintDialogGtk(context);
125}
126
127PrintDialogGtk::PrintDialogGtk(PrintingContextGtk* context)
128    : context_(context),
129      dialog_(NULL),
130      gtk_settings_(NULL),
131      page_setup_(NULL),
132      printer_(NULL) {
133}
134
135PrintDialogGtk::~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
156void 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
169bool 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      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
229void PrintDialogGtk::ShowDialog(
230    gfx::NativeView parent_view,
231    bool has_selection,
232    const PrintingContextGtk::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
267void 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
300void PrintDialogGtk::AddRefToDialog() {
301  AddRef();
302}
303
304void PrintDialogGtk::ReleaseDialog() {
305  Release();
306}
307
308void 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(PrintingContextGtk::OK);
372      callback_.Reset();
373      return;
374    }
375    case GTK_RESPONSE_DELETE_EVENT:  // Fall through.
376    case GTK_RESPONSE_CANCEL: {
377      callback_.Run(PrintingContextGtk::CANCEL);
378      callback_.Reset();
379      return;
380    }
381    case GTK_RESPONSE_APPLY:
382    default: {
383      NOTREACHED();
384    }
385  }
386}
387
388void 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      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
413void PrintDialogGtk::OnJobCompletedThunk(GtkPrintJob* print_job,
414                                         gpointer user_data,
415                                         GError* error) {
416  static_cast<PrintDialogGtk*>(user_data)->OnJobCompleted(print_job, error);
417}
418
419void 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
433void PrintDialogGtk::InitPrintSettings(PrintSettings* settings) {
434  printing::PrintSettingsInitializerGtk::InitPrintSettings(
435      gtk_settings_, page_setup_, settings);
436  context_->InitWithSettings(*settings);
437}
438