/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/ArrayUtils.h" #include "mozilla/gfx/PrintTargetCG.h" #include "mozilla/Preferences.h" #include "nsPrintDialogX.h" #include "nsIPrintSettings.h" #include "nsIPrintSettingsService.h" #include "nsPrintSettingsX.h" #include "nsCOMPtr.h" #include "nsQueryObject.h" #include "nsServiceManagerUtils.h" #include "nsIStringBundle.h" #include "nsCRT.h" #import #include "nsObjCExceptions.h" using namespace mozilla; using mozilla::gfx::PrintTarget; NS_IMPL_ISUPPORTS(nsPrintDialogServiceX, nsIPrintDialogService) // Splits our single pages-per-sheet count for native NSPrintInfo: static void setPagesPerSheet(NSPrintInfo* aPrintInfo, int32_t aPPS) { int32_t across, down; // Assumes portrait - we'll swap if landscape. switch (aPPS) { case 2: across = 1; down = 2; break; case 4: across = 2; down = 2; break; case 6: across = 2; down = 3; break; case 9: across = 3; down = 3; break; case 16: across = 4; down = 4; break; default: across = 1; down = 1; break; } if ([aPrintInfo orientation] == NSPaperOrientationLandscape) { std::swap(across, down); } NSMutableDictionary* dict = [aPrintInfo dictionary]; [dict setObject:[NSNumber numberWithInt:across] forKey:@"NSPagesAcross"]; [dict setObject:[NSNumber numberWithInt:down] forKey:@"NSPagesDown"]; } nsPrintDialogServiceX::nsPrintDialogServiceX() {} nsPrintDialogServiceX::~nsPrintDialogServiceX() {} NS_IMETHODIMP nsPrintDialogServiceX::Init() { return NS_OK; } NS_IMETHODIMP nsPrintDialogServiceX::ShowPrintDialog(mozIDOMWindowProxy* aParent, bool aHaveSelection, nsIPrintSettings* aSettings) { NS_OBJC_BEGIN_TRY_BLOCK_RETURN; MOZ_ASSERT(aSettings, "aSettings must not be null"); RefPtr settingsX(do_QueryObject(aSettings)); if (!settingsX) { return NS_ERROR_FAILURE; } NSPrintInfo* printInfo = settingsX->CreateOrCopyPrintInfo(/* aWithScaling = */ true); if (NS_WARN_IF(!printInfo)) { return NS_ERROR_FAILURE; } [printInfo autorelease]; // Set the print job title nsAutoString docName; nsresult rv = aSettings->GetTitle(docName); if (NS_SUCCEEDED(rv)) { nsAutoString adjustedTitle; PrintTarget::AdjustPrintJobNameForIPP(docName, adjustedTitle); CFStringRef cfTitleString = CFStringCreateWithCharacters( NULL, reinterpret_cast(adjustedTitle.BeginReading()), adjustedTitle.Length()); if (cfTitleString) { auto pmPrintSettings = static_cast([printInfo PMPrintSettings]); ::PMPrintSettingsSetJobName(pmPrintSettings, cfTitleString); [printInfo updateFromPMPrintSettings]; CFRelease(cfTitleString); } } // Temporarily set the pages-per-sheet count set in our print preview to // pre-populate the system dialog with the same value: int32_t pagesPerSheet; aSettings->GetNumPagesPerSheet(&pagesPerSheet); setPagesPerSheet(printInfo, pagesPerSheet); // Put the print info into the current print operation, since that's where // [panel runModal] will look for it. We create the view because otherwise // we'll get unrelated warnings printed to the console. NSView* tmpView = [[NSView alloc] init]; NSPrintOperation* printOperation = [NSPrintOperation printOperationWithView:tmpView printInfo:printInfo]; [NSPrintOperation setCurrentOperation:printOperation]; NSPrintPanel* panel = [NSPrintPanel printPanel]; [panel setOptions:NSPrintPanelShowsCopies | NSPrintPanelShowsPageRange | NSPrintPanelShowsPaperSize | NSPrintPanelShowsOrientation | NSPrintPanelShowsScaling]; PrintPanelAccessoryController* viewController = [[PrintPanelAccessoryController alloc] initWithSettings:aSettings haveSelection:aHaveSelection]; [panel addAccessoryController:viewController]; [viewController release]; // Show the dialog. nsCocoaUtils::PrepareForNativeAppModalDialog(); int button = [panel runModal]; nsCocoaUtils::CleanUpAfterNativeAppModalDialog(); // Retrieve a printInfo with the updated settings. (The NSPrintOperation // operates on a copy, so the object we passed in will not have been // modified.) NSPrintInfo* result = [[NSPrintOperation currentOperation] printInfo]; if (!result) { return NS_ERROR_FAILURE; } [NSPrintOperation setCurrentOperation:nil]; [tmpView release]; if (button != NSModalResponseOK) { return NS_ERROR_ABORT; } // We handle pages-per-sheet internally and we want to prevent the macOS // printing code from also applying the pages-per-sheet count. So we need // to move the count off the NSPrintInfo and over to the nsIPrintSettings. NSMutableDictionary* dict = [result dictionary]; auto pagesAcross = [[dict objectForKey:@"NSPagesAcross"] intValue]; auto pagesDown = [[dict objectForKey:@"NSPagesDown"] intValue]; [dict setObject:[NSNumber numberWithUnsignedInt:1] forKey:@"NSPagesAcross"]; [dict setObject:[NSNumber numberWithUnsignedInt:1] forKey:@"NSPagesDown"]; aSettings->SetNumPagesPerSheet(pagesAcross * pagesDown); // Export settings. [viewController exportSettings]; // Update our settings object based on the user's choices in the dialog. // We tell settingsX to adopt this printInfo so that it will be used to run // print job, so that any printer-specific custom settings from print dialog // extension panels will be carried through. settingsX->SetFromPrintInfo(result, /* aAdoptPrintInfo = */ true); return NS_OK; NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); } NS_IMETHODIMP nsPrintDialogServiceX::ShowPageSetupDialog(mozIDOMWindowProxy* aParent, nsIPrintSettings* aNSSettings) { NS_OBJC_BEGIN_TRY_BLOCK_RETURN; MOZ_ASSERT(aParent, "aParent must not be null"); MOZ_ASSERT(aNSSettings, "aSettings must not be null"); NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE); RefPtr settingsX(do_QueryObject(aNSSettings)); if (!settingsX) { return NS_ERROR_FAILURE; } NSPrintInfo* printInfo = settingsX->CreateOrCopyPrintInfo(/* aWithScaling = */ true); if (NS_WARN_IF(!printInfo)) { return NS_ERROR_FAILURE; } [printInfo autorelease]; NSPageLayout* pageLayout = [NSPageLayout pageLayout]; nsCocoaUtils::PrepareForNativeAppModalDialog(); int button = [pageLayout runModalWithPrintInfo:printInfo]; nsCocoaUtils::CleanUpAfterNativeAppModalDialog(); if (button == NSModalResponseOK) { // The Page Setup dialog does not include non-standard settings that need to // be preserved, separate from what the base printSettings object handles, // so we do not need it to adopt the printInfo object here. settingsX->SetFromPrintInfo(printInfo, /* aAdoptPrintInfo = */ false); nsCOMPtr printSettingsService = do_GetService("@mozilla.org/gfx/printsettings-service;1"); if (printSettingsService && Preferences::GetBool("print.save_print_settings", false)) { uint32_t flags = nsIPrintSettings::kInitSavePaperSize | nsIPrintSettings::kInitSaveOrientation | nsIPrintSettings::kInitSaveScaling; printSettingsService->MaybeSavePrintSettingsToPrefs(aNSSettings, flags); } return NS_OK; } return NS_ERROR_ABORT; NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); } // Accessory view @interface PrintPanelAccessoryView (Private) - (NSString*)localizedString:(const char*)aKey; - (const char*)headerFooterStringForList:(NSPopUpButton*)aList; - (void)exportHeaderFooterSettings; - (void)initBundle; - (NSTextField*)label:(const char*)aLabel withFrame:(NSRect)aRect alignment:(NSTextAlignment)aAlignment; - (void)addLabel:(const char*)aLabel withFrame:(NSRect)aRect alignment:(NSTextAlignment)aAlignment; - (void)addLabel:(const char*)aLabel withFrame:(NSRect)aRect; - (void)addCenteredLabel:(const char*)aLabel withFrame:(NSRect)aRect; - (NSButton*)checkboxWithLabel:(const char*)aLabel andFrame:(NSRect)aRect; - (NSPopUpButton*)headerFooterItemListWithFrame:(NSRect)aRect selectedItem: (const nsAString&)aCurrentString; - (void)addOptionsSection:(bool)aHaveSelection; - (void)addAppearanceSection; - (void)addHeaderFooterSection; - (NSString*)summaryValueForCheckbox:(NSButton*)aCheckbox; - (NSString*)headerSummaryValue; - (NSString*)footerSummaryValue; @end static const char sHeaderFooterTags[][4] = {"", "&T", "&U", "&D", "&P", "&PT"}; @implementation PrintPanelAccessoryView // Public methods - (id)initWithSettings:(nsIPrintSettings*)aSettings haveSelection:(bool)aHaveSelection { [super initWithFrame:NSMakeRect(0, 0, 540, 185)]; mSettings = aSettings; [self initBundle]; [self addOptionsSection:aHaveSelection]; [self addAppearanceSection]; [self addHeaderFooterSection]; return self; } - (void)exportSettings { mSettings->SetPrintSelectionOnly([mPrintSelectionOnlyCheckbox state] == NSControlStateValueOn); mSettings->SetShrinkToFit([mShrinkToFitCheckbox state] == NSControlStateValueOn); mSettings->SetPrintBGColors([mPrintBGColorsCheckbox state] == NSControlStateValueOn); mSettings->SetPrintBGImages([mPrintBGImagesCheckbox state] == NSControlStateValueOn); [self exportHeaderFooterSettings]; } - (void)dealloc { NS_IF_RELEASE(mPrintBundle); [super dealloc]; } // Localization - (void)initBundle { nsCOMPtr bundleSvc = do_GetService(NS_STRINGBUNDLE_CONTRACTID); bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties", &mPrintBundle); } - (NSString*)localizedString:(const char*)aKey { if (!mPrintBundle) return @""; nsAutoString intlString; mPrintBundle->GetStringFromName(aKey, intlString); NSMutableString* s = [NSMutableString stringWithUTF8String:NS_ConvertUTF16toUTF8(intlString).get()]; // Remove all underscores (they're used in the GTK dialog for accesskeys). [s replaceOccurrencesOfString:@"_" withString:@"" options:0 range:NSMakeRange(0, [s length])]; return s; } // Widget helpers - (NSTextField*)label:(const char*)aLabel withFrame:(NSRect)aRect alignment:(NSTextAlignment)aAlignment { NSTextField* label = [[[NSTextField alloc] initWithFrame:aRect] autorelease]; [label setStringValue:[self localizedString:aLabel]]; [label setEditable:NO]; [label setSelectable:NO]; [label setBezeled:NO]; [label setBordered:NO]; [label setDrawsBackground:NO]; [label setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]]; [label setAlignment:aAlignment]; return label; } - (void)addLabel:(const char*)aLabel withFrame:(NSRect)aRect alignment:(NSTextAlignment)aAlignment { NSTextField* label = [self label:aLabel withFrame:aRect alignment:aAlignment]; [self addSubview:label]; } - (void)addLabel:(const char*)aLabel withFrame:(NSRect)aRect { [self addLabel:aLabel withFrame:aRect alignment:NSTextAlignmentRight]; } - (void)addCenteredLabel:(const char*)aLabel withFrame:(NSRect)aRect { [self addLabel:aLabel withFrame:aRect alignment:NSTextAlignmentCenter]; } - (NSButton*)checkboxWithLabel:(const char*)aLabel andFrame:(NSRect)aRect { aRect.origin.y += 4.0f; NSButton* checkbox = [[[NSButton alloc] initWithFrame:aRect] autorelease]; [checkbox setButtonType:NSButtonTypeSwitch]; [checkbox setTitle:[self localizedString:aLabel]]; [checkbox setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]]; [checkbox sizeToFit]; return checkbox; } - (NSPopUpButton*)headerFooterItemListWithFrame:(NSRect)aRect selectedItem: (const nsAString&)aCurrentString { NSPopUpButton* list = [[[NSPopUpButton alloc] initWithFrame:aRect pullsDown:NO] autorelease]; [list setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; [[list cell] setControlSize:NSControlSizeSmall]; NSArray* items = [NSArray arrayWithObjects:[self localizedString:"headerFooterBlank"], [self localizedString:"headerFooterTitle"], [self localizedString:"headerFooterURL"], [self localizedString:"headerFooterDate"], [self localizedString:"headerFooterPage"], [self localizedString:"headerFooterPageTotal"], nil]; [list addItemsWithTitles:items]; NS_ConvertUTF16toUTF8 currentStringUTF8(aCurrentString); for (unsigned int i = 0; i < std::size(sHeaderFooterTags); i++) { if (!strcmp(currentStringUTF8.get(), sHeaderFooterTags[i])) { [list selectItemAtIndex:i]; break; } } return list; } // Build sections - (void)addOptionsSection:(bool)aHaveSelection { // Title [self addLabel:"optionsTitleMac" withFrame:NSMakeRect(0, 155, 151, 22)]; // "Print Selection Only" mPrintSelectionOnlyCheckbox = [self checkboxWithLabel:"selectionOnly" andFrame:NSMakeRect(156, 155, 0, 0)]; [mPrintSelectionOnlyCheckbox setEnabled:aHaveSelection]; if (mSettings->GetPrintSelectionOnly()) { [mPrintSelectionOnlyCheckbox setState:NSControlStateValueOn]; } [self addSubview:mPrintSelectionOnlyCheckbox]; // "Shrink To Fit" mShrinkToFitCheckbox = [self checkboxWithLabel:"shrinkToFit" andFrame:NSMakeRect(156, 133, 0, 0)]; bool shrinkToFit; mSettings->GetShrinkToFit(&shrinkToFit); [mShrinkToFitCheckbox setState:(shrinkToFit ? NSControlStateValueOn : NSControlStateValueOff)]; [self addSubview:mShrinkToFitCheckbox]; } - (void)addAppearanceSection { // Title [self addLabel:"appearanceTitleMac" withFrame:NSMakeRect(0, 103, 151, 22)]; // "Print Background Colors" mPrintBGColorsCheckbox = [self checkboxWithLabel:"printBGColors" andFrame:NSMakeRect(156, 103, 0, 0)]; bool geckoBool = mSettings->GetPrintBGColors(); [mPrintBGColorsCheckbox setState:(geckoBool ? NSControlStateValueOn : NSControlStateValueOff)]; [self addSubview:mPrintBGColorsCheckbox]; // "Print Background Images" mPrintBGImagesCheckbox = [self checkboxWithLabel:"printBGImages" andFrame:NSMakeRect(156, 81, 0, 0)]; geckoBool = mSettings->GetPrintBGImages(); [mPrintBGImagesCheckbox setState:(geckoBool ? NSControlStateValueOn : NSControlStateValueOff)]; [self addSubview:mPrintBGImagesCheckbox]; } - (void)addHeaderFooterSection { // Labels [self addLabel:"pageHeadersTitleMac" withFrame:NSMakeRect(0, 44, 151, 22)]; [self addLabel:"pageFootersTitleMac" withFrame:NSMakeRect(0, 0, 151, 22)]; [self addCenteredLabel:"left" withFrame:NSMakeRect(156, 22, 100, 22)]; [self addCenteredLabel:"center" withFrame:NSMakeRect(256, 22, 100, 22)]; [self addCenteredLabel:"right" withFrame:NSMakeRect(356, 22, 100, 22)]; // Lists nsString sel; mSettings->GetHeaderStrLeft(sel); mHeaderLeftList = [self headerFooterItemListWithFrame:NSMakeRect(156, 44, 100, 22) selectedItem:sel]; [self addSubview:mHeaderLeftList]; mSettings->GetHeaderStrCenter(sel); mHeaderCenterList = [self headerFooterItemListWithFrame:NSMakeRect(256, 44, 100, 22) selectedItem:sel]; [self addSubview:mHeaderCenterList]; mSettings->GetHeaderStrRight(sel); mHeaderRightList = [self headerFooterItemListWithFrame:NSMakeRect(356, 44, 100, 22) selectedItem:sel]; [self addSubview:mHeaderRightList]; mSettings->GetFooterStrLeft(sel); mFooterLeftList = [self headerFooterItemListWithFrame:NSMakeRect(156, 0, 100, 22) selectedItem:sel]; [self addSubview:mFooterLeftList]; mSettings->GetFooterStrCenter(sel); mFooterCenterList = [self headerFooterItemListWithFrame:NSMakeRect(256, 0, 100, 22) selectedItem:sel]; [self addSubview:mFooterCenterList]; mSettings->GetFooterStrRight(sel); mFooterRightList = [self headerFooterItemListWithFrame:NSMakeRect(356, 0, 100, 22) selectedItem:sel]; [self addSubview:mFooterRightList]; } // Export settings - (const char*)headerFooterStringForList:(NSPopUpButton*)aList { NSInteger index = [aList indexOfSelectedItem]; NS_ASSERTION(index < NSInteger(std::size(sHeaderFooterTags)), "Index of dropdown is higher than expected!"); return sHeaderFooterTags[index]; } - (void)exportHeaderFooterSettings { const char* headerFooterStr; headerFooterStr = [self headerFooterStringForList:mHeaderLeftList]; mSettings->SetHeaderStrLeft(NS_ConvertUTF8toUTF16(headerFooterStr)); headerFooterStr = [self headerFooterStringForList:mHeaderCenterList]; mSettings->SetHeaderStrCenter(NS_ConvertUTF8toUTF16(headerFooterStr)); headerFooterStr = [self headerFooterStringForList:mHeaderRightList]; mSettings->SetHeaderStrRight(NS_ConvertUTF8toUTF16(headerFooterStr)); headerFooterStr = [self headerFooterStringForList:mFooterLeftList]; mSettings->SetFooterStrLeft(NS_ConvertUTF8toUTF16(headerFooterStr)); headerFooterStr = [self headerFooterStringForList:mFooterCenterList]; mSettings->SetFooterStrCenter(NS_ConvertUTF8toUTF16(headerFooterStr)); headerFooterStr = [self headerFooterStringForList:mFooterRightList]; mSettings->SetFooterStrRight(NS_ConvertUTF8toUTF16(headerFooterStr)); } // Summary - (NSString*)summaryValueForCheckbox:(NSButton*)aCheckbox { if (![aCheckbox isEnabled]) return [self localizedString:"summaryNAValue"]; return [aCheckbox state] == NSControlStateValueOn ? [self localizedString:"summaryOnValue"] : [self localizedString:"summaryOffValue"]; } - (NSString*)headerSummaryValue { return [[mHeaderLeftList titleOfSelectedItem] stringByAppendingString: [@", " stringByAppendingString: [[mHeaderCenterList titleOfSelectedItem] stringByAppendingString: [@", " stringByAppendingString: [mHeaderRightList titleOfSelectedItem]]]]]; } - (NSString*)footerSummaryValue { return [[mFooterLeftList titleOfSelectedItem] stringByAppendingString: [@", " stringByAppendingString: [[mFooterCenterList titleOfSelectedItem] stringByAppendingString: [@", " stringByAppendingString: [mFooterRightList titleOfSelectedItem]]]]]; } - (NSArray*)localizedSummaryItems { return [NSArray arrayWithObjects: [NSDictionary dictionaryWithObjectsAndKeys: [self localizedString:"summarySelectionOnlyTitle"], NSPrintPanelAccessorySummaryItemNameKey, [self summaryValueForCheckbox:mPrintSelectionOnlyCheckbox], NSPrintPanelAccessorySummaryItemDescriptionKey, nil], [NSDictionary dictionaryWithObjectsAndKeys: [self localizedString:"summaryShrinkToFitTitle"], NSPrintPanelAccessorySummaryItemNameKey, [self summaryValueForCheckbox:mShrinkToFitCheckbox], NSPrintPanelAccessorySummaryItemDescriptionKey, nil], [NSDictionary dictionaryWithObjectsAndKeys: [self localizedString:"summaryPrintBGColorsTitle"], NSPrintPanelAccessorySummaryItemNameKey, [self summaryValueForCheckbox:mPrintBGColorsCheckbox], NSPrintPanelAccessorySummaryItemDescriptionKey, nil], [NSDictionary dictionaryWithObjectsAndKeys: [self localizedString:"summaryPrintBGImagesTitle"], NSPrintPanelAccessorySummaryItemNameKey, [self summaryValueForCheckbox:mPrintBGImagesCheckbox], NSPrintPanelAccessorySummaryItemDescriptionKey, nil], [NSDictionary dictionaryWithObjectsAndKeys: [self localizedString:"summaryHeaderTitle"], NSPrintPanelAccessorySummaryItemNameKey, [self headerSummaryValue], NSPrintPanelAccessorySummaryItemDescriptionKey, nil], [NSDictionary dictionaryWithObjectsAndKeys: [self localizedString:"summaryFooterTitle"], NSPrintPanelAccessorySummaryItemNameKey, [self footerSummaryValue], NSPrintPanelAccessorySummaryItemDescriptionKey, nil], nil]; } @end // Accessory controller @implementation PrintPanelAccessoryController - (id)initWithSettings:(nsIPrintSettings*)aSettings haveSelection:(bool)aHaveSelection { [super initWithNibName:nil bundle:nil]; NSView* accView = [[PrintPanelAccessoryView alloc] initWithSettings:aSettings haveSelection:aHaveSelection]; [self setView:accView]; [accView release]; return self; } - (void)exportSettings { return [(PrintPanelAccessoryView*)[self view] exportSettings]; } - (NSArray*)localizedSummaryItems { return [(PrintPanelAccessoryView*)[self view] localizedSummaryItems]; } @end