1/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2/* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6#include "mozilla/ArrayUtils.h"
7#include "mozilla/gfx/PrintTargetCG.h"
8#include "mozilla/Preferences.h"
9
10#include "nsPrintDialogX.h"
11#include "nsIPrintSettings.h"
12#include "nsIPrintSettingsService.h"
13#include "nsPrintSettingsX.h"
14#include "nsCOMPtr.h"
15#include "nsQueryObject.h"
16#include "nsServiceManagerUtils.h"
17#include "nsIStringBundle.h"
18#include "nsCRT.h"
19
20#import <Cocoa/Cocoa.h>
21#include "nsObjCExceptions.h"
22
23using namespace mozilla;
24using mozilla::gfx::PrintTarget;
25
26NS_IMPL_ISUPPORTS(nsPrintDialogServiceX, nsIPrintDialogService)
27
28nsPrintDialogServiceX::nsPrintDialogServiceX() {}
29
30nsPrintDialogServiceX::~nsPrintDialogServiceX() {}
31
32NS_IMETHODIMP
33nsPrintDialogServiceX::Init() { return NS_OK; }
34
35NS_IMETHODIMP
36nsPrintDialogServiceX::Show(nsPIDOMWindowOuter* aParent, nsIPrintSettings* aSettings) {
37  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
38
39  MOZ_ASSERT(aSettings, "aSettings must not be null");
40
41  RefPtr<nsPrintSettingsX> settingsX(do_QueryObject(aSettings));
42  if (!settingsX) {
43    return NS_ERROR_FAILURE;
44  }
45
46  nsCOMPtr<nsIPrintSettingsService> printSettingsSvc =
47      do_GetService("@mozilla.org/gfx/printsettings-service;1");
48
49  // Read the saved printer settings from prefs. (This relies on the printer name
50  // stored in settingsX to read the printer-specific prefs.)
51  printSettingsSvc->InitPrintSettingsFromPrefs(settingsX, true, nsIPrintSettings::kInitSaveAll);
52
53  NSPrintInfo* printInfo = settingsX->CreateOrCopyPrintInfo(/* aWithScaling = */ true);
54  if (NS_WARN_IF(!printInfo)) {
55    return NS_ERROR_FAILURE;
56  }
57  [printInfo autorelease];
58
59  // Set the print job title
60  nsAutoString docName;
61  nsresult rv = aSettings->GetTitle(docName);
62  if (NS_SUCCEEDED(rv)) {
63    nsAutoString adjustedTitle;
64    PrintTarget::AdjustPrintJobNameForIPP(docName, adjustedTitle);
65    CFStringRef cfTitleString = CFStringCreateWithCharacters(
66        NULL, reinterpret_cast<const UniChar*>(adjustedTitle.BeginReading()),
67        adjustedTitle.Length());
68    if (cfTitleString) {
69      auto pmPrintSettings = static_cast<PMPrintSettings>([printInfo PMPrintSettings]);
70      ::PMPrintSettingsSetJobName(pmPrintSettings, cfTitleString);
71      [printInfo updateFromPMPrintSettings];
72      CFRelease(cfTitleString);
73    }
74  }
75
76  // Put the print info into the current print operation, since that's where
77  // [panel runModal] will look for it. We create the view because otherwise
78  // we'll get unrelated warnings printed to the console.
79  NSView* tmpView = [[NSView alloc] init];
80  NSPrintOperation* printOperation = [NSPrintOperation printOperationWithView:tmpView
81                                                                    printInfo:printInfo];
82  [NSPrintOperation setCurrentOperation:printOperation];
83
84  NSPrintPanel* panel = [NSPrintPanel printPanel];
85  [panel setOptions:NSPrintPanelShowsCopies | NSPrintPanelShowsPageRange |
86                    NSPrintPanelShowsPaperSize | NSPrintPanelShowsOrientation |
87                    NSPrintPanelShowsScaling];
88  PrintPanelAccessoryController* viewController =
89      [[PrintPanelAccessoryController alloc] initWithSettings:aSettings];
90  [panel addAccessoryController:viewController];
91  [viewController release];
92
93  // Show the dialog.
94  nsCocoaUtils::PrepareForNativeAppModalDialog();
95  int button = [panel runModal];
96  nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
97
98  // Retrieve a printInfo with the updated settings. (The NSPrintOperation operates on a
99  // copy, so the object we passed in will not have been modified.)
100  NSPrintInfo* result = [[NSPrintOperation currentOperation] printInfo];
101  if (!result) {
102    return NS_ERROR_FAILURE;
103  }
104
105  [NSPrintOperation setCurrentOperation:nil];
106  [tmpView release];
107
108  if (button != NSFileHandlingPanelOKButton) {
109    return NS_ERROR_ABORT;
110  }
111
112  // Export settings.
113  [viewController exportSettings];
114
115  // Update our settings object based on the user's choices in the dialog.
116  // We tell settingsX to adopt this printInfo so that it will be used to run print job,
117  // so that any printer-specific custom settings from print dialog extension panels
118  // will be carried through.
119  settingsX->SetFromPrintInfo(result, /* aAdoptPrintInfo = */ true);
120
121  // Save settings unless saving is pref'd off
122  if (Preferences::GetBool("print.save_print_settings", false)) {
123    printSettingsSvc->SavePrintSettingsToPrefs(settingsX, true,
124                                               nsIPrintSettings::kInitSaveNativeData);
125  }
126
127  return NS_OK;
128
129  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
130}
131
132NS_IMETHODIMP
133nsPrintDialogServiceX::ShowPageSetup(nsPIDOMWindowOuter* aParent, nsIPrintSettings* aNSSettings) {
134  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
135
136  MOZ_ASSERT(aParent, "aParent must not be null");
137  MOZ_ASSERT(aNSSettings, "aSettings must not be null");
138  NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE);
139
140  RefPtr<nsPrintSettingsX> settingsX(do_QueryObject(aNSSettings));
141  if (!settingsX) {
142    return NS_ERROR_FAILURE;
143  }
144
145  NSPrintInfo* printInfo = settingsX->CreateOrCopyPrintInfo(/* aWithScaling = */ true);
146  if (NS_WARN_IF(!printInfo)) {
147    return NS_ERROR_FAILURE;
148  }
149  [printInfo autorelease];
150
151  NSPageLayout* pageLayout = [NSPageLayout pageLayout];
152  nsCocoaUtils::PrepareForNativeAppModalDialog();
153  int button = [pageLayout runModalWithPrintInfo:printInfo];
154  nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
155
156  if (button == NSFileHandlingPanelOKButton) {
157    // The Page Setup dialog does not include non-standard settings that need to be preserved,
158    // separate from what the base printSettings object handles, so we do not need it to adopt
159    // the printInfo object here.
160    settingsX->SetFromPrintInfo(printInfo, /* aAdoptPrintInfo = */ false);
161    nsCOMPtr<nsIPrintSettingsService> printSettingsService =
162        do_GetService("@mozilla.org/gfx/printsettings-service;1");
163    if (printSettingsService && Preferences::GetBool("print.save_print_settings", false)) {
164      uint32_t flags = nsIPrintSettings::kInitSaveNativeData |
165                       nsIPrintSettings::kInitSavePaperSize |
166                       nsIPrintSettings::kInitSaveOrientation | nsIPrintSettings::kInitSaveScaling;
167      printSettingsService->SavePrintSettingsToPrefs(aNSSettings, true, flags);
168    }
169    return NS_OK;
170  }
171  return NS_ERROR_ABORT;
172
173  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
174}
175
176// Accessory view
177
178@interface PrintPanelAccessoryView (Private)
179
180- (NSString*)localizedString:(const char*)aKey;
181
182- (const char*)headerFooterStringForList:(NSPopUpButton*)aList;
183
184- (void)exportHeaderFooterSettings;
185
186- (void)initBundle;
187
188- (NSTextField*)label:(const char*)aLabel
189            withFrame:(NSRect)aRect
190            alignment:(NSTextAlignment)aAlignment;
191
192- (void)addLabel:(const char*)aLabel withFrame:(NSRect)aRect alignment:(NSTextAlignment)aAlignment;
193
194- (void)addLabel:(const char*)aLabel withFrame:(NSRect)aRect;
195
196- (void)addCenteredLabel:(const char*)aLabel withFrame:(NSRect)aRect;
197
198- (NSButton*)checkboxWithLabel:(const char*)aLabel andFrame:(NSRect)aRect;
199
200- (NSPopUpButton*)headerFooterItemListWithFrame:(NSRect)aRect
201                                   selectedItem:(const nsAString&)aCurrentString;
202
203- (void)addOptionsSection;
204
205- (void)addAppearanceSection;
206
207- (void)addHeaderFooterSection;
208
209- (NSString*)summaryValueForCheckbox:(NSButton*)aCheckbox;
210
211- (NSString*)headerSummaryValue;
212
213- (NSString*)footerSummaryValue;
214
215@end
216
217static const char sHeaderFooterTags[][4] = {"", "&T", "&U", "&D", "&P", "&PT"};
218
219@implementation PrintPanelAccessoryView
220
221// Public methods
222
223- (id)initWithSettings:(nsIPrintSettings*)aSettings {
224  [super initWithFrame:NSMakeRect(0, 0, 540, 185)];
225
226  mSettings = aSettings;
227  [self initBundle];
228  [self addOptionsSection];
229  [self addAppearanceSection];
230  [self addHeaderFooterSection];
231
232  return self;
233}
234
235- (void)exportSettings {
236  mSettings->SetPrintSelectionOnly([mPrintSelectionOnlyCheckbox state] == NSOnState);
237  mSettings->SetShrinkToFit([mShrinkToFitCheckbox state] == NSOnState);
238  mSettings->SetPrintBGColors([mPrintBGColorsCheckbox state] == NSOnState);
239  mSettings->SetPrintBGImages([mPrintBGImagesCheckbox state] == NSOnState);
240
241  [self exportHeaderFooterSettings];
242}
243
244- (void)dealloc {
245  NS_IF_RELEASE(mPrintBundle);
246  [super dealloc];
247}
248
249// Localization
250
251- (void)initBundle {
252  nsCOMPtr<nsIStringBundleService> bundleSvc = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
253  bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties", &mPrintBundle);
254}
255
256- (NSString*)localizedString:(const char*)aKey {
257  if (!mPrintBundle) return @"";
258
259  nsAutoString intlString;
260  mPrintBundle->GetStringFromName(aKey, intlString);
261  NSMutableString* s =
262      [NSMutableString stringWithUTF8String:NS_ConvertUTF16toUTF8(intlString).get()];
263
264  // Remove all underscores (they're used in the GTK dialog for accesskeys).
265  [s replaceOccurrencesOfString:@"_" withString:@"" options:0 range:NSMakeRange(0, [s length])];
266  return s;
267}
268
269// Widget helpers
270
271- (NSTextField*)label:(const char*)aLabel
272            withFrame:(NSRect)aRect
273            alignment:(NSTextAlignment)aAlignment {
274  NSTextField* label = [[[NSTextField alloc] initWithFrame:aRect] autorelease];
275  [label setStringValue:[self localizedString:aLabel]];
276  [label setEditable:NO];
277  [label setSelectable:NO];
278  [label setBezeled:NO];
279  [label setBordered:NO];
280  [label setDrawsBackground:NO];
281  [label setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]];
282  [label setAlignment:aAlignment];
283  return label;
284}
285
286- (void)addLabel:(const char*)aLabel withFrame:(NSRect)aRect alignment:(NSTextAlignment)aAlignment {
287  NSTextField* label = [self label:aLabel withFrame:aRect alignment:aAlignment];
288  [self addSubview:label];
289}
290
291- (void)addLabel:(const char*)aLabel withFrame:(NSRect)aRect {
292  [self addLabel:aLabel withFrame:aRect alignment:NSTextAlignmentRight];
293}
294
295- (void)addCenteredLabel:(const char*)aLabel withFrame:(NSRect)aRect {
296  [self addLabel:aLabel withFrame:aRect alignment:NSTextAlignmentCenter];
297}
298
299- (NSButton*)checkboxWithLabel:(const char*)aLabel andFrame:(NSRect)aRect {
300  aRect.origin.y += 4.0f;
301  NSButton* checkbox = [[[NSButton alloc] initWithFrame:aRect] autorelease];
302  [checkbox setButtonType:NSSwitchButton];
303  [checkbox setTitle:[self localizedString:aLabel]];
304  [checkbox setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]];
305  [checkbox sizeToFit];
306  return checkbox;
307}
308
309- (NSPopUpButton*)headerFooterItemListWithFrame:(NSRect)aRect
310                                   selectedItem:(const nsAString&)aCurrentString {
311  NSPopUpButton* list = [[[NSPopUpButton alloc] initWithFrame:aRect pullsDown:NO] autorelease];
312  [list setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
313  [[list cell] setControlSize:NSControlSizeSmall];
314  NSArray* items = [NSArray arrayWithObjects:[self localizedString:"headerFooterBlank"],
315                                             [self localizedString:"headerFooterTitle"],
316                                             [self localizedString:"headerFooterURL"],
317                                             [self localizedString:"headerFooterDate"],
318                                             [self localizedString:"headerFooterPage"],
319                                             [self localizedString:"headerFooterPageTotal"], nil];
320  [list addItemsWithTitles:items];
321
322  NS_ConvertUTF16toUTF8 currentStringUTF8(aCurrentString);
323  for (unsigned int i = 0; i < ArrayLength(sHeaderFooterTags); i++) {
324    if (!strcmp(currentStringUTF8.get(), sHeaderFooterTags[i])) {
325      [list selectItemAtIndex:i];
326      break;
327    }
328  }
329
330  return list;
331}
332
333// Build sections
334
335- (void)addOptionsSection {
336  // Title
337  [self addLabel:"optionsTitleMac" withFrame:NSMakeRect(0, 155, 151, 22)];
338
339  // "Print Selection Only"
340  mPrintSelectionOnlyCheckbox = [self checkboxWithLabel:"selectionOnly"
341                                               andFrame:NSMakeRect(156, 155, 0, 0)];
342
343  bool canPrintSelection = mSettings->GetIsPrintSelectionRBEnabled();
344  [mPrintSelectionOnlyCheckbox setEnabled:canPrintSelection];
345
346  if (mSettings->GetPrintSelectionOnly()) {
347    [mPrintSelectionOnlyCheckbox setState:NSOnState];
348  }
349
350  [self addSubview:mPrintSelectionOnlyCheckbox];
351
352  // "Shrink To Fit"
353  mShrinkToFitCheckbox = [self checkboxWithLabel:"shrinkToFit" andFrame:NSMakeRect(156, 133, 0, 0)];
354
355  bool shrinkToFit;
356  mSettings->GetShrinkToFit(&shrinkToFit);
357  [mShrinkToFitCheckbox setState:(shrinkToFit ? NSOnState : NSOffState)];
358
359  [self addSubview:mShrinkToFitCheckbox];
360}
361
362- (void)addAppearanceSection {
363  // Title
364  [self addLabel:"appearanceTitleMac" withFrame:NSMakeRect(0, 103, 151, 22)];
365
366  // "Print Background Colors"
367  mPrintBGColorsCheckbox = [self checkboxWithLabel:"printBGColors"
368                                          andFrame:NSMakeRect(156, 103, 0, 0)];
369
370  bool geckoBool = mSettings->GetPrintBGColors();
371  [mPrintBGColorsCheckbox setState:(geckoBool ? NSOnState : NSOffState)];
372
373  [self addSubview:mPrintBGColorsCheckbox];
374
375  // "Print Background Images"
376  mPrintBGImagesCheckbox = [self checkboxWithLabel:"printBGImages"
377                                          andFrame:NSMakeRect(156, 81, 0, 0)];
378
379  geckoBool = mSettings->GetPrintBGImages();
380  [mPrintBGImagesCheckbox setState:(geckoBool ? NSOnState : NSOffState)];
381
382  [self addSubview:mPrintBGImagesCheckbox];
383}
384
385- (void)addHeaderFooterSection {
386  // Labels
387  [self addLabel:"pageHeadersTitleMac" withFrame:NSMakeRect(0, 44, 151, 22)];
388  [self addLabel:"pageFootersTitleMac" withFrame:NSMakeRect(0, 0, 151, 22)];
389  [self addCenteredLabel:"left" withFrame:NSMakeRect(156, 22, 100, 22)];
390  [self addCenteredLabel:"center" withFrame:NSMakeRect(256, 22, 100, 22)];
391  [self addCenteredLabel:"right" withFrame:NSMakeRect(356, 22, 100, 22)];
392
393  // Lists
394  nsString sel;
395
396  mSettings->GetHeaderStrLeft(sel);
397  mHeaderLeftList = [self headerFooterItemListWithFrame:NSMakeRect(156, 44, 100, 22)
398                                           selectedItem:sel];
399  [self addSubview:mHeaderLeftList];
400
401  mSettings->GetHeaderStrCenter(sel);
402  mHeaderCenterList = [self headerFooterItemListWithFrame:NSMakeRect(256, 44, 100, 22)
403                                             selectedItem:sel];
404  [self addSubview:mHeaderCenterList];
405
406  mSettings->GetHeaderStrRight(sel);
407  mHeaderRightList = [self headerFooterItemListWithFrame:NSMakeRect(356, 44, 100, 22)
408                                            selectedItem:sel];
409  [self addSubview:mHeaderRightList];
410
411  mSettings->GetFooterStrLeft(sel);
412  mFooterLeftList = [self headerFooterItemListWithFrame:NSMakeRect(156, 0, 100, 22)
413                                           selectedItem:sel];
414  [self addSubview:mFooterLeftList];
415
416  mSettings->GetFooterStrCenter(sel);
417  mFooterCenterList = [self headerFooterItemListWithFrame:NSMakeRect(256, 0, 100, 22)
418                                             selectedItem:sel];
419  [self addSubview:mFooterCenterList];
420
421  mSettings->GetFooterStrRight(sel);
422  mFooterRightList = [self headerFooterItemListWithFrame:NSMakeRect(356, 0, 100, 22)
423                                            selectedItem:sel];
424  [self addSubview:mFooterRightList];
425}
426
427// Export settings
428
429- (const char*)headerFooterStringForList:(NSPopUpButton*)aList {
430  NSInteger index = [aList indexOfSelectedItem];
431  NS_ASSERTION(index < NSInteger(ArrayLength(sHeaderFooterTags)),
432               "Index of dropdown is higher than expected!");
433  return sHeaderFooterTags[index];
434}
435
436- (void)exportHeaderFooterSettings {
437  const char* headerFooterStr;
438  headerFooterStr = [self headerFooterStringForList:mHeaderLeftList];
439  mSettings->SetHeaderStrLeft(NS_ConvertUTF8toUTF16(headerFooterStr));
440
441  headerFooterStr = [self headerFooterStringForList:mHeaderCenterList];
442  mSettings->SetHeaderStrCenter(NS_ConvertUTF8toUTF16(headerFooterStr));
443
444  headerFooterStr = [self headerFooterStringForList:mHeaderRightList];
445  mSettings->SetHeaderStrRight(NS_ConvertUTF8toUTF16(headerFooterStr));
446
447  headerFooterStr = [self headerFooterStringForList:mFooterLeftList];
448  mSettings->SetFooterStrLeft(NS_ConvertUTF8toUTF16(headerFooterStr));
449
450  headerFooterStr = [self headerFooterStringForList:mFooterCenterList];
451  mSettings->SetFooterStrCenter(NS_ConvertUTF8toUTF16(headerFooterStr));
452
453  headerFooterStr = [self headerFooterStringForList:mFooterRightList];
454  mSettings->SetFooterStrRight(NS_ConvertUTF8toUTF16(headerFooterStr));
455}
456
457// Summary
458
459- (NSString*)summaryValueForCheckbox:(NSButton*)aCheckbox {
460  if (![aCheckbox isEnabled]) return [self localizedString:"summaryNAValue"];
461
462  return [aCheckbox state] == NSOnState ? [self localizedString:"summaryOnValue"]
463                                        : [self localizedString:"summaryOffValue"];
464}
465
466- (NSString*)headerSummaryValue {
467  return [[mHeaderLeftList titleOfSelectedItem]
468      stringByAppendingString:
469          [@", "
470              stringByAppendingString:
471                  [[mHeaderCenterList titleOfSelectedItem]
472                      stringByAppendingString:
473                          [@", " stringByAppendingString:[mHeaderRightList titleOfSelectedItem]]]]];
474}
475
476- (NSString*)footerSummaryValue {
477  return [[mFooterLeftList titleOfSelectedItem]
478      stringByAppendingString:
479          [@", "
480              stringByAppendingString:
481                  [[mFooterCenterList titleOfSelectedItem]
482                      stringByAppendingString:
483                          [@", " stringByAppendingString:[mFooterRightList titleOfSelectedItem]]]]];
484}
485
486- (NSArray*)localizedSummaryItems {
487  return [NSArray
488      arrayWithObjects:
489          [NSDictionary
490              dictionaryWithObjectsAndKeys:[self localizedString:"summarySelectionOnlyTitle"],
491                                           NSPrintPanelAccessorySummaryItemNameKey,
492                                           [self
493                                               summaryValueForCheckbox:mPrintSelectionOnlyCheckbox],
494                                           NSPrintPanelAccessorySummaryItemDescriptionKey, nil],
495          [NSDictionary
496              dictionaryWithObjectsAndKeys:[self localizedString:"summaryShrinkToFitTitle"],
497                                           NSPrintPanelAccessorySummaryItemNameKey,
498                                           [self summaryValueForCheckbox:mShrinkToFitCheckbox],
499                                           NSPrintPanelAccessorySummaryItemDescriptionKey, nil],
500          [NSDictionary
501              dictionaryWithObjectsAndKeys:[self localizedString:"summaryPrintBGColorsTitle"],
502                                           NSPrintPanelAccessorySummaryItemNameKey,
503                                           [self summaryValueForCheckbox:mPrintBGColorsCheckbox],
504                                           NSPrintPanelAccessorySummaryItemDescriptionKey, nil],
505          [NSDictionary
506              dictionaryWithObjectsAndKeys:[self localizedString:"summaryPrintBGImagesTitle"],
507                                           NSPrintPanelAccessorySummaryItemNameKey,
508                                           [self summaryValueForCheckbox:mPrintBGImagesCheckbox],
509                                           NSPrintPanelAccessorySummaryItemDescriptionKey, nil],
510          [NSDictionary dictionaryWithObjectsAndKeys:[self localizedString:"summaryHeaderTitle"],
511                                                     NSPrintPanelAccessorySummaryItemNameKey,
512                                                     [self headerSummaryValue],
513                                                     NSPrintPanelAccessorySummaryItemDescriptionKey,
514                                                     nil],
515          [NSDictionary dictionaryWithObjectsAndKeys:[self localizedString:"summaryFooterTitle"],
516                                                     NSPrintPanelAccessorySummaryItemNameKey,
517                                                     [self footerSummaryValue],
518                                                     NSPrintPanelAccessorySummaryItemDescriptionKey,
519                                                     nil],
520          nil];
521}
522
523@end
524
525// Accessory controller
526
527@implementation PrintPanelAccessoryController
528
529- (id)initWithSettings:(nsIPrintSettings*)aSettings {
530  [super initWithNibName:nil bundle:nil];
531
532  NSView* accView = [[PrintPanelAccessoryView alloc] initWithSettings:aSettings];
533  [self setView:accView];
534  [accView release];
535  return self;
536}
537
538- (void)exportSettings {
539  return [(PrintPanelAccessoryView*)[self view] exportSettings];
540}
541
542- (NSArray*)localizedSummaryItems {
543  return [(PrintPanelAccessoryView*)[self view] localizedSummaryItems];
544}
545
546@end
547