1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qcupsprintengine_p.h"
41
42#include <qpa/qplatformprintplugin.h>
43#include <qpa/qplatformprintersupport.h>
44
45#include <qiodevice.h>
46#include <qfile.h>
47#include <qdebug.h>
48#include <qbuffer.h>
49#include "private/qcups_p.h" // Only needed for PPK_CupsOptions
50#include <QtGui/qpagelayout.h>
51
52#include <cups/cups.h>
53
54#include "private/qcore_unix_p.h" // overrides QT_OPEN
55
56QT_BEGIN_NAMESPACE
57
58extern QMarginsF qt_convertMargins(const QMarginsF &margins, QPageLayout::Unit fromUnits, QPageLayout::Unit toUnits);
59
60QCupsPrintEngine::QCupsPrintEngine(QPrinter::PrinterMode m, const QString &deviceId)
61 : QPdfPrintEngine(*new QCupsPrintEnginePrivate(m))
62{
63 Q_D(QCupsPrintEngine);
64 d->changePrinter(newPrinter: deviceId);
65 state = QPrinter::Idle;
66}
67
68QCupsPrintEngine::~QCupsPrintEngine()
69{
70}
71
72void QCupsPrintEngine::setProperty(PrintEnginePropertyKey key, const QVariant &value)
73{
74 Q_D(QCupsPrintEngine);
75
76 switch (int(key)) {
77 case PPK_PageSize:
78 d->setPageSize(QPageSize(QPageSize::PageSizeId(value.toInt())));
79 break;
80 case PPK_WindowsPageSize:
81 d->setPageSize(QPageSize(QPageSize::id(windowsId: value.toInt())));
82 break;
83 case PPK_CustomPaperSize:
84 d->setPageSize(QPageSize(value.toSizeF(), QPageSize::Point));
85 break;
86 case PPK_PaperName:
87 // Get the named page size from the printer if supported
88 d->setPageSize(d->m_printDevice.supportedPageSize(pageName: value.toString()));
89 break;
90 case PPK_Duplex: {
91 QPrint::DuplexMode mode = QPrint::DuplexMode(value.toInt());
92 if (d->m_printDevice.supportedDuplexModes().contains(t: mode)) {
93 d->duplex = mode;
94 d->duplexRequestedExplicitly = true;
95 }
96 break;
97 }
98 case PPK_PrinterName:
99 d->changePrinter(newPrinter: value.toString());
100 break;
101 case PPK_CupsOptions:
102 d->cupsOptions = value.toStringList();
103 break;
104 case PPK_QPageSize:
105 d->setPageSize(qvariant_cast<QPageSize>(v: value));
106 break;
107 case PPK_QPageLayout: {
108 QPageLayout pageLayout = qvariant_cast<QPageLayout>(v: value);
109 if (pageLayout.isValid() && (d->m_printDevice.isValidPageLayout(layout: pageLayout, resolution: d->resolution)
110 || d->m_printDevice.supportsCustomPageSizes()
111 || d->m_printDevice.supportedPageSizes().isEmpty())) {
112 // supportedPageSizes().isEmpty() because QPageSetupWidget::initPageSizes says
113 // "If no available printer page sizes, populate with all page sizes"
114 d->m_pageLayout = pageLayout;
115 d->setPageSize(pageLayout.pageSize());
116 }
117 break;
118 }
119 default:
120 QPdfPrintEngine::setProperty(key, value);
121 break;
122 }
123}
124
125QVariant QCupsPrintEngine::property(PrintEnginePropertyKey key) const
126{
127 Q_D(const QCupsPrintEngine);
128
129 QVariant ret;
130 switch (int(key)) {
131 case PPK_SupportsMultipleCopies:
132 // CUPS server always supports multiple copies, even if individual m_printDevice doesn't
133 ret = true;
134 break;
135 case PPK_NumberOfCopies:
136 ret = 1;
137 break;
138 case PPK_CupsOptions:
139 ret = d->cupsOptions;
140 break;
141 case PPK_Duplex:
142 ret = d->duplex;
143 break;
144 default:
145 ret = QPdfPrintEngine::property(key);
146 break;
147 }
148 return ret;
149}
150
151
152QCupsPrintEnginePrivate::QCupsPrintEnginePrivate(QPrinter::PrinterMode m)
153 : QPdfPrintEnginePrivate(m)
154 , duplex(QPrint::DuplexNone)
155{
156}
157
158QCupsPrintEnginePrivate::~QCupsPrintEnginePrivate()
159{
160}
161
162bool QCupsPrintEnginePrivate::openPrintDevice()
163{
164 if (outDevice)
165 return false;
166
167 if (!outputFileName.isEmpty()) {
168 QFile *file = new QFile(outputFileName);
169 if (! file->open(flags: QFile::WriteOnly|QFile::Truncate)) {
170 delete file;
171 return false;
172 }
173 outDevice = file;
174 } else {
175 char filename[512];
176 fd = cupsTempFd(filename, len: 512);
177 if (fd < 0) {
178 qWarning(msg: "QPdfPrinter: Could not open temporary file to print");
179 return false;
180 }
181 cupsTempFile = QString::fromLocal8Bit(str: filename);
182 outDevice = new QFile();
183 static_cast<QFile *>(outDevice)->open(fd, ioFlags: QIODevice::WriteOnly);
184 }
185
186 return true;
187}
188
189void QCupsPrintEnginePrivate::closePrintDevice()
190{
191 QPdfPrintEnginePrivate::closePrintDevice();
192
193 if (!cupsTempFile.isEmpty()) {
194 QString tempFile = cupsTempFile;
195 cupsTempFile.clear();
196
197 // Should never have got here without a printer, but check anyway
198 if (printerName.isEmpty()) {
199 qWarning(msg: "Could not determine printer to print to");
200 QFile::remove(fileName: tempFile);
201 return;
202 }
203
204 // Set up print options.
205 QList<QPair<QByteArray, QByteArray> > options;
206 QVector<cups_option_t> cupsOptStruct;
207
208 options.append(t: QPair<QByteArray, QByteArray>("media", m_pageLayout.pageSize().key().toLocal8Bit()));
209
210 if (copies > 1)
211 options.append(t: QPair<QByteArray, QByteArray>("copies", QString::number(copies).toLocal8Bit()));
212
213 if (copies > 1 && collate)
214 options.append(t: QPair<QByteArray, QByteArray>("Collate", "True"));
215
216 switch (duplex) {
217 case QPrint::DuplexNone:
218 options.append(t: QPair<QByteArray, QByteArray>("sides", "one-sided"));
219 break;
220 case QPrint::DuplexAuto:
221 if (m_pageLayout.orientation() == QPageLayout::Portrait)
222 options.append(t: QPair<QByteArray, QByteArray>("sides", "two-sided-long-edge"));
223 else
224 options.append(t: QPair<QByteArray, QByteArray>("sides", "two-sided-short-edge"));
225 break;
226 case QPrint::DuplexLongSide:
227 options.append(t: QPair<QByteArray, QByteArray>("sides", "two-sided-long-edge"));
228 break;
229 case QPrint::DuplexShortSide:
230 options.append(t: QPair<QByteArray, QByteArray>("sides", "two-sided-short-edge"));
231 break;
232 }
233
234 if (m_pageLayout.orientation() == QPageLayout::Landscape)
235 options.append(t: QPair<QByteArray, QByteArray>("landscape", ""));
236
237 QStringList::const_iterator it = cupsOptions.constBegin();
238 while (it != cupsOptions.constEnd()) {
239 options.append(t: QPair<QByteArray, QByteArray>((*it).toLocal8Bit(), (*(it+1)).toLocal8Bit()));
240 it += 2;
241 }
242
243 const int numOptions = options.size();
244 cupsOptStruct.reserve(asize: numOptions);
245 for (int c = 0; c < numOptions; ++c) {
246 cups_option_t opt;
247 opt.name = options[c].first.data();
248 opt.value = options[c].second.data();
249 cupsOptStruct.append(t: opt);
250 }
251
252 // Print the file
253 // Cups expect the printer original name without instance, the full name is used only to retrieve the configuration
254 const auto parts = printerName.splitRef(sep: QLatin1Char('/'));
255 const auto printerOriginalName = parts.at(i: 0);
256 cups_option_t* optPtr = cupsOptStruct.size() ? &cupsOptStruct.first() : 0;
257 cupsPrintFile(name: printerOriginalName.toLocal8Bit().constData(), filename: tempFile.toLocal8Bit().constData(),
258 title: title.toLocal8Bit().constData(), num_options: cupsOptStruct.size(), options: optPtr);
259
260 QFile::remove(fileName: tempFile);
261 }
262}
263
264void QCupsPrintEnginePrivate::changePrinter(const QString &newPrinter)
265{
266 // Don't waste time if same printer name
267 if (newPrinter == printerName)
268 return;
269
270 // Should never have reached here if no plugin available, but check just in case
271 QPlatformPrinterSupport *ps = QPlatformPrinterSupportPlugin::get();
272 if (!ps)
273 return;
274
275 // Try create the printer, only use it if it returns valid
276 QPrintDevice printDevice = ps->createPrintDevice(id: newPrinter);
277 if (!printDevice.isValid())
278 return;
279 m_printDevice.swap(other&: printDevice);
280 printerName = m_printDevice.id();
281
282 // in case a duplex value was explicitly set, check if new printer supports current value,
283 // otherwise use device default
284 if (!duplexRequestedExplicitly || !m_printDevice.supportedDuplexModes().contains(t: duplex)) {
285 duplex = m_printDevice.defaultDuplexMode();
286 duplexRequestedExplicitly = false;
287 }
288 QPrint::ColorMode colorMode = grayscale ? QPrint::GrayScale : QPrint::Color;
289 if (!m_printDevice.supportedColorModes().contains(t: colorMode))
290 grayscale = m_printDevice.defaultColorMode() == QPrint::GrayScale;
291
292 // Get the equivalent page size for this printer as supported names may be different
293 if (m_printDevice.supportedPageSize(pageSize: m_pageLayout.pageSize()).isValid())
294 setPageSize(m_pageLayout.pageSize());
295 else
296 setPageSize(QPageSize(m_pageLayout.pageSize().size(units: QPageSize::Point), QPageSize::Point));
297}
298
299void QCupsPrintEnginePrivate::setPageSize(const QPageSize &pageSize)
300{
301 if (pageSize.isValid()) {
302 // Find if the requested page size has a matching printer page size, if so use its defined name instead
303 QPageSize printerPageSize = m_printDevice.supportedPageSize(pageSize);
304 QPageSize usePageSize = printerPageSize.isValid() ? printerPageSize : pageSize;
305 QMarginsF printable = m_printDevice.printableMargins(pageSize: usePageSize, orientation: m_pageLayout.orientation(), resolution);
306 m_pageLayout.setPageSize(pageSize: usePageSize, minMargins: qt_convertMargins(margins: printable, fromUnits: QPageLayout::Point, toUnits: m_pageLayout.units()));
307 }
308}
309
310QT_END_NAMESPACE
311

source code of qtbase/src/plugins/printsupport/cups/qcupsprintengine.cpp