Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qprintdialog_mac.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <AppKit/AppKit.h>
5
6#include "qprintdialog.h"
8
9#include <QtCore/qtemporarydir.h>
10#include <QtCore/private/qcore_mac_p.h>
11#include <QtWidgets/private/qapplication_p.h>
12#include <QtPrintSupport/qprinter.h>
13#include <QtPrintSupport/qprintengine.h>
14#include <qpa/qplatformprintdevice.h>
15
16#include <QtPrintSupport/private/qprintengine_mac_p.h>
17
19
20using namespace Qt::StringLiterals;
21
22extern qreal qt_pointMultiplier(QPageLayout::Unit unit);
23
25{
26 Q_DECLARE_PUBLIC(QPrintDialog)
27
28public:
31
34
35 inline QPrintDialog *printDialog() { return q_func(); }
36
39};
40
42
44
45
46@class QT_MANGLE_NAMESPACE(QCocoaPrintPanelDelegate);
47
48@interface QT_MANGLE_NAMESPACE(QCocoaPrintPanelDelegate) : NSObject
49@end
50
51@implementation QT_MANGLE_NAMESPACE(QCocoaPrintPanelDelegate) {
52 NSPrintInfo *printInfo;
53}
54
55- (instancetype)initWithNSPrintInfo:(NSPrintInfo *)nsPrintInfo
56{
57 if ((self = [self init])) {
58 printInfo = nsPrintInfo;
59 }
60 return self;
61}
62
63- (void)printPanelDidEnd:(NSPrintPanel *)printPanel
64 returnCode:(int)returnCode contextInfo:(void *)contextInfo
65{
66 Q_UNUSED(printPanel);
67
68 QPrintDialog *dialog = static_cast<QPrintDialog *>(contextInfo);
69 QPrinter *printer = dialog->printer();
70
71 if (returnCode == NSModalResponseOK) {
72 PMPrintSession session = static_cast<PMPrintSession>(printInfo.PMPrintSession);
73 PMPrintSettings settings = static_cast<PMPrintSettings>(printInfo.PMPrintSettings);
74
75 UInt32 frompage, topage;
76 PMGetFirstPage(settings, &frompage);
77 PMGetLastPage(settings, &topage);
78 topage = qMin(UInt32(INT_MAX), topage);
79 dialog->setFromTo(frompage, topage);
80
81 // OK, I need to map these values back let's see
82 // If from is 1 and to is INT_MAX, then print it all
83 // (Apologies to the folks with more than INT_MAX pages)
84 if (dialog->fromPage() == 1 && dialog->toPage() == INT_MAX) {
85 dialog->setPrintRange(QPrintDialog::AllPages);
86 printer->setPageRanges(QPageRanges());
87 } else {
88 dialog->setPrintRange(QPrintDialog::PageRange); // In a way a lie, but it shouldn't hurt.
89 // Carbon hands us back a very large number here even for ALL, set it to max
90 // in that case to follow the behavior of the other print dialogs.
91 if (dialog->maxPage() < dialog->toPage())
92 dialog->setFromTo(dialog->fromPage(), dialog->maxPage());
93 }
94
95 // Keep us in sync with chosen destination
96 PMDestinationType dest;
97 PMSessionGetDestinationType(session, settings, &dest);
98 if (dest == kPMDestinationFile) {
99 QCFType<CFURLRef> file;
100 PMSessionCopyDestinationLocation(session, settings, &file);
101 UInt8 localFile[2048]; // Assuming there's a POSIX file system here.
102 CFURLGetFileSystemRepresentation(file, true, localFile, sizeof(localFile));
103 auto outputFile = QFileInfo(QString::fromUtf8(reinterpret_cast<const char *>(localFile)));
104 if (outputFile.suffix() == "pdf"_L1)
105 printer->setOutputFileName(outputFile.absoluteFilePath());
106 else
107 qWarning() << "Can not print to file type" << outputFile.suffix();
108 } else if (dest == kPMDestinationPreview) {
109 static QTemporaryDir printPreviews;
110 auto documentName = printer->docName();
111 if (documentName.isEmpty())
112 documentName = QGuiApplication::applicationDisplayName();
113 auto fileName = printPreviews.filePath(QString("%1.pdf"_L1).arg(documentName));
114 printer->setOutputFileName(fileName);
115 // Ideally we would have a callback when the PDF engine is done writing
116 // to the file, and open Preview in response to that. Lacking that, we
117 // use the quick and dirty assumption that the the print operation will
118 // happen synchronously after the dialog is accepted, so we can defer
119 // the opening of the file to the next runloop pass.
120 dispatch_async(dispatch_get_main_queue(), ^{
121 [NSWorkspace.sharedWorkspace openURL:[NSURL fileURLWithPath:fileName.toNSString()]];
122 });
123 } else if (dest == kPMDestinationProcessPDF) {
124 qWarning("Printing workflows are not supported");
125 } else if (dest == kPMDestinationPrinter) {
126 PMPrinter macPrinter;
127 PMSessionGetCurrentPrinter(session, &macPrinter);
128 QString printerId = QString::fromCFString(PMPrinterGetID(macPrinter)).trimmed();
129 if (printer->printerName() != printerId)
130 printer->setPrinterName(printerId);
131 }
132 }
133
134 // Note this code should be in QCocoaPrintDevice, but that implementation is in the plugin,
135 // we need to move the dialog implementation into the plugin first to be able to access it.
136 // Need to tell QPrinter/QPageLayout if the page size or orientation has been changed
137 PMPageFormat pageFormat = static_cast<PMPageFormat>([printInfo PMPageFormat]);
138 PMPaper paper;
139 PMGetPageFormatPaper(pageFormat, &paper);
140 PMOrientation orientation;
141 PMGetOrientation(pageFormat, &orientation);
142 QPageSize pageSize;
143 CFStringRef key;
144 double width = 0;
145 double height = 0;
146 // If the PPD name is empty then is custom, for some reason PMPaperIsCustom doesn't work here
147 PMPaperGetPPDPaperName(paper, &key);
148 if (PMPaperGetWidth(paper, &width) == noErr && PMPaperGetHeight(paper, &height) == noErr) {
149 QString ppdKey = QString::fromCFString(key);
150 if (ppdKey.isEmpty()) {
151 // Is using a custom page size as defined in the Print Dialog custom settings using mm or inches.
152 // We can't ask PMPaper what those units actually are, we can only get the point size which may return
153 // slightly wrong results due to rounding.
154 // Testing shows if using metric/mm then is rounded mm, if imperial/inch is rounded to 2 decimal places
155 // Even if we pass in our own custom size in mm with decimal places, the dialog will still round it!
156 // Suspect internal storage is in rounded mm?
157 if (QLocale().measurementSystem() == QLocale::MetricSystem) {
158 QSizeF sizef = QSizeF(width, height) / qt_pointMultiplier(QPageLayout::Millimeter);
159 // Round to 0 decimal places
160 pageSize = QPageSize(sizef.toSize(), QPageSize::Millimeter);
161 } else {
162 qreal multiplier = qt_pointMultiplier(QPageLayout::Inch);
163 const int w = qRound(width * 100 / multiplier);
164 const int h = qRound(height * 100 / multiplier);
165 pageSize = QPageSize(QSizeF(w / 100.0, h / 100.0), QPageSize::Inch);
166 }
167 } else {
168 pageSize = QPlatformPrintDevice::createPageSize(ppdKey, QSize(width, height), QString());
169 }
170 }
171 if (pageSize.isValid() && !pageSize.isEquivalentTo(printer->pageLayout().pageSize()))
172 printer->setPageSize(pageSize);
173 printer->setPageOrientation(orientation == kPMLandscape ? QPageLayout::Landscape : QPageLayout::Portrait);
174
175 dialog->done((returnCode == NSModalResponseOK) ? QDialog::Accepted : QDialog::Rejected);
176}
177
178@end
179
180QT_BEGIN_NAMESPACE
181
182void QPrintDialogPrivate::openCocoaPrintPanel(Qt::WindowModality modality)
183{
184 Q_Q(QPrintDialog);
185
186 if (printer->outputFormat() == QPrinter::NativeFormat) {
187 printInfo = static_cast<QMacPrintEngine *>(printer->printEngine())->printInfo();
188 [printInfo retain];
189 } else {
190 const QPageLayout pageLayout = printer->pageLayout();
191 // initialize the printInfo using the dictionary from the application-wide print info
192 const auto dictionary = [NSPrintInfo.sharedPrintInfo dictionary];
193 printInfo = [[NSPrintInfo alloc] initWithDictionary:dictionary];
194 printInfo.orientation = pageLayout.orientation() == QPageLayout::Landscape
195 ? NSPaperOrientationLandscape : NSPaperOrientationPortrait;
196 printInfo.paperSize = pageLayout.pageSize().size(QPageSize::Point).toCGSize();
197 }
198
199 // It seems the only way that PM lets you use all is if the minimum
200 // for the page range is 1. This _kind of_ makes sense if you think about
201 // it. However, calling PMSetFirstPage() or PMSetLastPage() always enforces
202 // the range.
203 // get print settings from the platform plugin
204 PMPrintSettings settings = static_cast<PMPrintSettings>([printInfo PMPrintSettings]);
205 PMSetPageRange(settings, q->minPage(), q->maxPage());
206 if (q->printRange() == QAbstractPrintDialog::PageRange) {
207 PMSetFirstPage(settings, q->fromPage(), false);
208 PMSetLastPage(settings, q->toPage(), false);
209 }
210 [printInfo updateFromPMPrintSettings];
211
212 QPrintDialog::PrintDialogOptions qtOptions = q->options();
213 NSPrintPanelOptions macOptions = NSPrintPanelShowsCopies;
214 if (qtOptions & QPrintDialog::PrintPageRange)
215 macOptions |= NSPrintPanelShowsPageRange;
216 if (qtOptions & QPrintDialog::PrintShowPageSize)
217 macOptions |= NSPrintPanelShowsPaperSize | NSPrintPanelShowsPageSetupAccessory
218 | NSPrintPanelShowsOrientation;
219
220 printPanel = [NSPrintPanel printPanel];
221 [printPanel retain];
222 [printPanel setOptions:macOptions];
223
224 // Call processEvents in case the event dispatcher has been interrupted, and needs to do
225 // cleanup of modal sessions. Do this before showing the native dialog, otherwise it will
226 // close down during the cleanup (QTBUG-17913):
227 qApp->processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers);
228
229 QT_MANGLE_NAMESPACE(QCocoaPrintPanelDelegate) *delegate = [[QT_MANGLE_NAMESPACE(QCocoaPrintPanelDelegate) alloc] initWithNSPrintInfo:printInfo];
230 if (modality == Qt::ApplicationModal || !q->parentWidget()) {
231 if (modality == Qt::NonModal)
232 qWarning("QPrintDialog is required to be modal on OS X");
233
234 // Make sure we don't interrupt the runModalWithPrintInfo call.
235 (void) QMetaObject::invokeMethod(qApp->platformNativeInterface(),
236 "clearCurrentThreadCocoaEventDispatcherInterruptFlag");
237
238 int rval = [printPanel runModalWithPrintInfo:printInfo];
239 [delegate printPanelDidEnd:printPanel returnCode:rval contextInfo:q];
240 } else {
241 Q_ASSERT(q->window());
242 QWindow *parentWindow = q->window()->windowHandle();
243 NSWindow *window = static_cast<NSWindow *>(qApp->platformNativeInterface()->nativeResourceForWindow("nswindow", parentWindow));
244 [printPanel beginSheetWithPrintInfo:printInfo
245 modalForWindow:window
246 delegate:delegate
247 didEndSelector:@selector(printPanelDidEnd:returnCode:contextInfo:)
248 contextInfo:q];
249 }
250}
251
253{
254 [printInfo release];
255 printInfo = 0;
256 [printPanel release];
257 printPanel = 0;
258}
259
260QPrintDialog::QPrintDialog(QPrinter *printer, QWidget *parent)
261 : QAbstractPrintDialog(*(new QPrintDialogPrivate), printer, parent)
262{
263 setAttribute(Qt::WA_DontShowOnScreen);
264}
265
266QPrintDialog::QPrintDialog(QWidget *parent)
267 : QAbstractPrintDialog(*(new QPrintDialogPrivate), 0, parent)
268{
269 setAttribute(Qt::WA_DontShowOnScreen);
270}
271
272QPrintDialog::~QPrintDialog()
273{
274 hide();
275}
276
277int QPrintDialog::exec()
278{
279 Q_D(QPrintDialog);
280
281 QDialog::setVisible(true);
282
283 QMacAutoReleasePool pool;
284 d->openCocoaPrintPanel(Qt::ApplicationModal);
285 d->closeCocoaPrintPanel();
286
287 QDialog::setVisible(false);
288
289 return result();
290}
291
292
293/*!
294 \reimp
295*/
296void QPrintDialog::setVisible(bool visible)
297{
298 Q_D(QPrintDialog);
299
300 bool isCurrentlyVisible = (d->printPanel != 0);
301
302 if (!visible == !isCurrentlyVisible)
303 return;
304
305 if (d->printer->outputFormat() != QPrinter::NativeFormat)
306 return;
307
308 QDialog::setVisible(visible);
309
310 if (visible) {
311 Qt::WindowModality modality = windowModality();
312 if (modality == Qt::NonModal) {
313 // NSPrintPanels can only be modal, so we must pick a type
314 modality = parentWidget() ? Qt::WindowModal : Qt::ApplicationModal;
315 }
316 d->openCocoaPrintPanel(modality);
317 return;
318 } else {
319 if (d->printPanel) {
320 d->closeCocoaPrintPanel();
321 return;
322 }
323 }
324}
325
326QT_END_NAMESPACE
327
328#include "moc_qprintdialog.cpp"
QPrintDialog * printDialog()
NSPrintPanel * printPanel
Combined button and popup list for selecting options.
qreal qt_pointMultiplier(QPageLayout::Unit unit)