Atomic: Use Async Commit
[chromium-blink-merge.git] / printing / printing_context_mac.mm
blob36badc37d55504fbe13b7c283903007db366a195
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 <AppKit/AppKit.h>
9 #import <iomanip>
10 #import <numeric>
12 #include "base/logging.h"
13 #include "base/mac/scoped_cftyperef.h"
14 #include "base/mac/scoped_nsautorelease_pool.h"
15 #include "base/strings/sys_string_conversions.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/values.h"
18 #include "printing/print_settings_initializer_mac.h"
19 #include "printing/units.h"
21 namespace printing {
23 namespace {
25 const int kMaxPaperSizeDiffereceInPoints = 2;
27 // Return true if PPD name of paper is equal.
28 bool IsPaperNameEqual(CFStringRef name1, const PMPaper& paper2) {
29   CFStringRef name2 = NULL;
30   return (name1 && PMPaperGetPPDPaperName(paper2, &name2) == noErr) &&
31          (CFStringCompare(name1, name2, kCFCompareCaseInsensitive) ==
32           kCFCompareEqualTo);
35 PMPaper MatchPaper(CFArrayRef paper_list,
36                    CFStringRef name,
37                    double width,
38                    double height) {
39   double best_match = std::numeric_limits<double>::max();
40   PMPaper best_matching_paper = NULL;
41   int num_papers = CFArrayGetCount(paper_list);
42   for (int i = 0; i < num_papers; ++i) {
43     PMPaper paper = (PMPaper)[(NSArray*)paper_list objectAtIndex : i];
44     double paper_width = 0.0;
45     double paper_height = 0.0;
46     PMPaperGetWidth(paper, &paper_width);
47     PMPaperGetHeight(paper, &paper_height);
48     double difference =
49         std::max(fabs(width - paper_width), fabs(height - paper_height));
51     // Ignore papers with size too different from expected.
52     if (difference > kMaxPaperSizeDiffereceInPoints)
53       continue;
55     if (name && IsPaperNameEqual(name, paper))
56       return paper;
58     if (difference < best_match) {
59       best_matching_paper = paper;
60       best_match = difference;
61     }
62   }
63   return best_matching_paper;
66 }  // namespace
68 // static
69 scoped_ptr<PrintingContext> PrintingContext::Create(Delegate* delegate) {
70   return make_scoped_ptr<PrintingContext>(new PrintingContextMac(delegate));
73 PrintingContextMac::PrintingContextMac(Delegate* delegate)
74     : PrintingContext(delegate),
75       print_info_([[NSPrintInfo sharedPrintInfo] copy]),
76       context_(NULL) {
79 PrintingContextMac::~PrintingContextMac() {
80   ReleaseContext();
83 void PrintingContextMac::AskUserForSettings(
84     int max_pages,
85     bool has_selection,
86     bool is_scripted,
87     const PrintSettingsCallback& callback) {
88   // Exceptions can also happen when the NSPrintPanel is being
89   // deallocated, so it must be autoreleased within this scope.
90   base::mac::ScopedNSAutoreleasePool pool;
92   DCHECK([NSThread isMainThread]);
94   // We deliberately don't feed max_pages into the dialog, because setting
95   // NSPrintLastPage makes the print dialog pre-select the option to only print
96   // a range.
98   // TODO(stuartmorgan): implement 'print selection only' (probably requires
99   // adding a new custom view to the panel on 10.5; 10.6 has
100   // NSPrintPanelShowsPrintSelection).
101   NSPrintPanel* panel = [NSPrintPanel printPanel];
102   NSPrintInfo* printInfo = print_info_.get();
104   NSPrintPanelOptions options = [panel options];
105   options |= NSPrintPanelShowsPaperSize;
106   options |= NSPrintPanelShowsOrientation;
107   options |= NSPrintPanelShowsScaling;
108   [panel setOptions:options];
110   // Set the print job title text.
111   gfx::NativeView parent_view = delegate_->GetParentView();
112   if (parent_view) {
113     NSString* job_title = [[parent_view window] title];
114     if (job_title) {
115       PMPrintSettings printSettings =
116           (PMPrintSettings)[printInfo PMPrintSettings];
117       PMPrintSettingsSetJobName(printSettings, (CFStringRef)job_title);
118       [printInfo updateFromPMPrintSettings];
119     }
120   }
122   // TODO(stuartmorgan): We really want a tab sheet here, not a modal window.
123   // Will require restructuring the PrintingContext API to use a callback.
124   NSInteger selection = [panel runModalWithPrintInfo:printInfo];
125   if (selection == NSOKButton) {
126     print_info_.reset([[panel printInfo] retain]);
127     settings_.set_ranges(GetPageRangesFromPrintInfo());
128     InitPrintSettingsFromPrintInfo();
129     callback.Run(OK);
130   } else {
131     callback.Run(CANCEL);
132   }
135 gfx::Size PrintingContextMac::GetPdfPaperSizeDeviceUnits() {
136   // NOTE: Reset |print_info_| with a copy of |sharedPrintInfo| so as to start
137   // with a clean slate.
138   print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
139   UpdatePageFormatWithPaperInfo();
141   PMPageFormat page_format =
142       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
143   PMRect paper_rect;
144   PMGetAdjustedPaperRect(page_format, &paper_rect);
146   // Device units are in points. Units per inch is 72.
147   gfx::Size physical_size_device_units(
148       (paper_rect.right - paper_rect.left),
149       (paper_rect.bottom - paper_rect.top));
150   DCHECK(settings_.device_units_per_inch() == kPointsPerInch);
151   return physical_size_device_units;
154 PrintingContext::Result PrintingContextMac::UseDefaultSettings() {
155   DCHECK(!in_print_job_);
157   print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
158   settings_.set_ranges(GetPageRangesFromPrintInfo());
159   InitPrintSettingsFromPrintInfo();
161   return OK;
164 PrintingContext::Result PrintingContextMac::UpdatePrinterSettings(
165     bool external_preview,
166     bool show_system_dialog,
167     int page_count) {
168   DCHECK(!show_system_dialog);
169   DCHECK(!in_print_job_);
171   // NOTE: Reset |print_info_| with a copy of |sharedPrintInfo| so as to start
172   // with a clean slate.
173   print_info_.reset([[NSPrintInfo sharedPrintInfo] copy]);
175   if (external_preview) {
176     if (!SetPrintPreviewJob())
177       return OnError();
178   } else {
179     // Don't need this for preview.
180     if (!SetPrinter(base::UTF16ToUTF8(settings_.device_name())) ||
181         !SetCopiesInPrintSettings(settings_.copies()) ||
182         !SetCollateInPrintSettings(settings_.collate()) ||
183         !SetDuplexModeInPrintSettings(settings_.duplex_mode()) ||
184         !SetOutputColor(settings_.color())) {
185       return OnError();
186     }
187   }
189   if (!UpdatePageFormatWithPaperInfo() ||
190       !SetOrientationIsLandscape(settings_.landscape())) {
191     return OnError();
192   }
194   [print_info_.get() updateFromPMPrintSettings];
196   InitPrintSettingsFromPrintInfo();
197   return OK;
200 bool PrintingContextMac::SetPrintPreviewJob() {
201   PMPrintSession print_session =
202       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
203   PMPrintSettings print_settings =
204       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
205   return PMSessionSetDestination(
206       print_session, print_settings, kPMDestinationPreview,
207       NULL, NULL) == noErr;
210 void PrintingContextMac::InitPrintSettingsFromPrintInfo() {
211   PMPrintSession print_session =
212       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
213   PMPageFormat page_format =
214       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
215   PMPrinter printer;
216   PMSessionGetCurrentPrinter(print_session, &printer);
217   PrintSettingsInitializerMac::InitPrintSettings(
218       printer, page_format, &settings_);
221 bool PrintingContextMac::SetPrinter(const std::string& device_name) {
222   DCHECK(print_info_.get());
223   PMPrintSession print_session =
224       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
226   PMPrinter current_printer;
227   if (PMSessionGetCurrentPrinter(print_session, &current_printer) != noErr)
228     return false;
230   CFStringRef current_printer_id = PMPrinterGetID(current_printer);
231   if (!current_printer_id)
232     return false;
234   base::ScopedCFTypeRef<CFStringRef> new_printer_id(
235       base::SysUTF8ToCFStringRef(device_name));
236   if (!new_printer_id.get())
237     return false;
239   if (CFStringCompare(new_printer_id.get(), current_printer_id, 0) ==
240           kCFCompareEqualTo) {
241     return true;
242   }
244   PMPrinter new_printer = PMPrinterCreateFromPrinterID(new_printer_id.get());
245   if (!new_printer)
246     return false;
248   OSStatus status = PMSessionSetCurrentPMPrinter(print_session, new_printer);
249   PMRelease(new_printer);
250   return status == noErr;
253 bool PrintingContextMac::UpdatePageFormatWithPaperInfo() {
254   PMPrintSession print_session =
255       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
257   PMPageFormat default_page_format =
258       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
260   PMPrinter current_printer = NULL;
261   if (PMSessionGetCurrentPrinter(print_session, &current_printer) != noErr)
262     return false;
264   double page_width = 0.0;
265   double page_height = 0.0;
266   base::ScopedCFTypeRef<CFStringRef> paper_name;
267   PMPaperMargins margins = {0};
269   const PrintSettings::RequestedMedia& media = settings_.requested_media();
270   if (media.IsDefault()) {
271     PMPaper default_paper;
272     if (PMGetPageFormatPaper(default_page_format, &default_paper) != noErr ||
273         PMPaperGetWidth(default_paper, &page_width) != noErr ||
274         PMPaperGetHeight(default_paper, &page_height) != noErr) {
275       return false;
276     }
278     // Ignore result, because we can continue without following.
279     CFStringRef tmp_paper_name = NULL;
280     PMPaperGetPPDPaperName(default_paper, &tmp_paper_name);
281     PMPaperGetMargins(default_paper, &margins);
282     paper_name.reset(tmp_paper_name, base::scoped_policy::RETAIN);
283   } else {
284     const double kMutiplier = kPointsPerInch / (10.0f * kHundrethsMMPerInch);
285     page_width = media.size_microns.width() * kMutiplier;
286     page_height = media.size_microns.height() * kMutiplier;
287     paper_name.reset(base::SysUTF8ToCFStringRef(media.vendor_id));
288   }
290   CFArrayRef paper_list = NULL;
291   if (PMPrinterGetPaperList(current_printer, &paper_list) != noErr)
292     return false;
294   PMPaper best_matching_paper =
295       MatchPaper(paper_list, paper_name, page_width, page_height);
297   if (best_matching_paper)
298     return UpdatePageFormatWithPaper(best_matching_paper, default_page_format);
300   // Do nothing if unmatched paper was default system paper.
301   if (media.IsDefault())
302     return true;
304   PMPaper paper = NULL;
305   if (PMPaperCreateCustom(current_printer,
306                           CFSTR("Custom paper ID"),
307                           CFSTR("Custom paper"),
308                           page_width,
309                           page_height,
310                           &margins,
311                           &paper) != noErr) {
312     return false;
313   }
314   bool result = UpdatePageFormatWithPaper(paper, default_page_format);
315   PMRelease(paper);
316   return result;
319 bool PrintingContextMac::UpdatePageFormatWithPaper(PMPaper paper,
320                                                    PMPageFormat page_format) {
321   PMPageFormat new_format = NULL;
322   if (PMCreatePageFormatWithPMPaper(&new_format, paper) != noErr)
323     return false;
324   // Copy over the original format with the new page format.
325   bool result = (PMCopyPageFormat(new_format, page_format) == noErr);
326   [print_info_.get() updateFromPMPageFormat];
327   PMRelease(new_format);
328   return result;
331 bool PrintingContextMac::SetCopiesInPrintSettings(int copies) {
332   if (copies < 1)
333     return false;
335   PMPrintSettings pmPrintSettings =
336       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
337   return PMSetCopies(pmPrintSettings, copies, false) == noErr;
340 bool PrintingContextMac::SetCollateInPrintSettings(bool collate) {
341   PMPrintSettings pmPrintSettings =
342       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
343   return PMSetCollate(pmPrintSettings, collate) == noErr;
346 bool PrintingContextMac::SetOrientationIsLandscape(bool landscape) {
347   PMPageFormat page_format =
348       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
350   PMOrientation orientation = landscape ? kPMLandscape : kPMPortrait;
352   if (PMSetOrientation(page_format, orientation, false) != noErr)
353     return false;
355   PMPrintSession print_session =
356       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
358   PMSessionValidatePageFormat(print_session, page_format, kPMDontWantBoolean);
360   [print_info_.get() updateFromPMPageFormat];
361   return true;
364 bool PrintingContextMac::SetDuplexModeInPrintSettings(DuplexMode mode) {
365   PMDuplexMode duplexSetting;
366   switch (mode) {
367     case LONG_EDGE:
368       duplexSetting = kPMDuplexNoTumble;
369       break;
370     case SHORT_EDGE:
371       duplexSetting = kPMDuplexTumble;
372       break;
373     case SIMPLEX:
374       duplexSetting = kPMDuplexNone;
375       break;
376     default:  // UNKNOWN_DUPLEX_MODE
377       return true;
378   }
380   PMPrintSettings pmPrintSettings =
381       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
382   return PMSetDuplex(pmPrintSettings, duplexSetting) == noErr;
385 bool PrintingContextMac::SetOutputColor(int color_mode) {
386   PMPrintSettings pmPrintSettings =
387       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
388   std::string color_setting_name;
389   std::string color_value;
390   GetColorModelForMode(color_mode, &color_setting_name, &color_value);
391   base::ScopedCFTypeRef<CFStringRef> color_setting(
392       base::SysUTF8ToCFStringRef(color_setting_name));
393   base::ScopedCFTypeRef<CFStringRef> output_color(
394       base::SysUTF8ToCFStringRef(color_value));
396   return PMPrintSettingsSetValue(pmPrintSettings,
397                                  color_setting.get(),
398                                  output_color.get(),
399                                  false) == noErr;
402 PageRanges PrintingContextMac::GetPageRangesFromPrintInfo() {
403   PageRanges page_ranges;
404   NSDictionary* print_info_dict = [print_info_.get() dictionary];
405   if (![[print_info_dict objectForKey:NSPrintAllPages] boolValue]) {
406     PageRange range;
407     range.from = [[print_info_dict objectForKey:NSPrintFirstPage] intValue] - 1;
408     range.to = [[print_info_dict objectForKey:NSPrintLastPage] intValue] - 1;
409     page_ranges.push_back(range);
410   }
411   return page_ranges;
414 PrintingContext::Result PrintingContextMac::InitWithSettings(
415     const PrintSettings& settings) {
416   DCHECK(!in_print_job_);
418   settings_ = settings;
420   NOTIMPLEMENTED();
422   return FAILED;
425 PrintingContext::Result PrintingContextMac::NewDocument(
426     const base::string16& document_name) {
427   DCHECK(!in_print_job_);
429   in_print_job_ = true;
431   PMPrintSession print_session =
432       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
433   PMPrintSettings print_settings =
434       static_cast<PMPrintSettings>([print_info_.get() PMPrintSettings]);
435   PMPageFormat page_format =
436       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
438   base::ScopedCFTypeRef<CFStringRef> job_title(
439       base::SysUTF16ToCFStringRef(document_name));
440   PMPrintSettingsSetJobName(print_settings, job_title.get());
442   OSStatus status = PMSessionBeginCGDocumentNoDialog(print_session,
443                                                      print_settings,
444                                                      page_format);
445   if (status != noErr)
446     return OnError();
448   return OK;
451 PrintingContext::Result PrintingContextMac::NewPage() {
452   if (abort_printing_)
453     return CANCEL;
454   DCHECK(in_print_job_);
455   DCHECK(!context_);
457   PMPrintSession print_session =
458       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
459   PMPageFormat page_format =
460       static_cast<PMPageFormat>([print_info_.get() PMPageFormat]);
461   OSStatus status;
462   status = PMSessionBeginPageNoDialog(print_session, page_format, NULL);
463   if (status != noErr)
464     return OnError();
465   status = PMSessionGetCGGraphicsContext(print_session, &context_);
466   if (status != noErr)
467     return OnError();
469   return OK;
472 PrintingContext::Result PrintingContextMac::PageDone() {
473   if (abort_printing_)
474     return CANCEL;
475   DCHECK(in_print_job_);
476   DCHECK(context_);
478   PMPrintSession print_session =
479       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
480   OSStatus status = PMSessionEndPageNoDialog(print_session);
481   if (status != noErr)
482     OnError();
483   context_ = NULL;
485   return OK;
488 PrintingContext::Result PrintingContextMac::DocumentDone() {
489   if (abort_printing_)
490     return CANCEL;
491   DCHECK(in_print_job_);
493   PMPrintSession print_session =
494       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
495   OSStatus status = PMSessionEndDocumentNoDialog(print_session);
496   if (status != noErr)
497     OnError();
499   ResetSettings();
500   return OK;
503 void PrintingContextMac::Cancel() {
504   abort_printing_ = true;
505   in_print_job_ = false;
506   context_ = NULL;
508   PMPrintSession print_session =
509       static_cast<PMPrintSession>([print_info_.get() PMPrintSession]);
510   PMSessionEndPageNoDialog(print_session);
513 void PrintingContextMac::ReleaseContext() {
514   print_info_.reset();
515   context_ = NULL;
518 gfx::NativeDrawingContext PrintingContextMac::context() const {
519   return context_;
522 }  // namespace printing