1 /*
2     SPDX-FileCopyrightText: 2010-2018 Daniel Nicoletti <dantti12@gmail.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "PrintKCM.h"
8 
9 #include "ui_PrintKCM.h"
10 
11 #include <config.h>
12 
13 #include <PrinterModel.h>
14 #include <PrinterSortFilterModel.h>
15 #include "PrinterDelegate.h"
16 #include "PrinterDescription.h"
17 
18 #include <KMessageBox>
19 #include <KAboutData>
20 #include <KIO/CommandLauncherJob>
21 
22 #include <QIcon>
23 #include <QMenu>
24 #include <KCupsRequest.h>
25 #include <NoSelectionRectDelegate.h>
26 
27 #include <cups/cups.h>
28 
PrintKCM(QWidget * parent,const QVariantList & args)29 PrintKCM::PrintKCM(QWidget *parent, const QVariantList &args) :
30     KCModule(parent, args),
31     ui(new Ui::PrintKCM)
32 {
33     auto aboutData = new KAboutData(QLatin1String("kcm_print"),
34                                     i18n("Print settings"),
35                                     QLatin1String(PM_VERSION),
36                                     i18n("Print settings"),
37                                     KAboutLicense::GPL,
38                                     i18n("(C) 2010-2018 Daniel Nicoletti"));
39     aboutData->addAuthor(QStringLiteral("Daniel Nicoletti"), QString(), QLatin1String("dantti12@gmail.com"));
40     aboutData->addAuthor(QStringLiteral("Jan Grulich"), i18n("Port to Qt 5 / Plasma 5"), QStringLiteral("jgrulich@redhat.com"));
41     setAboutData(aboutData);
42     setButtons(NoAdditionalButton);
43 
44     ui->setupUi(this);
45 
46     connect(ui->printerDesc, &PrinterDescription::updateNeeded, this, &PrintKCM::update);
47 
48     // The printer list needs to increase in width according to the icon sizes
49     // default dialog icon size is 32, this times 6 is 192 which is roughly the original width
50     ui->printersTV->setMinimumWidth(192);
51 
52     auto addMenu = new QMenu(this);
53     addMenu->addAction(i18nc("@action:intoolbar","Add a Printer Class"),
54                        this, &PrintKCM::addClass);
55     ui->addTB->setIcon(QIcon::fromTheme(QLatin1String("list-add")));
56     ui->addTB->setToolTip(i18n("Add a new printer or a printer class"));
57     ui->addTB->setMenu(addMenu);
58 
59     ui->removeTB->setIcon(QIcon::fromTheme(QLatin1String("list-remove")));
60     ui->removeTB->setToolTip(i18n("Remove Printer"));
61 
62     auto systemMenu = new QMenu(this);
63     connect(systemMenu, &QMenu::triggered, this, &PrintKCM::systemPreferencesTriggered);
64 #if CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6
65     m_showSharedPrinters = systemMenu->addAction(i18nc("@action:intoolbar","Show printers shared by other systems"));
66     m_showSharedPrinters->setCheckable(true);
67     systemMenu->addSeparator();
68 #endif // CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6
69     m_shareConnectedPrinters = systemMenu->addAction(i18nc("@action:intoolbar","Share printers connected to this system"));
70     m_shareConnectedPrinters->setCheckable(true);
71     m_allowPrintringFromInternet = systemMenu->addAction(i18nc("@action:intoolbar","Allow printing from the Internet"));
72     m_allowPrintringFromInternet->setCheckable(true);
73     m_allowPrintringFromInternet->setEnabled(false);
74     connect(m_shareConnectedPrinters, &QAction::toggled, m_allowPrintringFromInternet, &QAction::setEnabled);
75     connect(m_shareConnectedPrinters, &QAction::toggled, ui->printerDesc, &PrinterDescription::enableShareCheckBox);
76     systemMenu->addSeparator();
77     m_allowRemoteAdmin = systemMenu->addAction(i18nc("@action:intoolbar","Allow remote administration"));
78     m_allowRemoteAdmin->setCheckable(true);
79     m_allowUsersCancelAnyJob = systemMenu->addAction(i18nc("@action:intoolbar","Allow users to cancel any job (not just their own)"));
80     m_allowUsersCancelAnyJob->setCheckable(true);
81 
82     ui->systemPreferencesTB->setIcon(QIcon::fromTheme(QLatin1String("configure")));
83     ui->systemPreferencesTB->setToolTip(i18n("Configure the global preferences"));
84     ui->systemPreferencesTB->setMenu(systemMenu);
85 
86     m_model = new PrinterModel(this);
87     auto sortModel = new PrinterSortFilterModel(this);
88     sortModel->setSourceModel(m_model);
89     ui->printersTV->setModel(sortModel);
90     ui->printersTV->setItemDelegate(new NoSelectionRectDelegate(this));
91     ui->printersTV->setItemDelegate(new PrinterDelegate(this));
92     connect(ui->printersTV->selectionModel(), &QItemSelectionModel::selectionChanged, this, &PrintKCM::update);
93     connect(sortModel, &PrinterSortFilterModel::rowsInserted, this, &PrintKCM::update);
94     connect(sortModel, &PrinterSortFilterModel::rowsRemoved, this, &PrintKCM::update);
95     connect(m_model, &PrinterModel::dataChanged, this, &PrintKCM::update);
96     connect(m_model, &PrinterModel::error, this, &PrintKCM::error);
97 
98     ui->addPrinterBtn->setIcon(QIcon::fromTheme(QLatin1String("list-add")));
99     connect(ui->addPrinterBtn, &QPushButton::clicked, this, &PrintKCM::on_addTB_clicked);
100 
101     // Force the model update AFTER we setup the error signal
102     m_model->update();
103 
104     // Make sure we update our server settings if the user change it on
105     // another interface
106     connect(KCupsConnection::global(), &KCupsConnection::serverAudit, this, &PrintKCM::getServerSettings);
107     connect(KCupsConnection::global(), &KCupsConnection::serverStarted, this, &PrintKCM::getServerSettings);
108     connect(KCupsConnection::global(), &KCupsConnection::serverStopped, this, &PrintKCM::getServerSettings);
109     connect(KCupsConnection::global(), &KCupsConnection::serverRestarted, this, &PrintKCM::getServerSettings);
110 
111     // We need to know the server settings so we disable the
112     // share printer checkbox if sharing is disabled on the server
113     getServerSettings();
114 }
115 
~PrintKCM()116 PrintKCM::~PrintKCM()
117 {
118     delete ui;
119 }
120 
error(int lastError,const QString & errorTitle,const QString & errorMsg)121 void PrintKCM::error(int lastError, const QString &errorTitle, const QString &errorMsg)
122 {
123     if (lastError) {
124         // The user has no printer
125         // allow him to add a new one
126         if (lastError == IPP_NOT_FOUND) {
127             showInfo(QIcon::fromTheme(QLatin1String("dialog-information")),
128                      i18n("No printers have been configured or discovered"),
129                      QString(),
130                      true,
131                      true);
132         } else {
133             showInfo(QIcon::fromTheme(QLatin1String("printer")),
134                      QStringLiteral("<strong>%1</strong>").arg(errorTitle),
135                      errorMsg,
136                      false,
137                      false);
138         }
139     }
140 
141     if (m_lastError != lastError) {
142         // if no printer was found the server
143         // is still working
144         if (lastError == IPP_NOT_FOUND) {
145             ui->addTB->setEnabled(true);
146             ui->systemPreferencesTB->setEnabled(true);
147         } else {
148             ui->addTB->setEnabled(!lastError);
149             ui->systemPreferencesTB->setEnabled(!lastError);
150         }
151 
152         m_lastError = lastError;
153         // Force an update
154         update();
155     }
156 }
157 
showInfo(const QIcon & icon,const QString & title,const QString & comment,bool showAddPrinter,bool showToolButtons)158 void PrintKCM::showInfo(const QIcon &icon, const QString &title, const QString &comment, bool showAddPrinter, bool showToolButtons)
159 {
160     ui->hugeIcon->setPixmap(icon.pixmap(128, 128));
161     ui->errorText->setText(title);
162     ui->errorComment->setVisible(!comment.isEmpty());
163     ui->errorComment->setText(comment);
164     ui->addPrinterBtn->setVisible(showAddPrinter);
165 
166     // Well, when there is no printer, there is nothing to add to a printer class
167     // so we can actually hide the Add button nontheless?
168     ui->addTB->setVisible(!showAddPrinter && showToolButtons);
169     ui->removeTB->setVisible(!showAddPrinter && showToolButtons);
170     ui->lineTB->setVisible(!showAddPrinter && showToolButtons);
171     ui->printersTV->setVisible(!showAddPrinter && showToolButtons);
172 
173     // Make sure we are visible
174     ui->stackedWidget->setCurrentIndex(1);
175 }
176 
update()177 void PrintKCM::update()
178 {
179     if (m_model->rowCount()) {
180         m_lastError = -1; // if the model has printers reset the error code
181         if (ui->stackedWidget->currentIndex() != 0) {
182             ui->stackedWidget->setCurrentIndex(0);
183         }
184 
185         QItemSelection selection;
186         // we need to map the selection to source to get the real indexes
187         selection = ui->printersTV->selectionModel()->selection();
188         // select the first printer if there are printers
189         if (selection.indexes().isEmpty()) {
190             ui->printersTV->selectionModel()->select(m_model->index(0, 0), QItemSelectionModel::Select);
191             return;
192         }
193 
194         QModelIndex index = selection.indexes().first();
195         QString destName = index.data(PrinterModel::DestName).toString();
196         if (ui->printerDesc->destName() != destName) {
197             ui->printerDesc->setPrinterIcon(index.data(Qt::DecorationRole).value<QIcon>());
198             int type = index.data(PrinterModel::DestType).toUInt();
199             // If we remove discovered printers, they will come
200             // back to hunt us a bit later
201             ui->removeTB->setEnabled(!(type & CUPS_PRINTER_DISCOVERED));
202         }
203         ui->printerDesc->setDestName(index.data(PrinterModel::DestName).toString(),
204                                      index.data(PrinterModel::DestDescription).toString(),
205                                      index.data(PrinterModel::DestIsClass).toBool(),
206                                      m_model->rowCount() == 1);
207         ui->printerDesc->setDestStatus(index.data(PrinterModel::DestStatus).toString());
208         ui->printerDesc->setLocation(index.data(PrinterModel::DestLocation).toString());
209         ui->printerDesc->setKind(index.data(PrinterModel::DestKind).toString());
210         ui->printerDesc->setIsShared(index.data(PrinterModel::DestIsShared).toBool());
211         ui->printerDesc->setAcceptingJobs(index.data(PrinterModel::DestIsAcceptingJobs).toBool());
212         ui->printerDesc->setIsDefault(index.data(PrinterModel::DestIsDefault).toBool());
213         ui->printerDesc->setCommands(index.data(PrinterModel::DestCommands).toStringList());
214         ui->printerDesc->setMarkers(index.data(PrinterModel::DestMarkers).value<QVariantHash>());
215 
216         ui->addTB->show();
217         ui->removeTB->show();
218         ui->lineTB->show();
219         // Show the printer list only if there are more than 1 printer
220         ui->printersTV->setVisible(m_model->rowCount() > 1);
221     } else {
222         // disable the printer action buttons if there is nothing to selected
223         ui->removeTB->setEnabled(false);
224 
225         if (m_lastError == IPP_OK) {
226             // the model is empty and no problem happened
227             showInfo(QIcon::fromTheme(QLatin1String("dialog-information")),
228                      i18n("No printers have been configured or discovered"),
229                      QString(),
230                      true,
231                      true);
232         }
233     }
234 }
235 
on_addTB_clicked()236 void PrintKCM::on_addTB_clicked()
237 {
238     auto job = new KIO::CommandLauncherJob(QStringLiteral("kde-add-printer"), { QStringLiteral("--add-printer") });
239     job->start();
240 }
241 
addClass()242 void PrintKCM::addClass()
243 {
244     auto job = new KIO::CommandLauncherJob(QStringLiteral("kde-add-printer"), { QStringLiteral("--add-class") });
245     job->start();
246 }
247 
on_removeTB_clicked()248 void PrintKCM::on_removeTB_clicked()
249 {
250     QItemSelection selection;
251     // we need to map the selection to source to get the real indexes
252     selection = ui->printersTV->selectionModel()->selection();
253     // enable or disable the job action buttons if something is selected
254     if (!selection.indexes().isEmpty()) {
255         QModelIndex index = selection.indexes().first();
256         int resp;
257         QString msg, title;
258         if (index.data(PrinterModel::DestIsClass).toBool()) {
259             title = i18n("Remove class");
260             msg = i18n("Are you sure you want to remove the class '%1'?",
261                        index.data(Qt::DisplayRole).toString());
262         } else {
263             title = i18n("Remove printer");
264             msg = i18n("Are you sure you want to remove the printer '%1'?",
265                        index.data(Qt::DisplayRole).toString());
266         }
267         resp = KMessageBox::warningYesNo(this, msg, title);
268         if (resp == KMessageBox::Yes) {
269             QPointer<KCupsRequest> request = new KCupsRequest;
270             request->deletePrinter(index.data(PrinterModel::DestName).toString());
271             request->waitTillFinished();
272             if (request) {
273                 request->deleteLater();
274             }
275         }
276     }
277 }
278 
getServerSettings()279 void PrintKCM::getServerSettings()
280 {
281     if (!m_serverRequest) {
282         auto systemMenu = qobject_cast<QMenu*>(sender());
283         m_serverRequest = new KCupsRequest;
284         m_serverRequest->setProperty("interactive", static_cast<bool>(systemMenu));
285         connect(m_serverRequest, &KCupsRequest::finished, this, &PrintKCM::getServerSettingsFinished);
286         m_serverRequest->getServerSettings();
287     }
288 }
289 
getServerSettingsFinished(KCupsRequest * request)290 void PrintKCM::getServerSettingsFinished(KCupsRequest *request)
291 {
292     // When we don't have any destinations error is set to IPP_NOT_FOUND
293     // we can safely ignore the error since it DOES bring the server settings
294     bool error = request->hasError() && request->error() != IPP_NOT_FOUND;
295 
296 #if CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6
297     m_showSharedPrinters->setEnabled(!error);
298 #endif // CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6
299     m_shareConnectedPrinters->setEnabled(!error);
300     m_allowRemoteAdmin->setEnabled(!error);
301     m_allowUsersCancelAnyJob->setEnabled(!error);
302 
303     if (error) {
304         if (request->property("interactive").toBool()) {
305             KMessageBox::detailedSorry(this,
306                                        i18nc("@info", "Failed to get server settings"),
307                                        request->errorMsg(),
308                                        i18nc("@title:window", "Failed"));
309         }
310     } else {
311         KCupsServer server = request->serverSettings();
312 
313 #if CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6
314         m_showSharedPrinters->setChecked(server.showSharedPrinters());
315 #endif // CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6
316         m_shareConnectedPrinters->setChecked(server.sharePrinters());
317         m_allowPrintringFromInternet->setChecked(server.allowPrintingFromInternet());
318         m_allowRemoteAdmin->setChecked(server.allowRemoteAdmin());
319         m_allowUsersCancelAnyJob->setChecked(server.allowUserCancelAnyJobs());
320     }
321 
322     request->deleteLater();
323 
324     m_serverRequest = nullptr;
325 }
326 
updateServerFinished(KCupsRequest * request)327 void PrintKCM::updateServerFinished(KCupsRequest *request)
328 {
329     if (request->hasError()) {
330         if (request->error() == IPP_SERVICE_UNAVAILABLE ||
331                 request->error() == IPP_INTERNAL_ERROR ||
332                 request->error() == IPP_AUTHENTICATION_CANCELED) {
333             // Server is restarting, or auth was canceled, update the settings in one second
334             QTimer::singleShot(1000, this, &PrintKCM::update);
335         } else {
336             KMessageBox::detailedSorry(this,
337                                        i18nc("@info", "Failed to configure server settings"),
338                                        request->errorMsg(),
339                                        request->serverError());
340 
341             // Force the settings to be retrieved again
342             update();
343         }
344     }
345     request->deleteLater();
346 }
347 
systemPreferencesTriggered()348 void PrintKCM::systemPreferencesTriggered()
349 {
350     KCupsServer server;
351 #if CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6
352     server.setShowSharedPrinters(m_showSharedPrinters->isChecked());
353 #endif // CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR < 6
354     server.setSharePrinters(m_shareConnectedPrinters->isChecked());
355     server.setAllowPrintingFromInternet(m_allowPrintringFromInternet->isChecked());
356     server.setAllowRemoteAdmin(m_allowRemoteAdmin->isChecked());
357     server.setAllowUserCancelAnyJobs(m_allowUsersCancelAnyJob->isChecked());
358     auto request = new KCupsRequest;
359     connect(request, &KCupsRequest::finished, this, &PrintKCM::updateServerFinished);
360     request->setServerSettings(server);
361 }
362 
363 #include "moc_PrintKCM.cpp"
364