File indexing completed on 2024-04-21 05:54:08

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     if (d->m_ksaneInterface.getOption(Interface::TopLeftXOption) == NULL ||
0193         d->m_ksaneInterface.getOption(Interface::TopLeftYOption) == NULL ||
0194         d->m_ksaneInterface.getOption(Interface::BottomRightXOption) == NULL ||
0195         d->m_ksaneInterface.getOption(Interface::BottomRightYOption) == NULL) return;
0196     d->m_ksaneInterface.getOption(Interface::TopLeftXOption)->setValue(area.left() * d->m_maximumScanArea.width());
0197     d->m_ksaneInterface.getOption(Interface::TopLeftYOption)->setValue(area.top() * d->m_maximumScanArea.height());
0198     d->m_ksaneInterface.getOption(Interface::BottomRightXOption)->setValue(area.right() * d->m_maximumScanArea.width());
0199     d->m_ksaneInterface.getOption(Interface::BottomRightYOption)->setValue(area.bottom() * d->m_maximumScanArea.height());
0200 }
0201 
0202 Skanpage::ScanSplit Skanpage::scanSplit() const
0203 {
0204     return d->m_scanSplit;
0205 }
0206 
0207 void Skanpage::setScanSplit(Skanpage::ScanSplit split)
0208 {
0209     if (split != d->m_scanSplit) {
0210         d->m_scanSplit = split;
0211         Q_EMIT scanSplitChanged(d->m_scanSplit);
0212         if (split != ScanNotSplit) clearSubAreas();
0213     }
0214 }
0215 
0216 const QList<QRectF> &Skanpage::scanSubAreas()
0217 {
0218     return d->m_scanSubAreas;
0219 }
0220 
0221 void Skanpage::clearSubAreas()
0222 {
0223     if (!d->m_scanSubAreas.isEmpty()) {
0224         d->m_scanSubAreas.clear();
0225         Q_EMIT scanSubAreasChanged(d->m_scanSubAreas);
0226     }
0227 }
0228 
0229 void Skanpage::eraseSubArea(int index)
0230 {
0231     d->m_scanSubAreas.removeAt(index);
0232     Q_EMIT scanSubAreasChanged(d->m_scanSubAreas);
0233 }
0234 
0235 bool Skanpage::appendSubArea(QRectF area)
0236 {
0237     for (int i = 0; i < d->m_scanSubAreas.length(); i++) {
0238         if (area == d->m_scanSubAreas[i]) { // If the appended area is a duplicate
0239             return false;
0240         } else if (area.contains(d->m_scanSubAreas[i])) { // This area contains a smaller one within
0241             d->m_scanSubAreas.removeAt(i); i--; // Remove redundant areas
0242         } else if (area.intersects(d->m_scanSubAreas[i])) { // Avoid very similar (overlaping too much)
0243             // return; // To not allow any overlap
0244             QRectF intersect = area.intersected(d->m_scanSubAreas[i]);
0245             float overlapProportion = intersect.width()*intersect.height() / (area.width()*area.height());
0246             if (overlapProportion > 0.33) { d->m_scanSubAreas.removeAt(i); i--; }
0247         }
0248     }
0249     d->m_scanSubAreas.append(area);
0250     Q_EMIT scanSubAreasChanged(d->m_scanSubAreas);
0251     setScanSplit(Skanpage::ScanNotSplit); // Spliting and sub-areas... no
0252     return true;
0253 }
0254 
0255 void Skanpage::selectSubArea(int index)
0256 {
0257     QRectF tmp = scanArea();
0258     setScanArea(scanSubAreas().at(index));
0259     eraseSubArea(index);
0260     appendSubArea(tmp);
0261 }
0262 
0263 QImage Skanpage::previewImage() const
0264 {
0265     return d->m_previewImage;
0266 }
0267 
0268 void Skanpage::preview()
0269 {
0270     if (Option *opt = d->m_ksaneInterface.getOption(Interface::TopLeftXOption)) opt->storeCurrentData();
0271     if (Option *opt = d->m_ksaneInterface.getOption(Interface::TopLeftYOption)) opt->storeCurrentData();
0272     if (Option *opt = d->m_ksaneInterface.getOption(Interface::BottomRightXOption)) opt->storeCurrentData();
0273     if (Option *opt = d->m_ksaneInterface.getOption(Interface::BottomRightYOption)) opt->storeCurrentData();
0274     if (d->m_maximumScanArea.isValid()) setScanArea(QRectF(0.0, 0.0, 1.0, 1.0));
0275 
0276     if (Option *opt = d->m_ksaneInterface.getOption(Interface::ResolutionOption)) {
0277         opt->storeCurrentData();
0278         if (QVariant minRes = opt->minimumValue(); minRes.isValid()) {
0279             if (opt->type() == Option::TypeValueList) opt->setValue(minRes);
0280             else opt->setValue(minRes.toInt() < 25 ? 25 : minRes);
0281         }
0282     }
0283     if (Option* opt = d->m_ksaneInterface.getOption(Interface::PreviewOption)) opt->setValue(true);
0284 
0285     d->m_scanIsPreview = true;
0286 
0287     startScan();
0288 }
0289 
0290 void Skanpage::finishPreview()
0291 {
0292     if (Option* opt = d->m_ksaneInterface.getOption(Interface::TopLeftXOption)) opt->restoreSavedData();
0293     if (Option* opt = d->m_ksaneInterface.getOption(Interface::TopLeftYOption)) opt->restoreSavedData();
0294     if (Option* opt = d->m_ksaneInterface.getOption(Interface::BottomRightXOption)) opt->restoreSavedData();
0295     if (Option* opt = d->m_ksaneInterface.getOption(Interface::BottomRightYOption)) opt->restoreSavedData();
0296 
0297     if (Option* opt = d->m_ksaneInterface.getOption(Interface::ResolutionOption)) opt->restoreSavedData();
0298     if (Option* opt = d->m_ksaneInterface.getOption(Interface::PreviewOption)) opt->setValue(false);
0299 
0300     d->m_scanIsPreview = false;
0301 }
0302 
0303 void Skanpage::startScan()
0304 {
0305     if (!d->m_scanSubAreas.isEmpty()) {
0306         QRectF totalArea = d->m_scanArea; // Include last (unadded) area
0307         // This makes a rectangle that covers all the areas
0308         for (const QRectF& area : d->m_scanSubAreas) totalArea = totalArea.united(area);
0309         appendSubArea(d->m_scanArea); // Remember last area, for later use
0310         setScanArea(totalArea); // Scan all the needed area
0311     }
0312     d->m_ksaneInterface.startScan();
0313     d->m_scanInProgress = true;
0314     d->m_state = ApplicationState::ScanInProgress;
0315     Q_EMIT applicationStateChanged(d->m_state);
0316 }
0317 
0318 Skanpage::ApplicationState Skanpage::applicationState() const
0319 {
0320     return d->m_state;
0321 }
0322 
0323 void Skanpage::imageReady(const QImage &image)
0324 {
0325     if (d->m_scanIsPreview) {
0326         d->m_ksaneInterface.stopScan(); // Needed for ADF
0327         finishPreview();
0328         d->m_previewImage = image;
0329         Q_EMIT previewImageChanged(d->m_previewImage);
0330         return; // Do not save the preview to disk
0331     }
0332     if (d->m_scanSplit == ScanNotSplit && d->m_scanSubAreas.isEmpty()) {
0333         d->m_documentHandler.addImage(image);
0334         d->m_scannedImages++;
0335         return; // Regular scan ends here
0336     }
0337     auto applySubAreasToImage = [&]() {
0338         auto toOrigin = QTransform::fromTranslate(-d->m_scanArea.left(), -d->m_scanArea.top());
0339         auto toScale = QTransform::fromScale(image.width() / d->m_scanArea.width(), image.height() / d->m_scanArea.height());
0340         for (const QRectF& area : d->m_scanSubAreas) {
0341             QImage individualImage = image.copy(toScale.mapRect(toOrigin.mapRect(area)).toRect());
0342             d->m_documentHandler.addImage(individualImage);
0343             d->m_scannedImages++;
0344         }
0345     };
0346     if (!d->m_scanSubAreas.isEmpty()) { // There are sub-areas
0347         applySubAreasToImage();
0348         setScanArea(d->m_scanSubAreas.back()); // Leave scanArea as last selection
0349     } else {
0350         bool v = d->m_scanSplit == ScanIsSplitV;
0351         QRectF half = d->m_scanArea;
0352         if (v) half.setWidth(half.width()/2); else half.setHeight(half.height()/2);
0353         d->m_scanSubAreas.append(half); // Fake sub-areas, no need for notification
0354         if (v) half.moveRight(d->m_scanArea.right()); else half.moveBottom(d->m_scanArea.bottom());
0355         d->m_scanSubAreas.append(half);
0356         applySubAreasToImage();
0357     }
0358     clearSubAreas(); // The sub-areas last just one scan
0359 }
0360 
0361 void Skanpage::saveScannerOptions()
0362 {
0363     KConfigGroup options(KSharedConfig::openStateConfig(), QString::fromLatin1("Options For %1").arg(d->m_ksaneInterface.deviceName()));
0364 
0365     QMap<QString, QString> optionMap = d->m_ksaneInterface.getOptionsMap();
0366 
0367     qCDebug(SKANPAGE_LOG) << QStringLiteral("Saving scanner options") << optionMap;
0368     QMap<QString, QString>::const_iterator it = optionMap.constBegin();
0369     while (it != optionMap.constEnd()) {
0370         options.writeEntry(it.key(), it.value());
0371         ++it;
0372     }
0373     options.sync();
0374 }
0375 
0376 void Skanpage::loadScannerOptions()
0377 {
0378     KConfigGroup scannerOptions(KSharedConfig::openStateConfig(), QString::fromLatin1("Options For %1").arg(d->m_ksaneInterface.deviceName()));
0379 
0380     qCDebug(SKANPAGE_LOG) << QStringLiteral("Loading scanner options") << scannerOptions.entryMap();
0381 
0382     d->m_ksaneInterface.setOptionsMap(scannerOptions.entryMap());
0383 }
0384 
0385 void Skanpage::availableDevices(const QList<DeviceInformation *> &deviceList)
0386 {
0387     if (d->m_state == SearchingForDevices) {
0388         d->m_availableDevices.updateDevicesList(deviceList);
0389 
0390         d->m_state = DeviceSelection;
0391         Q_EMIT applicationStateChanged(d->m_state);
0392 
0393         // if there is only one scanning device available, open it
0394         if (d->m_availableDevices.rowCount() == 1) {
0395             d->m_availableDevices.selectDevice(0);
0396             qCDebug(SKANPAGE_LOG) << QStringLiteral("Automatically selecting only available device: ") << d->m_availableDevices.getSelectedDeviceName();
0397             openDevice(d->m_availableDevices.getSelectedDeviceName());
0398         }
0399     }
0400 }
0401 
0402 bool Skanpage::openDevice(const QString &deviceName, const QString &deviceVendor, const QString &deviceModel)
0403 {
0404     Interface::OpenStatus status = Interface::OpeningFailed;
0405     if (!deviceName.isEmpty()) {
0406         qCDebug(SKANPAGE_LOG) << QStringLiteral("Trying to open device: %1").arg(deviceName);
0407         status = d->m_ksaneInterface.openDevice(deviceName);
0408         if (status == Interface::OpeningSucceeded) {
0409             if (!deviceVendor.isEmpty()) {
0410                 finishOpeningDevice(deviceName, deviceVendor, deviceModel);
0411             } else {
0412                 finishOpeningDevice(deviceName, d->m_ksaneInterface.deviceVendor(), d->m_ksaneInterface.deviceModel());
0413             }
0414         } else if (status == Interface::OpeningDenied) {
0415             showUserMessage(SkanpageUtils::ErrorMessage, QStringLiteral("Access to selected device has been denied"));
0416         } else {
0417             showUserMessage(SkanpageUtils::ErrorMessage, QStringLiteral("Failed to open selected device."));
0418         }
0419 
0420     }
0421     return status == Interface::OpeningSucceeded;
0422 }
0423 
0424 void Skanpage::finishOpeningDevice(const QString &deviceName, const QString &deviceVendor, const QString &deviceModel)
0425 {
0426     qCDebug(SKANPAGE_LOG()) << QStringLiteral("Finishing opening of device %1 and loading options").arg(deviceName);
0427 
0428     KConfigGroup options(KSharedConfig::openStateConfig(), QStringLiteral("general"));
0429     options.writeEntry(QStringLiteral("deviceName"), deviceName);
0430     options.writeEntry(QStringLiteral("deviceModel"), deviceVendor);
0431     options.writeEntry(QStringLiteral("deviceVendor"), deviceModel);
0432 
0433     d->m_deviceName = deviceName;
0434     d->m_deviceVendor = deviceVendor;
0435     d->m_deviceModel = deviceModel;
0436     Q_EMIT deviceInfoUpdated();
0437     
0438     d->m_optionsModel.setOptionsList(d->m_ksaneInterface.getOptionsList());
0439     Q_EMIT optionsChanged();
0440 
0441     // load saved options
0442     loadScannerOptions();
0443 
0444     d->m_scanAreaConnectionsDone = false;
0445     setupScanningBounds();
0446 
0447     d->m_state = ReadyForScan;
0448     Q_EMIT applicationStateChanged(d->m_state);
0449 }
0450 
0451 void Skanpage::reloadDevicesList()
0452 {
0453     qCDebug(SKANPAGE_LOG()) << QStringLiteral("(Re-)loading devices list");
0454 
0455     if (d->m_ksaneInterface.closeDevice()) {
0456         d->m_deviceName.clear();
0457         d->m_deviceVendor.clear();
0458         d->m_deviceModel.clear();
0459         Q_EMIT deviceInfoUpdated();
0460         d->m_optionsModel.clearOptions();
0461         Q_EMIT optionsChanged();
0462     }
0463     d->m_state = SearchingForDevices;
0464     Q_EMIT applicationStateChanged(d->m_state);
0465     d->m_ksaneInterface.reloadDevicesList(d->m_configuration->showAllDevices() ? Interface::DeviceType::AllDevices : Interface::DeviceType::NoCameraAndVirtualDevices);
0466 }
0467 
0468 void Skanpage::showKSaneMessage(Interface::ScanStatus status, const QString &strStatus)
0469 {
0470     switch (status) {
0471         case Interface::ErrorGeneral:
0472             showUserMessage(SkanpageUtils::ErrorMessage, strStatus);
0473             break;
0474         case Interface::Information:
0475             showUserMessage(SkanpageUtils::InformationMessage, strStatus);
0476             break;
0477         default:
0478             break;
0479     }
0480 }
0481 
0482 void Skanpage::showUserMessage(SkanpageUtils::MessageLevel level, const QString &text)
0483 {
0484     Q_EMIT newUserMessage(QVariant(level), QVariant(text));
0485 }
0486 
0487 void Skanpage::progressUpdated(int progress)
0488 {
0489     d->m_progress = progress;
0490     Q_EMIT progressChanged(d->m_progress);
0491 }
0492 
0493 void Skanpage::batchModeCountDown(int remainingSeconds)
0494 {
0495     d->m_remainingSeconds = remainingSeconds;
0496     Q_EMIT countDownChanged(d->m_remainingSeconds);
0497 }
0498 
0499 int Skanpage::progress() const
0500 {
0501     return d->m_progress;
0502 }
0503 
0504 int Skanpage::countDown() const
0505 {
0506     return d->m_remainingSeconds;
0507 }
0508 
0509 Interface *Skanpage::ksaneInterface() const
0510 {
0511     return &d->m_ksaneInterface;
0512 }
0513 
0514 DocumentModel *Skanpage::documentModel() const
0515 {
0516     return &d->m_documentHandler;
0517 }
0518 
0519 DevicesModel *Skanpage::devicesModel() const
0520 {
0521     return &d->m_availableDevices;
0522 }
0523 
0524 FormatModel *Skanpage::formatModel() const
0525 {
0526     return &d->m_formatModel;
0527 }
0528 
0529 FilteredOptionsModel *Skanpage::optionsModel() const
0530 {
0531     return &d->m_filteredOptionsModel;
0532 }
0533 
0534 OCRLanguageModel *Skanpage::languageModel() const
0535 {
0536     return d->m_OCREngine.languages();
0537 }
0538 
0539 SkanpageConfiguration *Skanpage::configuration() const
0540 {
0541     return d->m_configuration;
0542 }
0543 
0544 SkanpageState *Skanpage::stateConfiguration() const
0545 {
0546     return d->m_stateConfiguration;
0547 }
0548 
0549 bool Skanpage::OCRavailable() const
0550 {
0551     return d->m_OCREngine.available();
0552 }
0553 
0554 void Skanpage::print() {
0555     d->m_documentPrinter.printDocument(d->m_documentHandler.selectPages(QList<int>()));
0556 }
0557 
0558 void Skanpage::registerAction(QObject* item, QObject* shortcuts, const QString &iconText)
0559 {
0560     auto getQKeySequence = [](const QVariant &variant) -> QKeySequence {
0561         if (variant.typeId() == QMetaType::QKeySequence) return variant.value<QKeySequence>();
0562         else if (variant.typeId() == QMetaType::QString) return variant.value<QString>();
0563         else return variant.value<QKeySequence::StandardKey>();
0564     };
0565 
0566     auto getKStandardShortcuts = [](const QVariant &variant) -> QList<QKeySequence> {
0567         auto id = KStandardShortcut::findByName(variant.toString());
0568         if (id != KStandardShortcut::AccelNone) {
0569             return KStandardShortcut::shortcut(id);
0570         } else {
0571             qCDebug(SKANPAGE_LOG) << "Invalid KStandardShortcut specified from QML" << variant.toString();
0572             return QList<QKeySequence>();
0573         }
0574     };
0575 
0576     QString id = QQmlEngine::contextForObject(item)->nameForObject(item);
0577 
0578     QAction *act = d->m_actionCollection->addAction(id);
0579     act->setText(item->property("text").toString());
0580     act->setIcon(QIcon::fromTheme(iconText));
0581     act->setIconVisibleInMenu(true);
0582 
0583     QList<QKeySequence> sequences;
0584     if (QVariant prop = item->property("shortcut"); prop.isValid()) {
0585         QKeySequence seq = getQKeySequence(prop);
0586         if (!seq.isEmpty()) sequences.append(seq);
0587     }
0588     if (QVariant prop = item->property("shortcutsName"); prop.isValid() && !prop.toString().isEmpty()) {
0589         sequences.append(getKStandardShortcuts(prop));
0590     }
0591     d->m_actionCollection->setDefaultShortcuts(act, sequences);
0592     d->m_actionCollection->readSettings();
0593 
0594     auto updateKeySequences = [=]() {
0595         // Set the first, only, or empty shortcut. Passing a QKeySequence doesn't always work
0596         item->setProperty("shortcut", act->shortcut().toString(QKeySequence::PortableText));
0597 
0598         QList<QVariant> sequenceList; // To set the alternate shortcuts
0599         for (int i = 1; i < act->shortcuts().size(); i++) {
0600             sequenceList.append(act->shortcuts().at(i).toString(QKeySequence::PortableText));
0601         }
0602         shortcuts->setProperty("sequences", sequenceList);
0603     };
0604     updateKeySequences(); // Move the specified shortcut to the QML Shortcut object
0605     connect(act, &QAction::changed, this, updateKeySequences);
0606 }
0607 
0608 void Skanpage::showShortcutsDialog() {
0609     KShortcutsDialog::showDialog(d->m_actionCollection);
0610 }
0611 
0612 void Skanpage::cancelScan()
0613 {
0614     if (d->m_progress > 0 && d->m_ksaneInterface.scanImage()) {
0615         d->m_ksaneInterface.lockScanImage();
0616         QImage image = *d->m_ksaneInterface.scanImage();
0617         d->m_ksaneInterface.unlockScanImage();
0618         imageReady(image);
0619     }
0620 
0621     d->m_ksaneInterface.stopScan();
0622 }
0623 
0624 void Skanpage::imageTemporarilySaved()
0625 {
0626     d->m_scannedImages--;
0627     checkFinish();
0628 }
0629 
0630 void Skanpage::scanningFinished(Interface::ScanStatus status, const QString &strStatus)
0631 {
0632     //only print debug, errors are already reported by Interface::userMessage
0633     qCDebug(SKANPAGE_LOG) << QStringLiteral("Finished scanning! Status code:") << status << QStringLiteral("Status message:") << strStatus;
0634 
0635     if (d->m_scanIsPreview) { // imageReady didn't execute (there was an error)
0636         finishPreview(); // Restore options anyways
0637     }
0638     
0639     d->m_scanInProgress = false;
0640     checkFinish();
0641 }
0642 
0643 void Skanpage::checkFinish()
0644 {
0645     if (d->m_scannedImages == 0 && !d->m_scanInProgress) {
0646         d->m_state = ApplicationState::ReadyForScan;
0647         Q_EMIT applicationStateChanged(d->m_state);
0648     }
0649 }
0650 
0651 #include "moc_Skanpage.cpp"