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(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(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(value.toString()));
89 break;
90 case PPK_Duplex: {
91 QPrint::DuplexMode mode = QPrint::DuplexMode(value.toInt());
92 if (mode != d->duplex && d->m_printDevice.supportedDuplexModes().contains(mode))
93 d->duplex = mode;
94 break;
95 }
96 case PPK_PrinterName:
97 d->changePrinter(value.toString());
98 break;
99 case PPK_CupsOptions:
100 d->cupsOptions = value.toStringList();
101 break;
102 case PPK_QPageSize:
103 d->setPageSize(value.value<QPageSize>());
104 break;
105 case PPK_QPageLayout: {
106 QPageLayout pageLayout = value.value<QPageLayout>();
107 if (pageLayout.isValid() && (d->m_printDevice.isValidPageLayout(pageLayout, d->resolution)
108 || d->m_printDevice.supportsCustomPageSizes()
109 || d->m_printDevice.supportedPageSizes().isEmpty())) {
110 // supportedPageSizes().isEmpty() because QPageSetupWidget::initPageSizes says
111 // "If no available printer page sizes, populate with all page sizes"
112 d->m_pageLayout = pageLayout;
113 d->setPageSize(pageLayout.pageSize());
114 }
115 break;
116 }
117 default:
118 QPdfPrintEngine::setProperty(key, value);
119 break;
120 }
121}
122
123QVariant QCupsPrintEngine::property(PrintEnginePropertyKey key) const
124{
125 Q_D(const QCupsPrintEngine);
126
127 QVariant ret;
128 switch (int(key)) {
129 case PPK_SupportsMultipleCopies:
130 // CUPS server always supports multiple copies, even if individual m_printDevice doesn't
131 ret = true;
132 break;
133 case PPK_NumberOfCopies:
134 ret = 1;
135 break;
136 case PPK_CupsOptions:
137 ret = d->cupsOptions;
138 break;
139 case PPK_Duplex:
140 ret = d->duplex;
141 break;
142 default:
143 ret = QPdfPrintEngine::property(key);
144 break;
145 }
146 return ret;
147}
148
149
150QCupsPrintEnginePrivate::QCupsPrintEnginePrivate(QPrinter::PrinterMode m)
151 : QPdfPrintEnginePrivate(m)
152 , duplex(QPrint::DuplexNone)
153{
154}
155
156QCupsPrintEnginePrivate::~QCupsPrintEnginePrivate()
157{
158}
159
160bool QCupsPrintEnginePrivate::openPrintDevice()
161{
162 if (outDevice)
163 return false;
164
165 if (!outputFileName.isEmpty()) {
166 QFile *file = new QFile(outputFileName);
167 if (! file->open(QFile::WriteOnly|QFile::Truncate)) {
168 delete file;
169 return false;
170 }
171 outDevice = file;
172 } else {
173 char filename[512];
174 fd = cupsTempFd(filename, 512);
175 if (fd < 0) {
176 qWarning("QPdfPrinter: Could not open temporary file to print");
177 return false;
178 }
179 cupsTempFile = QString::fromLocal8Bit(filename);
180 outDevice = new QFile();
181 static_cast<QFile *>(outDevice)->open(fd, QIODevice::WriteOnly);
182 }
183
184 return true;
185}
186
187void QCupsPrintEnginePrivate::closePrintDevice()
188{
189 QPdfPrintEnginePrivate::closePrintDevice();
190
191 if (!cupsTempFile.isEmpty()) {
192 QString tempFile = cupsTempFile;
193 cupsTempFile.clear();
194
195 // Should never have got here without a printer, but check anyway
196 if (printerName.isEmpty()) {
197 qWarning("Could not determine printer to print to");
198 QFile::remove(tempFile);
199 return;
200 }
201
202 // Set up print options.
203 QList<QPair<QByteArray, QByteArray> > options;
204 QVector<cups_option_t> cupsOptStruct;
205
206 options.append(QPair<QByteArray, QByteArray>("media", m_pageLayout.pageSize().key().toLocal8Bit()));
207
208 if (copies > 1)
209 options.append(QPair<QByteArray, QByteArray>("copies", QString::number(copies).toLocal8Bit()));
210
211 if (copies > 1 && collate)
212 options.append(QPair<QByteArray, QByteArray>("Collate", "True"));
213
214 switch (duplex) {
215 case QPrint::DuplexNone:
216 options.append(QPair<QByteArray, QByteArray>("sides", "one-sided"));
217 break;
218 case QPrint::DuplexAuto:
219 if (m_pageLayout.orientation() == QPageLayout::Portrait)
220 options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-long-edge"));
221 else
222 options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-short-edge"));
223 break;
224 case QPrint::DuplexLongSide:
225 options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-long-edge"));
226 break;
227 case QPrint::DuplexShortSide:
228 options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-short-edge"));
229 break;
230 }
231
232 if (m_pageLayout.orientation() == QPageLayout::Landscape)
233 options.append(QPair<QByteArray, QByteArray>("landscape", ""));
234
235 QStringList::const_iterator it = cupsOptions.constBegin();
236 while (it != cupsOptions.constEnd()) {
237 options.append(QPair<QByteArray, QByteArray>((*it).toLocal8Bit(), (*(it+1)).toLocal8Bit()));
238 it += 2;
239 }
240
241 const int numOptions = options.size();
242 cupsOptStruct.reserve(numOptions);
243 for (int c = 0; c < numOptions; ++c) {
244 cups_option_t opt;
245 opt.name = options[c].first.data();
246 opt.value = options[c].second.data();
247 cupsOptStruct.append(opt);
248 }
249
250 // Print the file
251 // Cups expect the printer original name without instance, the full name is used only to retrieve the configuration
252 const auto parts = printerName.splitRef(QLatin1Char('/'));
253 const auto printerOriginalName = parts.at(0);
254 cups_option_t* optPtr = cupsOptStruct.size() ? &cupsOptStruct.first() : 0;
255 cupsPrintFile(printerOriginalName.toLocal8Bit().constData(), tempFile.toLocal8Bit().constData(),
256 title.toLocal8Bit().constData(), cupsOptStruct.size(), optPtr);
257
258 QFile::remove(tempFile);
259 }
260}
261
262void QCupsPrintEnginePrivate::changePrinter(const QString &newPrinter)
263{
264 // Don't waste time if same printer name
265 if (newPrinter == printerName)
266 return;
267
268 // Should never have reached here if no plugin available, but check just in case
269 QPlatformPrinterSupport *ps = QPlatformPrinterSupportPlugin::get();
270 if (!ps)
271 return;
272
273 // Try create the printer, only use it if it returns valid
274 QPrintDevice printDevice = ps->createPrintDevice(newPrinter);
275 if (!printDevice.isValid())
276 return;
277 m_printDevice.swap(printDevice);
278 printerName = m_printDevice.id();
279
280 // Check if new printer supports current settings, otherwise us defaults
281 if (duplex != QPrint::DuplexAuto && !m_printDevice.supportedDuplexModes().contains(duplex))
282 duplex = m_printDevice.defaultDuplexMode();
283 QPrint::ColorMode colorMode = grayscale ? QPrint::GrayScale : QPrint::Color;
284 if (!m_printDevice.supportedColorModes().contains(colorMode))
285 grayscale = m_printDevice.defaultColorMode() == QPrint::GrayScale;
286
287 // Get the equivalent page size for this printer as supported names may be different
288 if (m_printDevice.supportedPageSize(m_pageLayout.pageSize()).isValid())
289 setPageSize(m_pageLayout.pageSize());
290 else
291 setPageSize(QPageSize(m_pageLayout.pageSize().size(QPageSize::Point), QPageSize::Point));
292}
293
294void QCupsPrintEnginePrivate::setPageSize(const QPageSize &pageSize)
295{
296 if (pageSize.isValid()) {
297 // Find if the requested page size has a matching printer page size, if so use its defined name instead
298 QPageSize printerPageSize = m_printDevice.supportedPageSize(pageSize);
299 QPageSize usePageSize = printerPageSize.isValid() ? printerPageSize : pageSize;
300 QMarginsF printable = m_printDevice.printableMargins(usePageSize, m_pageLayout.orientation(), resolution);
301 m_pageLayout.setPageSize(usePageSize, qt_convertMargins(printable, QPageLayout::Point, m_pageLayout.units()));
302 }
303}
304
305QT_END_NAMESPACE
306