Warning, file /graphics/skanlite/src/skanlite.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /* ============================================================
0002 *
0003 * SPDX-FileCopyrightText: 2007-2012 Kåre Särs <kare.sars@iki .fi>
0004 * SPDX-FileCopyrightText: 2009 Arseniy Lartsev <receive-spam at yandex dot ru>
0005 * SPDX-FileCopyrightText: 2014 Gregor Mitsch : port to KDE5 frameworks
0006 *
0007 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0008 *
0009 * ============================================================ */
0010 
0011 #include "skanlite.h"
0012 
0013 #include "SaveLocation.h"
0014 #include "showimagedialog.h"
0015 #include "SkanliteImageSaver.h"
0016 
0017 #include <QApplication>
0018 #include <QScrollArea>
0019 #include <QStringList>
0020 #include <QFileDialog>
0021 #include <QDialogButtonBox>
0022 #include <QComboBox>
0023 #include <QMessageBox>
0024 #include <QTemporaryFile>
0025 #include <QImageWriter>
0026 #include <QMimeType>
0027 #include <QMimeDatabase>
0028 #include <QCloseEvent>
0029 #include <QProgressBar>
0030 
0031 #include <kio_version.h>
0032 #include <KAboutData>
0033 #include <KAboutApplicationDialog>
0034 #include <KLocalizedString>
0035 #include <KMessageBox>
0036 #include <KIO/StatJob>
0037 #include <KIO/Job>
0038 #include <KIO/StoredTransferJob>
0039 #include <KJobWidgets>
0040 #include <kio/global.h>
0041 #include <KSharedConfig>
0042 #include <KConfigGroup>
0043 #include <KHelpClient>
0044 #include <KHelpMenu>
0045 #include <KSaneWidget>
0046 
0047 #include <skanlite_debug.h>
0048 
0049 #include <errno.h>
0050 using namespace KSaneIface;
0051 Skanlite::Skanlite(const QString &device, QWidget *parent)
0052     : QDialog(parent)
0053     , m_dbusInterface(this)
0054 {
0055     QVBoxLayout *mainLayout = new QVBoxLayout(this);
0056 
0057     QDialogButtonBox *dlgButtonBoxBottom = new QDialogButtonBox(this);
0058     dlgButtonBoxBottom->setStandardButtons(QDialogButtonBox::Help);
0059     dlgButtonBoxBottom->button(QDialogButtonBox::Help)->setAutoDefault(false);
0060 
0061     KHelpMenu *helpMenu = new KHelpMenu(this, KAboutData::applicationData(), false);
0062     dlgButtonBoxBottom->button(QDialogButtonBox::Help)->setMenu(helpMenu->menu());
0063 
0064     QPushButton *btnConfigure = dlgButtonBoxBottom->addButton(i18n("Configure..."), QDialogButtonBox::ButtonRole::ResetRole);
0065     btnConfigure->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
0066     btnConfigure->setAutoDefault(false);
0067 
0068     m_btnReselectDevice = dlgButtonBoxBottom->addButton(i18n("Reselect scanner device"), QDialogButtonBox::ButtonRole::ActionRole);
0069     m_btnReselectDevice->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
0070     m_btnReselectDevice->setAutoDefault(false);
0071 
0072     m_firstImage = true;
0073 
0074     m_ksanew = new KSaneIface::KSaneWidget(this);
0075     connect(m_ksanew, &KSaneWidget::scannedImageReady, this, &Skanlite::imageReady);
0076     connect(m_ksanew, &KSaneWidget::userMessage, this, &Skanlite::alertUser);
0077     connect(m_ksanew, &KSaneWidget::buttonPressed, this, &Skanlite::buttonPressed);
0078     connect(m_ksanew, &KSaneWidget::scanProgress, this, [this](int percent){
0079         if (percent < 100) {
0080             m_btnReselectDevice->setEnabled(false);
0081         }
0082     });
0083     connect(m_ksanew, &KSaneWidget::scanDone, this, [this](){
0084         if (!m_pendingApplyScanOpts.isEmpty()) {
0085             applyScannerOptions(m_pendingApplyScanOpts);
0086         }
0087         m_btnReselectDevice->setEnabled(true);
0088     });
0089 
0090     m_saveProgressBar = new QProgressBar(this);
0091     m_saveProgressBar->setVisible(false);
0092     m_saveProgressBar->setFormat(i18n("Saving: %v kB"));
0093     m_saveProgressBar->setTextVisible(true);
0094 
0095     m_saveUpdateTimer.setInterval(200);
0096     m_saveUpdateTimer.setSingleShot(false);
0097     connect(&m_saveUpdateTimer, &QTimer::timeout, this, &Skanlite::updateSaveProgress);
0098 
0099     mainLayout->addWidget(m_ksanew);
0100     mainLayout->addWidget(m_saveProgressBar);
0101     mainLayout->addWidget(dlgButtonBoxBottom);
0102 
0103     // read the size here...
0104     KConfigGroup window(KSharedConfig::openStateConfig(), QStringLiteral("Window"));
0105     QSize rect = window.readEntry("Geometry", QSize(740, 400));
0106     resize(rect);
0107 
0108     // open scanner device from command line, otherwise try remembered one
0109     QString deviceName;
0110     QString deviceVendor;
0111     QString deviceModel;
0112     if (device.isEmpty()) {
0113         KConfigGroup general(KSharedConfig::openStateConfig(), QStringLiteral("General"));
0114         deviceName = general.readEntry(QStringLiteral("deviceName"));
0115         deviceVendor = general.readEntry(QStringLiteral("deviceVendor"));
0116         deviceModel = general.readEntry(QStringLiteral("deviceModel"));
0117     } else {
0118         deviceName = device;
0119     }
0120 
0121     connect(dlgButtonBoxBottom, &QDialogButtonBox::rejected, this, &QDialog::close);
0122     connect(this, &QDialog::finished, this, &Skanlite::saveWindowSize);
0123     connect(this, &QDialog::finished, this, &Skanlite::saveScannerDevice);
0124     connect(this, &QDialog::finished, this, &Skanlite::saveScannerOptions);
0125     connect(btnConfigure, &QPushButton::clicked, this, &Skanlite::showSettingsDialog);
0126     connect(m_btnReselectDevice, &QPushButton::clicked, this, &Skanlite::reselectScannerDevice);
0127     connect(dlgButtonBoxBottom, &QDialogButtonBox::helpRequested, this, &Skanlite::showHelp);
0128 
0129     //
0130     // Create the settings dialog
0131     //
0132     {
0133         m_settingsDialog = new QDialog(this);
0134 
0135         QVBoxLayout *mainLayout = new QVBoxLayout(m_settingsDialog);
0136 
0137         QWidget *settingsWidget = new QWidget(m_settingsDialog);
0138         m_settingsUi.setupUi(settingsWidget);
0139         m_settingsUi.revertOptions->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo")));
0140         m_saveLocation = new SaveLocation(this);
0141 
0142         // add the supported image types
0143         const QList<QByteArray> tmpList = QImageWriter::supportedMimeTypes();
0144         m_filterList.clear();
0145         for (const auto &ba : tmpList) {
0146             if (ba.isEmpty()) {
0147                 continue;
0148             }
0149             m_filterList.append(QString::fromLatin1(ba));
0150         }
0151 
0152         qCDebug(SKANLITE_LOG) << m_filterList;
0153 
0154         // Put first class citizens at first place
0155         m_filterList.removeAll(QStringLiteral("image/jpeg"));
0156         m_filterList.removeAll(QStringLiteral("image/tiff"));
0157         m_filterList.removeAll(QStringLiteral("image/png"));
0158         m_filterList.insert(0, QStringLiteral("image/png"));
0159         m_filterList.insert(1, QStringLiteral("image/jpeg"));
0160         m_filterList.insert(2, QStringLiteral("image/tiff"));
0161         m_filterList.insert(3, QStringLiteral("application/pdf"));
0162 
0163         m_filter16BitList << QStringLiteral("image/png");
0164         m_filter16BitList << QStringLiteral("image/tiff");
0165 
0166         // fill m_filterList (...)
0167         {
0168             QStringList namedMimeTypes;
0169             for (const QString &mimeStr : qAsConst(m_filterList)) {
0170                 QMimeType mimeType = QMimeDatabase().mimeTypeForName(mimeStr);
0171                 namedMimeTypes.append(mimeType.name());
0172 
0173                 m_settingsUi.imgFormat->addItem(mimeType.preferredSuffix(), mimeType.name());
0174                 m_saveLocation->addImageFormat(mimeType.preferredSuffix(), mimeType.name());
0175             }
0176             m_filterList << std::move(namedMimeTypes);
0177         }
0178 
0179         mainLayout->addWidget(settingsWidget);
0180 
0181         QDialogButtonBox *dlgButtonBoxBottom = new QDialogButtonBox(this);
0182         dlgButtonBoxBottom->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Close);
0183         connect(dlgButtonBoxBottom, &QDialogButtonBox::accepted, m_settingsDialog, &QDialog::accept);
0184         connect(dlgButtonBoxBottom, &QDialogButtonBox::rejected, m_settingsDialog, &QDialog::reject);
0185 
0186         mainLayout->addWidget(dlgButtonBoxBottom);
0187 
0188         m_settingsDialog->setWindowTitle(i18n("Skanlite Settings"));
0189 
0190         connect(m_settingsUi.revertOptions, &QPushButton::clicked, this, &Skanlite::defaultScannerOptions);
0191         readSettings();
0192 
0193         // default directory for the save dialog
0194         m_saveLocation->setFolderUrl(m_settingsUi.saveDirRequester->url());
0195         m_saveLocation->setImagePrefix(m_settingsUi.imgPrefix->text());
0196         m_saveLocation->setImageFormatIndex(m_settingsUi.imgFormat->currentIndex());
0197     }
0198 
0199     // open the scan device
0200     if (!m_ksanew->openDevice(deviceName)) {
0201         QString dev = m_ksanew->selectDevice(nullptr);
0202         if (dev.isEmpty()) {
0203             // either no scanner was found or then cancel was pressed.
0204             exit(0);
0205         }
0206         if (!m_ksanew->openDevice(dev)) {
0207             // could not open a scanner
0208             KMessageBox::error(nullptr, i18n("Opening the selected scanner failed."));
0209             exit(1);
0210         }
0211         else {
0212             updateWindowTitle(dev, m_ksanew->deviceVendor(), m_ksanew->deviceModel());
0213             m_deviceName = dev;
0214         }
0215     }
0216     else {
0217         if (deviceVendor.isEmpty()) {
0218             updateWindowTitle(deviceName);
0219         } else {
0220             updateWindowTitle(deviceName, deviceVendor, deviceModel);
0221         }
0222         m_deviceName = deviceName;
0223         m_deviceModel = deviceModel;
0224         m_deviceVendor = deviceVendor;
0225     }
0226 
0227     // prepare the Show Image Dialog
0228     m_showImgDialog = new ShowImageDialog(this);
0229     connect(m_showImgDialog, &ShowImageDialog::saveRequested, this, &Skanlite::saveImage);
0230     connect(m_showImgDialog, &ShowImageDialog::rejected, m_ksanew, &KSaneWidget::cancelScan);
0231 
0232     // save the default sane options for later use
0233     m_ksanew->getOptionValues(m_defaultScanOpts);
0234 
0235     // load saved options
0236     loadScannerOptions();
0237 
0238     m_firstImage = true;
0239     m_ksanew->setFocus();
0240 
0241     if (m_dbusInterface.setupDBusInterface()) {
0242         // D-Bus related slots
0243         connect(&m_dbusInterface, &DBusInterface::requestedScan, m_ksanew, &KSaneWidget::startScan);
0244         connect(&m_dbusInterface, &DBusInterface::requestedPreview, m_ksanew, &KSaneWidget::startPreviewScan);
0245         connect(&m_dbusInterface, &DBusInterface::requestedScanCancel, m_ksanew, &KSaneWidget::cancelScan);
0246         connect(&m_dbusInterface, &DBusInterface::requestedSetScannerOptions, this, &Skanlite::setScannerOptions);
0247         connect(&m_dbusInterface, &DBusInterface::requestedSetSelection, this, &Skanlite::setSelection);
0248 
0249         // D-Bus related slots below must be Qt::DirectConnection to simplify return value forwarding via DBusInterface
0250         connect(&m_dbusInterface, &DBusInterface::requestedGetScannerOptions, this, &Skanlite::getScannerOptions, Qt::DirectConnection);
0251         connect(&m_dbusInterface, &DBusInterface::requestedDefaultScannerOptions, this, &Skanlite::getDefaultScannerOptions, Qt::DirectConnection);
0252         connect(&m_dbusInterface, &DBusInterface::requestedDeviceName, this, &Skanlite::getDeviceName, Qt::DirectConnection);
0253         connect(&m_dbusInterface, &DBusInterface::requestedSaveScannerOptionsToProfile, this, &Skanlite::saveScannerOptionsToProfile, Qt::DirectConnection);
0254         connect(&m_dbusInterface, &DBusInterface::requestedSwitchToProfile, this, &Skanlite::switchToProfile, Qt::DirectConnection);
0255         connect(&m_dbusInterface, &DBusInterface::requestedGetSelection, this, &Skanlite::getSelection, Qt::DirectConnection);
0256 
0257         // D-Bus related signals
0258         connect(m_ksanew, &KSaneWidget::scanDone, &m_dbusInterface, &DBusInterface::scanDone);
0259         connect(m_ksanew, &KSaneWidget::userMessage, &m_dbusInterface, &DBusInterface::userMessage);
0260         connect(m_ksanew, &KSaneWidget::scanProgress, &m_dbusInterface, &DBusInterface::scanProgress);
0261         connect(m_ksanew, &KSaneWidget::buttonPressed, &m_dbusInterface, &DBusInterface::buttonPressed);
0262     }
0263     else {
0264         // keep working without dbus
0265     }
0266 }
0267 
0268 void Skanlite::showHelp()
0269 {
0270     KHelpClient::invokeHelp(QStringLiteral("index"), QStringLiteral("skanlite"));
0271 }
0272 
0273 void Skanlite::closeEvent(QCloseEvent *event)
0274 {
0275     saveWindowSize();
0276     saveScannerDevice();
0277     saveScannerOptions();
0278     event->accept();
0279 }
0280 
0281 void Skanlite::saveWindowSize()
0282 {
0283     KConfigGroup window(KSharedConfig::openStateConfig(), QStringLiteral("Window"));
0284     window.writeEntry("Geometry", size());
0285     window.sync();
0286 }
0287 
0288 void Skanlite::saveScannerDevice()
0289 {
0290     KConfigGroup general(KSharedConfig::openStateConfig(), QStringLiteral("General"));
0291     general.writeEntry(QStringLiteral("deviceName"), m_deviceName);
0292     general.writeEntry(QStringLiteral("deviceModel"), m_deviceModel);
0293     general.writeEntry(QStringLiteral("deviceVendor"), m_deviceVendor);
0294     general.sync();
0295 }
0296 
0297 void Skanlite::reselectScannerDevice()
0298 {
0299     m_ksanew->closeDevice();
0300     m_deviceName.clear();
0301     m_deviceVendor.clear();
0302     m_deviceModel.clear();
0303     // open the scan device dialog
0304     QString dev = m_ksanew->selectDevice(nullptr);
0305     if (m_ksanew->openDevice(dev) == false) {
0306         // could not open a scanner
0307         KMessageBox::error(nullptr, i18n("Opening the selected scanner failed."));
0308     }
0309     else {
0310         updateWindowTitle(dev, m_ksanew->deviceVendor(), m_ksanew->deviceModel());
0311         m_deviceName = dev;
0312         m_deviceModel = m_ksanew->deviceModel();
0313         m_deviceVendor = m_ksanew->deviceVendor();
0314     }
0315 }
0316 
0317 // Pops up message box similar to what perror() would print
0318 //************************************************************
0319 static void perrorMessageBox(const QString &text)
0320 {
0321     if (errno != 0) {
0322         KMessageBox::error(nullptr, i18n("%1: %2", text, QString::fromLocal8Bit(strerror(errno))));
0323     }
0324     else {
0325         KMessageBox::error(nullptr, text);
0326     }
0327 }
0328 
0329 void Skanlite::readSettings(void)
0330 {
0331     // enable the widgets to allow modifying
0332     m_settingsUi.setQuality->setChecked(true);
0333     m_settingsUi.setPreviewDPI->setChecked(true);
0334 
0335     // read the saved parameters
0336     KConfigGroup saving(KSharedConfig::openConfig(), QStringLiteral("Image Saving"));
0337     m_settingsUi.saveModeCB->setCurrentIndex(saving.readEntry("SaveMode", (int)SaveModeManual));
0338     if (m_settingsUi.saveModeCB->currentIndex() != SaveModeAskFirst) {
0339         m_firstImage = false;
0340     }
0341     m_settingsUi.saveDirRequester->setUrl(saving.readEntry("Location", QUrl(QDir::homePath())));
0342     m_settingsUi.imgPrefix->setText(saving.readEntry("NamePrefix", i18nc("prefix for auto naming", "Image-")));
0343     QString format = saving.readEntry("ImgFormat", "image/png");
0344     int index = m_settingsUi.imgFormat->findData(format);
0345     if (index >= 0) {
0346         m_settingsUi.imgFormat->setCurrentIndex(index);
0347     }
0348 
0349     m_settingsUi.imgQuality->setValue(saving.readEntry("ImgQuality", 90));
0350     m_settingsUi.setQuality->setChecked(saving.readEntry("SetQuality", false));
0351     m_settingsUi.showB4Save->setChecked(saving.readEntry("ShowBeforeSave", true));
0352 
0353     KConfigGroup general(KSharedConfig::openConfig(), QStringLiteral("General"));
0354 
0355     //m_settingsUi.previewDPI->setCurrentItem(general.readEntry("PreviewDPI", "100"), true); // FIXME KF5 is the 'true' parameter still needed?
0356     m_settingsUi.previewDPI->setCurrentText(general.readEntry("PreviewDPI", "100"));
0357 
0358     m_settingsUi.setPreviewDPI->setChecked(general.readEntry("SetPreviewDPI", false));
0359     if (m_settingsUi.setPreviewDPI->isChecked()) {
0360         m_ksanew->setPreviewResolution(m_settingsUi.previewDPI->currentText().toFloat());
0361     }
0362     else {
0363         m_ksanew->setPreviewResolution(0.0);
0364     }
0365     m_settingsUi.u_disableSelections->setChecked(general.readEntry("DisableAutoSelection", false));
0366     m_ksanew->enableAutoSelect(!m_settingsUi.u_disableSelections->isChecked());
0367 }
0368 
0369 void Skanlite::showSettingsDialog(void)
0370 {
0371     readSettings();
0372 
0373     // show the dialog
0374     if (m_settingsDialog->exec()) {
0375         // save the settings
0376         KConfigGroup saving(KSharedConfig::openConfig(), QStringLiteral("Image Saving"));
0377         saving.writeEntry("SaveMode", m_settingsUi.saveModeCB->currentIndex());
0378         saving.writeEntry("Location", m_settingsUi.saveDirRequester->url());
0379         saving.writeEntry("NamePrefix", m_settingsUi.imgPrefix->text());
0380         saving.writeEntry("ImgFormat", m_settingsUi.imgFormat->currentData().toString());
0381         saving.writeEntry("SetQuality", m_settingsUi.setQuality->isChecked());
0382         saving.writeEntry("ImgQuality", m_settingsUi.imgQuality->value());
0383         saving.writeEntry("ShowBeforeSave", m_settingsUi.showB4Save->isChecked());
0384         saving.sync();
0385 
0386         KConfigGroup general(KSharedConfig::openConfig(), QStringLiteral("General"));
0387         general.writeEntry("PreviewDPI", m_settingsUi.previewDPI->currentText());
0388         general.writeEntry("SetPreviewDPI", m_settingsUi.setPreviewDPI->isChecked());
0389         general.writeEntry("DisableAutoSelection", m_settingsUi.u_disableSelections->isChecked());
0390         general.sync();
0391 
0392         // the previewDPI has to be set here
0393         if (m_settingsUi.setPreviewDPI->isChecked()) {
0394             m_ksanew->setPreviewResolution(m_settingsUi.previewDPI->currentText().toFloat());
0395         }
0396         else {
0397             // 0.0 means default value.
0398             m_ksanew->setPreviewResolution(0.0);
0399         }
0400         m_ksanew->enableAutoSelect(!m_settingsUi.u_disableSelections->isChecked());
0401 
0402         // pressing OK in the settings dialog means use those settings.
0403         m_saveLocation->setFolderUrl(m_settingsUi.saveDirRequester->url());
0404         m_saveLocation->setImagePrefix(m_settingsUi.imgPrefix->text());
0405         m_saveLocation->setImageFormatIndex(m_settingsUi.imgFormat->currentIndex());
0406 
0407         m_firstImage = true;
0408     }
0409     else {
0410         //Forget Changes
0411         readSettings();
0412     }
0413 }
0414 
0415 void Skanlite::imageReady(const QImage &image)
0416 {
0417     // save the image data
0418     m_img = image;
0419     if (m_settingsUi.showB4Save->isChecked() == true) {
0420         // show the image in the preview
0421         m_showImgDialog->setQImage(&m_img);
0422         m_showImgDialog->zoom2Fit();
0423         m_showImgDialog->exec();
0424         // save has been done as a result of save or then we got cancel
0425     }
0426     else {
0427         saveImage();
0428     }
0429 }
0430 
0431 bool urlExists(const QUrl& url)
0432 {
0433     if (url.isLocalFile()) {
0434         if (!QFileInfo::exists(url.toLocalFile())) {
0435             return false;
0436         }
0437     }
0438     else {
0439 #if KIO_VERSION >= QT_VERSION_CHECK(5, 240, 0)
0440         KIO::StatJob *statJob = KIO::stat(url, KIO::StatJob::DestinationSide, KIO::StatNoDetails);
0441 #else
0442         KIO::StatJob *statJob = KIO::statDetails(url, KIO::StatJob::DestinationSide, KIO::StatNoDetails);
0443 #endif
0444         KJobWidgets::setWindow(statJob, QApplication::activeWindow());
0445         if (!statJob->exec()) {
0446             return false;
0447         }
0448     }
0449     return true;
0450 }
0451 
0452 void Skanlite::saveImage()
0453 {
0454     QUrl dirUrl = m_saveLocation->folderUrl();
0455     bool dirExists = urlExists(dirUrl);
0456 
0457     // Ask the first time if we are in "ask on first" mode
0458     if (m_settingsUi.saveModeCB->currentIndex() == SaveModeAskFirst) {
0459         while (m_firstImage || !dirExists) {
0460             m_saveLocation->setOpenRequesterOnShow(!dirExists);
0461             if (m_saveLocation->exec() != QFileDialog::Accepted) {
0462                 m_ksanew->cancelScan(); // In case we are cancelling a document feeder scan
0463                 return;
0464             }
0465             dirUrl = m_saveLocation->folderUrl();
0466             dirExists = urlExists(dirUrl); // check that we actually got an existing folder
0467             m_firstImage = false;
0468         }
0469     }
0470     else if (!dirExists) {
0471         // The save-folder from settings does not exist! Use the users home directory.
0472         dirUrl = QUrl::fromUserInput(QDir::homePath() + QLatin1Char('/'));
0473         m_saveLocation->setFolderUrl(dirUrl);
0474     }
0475 
0476     QString prefix = m_saveLocation->imagePrefix();
0477     QString imageMimetype = m_saveLocation->imageMimetype();
0478     int fileNumber = m_saveLocation->startNumber();
0479     QStringList filterList;
0480 
0481     if ((m_img.format() == QImage::Format_Grayscale16) ||
0482         (m_img.format() == QImage::Format_RGBX64))
0483     {
0484         filterList = m_filter16BitList;
0485         if (imageMimetype != QLatin1String("image/png") && imageMimetype != QLatin1String("image/tiff")) {
0486             imageMimetype = QStringLiteral("image/png");
0487             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."));
0488         }
0489     } else {
0490         filterList = m_filterList;
0491     }
0492 
0493     // find next available file name for name suggestion
0494     QUrl fileUrl;
0495     QString fname;
0496     for (int i = fileNumber; i <= m_saveLocation->startNumberMax(); ++i) {
0497         fname = QStringLiteral("%1%2.%3")
0498                 .arg(prefix)
0499                 .arg(i, 4, 10, QLatin1Char('0'))
0500                 .arg(m_saveLocation->imageSuffix());
0501 
0502         fileUrl = dirUrl;
0503         fileUrl.setPath(fileUrl.path() + fname);
0504         fileUrl = fileUrl.adjusted(QUrl::NormalizePathSegments);
0505         if (!urlExists(fileUrl)) {
0506             break;
0507         }
0508     }
0509 
0510     if (m_settingsUi.saveModeCB->currentIndex() == SaveModeManual) {
0511         // prepare the save dialog
0512         QFileDialog saveDialog(this, i18n("New Image File Name"));
0513         saveDialog.setAcceptMode(QFileDialog::AcceptSave);
0514         saveDialog.setFileMode(QFileDialog::AnyFile);
0515 
0516         // ask for a filename if requested.
0517         saveDialog.setDirectoryUrl(fileUrl.adjusted(QUrl::RemoveFilename));
0518         saveDialog.selectUrl(fileUrl);
0519         // NOTE it is probably a bug that both setDirectoryUrl and selectUrl have
0520         // to be set to get remote urls to work
0521 
0522         saveDialog.setMimeTypeFilters(filterList);
0523         saveDialog.selectMimeTypeFilter(imageMimetype);
0524 
0525         if (saveDialog.exec() != QFileDialog::Accepted) {
0526             return;
0527         }
0528 
0529         fileUrl = saveDialog.selectedUrls().at(0);
0530     }
0531 
0532     m_firstImage = false;
0533 
0534     // Get the quality
0535     int quality = -1;
0536     if (m_settingsUi.setQuality->isChecked()) {
0537         quality = m_settingsUi.imgQuality->value();
0538     }
0539 
0540     QString localName;
0541     QString suffix = QFileInfo(fileUrl.fileName()).suffix();
0542     QString fileFormat;
0543     if (suffix.isEmpty()) {
0544         fileFormat = QStringLiteral("png");
0545     }
0546     if (suffix == QLatin1String("pdf")) {
0547         fileFormat = QStringLiteral("pdf");
0548     }
0549 
0550     if (!fileUrl.isLocalFile()) {
0551         QTemporaryFile tmp;
0552         tmp.open();
0553         if (suffix.isEmpty()) {
0554             localName = tmp.fileName();
0555         }
0556         else {
0557             localName = QStringLiteral("%1.%2").arg(tmp.fileName(), suffix);
0558         }
0559         tmp.close(); // we just want the filename
0560     }
0561     else {
0562         localName = fileUrl.toLocalFile();
0563     }
0564 
0565     SkanliteImageSaver *imageSaver = new SkanliteImageSaver(this);
0566     connect(imageSaver, &SkanliteImageSaver::imageSaved, this, &Skanlite::imageSaved);
0567     connect(imageSaver, &SkanliteImageSaver::finished, &SkanliteImageSaver::deleteLater);
0568 
0569     imageSaver->saveQImage(fileUrl, localName, m_img, fileFormat, quality);
0570 
0571     m_showImgDialog->blockSignals(true);
0572     m_showImgDialog->close(); // calling close() on a closed window does nothing.
0573     // NOTE we need to block the signals since close() will emit rejected()
0574     m_showImgDialog->blockSignals(false);
0575 
0576     // Disable parts of the interface and indicate that we are saving the image
0577     m_currentSaveUrl = fileUrl;
0578     m_ksanew->setDisabled(true);
0579     m_saveProgressBar->setMaximum(0);
0580     m_saveProgressBar->setValue(0);
0581     m_saveProgressBar->setVisible(true);
0582     m_saveUpdateTimer.start();
0583 
0584     // Save the file base name without number
0585     QString baseName = QFileInfo(fileUrl.fileName()).completeBaseName();
0586     while ((!baseName.isEmpty()) && (baseName[baseName.size() - 1].isNumber())) {
0587         baseName.remove(baseName.size() - 1, 1);
0588     }
0589     m_saveLocation->setImagePrefix(baseName);
0590 
0591     // Save the number
0592     if (fileNumber) {
0593         m_saveLocation->setStartNumber(fileNumber + 1);
0594     }
0595 
0596     if (m_settingsUi.saveModeCB->currentIndex() == SaveModeManual) {
0597         // Save last used dir, prefix and suffix.
0598         m_saveLocation->setFolderUrl(KIO::upUrl(fileUrl));
0599         m_saveLocation->setImageFormat(QFileInfo(fileUrl.fileName()).suffix());
0600     }
0601 
0602 
0603 }
0604 
0605 void Skanlite::updateSaveProgress()
0606 {
0607     QFileInfo saveInfo(m_currentSaveUrl.toLocalFile());
0608     quint64 size = saveInfo.size()/1024;
0609     m_saveProgressBar->setMaximum(size);
0610     m_saveProgressBar->setValue(size);
0611 }
0612 
0613 void Skanlite::imageSaved(const QUrl &fileUrl, const QString &localName, bool success)
0614 {
0615     if (!success) {
0616         perrorMessageBox(i18n("Failed to save image"));
0617         return;
0618     }
0619 
0620     if (!fileUrl.isLocalFile()) {
0621         QFile tmpFile(localName);
0622         tmpFile.open(QIODevice::ReadOnly);
0623         auto uploadJob = KIO::storedPut(&tmpFile, fileUrl, -1);
0624         KJobWidgets::setWindow(uploadJob, QApplication::activeWindow());
0625         bool ok = uploadJob->exec();
0626         tmpFile.close();
0627         tmpFile.remove();
0628         if (!ok) {
0629             KMessageBox::error(nullptr, i18n("Failed to upload image"));
0630         }
0631         else {
0632             Q_EMIT m_dbusInterface.imageSaved(fileUrl.toString());
0633         }
0634     }
0635     else {
0636         Q_EMIT m_dbusInterface.imageSaved(localName);
0637     }
0638     m_ksanew->setDisabled(false);
0639     m_ksanew->setFocus();
0640     m_saveUpdateTimer.stop();
0641     m_saveProgressBar->setVisible(false);
0642 }
0643 
0644 void writeScannerOptions(const QString &groupName, const QMap <QString, QString> &opts)
0645 {
0646     KConfigGroup options(KSharedConfig::openStateConfig(), groupName);
0647     QMap<QString, QString>::const_iterator it = opts.constBegin();
0648     while (it != opts.constEnd()) {
0649         options.writeEntry(it.key(), it.value());
0650         ++it;
0651     }
0652     options.sync();
0653 }
0654 
0655 void readScannerOptions(const QString &groupName, QMap <QString, QString> &opts)
0656 {
0657     KConfigGroup scannerOptions(KSharedConfig::openStateConfig(), groupName);
0658     opts = scannerOptions.entryMap();
0659 }
0660 
0661 void Skanlite::saveScannerOptions()
0662 {
0663     KConfigGroup saving(KSharedConfig::openConfig(), QStringLiteral("Image Saving"));
0664     saving.writeEntry("NumberStartsFrom", m_saveLocation->startNumber());
0665 
0666     if (!m_ksanew) {
0667         return;
0668     }
0669 
0670     if (!m_deviceName.isEmpty()) {
0671         KConfigGroup options(KSharedConfig::openStateConfig(), QStringLiteral("Options For %1").arg(m_deviceName));
0672         QMap <QString, QString> opts;
0673         m_ksanew->getOptionValues(opts);
0674         writeScannerOptions(QStringLiteral("Options For %1").arg(m_deviceName), opts);
0675     }
0676 }
0677 
0678 void Skanlite::defaultScannerOptions()
0679 {
0680     if (!m_ksanew) {
0681         return;
0682     }
0683 
0684     applyScannerOptions(m_defaultScanOpts);
0685 }
0686 
0687 void Skanlite::applyScannerOptions(const QMap <QString, QString> &opts)
0688 {
0689     if (m_ksanew->setOptionValues(opts) == -1) {
0690         m_pendingApplyScanOpts = opts;
0691     } else {
0692         m_pendingApplyScanOpts.clear();
0693     }
0694 }
0695 
0696 void Skanlite::loadScannerOptions()
0697 {
0698     if (!m_deviceName.isEmpty()) {
0699         KConfigGroup saving(KSharedConfig::openConfig(), QStringLiteral("Image Saving"));
0700         m_saveLocation->setStartNumber(saving.readEntry("NumberStartsFrom", 1));
0701 
0702         if (!m_ksanew) {
0703             return;
0704         }
0705 
0706         QMap <QString, QString> opts;
0707         readScannerOptions(QStringLiteral("Options For %1").arg(m_deviceName), opts);
0708         applyScannerOptions(opts);
0709     }
0710 }
0711 
0712 void Skanlite::alertUser(int type, const QString &strStatus)
0713 {
0714     switch (type) {
0715     case KSaneWidget::ErrorGeneral:
0716         KMessageBox::error(nullptr, strStatus, QStringLiteral("Skanlite Test"));
0717         break;
0718     default:
0719         KMessageBox::information(nullptr, strStatus, QStringLiteral("Skanlite Test"));
0720     }
0721 }
0722 
0723 void Skanlite::buttonPressed(const QString &optionName, const QString &optionLabel, bool pressed)
0724 {
0725     qCDebug(SKANLITE_LOG) << "Button" << optionName << optionLabel << ((pressed) ? "pressed" : "released");
0726 }
0727 
0728 // D-Bus interface related helper functions
0729 
0730 QStringList serializeScannerOptions(const QMap<QString, QString> &opts)
0731 {
0732     QStringList sl;
0733     QMap<QString, QString>::const_iterator it = opts.constBegin();
0734     while (it != opts.constEnd()) {
0735         sl.append(it.key() + QLatin1Char('=') + it.value());
0736         ++it;
0737     }
0738     return sl;
0739 }
0740 
0741 void deserializeScannerOptions(const QStringList &settings, QMap<QString, QString> &opts)
0742 {
0743     for (const QString &s : settings) {
0744         int i = s.lastIndexOf(QLatin1Char('='));
0745         opts[s.left(i)] = s.right(s.length()-i-1);
0746     }
0747 }
0748 
0749 static const auto selectionSettings = { QLatin1String("tl-x"), QLatin1String("tl-y"),
0750                                         QLatin1String("br-x"), QLatin1String("br-y") };
0751 
0752 void filterSelectionSettings(QMap<QString, QString> &opts)
0753 {
0754     for (const auto &s : selectionSettings) {
0755         opts.remove(s);
0756     }
0757 }
0758 
0759 bool containsSelectionSettings(const QMap<QString, QString> &opts)
0760 {
0761     for (const auto &s : selectionSettings) {
0762         if (opts.contains(s)) {
0763             return true;
0764         }
0765     }
0766     return false;
0767 }
0768 
0769 void Skanlite::processSelectionOptions(QMap<QString, QString> &opts, bool ignoreSelection)
0770 {
0771     if (ignoreSelection) {
0772         filterSelectionSettings(opts);
0773     }
0774     else {
0775         if (containsSelectionSettings(opts)) { // make sure we really have selection to apply
0776             m_ksanew->setSelection(QPointF(0,0), QPointF(1,1)); // bcs settings have no effect if nothing was selected beforehand (Bug 377009)
0777         }
0778     }
0779 }
0780 
0781 void Skanlite::updateWindowTitle(const QString &deviceName, const QString &deviceVendor, const QString &deviceModel)
0782 {
0783     if (!deviceVendor.isEmpty() &&  !deviceModel.isEmpty()) {
0784         setWindowTitle(i18nc("@title:window %1 = scanner maker, %2 = scanner model", "%1 %2 - Skanlite", deviceVendor, deviceModel));
0785     } else if (!deviceName.isEmpty()) {
0786         setWindowTitle(i18nc("@title:window %1 = scanner device", "%1 - Skanlite", deviceName));
0787     } else {
0788         setWindowTitle(i18n("Skanlite"));
0789     }
0790 }
0791 
0792 // D-Bus interface related slots
0793 
0794 void Skanlite::getScannerOptions()
0795 {
0796     if (!m_deviceName.isEmpty()) {
0797         QMap <QString, QString> opts;
0798         m_ksanew->getOptionValues(opts);
0799         m_dbusInterface.setReply(serializeScannerOptions(opts));
0800     }
0801 }
0802 
0803 void Skanlite::setScannerOptions(const QStringList &options, bool ignoreSelection)
0804 {
0805     if (!m_deviceName.isEmpty()) {
0806         QMap <QString, QString> opts;
0807         deserializeScannerOptions(options, opts);
0808         processSelectionOptions(opts, ignoreSelection);
0809         applyScannerOptions(opts);
0810     }
0811 }
0812 
0813 
0814 void Skanlite::getDefaultScannerOptions()
0815 {
0816     m_dbusInterface.setReply(serializeScannerOptions(m_defaultScanOpts));
0817 }
0818 
0819 static const QLatin1String defaultProfileGroup("Options For %1 - Profile %2"); // 1 - device, 2 - arg
0820 
0821 void Skanlite::saveScannerOptionsToProfile(const QStringList &options, const QString &profile, bool ignoreSelection)
0822 {
0823     if (!m_deviceName.isEmpty()) {
0824         QMap <QString, QString> opts;
0825         deserializeScannerOptions(options, opts);
0826         processSelectionOptions(opts, ignoreSelection);
0827         writeScannerOptions(QString(defaultProfileGroup).arg(m_deviceName, profile), opts);
0828     }
0829 }
0830 
0831 void Skanlite::switchToProfile(const QString &profile, bool ignoreSelection)
0832 {
0833     if (!m_deviceName.isEmpty()) {
0834         QMap <QString, QString> opts;
0835         readScannerOptions(QString(defaultProfileGroup).arg(m_deviceName, profile), opts);
0836 
0837         if (opts.empty()) {
0838             opts = m_defaultScanOpts;
0839         }
0840 
0841         processSelectionOptions(opts, ignoreSelection);
0842         applyScannerOptions(opts);
0843     }
0844 }
0845 
0846 void Skanlite::getDeviceName()
0847 {
0848     if (!m_deviceName.isEmpty()) {
0849         m_dbusInterface.setReply(QStringList(m_deviceName));
0850     }
0851 }
0852 
0853 void Skanlite::getSelection()
0854 {
0855     if (!m_deviceName.isEmpty()) {
0856         QMap <QString, QString> opts;
0857         m_ksanew->getOptionValues(opts);
0858 
0859         QStringList reply;
0860         for (const auto &key : selectionSettings ) {
0861             if (opts.contains(key)) {
0862                 reply.append(key + QLatin1Char('=') + opts[key]);
0863             }
0864         }
0865         m_dbusInterface.setReply(reply);
0866     }
0867 }
0868 
0869 void Skanlite::setSelection(const QStringList &options)
0870 { // here options contains selection related subset of options
0871     setScannerOptions(options, false);
0872 }
0873 
0874 #include "moc_skanlite.cpp"