| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtGui module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qcupsprintengine_p.h" |
| |
| #include <qpa/qplatformprintplugin.h> |
| #include <qpa/qplatformprintersupport.h> |
| |
| #include <qiodevice.h> |
| #include <qfile.h> |
| #include <qdebug.h> |
| #include <qbuffer.h> |
| #include "private/qcups_p.h" // Only needed for PPK_CupsOptions |
| #include <QtGui/qpagelayout.h> |
| |
| #include <cups/cups.h> |
| |
| #include "private/qcore_unix_p.h" // overrides QT_OPEN |
| |
| QT_BEGIN_NAMESPACE |
| |
| extern QMarginsF qt_convertMargins(const QMarginsF &margins, QPageLayout::Unit fromUnits, QPageLayout::Unit toUnits); |
| |
| QCupsPrintEngine::QCupsPrintEngine(QPrinter::PrinterMode m, const QString &deviceId) |
| : QPdfPrintEngine(*new QCupsPrintEnginePrivate(m)) |
| { |
| Q_D(QCupsPrintEngine); |
| d->changePrinter(deviceId); |
| state = QPrinter::Idle; |
| } |
| |
| QCupsPrintEngine::~QCupsPrintEngine() |
| { |
| } |
| |
| void QCupsPrintEngine::setProperty(PrintEnginePropertyKey key, const QVariant &value) |
| { |
| Q_D(QCupsPrintEngine); |
| |
| switch (int(key)) { |
| case PPK_PageSize: |
| d->setPageSize(QPageSize(QPageSize::PageSizeId(value.toInt()))); |
| break; |
| case PPK_WindowsPageSize: |
| d->setPageSize(QPageSize(QPageSize::id(value.toInt()))); |
| break; |
| case PPK_CustomPaperSize: |
| d->setPageSize(QPageSize(value.toSizeF(), QPageSize::Point)); |
| break; |
| case PPK_PaperName: |
| // Get the named page size from the printer if supported |
| d->setPageSize(d->m_printDevice.supportedPageSize(value.toString())); |
| break; |
| case PPK_Duplex: { |
| QPrint::DuplexMode mode = QPrint::DuplexMode(value.toInt()); |
| if (mode != d->duplex && d->m_printDevice.supportedDuplexModes().contains(mode)) |
| d->duplex = mode; |
| break; |
| } |
| case PPK_PrinterName: |
| d->changePrinter(value.toString()); |
| break; |
| case PPK_CupsOptions: |
| d->cupsOptions = value.toStringList(); |
| break; |
| case PPK_QPageSize: |
| d->setPageSize(qvariant_cast<QPageSize>(value)); |
| break; |
| case PPK_QPageLayout: { |
| QPageLayout pageLayout = qvariant_cast<QPageLayout>(value); |
| if (pageLayout.isValid() && (d->m_printDevice.isValidPageLayout(pageLayout, d->resolution) |
| || d->m_printDevice.supportsCustomPageSizes() |
| || d->m_printDevice.supportedPageSizes().isEmpty())) { |
| // supportedPageSizes().isEmpty() because QPageSetupWidget::initPageSizes says |
| // "If no available printer page sizes, populate with all page sizes" |
| d->m_pageLayout = pageLayout; |
| d->setPageSize(pageLayout.pageSize()); |
| } |
| break; |
| } |
| default: |
| QPdfPrintEngine::setProperty(key, value); |
| break; |
| } |
| } |
| |
| QVariant QCupsPrintEngine::property(PrintEnginePropertyKey key) const |
| { |
| Q_D(const QCupsPrintEngine); |
| |
| QVariant ret; |
| switch (int(key)) { |
| case PPK_SupportsMultipleCopies: |
| // CUPS server always supports multiple copies, even if individual m_printDevice doesn't |
| ret = true; |
| break; |
| case PPK_NumberOfCopies: |
| ret = 1; |
| break; |
| case PPK_CupsOptions: |
| ret = d->cupsOptions; |
| break; |
| case PPK_Duplex: |
| ret = d->duplex; |
| break; |
| default: |
| ret = QPdfPrintEngine::property(key); |
| break; |
| } |
| return ret; |
| } |
| |
| |
| QCupsPrintEnginePrivate::QCupsPrintEnginePrivate(QPrinter::PrinterMode m) |
| : QPdfPrintEnginePrivate(m) |
| , duplex(QPrint::DuplexNone) |
| { |
| } |
| |
| QCupsPrintEnginePrivate::~QCupsPrintEnginePrivate() |
| { |
| } |
| |
| bool QCupsPrintEnginePrivate::openPrintDevice() |
| { |
| if (outDevice) |
| return false; |
| |
| if (!outputFileName.isEmpty()) { |
| QFile *file = new QFile(outputFileName); |
| if (! file->open(QFile::WriteOnly|QFile::Truncate)) { |
| delete file; |
| return false; |
| } |
| outDevice = file; |
| } else { |
| char filename[512]; |
| fd = cupsTempFd(filename, 512); |
| if (fd < 0) { |
| qWarning("QPdfPrinter: Could not open temporary file to print"); |
| return false; |
| } |
| cupsTempFile = QString::fromLocal8Bit(filename); |
| outDevice = new QFile(); |
| static_cast<QFile *>(outDevice)->open(fd, QIODevice::WriteOnly); |
| } |
| |
| return true; |
| } |
| |
| void QCupsPrintEnginePrivate::closePrintDevice() |
| { |
| QPdfPrintEnginePrivate::closePrintDevice(); |
| |
| if (!cupsTempFile.isEmpty()) { |
| QString tempFile = cupsTempFile; |
| cupsTempFile.clear(); |
| |
| // Should never have got here without a printer, but check anyway |
| if (printerName.isEmpty()) { |
| qWarning("Could not determine printer to print to"); |
| QFile::remove(tempFile); |
| return; |
| } |
| |
| // Set up print options. |
| QList<QPair<QByteArray, QByteArray> > options; |
| QVector<cups_option_t> cupsOptStruct; |
| |
| options.append(QPair<QByteArray, QByteArray>("media", m_pageLayout.pageSize().key().toLocal8Bit())); |
| |
| if (copies > 1) |
| options.append(QPair<QByteArray, QByteArray>("copies", QString::number(copies).toLocal8Bit())); |
| |
| if (copies > 1 && collate) |
| options.append(QPair<QByteArray, QByteArray>("Collate", "True")); |
| |
| switch (duplex) { |
| case QPrint::DuplexNone: |
| options.append(QPair<QByteArray, QByteArray>("sides", "one-sided")); |
| break; |
| case QPrint::DuplexAuto: |
| if (m_pageLayout.orientation() == QPageLayout::Portrait) |
| options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-long-edge")); |
| else |
| options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-short-edge")); |
| break; |
| case QPrint::DuplexLongSide: |
| options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-long-edge")); |
| break; |
| case QPrint::DuplexShortSide: |
| options.append(QPair<QByteArray, QByteArray>("sides", "two-sided-short-edge")); |
| break; |
| } |
| |
| if (m_pageLayout.orientation() == QPageLayout::Landscape) |
| options.append(QPair<QByteArray, QByteArray>("landscape", "")); |
| |
| QStringList::const_iterator it = cupsOptions.constBegin(); |
| while (it != cupsOptions.constEnd()) { |
| options.append(QPair<QByteArray, QByteArray>((*it).toLocal8Bit(), (*(it+1)).toLocal8Bit())); |
| it += 2; |
| } |
| |
| const int numOptions = options.size(); |
| cupsOptStruct.reserve(numOptions); |
| for (int c = 0; c < numOptions; ++c) { |
| cups_option_t opt; |
| opt.name = options[c].first.data(); |
| opt.value = options[c].second.data(); |
| cupsOptStruct.append(opt); |
| } |
| |
| // Print the file |
| // Cups expect the printer original name without instance, the full name is used only to retrieve the configuration |
| const auto parts = printerName.splitRef(QLatin1Char('/')); |
| const auto printerOriginalName = parts.at(0); |
| cups_option_t* optPtr = cupsOptStruct.size() ? &cupsOptStruct.first() : 0; |
| cupsPrintFile(printerOriginalName.toLocal8Bit().constData(), tempFile.toLocal8Bit().constData(), |
| title.toLocal8Bit().constData(), cupsOptStruct.size(), optPtr); |
| |
| QFile::remove(tempFile); |
| } |
| } |
| |
| void QCupsPrintEnginePrivate::changePrinter(const QString &newPrinter) |
| { |
| // Don't waste time if same printer name |
| if (newPrinter == printerName) |
| return; |
| |
| // Should never have reached here if no plugin available, but check just in case |
| QPlatformPrinterSupport *ps = QPlatformPrinterSupportPlugin::get(); |
| if (!ps) |
| return; |
| |
| // Try create the printer, only use it if it returns valid |
| QPrintDevice printDevice = ps->createPrintDevice(newPrinter); |
| if (!printDevice.isValid()) |
| return; |
| m_printDevice.swap(printDevice); |
| printerName = m_printDevice.id(); |
| |
| // Check if new printer supports current settings, otherwise us defaults |
| if (duplex != QPrint::DuplexAuto && !m_printDevice.supportedDuplexModes().contains(duplex)) |
| duplex = m_printDevice.defaultDuplexMode(); |
| QPrint::ColorMode colorMode = grayscale ? QPrint::GrayScale : QPrint::Color; |
| if (!m_printDevice.supportedColorModes().contains(colorMode)) |
| grayscale = m_printDevice.defaultColorMode() == QPrint::GrayScale; |
| |
| // Get the equivalent page size for this printer as supported names may be different |
| if (m_printDevice.supportedPageSize(m_pageLayout.pageSize()).isValid()) |
| setPageSize(m_pageLayout.pageSize()); |
| else |
| setPageSize(QPageSize(m_pageLayout.pageSize().size(QPageSize::Point), QPageSize::Point)); |
| } |
| |
| void QCupsPrintEnginePrivate::setPageSize(const QPageSize &pageSize) |
| { |
| if (pageSize.isValid()) { |
| // Find if the requested page size has a matching printer page size, if so use its defined name instead |
| QPageSize printerPageSize = m_printDevice.supportedPageSize(pageSize); |
| QPageSize usePageSize = printerPageSize.isValid() ? printerPageSize : pageSize; |
| QMarginsF printable = m_printDevice.printableMargins(usePageSize, m_pageLayout.orientation(), resolution); |
| m_pageLayout.setPageSize(usePageSize, qt_convertMargins(printable, QPageLayout::Point, m_pageLayout.units())); |
| } |
| } |
| |
| QT_END_NAMESPACE |