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