blob: 4d83c13071889becbcdb453756e5c0985528e530 [file] [log] [blame]
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001// 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"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010017#include "base/strings/sys_string_conversions.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000018#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
Ben Murdocheb525c52013-07-10 11:40:50 +0100217 base::ScopedCFTypeRef<CFStringRef> new_printer_id(
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000218 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);
Ben Murdocheb525c52013-07-10 11:40:50 +0100388 base::ScopedCFTypeRef<CFStringRef> color_setting(
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000389 base::SysUTF8ToCFStringRef(color_setting_name));
Ben Murdocheb525c52013-07-10 11:40:50 +0100390 base::ScopedCFTypeRef<CFStringRef> output_color(
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000391 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
Ben Murdocheb525c52013-07-10 11:40:50 +0100435 base::ScopedCFTypeRef<CFStringRef> job_title(
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000436 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