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