Home | History | Annotate | Download | only in libgtk2ui
      1 // Copyright 2014 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/ui/libgtk2ui/print_dialog_gtk2.h"
      6 
      7 #include <gtk/gtkunixprint.h>
      8 
      9 #include <algorithm>
     10 #include <cmath>
     11 #include <string>
     12 #include <vector>
     13 
     14 #include "base/bind.h"
     15 #include "base/file_util.h"
     16 #include "base/files/file_util_proxy.h"
     17 #include "base/lazy_instance.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 "chrome/browser/ui/libgtk2ui/gtk2_util.h"
     23 #include "chrome/browser/ui/libgtk2ui/printing_gtk2_util.h"
     24 #include "printing/metafile.h"
     25 #include "printing/print_job_constants.h"
     26 #include "printing/print_settings.h"
     27 #include "ui/aura/window.h"
     28 
     29 using content::BrowserThread;
     30 using printing::PageRanges;
     31 using printing::PrintSettings;
     32 
     33 namespace {
     34 
     35 // CUPS Duplex attribute and values.
     36 const char kCUPSDuplex[] = "cups-Duplex";
     37 const char kDuplexNone[] = "None";
     38 const char kDuplexTumble[] = "DuplexTumble";
     39 const char kDuplexNoTumble[] = "DuplexNoTumble";
     40 
     41 int kPaperSizeTresholdMicrons = 100;
     42 int kMicronsInMm = 1000;
     43 
     44 // Checks whether gtk_paper_size can be used to represent user selected media.
     45 // In fuzzy match mode checks that paper sizes are "close enough" (less than
     46 // 1mm difference). In the exact mode, looks for the paper with the same PPD
     47 // name and "close enough" size.
     48 bool PaperSizeMatch(GtkPaperSize* gtk_paper_size,
     49                     const PrintSettings::RequestedMedia& media,
     50                     bool fuzzy_match) {
     51   if (!gtk_paper_size) {
     52     return false;
     53   }
     54   gfx::Size paper_size_microns(
     55       static_cast<int>(gtk_paper_size_get_width(gtk_paper_size, GTK_UNIT_MM) *
     56                        kMicronsInMm + 0.5),
     57       static_cast<int>(gtk_paper_size_get_height(gtk_paper_size, GTK_UNIT_MM) *
     58                        kMicronsInMm + 0.5));
     59   int diff = std::max(
     60       std::abs(paper_size_microns.width() - media.size_microns.width()),
     61       std::abs(paper_size_microns.height() - media.size_microns.height()));
     62   if (fuzzy_match) {
     63     return diff <= kPaperSizeTresholdMicrons;
     64   }
     65   return !media.vendor_id.empty() &&
     66          media.vendor_id == gtk_paper_size_get_ppd_name(gtk_paper_size) &&
     67          diff <= kPaperSizeTresholdMicrons;
     68 }
     69 
     70 // Looks up a paper size matching (in terms of PaperSizeMatch) the user selected
     71 // media in the paper size list reported by GTK. Returns NULL if there's no
     72 // match found.
     73 GtkPaperSize* FindPaperSizeMatch(GList* gtk_paper_sizes,
     74                                  const PrintSettings::RequestedMedia& media) {
     75   GtkPaperSize* first_fuzzy_match = NULL;
     76   for (GList* p = gtk_paper_sizes; p && p->data; p = g_list_next(p)) {
     77     GtkPaperSize* gtk_paper_size = static_cast<GtkPaperSize*>(p->data);
     78     if (PaperSizeMatch(gtk_paper_size, media, false)) {
     79       return gtk_paper_size;
     80     }
     81     if (!first_fuzzy_match && PaperSizeMatch(gtk_paper_size, media, true)) {
     82       first_fuzzy_match = gtk_paper_size;
     83     }
     84   }
     85   return first_fuzzy_match;
     86 }
     87 
     88 class StickyPrintSettingGtk {
     89  public:
     90   StickyPrintSettingGtk() : last_used_settings_(gtk_print_settings_new()) {
     91   }
     92   ~StickyPrintSettingGtk() {
     93     NOTREACHED();  // Intended to be used with a Leaky LazyInstance.
     94   }
     95 
     96   GtkPrintSettings* settings() {
     97     return last_used_settings_;
     98   }
     99 
    100   void SetLastUsedSettings(GtkPrintSettings* settings) {
    101     DCHECK(last_used_settings_);
    102     g_object_unref(last_used_settings_);
    103     last_used_settings_ = gtk_print_settings_copy(settings);
    104   }
    105 
    106  private:
    107   GtkPrintSettings* last_used_settings_;
    108 
    109   DISALLOW_COPY_AND_ASSIGN(StickyPrintSettingGtk);
    110 };
    111 
    112 base::LazyInstance<StickyPrintSettingGtk>::Leaky g_last_used_settings =
    113     LAZY_INSTANCE_INITIALIZER;
    114 
    115 // Helper class to track GTK printers.
    116 class GtkPrinterList {
    117  public:
    118   GtkPrinterList() : default_printer_(NULL) {
    119     gtk_enumerate_printers(SetPrinter, this, NULL, TRUE);
    120   }
    121 
    122   ~GtkPrinterList() {
    123     for (std::vector<GtkPrinter*>::iterator it = printers_.begin();
    124          it < printers_.end(); ++it) {
    125       g_object_unref(*it);
    126     }
    127   }
    128 
    129   // Can return NULL if there's no default printer. E.g. Printer on a laptop
    130   // is "home_printer", but the laptop is at work.
    131   GtkPrinter* default_printer() {
    132     return default_printer_;
    133   }
    134 
    135   // Can return NULL if the printer cannot be found due to:
    136   // - Printer list out of sync with printer dialog UI.
    137   // - Querying for non-existant printers like 'Print to PDF'.
    138   GtkPrinter* GetPrinterWithName(const std::string& name) {
    139     if (name.empty())
    140       return NULL;
    141 
    142     for (std::vector<GtkPrinter*>::iterator it = printers_.begin();
    143          it < printers_.end(); ++it) {
    144       if (gtk_printer_get_name(*it) == name) {
    145         return *it;
    146       }
    147     }
    148 
    149     return NULL;
    150   }
    151 
    152  private:
    153   // Callback function used by gtk_enumerate_printers() to get all printer.
    154   static gboolean SetPrinter(GtkPrinter* printer, gpointer data) {
    155     GtkPrinterList* printer_list = reinterpret_cast<GtkPrinterList*>(data);
    156     if (gtk_printer_is_default(printer))
    157       printer_list->default_printer_ = printer;
    158 
    159     g_object_ref(printer);
    160     printer_list->printers_.push_back(printer);
    161 
    162     return FALSE;
    163   }
    164 
    165   std::vector<GtkPrinter*> printers_;
    166   GtkPrinter* default_printer_;
    167 };
    168 
    169 }  // namespace
    170 
    171 // static
    172 printing::PrintDialogGtkInterface* PrintDialogGtk2::CreatePrintDialog(
    173     PrintingContextLinux* context) {
    174   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    175   return new PrintDialogGtk2(context);
    176 }
    177 
    178 PrintDialogGtk2::PrintDialogGtk2(PrintingContextLinux* context)
    179     : context_(context),
    180       dialog_(NULL),
    181       gtk_settings_(NULL),
    182       page_setup_(NULL),
    183       printer_(NULL) {
    184 }
    185 
    186 PrintDialogGtk2::~PrintDialogGtk2() {
    187   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    188 
    189   if (dialog_) {
    190     aura::Window* parent = libgtk2ui::GetAuraTransientParent(dialog_);
    191     if (parent) {
    192       parent->RemoveObserver(this);
    193       libgtk2ui::ClearAuraTransientParent(dialog_);
    194     }
    195     gtk_widget_destroy(dialog_);
    196     dialog_ = NULL;
    197   }
    198   if (gtk_settings_) {
    199     g_object_unref(gtk_settings_);
    200     gtk_settings_ = NULL;
    201   }
    202   if (page_setup_) {
    203     g_object_unref(page_setup_);
    204     page_setup_ = NULL;
    205   }
    206   if (printer_) {
    207     g_object_unref(printer_);
    208     printer_ = NULL;
    209   }
    210 }
    211 
    212 void PrintDialogGtk2::UseDefaultSettings() {
    213   DCHECK(!page_setup_);
    214   DCHECK(!printer_);
    215 
    216   // |gtk_settings_| is a new copy.
    217   gtk_settings_ =
    218       gtk_print_settings_copy(g_last_used_settings.Get().settings());
    219   page_setup_ = gtk_page_setup_new();
    220 
    221   PrintSettings settings;
    222   InitPrintSettings(&settings);
    223 }
    224 
    225 bool PrintDialogGtk2::UpdateSettings(printing::PrintSettings* settings) {
    226   if (!gtk_settings_) {
    227     gtk_settings_ =
    228         gtk_print_settings_copy(g_last_used_settings.Get().settings());
    229   }
    230 
    231   scoped_ptr<GtkPrinterList> printer_list(new GtkPrinterList);
    232   printer_ = printer_list->GetPrinterWithName(
    233       base::UTF16ToUTF8(settings->device_name()));
    234   if (printer_) {
    235     g_object_ref(printer_);
    236     gtk_print_settings_set_printer(gtk_settings_,
    237                                    gtk_printer_get_name(printer_));
    238     if (!page_setup_) {
    239       page_setup_ = gtk_printer_get_default_page_size(printer_);
    240     }
    241   }
    242 
    243   gtk_print_settings_set_n_copies(gtk_settings_, settings->copies());
    244   gtk_print_settings_set_collate(gtk_settings_, settings->collate());
    245 
    246 #if defined(USE_CUPS)
    247   std::string color_value;
    248   std::string color_setting_name;
    249   printing::GetColorModelForMode(settings->color(), &color_setting_name,
    250                                  &color_value);
    251   gtk_print_settings_set(gtk_settings_, color_setting_name.c_str(),
    252                          color_value.c_str());
    253 
    254   if (settings->duplex_mode() != printing::UNKNOWN_DUPLEX_MODE) {
    255     const char* cups_duplex_mode = NULL;
    256     switch (settings->duplex_mode()) {
    257       case printing::LONG_EDGE:
    258         cups_duplex_mode = kDuplexNoTumble;
    259         break;
    260       case printing::SHORT_EDGE:
    261         cups_duplex_mode = kDuplexTumble;
    262         break;
    263       case printing::SIMPLEX:
    264         cups_duplex_mode = kDuplexNone;
    265         break;
    266       default:  // UNKNOWN_DUPLEX_MODE
    267         NOTREACHED();
    268         break;
    269     }
    270     gtk_print_settings_set(gtk_settings_, kCUPSDuplex, cups_duplex_mode);
    271   }
    272 #endif
    273   if (!page_setup_)
    274     page_setup_ = gtk_page_setup_new();
    275 
    276   if (page_setup_ && !settings->requested_media().IsDefault()) {
    277     const PrintSettings::RequestedMedia& requested_media =
    278         settings->requested_media();
    279     GtkPaperSize* gtk_current_paper_size =
    280         gtk_page_setup_get_paper_size(page_setup_);
    281     if (!PaperSizeMatch(gtk_current_paper_size, requested_media,
    282                         true /*fuzzy_match*/)) {
    283       GList* gtk_paper_sizes =
    284           gtk_paper_size_get_paper_sizes(false /*include_custom*/);
    285       if (gtk_paper_sizes) {
    286         GtkPaperSize* matching_gtk_paper_size =
    287             FindPaperSizeMatch(gtk_paper_sizes, requested_media);
    288         if (matching_gtk_paper_size) {
    289           VLOG(1) << "Using listed paper size";
    290           gtk_page_setup_set_paper_size(page_setup_, matching_gtk_paper_size);
    291         } else {
    292           VLOG(1) << "Using custom paper size";
    293           GtkPaperSize* custom_size = gtk_paper_size_new_custom(
    294               requested_media.vendor_id.c_str(),
    295               requested_media.vendor_id.c_str(),
    296               requested_media.size_microns.width() / kMicronsInMm,
    297               requested_media.size_microns.height() / kMicronsInMm,
    298               GTK_UNIT_MM);
    299           gtk_page_setup_set_paper_size(page_setup_, custom_size);
    300           gtk_paper_size_free(custom_size);
    301         }
    302         g_list_free_full(gtk_paper_sizes,
    303                          reinterpret_cast<GDestroyNotify>(gtk_paper_size_free));
    304       }
    305     } else {
    306       VLOG(1) << "Using default paper size";
    307     }
    308   }
    309 
    310   gtk_print_settings_set_orientation(
    311       gtk_settings_,
    312       settings->landscape() ? GTK_PAGE_ORIENTATION_LANDSCAPE :
    313                               GTK_PAGE_ORIENTATION_PORTRAIT);
    314 
    315   InitPrintSettings(settings);
    316   return true;
    317 }
    318 
    319 void PrintDialogGtk2::ShowDialog(
    320     gfx::NativeView parent_view,
    321     bool has_selection,
    322     const PrintingContextLinux::PrintSettingsCallback& callback) {
    323   callback_ = callback;
    324 
    325   dialog_ = gtk_print_unix_dialog_new(NULL, NULL);
    326   libgtk2ui::SetGtkTransientForAura(dialog_, parent_view);
    327   if (parent_view)
    328     parent_view->AddObserver(this);
    329   g_signal_connect(dialog_, "delete-event",
    330                    G_CALLBACK(gtk_widget_hide_on_delete), NULL);
    331 
    332 
    333   // Set modal so user cannot focus the same tab and press print again.
    334   gtk_window_set_modal(GTK_WINDOW(dialog_), TRUE);
    335 
    336   // Since we only generate PDF, only show printers that support PDF.
    337   // TODO(thestig) Add more capabilities to support?
    338   GtkPrintCapabilities cap = static_cast<GtkPrintCapabilities>(
    339       GTK_PRINT_CAPABILITY_GENERATE_PDF |
    340       GTK_PRINT_CAPABILITY_PAGE_SET |
    341       GTK_PRINT_CAPABILITY_COPIES |
    342       GTK_PRINT_CAPABILITY_COLLATE |
    343       GTK_PRINT_CAPABILITY_REVERSE);
    344   gtk_print_unix_dialog_set_manual_capabilities(GTK_PRINT_UNIX_DIALOG(dialog_),
    345                                                 cap);
    346   gtk_print_unix_dialog_set_embed_page_setup(GTK_PRINT_UNIX_DIALOG(dialog_),
    347                                              TRUE);
    348   gtk_print_unix_dialog_set_support_selection(GTK_PRINT_UNIX_DIALOG(dialog_),
    349                                               TRUE);
    350   gtk_print_unix_dialog_set_has_selection(GTK_PRINT_UNIX_DIALOG(dialog_),
    351                                           has_selection);
    352   gtk_print_unix_dialog_set_settings(GTK_PRINT_UNIX_DIALOG(dialog_),
    353                                      gtk_settings_);
    354   g_signal_connect(dialog_, "response", G_CALLBACK(OnResponseThunk), this);
    355   gtk_widget_show(dialog_);
    356 }
    357 
    358 void PrintDialogGtk2::PrintDocument(const printing::Metafile* metafile,
    359                                    const base::string16& document_name) {
    360   // This runs on the print worker thread, does not block the UI thread.
    361   DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
    362 
    363   // The document printing tasks can outlive the PrintingContext that created
    364   // this dialog.
    365   AddRef();
    366 
    367   bool error = false;
    368   if (!base::CreateTemporaryFile(&path_to_pdf_)) {
    369     LOG(ERROR) << "Creating temporary file failed";
    370     error = true;
    371   }
    372 
    373   if (!error && !metafile->SaveTo(path_to_pdf_)) {
    374     LOG(ERROR) << "Saving metafile failed";
    375     base::DeleteFile(path_to_pdf_, false);
    376     error = true;
    377   }
    378 
    379   if (error) {
    380     // Matches AddRef() above.
    381     Release();
    382   } else {
    383     // No errors, continue printing.
    384     BrowserThread::PostTask(
    385         BrowserThread::UI, FROM_HERE,
    386         base::Bind(&PrintDialogGtk2::SendDocumentToPrinter, this,
    387                    document_name));
    388   }
    389 }
    390 
    391 void PrintDialogGtk2::AddRefToDialog() {
    392   AddRef();
    393 }
    394 
    395 void PrintDialogGtk2::ReleaseDialog() {
    396   Release();
    397 }
    398 
    399 void PrintDialogGtk2::OnResponse(GtkWidget* dialog, int response_id) {
    400   int num_matched_handlers = g_signal_handlers_disconnect_by_func(
    401       dialog_, reinterpret_cast<gpointer>(&OnResponseThunk), this);
    402   CHECK_EQ(1, num_matched_handlers);
    403 
    404   gtk_widget_hide(dialog_);
    405 
    406   switch (response_id) {
    407     case GTK_RESPONSE_OK: {
    408       if (gtk_settings_)
    409         g_object_unref(gtk_settings_);
    410       gtk_settings_ = gtk_print_unix_dialog_get_settings(
    411           GTK_PRINT_UNIX_DIALOG(dialog_));
    412 
    413       if (printer_)
    414         g_object_unref(printer_);
    415       printer_ = gtk_print_unix_dialog_get_selected_printer(
    416           GTK_PRINT_UNIX_DIALOG(dialog_));
    417       g_object_ref(printer_);
    418 
    419       if (page_setup_)
    420         g_object_unref(page_setup_);
    421       page_setup_ = gtk_print_unix_dialog_get_page_setup(
    422           GTK_PRINT_UNIX_DIALOG(dialog_));
    423       g_object_ref(page_setup_);
    424 
    425       // Handle page ranges.
    426       PageRanges ranges_vector;
    427       gint num_ranges;
    428       bool print_selection_only = false;
    429       switch (gtk_print_settings_get_print_pages(gtk_settings_)) {
    430         case GTK_PRINT_PAGES_RANGES: {
    431           GtkPageRange* gtk_range =
    432               gtk_print_settings_get_page_ranges(gtk_settings_, &num_ranges);
    433           if (gtk_range) {
    434             for (int i = 0; i < num_ranges; ++i) {
    435               printing::PageRange range;
    436               range.from = gtk_range[i].start;
    437               range.to = gtk_range[i].end;
    438               ranges_vector.push_back(range);
    439             }
    440             g_free(gtk_range);
    441           }
    442           break;
    443         }
    444         case GTK_PRINT_PAGES_SELECTION:
    445           print_selection_only = true;
    446           break;
    447         case GTK_PRINT_PAGES_ALL:
    448           // Leave |ranges_vector| empty to indicate print all pages.
    449           break;
    450         case GTK_PRINT_PAGES_CURRENT:
    451         default:
    452           NOTREACHED();
    453           break;
    454       }
    455 
    456       PrintSettings settings;
    457       settings.set_ranges(ranges_vector);
    458       settings.set_selection_only(print_selection_only);
    459       InitPrintSettingsGtk(gtk_settings_, page_setup_, &settings);
    460       context_->InitWithSettings(settings);
    461       callback_.Run(PrintingContextLinux::OK);
    462       callback_.Reset();
    463       return;
    464     }
    465     case GTK_RESPONSE_DELETE_EVENT:  // Fall through.
    466     case GTK_RESPONSE_CANCEL: {
    467       callback_.Run(PrintingContextLinux::CANCEL);
    468       callback_.Reset();
    469       return;
    470     }
    471     case GTK_RESPONSE_APPLY:
    472     default: {
    473       NOTREACHED();
    474     }
    475   }
    476 }
    477 
    478 void PrintDialogGtk2::SendDocumentToPrinter(
    479     const base::string16& document_name) {
    480   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    481 
    482   // If |printer_| is NULL then somehow the GTK printer list changed out under
    483   // us. In which case, just bail out.
    484   if (!printer_) {
    485     // Matches AddRef() in PrintDocument();
    486     Release();
    487     return;
    488   }
    489 
    490   // Save the settings for next time.
    491   g_last_used_settings.Get().SetLastUsedSettings(gtk_settings_);
    492 
    493   GtkPrintJob* print_job = gtk_print_job_new(
    494       base::UTF16ToUTF8(document_name).c_str(),
    495       printer_,
    496       gtk_settings_,
    497       page_setup_);
    498   gtk_print_job_set_source_file(print_job, path_to_pdf_.value().c_str(), NULL);
    499   gtk_print_job_send(print_job, OnJobCompletedThunk, this, NULL);
    500 }
    501 
    502 // static
    503 void PrintDialogGtk2::OnJobCompletedThunk(GtkPrintJob* print_job,
    504                                          gpointer user_data,
    505                                          GError* error) {
    506   static_cast<PrintDialogGtk2*>(user_data)->OnJobCompleted(print_job, error);
    507 }
    508 
    509 void PrintDialogGtk2::OnJobCompleted(GtkPrintJob* print_job, GError* error) {
    510   if (error)
    511     LOG(ERROR) << "Printing failed: " << error->message;
    512   if (print_job)
    513     g_object_unref(print_job);
    514   base::FileUtilProxy::DeleteFile(
    515       BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE).get(),
    516       path_to_pdf_,
    517       false,
    518       base::FileUtilProxy::StatusCallback());
    519   // Printing finished. Matches AddRef() in PrintDocument();
    520   Release();
    521 }
    522 
    523 void PrintDialogGtk2::InitPrintSettings(PrintSettings* settings) {
    524   InitPrintSettingsGtk(gtk_settings_, page_setup_, settings);
    525   context_->InitWithSettings(*settings);
    526 }
    527 
    528 void PrintDialogGtk2::OnWindowDestroying(aura::Window* window) {
    529   DCHECK_EQ(libgtk2ui::GetAuraTransientParent(dialog_), window);
    530 
    531   libgtk2ui::ClearAuraTransientParent(dialog_);
    532   window->RemoveObserver(this);
    533   Release();
    534 }
    535