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