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 "qplatformdefs.h"
41 #include <QtPrintSupport/private/qtprintsupportglobal_p.h>
42 
43 #include "private/qabstractprintdialog_p.h"
44 #if QT_CONFIG(messagebox)
45 #include <QtWidgets/qmessagebox.h>
46 #endif
47 #include "qprintdialog.h"
48 #if QT_CONFIG(filedialog)
49 #include "qfiledialog.h"
50 #endif
51 #include <QtCore/qdebug.h>
52 #include <QtCore/qdir.h>
53 #include <QtCore/qglobal.h>
54 #include <QtCore/qtextcodec.h>
55 #include <QtGui/qevent.h>
56 #if QT_CONFIG(filesystemmodel)
57 #include <QtWidgets/qfilesystemmodel.h>
58 #endif
59 #include <QtWidgets/qstyleditemdelegate.h>
60 #include <QtWidgets/qformlayout.h>
61 #include <QtPrintSupport/qprinter.h>
62 
63 #include <qpa/qplatformprintplugin.h>
64 #include <qpa/qplatformprintersupport.h>
65 
66 #include <private/qprintdevice_p.h>
67 
68 #include <QtWidgets/qdialogbuttonbox.h>
69 
70 #if QT_CONFIG(completer)
71 #include <private/qcompleter_p.h>
72 #endif
73 #include "ui_qprintpropertieswidget.h"
74 #include "ui_qprintsettingsoutput.h"
75 #include "ui_qprintwidget.h"
76 
77 #if QT_CONFIG(cups)
Q_DECLARE_METATYPE(const ppd_option_t *)78 Q_DECLARE_METATYPE(const ppd_option_t *)
79 #include <private/qcups_p.h>
80 #if QT_CONFIG(cupsjobwidget)
81 #include "qcupsjobwidget_p.h"
82 #endif
83 #endif
84 
85 /*
86 
87 Print dialog class declarations
88 
89     QPrintDialog:            The main Print Dialog, nothing really held here.
90 
91     QUnixPrintWidget:
92     QUnixPrintWidgetPrivate: The real Unix Print Dialog implementation.
93 
94                              Directly includes the upper half of the Print Dialog
95                              containing the Printer Selection widgets and
96                              Properties button.
97 
98                              Embeds the Properties pop-up dialog from
99                              QPrintPropertiesDialog
100 
101                              Embeds the lower half from separate widget class
102                              QPrintDialogPrivate
103 
104                              Layout in qprintwidget.ui
105 
106     QPrintDialogPrivate:     The lower half of the Print Dialog containing the
107                              Copies and Options tabs that expands when the
108                              Options button is selected.
109 
110                              Layout in qprintsettingsoutput.ui
111 
112     QPrintPropertiesDialog:  Dialog displayed when clicking on Properties button to
113                              allow editing of Page and Advanced tabs.
114 
115                              Layout in qprintpropertieswidget.ui
116 */
117 
118 static void initResources()
119 {
120     Q_INIT_RESOURCE(qprintdialog);
121 }
122 
123 QT_BEGIN_NAMESPACE
124 
125 class QPrintPropertiesDialog : public QDialog
126 {
127     Q_OBJECT
128 public:
129     QPrintPropertiesDialog(QPrinter *printer, QPrintDevice *currentPrintDevice,
130                            QPrinter::OutputFormat outputFormat, const QString &printerName,
131                            QAbstractPrintDialog *parent);
132     ~QPrintPropertiesDialog();
133 
134     void setupPrinter() const;
135 
136 private slots:
137     void reject() override;
138     void accept() override;
139 
140 private:
141     void showEvent(QShowEvent *event) override;
142 
143     friend class QUnixPrintWidgetPrivate;
144 #if QT_CONFIG(cups)
145     QPrinter *m_printer;
146 #endif
147     Ui::QPrintPropertiesWidget widget;
148     QDialogButtonBox *m_buttons;
149 #if QT_CONFIG(cupsjobwidget)
150     QCupsJobWidget *m_jobOptions;
151 #endif
152 
153 #if QT_CONFIG(cups)
154     bool createAdvancedOptionsWidget();
155     void setPrinterAdvancedCupsOptions() const;
156     void revertAdvancedOptionsToSavedValues() const;
157     void advancedOptionsUpdateSavedValues() const;
158     bool anyPpdOptionConflict() const;
159     bool anyAdvancedOptionConflict() const;
160 
161     QPrintDevice *m_currentPrintDevice;
162     QTextCodec *m_cupsCodec = nullptr;
163     QVector<QComboBox*> m_advancedOptionsCombos;
164 #endif
165 };
166 
167 class QUnixPrintWidgetPrivate;
168 
169 class QUnixPrintWidget : public QWidget
170 {
171     Q_OBJECT
172 
173 public:
174     explicit QUnixPrintWidget(QPrinter *printer, QWidget *parent = nullptr);
175     ~QUnixPrintWidget();
176     void updatePrinter();
177 
178 private:
179     friend class QPrintDialog;
180     friend class QPrintDialogPrivate;
181     friend class QUnixPrintWidgetPrivate;
182     QUnixPrintWidgetPrivate *d;
183     Q_PRIVATE_SLOT(d, void _q_printerChanged(int))
184     Q_PRIVATE_SLOT(d, void _q_btnBrowseClicked())
185     Q_PRIVATE_SLOT(d, void _q_btnPropertiesClicked())
186 };
187 
188 class QUnixPrintWidgetPrivate
189 {
190 public:
191     QUnixPrintWidgetPrivate(QUnixPrintWidget *q, QPrinter *prn);
192     ~QUnixPrintWidgetPrivate();
193 
194     bool checkFields();
195     void setupPrinter();
196     void setOptionsPane(QPrintDialogPrivate *pane);
197     void setupPrinterProperties();
198 // slots
199     void _q_printerChanged(int index);
200     void _q_btnPropertiesClicked();
201     void _q_btnBrowseClicked();
202 
203     QUnixPrintWidget * const parent;
204     QPrintPropertiesDialog *propertiesDialog;
205     Ui::QPrintWidget widget;
206     QAbstractPrintDialog * q;
207     QPrinter *printer;
208     QPrintDevice m_currentPrintDevice;
209 
210     void updateWidget();
211 
212 #if QT_CONFIG(cups)
213     void setPpdDuplex(QPrinter::DuplexMode mode);
214     ppd_option_t *m_duplexPpdOption;
215 #endif
216 
217 private:
218     QPrintDialogPrivate *optionsPane;
219     bool filePrintersAdded;
220 };
221 
222 class QPrintDialogPrivate : public QAbstractPrintDialogPrivate
223 {
224     Q_DECLARE_PUBLIC(QPrintDialog)
225     Q_DECLARE_TR_FUNCTIONS(QPrintDialog)
226 public:
227     QPrintDialogPrivate();
228     ~QPrintDialogPrivate();
229 
230     void init();
231 
232     void selectPrinter(const QPrinter::OutputFormat outputFormat);
233 
234     void _q_togglePageSetCombo(bool);
235 #if QT_CONFIG(messagebox)
236     void _q_checkFields();
237 #endif
238     void _q_collapseOrExpandDialog();
239 
240 #if QT_CONFIG(cups)
241     void updatePpdDuplexOption(QRadioButton *radio);
242 #endif
243     void setupPrinter();
244     void updateWidgets();
245 
246     virtual void setTabs(const QList<QWidget*> &tabs) override;
247 
248     Ui::QPrintSettingsOutput options;
249     QUnixPrintWidget *top;
250     QWidget *bottom;
251     QDialogButtonBox *buttons;
252     QPushButton *collapseButton;
253     QPrinter::OutputFormat printerOutputFormat;
254 private:
255     void setExplicitDuplexMode(QPrint::DuplexMode duplexMode);
256     // duplex mode explicitly set by user, QPrint::DuplexAuto otherwise
257     QPrint::DuplexMode explicitDuplexMode;
258 };
259 
260 ////////////////////////////////////////////////////////////////////////////////
261 ////////////////////////////////////////////////////////////////////////////////
262 
263 /*
264 
265     QPrintPropertiesDialog
266 
267     Dialog displayed when clicking on Properties button to allow editing of Page
268     and Advanced tabs.
269 
270 */
271 
QPrintPropertiesDialog(QPrinter * printer,QPrintDevice * currentPrintDevice,QPrinter::OutputFormat outputFormat,const QString & printerName,QAbstractPrintDialog * parent)272 QPrintPropertiesDialog::QPrintPropertiesDialog(QPrinter *printer, QPrintDevice *currentPrintDevice,
273                                                QPrinter::OutputFormat outputFormat, const QString &printerName,
274                                                QAbstractPrintDialog *parent)
275     : QDialog(parent)
276 #if QT_CONFIG(cups)
277     , m_printer(printer)
278 #endif
279 {
280     setWindowTitle(tr("Printer Properties"));
281     QVBoxLayout *lay = new QVBoxLayout(this);
282     QWidget *content = new QWidget(this);
283     widget.setupUi(content);
284     m_buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this);
285     lay->addWidget(content);
286     lay->addWidget(m_buttons);
287 
288     connect(m_buttons->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, &QPrintPropertiesDialog::accept);
289     connect(m_buttons->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &QPrintPropertiesDialog::reject);
290 
291     widget.pageSetup->setPrinter(printer, currentPrintDevice, outputFormat, printerName);
292 
293 #if QT_CONFIG(cupsjobwidget)
294     m_jobOptions = new QCupsJobWidget(printer, currentPrintDevice);
295     widget.tabs->insertTab(1, m_jobOptions, tr("Job Options"));
296 #endif
297 
298     const int advancedTabIndex = widget.tabs->indexOf(widget.cupsPropertiesPage);
299 #if QT_CONFIG(cups)
300     m_currentPrintDevice = currentPrintDevice;
301     const bool anyWidgetCreated = createAdvancedOptionsWidget();
302 
303     widget.tabs->setTabEnabled(advancedTabIndex, anyWidgetCreated);
304 
305     connect(widget.pageSetup, &QPageSetupWidget::ppdOptionChanged, this, [this] {
306         widget.conflictsLabel->setVisible(anyPpdOptionConflict());
307     });
308 
309 #else
310     Q_UNUSED(currentPrintDevice)
311     widget.tabs->setTabEnabled(advancedTabIndex, false);
312 #endif
313 }
314 
~QPrintPropertiesDialog()315 QPrintPropertiesDialog::~QPrintPropertiesDialog()
316 {
317 }
318 
setupPrinter() const319 void QPrintPropertiesDialog::setupPrinter() const
320 {
321 #if QT_CONFIG(cups)
322     QCUPSSupport::clearCupsOptions(m_printer);
323 #endif
324 
325     widget.pageSetup->setupPrinter();
326 #if QT_CONFIG(cupsjobwidget)
327     m_jobOptions->setupPrinter();
328 #endif
329 
330 #if QT_CONFIG(cups)
331     // Set Color by default, that will change if the "ColorModel" property is available
332     m_printer->setColorMode(QPrinter::Color);
333 
334     setPrinterAdvancedCupsOptions();
335 #endif
336 }
337 
reject()338 void QPrintPropertiesDialog::reject()
339 {
340     widget.pageSetup->revertToSavedValues();
341 
342 #if QT_CONFIG(cupsjobwidget)
343     m_jobOptions->revertToSavedValues();
344 #endif
345 
346 #if QT_CONFIG(cups)
347     revertAdvancedOptionsToSavedValues();
348 #endif
349     QDialog::reject();
350 }
351 
accept()352 void QPrintPropertiesDialog::accept()
353 {
354 #if QT_CONFIG(cups)
355     if (widget.pageSetup->hasPpdConflict()) {
356         widget.tabs->setCurrentWidget(widget.tabPage);
357         const QMessageBox::StandardButton answer = QMessageBox::warning(this, tr("Page Setup Conflicts"),
358                                                                         tr("There are conflicts in page setup options. Do you want to fix them?"),
359                                                                         QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
360         if (answer != QMessageBox::No)
361             return;
362     } else if (anyAdvancedOptionConflict()) {
363         widget.tabs->setCurrentWidget(widget.cupsPropertiesPage);
364         const QMessageBox::StandardButton answer = QMessageBox::warning(this, tr("Advanced Option Conflicts"),
365                                                                         tr("There are conflicts in some advanced options. Do you want to fix them?"),
366                                                                         QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
367         if (answer != QMessageBox::No)
368             return;
369     }
370     advancedOptionsUpdateSavedValues();
371 #endif
372 
373 #if QT_CONFIG(cupsjobwidget)
374     m_jobOptions->updateSavedValues();
375 #endif
376 
377     widget.pageSetup->updateSavedValues();
378 
379     QDialog::accept();
380 }
381 
showEvent(QShowEvent * event)382 void QPrintPropertiesDialog::showEvent(QShowEvent *event)
383 {
384 #if QT_CONFIG(cups)
385     widget.conflictsLabel->setVisible(anyPpdOptionConflict());
386 #endif
387     QDialog::showEvent(event);
388 }
389 
390 #if QT_CONFIG(cups)
391 
392 // Used to store the ppd_option_t for each QComboBox that represents an advanced option
393 static const char *ppdOptionProperty = "_q_ppd_option";
394 
395 // Used to store the originally selected choice index for each QComboBox that represents an advanced option
396 static const char *ppdOriginallySelectedChoiceProperty = "_q_ppd_originally_selected_choice";
397 
398 // Used to store the warning label pointer for each QComboBox that represents an advanced option
399 static const char *warningLabelProperty = "_q_warning_label";
400 
isBlacklistedGroup(const ppd_group_t * group)401 static bool isBlacklistedGroup(const ppd_group_t *group) noexcept
402 {
403     return qstrcmp(group->name, "InstallableOptions") == 0;
404 };
405 
isBlacklistedOption(const char * keyword)406 static bool isBlacklistedOption(const char *keyword) noexcept
407 {
408     // We already let the user set these options elsewhere
409     const char *cupsOptionBlacklist[] = {
410         "Collate",
411         "Copies",
412         "OutputOrder",
413         "PageRegion",
414         "PageSize",
415         "Duplex" // handled by the main dialog
416     };
417     auto equals = [](const char *keyword) {
418         return [keyword](const char *candidate) {
419             return qstrcmp(keyword, candidate) == 0;
420         };
421     };
422     return std::any_of(std::begin(cupsOptionBlacklist), std::end(cupsOptionBlacklist), equals(keyword));
423 };
424 
createAdvancedOptionsWidget()425 bool QPrintPropertiesDialog::createAdvancedOptionsWidget()
426 {
427     bool anyWidgetCreated = false;
428 
429     ppd_file_t *ppd = qvariant_cast<ppd_file_t*>(m_currentPrintDevice->property(PDPK_PpdFile));
430 
431     if (ppd) {
432         m_cupsCodec = QTextCodec::codecForName(ppd->lang_encoding);
433 
434         QWidget *holdingWidget = new QWidget();
435         QVBoxLayout *layout = new QVBoxLayout(holdingWidget);
436 
437         for (int i = 0; i < ppd->num_groups; ++i) {
438             const ppd_group_t *group = &ppd->groups[i];
439 
440             if (!isBlacklistedGroup(group)) {
441                 QFormLayout *groupLayout = new QFormLayout();
442 
443                 for (int i = 0; i < group->num_options; ++i) {
444                     const ppd_option_t *option = &group->options[i];
445 
446                     if (!isBlacklistedOption(option->keyword)) {
447                         QComboBox *choicesCb = new QComboBox();
448 
449                         const auto setPpdOptionFromCombo = [this, choicesCb, option] {
450                             // We can't use choicesCb->currentIndex() to know the index of the option in the choices[] array
451                             // because some of them may not be present in the list because they conflict with the
452                             // installable options so use the index passed on addItem
453                             const int selectedChoiceIndex = choicesCb->currentData().toInt();
454                             const auto values = QStringList{} << QString::fromLatin1(option->keyword)
455                                                                 << QString::fromLatin1(option->choices[selectedChoiceIndex].choice);
456                             m_currentPrintDevice->setProperty(PDPK_PpdOption, values);
457                             widget.conflictsLabel->setVisible(anyPpdOptionConflict());
458                         };
459 
460                         bool foundMarkedChoice = false;
461                         bool markedChoiceNotAvailable = false;
462                         for (int i = 0; i < option->num_choices; ++i) {
463                             const ppd_choice_t *choice = &option->choices[i];
464                             const auto values = QStringList{} << QString::fromLatin1(option->keyword) << QString::fromLatin1(choice->choice);
465                             const bool choiceIsInstallableConflict = m_currentPrintDevice->isFeatureAvailable(PDPK_PpdChoiceIsInstallableConflict, values);
466                             if (choiceIsInstallableConflict && static_cast<int>(choice->marked) == 1) {
467                                 markedChoiceNotAvailable = true;
468                             } else if (!choiceIsInstallableConflict) {
469                                 choicesCb->addItem(m_cupsCodec->toUnicode(choice->text), i);
470                                 if (static_cast<int>(choice->marked) == 1) {
471                                     choicesCb->setCurrentIndex(choicesCb->count() - 1);
472                                     choicesCb->setProperty(ppdOriginallySelectedChoiceProperty, QVariant(i));
473                                     foundMarkedChoice = true;
474                                 } else if (!foundMarkedChoice && qstrcmp(choice->choice, option->defchoice) == 0) {
475                                     choicesCb->setCurrentIndex(choicesCb->count() - 1);
476                                     choicesCb->setProperty(ppdOriginallySelectedChoiceProperty, QVariant(i));
477                                 }
478                             }
479                         }
480 
481                         if (markedChoiceNotAvailable) {
482                             // If the user default option is not available because of it conflicting with
483                             // the installed options, we need to set the internal ppd value to the value
484                             // being shown in the combo
485                             setPpdOptionFromCombo();
486                         }
487 
488                         if (choicesCb->count() > 1) {
489 
490                             connect(choicesCb, QOverload<int>::of(&QComboBox::currentIndexChanged), this, setPpdOptionFromCombo);
491 
492                             // We need an extra label at the end to show the conflict warning
493                             QWidget *choicesCbWithLabel = new QWidget();
494                             QHBoxLayout *choicesCbWithLabelLayout = new QHBoxLayout(choicesCbWithLabel);
495                             choicesCbWithLabelLayout->setContentsMargins(0, 0, 0, 0);
496                             QLabel *warningLabel = new QLabel();
497                             choicesCbWithLabelLayout->addWidget(choicesCb);
498                             choicesCbWithLabelLayout->addWidget(warningLabel);
499 
500                             QLabel *optionLabel = new QLabel(m_cupsCodec->toUnicode(option->text));
501                             groupLayout->addRow(optionLabel, choicesCbWithLabel);
502                             anyWidgetCreated = true;
503                             choicesCb->setProperty(ppdOptionProperty, QVariant::fromValue(option));
504                             choicesCb->setProperty(warningLabelProperty, QVariant::fromValue(warningLabel));
505                             m_advancedOptionsCombos << choicesCb;
506                         } else {
507                             delete choicesCb;
508                         }
509                     }
510                 }
511 
512                 if (groupLayout->rowCount() > 0) {
513                     QGroupBox *groupBox = new QGroupBox(m_cupsCodec->toUnicode(group->text));
514                     groupBox->setLayout(groupLayout);
515                     layout->addWidget(groupBox);
516                 } else {
517                     delete groupLayout;
518                 }
519             }
520         }
521 
522         layout->addStretch();
523         widget.scrollArea->setWidget(holdingWidget);
524     }
525 
526     if (!m_cupsCodec)
527         m_cupsCodec = QTextCodec::codecForLocale();
528 
529     return anyWidgetCreated;
530 }
531 
setPrinterAdvancedCupsOptions() const532 void QPrintPropertiesDialog::setPrinterAdvancedCupsOptions() const
533 {
534     for (const QComboBox *choicesCb : m_advancedOptionsCombos) {
535         const ppd_option_t *option = qvariant_cast<const ppd_option_t *>(choicesCb->property(ppdOptionProperty));
536 
537         // We can't use choicesCb->currentIndex() to know the index of the option in the choices[] array
538         // because some of them may not be present in the list because they conflict with the
539         // installable options so use the index passed on addItem
540         const int selectedChoiceIndex = choicesCb->currentData().toInt();
541         const char *selectedChoice = option->choices[selectedChoiceIndex].choice;
542 
543         if (qstrcmp(option->keyword, "ColorModel") == 0)
544             m_printer->setColorMode(qstrcmp(selectedChoice, "Gray") == 0 ? QPrinter::GrayScale : QPrinter::Color);
545 
546         if (qstrcmp(option->defchoice, selectedChoice) != 0)
547             QCUPSSupport::setCupsOption(m_printer, QString::fromLatin1(option->keyword), QString::fromLatin1(selectedChoice));
548     }
549 }
550 
revertAdvancedOptionsToSavedValues() const551 void QPrintPropertiesDialog::revertAdvancedOptionsToSavedValues() const
552 {
553     for (QComboBox *choicesCb : m_advancedOptionsCombos) {
554         const int originallySelectedChoice = qvariant_cast<int>(choicesCb->property(ppdOriginallySelectedChoiceProperty));
555         const int newComboIndexToSelect = choicesCb->findData(originallySelectedChoice);
556         choicesCb->setCurrentIndex(newComboIndexToSelect);
557         // The currentIndexChanged lambda takes care of resetting the ppd option
558     }
559     widget.conflictsLabel->setVisible(anyPpdOptionConflict());
560 }
561 
advancedOptionsUpdateSavedValues() const562 void QPrintPropertiesDialog::advancedOptionsUpdateSavedValues() const
563 {
564     for (QComboBox *choicesCb : m_advancedOptionsCombos)
565         choicesCb->setProperty(ppdOriginallySelectedChoiceProperty, choicesCb->currentData());
566 }
567 
anyPpdOptionConflict() const568 bool QPrintPropertiesDialog::anyPpdOptionConflict() const
569 {
570     // we need to execute both since besides returning true/false they update the warning icons
571     const bool pageSetupConflicts = widget.pageSetup->hasPpdConflict();
572     const bool advancedOptionConflicts = anyAdvancedOptionConflict();
573     return pageSetupConflicts || advancedOptionConflicts;
574 }
575 
anyAdvancedOptionConflict() const576 bool QPrintPropertiesDialog::anyAdvancedOptionConflict() const
577 {
578     const QIcon warning = QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning, nullptr, nullptr);
579 
580     bool anyConflicted = false;
581 
582     for (const QComboBox *choicesCb : m_advancedOptionsCombos) {
583         const ppd_option_t *option = qvariant_cast<const ppd_option_t *>(choicesCb->property(ppdOptionProperty));
584         QLabel *warningLabel = qvariant_cast<QLabel *>(choicesCb->property(warningLabelProperty));
585         if (option->conflicted) {
586             anyConflicted = true;
587             const int pixmap_size = choicesCb->sizeHint().height() * .75;
588             warningLabel->setPixmap(warning.pixmap(pixmap_size, pixmap_size));
589         } else {
590             warningLabel->setPixmap(QPixmap());
591         }
592     }
593 
594     return anyConflicted;
595 }
596 
597 #endif
598 
599 
600 ////////////////////////////////////////////////////////////////////////////////
601 ////////////////////////////////////////////////////////////////////////////////
602 
603 /*
604 
605     QPrintDialogPrivate
606 
607     The lower half of the Print Dialog containing the Copies and Options
608     tabs that expands when the Options button is selected.
609 
610 */
QPrintDialogPrivate()611 QPrintDialogPrivate::QPrintDialogPrivate()
612     : top(nullptr), bottom(nullptr), buttons(nullptr), collapseButton(nullptr),
613       explicitDuplexMode(QPrint::DuplexAuto)
614 {
615     initResources();
616 }
617 
~QPrintDialogPrivate()618 QPrintDialogPrivate::~QPrintDialogPrivate()
619 {
620 }
621 
init()622 void QPrintDialogPrivate::init()
623 {
624     Q_Q(QPrintDialog);
625 
626     top = new QUnixPrintWidget(q->printer(), q);
627     bottom = new QWidget(q);
628     options.setupUi(bottom);
629     options.color->setIconSize(QSize(32, 32));
630     options.color->setIcon(QIcon(QLatin1String(":/qt-project.org/dialogs/qprintdialog/images/status-color.png")));
631     options.grayscale->setIconSize(QSize(32, 32));
632     options.grayscale->setIcon(QIcon(QLatin1String(":/qt-project.org/dialogs/qprintdialog/images/status-gray-scale.png")));
633 
634 #if QT_CONFIG(cups)
635     // Add Page Set widget if CUPS is available
636     options.pageSetCombo->addItem(tr("All Pages"), QVariant::fromValue(QCUPSSupport::AllPages));
637     options.pageSetCombo->addItem(tr("Odd Pages"), QVariant::fromValue(QCUPSSupport::OddPages));
638     options.pageSetCombo->addItem(tr("Even Pages"), QVariant::fromValue(QCUPSSupport::EvenPages));
639 #else
640     delete options.pagesRadioButton;
641     delete options.pagesLineEdit;
642     options.pagesRadioButton = nullptr;
643     options.pagesLineEdit = nullptr;
644 #endif
645 
646     top->d->setOptionsPane(this);
647 
648     buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, q);
649     collapseButton = new QPushButton(QPrintDialog::tr("&Options >>"), buttons);
650     buttons->addButton(collapseButton, QDialogButtonBox::ResetRole);
651     bottom->setVisible(false);
652 
653     QPushButton *printButton = buttons->button(QDialogButtonBox::Ok);
654     printButton->setText(QPrintDialog::tr("&Print"));
655     printButton->setDefault(true);
656 
657     QVBoxLayout *lay = new QVBoxLayout(q);
658     lay->addWidget(top);
659     lay->addWidget(bottom);
660     lay->addWidget(buttons);
661 
662 #if !QT_CONFIG(messagebox)
663     QObject::connect(buttons, SIGNAL(accepted()), q, SLOT(accept()));
664 #else
665     QObject::connect(buttons, SIGNAL(accepted()), q, SLOT(_q_checkFields()));
666 #endif
667     QObject::connect(buttons, SIGNAL(rejected()), q, SLOT(reject()));
668 
669     QObject::connect(options.printSelection, SIGNAL(toggled(bool)),
670                      q, SLOT(_q_togglePageSetCombo(bool)));
671 
672     QObject::connect(options.printCurrentPage, SIGNAL(toggled(bool)),
673                      q, SLOT(_q_togglePageSetCombo(bool)));
674 
675     QObject::connect(collapseButton, SIGNAL(released()), q, SLOT(_q_collapseOrExpandDialog()));
676 
677     QObject::connect(options.noDuplex, &QAbstractButton::clicked, q, [this] { setExplicitDuplexMode(QPrint::DuplexNone); });
678     QObject::connect(options.duplexLong, &QAbstractButton::clicked, q, [this] { setExplicitDuplexMode(QPrint::DuplexLongSide); });
679     QObject::connect(options.duplexShort, &QAbstractButton::clicked, q, [this] { setExplicitDuplexMode(QPrint::DuplexShortSide); });
680 
681 #if QT_CONFIG(cups)
682     QObject::connect(options.noDuplex, &QAbstractButton::toggled, q, [this] { updatePpdDuplexOption(options.noDuplex); });
683     QObject::connect(options.duplexLong, &QAbstractButton::toggled, q, [this] { updatePpdDuplexOption(options.duplexLong); });
684     QObject::connect(options.duplexShort, &QAbstractButton::toggled, q, [this] { updatePpdDuplexOption(options.duplexShort); });
685 #endif
686 }
687 
688 // initialize printer options
selectPrinter(const QPrinter::OutputFormat outputFormat)689 void QPrintDialogPrivate::selectPrinter(const QPrinter::OutputFormat outputFormat)
690 {
691         Q_Q(QPrintDialog);
692         QPrinter *p = q->printer();
693         printerOutputFormat = outputFormat;
694 
695         // printer supports duplex mode?
696         const auto supportedDuplexMode = top->d->m_currentPrintDevice.supportedDuplexModes();
697         options.duplexLong->setEnabled(supportedDuplexMode.contains(QPrint::DuplexLongSide));
698         options.duplexShort->setEnabled(supportedDuplexMode.contains(QPrint::DuplexShortSide));
699 
700         if (p->colorMode() == QPrinter::Color)
701             options.color->setChecked(true);
702         else
703             options.grayscale->setChecked(true);
704 
705         // keep duplex value explicitly set by user, if any, and selected printer supports it;
706         // use device default otherwise
707         QPrint::DuplexMode duplex;
708         if (explicitDuplexMode != QPrint::DuplexAuto && supportedDuplexMode.contains(explicitDuplexMode))
709             duplex = explicitDuplexMode;
710         else
711             duplex = top->d->m_currentPrintDevice.defaultDuplexMode();
712         switch (duplex) {
713         case QPrint::DuplexNone:
714             options.noDuplex->setChecked(true); break;
715         case QPrint::DuplexLongSide:
716         case QPrint::DuplexAuto:
717             options.duplexLong->setChecked(true); break;
718         case QPrint::DuplexShortSide:
719             options.duplexShort->setChecked(true); break;
720         }
721         options.copies->setValue(p->copyCount());
722         options.collate->setChecked(p->collateCopies());
723         options.reverse->setChecked(p->pageOrder() == QPrinter::LastPageFirst);
724 
725         if (outputFormat == QPrinter::PdfFormat || options.printSelection->isChecked()
726             || options.printCurrentPage->isChecked())
727 
728             options.pageSetCombo->setEnabled(false);
729         else
730             options.pageSetCombo->setEnabled(true);
731 
732 #if QT_CONFIG(cups)
733         // Disable complex page ranges widget when printing to pdf
734         // It doesn't work since it relies on cups to do the heavy lifting and cups
735         // is not used when printing to PDF
736         options.pagesRadioButton->setEnabled(outputFormat != QPrinter::PdfFormat);
737 
738         // Disable color options on main dialog if not printing to file, it will be handled by CUPS advanced dialog
739         options.colorMode->setVisible(outputFormat == QPrinter::PdfFormat);
740 #endif
741 }
742 
743 #if QT_CONFIG(cups)
pageRangesFromString(const QString & pagesString)744 static std::vector<std::pair<int, int>> pageRangesFromString(const QString &pagesString) noexcept
745 {
746     std::vector<std::pair<int, int>> result;
747     const QStringList items = pagesString.split(',');
748     for (const QString &item : items) {
749         if (item.isEmpty())
750             return {};
751 
752         if (item.contains(QLatin1Char('-'))) {
753             const QStringList rangeItems = item.split('-');
754             if (rangeItems.count() != 2)
755                 return {};
756 
757             bool ok;
758             const int number1 = rangeItems[0].toInt(&ok);
759             if (!ok)
760                 return {};
761 
762             const int number2 = rangeItems[1].toInt(&ok);
763             if (!ok)
764                 return {};
765 
766             if (number1 < 1 || number2 < 1 || number2 < number1)
767                 return {};
768 
769             result.push_back(std::make_pair(number1, number2));
770 
771         } else {
772             bool ok;
773             const int number = item.toInt(&ok);
774             if (!ok)
775                 return {};
776 
777             if (number < 1)
778                 return {};
779 
780             result.push_back(std::make_pair(number, number));
781         }
782     }
783 
784     // check no range intersects with the next
785     std::sort(result.begin(), result.end(),
786               [](const std::pair<int, int> &it1, const std::pair<int, int> &it2) { return it1.first < it2.first; });
787     int previousSecond = -1;
788     for (auto pair : result) {
789         if (pair.first <= previousSecond)
790             return {};
791 
792         previousSecond = pair.second;
793     }
794 
795     return result;
796 }
797 
stringFromPageRanges(const std::vector<std::pair<int,int>> & pageRanges)798 static QString stringFromPageRanges(const std::vector<std::pair<int, int>> &pageRanges) noexcept
799 {
800     QString result;
801 
802     for (auto pair : pageRanges) {
803         if (!result.isEmpty())
804             result += QLatin1Char(',');
805 
806         if (pair.first == pair.second)
807             result += QString::number(pair.first);
808         else
809             result += QStringLiteral("%1-%2").arg(pair.first).arg(pair.second);
810     }
811 
812     return result;
813 }
814 
isValidPagesString(const QString & pagesString)815 static bool isValidPagesString(const QString &pagesString) noexcept
816 {
817     if (pagesString.isEmpty())
818         return false;
819 
820     auto pagesRanges = pageRangesFromString(pagesString);
821     return !pagesRanges.empty();
822 }
823 
updatePpdDuplexOption(QRadioButton * radio)824 void QPrintDialogPrivate::updatePpdDuplexOption(QRadioButton *radio)
825 {
826     const bool checked = radio->isChecked();
827     if (checked) {
828         if (radio == options.noDuplex) top->d->setPpdDuplex(QPrinter::DuplexNone);
829         else if (radio == options.duplexLong) top->d->setPpdDuplex(QPrinter::DuplexLongSide);
830         else if (radio == options.duplexShort) top->d->setPpdDuplex(QPrinter::DuplexShortSide);
831     }
832     const bool conflict = checked && top->d->m_duplexPpdOption && top->d->m_duplexPpdOption->conflicted;
833     radio->setIcon(conflict ? QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning, nullptr, nullptr) : QIcon());
834 }
835 
836 #endif
837 
setExplicitDuplexMode(const QPrint::DuplexMode duplexMode)838 void QPrintDialogPrivate::setExplicitDuplexMode(const QPrint::DuplexMode duplexMode)
839 {
840     explicitDuplexMode = duplexMode;
841 }
842 
setupPrinter()843 void QPrintDialogPrivate::setupPrinter()
844 {
845     // First setup the requested OutputFormat, Printer and Page Size first
846     top->d->setupPrinter();
847 
848     // Then setup Print Job options
849     Q_Q(QPrintDialog);
850     QPrinter* p = q->printer();
851 
852     if (options.duplex->isEnabled()) {
853         if (options.noDuplex->isChecked())
854             p->setDuplex(QPrinter::DuplexNone);
855         else if (options.duplexLong->isChecked())
856             p->setDuplex(QPrinter::DuplexLongSide);
857         else
858             p->setDuplex(QPrinter::DuplexShortSide);
859     }
860 
861 #if QT_CONFIG(cups)
862     // When printing to a device the colorMode will be set by the advanced panel
863     if (p->outputFormat() == QPrinter::PdfFormat)
864 #endif
865         p->setColorMode(options.color->isChecked() ? QPrinter::Color : QPrinter::GrayScale);
866 
867     p->setPageOrder(options.reverse->isChecked() ? QPrinter::LastPageFirst : QPrinter::FirstPageFirst);
868 
869     // print range
870     if (options.printAll->isChecked()) {
871         p->setPrintRange(QPrinter::AllPages);
872         p->setFromTo(0,0);
873     } else if (options.printSelection->isChecked()) {
874         p->setPrintRange(QPrinter::Selection);
875         p->setFromTo(0,0);
876     } else if (options.printCurrentPage->isChecked()) {
877         p->setPrintRange(QPrinter::CurrentPage);
878         p->setFromTo(0,0);
879     } else if (options.printRange->isChecked()) {
880         if (q->isOptionEnabled(QPrintDialog::PrintPageRange)) {
881             p->setPrintRange(QPrinter::PageRange);
882             p->setFromTo(options.from->value(), qMax(options.from->value(), options.to->value()));
883         } else {
884             // This case happens when CUPS server-side page range is enabled
885             // Setting the range to the printer occurs below
886             p->setPrintRange(QPrinter::AllPages);
887             p->setFromTo(0,0);
888         }
889     }
890 
891 #if QT_CONFIG(cups)
892     if (options.pagesRadioButton->isChecked()) {
893         auto pageRanges = pageRangesFromString(options.pagesLineEdit->text());
894 
895         p->setPrintRange(QPrinter::AllPages);
896         p->setFromTo(0, 0);
897 
898         // server-side page filtering
899         QCUPSSupport::setPageRange(p, stringFromPageRanges(pageRanges));
900     }
901 
902     // page set
903     if (p->printRange() == QPrinter::AllPages || p->printRange() == QPrinter::PageRange) {
904         //If the application is selecting pages and the first page number is even then need to adjust the odd-even accordingly
905         QCUPSSupport::PageSet pageSet = qvariant_cast<QCUPSSupport::PageSet>(options.pageSetCombo->itemData(options.pageSetCombo->currentIndex()));
906         if (q->isOptionEnabled(QPrintDialog::PrintPageRange)
907             && p->printRange() == QPrinter::PageRange
908             && (q->fromPage() % 2 == 0)) {
909 
910             switch (pageSet) {
911             case QCUPSSupport::AllPages:
912                 break;
913             case QCUPSSupport::OddPages:
914                 QCUPSSupport::setPageSet(p, QCUPSSupport::EvenPages);
915                 break;
916             case QCUPSSupport::EvenPages:
917                 QCUPSSupport::setPageSet(p, QCUPSSupport::OddPages);
918                 break;
919             }
920         } else if (pageSet != QCUPSSupport::AllPages) {
921             QCUPSSupport::setPageSet(p, pageSet);
922         }
923 
924         // server-side page range, since we set the page range on the printer to 0-0/AllPages above,
925         // we need to take the values directly from the widget as q->fromPage() will return 0
926         if (!q->isOptionEnabled(QPrintDialog::PrintPageRange) && options.printRange->isChecked())
927             QCUPSSupport::setPageRange(p, options.from->value(), qMax(options.from->value(), options.to->value()));
928     }
929 #endif
930 
931     // copies
932     p->setCopyCount(options.copies->value());
933     p->setCollateCopies(options.collate->isChecked());
934 }
935 
_q_togglePageSetCombo(bool checked)936 void QPrintDialogPrivate::_q_togglePageSetCombo(bool checked)
937 {
938     if (printerOutputFormat == QPrinter::PdfFormat)
939         return;
940 
941     options.pageSetCombo->setDisabled(checked);
942 }
943 
_q_collapseOrExpandDialog()944 void QPrintDialogPrivate::_q_collapseOrExpandDialog()
945 {
946     int collapseHeight = 0;
947     Q_Q(QPrintDialog);
948     QWidget *widgetToHide = bottom;
949     if (widgetToHide->isVisible()) {
950         collapseButton->setText(QPrintDialog::tr("&Options >>"));
951         collapseHeight = widgetToHide->y() + widgetToHide->height() - (top->y() + top->height());
952     }
953     else
954         collapseButton->setText(QPrintDialog::tr("&Options <<"));
955     widgetToHide->setVisible(! widgetToHide->isVisible());
956     if (! widgetToHide->isVisible()) { // make it shrink
957         q->layout()->activate();
958         q->resize( QSize(q->width(), q->height() - collapseHeight) );
959     }
960 }
961 
962 #if QT_CONFIG(messagebox)
_q_checkFields()963 void QPrintDialogPrivate::_q_checkFields()
964 {
965     Q_Q(QPrintDialog);
966     if (top->d->checkFields())
967         q->accept();
968 }
969 #endif // QT_CONFIG(messagebox)
970 
971 
updateWidgets()972 void QPrintDialogPrivate::updateWidgets()
973 {
974     Q_Q(QPrintDialog);
975     options.gbPrintRange->setVisible(q->isOptionEnabled(QPrintDialog::PrintPageRange) ||
976                                      q->isOptionEnabled(QPrintDialog::PrintSelection) ||
977                                      q->isOptionEnabled(QPrintDialog::PrintCurrentPage));
978 
979     options.printRange->setEnabled(q->isOptionEnabled(QPrintDialog::PrintPageRange));
980     options.printSelection->setVisible(q->isOptionEnabled(QPrintDialog::PrintSelection));
981     options.printCurrentPage->setVisible(q->isOptionEnabled(QPrintDialog::PrintCurrentPage));
982     options.collate->setVisible(q->isOptionEnabled(QPrintDialog::PrintCollateCopies));
983 
984 #if QT_CONFIG(cups)
985     // Don't display Page Set if only Selection or Current Page are enabled
986     if (!q->isOptionEnabled(QPrintDialog::PrintPageRange)
987         && (q->isOptionEnabled(QPrintDialog::PrintSelection) || q->isOptionEnabled(QPrintDialog::PrintCurrentPage))) {
988         options.pageSetCombo->setVisible(false);
989         options.pageSetLabel->setVisible(false);
990     } else {
991         options.pageSetCombo->setVisible(true);
992         options.pageSetLabel->setVisible(true);
993     }
994 
995     if (!q->isOptionEnabled(QPrintDialog::PrintPageRange)) {
996         // If we can do CUPS server side pages selection,
997         // display the page range widgets
998         options.gbPrintRange->setVisible(true);
999         options.printRange->setEnabled(true);
1000     }
1001 #endif
1002 
1003     switch (q->printRange()) {
1004     case QPrintDialog::AllPages:
1005         options.printAll->setChecked(true);
1006         options.pageSetCombo->setEnabled(true);
1007         break;
1008     case QPrintDialog::Selection:
1009         options.printSelection->setChecked(true);
1010         options.pageSetCombo->setEnabled(false);
1011         break;
1012     case QPrintDialog::PageRange:
1013         options.printRange->setChecked(true);
1014         options.pageSetCombo->setEnabled(true);
1015         break;
1016     case QPrintDialog::CurrentPage:
1017         if (q->isOptionEnabled(QPrintDialog::PrintCurrentPage)) {
1018             options.printCurrentPage->setChecked(true);
1019             options.pageSetCombo->setEnabled(false);
1020         }
1021         break;
1022     default:
1023         break;
1024     }
1025     const int minPage = qMax(1, qMin(q->minPage() , q->maxPage()));
1026     const int maxPage = qMax(1, q->maxPage() == INT_MAX ? 9999 : q->maxPage());
1027 
1028     options.from->setMinimum(minPage);
1029     options.to->setMinimum(minPage);
1030     options.from->setMaximum(maxPage);
1031     options.to->setMaximum(maxPage);
1032 
1033     options.from->setValue(q->fromPage());
1034     options.to->setValue(q->toPage());
1035     top->d->updateWidget();
1036 }
1037 
setTabs(const QList<QWidget * > & tabWidgets)1038 void QPrintDialogPrivate::setTabs(const QList<QWidget*> &tabWidgets)
1039 {
1040     QList<QWidget*>::ConstIterator iter = tabWidgets.begin();
1041     while(iter != tabWidgets.constEnd()) {
1042         QWidget *tab = *iter;
1043         options.tabs->addTab(tab, tab->windowTitle());
1044         ++iter;
1045     }
1046 }
1047 
1048 ////////////////////////////////////////////////////////////////////////////////
1049 ////////////////////////////////////////////////////////////////////////////////
1050 
1051 /*
1052 
1053     QPrintDialog
1054 
1055     The main Print Dialog.
1056 
1057 */
1058 
QPrintDialog(QPrinter * printer,QWidget * parent)1059 QPrintDialog::QPrintDialog(QPrinter *printer, QWidget *parent)
1060     : QAbstractPrintDialog(*(new QPrintDialogPrivate), printer, parent)
1061 {
1062     Q_D(QPrintDialog);
1063     d->init();
1064 }
1065 
1066 /*!
1067     Constructs a print dialog with the given \a parent.
1068 */
QPrintDialog(QWidget * parent)1069 QPrintDialog::QPrintDialog(QWidget *parent)
1070     : QAbstractPrintDialog(*(new QPrintDialogPrivate), nullptr, parent)
1071 {
1072     Q_D(QPrintDialog);
1073     d->init();
1074 }
1075 
~QPrintDialog()1076 QPrintDialog::~QPrintDialog()
1077 {
1078 }
1079 
setVisible(bool visible)1080 void QPrintDialog::setVisible(bool visible)
1081 {
1082     Q_D(QPrintDialog);
1083 
1084     if (visible)
1085         d->updateWidgets();
1086 
1087     QAbstractPrintDialog::setVisible(visible);
1088 }
1089 
exec()1090 int QPrintDialog::exec()
1091 {
1092     return QAbstractPrintDialog::exec();
1093 }
1094 
accept()1095 void QPrintDialog::accept()
1096 {
1097     Q_D(QPrintDialog);
1098 #if QT_CONFIG(cups)
1099     if (d->options.pagesRadioButton->isChecked() && !isValidPagesString(d->options.pagesLineEdit->text())) {
1100         QMessageBox::critical(this, tr("Invalid Pages Definition"),
1101                               tr("%1 does not follow the correct syntax. Please use ',' to separate "
1102                               "ranges and pages, '-' to define ranges and make sure ranges do "
1103                               "not intersect with each other.").arg(d->options.pagesLineEdit->text()),
1104                               QMessageBox::Ok, QMessageBox::Ok);
1105         return;
1106     }
1107     if (d->top->d->m_duplexPpdOption && d->top->d->m_duplexPpdOption->conflicted) {
1108         const QMessageBox::StandardButton answer = QMessageBox::warning(this, tr("Duplex Settings Conflicts"),
1109                                                                         tr("There are conflicts in duplex settings. Do you want to fix them?"),
1110                                                                         QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
1111         if (answer != QMessageBox::No)
1112             return;
1113     }
1114 #endif
1115     d->setupPrinter();
1116     QDialog::accept();
1117 }
1118 
1119 ////////////////////////////////////////////////////////////////////////////////
1120 ////////////////////////////////////////////////////////////////////////////////
1121 
1122 /*
1123 
1124     QUnixPrintWidget && QUnixPrintWidgetPrivate
1125 
1126     The upper half of the Print Dialog containing the Printer Selection widgets
1127 
1128 */
1129 
1130 #if defined (Q_OS_UNIX)
1131 
1132 /*! \internal
1133 */
QUnixPrintWidgetPrivate(QUnixPrintWidget * p,QPrinter * prn)1134 QUnixPrintWidgetPrivate::QUnixPrintWidgetPrivate(QUnixPrintWidget *p, QPrinter *prn)
1135     : parent(p), propertiesDialog(nullptr), printer(prn),
1136 #if QT_CONFIG(cups)
1137       m_duplexPpdOption(nullptr),
1138 #endif
1139       optionsPane(nullptr), filePrintersAdded(false)
1140 {
1141     q = nullptr;
1142     if (parent)
1143         q = qobject_cast<QAbstractPrintDialog*> (parent->parent());
1144 
1145     widget.setupUi(parent);
1146 
1147     int currentPrinterIndex = 0;
1148     QPlatformPrinterSupport *ps = QPlatformPrinterSupportPlugin::get();
1149     if (ps) {
1150         const QStringList printers = ps->availablePrintDeviceIds();
1151         const QString defaultPrinter = ps->defaultPrintDeviceId();
1152 
1153         widget.printers->addItems(printers);
1154 
1155         const QString selectedPrinter = prn && !prn->printerName().isEmpty() ? prn->printerName() : defaultPrinter;
1156         const int idx = printers.indexOf(selectedPrinter);
1157 
1158         if (idx >= 0)
1159             currentPrinterIndex = idx;
1160     }
1161     widget.properties->setEnabled(true);
1162 
1163 #if QT_CONFIG(filesystemmodel) && QT_CONFIG(completer)
1164     QFileSystemModel *fsm = new QFileSystemModel(widget.filename);
1165     fsm->setRootPath(QDir::homePath());
1166     widget.filename->setCompleter(new QCompleter(fsm, widget.filename));
1167 #endif
1168     _q_printerChanged(currentPrinterIndex);
1169 
1170     QObject::connect(widget.printers, SIGNAL(currentIndexChanged(int)),
1171                      parent, SLOT(_q_printerChanged(int)));
1172     QObject::connect(widget.fileBrowser, SIGNAL(clicked()), parent, SLOT(_q_btnBrowseClicked()));
1173     QObject::connect(widget.properties, SIGNAL(clicked()), parent, SLOT(_q_btnPropertiesClicked()));
1174 
1175     // disable features that QPrinter does not yet support.
1176     widget.preview->setVisible(false);
1177 }
1178 
updateWidget()1179 void QUnixPrintWidgetPrivate::updateWidget()
1180 {
1181     const bool printToFile = q == nullptr || q->isOptionEnabled(QPrintDialog::PrintToFile);
1182     if (printToFile && !filePrintersAdded) {
1183         if (widget.printers->count())
1184             widget.printers->insertSeparator(widget.printers->count());
1185         widget.printers->addItem(QPrintDialog::tr("Print to File (PDF)"));
1186         filePrintersAdded = true;
1187         if (widget.printers->count() == 1)
1188             _q_printerChanged(0);
1189     }
1190     if (!printToFile && filePrintersAdded) {
1191         widget.printers->removeItem(widget.printers->count()-1);
1192         widget.printers->removeItem(widget.printers->count()-1);
1193         if (widget.printers->count())
1194             widget.printers->removeItem(widget.printers->count()-1); // remove separator
1195         filePrintersAdded = false;
1196     }
1197     if (printer && filePrintersAdded && (printer->outputFormat() != QPrinter::NativeFormat
1198                                          || printer->printerName().isEmpty()))
1199     {
1200         if (printer->outputFormat() == QPrinter::PdfFormat)
1201             widget.printers->setCurrentIndex(widget.printers->count() - 1);
1202         widget.filename->setEnabled(true);
1203         widget.lOutput->setEnabled(true);
1204     }
1205 
1206     widget.filename->setVisible(printToFile);
1207     widget.lOutput->setVisible(printToFile);
1208     widget.fileBrowser->setVisible(printToFile);
1209 
1210     widget.properties->setVisible(q->isOptionEnabled(QAbstractPrintDialog::PrintShowPageSize));
1211 }
1212 
~QUnixPrintWidgetPrivate()1213 QUnixPrintWidgetPrivate::~QUnixPrintWidgetPrivate()
1214 {
1215 }
1216 
_q_printerChanged(int index)1217 void QUnixPrintWidgetPrivate::_q_printerChanged(int index)
1218 {
1219     if (index < 0)
1220         return;
1221     const int printerCount = widget.printers->count();
1222     widget.filename->setEnabled(false);
1223     widget.lOutput->setEnabled(false);
1224 
1225     // Reset properties dialog when printer is changed
1226     if (propertiesDialog){
1227         delete propertiesDialog;
1228         propertiesDialog = nullptr;
1229     }
1230 
1231 #if QT_CONFIG(cups)
1232     m_duplexPpdOption = nullptr;
1233 #endif
1234 
1235     if (filePrintersAdded) {
1236         Q_ASSERT(index != printerCount - 2); // separator
1237         if (index == printerCount - 1) { // PDF
1238             widget.location->setText(QPrintDialog::tr("Local file"));
1239             widget.type->setText(QPrintDialog::tr("Write PDF file"));
1240             widget.properties->setEnabled(true);
1241             widget.filename->setEnabled(true);
1242             QString filename = widget.filename->text();
1243             widget.filename->setText(filename);
1244             widget.lOutput->setEnabled(true);
1245             if (optionsPane)
1246                 optionsPane->selectPrinter(QPrinter::PdfFormat);
1247             printer->setOutputFormat(QPrinter::PdfFormat);
1248             m_currentPrintDevice = QPrintDevice();
1249             return;
1250         }
1251     }
1252 
1253     if (printer) {
1254         printer->setOutputFormat(QPrinter::NativeFormat);
1255 
1256         QPlatformPrinterSupport *ps = QPlatformPrinterSupportPlugin::get();
1257         if (ps)
1258             m_currentPrintDevice = ps->createPrintDevice(widget.printers->itemText(index));
1259         else
1260             m_currentPrintDevice = QPrintDevice();
1261 
1262         printer->setPrinterName(m_currentPrintDevice.id());
1263 
1264         widget.location->setText(m_currentPrintDevice.location());
1265         widget.type->setText(m_currentPrintDevice.makeAndModel());
1266         if (optionsPane)
1267             optionsPane->selectPrinter(QPrinter::NativeFormat);
1268     }
1269 
1270 #if QT_CONFIG(cups)
1271     m_duplexPpdOption = QCUPSSupport::findPpdOption("Duplex", &m_currentPrintDevice);
1272 #endif
1273 }
1274 
setOptionsPane(QPrintDialogPrivate * pane)1275 void QUnixPrintWidgetPrivate::setOptionsPane(QPrintDialogPrivate *pane)
1276 {
1277     optionsPane = pane;
1278     if (optionsPane)
1279         optionsPane->selectPrinter(QPrinter::NativeFormat);
1280 }
1281 
_q_btnBrowseClicked()1282 void QUnixPrintWidgetPrivate::_q_btnBrowseClicked()
1283 {
1284     QString filename = widget.filename->text();
1285 #if QT_CONFIG(filedialog)
1286     filename = QFileDialog::getSaveFileName(parent, QPrintDialog::tr("Print To File ..."), filename,
1287                                             QString(), nullptr, QFileDialog::DontConfirmOverwrite);
1288 #else
1289     filename.clear();
1290 #endif
1291     if (!filename.isEmpty()) {
1292         widget.filename->setText(filename);
1293         widget.printers->setCurrentIndex(widget.printers->count() - 1); // the pdf one
1294     }
1295 }
1296 
1297 #if QT_CONFIG(messagebox)
checkFields()1298 bool QUnixPrintWidgetPrivate::checkFields()
1299 {
1300     if (widget.filename->isEnabled()) {
1301         QString file = widget.filename->text();
1302         QFile f(file);
1303         QFileInfo fi(f);
1304         bool exists = fi.exists();
1305         bool opened = false;
1306         if (exists && fi.isDir()) {
1307             QMessageBox::warning(q, q->windowTitle(),
1308                             QPrintDialog::tr("%1 is a directory.\nPlease choose a different file name.").arg(file));
1309             return false;
1310         } else if ((exists && !fi.isWritable()) || !(opened = f.open(QFile::Append))) {
1311             QMessageBox::warning(q, q->windowTitle(),
1312                             QPrintDialog::tr("File %1 is not writable.\nPlease choose a different file name.").arg(file));
1313             return false;
1314         } else if (exists) {
1315             int ret = QMessageBox::question(q, q->windowTitle(),
1316                                             QPrintDialog::tr("%1 already exists.\nDo you want to overwrite it?").arg(file),
1317                                             QMessageBox::Yes|QMessageBox::No, QMessageBox::No);
1318             if (ret == QMessageBox::No)
1319                 return false;
1320         }
1321         if (opened) {
1322             f.close();
1323             if (!exists)
1324                 f.remove();
1325         }
1326     }
1327 
1328 #if QT_CONFIG(cups)
1329     if (propertiesDialog) {
1330         QCUPSSupport::PagesPerSheet pagesPerSheet = qvariant_cast<QCUPSSupport::PagesPerSheet>(propertiesDialog->widget.pageSetup->m_ui.pagesPerSheetCombo
1331                                                                     ->currentData());
1332 
1333         QCUPSSupport::PageSet pageSet = qvariant_cast<QCUPSSupport::PageSet>(optionsPane->options.pageSetCombo->currentData());
1334 
1335 
1336         if (pagesPerSheet != QCUPSSupport::OnePagePerSheet
1337             && pageSet != QCUPSSupport::AllPages) {
1338             QMessageBox::warning(q, q->windowTitle(),
1339                                  QPrintDialog::tr("Options 'Pages Per Sheet' and 'Page Set' cannot be used together.\nPlease turn one of those options off."));
1340             return false;
1341         }
1342     }
1343 #endif
1344 
1345     // Every test passed. Accept the dialog.
1346     return true;
1347 }
1348 #endif // QT_CONFIG(messagebox)
1349 
setupPrinterProperties()1350 void QUnixPrintWidgetPrivate::setupPrinterProperties()
1351 {
1352     delete propertiesDialog;
1353 
1354     QPrinter::OutputFormat outputFormat;
1355     QString printerName;
1356 
1357     if (q->isOptionEnabled(QPrintDialog::PrintToFile)
1358         && (widget.printers->currentIndex() == widget.printers->count() - 1)) {// PDF
1359         outputFormat = QPrinter::PdfFormat;
1360     } else {
1361         outputFormat = QPrinter::NativeFormat;
1362         printerName = widget.printers->currentText();
1363     }
1364 
1365     propertiesDialog = new QPrintPropertiesDialog(q->printer(), &m_currentPrintDevice, outputFormat, printerName, q);
1366 }
1367 
1368 #if QT_CONFIG(cups)
setPpdDuplex(QPrinter::DuplexMode mode)1369 void QUnixPrintWidgetPrivate::setPpdDuplex(QPrinter::DuplexMode mode)
1370 {
1371     auto values = QStringList{} << QStringLiteral("Duplex");
1372     if (mode == QPrinter::DuplexNone) values << QStringLiteral("None");
1373     else if (mode == QPrinter::DuplexLongSide) values << QStringLiteral("DuplexNoTumble");
1374     else if (mode == QPrinter::DuplexShortSide) values << QStringLiteral("DuplexTumble");
1375 
1376     m_currentPrintDevice.setProperty(PDPK_PpdOption, values);
1377 }
1378 #endif
1379 
_q_btnPropertiesClicked()1380 void QUnixPrintWidgetPrivate::_q_btnPropertiesClicked()
1381 {
1382     if (!propertiesDialog)
1383         setupPrinterProperties();
1384     propertiesDialog->exec();
1385 
1386 #if QT_CONFIG(cups)
1387     // update the warning icon on the duplex options if needed
1388     optionsPane->updatePpdDuplexOption(optionsPane->options.noDuplex);
1389     optionsPane->updatePpdDuplexOption(optionsPane->options.duplexLong);
1390     optionsPane->updatePpdDuplexOption(optionsPane->options.duplexShort);
1391 #endif
1392 }
1393 
setupPrinter()1394 void QUnixPrintWidgetPrivate::setupPrinter()
1395 {
1396     const int printerCount = widget.printers->count();
1397     const int index = widget.printers->currentIndex();
1398 
1399     if (filePrintersAdded && index == printerCount - 1) { // PDF
1400         printer->setPrinterName(QString());
1401         Q_ASSERT(index != printerCount - 2); // separator
1402         printer->setOutputFormat(QPrinter::PdfFormat);
1403         QString path = widget.filename->text();
1404         if (QDir::isRelativePath(path))
1405             path = QDir::homePath() + QDir::separator() + path;
1406         printer->setOutputFileName(path);
1407     }
1408     else {
1409         printer->setPrinterName(widget.printers->currentText());
1410         printer->setOutputFileName(QString());
1411     }
1412 
1413     if (!propertiesDialog)
1414         setupPrinterProperties();
1415 
1416     propertiesDialog->setupPrinter();
1417 }
1418 
1419 /*! \internal
1420 */
QUnixPrintWidget(QPrinter * printer,QWidget * parent)1421 QUnixPrintWidget::QUnixPrintWidget(QPrinter *printer, QWidget *parent)
1422     : QWidget(parent), d(new QUnixPrintWidgetPrivate(this, printer))
1423 {
1424     if (printer == nullptr)
1425         return;
1426     if (printer->outputFileName().isEmpty()) {
1427         QString home = QDir::homePath();
1428         QString cur = QDir::currentPath();
1429         if (!home.endsWith(QLatin1Char('/')))
1430             home += QLatin1Char('/');
1431         if (!cur.startsWith(home))
1432             cur = home;
1433         else if (!cur.endsWith(QLatin1Char('/')))
1434             cur += QLatin1Char('/');
1435         if (QGuiApplication::platformName() == QStringLiteral("xcb")) {
1436             if (printer->docName().isEmpty()) {
1437                 cur += QStringLiteral("print.pdf");
1438             } else {
1439                 const QRegExp re(QStringLiteral("(.*)\\.\\S+"));
1440                 if (re.exactMatch(printer->docName()))
1441                     cur += re.cap(1);
1442                 else
1443                     cur += printer->docName();
1444                 cur += QStringLiteral(".pdf");
1445             }
1446         } // xcb
1447 
1448         d->widget.filename->setText(cur);
1449     }
1450     else
1451         d->widget.filename->setText(printer->outputFileName());
1452     const QString printerName = printer->printerName();
1453     if (!printerName.isEmpty()) {
1454         const int i = d->widget.printers->findText(printerName);
1455         if (i >= 0)
1456             d->widget.printers->setCurrentIndex(i);
1457     }
1458     // PDF printer not added to the dialog yet, we'll handle those cases in QUnixPrintWidgetPrivate::updateWidget
1459 }
1460 
1461 /*! \internal
1462 */
~QUnixPrintWidget()1463 QUnixPrintWidget::~QUnixPrintWidget()
1464 {
1465     delete d;
1466 }
1467 
1468 /*! \internal
1469 
1470     Updates the printer with the states held in the QUnixPrintWidget.
1471 */
updatePrinter()1472 void QUnixPrintWidget::updatePrinter()
1473 {
1474     d->setupPrinter();
1475 }
1476 
1477 #if QT_CONFIG(cups)
1478 
1479 ////////////////////////////////////////////////////////////////////////////////
1480 ////////////////////////////////////////////////////////////////////////////////
1481 
1482 #endif // QT_CONFIG(cups)
1483 #endif // defined (Q_OS_UNIX)
1484 
1485 QT_END_NAMESPACE
1486 
1487 #include "moc_qprintdialog.cpp"
1488 #include "qprintdialog_unix.moc"
1489