Home | History | Annotate | Download | only in printing
      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 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 
    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(
    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
    413 void PrintDialogGtk::OnJobCompletedThunk(GtkPrintJob* print_job,
    414                                          gpointer user_data,
    415                                          GError* error) {
    416   static_cast<PrintDialogGtk*>(user_data)->OnJobCompleted(print_job, error);
    417 }
    418 
    419 void 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 
    433 void PrintDialogGtk::InitPrintSettings(PrintSettings* settings) {
    434   printing::PrintSettingsInitializerGtk::InitPrintSettings(
    435       gtk_settings_, page_setup_, settings);
    436   context_->InitWithSettings(*settings);
    437 }
    438