Home | History | Annotate | Download | only in printing
      1 // Copyright (c) 2011 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 <fcntl.h>
      8 #include <gtk/gtkpagesetupunixdialog.h>
      9 #include <gtk/gtkprintjob.h>
     10 #include <sys/stat.h>
     11 #include <sys/types.h>
     12 
     13 #include <string>
     14 #include <vector>
     15 
     16 #include "base/file_util.h"
     17 #include "base/file_util_proxy.h"
     18 #include "base/logging.h"
     19 #include "base/synchronization/waitable_event.h"
     20 #include "base/utf_string_conversions.h"
     21 #include "chrome/browser/ui/browser_list.h"
     22 #include "chrome/browser/ui/browser_window.h"
     23 #include "printing/metafile.h"
     24 #include "printing/print_job_constants.h"
     25 #include "printing/print_settings_initializer_gtk.h"
     26 
     27 using printing::PageRanges;
     28 using printing::PrintSettings;
     29 
     30 namespace {
     31 
     32 // Helper class to track GTK printers.
     33 class GtkPrinterList {
     34  public:
     35   GtkPrinterList() : default_printer_(NULL) {
     36     gtk_enumerate_printers((GtkPrinterFunc)SetPrinter, this, NULL, TRUE);
     37   }
     38 
     39   ~GtkPrinterList() {
     40     for (std::vector<GtkPrinter*>::iterator it = printers_.begin();
     41          it < printers_.end(); ++it) {
     42       g_object_unref(*it);
     43     }
     44   }
     45 
     46   // Can return NULL if there's no default printer. E.g. Printer on a laptop
     47   // is "home_printer", but the laptop is at work.
     48   GtkPrinter* default_printer() {
     49     return default_printer_;
     50   }
     51 
     52   // Can return NULL if the printer cannot be found due to:
     53   // - Printer list out of sync with printer dialog UI.
     54   // - Querying for non-existant printers like 'Print to PDF'.
     55   GtkPrinter* GetPrinterWithName(const char* name) {
     56     if (!name || !*name)
     57       return NULL;
     58 
     59     for (std::vector<GtkPrinter*>::iterator it = printers_.begin();
     60          it < printers_.end(); ++it) {
     61       if (strcmp(name, gtk_printer_get_name(*it)) == 0) {
     62         return *it;
     63       }
     64     }
     65 
     66     return NULL;
     67   }
     68 
     69  private:
     70   // Callback function used by gtk_enumerate_printers() to get all printer.
     71   static bool SetPrinter(GtkPrinter* printer, GtkPrinterList* printer_list) {
     72     if (gtk_printer_is_default(printer))
     73       printer_list->default_printer_ = printer;
     74 
     75     g_object_ref(printer);
     76     printer_list->printers_.push_back(printer);
     77 
     78     return false;
     79   }
     80 
     81   std::vector<GtkPrinter*> printers_;
     82   GtkPrinter* default_printer_;
     83 };
     84 
     85 }  // namespace
     86 
     87 // static
     88 printing::PrintDialogGtkInterface* PrintDialogGtk::CreatePrintDialog(
     89     PrintingContextCairo* context) {
     90   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     91   return new PrintDialogGtk(context);
     92 }
     93 
     94 PrintDialogGtk::PrintDialogGtk(PrintingContextCairo* context)
     95     : callback_(NULL),
     96       context_(context),
     97       dialog_(NULL),
     98       gtk_settings_(NULL),
     99       page_setup_(NULL),
    100       printer_(NULL) {
    101 }
    102 
    103 PrintDialogGtk::~PrintDialogGtk() {
    104   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    105 
    106   if (dialog_) {
    107     gtk_widget_destroy(dialog_);
    108     dialog_ = NULL;
    109   }
    110   if (gtk_settings_) {
    111     g_object_unref(gtk_settings_);
    112     gtk_settings_ = NULL;
    113   }
    114   if (page_setup_) {
    115     g_object_unref(page_setup_);
    116     page_setup_ = NULL;
    117   }
    118   if (printer_) {
    119     g_object_unref(printer_);
    120     printer_ = NULL;
    121   }
    122 }
    123 
    124 void PrintDialogGtk::UseDefaultSettings() {
    125   DCHECK(!save_document_event_.get());
    126   DCHECK(!page_setup_);
    127 
    128   // |gtk_settings_| is a new object.
    129   gtk_settings_ = gtk_print_settings_new();
    130 
    131   scoped_ptr<GtkPrinterList> printer_list(new GtkPrinterList);
    132   printer_ = printer_list->default_printer();
    133   if (printer_) {
    134     g_object_ref(printer_);
    135     gtk_print_settings_set_printer(gtk_settings_,
    136                                    gtk_printer_get_name(printer_));
    137 #if GTK_CHECK_VERSION(2, 14, 0)
    138     page_setup_ = gtk_printer_get_default_page_size(printer_);
    139 #endif
    140   }
    141 
    142   if (!page_setup_)
    143     page_setup_ = gtk_page_setup_new();
    144 
    145   // No page range to initialize for default settings.
    146   PageRanges ranges_vector;
    147   InitPrintSettings(ranges_vector);
    148 }
    149 
    150 bool PrintDialogGtk::UpdateSettings(const DictionaryValue& settings,
    151                                     const printing::PageRanges& ranges) {
    152   std::string printer_name;
    153   settings.GetString(printing::kSettingPrinterName, &printer_name);
    154 
    155   scoped_ptr<GtkPrinterList> printer_list(new GtkPrinterList);
    156   printer_ = printer_list->GetPrinterWithName(printer_name.c_str());
    157   if (printer_) {
    158     g_object_ref(printer_);
    159     gtk_print_settings_set_printer(gtk_settings_,
    160                                    gtk_printer_get_name(printer_));
    161   }
    162 
    163   bool landscape;
    164   if (!settings.GetBoolean(printing::kSettingLandscape, &landscape))
    165     return false;
    166 
    167   gtk_print_settings_set_orientation(
    168       gtk_settings_,
    169       landscape ? GTK_PAGE_ORIENTATION_LANDSCAPE :
    170                   GTK_PAGE_ORIENTATION_PORTRAIT);
    171 
    172   int copies;
    173   if (!settings.GetInteger(printing::kSettingCopies, &copies))
    174     return false;
    175   gtk_print_settings_set_n_copies(gtk_settings_, copies);
    176 
    177   bool collate;
    178   if (!settings.GetBoolean(printing::kSettingCollate, &collate))
    179     return false;
    180   gtk_print_settings_set_collate(gtk_settings_, collate);
    181 
    182   // TODO(thestig) Color: gtk_print_settings_set_color() does not work.
    183   // TODO(thestig) Duplex: gtk_print_settings_set_duplex() does not work.
    184 
    185   InitPrintSettings(ranges);
    186   return true;
    187 }
    188 
    189 void PrintDialogGtk::ShowDialog(
    190     PrintingContextCairo::PrintSettingsCallback* callback) {
    191   DCHECK(!save_document_event_.get());
    192 
    193   callback_ = callback;
    194 
    195   GtkWindow* parent = BrowserList::GetLastActive()->window()->GetNativeHandle();
    196   // TODO(estade): We need a window title here.
    197   dialog_ = gtk_print_unix_dialog_new(NULL, parent);
    198 
    199   // Set modal so user cannot focus the same tab and press print again.
    200   gtk_window_set_modal(GTK_WINDOW(dialog_), TRUE);
    201 
    202   // Since we only generate PDF, only show printers that support PDF.
    203   // TODO(thestig) Add more capabilities to support?
    204   GtkPrintCapabilities cap = static_cast<GtkPrintCapabilities>(
    205       GTK_PRINT_CAPABILITY_GENERATE_PDF |
    206       GTK_PRINT_CAPABILITY_PAGE_SET |
    207       GTK_PRINT_CAPABILITY_COPIES |
    208       GTK_PRINT_CAPABILITY_COLLATE |
    209       GTK_PRINT_CAPABILITY_REVERSE);
    210   gtk_print_unix_dialog_set_manual_capabilities(GTK_PRINT_UNIX_DIALOG(dialog_),
    211                                                 cap);
    212 #if GTK_CHECK_VERSION(2, 18, 0)
    213   gtk_print_unix_dialog_set_embed_page_setup(GTK_PRINT_UNIX_DIALOG(dialog_),
    214                                              TRUE);
    215 #endif
    216   g_signal_connect(dialog_, "response", G_CALLBACK(OnResponseThunk), this);
    217   gtk_widget_show(dialog_);
    218 }
    219 
    220 void PrintDialogGtk::PrintDocument(const printing::Metafile* metafile,
    221                                    const string16& document_name) {
    222   // This runs on the print worker thread, does not block the UI thread.
    223   DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
    224 
    225   // The document printing tasks can outlive the PrintingContext that created
    226   // this dialog.
    227   AddRef();
    228   DCHECK(!save_document_event_.get());
    229   save_document_event_.reset(new base::WaitableEvent(false, false));
    230 
    231   BrowserThread::PostTask(
    232       BrowserThread::FILE, FROM_HERE,
    233       NewRunnableMethod(this,
    234                         &PrintDialogGtk::SaveDocumentToDisk,
    235                         metafile,
    236                         document_name));
    237   // Wait for SaveDocumentToDisk() to finish.
    238   save_document_event_->Wait();
    239 }
    240 
    241 void PrintDialogGtk::AddRefToDialog() {
    242   AddRef();
    243 }
    244 
    245 void PrintDialogGtk::ReleaseDialog() {
    246   Release();
    247 }
    248 
    249 void PrintDialogGtk::OnResponse(GtkWidget* dialog, int response_id) {
    250   gtk_widget_hide(dialog_);
    251 
    252   switch (response_id) {
    253     case GTK_RESPONSE_OK: {
    254       if (gtk_settings_)
    255         g_object_unref(gtk_settings_);
    256       gtk_settings_ = gtk_print_unix_dialog_get_settings(
    257           GTK_PRINT_UNIX_DIALOG(dialog_));
    258 
    259       if (printer_)
    260         g_object_unref(printer_);
    261       printer_ = gtk_print_unix_dialog_get_selected_printer(
    262           GTK_PRINT_UNIX_DIALOG(dialog_));
    263       g_object_ref(printer_);
    264 
    265       if (page_setup_)
    266         g_object_unref(page_setup_);
    267       page_setup_ = gtk_print_unix_dialog_get_page_setup(
    268           GTK_PRINT_UNIX_DIALOG(dialog_));
    269       g_object_ref(page_setup_);
    270 
    271       // Handle page ranges.
    272       PageRanges ranges_vector;
    273       gint num_ranges;
    274       GtkPageRange* gtk_range =
    275           gtk_print_settings_get_page_ranges(gtk_settings_, &num_ranges);
    276       if (gtk_range) {
    277         for (int i = 0; i < num_ranges; ++i) {
    278           printing::PageRange range;
    279           range.from = gtk_range[i].start;
    280           range.to = gtk_range[i].end;
    281           ranges_vector.push_back(range);
    282         }
    283         g_free(gtk_range);
    284       }
    285 
    286       PrintSettings settings;
    287       printing::PrintSettingsInitializerGtk::InitPrintSettings(
    288           gtk_settings_, page_setup_, ranges_vector, false, &settings);
    289       context_->InitWithSettings(settings);
    290       callback_->Run(PrintingContextCairo::OK);
    291       callback_ = NULL;
    292       return;
    293     }
    294     case GTK_RESPONSE_DELETE_EVENT:  // Fall through.
    295     case GTK_RESPONSE_CANCEL: {
    296       callback_->Run(PrintingContextCairo::CANCEL);
    297       callback_ = NULL;
    298       return;
    299     }
    300     case GTK_RESPONSE_APPLY:
    301     default: {
    302       NOTREACHED();
    303     }
    304   }
    305 }
    306 
    307 void PrintDialogGtk::SaveDocumentToDisk(const printing::Metafile* metafile,
    308                                         const string16& document_name) {
    309   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    310 
    311   bool error = false;
    312   if (!file_util::CreateTemporaryFile(&path_to_pdf_)) {
    313     LOG(ERROR) << "Creating temporary file failed";
    314     error = true;
    315   }
    316 
    317   if (!error && !metafile->SaveTo(path_to_pdf_)) {
    318     LOG(ERROR) << "Saving metafile failed";
    319     file_util::Delete(path_to_pdf_, false);
    320     error = true;
    321   }
    322 
    323   // Done saving, let PrintDialogGtk::PrintDocument() continue.
    324   save_document_event_->Signal();
    325 
    326   if (error) {
    327     // Matches AddRef() in PrintDocument();
    328     Release();
    329   } else {
    330     // No errors, continue printing.
    331     BrowserThread::PostTask(
    332         BrowserThread::UI, FROM_HERE,
    333         NewRunnableMethod(this,
    334                           &PrintDialogGtk::SendDocumentToPrinter,
    335                           document_name));
    336   }
    337 }
    338 
    339 void PrintDialogGtk::SendDocumentToPrinter(const string16& document_name) {
    340   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    341 
    342   // If |printer_| is NULL then somehow the GTK printer list changed out under
    343   // us. In which case, just bail out.
    344   if (!printer_) {
    345     // Matches AddRef() in PrintDocument();
    346     Release();
    347     return;
    348   }
    349 
    350   GtkPrintJob* print_job = gtk_print_job_new(
    351       UTF16ToUTF8(document_name).c_str(),
    352       printer_,
    353       gtk_settings_,
    354       page_setup_);
    355   gtk_print_job_set_source_file(print_job, path_to_pdf_.value().c_str(), NULL);
    356   gtk_print_job_send(print_job, OnJobCompletedThunk, this, NULL);
    357 }
    358 
    359 // static
    360 void PrintDialogGtk::OnJobCompletedThunk(GtkPrintJob* print_job,
    361                                          gpointer user_data,
    362                                          GError* error) {
    363   static_cast<PrintDialogGtk*>(user_data)->OnJobCompleted(print_job, error);
    364 }
    365 
    366 void PrintDialogGtk::OnJobCompleted(GtkPrintJob* print_job, GError* error) {
    367   if (error)
    368     LOG(ERROR) << "Printing failed: " << error->message;
    369   if (print_job)
    370     g_object_unref(print_job);
    371   base::FileUtilProxy::Delete(
    372       BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE),
    373       path_to_pdf_,
    374       false,
    375       NULL);
    376   // Printing finished. Matches AddRef() in PrintDocument();
    377   Release();
    378 }
    379 
    380 void PrintDialogGtk::InitPrintSettings(const PageRanges& page_ranges) {
    381   PrintSettings settings;
    382   printing::PrintSettingsInitializerGtk::InitPrintSettings(
    383       gtk_settings_, page_setup_, page_ranges, false, &settings);
    384   context_->InitWithSettings(settings);
    385 }
    386