1 /* ============================================================
2 *
3 * SPDX-FileCopyrightText: 2007-2012 Kåre Särs <kare.sars@iki .fi>
4 * SPDX-FileCopyrightText: 2009 Arseniy Lartsev <receive-spam at yandex dot ru>
5 * SPDX-FileCopyrightText: 2014 Gregor Mitsch : port to KDE5 frameworks
6 *
7 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
8 *
9 * ============================================================ */
10
11 #include "skanlite.h"
12
13 #include "SaveLocation.h"
14 #include "showimagedialog.h"
15 #include "SkanliteImageSaver.h"
16
17 #include <QApplication>
18 #include <QScrollArea>
19 #include <QStringList>
20 #include <QFileDialog>
21 #include <QUrl>
22 #include <QDialogButtonBox>
23 #include <QComboBox>
24 #include <QMessageBox>
25 #include <QTemporaryFile>
26 #include <QImageWriter>
27 #include <QMimeType>
28 #include <QMimeDatabase>
29 #include <QCloseEvent>
30 #include <QProgressBar>
31
32 #include <KAboutData>
33 #include <KAboutApplicationDialog>
34 #include <KLocalizedString>
35 #include <KMessageBox>
36 #include <KIO/StatJob>
37 #include <KIO/Job>
38 #include <KJobWidgets>
39 #include <kio/global.h>
40 #include <KSharedConfig>
41 #include <KConfigGroup>
42 #include <KHelpClient>
43 #include <kio_version.h>
44
45 #include <skanlite_debug.h>
46
47 #include <errno.h>
48
Skanlite(const QString & device,QWidget * parent)49 Skanlite::Skanlite(const QString &device, QWidget *parent)
50 : QDialog(parent)
51 , m_dbusInterface(this)
52 {
53 QVBoxLayout *mainLayout = new QVBoxLayout(this);
54
55 QDialogButtonBox *dlgButtonBoxBottom = new QDialogButtonBox(this);
56 dlgButtonBoxBottom->setStandardButtons(QDialogButtonBox::Help | QDialogButtonBox::Close);
57 QPushButton *btnAbout = dlgButtonBoxBottom->addButton(i18n("About"), QDialogButtonBox::ButtonRole::HelpRole);
58 btnAbout->setIcon(QIcon::fromTheme(QStringLiteral("skanlite")));
59 QPushButton *btnReselectDevice = dlgButtonBoxBottom->addButton(i18n("Reselect scanner device"), QDialogButtonBox::ButtonRole::ActionRole);
60 btnReselectDevice->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
61 QPushButton *btnSettings = dlgButtonBoxBottom->addButton(i18n("Settings"), QDialogButtonBox::ButtonRole::ActionRole);
62 btnSettings->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
63
64 m_firstImage = true;
65
66 m_ksanew = new KSaneIface::KSaneWidget(this);
67 connect(m_ksanew, &KSaneWidget::scannedImageReady, this, &Skanlite::imageReady);
68 connect(m_ksanew, &KSaneWidget::userMessage, this, &Skanlite::alertUser);
69 connect(m_ksanew, &KSaneWidget::buttonPressed, this, &Skanlite::buttonPressed);
70 connect(m_ksanew, &KSaneWidget::scanDone, this, [this](){
71 if (!m_pendingApplyScanOpts.isEmpty()) {
72 applyScannerOptions(m_pendingApplyScanOpts);
73 }
74 });
75
76 m_saveProgressBar = new QProgressBar(this);
77 m_saveProgressBar->setVisible(false);
78 m_saveProgressBar->setFormat(i18n("Saving: %v kB"));
79 m_saveProgressBar->setTextVisible(true);
80
81 m_saveUpdateTimer.setInterval(200);
82 m_saveUpdateTimer.setSingleShot(false);
83 connect(&m_saveUpdateTimer, &QTimer::timeout, this, &Skanlite::updateSaveProgress);
84
85 mainLayout->addWidget(m_ksanew);
86 mainLayout->addWidget(m_saveProgressBar);
87 mainLayout->addWidget(dlgButtonBoxBottom);
88
89 // read the size here...
90 KConfigGroup window(KSharedConfig::openConfig(), "Window");
91 QSize rect = window.readEntry("Geometry", QSize(740, 400));
92 resize(rect);
93
94 // open scanner device from command line, otherwise try remembered one
95 QString deviceName;
96 QString deviceVendor;
97 QString deviceModel;
98 if (device.isEmpty()) {
99 KConfigGroup general(KSharedConfig::openConfig(), QStringLiteral("General"));
100 deviceName = general.readEntry(QStringLiteral("deviceName"));
101 deviceVendor = general.readEntry(QStringLiteral("deviceVendor"));
102 deviceModel = general.readEntry(QStringLiteral("deviceModel"));
103 } else {
104 deviceName = device;
105 }
106
107 connect(dlgButtonBoxBottom, &QDialogButtonBox::rejected, this, &QDialog::close);
108 connect(this, &QDialog::finished, this, &Skanlite::saveWindowSize);
109 connect(this, &QDialog::finished, this, &Skanlite::saveScannerDevice);
110 connect(this, &QDialog::finished, this, &Skanlite::saveScannerOptions);
111 connect(btnSettings, &QPushButton::clicked, this, &Skanlite::showSettingsDialog);
112 connect(btnReselectDevice, &QPushButton::clicked, this, &Skanlite::reselectScannerDevice);
113 connect(btnAbout, &QPushButton::clicked, this, &Skanlite::showAboutDialog);
114 connect(dlgButtonBoxBottom, &QDialogButtonBox::helpRequested, this, &Skanlite::showHelp);
115
116 //
117 // Create the settings dialog
118 //
119 {
120 m_settingsDialog = new QDialog(this);
121
122 QVBoxLayout *mainLayout = new QVBoxLayout(m_settingsDialog);
123
124 QWidget *settingsWidget = new QWidget(m_settingsDialog);
125 m_settingsUi.setupUi(settingsWidget);
126 m_settingsUi.revertOptions->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo")));
127 m_saveLocation = new SaveLocation(this);
128
129 // add the supported image types
130 const QList<QByteArray> tmpList = QImageWriter::supportedMimeTypes();
131 m_filterList.clear();
132 for (const auto &ba : tmpList) {
133 if (ba.isEmpty()) {
134 continue;
135 }
136 m_filterList.append(QString::fromLatin1(ba));
137 }
138
139 qCDebug(SKANLITE_LOG) << m_filterList;
140
141 // Put first class citizens at first place
142 m_filterList.removeAll(QStringLiteral("image/jpeg"));
143 m_filterList.removeAll(QStringLiteral("image/tiff"));
144 m_filterList.removeAll(QStringLiteral("image/png"));
145 m_filterList.insert(0, QStringLiteral("image/png"));
146 m_filterList.insert(1, QStringLiteral("image/jpeg"));
147 m_filterList.insert(2, QStringLiteral("image/tiff"));
148 m_filterList.insert(3, QStringLiteral("application/pdf"));
149
150 m_filter16BitList << QStringLiteral("image/png");
151 m_filter16BitList << QStringLiteral("image/tiff");
152
153 // fill m_filterList (...)
154 {
155 QStringList namedMimeTypes;
156 for (const QString &mimeStr : qAsConst(m_filterList)) {
157 QMimeType mimeType = QMimeDatabase().mimeTypeForName(mimeStr);
158 namedMimeTypes.append(mimeType.name());
159
160 m_settingsUi.imgFormat->addItem(mimeType.preferredSuffix(), mimeType.name());
161 m_saveLocation->addImageFormat(mimeType.preferredSuffix(), mimeType.name());
162 }
163 m_filterList << std::move(namedMimeTypes);
164 }
165
166 mainLayout->addWidget(settingsWidget);
167
168 QDialogButtonBox *dlgButtonBoxBottom = new QDialogButtonBox(this);
169 dlgButtonBoxBottom->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Close);
170 connect(dlgButtonBoxBottom, &QDialogButtonBox::accepted, m_settingsDialog, &QDialog::accept);
171 connect(dlgButtonBoxBottom, &QDialogButtonBox::rejected, m_settingsDialog, &QDialog::reject);
172
173 mainLayout->addWidget(dlgButtonBoxBottom);
174
175 m_settingsDialog->setWindowTitle(i18n("Skanlite Settings"));
176
177 connect(m_settingsUi.revertOptions, &QPushButton::clicked, this, &Skanlite::defaultScannerOptions);
178 readSettings();
179
180 // default directory for the save dialog
181 m_saveLocation->setFolderUrl(m_settingsUi.saveDirRequester->url());
182 m_saveLocation->setImagePrefix(m_settingsUi.imgPrefix->text());
183 m_saveLocation->setImageFormatIndex(m_settingsUi.imgFormat->currentIndex());
184 }
185
186 // open the scan device
187 if (!m_ksanew->openDevice(deviceName)) {
188 QString dev = m_ksanew->selectDevice(nullptr);
189 if (dev.isEmpty()) {
190 // either no scanner was found or then cancel was pressed.
191 exit(0);
192 }
193 if (!m_ksanew->openDevice(dev)) {
194 // could not open a scanner
195 KMessageBox::sorry(nullptr, i18n("Opening the selected scanner failed."));
196 exit(1);
197 }
198 else {
199 updateWindowTitle(dev, m_ksanew->deviceVendor(), m_ksanew->deviceModel());
200 m_deviceName = dev;
201 }
202 }
203 else {
204 if (deviceVendor.isEmpty()) {
205 updateWindowTitle(deviceName);
206 } else {
207 updateWindowTitle(deviceName, deviceVendor, deviceModel);
208 }
209 m_deviceName = deviceName;
210 m_deviceModel = deviceModel;
211 m_deviceVendor = deviceVendor;
212 }
213
214 // prepare the Show Image Dialog
215 m_showImgDialog = new ShowImageDialog(this);
216 connect(m_showImgDialog, &ShowImageDialog::saveRequested, this, &Skanlite::saveImage);
217 connect(m_showImgDialog, &ShowImageDialog::rejected, m_ksanew, &KSaneWidget::scanCancel);
218
219 // save the default sane options for later use
220 m_ksanew->getOptVals(m_defaultScanOpts);
221
222 // load saved options
223 loadScannerOptions();
224
225 m_firstImage = true;
226 m_ksanew->setFocus();
227
228 if (m_dbusInterface.setupDBusInterface()) {
229 // D-Bus related slots
230 connect(&m_dbusInterface, &DBusInterface::requestedScan, m_ksanew, &KSaneWidget::scanFinal);
231 connect(&m_dbusInterface, &DBusInterface::requestedPreview, m_ksanew, &KSaneWidget::startPreviewScan);
232 connect(&m_dbusInterface, &DBusInterface::requestedScanCancel, m_ksanew, &KSaneWidget::scanCancel);
233 connect(&m_dbusInterface, &DBusInterface::requestedSetScannerOptions, this, &Skanlite::setScannerOptions);
234 connect(&m_dbusInterface, &DBusInterface::requestedSetSelection, this, &Skanlite::setSelection);
235
236 // D-Bus related slots below must be Qt::DirectConnection to simplify return value forwarding via DBusInterface
237 connect(&m_dbusInterface, &DBusInterface::requestedGetScannerOptions, this, &Skanlite::getScannerOptions, Qt::DirectConnection);
238 connect(&m_dbusInterface, &DBusInterface::requestedDefaultScannerOptions, this, &Skanlite::getDefaultScannerOptions, Qt::DirectConnection);
239 connect(&m_dbusInterface, &DBusInterface::requestedDeviceName, this, &Skanlite::getDeviceName, Qt::DirectConnection);
240 connect(&m_dbusInterface, &DBusInterface::requestedSaveScannerOptionsToProfile, this, &Skanlite::saveScannerOptionsToProfile, Qt::DirectConnection);
241 connect(&m_dbusInterface, &DBusInterface::requestedSwitchToProfile, this, &Skanlite::switchToProfile, Qt::DirectConnection);
242 connect(&m_dbusInterface, &DBusInterface::requestedGetSelection, this, &Skanlite::getSelection, Qt::DirectConnection);
243
244 // D-Bus related signals
245 connect(m_ksanew, &KSaneWidget::scanDone, &m_dbusInterface, &DBusInterface::scanDone);
246 connect(m_ksanew, &KSaneWidget::userMessage, &m_dbusInterface, &DBusInterface::userMessage);
247 connect(m_ksanew, &KSaneWidget::scanProgress, &m_dbusInterface, &DBusInterface::scanProgress);
248 connect(m_ksanew, &KSaneWidget::buttonPressed, &m_dbusInterface, &DBusInterface::buttonPressed);
249 }
250 else {
251 // keep working without dbus
252 }
253 }
254
showHelp()255 void Skanlite::showHelp()
256 {
257 KHelpClient::invokeHelp(QStringLiteral("index"), QStringLiteral("skanlite"));
258 }
259
closeEvent(QCloseEvent * event)260 void Skanlite::closeEvent(QCloseEvent *event)
261 {
262 saveWindowSize();
263 saveScannerDevice();
264 saveScannerOptions();
265 event->accept();
266 }
267
saveWindowSize()268 void Skanlite::saveWindowSize()
269 {
270 KConfigGroup window(KSharedConfig::openConfig(), "Window");
271 window.writeEntry("Geometry", size());
272 window.sync();
273 }
274
saveScannerDevice()275 void Skanlite::saveScannerDevice()
276 {
277 KConfigGroup general(KSharedConfig::openConfig(), "General");
278 general.writeEntry(QStringLiteral("deviceName"), m_deviceName);
279 general.writeEntry(QStringLiteral("deviceModel"), m_deviceModel);
280 general.writeEntry(QStringLiteral("deviceVendor"), m_deviceVendor);
281 general.sync();
282 }
283
reselectScannerDevice()284 void Skanlite::reselectScannerDevice()
285 {
286 m_ksanew->closeDevice();
287 m_deviceName.clear();
288 m_deviceVendor.clear();
289 m_deviceModel.clear();
290 // open the scan device dialog
291 QString dev = m_ksanew->selectDevice(nullptr);
292 if (m_ksanew->openDevice(dev) == false) {
293 // could not open a scanner
294 KMessageBox::sorry(nullptr, i18n("Opening the selected scanner failed."));
295 }
296 else {
297 updateWindowTitle(dev, m_ksanew->deviceVendor(), m_ksanew->deviceModel());
298 m_deviceName = dev;
299 m_deviceModel = m_ksanew->deviceModel();
300 m_deviceVendor = m_ksanew->deviceVendor();
301 }
302 }
303
304 // Pops up message box similar to what perror() would print
305 //************************************************************
perrorMessageBox(const QString & text)306 static void perrorMessageBox(const QString &text)
307 {
308 if (errno != 0) {
309 KMessageBox::sorry(nullptr, i18n("%1: %2", text, QString::fromLocal8Bit(strerror(errno))));
310 }
311 else {
312 KMessageBox::sorry(nullptr, text);
313 }
314 }
315
readSettings(void)316 void Skanlite::readSettings(void)
317 {
318 // enable the widgets to allow modifying
319 m_settingsUi.setQuality->setChecked(true);
320 m_settingsUi.setPreviewDPI->setChecked(true);
321
322 // read the saved parameters
323 KConfigGroup saving(KSharedConfig::openConfig(), "Image Saving");
324 m_settingsUi.saveModeCB->setCurrentIndex(saving.readEntry("SaveMode", (int)SaveModeManual));
325 if (m_settingsUi.saveModeCB->currentIndex() != SaveModeAskFirst) {
326 m_firstImage = false;
327 }
328 m_settingsUi.saveDirRequester->setUrl(saving.readEntry("Location", QUrl(QDir::homePath())));
329 m_settingsUi.imgPrefix->setText(saving.readEntry("NamePrefix", i18nc("prefix for auto naming", "Image-")));
330 QString format = saving.readEntry("ImgFormat", "image/png");
331 int index = m_settingsUi.imgFormat->findData(format);
332 if (index >= 0) {
333 m_settingsUi.imgFormat->setCurrentIndex(index);
334 }
335
336 m_settingsUi.imgQuality->setValue(saving.readEntry("ImgQuality", 90));
337 m_settingsUi.setQuality->setChecked(saving.readEntry("SetQuality", false));
338 m_settingsUi.showB4Save->setChecked(saving.readEntry("ShowBeforeSave", true));
339
340 KConfigGroup general(KSharedConfig::openConfig(), "General");
341
342 //m_settingsUi.previewDPI->setCurrentItem(general.readEntry("PreviewDPI", "100"), true); // FIXME KF5 is the 'true' parameter still needed?
343 m_settingsUi.previewDPI->setCurrentText(general.readEntry("PreviewDPI", "100"));
344
345 m_settingsUi.setPreviewDPI->setChecked(general.readEntry("SetPreviewDPI", false));
346 if (m_settingsUi.setPreviewDPI->isChecked()) {
347 m_ksanew->setPreviewResolution(m_settingsUi.previewDPI->currentText().toFloat());
348 }
349 else {
350 m_ksanew->setPreviewResolution(0.0);
351 }
352 m_settingsUi.u_disableSelections->setChecked(general.readEntry("DisableAutoSelection", false));
353 m_ksanew->enableAutoSelect(!m_settingsUi.u_disableSelections->isChecked());
354 }
355
showSettingsDialog(void)356 void Skanlite::showSettingsDialog(void)
357 {
358 readSettings();
359
360 // show the dialog
361 if (m_settingsDialog->exec()) {
362 // save the settings
363 KConfigGroup saving(KSharedConfig::openConfig(), "Image Saving");
364 saving.writeEntry("SaveMode", m_settingsUi.saveModeCB->currentIndex());
365 saving.writeEntry("Location", m_settingsUi.saveDirRequester->url());
366 saving.writeEntry("NamePrefix", m_settingsUi.imgPrefix->text());
367 saving.writeEntry("ImgFormat", m_settingsUi.imgFormat->currentData().toString());
368 saving.writeEntry("SetQuality", m_settingsUi.setQuality->isChecked());
369 saving.writeEntry("ImgQuality", m_settingsUi.imgQuality->value());
370 saving.writeEntry("ShowBeforeSave", m_settingsUi.showB4Save->isChecked());
371 saving.sync();
372
373 KConfigGroup general(KSharedConfig::openConfig(), "General");
374 general.writeEntry("PreviewDPI", m_settingsUi.previewDPI->currentText());
375 general.writeEntry("SetPreviewDPI", m_settingsUi.setPreviewDPI->isChecked());
376 general.writeEntry("DisableAutoSelection", m_settingsUi.u_disableSelections->isChecked());
377 general.sync();
378
379 // the previewDPI has to be set here
380 if (m_settingsUi.setPreviewDPI->isChecked()) {
381 m_ksanew->setPreviewResolution(m_settingsUi.previewDPI->currentText().toFloat());
382 }
383 else {
384 // 0.0 means default value.
385 m_ksanew->setPreviewResolution(0.0);
386 }
387 m_ksanew->enableAutoSelect(!m_settingsUi.u_disableSelections->isChecked());
388
389 // pressing OK in the settings dialog means use those settings.
390 m_saveLocation->setFolderUrl(m_settingsUi.saveDirRequester->url());
391 m_saveLocation->setImagePrefix(m_settingsUi.imgPrefix->text());
392 m_saveLocation->setImageFormatIndex(m_settingsUi.imgFormat->currentIndex());
393
394 m_firstImage = true;
395 }
396 else {
397 //Forget Changes
398 readSettings();
399 }
400 }
401
imageReady(const QImage & image)402 void Skanlite::imageReady(const QImage &image)
403 {
404 // save the image data
405 m_img = image;
406 if (m_settingsUi.showB4Save->isChecked() == true) {
407 // show the image in the preview
408 m_showImgDialog->setQImage(&m_img);
409 m_showImgDialog->zoom2Fit();
410 m_showImgDialog->exec();
411 // save has been done as a result of save or then we got cancel
412 }
413 else {
414 saveImage();
415 }
416 }
417
urlExists(const QUrl & url)418 bool urlExists(const QUrl& url)
419 {
420 if (url.isLocalFile()) {
421 if (!QFileInfo::exists(url.toLocalFile())) {
422 return false;
423 }
424 }
425 else {
426 KIO::StatJob *statJob = KIO::statDetails(url, KIO::StatJob::DestinationSide, KIO::StatNoDetails);
427 KJobWidgets::setWindow(statJob, QApplication::activeWindow());
428 if (!statJob->exec()) {
429 return false;
430 }
431 }
432 return true;
433 }
434
saveImage()435 void Skanlite::saveImage()
436 {
437 QUrl dirUrl = m_saveLocation->folderUrl();
438 bool dirExists = urlExists(dirUrl);
439
440 // Ask the first time if we are in "ask on first" mode
441 if (m_settingsUi.saveModeCB->currentIndex() == SaveModeAskFirst) {
442 while (m_firstImage || !dirExists) {
443 m_saveLocation->setOpenRequesterOnShow(!dirExists);
444 if (m_saveLocation->exec() != QFileDialog::Accepted) {
445 m_ksanew->scanCancel(); // In case we are cancelling a document feeder scan
446 return;
447 }
448 dirUrl = m_saveLocation->folderUrl();
449 dirExists = urlExists(dirUrl); // check that we actually got an existing folder
450 m_firstImage = false;
451 }
452 }
453 else if (!dirExists) {
454 // The save-folder from settings does not exist! Use the users home directory.
455 dirUrl = QUrl::fromUserInput(QDir::homePath() + QLatin1Char('/'));
456 m_saveLocation->setFolderUrl(dirUrl);
457 }
458
459 QString prefix = m_saveLocation->imagePrefix();
460 QString imageMimetype = m_saveLocation->imageMimetype();
461 int fileNumber = m_saveLocation->startNumber();
462 QStringList filterList;
463
464 if ((m_img.format() == QImage::Format_Grayscale16) ||
465 (m_img.format() == QImage::Format_RGBX64))
466 {
467 filterList = m_filter16BitList;
468 if (imageMimetype != QLatin1String("image/png") && imageMimetype != QLatin1String("image/tiff")) {
469 imageMimetype = QStringLiteral("image/png");
470 KMessageBox::information(this, i18n("The image will be saved in the PNG format, as the selected image type does not support saving 16 bit color images."));
471 }
472 } else {
473 filterList = m_filterList;
474 }
475
476 // find next available file name for name suggestion
477 QUrl fileUrl;
478 QString fname;
479 for (int i = fileNumber; i <= m_saveLocation->startNumberMax(); ++i) {
480 fname = QStringLiteral("%1%2.%3")
481 .arg(prefix)
482 .arg(i, 4, 10, QLatin1Char('0'))
483 .arg(m_saveLocation->imageSuffix());
484
485 fileUrl = dirUrl;
486 fileUrl.setPath(fileUrl.path() + fname);
487 fileUrl = fileUrl.adjusted(QUrl::NormalizePathSegments);
488 if (!urlExists(fileUrl)) {
489 break;
490 }
491 }
492
493 if (m_settingsUi.saveModeCB->currentIndex() == SaveModeManual) {
494 // prepare the save dialog
495 QFileDialog saveDialog(this, i18n("New Image File Name"));
496 saveDialog.setAcceptMode(QFileDialog::AcceptSave);
497 saveDialog.setFileMode(QFileDialog::AnyFile);
498
499 // ask for a filename if requested.
500 saveDialog.setDirectoryUrl(fileUrl.adjusted(QUrl::RemoveFilename));
501 saveDialog.selectUrl(fileUrl);
502 // NOTE it is probably a bug that both setDirectoryUrl and selectUrl have
503 // to be set to get remote urls to work
504
505 saveDialog.setMimeTypeFilters(filterList);
506 saveDialog.selectMimeTypeFilter(imageMimetype);
507
508 if (saveDialog.exec() != QFileDialog::Accepted) {
509 return;
510 }
511
512 fileUrl = saveDialog.selectedUrls().at(0);
513 }
514
515 m_firstImage = false;
516
517 // Get the quality
518 int quality = -1;
519 if (m_settingsUi.setQuality->isChecked()) {
520 quality = m_settingsUi.imgQuality->value();
521 }
522
523 QString localName;
524 QString suffix = QFileInfo(fileUrl.fileName()).suffix();
525 QString fileFormat;
526 if (suffix.isEmpty()) {
527 fileFormat = QStringLiteral("png");
528 }
529 if (suffix == QLatin1String("pdf")) {
530 fileFormat = QStringLiteral("pdf");
531 }
532
533 if (!fileUrl.isLocalFile()) {
534 QTemporaryFile tmp;
535 tmp.open();
536 if (suffix.isEmpty()) {
537 localName = tmp.fileName();
538 }
539 else {
540 localName = QStringLiteral("%1.%2").arg(tmp.fileName(), suffix);
541 }
542 tmp.close(); // we just want the filename
543 }
544 else {
545 localName = fileUrl.toLocalFile();
546 }
547
548 SkanliteImageSaver *imageSaver = new SkanliteImageSaver(this);
549 connect(imageSaver, &SkanliteImageSaver::imageSaved, this, &Skanlite::imageSaved);
550
551 imageSaver->saveQImage(fileUrl, localName, m_img, fileFormat, quality);
552
553 m_showImgDialog->blockSignals(true);
554 m_showImgDialog->close(); // calling close() on a closed window does nothing.
555 // NOTE we need to block the signals since close() will emit rejected()
556 m_showImgDialog->blockSignals(false);
557
558 // Disable parts of the interface and indicate that we are saving the image
559 m_currentSaveUrl = fileUrl;
560 m_ksanew->setDisabled(true);
561 m_saveProgressBar->setMaximum(0);
562 m_saveProgressBar->setValue(0);
563 m_saveProgressBar->setVisible(true);
564 m_saveUpdateTimer.start();
565
566 // Save the file base name without number
567 QString baseName = QFileInfo(fileUrl.fileName()).completeBaseName();
568 while ((!baseName.isEmpty()) && (baseName[baseName.size() - 1].isNumber())) {
569 baseName.remove(baseName.size() - 1, 1);
570 }
571 m_saveLocation->setImagePrefix(baseName);
572
573 // Save the number
574 if (fileNumber) {
575 m_saveLocation->setStartNumber(fileNumber + 1);
576 }
577
578 if (m_settingsUi.saveModeCB->currentIndex() == SaveModeManual) {
579 // Save last used dir, prefix and suffix.
580 m_saveLocation->setFolderUrl(KIO::upUrl(fileUrl));
581 m_saveLocation->setImageFormat(QFileInfo(fileUrl.fileName()).suffix());
582 }
583
584
585 }
586
updateSaveProgress()587 void Skanlite::updateSaveProgress()
588 {
589 QFileInfo saveInfo(m_currentSaveUrl.toLocalFile());
590 quint64 size = saveInfo.size()/1024;
591 m_saveProgressBar->setMaximum(size);
592 m_saveProgressBar->setValue(size);
593 }
594
imageSaved(const QUrl & fileUrl,const QString & localName,bool success)595 void Skanlite::imageSaved(const QUrl &fileUrl, const QString &localName, bool success)
596 {
597 if (!success) {
598 perrorMessageBox(i18n("Failed to save image"));
599 return;
600 }
601
602 if (!fileUrl.isLocalFile()) {
603 QFile tmpFile(localName);
604 tmpFile.open(QIODevice::ReadOnly);
605 auto uploadJob = KIO::storedPut(&tmpFile, fileUrl, -1);
606 KJobWidgets::setWindow(uploadJob, QApplication::activeWindow());
607 bool ok = uploadJob->exec();
608 tmpFile.close();
609 tmpFile.remove();
610 if (!ok) {
611 KMessageBox::sorry(nullptr, i18n("Failed to upload image"));
612 }
613 else {
614 Q_EMIT m_dbusInterface.imageSaved(fileUrl.toString());
615 }
616 }
617 else {
618 Q_EMIT m_dbusInterface.imageSaved(localName);
619 }
620 m_ksanew->setDisabled(false);
621 m_ksanew->setFocus();
622 m_saveUpdateTimer.stop();
623 m_saveProgressBar->setVisible(false);
624
625 SkanliteImageSaver *imageSaver = qobject_cast<SkanliteImageSaver *>(sender());
626 if (imageSaver) {
627 imageSaver->deleteLater();
628 }
629 }
630
showAboutDialog(void)631 void Skanlite::showAboutDialog(void)
632 {
633 KAboutApplicationDialog(KAboutData::applicationData()).exec();
634 }
635
writeScannerOptions(const QString & groupName,const QMap<QString,QString> & opts)636 void writeScannerOptions(const QString &groupName, const QMap <QString, QString> &opts)
637 {
638 KConfigGroup options(KSharedConfig::openConfig(), groupName);
639 QMap<QString, QString>::const_iterator it = opts.constBegin();
640 while (it != opts.constEnd()) {
641 options.writeEntry(it.key(), it.value());
642 ++it;
643 }
644 options.sync();
645 }
646
readScannerOptions(const QString & groupName,QMap<QString,QString> & opts)647 void readScannerOptions(const QString &groupName, QMap <QString, QString> &opts)
648 {
649 KConfigGroup scannerOptions(KSharedConfig::openConfig(), groupName);
650 opts = scannerOptions.entryMap();
651 }
652
saveScannerOptions()653 void Skanlite::saveScannerOptions()
654 {
655 KConfigGroup saving(KSharedConfig::openConfig(), "Image Saving");
656 saving.writeEntry("NumberStartsFrom", m_saveLocation->startNumber());
657
658 if (!m_ksanew) {
659 return;
660 }
661
662 if (!m_deviceName.isEmpty()) {
663 KConfigGroup options(KSharedConfig::openConfig(), QStringLiteral("Options For %1").arg(m_deviceName));
664 QMap <QString, QString> opts;
665 m_ksanew->getOptVals(opts);
666 writeScannerOptions(QStringLiteral("Options For %1").arg(m_deviceName), opts);
667 }
668 }
669
defaultScannerOptions()670 void Skanlite::defaultScannerOptions()
671 {
672 if (!m_ksanew) {
673 return;
674 }
675
676 applyScannerOptions(m_defaultScanOpts);
677 }
678
applyScannerOptions(const QMap<QString,QString> & opts)679 void Skanlite::applyScannerOptions(const QMap <QString, QString> &opts)
680 {
681 if (m_ksanew->setOptVals(opts) == -1) {
682 m_pendingApplyScanOpts = opts;
683 } else {
684 m_pendingApplyScanOpts.clear();
685 }
686 }
687
loadScannerOptions()688 void Skanlite::loadScannerOptions()
689 {
690 if (!m_deviceName.isEmpty()) {
691 KConfigGroup saving(KSharedConfig::openConfig(), "Image Saving");
692 m_saveLocation->setStartNumber(saving.readEntry("NumberStartsFrom", 1));
693
694 if (!m_ksanew) {
695 return;
696 }
697
698 QMap <QString, QString> opts;
699 readScannerOptions(QStringLiteral("Options For %1").arg(m_deviceName), opts);
700 applyScannerOptions(opts);
701 }
702 }
703
alertUser(int type,const QString & strStatus)704 void Skanlite::alertUser(int type, const QString &strStatus)
705 {
706 switch (type) {
707 case KSaneWidget::ErrorGeneral:
708 KMessageBox::sorry(nullptr, strStatus, QStringLiteral("Skanlite Test"));
709 break;
710 default:
711 KMessageBox::information(nullptr, strStatus, QStringLiteral("Skanlite Test"));
712 }
713 }
714
buttonPressed(const QString & optionName,const QString & optionLabel,bool pressed)715 void Skanlite::buttonPressed(const QString &optionName, const QString &optionLabel, bool pressed)
716 {
717 qCDebug(SKANLITE_LOG) << "Button" << optionName << optionLabel << ((pressed) ? "pressed" : "released");
718 }
719
720 // D-Bus interface related helper functions
721
serializeScannerOptions(const QMap<QString,QString> & opts)722 QStringList serializeScannerOptions(const QMap<QString, QString> &opts)
723 {
724 QStringList sl;
725 QMap<QString, QString>::const_iterator it = opts.constBegin();
726 while (it != opts.constEnd()) {
727 sl.append(it.key() + QLatin1Char('=') + it.value());
728 ++it;
729 }
730 return sl;
731 }
732
deserializeScannerOptions(const QStringList & settings,QMap<QString,QString> & opts)733 void deserializeScannerOptions(const QStringList &settings, QMap<QString, QString> &opts)
734 {
735 for (const QString &s : settings) {
736 int i = s.lastIndexOf(QLatin1Char('='));
737 opts[s.left(i)] = s.right(s.length()-i-1);
738 }
739 }
740
741 static const auto selectionSettings = { QLatin1String("tl-x"), QLatin1String("tl-y"),
742 QLatin1String("br-x"), QLatin1String("br-y") };
743
filterSelectionSettings(QMap<QString,QString> & opts)744 void filterSelectionSettings(QMap<QString, QString> &opts)
745 {
746 for (const auto &s : selectionSettings) {
747 opts.remove(s);
748 }
749 }
750
containsSelectionSettings(const QMap<QString,QString> & opts)751 bool containsSelectionSettings(const QMap<QString, QString> &opts)
752 {
753 for (const auto &s : selectionSettings) {
754 if (opts.contains(s)) {
755 return true;
756 }
757 }
758 return false;
759 }
760
processSelectionOptions(QMap<QString,QString> & opts,bool ignoreSelection)761 void Skanlite::processSelectionOptions(QMap<QString, QString> &opts, bool ignoreSelection)
762 {
763 if (ignoreSelection) {
764 filterSelectionSettings(opts);
765 }
766 else {
767 if (containsSelectionSettings(opts)) { // make sure we really have selection to apply
768 m_ksanew->setSelection(QPointF(0,0), QPointF(1,1)); // bcs settings have no effect if nothing was selected beforehand (Bug 377009)
769 }
770 }
771 }
772
updateWindowTitle(const QString & deviceName,const QString & deviceVendor,const QString & deviceModel)773 void Skanlite::updateWindowTitle(const QString &deviceName, const QString &deviceVendor, const QString &deviceModel)
774 {
775 if (!deviceVendor.isEmpty() && !deviceModel.isEmpty()) {
776 setWindowTitle(i18nc("@title:window %1 = scanner maker, %2 = scanner model", "%1 %2 - Skanlite", deviceVendor, deviceModel));
777 } else if (!deviceName.isEmpty()) {
778 setWindowTitle(i18nc("@title:window %1 = scanner device", "%1 - Skanlite", deviceName));
779 } else {
780 setWindowTitle(i18n("Skanlite"));
781 }
782 }
783
784 // D-Bus interface related slots
785
getScannerOptions()786 void Skanlite::getScannerOptions()
787 {
788 if (!m_deviceName.isEmpty()) {
789 QMap <QString, QString> opts;
790 m_ksanew->getOptVals(opts);
791 m_dbusInterface.setReply(serializeScannerOptions(opts));
792 }
793 }
794
setScannerOptions(const QStringList & options,bool ignoreSelection)795 void Skanlite::setScannerOptions(const QStringList &options, bool ignoreSelection)
796 {
797 if (!m_deviceName.isEmpty()) {
798 QMap <QString, QString> opts;
799 deserializeScannerOptions(options, opts);
800 processSelectionOptions(opts, ignoreSelection);
801 applyScannerOptions(opts);
802 }
803 }
804
805
getDefaultScannerOptions()806 void Skanlite::getDefaultScannerOptions()
807 {
808 m_dbusInterface.setReply(serializeScannerOptions(m_defaultScanOpts));
809 }
810
811 static const QLatin1String defaultProfileGroup("Options For %1 - Profile %2"); // 1 - device, 2 - arg
812
saveScannerOptionsToProfile(const QStringList & options,const QString & profile,bool ignoreSelection)813 void Skanlite::saveScannerOptionsToProfile(const QStringList &options, const QString &profile, bool ignoreSelection)
814 {
815 if (!m_deviceName.isEmpty()) {
816 QMap <QString, QString> opts;
817 deserializeScannerOptions(options, opts);
818 processSelectionOptions(opts, ignoreSelection);
819 writeScannerOptions(QString(defaultProfileGroup).arg(m_deviceName, profile), opts);
820 }
821 }
822
switchToProfile(const QString & profile,bool ignoreSelection)823 void Skanlite::switchToProfile(const QString &profile, bool ignoreSelection)
824 {
825 if (!m_deviceName.isEmpty()) {
826 QMap <QString, QString> opts;
827 readScannerOptions(QString(defaultProfileGroup).arg(m_deviceName, profile), opts);
828
829 if (opts.empty()) {
830 opts = m_defaultScanOpts;
831 }
832
833 processSelectionOptions(opts, ignoreSelection);
834 applyScannerOptions(opts);
835 }
836 }
837
getDeviceName()838 void Skanlite::getDeviceName()
839 {
840 if (!m_deviceName.isEmpty()) {
841 m_dbusInterface.setReply(QStringList(m_deviceName));
842 }
843 }
844
getSelection()845 void Skanlite::getSelection()
846 {
847 if (!m_deviceName.isEmpty()) {
848 QMap <QString, QString> opts;
849 m_ksanew->getOptVals(opts);
850
851 QStringList reply;
852 for (const auto &key : selectionSettings ) {
853 if (opts.contains(key)) {
854 reply.append(key + QLatin1Char('=') + opts[key]);
855 }
856 }
857 m_dbusInterface.setReply(reply);
858 }
859 }
860
setSelection(const QStringList & options)861 void Skanlite::setSelection(const QStringList &options)
862 { // here options contains selection related subset of options
863 setScannerOptions(options, false);
864 }
865