printing_context_mac.mm revision 5821806d5e7f356e8fa4b058a389a808ea183019
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 "printing/printing_context_mac.h"
6
7#import <ApplicationServices/ApplicationServices.h>
8#import <AppKit/AppKit.h>
9
10#import <iomanip>
11#import <numeric>
12
13#include "base/logging.h"
14#include "base/mac/scoped_cftyperef.h"
15#include "base/mac/scoped_nsautorelease_pool.h"
16#include "base/mac/scoped_nsexception_enabler.h"
17#include "base/sys_string_conversions.h"
18#include "base/values.h"
19#include "printing/print_settings_initializer_mac.h"
20
21namespace printing {
22
23namespace {
24
25// Return true if PPD name of paper is equal.
26bool IsPaperNameEqual(const PMPaper& paper1, const PMPaper& paper2) {
27  CFStringRef name1 = NULL;
28  CFStringRef name2 = NULL;
29  return (PMPaperGetPPDPaperName(paper1, &name1) == noErr) &&
30         (PMPaperGetPPDPaperName(paper2, &name2) == noErr) &&
31         (CFStringCompare(name1, name2,
32                          kCFCompareCaseInsensitive) == kCFCompareEqualTo);
33}
34
35}  // namespace
36
37// static
38PrintingContext* PrintingContext::Create(const std::string& app_locale) {
39  return static_cast<PrintingContext*>(new PrintingContextMac(app_locale));
40}
41
42PrintingContextMac::PrintingContextMac(const std::string& app_locale)
43    : PrintingContext(app_locale),
44      print_info_([[NSPrintInfo sharedPrintInfo] copy]),
45      context_(NULL) {
46}
47
48PrintingContextMac::~PrintingContextMac() {
49  ReleaseContext();
50}
51
52void PrintingContextMac::AskUserForSettings(
53    gfx::NativeView parent_view,
54    int max_pages,
55    bool has_selection,
56    const PrintSettingsCallback& callback) {
57  // Third-party print drivers seem to be an area prone to raising exceptions.
58  // This will allow exceptions to be raised, but does not handle them.  The
59  // NSPrintPanel appears to have appropriate NSException handlers.
60  base::mac::ScopedNSExceptionEnabler enabler;
61
62  // Exceptions can also happen when the NSPrintPanel is being
63  // deallocated, so it must be autoreleased within this scope.
64  base::mac::ScopedNSAutoreleasePool pool;
65
66  DCHECK([NSThread isMainThread]);
67
68  // We deliberately don't feed max_pages into the dialog, because setting
69  // NSPrintLastPage makes the print dialog pre-select the option to only print
70  // a range.
71
72  // TODO(stuartmorgan): implement 'print selection only' (probably requires
73  // adding a new custom view to the panel on 10.5; 10.6 has
74  // NSPrintPanelShowsPrintSelection).
75  NSPrintPanel* panel = [NSPrintPanel printPanel];
76  NSPrintInfo* printInfo = print_info_.get();
77
78  NSPrintPanelOptions options = [panel options];
79  options |= NSPrintPanelShowsPaperSize;
80  options |= NSPrintPanelShowsOrientation;
81  options |= NSPrintPanelShowsScaling;
82  [panel setOptions:options];
83
84  // Set the print job title text.
85  if (parent_view) {
86    NSString* job_title = [[parent_view window] title];
87    if (job_title) {
88      PMPrintSettings printSettings =
89          (PMPrintSettings)[printInfo PMPrintSettings];
90      PMPrintSettingsSetJobName(printSettings, (CFStringRef)job_title);
91      [printInfo updateFromPMPrintSettings];
92    }
93  }
94
95  // TODO(stuartmorgan): We really want a tab sheet here, not a modal window.
96  // Will require restructuring the PrintingContext API to use a callback.
97  NSInteger selection = [panel runModalWithPrintInfo:printInfo];
98  if (selection == NSOKButton) {
99    print_info_.reset([[panel printInfo] retain]);
100    InitPrintSettingsFromPrintInfo(GetPageRangesFromPrintInfo());
101    callback.Run(OK);
102  } else {
103    callback.Run(CANCEL);
104  }
105}
106
107PrintingContext::Result PrintingContextMac::UseDefaultSettings() {
108  DCHECK(!in_print_job_);
109
110  print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
111  InitPrintSettingsFromPrintInfo(GetPageRangesFromPrintInfo());
112
113  return OK;
114}
115
116PrintingContext::Result PrintingContextMac::UpdatePrinterSettings(
117    const DictionaryValue& job_settings, const PageRanges& ranges) {
118  DCHECK(!in_print_job_);
119
120  // NOTE: Reset |print_info_| with a copy of |sharedPrintInfo| so as to start
121  // with a clean slate.
122  print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
123
124  bool collate;
125  int color;
126  bool landscape;
127  bool print_to_pdf;
128  bool is_cloud_dialog;
129  int copies;
130  int duplex_mode;
131  std::string device_name;
132
133  if (!job_settings.GetBoolean(kSettingLandscape, &landscape) ||
134      !job_settings.GetBoolean(kSettingCollate, &collate) ||
135      !job_settings.GetInteger(kSettingColor, &color) ||
136      !job_settings.GetBoolean(kSettingPrintToPDF, &print_to_pdf) ||
137      !job_settings.GetInteger(kSettingDuplexMode, &duplex_mode) ||
138      !job_settings.GetInteger(kSettingCopies, &copies) ||
139      !job_settings.GetString(kSettingDeviceName, &device_name) ||
140      !job_settings.GetBoolean(kSettingCloudPrintDialog, &is_cloud_dialog)) {
141    return OnError();
142  }
143
144  bool print_to_cloud = job_settings.HasKey(kSettingCloudPrintId);
145  bool open_pdf_in_preview = job_settings.HasKey(kSettingOpenPDFInPreview);
146
147  if (!print_to_pdf && !print_to_cloud && !is_cloud_dialog) {
148    if (!SetPrinter(device_name))
149      return OnError();
150
151    if (!SetCopiesInPrintSettings(copies))
152      return OnError();
153
154    if (!SetCollateInPrintSettings(collate))
155      return OnError();
156
157    if (!SetDuplexModeInPrintSettings(
158            static_cast<DuplexMode>(duplex_mode))) {
159      return OnError();
160    }
161
162    if (!SetOutputColor(color))
163      return OnError();
164  }
165  if (open_pdf_in_preview) {
166    if (!SetPrintPreviewJob())
167      return OnError();
168  }
169
170  if (!UpdatePageFormatWithPaperInfo())
171    return OnError();
172
173  if (!SetOrientationIsLandscape(landscape))
174    return OnError();
175
176  [print_info_.get() updateFromPMPrintSettings];
177
178  InitPrintSettingsFromPrintInfo(ranges);
179  return OK;
180}
181
182bool PrintingContextMac::SetPrintPreviewJob() {
183  PMPrintSession print_session =
184      static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
185  PMPrintSettings print_settings =
186      static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
187  return PMSessionSetDestination(
188      print_session, print_settings, kPMDestinationPreview,
189      NULL, NULL) == noErr;
190}
191
192void PrintingContextMac::InitPrintSettingsFromPrintInfo(
193    const PageRanges& ranges) {
194  PMPrintSession print_session =
195      static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
196  PMPageFormat page_format =
197      static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
198  PMPrinter printer;
199  PMSessionGetCurrentPrinter(print_session, &printer);
200  PrintSettingsInitializerMac::InitPrintSettings(
201      printer, page_format, ranges, false, &settings_);
202}
203
204bool PrintingContextMac::SetPrinter(const std::string& device_name) {
205  DCHECK(print_info_.get());
206  PMPrintSession print_session =
207      static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
208
209  PMPrinter current_printer;
210  if (PMSessionGetCurrentPrinter(print_session, &current_printer) != noErr)
211    return false;
212
213  CFStringRef current_printer_id = PMPrinterGetID(current_printer);
214  if (!current_printer_id)
215    return false;
216
217  base::mac::ScopedCFTypeRef<CFStringRef> new_printer_id(
218      base::SysUTF8ToCFStringRef(device_name));
219  if (!new_printer_id.get())
220    return false;
221
222  if (CFStringCompare(new_printer_id.get(), current_printer_id, 0) ==
223          kCFCompareEqualTo) {
224    return true;
225  }
226
227  PMPrinter new_printer = PMPrinterCreateFromPrinterID(new_printer_id.get());
228  if (new_printer == NULL)
229    return false;
230
231  OSStatus status = PMSessionSetCurrentPMPrinter(print_session, new_printer);
232  PMRelease(new_printer);
233  return status == noErr;
234}
235
236bool PrintingContextMac::UpdatePageFormatWithPaperInfo() {
237  PMPrintSession print_session =
238      static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
239
240  PMPageFormat default_page_format =
241      static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
242
243  PMPaper default_paper;
244  if (PMGetPageFormatPaper(default_page_format, &default_paper) != noErr)
245    return false;
246
247  double default_page_width = 0.0;
248  double default_page_height = 0.0;
249  if (PMPaperGetWidth(default_paper, &default_page_width) != noErr)
250    return false;
251
252  if (PMPaperGetHeight(default_paper, &default_page_height) != noErr)
253    return false;
254
255  PMPrinter current_printer = NULL;
256  if (PMSessionGetCurrentPrinter(print_session, &current_printer) != noErr)
257    return false;
258
259  if (current_printer == nil)
260    return false;
261
262  CFArrayRef paper_list = NULL;
263  if (PMPrinterGetPaperList(current_printer, &paper_list) != noErr)
264    return false;
265
266  double best_match = std::numeric_limits<double>::max();
267  PMPaper best_matching_paper = kPMNoData;
268  int num_papers = CFArrayGetCount(paper_list);
269  for (int i = 0; i < num_papers; ++i) {
270    PMPaper paper = (PMPaper)[(NSArray*)paper_list objectAtIndex: i];
271    double paper_width = 0.0;
272    double paper_height = 0.0;
273    PMPaperGetWidth(paper, &paper_width);
274    PMPaperGetHeight(paper, &paper_height);
275    double current_match = std::max(fabs(default_page_width - paper_width),
276                                    fabs(default_page_height - paper_height));
277    // Ignore paper sizes that are very different.
278    if (current_match > 2)
279      continue;
280    current_match += IsPaperNameEqual(paper, default_paper) ? 0 : 1;
281    if (current_match < best_match) {
282      best_matching_paper = paper;
283      best_match = current_match;
284    }
285  }
286
287  if (best_matching_paper == kPMNoData) {
288    PMPaper paper = kPMNoData;
289    // Create a custom paper for the specified default page size.
290    PMPaperMargins default_margins;
291    if (PMPaperGetMargins(default_paper, &default_margins) != noErr)
292      return false;
293
294    const PMPaperMargins margins =
295        {default_margins.top, default_margins.left, default_margins.bottom,
296         default_margins.right};
297    CFStringRef paper_id = CFSTR("Custom paper ID");
298    CFStringRef paper_name = CFSTR("Custom paper");
299    if (PMPaperCreateCustom(current_printer, paper_id, paper_name,
300            default_page_width, default_page_height, &margins, &paper) !=
301            noErr) {
302      return false;
303    }
304    [print_info_.get() updateFromPMPageFormat];
305    PMRelease(paper);
306  } else {
307    PMPageFormat chosen_page_format = NULL;
308    if (PMCreatePageFormat((PMPageFormat*) &chosen_page_format) != noErr)
309      return false;
310
311    // Create page format from that paper.
312    if (PMCreatePageFormatWithPMPaper(&chosen_page_format,
313            best_matching_paper) != noErr) {
314      PMRelease(chosen_page_format);
315      return false;
316    }
317    // Copy over the original format with the new page format.
318    if (PMCopyPageFormat(chosen_page_format, default_page_format) != noErr) {
319      PMRelease(chosen_page_format);
320      return false;
321    }
322    [print_info_.get() updateFromPMPageFormat];
323    PMRelease(chosen_page_format);
324  }
325  return true;
326}
327
328bool PrintingContextMac::SetCopiesInPrintSettings(int copies) {
329  if (copies < 1)
330    return false;
331
332  PMPrintSettings pmPrintSettings =
333      static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
334  return PMSetCopies(pmPrintSettings, copies, false) == noErr;
335}
336
337bool PrintingContextMac::SetCollateInPrintSettings(bool collate) {
338  PMPrintSettings pmPrintSettings =
339      static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
340  return PMSetCollate(pmPrintSettings, collate) == noErr;
341}
342
343bool PrintingContextMac::SetOrientationIsLandscape(bool landscape) {
344  PMPageFormat page_format =
345      static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
346
347  PMOrientation orientation = landscape ? kPMLandscape : kPMPortrait;
348
349  if (PMSetOrientation(page_format, orientation, false) != noErr)
350    return false;
351
352  PMPrintSession print_session =
353      static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
354
355  PMSessionValidatePageFormat(print_session, page_format, kPMDontWantBoolean);
356
357  [print_info_.get() updateFromPMPageFormat];
358  return true;
359}
360
361bool PrintingContextMac::SetDuplexModeInPrintSettings(DuplexMode mode) {
362  PMDuplexMode duplexSetting;
363  switch (mode) {
364    case LONG_EDGE:
365      duplexSetting = kPMDuplexNoTumble;
366      break;
367    case SHORT_EDGE:
368      duplexSetting = kPMDuplexTumble;
369      break;
370    case SIMPLEX:
371      duplexSetting = kPMDuplexNone;
372      break;
373    default:  // UNKNOWN_DUPLEX_MODE
374      return true;
375  }
376
377  PMPrintSettings pmPrintSettings =
378      static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
379  return PMSetDuplex(pmPrintSettings, duplexSetting) == noErr;
380}
381
382bool PrintingContextMac::SetOutputColor(int color_mode) {
383  PMPrintSettings pmPrintSettings =
384      static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
385  std::string color_setting_name;
386  std::string color_value;
387  GetColorModelForMode(color_mode, &color_setting_name, &color_value);
388  base::mac::ScopedCFTypeRef<CFStringRef> color_setting(
389      base::SysUTF8ToCFStringRef(color_setting_name));
390  base::mac::ScopedCFTypeRef<CFStringRef> output_color(
391      base::SysUTF8ToCFStringRef(color_value));
392
393  return PMPrintSettingsSetValue(pmPrintSettings,
394                                 color_setting.get(),
395                                 output_color.get(),
396                                 false) == noErr;
397}
398
399PageRanges PrintingContextMac::GetPageRangesFromPrintInfo() {
400  PageRanges page_ranges;
401  NSDictionary* print_info_dict = [print_info_.get() dictionary];
402  if (![[print_info_dict objectForKey:NSPrintAllPages] boolValue]) {
403    PageRange range;
404    range.from = [[print_info_dict objectForKey:NSPrintFirstPage] intValue] - 1;
405    range.to = [[print_info_dict objectForKey:NSPrintLastPage] intValue] - 1;
406    page_ranges.push_back(range);
407  }
408  return page_ranges;
409}
410
411PrintingContext::Result PrintingContextMac::InitWithSettings(
412    const PrintSettings& settings) {
413  DCHECK(!in_print_job_);
414
415  settings_ = settings;
416
417  NOTIMPLEMENTED();
418
419  return FAILED;
420}
421
422PrintingContext::Result PrintingContextMac::NewDocument(
423    const string16& document_name) {
424  DCHECK(!in_print_job_);
425
426  in_print_job_ = true;
427
428  PMPrintSession print_session =
429      static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
430  PMPrintSettings print_settings =
431      static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
432  PMPageFormat page_format =
433      static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
434
435  base::mac::ScopedCFTypeRef<CFStringRef> job_title(
436      base::SysUTF16ToCFStringRef(document_name));
437  PMPrintSettingsSetJobName(print_settings, job_title.get());
438
439  OSStatus status = PMSessionBeginCGDocumentNoDialog(print_session,
440                                                     print_settings,
441                                                     page_format);
442  if (status != noErr)
443    return OnError();
444
445  return OK;
446}
447
448PrintingContext::Result PrintingContextMac::NewPage() {
449  if (abort_printing_)
450    return CANCEL;
451  DCHECK(in_print_job_);
452  DCHECK(!context_);
453
454  PMPrintSession print_session =
455      static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
456  PMPageFormat page_format =
457      static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
458  OSStatus status;
459  status = PMSessionBeginPageNoDialog(print_session, page_format, NULL);
460  if (status != noErr)
461    return OnError();
462  status = PMSessionGetCGGraphicsContext(print_session, &context_);
463  if (status != noErr)
464    return OnError();
465
466  return OK;
467}
468
469PrintingContext::Result PrintingContextMac::PageDone() {
470  if (abort_printing_)
471    return CANCEL;
472  DCHECK(in_print_job_);
473  DCHECK(context_);
474
475  PMPrintSession print_session =
476      static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
477  OSStatus status = PMSessionEndPageNoDialog(print_session);
478  if (status != noErr)
479    OnError();
480  context_ = NULL;
481
482  return OK;
483}
484
485PrintingContext::Result PrintingContextMac::DocumentDone() {
486  if (abort_printing_)
487    return CANCEL;
488  DCHECK(in_print_job_);
489
490  PMPrintSession print_session =
491      static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
492  OSStatus status = PMSessionEndDocumentNoDialog(print_session);
493  if (status != noErr)
494    OnError();
495
496  ResetSettings();
497  return OK;
498}
499
500void PrintingContextMac::Cancel() {
501  abort_printing_ = true;
502  in_print_job_ = false;
503  context_ = NULL;
504
505  PMPrintSession print_session =
506      static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
507  PMSessionEndPageNoDialog(print_session);
508}
509
510void PrintingContextMac::ReleaseContext() {
511  print_info_.reset();
512  context_ = NULL;
513}
514
515gfx::NativeDrawingContext PrintingContextMac::context() const {
516  return context_;
517}
518
519}  // namespace printing
520