File indexing completed on 2024-04-28 09:49:43

0001 /**
0002  * SPDX-FileCopyrightText: 2015 by Kåre Särs <kare.sars@iki .fi>
0003  * SPDX-FileCopyrightText: 2021 by Alexander Stippich <a.stippich@gmx.net>
0004  *
0005  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006  */
0007 
0008 #include "Skanpage.h"
0009 
0010 #include <QThread>
0011 #include <QtQml>
0012 
0013 #include <KConfigGroup>
0014 #include <KSharedConfig>
0015 #include <KShortcutsDialog>
0016 
0017 #include "DevicesModel.h"
0018 #include "OptionsModel.h"
0019 #include "FormatModel.h"
0020 #include "FilteredOptionsModel.h"
0021 #include "DocumentSaver.h"
0022 #include "DocumentPrinter.h"
0023 #include "OCREngine.h"
0024 #include "skanpage_debug.h"
0025 
0026 class SkanpagePrivate {
0027 public:
0028     KSaneCore::Interface m_ksaneInterface;
0029     DocumentModel m_documentHandler;
0030     DevicesModel m_availableDevices;
0031     OptionsModel m_optionsModel;
0032     FormatModel m_formatModel;
0033     FilteredOptionsModel m_filteredOptionsModel;
0034     DocumentSaver m_documentSaver;
0035     DocumentPrinter m_documentPrinter;
0036     OCREngine m_OCREngine;
0037     QThread m_fileIOThread;
0038     SkanpageConfiguration *m_configuration;
0039     KActionCollection *m_actionCollection;
0040     SkanpageState *m_stateConfiguration;
0041 
0042     int m_progress = 100;
0043     int m_remainingSeconds = 0;
0044     int m_scannedImages = 0;
0045     Skanpage::ApplicationState m_state = Skanpage::SearchingForDevices;
0046     bool m_scanInProgress = false;
0047     bool m_scanIsPreview = false;
0048     QRectF m_maximumScanArea;
0049     QRectF m_scanArea; // Rectangle from (0, 0) to (1, 1)
0050     Skanpage::ScanSplit m_scanSplit = Skanpage::ScanNotSplit;
0051     QList<QRectF> m_scanSubAreas;
0052     bool m_scanAreaConnectionsDone = false;
0053     QImage m_previewImage;
0054     QString m_deviceName;
0055     QString m_deviceVendor;
0056     QString m_deviceModel;
0057 };
0058 
0059 using namespace KSaneCore;
0060 
0061 Skanpage::Skanpage(const QString &deviceName, QObject *parent)
0062     : QObject(parent)
0063     , d(std::make_unique<SkanpagePrivate>())
0064 {
0065     d->m_stateConfiguration = SkanpageState::self();
0066     d->m_configuration = SkanpageConfiguration::self();
0067     if (d->m_configuration->defaultFolder().isEmpty()) {
0068         d->m_configuration->setDefaultFolder(QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)));
0069     }
0070 
0071     d->m_filteredOptionsModel.setSourceModel(&d->m_optionsModel);
0072 
0073     d->m_actionCollection = new KActionCollection(this);
0074 
0075     connect(&d->m_ksaneInterface, &Interface::scannedImageReady, this, &Skanpage::imageReady);
0076     connect(&d->m_ksaneInterface, &Interface::availableDevices, this, &Skanpage::availableDevices);
0077     connect(&d->m_ksaneInterface, &Interface::userMessage, this, &Skanpage::showKSaneMessage);
0078     connect(&d->m_ksaneInterface, &Interface::scanProgress, this, &Skanpage::progressUpdated);
0079     connect(&d->m_ksaneInterface, &Interface::scanFinished, this, &Skanpage::scanningFinished);
0080     connect(&d->m_ksaneInterface, &Interface::batchModeCountDown, this, &Skanpage::batchModeCountDown);
0081     connect(&d->m_documentHandler, &DocumentModel::newPageAdded, this, &Skanpage::imageTemporarilySaved);
0082     
0083     d->m_fileIOThread.start();
0084     d->m_documentSaver.moveToThread(&d->m_fileIOThread);
0085     d->m_OCREngine.moveToThread(&d->m_fileIOThread);
0086     d->m_documentSaver.setOCREngine(&d->m_OCREngine);
0087 
0088     connect(&d->m_documentHandler, &DocumentModel::saveDocument, &d->m_documentSaver, &DocumentSaver::saveDocument);
0089     connect(&d->m_documentHandler, &DocumentModel::saveNewPageTemporary, &d->m_documentSaver, &DocumentSaver::saveNewPageTemporary);
0090     connect(&d->m_documentSaver, &DocumentSaver::pageTemporarilySaved, &d->m_documentHandler, &DocumentModel::updatePageInModel);
0091     connect(&d->m_documentSaver, &DocumentSaver::showUserMessage, this, &Skanpage::showUserMessage);
0092     connect(&d->m_documentSaver, &DocumentSaver::fileSaved, &d->m_documentHandler, &DocumentModel::updateFileInformation);
0093     connect(&d->m_documentSaver, &DocumentSaver::sharingFileSaved, &d->m_documentHandler, &DocumentModel::updateSharingFileInformation);
0094     connect(&d->m_documentPrinter, &DocumentPrinter::showUserMessage, this, &Skanpage::showUserMessage);
0095 
0096     // try to open device from command line option first, then remembered device
0097     if (deviceName.isEmpty() || !openDevice(deviceName)) {
0098 
0099         KConfigGroup options(KSharedConfig::openStateConfig(), QStringLiteral("general"));
0100         const QString savedDeviceName = options.readEntry(QStringLiteral("deviceName"));
0101         const QString savedDeviceVendor = options.readEntry(QStringLiteral("deviceVendor"));
0102         const QString savedDeviceModel = options.readEntry(QStringLiteral("deviceModel"));
0103 
0104         if (!openDevice(savedDeviceName, savedDeviceVendor, savedDeviceModel)) {
0105             reloadDevicesList();
0106         }
0107     }
0108 }
0109 
0110 Skanpage::~Skanpage()
0111 {
0112     d->m_fileIOThread.quit();
0113     d->m_configuration->save();
0114     d->m_stateConfiguration->save();
0115     saveScannerOptions();
0116     d->m_fileIOThread.wait();
0117 }
0118 
0119 QString Skanpage::deviceVendor() const
0120 {
0121     return d->m_deviceVendor;
0122 }
0123 
0124 QString Skanpage::deviceModel() const
0125 {
0126     return d->m_deviceModel;
0127 }
0128 
0129 QString Skanpage::deviceName() const
0130 {
0131     return d->m_deviceName;
0132 }
0133 
0134 void Skanpage::setupScanningBounds()
0135 {
0136     d->m_scanArea = QRectF();
0137 
0138     Option *tlx = d->m_ksaneInterface.getOption(Interface::TopLeftXOption);
0139     Option *tly = d->m_ksaneInterface.getOption(Interface::TopLeftYOption);
0140     Option *brx = d->m_ksaneInterface.getOption(Interface::BottomRightXOption);
0141     Option *bry = d->m_ksaneInterface.getOption(Interface::BottomRightYOption);
0142 
0143     if (tlx && tly && brx && bry &&
0144         tlx->state() == Option::StateActive && tly->state() == Option::StateActive &&
0145         brx->state() == Option::StateActive && bry->state() == Option::StateActive
0146     ) {
0147         QVariant tlxMin = tlx->minimumValue(), tlyMin = tly->minimumValue();
0148         QVariant brxMax = brx->maximumValue(), bryMax = bry->maximumValue();
0149         if (tlxMin.isValid() && tlyMin.isValid() && brxMax.isValid() && bryMax.isValid()) {
0150             d->m_maximumScanArea.setCoords(tlxMin.toReal(), tlyMin.toReal(), brxMax.toReal(), bryMax.toReal());
0151             d->m_scanArea.setCoords(tlx->value().toReal() / d->m_maximumScanArea.width(),
0152                                     tly->value().toReal() / d->m_maximumScanArea.height(),
0153                                     brx->value().toReal() / d->m_maximumScanArea.width(),
0154                                     bry->value().toReal() / d->m_maximumScanArea.height());
0155 
0156             if (!d->m_scanAreaConnectionsDone) {
0157                 connect(tlx, &Option::valueChanged, this, [&](const QVariant &value){
0158                     d->m_scanArea.setLeft(value.toReal() / d->m_maximumScanArea.width());
0159                     Q_EMIT scanAreaChanged(d->m_scanArea);
0160                 });
0161                 connect(tly, &Option::valueChanged, this, [&](const QVariant &value){
0162                     d->m_scanArea.setTop(value.toReal() / d->m_maximumScanArea.height());
0163                     Q_EMIT scanAreaChanged(d->m_scanArea);
0164                 });
0165                 connect(brx, &Option::valueChanged, this, [&](const QVariant &value){
0166                     d->m_scanArea.setRight(value.toReal() / d->m_maximumScanArea.width());
0167                     Q_EMIT scanAreaChanged(d->m_scanArea);
0168                 });
0169                 connect(bry, &Option::valueChanged, this, [&](const QVariant &value){
0170                     d->m_scanArea.setBottom(value.toReal() / d->m_maximumScanArea.height());
0171                     Q_EMIT scanAreaChanged(d->m_scanArea);
0172                 });
0173                 d->m_scanAreaConnectionsDone = true;
0174             }
0175         }
0176         connect(tlx, &Option::optionReloaded, this, &Skanpage::setupScanningBounds, Qt::UniqueConnection);
0177         connect(tly, &Option::optionReloaded, this, &Skanpage::setupScanningBounds, Qt::UniqueConnection);
0178         connect(brx, &Option::optionReloaded, this, &Skanpage::setupScanningBounds, Qt::UniqueConnection);
0179         connect(bry, &Option::optionReloaded, this, &Skanpage::setupScanningBounds, Qt::UniqueConnection);
0180     }
0181     Q_EMIT scanAreaChanged(d->m_scanArea);
0182 }
0183 
0184 QRectF Skanpage::scanArea() const
0185 {
0186     return d->m_scanArea;
0187 }
0188 
0189 void Skanpage::setScanArea(QRectF area)
0190 {
0191     if (area == d->m_scanArea) return;
0192     d->m_ksaneInterface.getOption(Interface::TopLeftXOption)->setValue(area.left() * d->m_maximumScanArea.width());
0193     d->m_ksaneInterface.getOption(Interface::TopLeftYOption)->setValue(area.top() * d->m_maximumScanArea.height());
0194     d->m_ksaneInterface.getOption(Interface::BottomRightXOption)->setValue(area.right() * d->m_maximumScanArea.width());
0195     d->m_ksaneInterface.getOption(Interface::BottomRightYOption)->setValue(area.bottom() * d->m_maximumScanArea.height());
0196 }
0197 
0198 Skanpage::ScanSplit Skanpage::scanSplit() const
0199 {
0200     return d->m_scanSplit;
0201 }
0202 
0203 void Skanpage::setScanSplit(Skanpage::ScanSplit split)
0204 {
0205     if (split != d->m_scanSplit) {
0206         d->m_scanSplit = split;
0207         Q_EMIT scanSplitChanged(d->m_scanSplit);
0208         if (split != ScanNotSplit) clearSubAreas();
0209     }
0210 }
0211 
0212 const QList<QRectF> &Skanpage::scanSubAreas()
0213 {
0214     return d->m_scanSubAreas;
0215 }
0216 
0217 void Skanpage::clearSubAreas()
0218 {
0219     if (!d->m_scanSubAreas.isEmpty()) {
0220         d->m_scanSubAreas.clear();
0221         Q_EMIT scanSubAreasChanged(d->m_scanSubAreas);
0222     }
0223 }
0224 
0225 void Skanpage::eraseSubArea(int index)
0226 {
0227     d->m_scanSubAreas.removeAt(index);
0228     Q_EMIT scanSubAreasChanged(d->m_scanSubAreas);
0229 }
0230 
0231 bool Skanpage::appendSubArea(QRectF area)
0232 {
0233     for (int i = 0; i < d->m_scanSubAreas.length(); i++) {
0234         if (area == d->m_scanSubAreas[i]) { // If the appended area is a duplicate
0235             return false;
0236         } else if (area.contains(d->m_scanSubAreas[i])) { // This area contains a smaller one within
0237             d->m_scanSubAreas.removeAt(i); i--; // Remove redundant areas
0238         } else if (area.intersects(d->m_scanSubAreas[i])) { // Avoid very similar (overlaping too much)
0239             // return; // To not allow any overlap
0240             QRectF intersect = area.intersected(d->m_scanSubAreas[i]);
0241             float overlapProportion = intersect.width()*intersect.height() / (area.width()*area.height());
0242             if (overlapProportion > 0.33) { d->m_scanSubAreas.removeAt(i); i--; }
0243         }
0244     }
0245     d->m_scanSubAreas.append(area);
0246     Q_EMIT scanSubAreasChanged(d->m_scanSubAreas);
0247     setScanSplit(Skanpage::ScanNotSplit); // Spliting and sub-areas... no
0248     return true;
0249 }
0250 
0251 void Skanpage::selectSubArea(int index)
0252 {
0253     QRectF tmp = scanArea();
0254     setScanArea(scanSubAreas().at(index));
0255     eraseSubArea(index);
0256     appendSubArea(tmp);
0257 }
0258 
0259 QImage Skanpage::previewImage() const
0260 {
0261     return d->m_previewImage;
0262 }
0263 
0264 void Skanpage::preview()
0265 {
0266     if (Option *opt = d->m_ksaneInterface.getOption(Interface::TopLeftXOption)) opt->storeCurrentData();
0267     if (Option *opt = d->m_ksaneInterface.getOption(Interface::TopLeftYOption)) opt->storeCurrentData();
0268     if (Option *opt = d->m_ksaneInterface.getOption(Interface::BottomRightXOption)) opt->storeCurrentData();
0269     if (Option *opt = d->m_ksaneInterface.getOption(Interface::BottomRightYOption)) opt->storeCurrentData();
0270     if (d->m_maximumScanArea.isValid()) setScanArea(QRectF(0.0, 0.0, 1.0, 1.0));
0271 
0272     if (Option *opt = d->m_ksaneInterface.getOption(Interface::ResolutionOption)) {
0273         opt->storeCurrentData();
0274         if (QVariant minRes = opt->minimumValue(); minRes.isValid()) {
0275             if (opt->type() == Option::TypeValueList) opt->setValue(minRes);
0276             else opt->setValue(minRes.toInt() < 25 ? 25 : minRes);
0277         }
0278     }
0279     if (Option* opt = d->m_ksaneInterface.getOption(Interface::PreviewOption)) opt->setValue(true);
0280 
0281     d->m_scanIsPreview = true;
0282 
0283     startScan();
0284 }
0285 
0286 void Skanpage::finishPreview()
0287 {
0288     if (Option* opt = d->m_ksaneInterface.getOption(Interface::TopLeftXOption)) opt->restoreSavedData();
0289     if (Option* opt = d->m_ksaneInterface.getOption(Interface::TopLeftYOption)) opt->restoreSavedData();
0290     if (Option* opt = d->m_ksaneInterface.getOption(Interface::BottomRightXOption)) opt->restoreSavedData();
0291     if (Option* opt = d->m_ksaneInterface.getOption(Interface::BottomRightYOption)) opt->restoreSavedData();
0292 
0293     if (Option* opt = d->m_ksaneInterface.getOption(Interface::ResolutionOption)) opt->restoreSavedData();
0294     if (Option* opt = d->m_ksaneInterface.getOption(Interface::PreviewOption)) opt->setValue(false);
0295 
0296     d->m_scanIsPreview = false;
0297 }
0298 
0299 void Skanpage::startScan()
0300 {
0301     if (!d->m_scanSubAreas.isEmpty()) {
0302         QRectF totalArea = d->m_scanArea; // Include last (unadded) area
0303         // This makes a rectangle that covers all the areas
0304         for (const QRectF& area : d->m_scanSubAreas) totalArea = totalArea.united(area);
0305         appendSubArea(d->m_scanArea); // Remember last area, for later use
0306         setScanArea(totalArea); // Scan all the needed area
0307     }
0308     d->m_ksaneInterface.startScan();
0309     d->m_scanInProgress = true;
0310     d->m_state = ApplicationState::ScanInProgress;
0311     Q_EMIT applicationStateChanged(d->m_state);
0312 }
0313 
0314 Skanpage::ApplicationState Skanpage::applicationState() const
0315 {
0316     return d->m_state;
0317 }
0318 
0319 void Skanpage::imageReady(const QImage &image)
0320 {
0321     if (d->m_scanIsPreview) {
0322         d->m_ksaneInterface.stopScan(); // Needed for ADF
0323         finishPreview();
0324         d->m_previewImage = image;
0325         Q_EMIT previewImageChanged(d->m_previewImage);
0326         return; // Do not save the preview to disk
0327     }
0328     if (d->m_scanSplit == ScanNotSplit && d->m_scanSubAreas.isEmpty()) {
0329         d->m_documentHandler.addImage(image);
0330         d->m_scannedImages++;
0331         return; // Regular scan ends here
0332     }
0333     auto applySubAreasToImage = [&]() {
0334         auto toOrigin = QTransform::fromTranslate(-d->m_scanArea.left(), -d->m_scanArea.top());
0335         auto toScale = QTransform::fromScale(image.width() / d->m_scanArea.width(), image.height() / d->m_scanArea.height());
0336         for (const QRectF& area : d->m_scanSubAreas) {
0337             QImage individualImage = image.copy(toScale.mapRect(toOrigin.mapRect(area)).toRect());
0338             d->m_documentHandler.addImage(individualImage);
0339             d->m_scannedImages++;
0340         }
0341     };
0342     if (!d->m_scanSubAreas.isEmpty()) { // There are sub-areas
0343         applySubAreasToImage();
0344         setScanArea(d->m_scanSubAreas.back()); // Leave scanArea as last selection
0345     } else {
0346         bool v = d->m_scanSplit == ScanIsSplitV;
0347         QRectF half = d->m_scanArea;
0348         if (v) half.setWidth(half.width()/2); else half.setHeight(half.height()/2);
0349         d->m_scanSubAreas.append(half); // Fake sub-areas, no need for notification
0350         if (v) half.moveRight(d->m_scanArea.right()); else half.moveBottom(d->m_scanArea.bottom());
0351         d->m_scanSubAreas.append(half);
0352         applySubAreasToImage();
0353     }
0354     clearSubAreas(); // The sub-areas last just one scan
0355 }
0356 
0357 void Skanpage::saveScannerOptions()
0358 {
0359     KConfigGroup options(KSharedConfig::openStateConfig(), QString::fromLatin1("Options For %1").arg(d->m_ksaneInterface.deviceName()));
0360 
0361     QMap<QString, QString> optionMap = d->m_ksaneInterface.getOptionsMap();
0362 
0363     qCDebug(SKANPAGE_LOG) << QStringLiteral("Saving scanner options") << optionMap;
0364     QMap<QString, QString>::const_iterator it = optionMap.constBegin();
0365     while (it != optionMap.constEnd()) {
0366         options.writeEntry(it.key(), it.value());
0367         ++it;
0368     }
0369     options.sync();
0370 }
0371 
0372 void Skanpage::loadScannerOptions()
0373 {
0374     KConfigGroup scannerOptions(KSharedConfig::openStateConfig(), QString::fromLatin1("Options For %1").arg(d->m_ksaneInterface.deviceName()));
0375 
0376     qCDebug(SKANPAGE_LOG) << QStringLiteral("Loading scanner options") << scannerOptions.entryMap();
0377 
0378     d->m_ksaneInterface.setOptionsMap(scannerOptions.entryMap());
0379 }
0380 
0381 void Skanpage::availableDevices(const QList<DeviceInformation *> &deviceList)
0382 {
0383     if (d->m_state == SearchingForDevices) {
0384         d->m_availableDevices.updateDevicesList(deviceList);
0385 
0386         d->m_state = DeviceSelection;
0387         Q_EMIT applicationStateChanged(d->m_state);
0388 
0389         // if there is only one scanning device available, open it
0390         if (d->m_availableDevices.rowCount() == 1) {
0391             d->m_availableDevices.selectDevice(0);
0392             qCDebug(SKANPAGE_LOG) << QStringLiteral("Automatically selecting only available device: ") << d->m_availableDevices.getSelectedDeviceName();
0393             openDevice(d->m_availableDevices.getSelectedDeviceName());
0394         }
0395     }
0396 }
0397 
0398 bool Skanpage::openDevice(const QString &deviceName, const QString &deviceVendor, const QString &deviceModel)
0399 {
0400     Interface::OpenStatus status = Interface::OpeningFailed;
0401     if (!deviceName.isEmpty()) {
0402         qCDebug(SKANPAGE_LOG) << QStringLiteral("Trying to open device: %1").arg(deviceName);
0403         status = d->m_ksaneInterface.openDevice(deviceName);
0404         if (status == Interface::OpeningSucceeded) {
0405             if (!deviceVendor.isEmpty()) {
0406                 finishOpeningDevice(deviceName, deviceVendor, deviceModel);
0407             } else {
0408                 finishOpeningDevice(deviceName, d->m_ksaneInterface.deviceVendor(), d->m_ksaneInterface.deviceModel());
0409             }
0410         } else if (status == Interface::OpeningDenied) {
0411             showUserMessage(SkanpageUtils::ErrorMessage, QStringLiteral("Access to selected device has been denied"));
0412         } else {
0413             showUserMessage(SkanpageUtils::ErrorMessage, QStringLiteral("Failed to open selected device."));
0414         }
0415 
0416     }
0417     return status == Interface::OpeningSucceeded;
0418 }
0419 
0420 void Skanpage::finishOpeningDevice(const QString &deviceName, const QString &deviceVendor, const QString &deviceModel)
0421 {
0422     qCDebug(SKANPAGE_LOG()) << QStringLiteral("Finishing opening of device %1 and loading options").arg(deviceName);
0423 
0424     KConfigGroup options(KSharedConfig::openStateConfig(), QStringLiteral("general"));
0425     options.writeEntry(QStringLiteral("deviceName"), deviceName);
0426     options.writeEntry(QStringLiteral("deviceModel"), deviceVendor);
0427     options.writeEntry(QStringLiteral("deviceVendor"), deviceModel);
0428 
0429     d->m_deviceName = deviceName;
0430     d->m_deviceVendor = deviceVendor;
0431     d->m_deviceModel = deviceModel;
0432     Q_EMIT deviceInfoUpdated();
0433     
0434     d->m_optionsModel.setOptionsList(d->m_ksaneInterface.getOptionsList());
0435     Q_EMIT optionsChanged();
0436 
0437     // load saved options
0438     loadScannerOptions();
0439 
0440     d->m_scanAreaConnectionsDone = false;
0441     setupScanningBounds();
0442 
0443     d->m_state = ReadyForScan;
0444     Q_EMIT applicationStateChanged(d->m_state);
0445 }
0446 
0447 void Skanpage::reloadDevicesList()
0448 {
0449     qCDebug(SKANPAGE_LOG()) << QStringLiteral("(Re-)loading devices list");
0450 
0451     if (d->m_ksaneInterface.closeDevice()) {
0452         d->m_deviceName.clear();
0453         d->m_deviceVendor.clear();
0454         d->m_deviceModel.clear();
0455         Q_EMIT deviceInfoUpdated();
0456         d->m_optionsModel.clearOptions();
0457         Q_EMIT optionsChanged();
0458     }
0459     d->m_state = SearchingForDevices;
0460     Q_EMIT applicationStateChanged(d->m_state);
0461     d->m_ksaneInterface.reloadDevicesList(d->m_configuration->showAllDevices() ? Interface::DeviceType::AllDevices : Interface::DeviceType::NoCameraAndVirtualDevices);
0462 }
0463 
0464 void Skanpage::showKSaneMessage(Interface::ScanStatus status, const QString &strStatus)
0465 {
0466     switch (status) {
0467         case Interface::ErrorGeneral:
0468             showUserMessage(SkanpageUtils::ErrorMessage, strStatus);
0469             break;
0470         case Interface::Information:
0471             showUserMessage(SkanpageUtils::InformationMessage, strStatus);
0472             break;
0473         default:
0474             break;
0475     }
0476 }
0477 
0478 void Skanpage::showUserMessage(SkanpageUtils::MessageLevel level, const QString &text)
0479 {
0480     Q_EMIT newUserMessage(QVariant(level), QVariant(text));
0481 }
0482 
0483 void Skanpage::progressUpdated(int progress)
0484 {
0485     d->m_progress = progress;
0486     Q_EMIT progressChanged(d->m_progress);
0487 }
0488 
0489 void Skanpage::batchModeCountDown(int remainingSeconds)
0490 {
0491     d->m_remainingSeconds = remainingSeconds;
0492     Q_EMIT countDownChanged(d->m_remainingSeconds);
0493 }
0494 
0495 int Skanpage::progress() const
0496 {
0497     return d->m_progress;
0498 }
0499 
0500 int Skanpage::countDown() const
0501 {
0502     return d->m_remainingSeconds;
0503 }
0504 
0505 Interface *Skanpage::ksaneInterface() const
0506 {
0507     return &d->m_ksaneInterface;
0508 }
0509 
0510 DocumentModel *Skanpage::documentModel() const
0511 {
0512     return &d->m_documentHandler;
0513 }
0514 
0515 DevicesModel *Skanpage::devicesModel() const
0516 {
0517     return &d->m_availableDevices;
0518 }
0519 
0520 FormatModel *Skanpage::formatModel() const
0521 {
0522     return &d->m_formatModel;
0523 }
0524 
0525 FilteredOptionsModel *Skanpage::optionsModel() const
0526 {
0527     return &d->m_filteredOptionsModel;
0528 }
0529 
0530 OCRLanguageModel *Skanpage::languageModel() const
0531 {
0532     return d->m_OCREngine.languages();
0533 }
0534 
0535 SkanpageConfiguration *Skanpage::configuration() const
0536 {
0537     return d->m_configuration;
0538 }
0539 
0540 SkanpageState *Skanpage::stateConfiguration() const
0541 {
0542     return d->m_stateConfiguration;
0543 }
0544 
0545 bool Skanpage::OCRavailable() const
0546 {
0547     return d->m_OCREngine.available();
0548 }
0549 
0550 void Skanpage::print() {
0551     d->m_documentPrinter.printDocument(d->m_documentHandler.selectPages(QList<int>()));
0552 }
0553 
0554 void Skanpage::registerAction(QObject* item, QObject* shortcuts, const QString &iconText)
0555 {
0556     auto getQKeySequence = [](const QVariant &variant) -> QKeySequence {
0557         if (variant.typeId() == QMetaType::QKeySequence) return variant.value<QKeySequence>();
0558         else if (variant.typeId() == QMetaType::QString) return variant.value<QString>();
0559         else return variant.value<QKeySequence::StandardKey>();
0560     };
0561 
0562     auto getKStandardShortcuts = [](const QVariant &variant) -> QList<QKeySequence> {
0563         auto id = KStandardShortcut::findByName(variant.toString());
0564         if (id != KStandardShortcut::AccelNone) {
0565             return KStandardShortcut::shortcut(id);
0566         } else {
0567             qCDebug(SKANPAGE_LOG) << "Invalid KStandardShortcut specified from QML" << variant.toString();
0568             return QList<QKeySequence>();
0569         }
0570     };
0571 
0572     QString id = QQmlEngine::contextForObject(item)->nameForObject(item);
0573 
0574     QAction *act = d->m_actionCollection->addAction(id);
0575     act->setText(item->property("text").toString());
0576     act->setIcon(QIcon::fromTheme(iconText));
0577     act->setIconVisibleInMenu(true);
0578 
0579     QList<QKeySequence> sequences;
0580     if (QVariant prop = item->property("shortcut"); prop.isValid()) {
0581         QKeySequence seq = getQKeySequence(prop);
0582         if (!seq.isEmpty()) sequences.append(seq);
0583     }
0584     if (QVariant prop = item->property("shortcutsName"); prop.isValid() && !prop.toString().isEmpty()) {
0585         sequences.append(getKStandardShortcuts(prop));
0586     }
0587     d->m_actionCollection->setDefaultShortcuts(act, sequences);
0588     d->m_actionCollection->readSettings();
0589 
0590     auto updateKeySequences = [=]() {
0591         // Set the first, only, or empty shortcut. Passing a QKeySequence doesn't always work
0592         item->setProperty("shortcut", act->shortcut().toString(QKeySequence::PortableText));
0593 
0594         QList<QVariant> sequenceList; // To set the alternate shortcuts
0595         for (int i = 1; i < act->shortcuts().size(); i++) {
0596             sequenceList.append(act->shortcuts().at(i).toString(QKeySequence::PortableText));
0597         }
0598         shortcuts->setProperty("sequences", sequenceList);
0599     };
0600     updateKeySequences(); // Move the specified shortcut to the QML Shortcut object
0601     connect(act, &QAction::changed, this, updateKeySequences);
0602 }
0603 
0604 void Skanpage::showShortcutsDialog() {
0605     KShortcutsDialog::showDialog(d->m_actionCollection);
0606 }
0607 
0608 void Skanpage::cancelScan()
0609 {
0610     if (d->m_progress > 0 && d->m_ksaneInterface.scanImage()) {
0611         d->m_ksaneInterface.lockScanImage();
0612         QImage image = *d->m_ksaneInterface.scanImage();
0613         d->m_ksaneInterface.unlockScanImage();
0614         imageReady(image);
0615     }
0616 
0617     d->m_ksaneInterface.stopScan();
0618 }
0619 
0620 void Skanpage::imageTemporarilySaved()
0621 {
0622     d->m_scannedImages--;
0623     checkFinish();
0624 }
0625 
0626 void Skanpage::scanningFinished(Interface::ScanStatus status, const QString &strStatus)
0627 {
0628     //only print debug, errors are already reported by Interface::userMessage
0629     qCDebug(SKANPAGE_LOG) << QStringLiteral("Finished scanning! Status code:") << status << QStringLiteral("Status message:") << strStatus;
0630 
0631     if (d->m_scanIsPreview) { // imageReady didn't execute (there was an error)
0632         finishPreview(); // Restore options anyways
0633     }
0634     
0635     d->m_scanInProgress = false;
0636     checkFinish();
0637 }
0638 
0639 void Skanpage::checkFinish()
0640 {
0641     if (d->m_scannedImages == 0 && !d->m_scanInProgress) {
0642         d->m_state = ApplicationState::ReadyForScan;
0643         Q_EMIT applicationStateChanged(d->m_state);
0644     }
0645 }
0646 
0647 #include "moc_Skanpage.cpp"