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