- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / printing / print_dialog_gtk.cc
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     PrintingContextGtk* context) {
123   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
124   return new PrintDialogGtk(context);
125 }
126
127 PrintDialogGtk::PrintDialogGtk(PrintingContextGtk* 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       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 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
267 void PrintDialogGtk::PrintDocument(const printing::Metafile* metafile,
268                                    const 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 (!file_util::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(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
388 void PrintDialogGtk::SendDocumentToPrinter(const string16& document_name) {
389   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
390
391   // If |printer_| is NULL then somehow the GTK printer list changed out under
392   // us. In which case, just bail out.
393   if (!printer_) {
394     // Matches AddRef() in PrintDocument();
395     Release();
396     return;
397   }
398
399   // Save the settings for next time.
400   g_last_used_settings.Get().SetLastUsedSettings(gtk_settings_);
401
402   GtkPrintJob* print_job = gtk_print_job_new(
403       UTF16ToUTF8(document_name).c_str(),
404       printer_,
405       gtk_settings_,
406       page_setup_);
407   gtk_print_job_set_source_file(print_job, path_to_pdf_.value().c_str(), NULL);
408   gtk_print_job_send(print_job, OnJobCompletedThunk, this, NULL);
409 }
410
411 // static
412 void PrintDialogGtk::OnJobCompletedThunk(GtkPrintJob* print_job,
413                                          gpointer user_data,
414                                          GError* error) {
415   static_cast<PrintDialogGtk*>(user_data)->OnJobCompleted(print_job, error);
416 }
417
418 void PrintDialogGtk::OnJobCompleted(GtkPrintJob* print_job, GError* error) {
419   if (error)
420     LOG(ERROR) << "Printing failed: " << error->message;
421   if (print_job)
422     g_object_unref(print_job);
423   base::FileUtilProxy::DeleteFile(
424       BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE).get(),
425       path_to_pdf_,
426       false,
427       base::FileUtilProxy::StatusCallback());
428   // Printing finished. Matches AddRef() in PrintDocument();
429   Release();
430 }
431
432 void PrintDialogGtk::InitPrintSettings(PrintSettings* settings) {
433   printing::PrintSettingsInitializerGtk::InitPrintSettings(
434       gtk_settings_, page_setup_, settings);
435   context_->InitWithSettings(*settings);
436 }