Android: Work around driver bug reporting GL errors.
[chromium-blink-merge.git] / printing / printing_context_mac.mm
blob8936f29f2e0c9be8c469f1dfe52a947640406ad0
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.
5 #include "printing/printing_context_mac.h"
7 #import <ApplicationServices/ApplicationServices.h>
8 #import <AppKit/AppKit.h>
10 #import <iomanip>
11 #import <numeric>
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"
21 namespace printing {
23 namespace {
25 // Return true if PPD name of paper is equal.
26 bool 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);
35 }  // namespace
37 // static
38 PrintingContext* PrintingContext::Create(const std::string& app_locale) {
39   return static_cast<PrintingContext*>(new PrintingContextMac(app_locale));
42 PrintingContextMac::PrintingContextMac(const std::string& app_locale)
43     : PrintingContext(app_locale),
44       print_info_([[NSPrintInfo sharedPrintInfo] copy]),
45       context_(NULL) {
48 PrintingContextMac::~PrintingContextMac() {
49   ReleaseContext();
52 void 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;
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;
66   DCHECK([NSThread isMainThread]);
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.
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();
78   NSPrintPanelOptions options = [panel options];
79   options |= NSPrintPanelShowsPaperSize;
80   options |= NSPrintPanelShowsOrientation;
81   options |= NSPrintPanelShowsScaling;
82   [panel setOptions:options];
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   }
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   }
107 PrintingContext::Result PrintingContextMac::UseDefaultSettings() {
108   DCHECK(!in_print_job_);
110   print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
111   InitPrintSettingsFromPrintInfo(GetPageRangesFromPrintInfo());
113   return OK;
116 PrintingContext::Result PrintingContextMac::UpdatePrinterSettings(
117     const DictionaryValue& job_settings, const PageRanges& ranges) {
118   DCHECK(!in_print_job_);
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]);
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;
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   }
144   bool print_to_cloud = job_settings.HasKey(kSettingCloudPrintId);
145   bool open_pdf_in_preview = job_settings.HasKey(kSettingOpenPDFInPreview);
147   if (!print_to_pdf && !print_to_cloud && !is_cloud_dialog) {
148     if (!SetPrinter(device_name))
149       return OnError();
151     if (!SetCopiesInPrintSettings(copies))
152       return OnError();
154     if (!SetCollateInPrintSettings(collate))
155       return OnError();
157     if (!SetDuplexModeInPrintSettings(
158             static_cast<DuplexMode>(duplex_mode))) {
159       return OnError();
160     }
162     if (!SetOutputColor(color))
163       return OnError();
164   }
165   if (open_pdf_in_preview) {
166     if (!SetPrintPreviewJob())
167       return OnError();
168   }
170   if (!UpdatePageFormatWithPaperInfo())
171     return OnError();
173   if (!SetOrientationIsLandscape(landscape))
174     return OnError();
176   [print_info_.get() updateFromPMPrintSettings];
178   InitPrintSettingsFromPrintInfo(ranges);
179   return OK;
182 bool 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;
192 void 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_);
204 bool PrintingContextMac::SetPrinter(const std::string& device_name) {
205   DCHECK(print_info_.get());
206   PMPrintSession print_session =
207       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
209   PMPrinter current_printer;
210   if (PMSessionGetCurrentPrinter(print_session, &current_printer) != noErr)
211     return false;
213   CFStringRef current_printer_id = PMPrinterGetID(current_printer);
214   if (!current_printer_id)
215     return false;
217   base::mac::ScopedCFTypeRef<CFStringRef> new_printer_id(
218       base::SysUTF8ToCFStringRef(device_name));
219   if (!new_printer_id.get())
220     return false;
222   if (CFStringCompare(new_printer_id.get(), current_printer_id, 0) ==
223           kCFCompareEqualTo) {
224     return true;
225   }
227   PMPrinter new_printer = PMPrinterCreateFromPrinterID(new_printer_id.get());
228   if (new_printer == NULL)
229     return false;
231   OSStatus status = PMSessionSetCurrentPMPrinter(print_session, new_printer);
232   PMRelease(new_printer);
233   return status == noErr;
236 bool PrintingContextMac::UpdatePageFormatWithPaperInfo() {
237   PMPrintSession print_session =
238       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
240   PMPageFormat default_page_format =
241       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
243   PMPaper default_paper;
244   if (PMGetPageFormatPaper(default_page_format, &default_paper) != noErr)
245     return false;
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;
252   if (PMPaperGetHeight(default_paper, &default_page_height) != noErr)
253     return false;
255   PMPrinter current_printer = NULL;
256   if (PMSessionGetCurrentPrinter(print_session, &current_printer) != noErr)
257     return false;
259   if (current_printer == nil)
260     return false;
262   CFArrayRef paper_list = NULL;
263   if (PMPrinterGetPaperList(current_printer, &paper_list) != noErr)
264     return false;
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   }
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;
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;
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;
328 bool PrintingContextMac::SetCopiesInPrintSettings(int copies) {
329   if (copies < 1)
330     return false;
332   PMPrintSettings pmPrintSettings =
333       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
334   return PMSetCopies(pmPrintSettings, copies, false) == noErr;
337 bool PrintingContextMac::SetCollateInPrintSettings(bool collate) {
338   PMPrintSettings pmPrintSettings =
339       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
340   return PMSetCollate(pmPrintSettings, collate) == noErr;
343 bool PrintingContextMac::SetOrientationIsLandscape(bool landscape) {
344   PMPageFormat page_format =
345       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
347   PMOrientation orientation = landscape ? kPMLandscape : kPMPortrait;
349   if (PMSetOrientation(page_format, orientation, false) != noErr)
350     return false;
352   PMPrintSession print_session =
353       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
355   PMSessionValidatePageFormat(print_session, page_format, kPMDontWantBoolean);
357   [print_info_.get() updateFromPMPageFormat];
358   return true;
361 bool 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   }
377   PMPrintSettings pmPrintSettings =
378       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
379   return PMSetDuplex(pmPrintSettings, duplexSetting) == noErr;
382 bool 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));
393   return PMPrintSettingsSetValue(pmPrintSettings,
394                                  color_setting.get(),
395                                  output_color.get(),
396                                  false) == noErr;
399 PageRanges 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;
411 PrintingContext::Result PrintingContextMac::InitWithSettings(
412     const PrintSettings& settings) {
413   DCHECK(!in_print_job_);
415   settings_ = settings;
417   NOTIMPLEMENTED();
419   return FAILED;
422 PrintingContext::Result PrintingContextMac::NewDocument(
423     const string16& document_name) {
424   DCHECK(!in_print_job_);
426   in_print_job_ = true;
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]);
435   base::mac::ScopedCFTypeRef<CFStringRef> job_title(
436       base::SysUTF16ToCFStringRef(document_name));
437   PMPrintSettingsSetJobName(print_settings, job_title.get());
439   OSStatus status = PMSessionBeginCGDocumentNoDialog(print_session,
440                                                      print_settings,
441                                                      page_format);
442   if (status != noErr)
443     return OnError();
445   return OK;
448 PrintingContext::Result PrintingContextMac::NewPage() {
449   if (abort_printing_)
450     return CANCEL;
451   DCHECK(in_print_job_);
452   DCHECK(!context_);
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();
466   return OK;
469 PrintingContext::Result PrintingContextMac::PageDone() {
470   if (abort_printing_)
471     return CANCEL;
472   DCHECK(in_print_job_);
473   DCHECK(context_);
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;
482   return OK;
485 PrintingContext::Result PrintingContextMac::DocumentDone() {
486   if (abort_printing_)
487     return CANCEL;
488   DCHECK(in_print_job_);
490   PMPrintSession print_session =
491       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
492   OSStatus status = PMSessionEndDocumentNoDialog(print_session);
493   if (status != noErr)
494     OnError();
496   ResetSettings();
497   return OK;
500 void PrintingContextMac::Cancel() {
501   abort_printing_ = true;
502   in_print_job_ = false;
503   context_ = NULL;
505   PMPrintSession print_session =
506       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
507   PMSessionEndPageNoDialog(print_session);
510 void PrintingContextMac::ReleaseContext() {
511   print_info_.reset();
512   context_ = NULL;
515 gfx::NativeDrawingContext PrintingContextMac::context() const {
516   return context_;
519 }  // namespace printing