File indexing completed on 2024-04-14 14:36:05

0001 /************************************************************************
0002  *                                  *
0003  *  This file is part of Kooka, a scanning/OCR application using    *
0004  *  Qt <http://www.qt.io> and KDE Frameworks <http://www.kde.org>.  *
0005  *                                  *
0006  *  Copyright (C) 1999-2016 Klaas Freitag <freitag@suse.de>     *
0007  *                          Jonathan Marten <jjm@keelhaul.me.uk>    *
0008  *                                  *
0009  *  Kooka is free software; you can redistribute it and/or modify it    *
0010  *  under the terms of the GNU Library General Public License as    *
0011  *  published by the Free Software Foundation and appearing in the  *
0012  *  file COPYING included in the packaging of this file;  either    *
0013  *  version 2 of the License, or (at your option) any later version.    *
0014  *                                  *
0015  *  As a special exception, permission is given to link this program    *
0016  *  with any version of the KADMOS OCR/ICR engine (a product of     *
0017  *  reRecognition GmbH, Kreuzlingen), and distribute the resulting  *
0018  *  executable without including the source code for KADMOS in the  *
0019  *  source distribution.                        *
0020  *                                  *
0021  *  This program is distributed in the hope that it will be useful, *
0022  *  but WITHOUT ANY WARRANTY; without even the implied warranty of  *
0023  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the   *
0024  *  GNU General Public License for more details.            *
0025  *                                  *
0026  *  You should have received a copy of the GNU General Public       *
0027  *  License along with this program;  see the file COPYING.  If     *
0028  *  not, see <http://www.gnu.org/licenses/>.                *
0029  *                                  *
0030  ************************************************************************/
0031 
0032 #include "scanparams.h"
0033 
0034 #include <qpushbutton.h>
0035 #include <qimage.h>
0036 #include <qtooltip.h>
0037 #include <qprogressdialog.h>
0038 #include <qcheckbox.h>
0039 #include <qgroupbox.h>
0040 #include <qbuttongroup.h>
0041 #include <qradiobutton.h>
0042 #include <qfileinfo.h>
0043 #include <qgridlayout.h>
0044 #include <qlabel.h>
0045 #include <qscrollarea.h>
0046 #include <qtabwidget.h>
0047 #include <qmimetype.h>
0048 #include <qmimedatabase.h>
0049 #include <qlayout.h>
0050 #include <qdesktopservices.h>
0051 #include <qcombobox.h>
0052 
0053 #include <klocalizedstring.h>
0054 #include <kled.h>
0055 #include <kmessagebox.h>
0056 #include <kmessagewidget.h>
0057 
0058 extern "C"
0059 {
0060 #include <sane/saneopts.h>
0061 }
0062 
0063 #include "scanglobal.h"
0064 #include "scanparamspage.h"
0065 #include "gammadialog.h"
0066 #include "kgammatable.h"
0067 #include "kscancontrols.h"
0068 #include "scansizeselector.h"
0069 #include "kscanoption.h"
0070 #include "kscanoptset.h"
0071 #include "scanicons.h"
0072 #include "dialogbase.h"
0073 #include "libkookascan_logging.h"
0074 
0075 //  Debugging options
0076 #undef DEBUG_ADF
0077 
0078 //  SANE testing options
0079 #ifndef SANE_NAME_TEST_PICTURE
0080 #define SANE_NAME_TEST_PICTURE      "test-picture"
0081 #endif
0082 #ifndef SANE_NAME_THREE_PASS
0083 #define SANE_NAME_THREE_PASS        "three-pass"
0084 #endif
0085 #ifndef SANE_NAME_HAND_SCANNER
0086 #define SANE_NAME_HAND_SCANNER      "hand-scanner"
0087 #endif
0088 #ifndef SANE_NAME_GRAYIFY
0089 #define SANE_NAME_GRAYIFY       "grayify"
0090 #endif
0091 
0092 
0093 ScanParams::ScanParams(QWidget *parent)
0094     : QWidget(parent)
0095 {
0096     setObjectName("ScanParams");
0097 
0098     mSaneDevice = nullptr;
0099     mVirtualFile = nullptr;
0100     mGammaEditButt = nullptr;
0101     mResolutionBind = nullptr;
0102     mProgressDialog = nullptr;
0103     mSourceSelect = nullptr;
0104     mLed = nullptr;
0105 
0106     mProblemMessage = nullptr;
0107     mNoScannerMessage = nullptr;
0108 }
0109 
0110 ScanParams::~ScanParams()
0111 {
0112     delete mProgressDialog;
0113 }
0114 
0115 bool ScanParams::connectDevice(KScanDevice *newScanDevice, bool galleryMode)
0116 {
0117     QGridLayout *lay = new QGridLayout(this);
0118     lay->setMargin(0);
0119     lay->setColumnStretch(0, 9);
0120 
0121     if (newScanDevice == nullptr) {            // no scanner device
0122         qCDebug(LIBKOOKASCAN_LOG) << "No scan device, gallery=" << galleryMode;
0123         mSaneDevice = nullptr;
0124         createNoScannerMsg(galleryMode);
0125         return (true);
0126     }
0127 
0128     mSaneDevice = newScanDevice;
0129     mScanMode = ScanParams::NormalMode;
0130 #if 0
0131     // TOTO: port/update
0132     adf = ADF_OFF;
0133 #endif
0134     QLabel *lab = new QLabel(xi18nc("@info", "<title>Scanner&nbsp;Settings</title>"), this);
0135     lay->addWidget(lab, 0, 0, Qt::AlignLeft);
0136 
0137     mLed = new KLed(this);
0138     mLed->setState(KLed::Off);
0139     mLed->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
0140     lay->addWidget(mLed, 0, 1, Qt::AlignRight);
0141 
0142     lab = new QLabel(mSaneDevice->scannerDescription(), this);
0143     lay->addWidget(lab, 1, 0, 1, 2, Qt::AlignLeft);
0144 
0145     /* load the startup scanoptions */
0146 
0147     /* Now create Widgets for the important scan settings */
0148     QWidget *sv = createScannerParams();
0149     lay->addWidget(sv, 3, 0, 1, 2);
0150     lay->setRowStretch(3, 9);
0151 
0152     // Load the startup options
0153     // TODO: check whether the saved scanner options apply to the current scanner?
0154     // They may be for a completely different one...
0155     // Or update KScanDevice and here to save/load the startup options
0156     // on a per-scanner basis.
0157 
0158     qCDebug(LIBKOOKASCAN_LOG) << "looking for startup options";
0159     KScanOptSet startupOptions(KScanOptSet::startupSetName());
0160     if (startupOptions.loadConfig(mSaneDevice->scannerBackendName()))
0161     {
0162         qCDebug(LIBKOOKASCAN_LOG) << "loading startup options";
0163         mSaneDevice->loadOptionSet(&startupOptions);
0164     }
0165     else qCDebug(LIBKOOKASCAN_LOG) << "no startup options to load";
0166 
0167     // Reload all options, to take account of inactive ones
0168     mSaneDevice->reloadAllOptions();
0169 
0170     // Send the current settings to the previewer
0171     initStartupArea(startupOptions.isEmpty());      // signal newCustomScanSize()
0172     slotNewScanMode();                  // signal scanModeChanged()
0173     slotNewResolution(nullptr);             // signal scanResolutionChanged
0174 
0175     /* Create the Scan Buttons */
0176     QPushButton *pb = new QPushButton(QIcon::fromTheme("preview"), i18n("Pre&view"), this);
0177     pb->setToolTip(i18n("Start a preview scan and show the preview image"));
0178     pb->setMinimumWidth(100);
0179     connect(pb, &QPushButton::clicked, this, &ScanParams::slotAcquirePreview);
0180     lay->addWidget(pb, 5, 0, Qt::AlignLeft);
0181 
0182     pb = new QPushButton(QIcon::fromTheme("scan"), i18n("Star&t Scan"), this);
0183     pb->setToolTip(i18n("Start a scan and save the scanned image"));
0184     pb->setMinimumWidth(100);
0185     connect(pb, &QPushButton::clicked, this, &ScanParams::slotStartScan);
0186     lay->addWidget(pb, 5, 1, Qt::AlignRight);
0187 
0188     /* Initialise the progress dialog */
0189     mProgressDialog = new QProgressDialog(QString(), i18n("Stop"), 0, 100, nullptr);
0190     mProgressDialog->setModal(true);
0191     mProgressDialog->setAutoClose(true);
0192     mProgressDialog->setAutoReset(true);
0193     mProgressDialog->setWindowTitle(i18n("Scanning"));
0194     mProgressDialog->setMinimumDuration(100);
0195     // The next is necessary with Qt5, as otherwise the progress dialogue
0196     // appears to show itself after the default 'minimumDuration' (= 4 seconds),
0197     // even despite the previous and no 'value' being set.
0198     mProgressDialog->reset();
0199     setScanDestination(KLocalizedString());     // reset destination display
0200 
0201     connect(mProgressDialog, &QProgressDialog::canceled, mSaneDevice, &KScanDevice::slotStopScanning);
0202     connect(mSaneDevice, &KScanDevice::sigScanProgress, this, &ScanParams::slotScanProgress);
0203 
0204     return (true);
0205 }
0206 
0207 KLed *ScanParams::operationLED() const
0208 {
0209     return (mLed);
0210 }
0211 
0212 ScanParamsPage *ScanParams::createTab(QTabWidget *tw, const QString &title, const char *name)
0213 {
0214     QScrollArea *scroll = new QScrollArea(this);
0215     scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
0216     scroll->setWidgetResizable(true);           // stretch to width
0217     scroll->setFrameStyle(QFrame::NoFrame);
0218 
0219     ScanParamsPage *frame = new ScanParamsPage(this, name);
0220     scroll->setWidget(frame);
0221     tw->addTab(scroll, title);
0222 
0223     return (frame);
0224 }
0225 
0226 QWidget *ScanParams::createScannerParams()
0227 {
0228     QTabWidget *tw = new QTabWidget(this);
0229     tw->setTabsClosable(false);
0230     tw->setTabPosition(QTabWidget::North);
0231 
0232     ScanParamsPage *basicFrame = createTab(tw, i18n("&Basic"), "BasicFrame");
0233     ScanParamsPage *otherFrame = createTab(tw, i18n("Other"), "OtherFrame");
0234     ScanParamsPage *advancedFrame = createTab(tw, i18n("Advanced"), "AdvancedFrame");
0235 
0236     KScanOption *so;
0237     QLabel *l;
0238     QWidget *w;
0239     QLabel *u;
0240     ScanParamsPage *frame;
0241 
0242     // Initial "Basic" options
0243     frame = basicFrame;
0244 
0245     // Virtual/debug image file
0246     mVirtualFile = mSaneDevice->getGuiElement(SANE_NAME_FILE, frame);
0247     if (mVirtualFile != nullptr) {
0248         connect(mVirtualFile, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged);
0249 
0250         l = mVirtualFile->getLabel(frame, true);
0251         w = mVirtualFile->widget();
0252         frame->addRow(l, w);
0253 
0254         // Selection for either virtual scanner or SANE debug
0255 
0256         QWidget *vbg = new QWidget(frame);
0257         QVBoxLayout *vbl = new QVBoxLayout(vbg);
0258         vbl->setMargin(0);
0259 
0260         QRadioButton *rb1 = new QRadioButton(i18n("SANE Debug (from PNM image)"), vbg);
0261         rb1->setToolTip(xi18nc("@info:tooltip", "Operate in the same way that a real scanner does (including scan area, image processing etc.), but reading from the specified image file. See <link url=\"man:sane-pnm(5)\">sane-pnm(5)</link> for more information."));
0262         vbl->addWidget(rb1);
0263         QRadioButton *rb2 = new QRadioButton(i18n("Virtual Scanner (any image format)"), vbg);
0264         rb2->setToolTip(xi18nc("@info:tooltip", "Do not perform any scanning or processing, but simply read the specified image file. This is for testing the image saving, etc."));
0265         vbl->addWidget(rb2);
0266 
0267         if (mScanMode == ScanParams::NormalMode) mScanMode = ScanParams::SaneDebugMode;
0268         rb1->setChecked(mScanMode == ScanParams::SaneDebugMode);
0269         rb2->setChecked(mScanMode == ScanParams::VirtualScannerMode);
0270 
0271         // needed for new 'buttonClicked' signal:
0272         QButtonGroup *vbgGroup = new QButtonGroup(vbg);
0273         vbgGroup->addButton(rb1, 0);
0274         vbgGroup->addButton(rb2, 1);
0275 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
0276         connect(vbgGroup, &QButtonGroup::idClicked, this, &ScanParams::slotVirtScanModeSelect);
0277 #else
0278         connect(vbgGroup, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this, &ScanParams::slotVirtScanModeSelect);
0279 #endif
0280 
0281         l = new QLabel(i18n("Reading mode:"), frame);
0282         frame->addRow(l, vbg, nullptr, Qt::AlignTop);
0283 
0284         // Separator line after these.  Using a KScanGroup with a null text,
0285         // so that it looks the same as any real group separators following.
0286         frame->addGroup(new KScanGroup(frame, QString()));
0287     }
0288 
0289     // Mode setting
0290     so = mSaneDevice->getGuiElement(SANE_NAME_SCAN_MODE, frame);
0291     if (so != nullptr) {
0292         KScanCombo *cb = (KScanCombo *) so->widget();
0293 
0294         // Set icons for all possible scan modes which the combo box may contain.
0295         // Modes which the scanner does not support and hence which are not in
0296         // the combo box don't matter.  See ScanIcons and KScanCombo::setIcon().
0297         //
0298         // The combo has already been populated with the current list of SANE values
0299         // at the KScanOption initialisation (by KScanOption::updateList() calling
0300         // KScanOption::getList() which reads the list values from SANE).  But
0301         // this may not work properly if the list of scan modes changes at runtime!
0302         const QList<QByteArray> scanModes = ScanIcons::self()->allModes();
0303         for (const QByteArray &scanMode : scanModes)
0304         {
0305             cb->setIcon(ScanIcons::self()->icon(scanMode), scanMode.constData());
0306         }
0307 
0308         connect(so, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged);
0309         connect(so, &KScanOption::guiChange, this, &ScanParams::slotNewScanMode);
0310 
0311         l = so->getLabel(frame, true);
0312         frame->addRow(l, cb);
0313     }
0314 
0315     // Resolution setting.  Try "X-Resolution" setting first, this is the
0316     // option we want if the resolutions are split up.  If there is no such
0317     // option then try just "Resolution", this may not be the same as
0318     // "X-Resolution" even though this was the case in SANE<=1.0.19.
0319     so = mSaneDevice->getGuiElement(SANE_NAME_SCAN_X_RESOLUTION, frame);
0320     if (so == nullptr) {
0321         so = mSaneDevice->getGuiElement(SANE_NAME_SCAN_RESOLUTION, frame);
0322     }
0323     if (so != nullptr) {
0324         connect(so, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged);
0325         // Connection that passes the resolution to the previewer
0326         connect(so, &KScanOption::guiChange, this, &ScanParams::slotNewResolution);
0327 
0328         l = so->getLabel(frame, true);
0329         w = so->widget();
0330         u = so->getUnit(frame);
0331         frame->addRow(l, w, u);
0332 
0333         // Same X/Y resolution option (if present)
0334         mResolutionBind = mSaneDevice->getGuiElement(SANE_NAME_RESOLUTION_BIND, frame);
0335         if (mResolutionBind != nullptr) {
0336             connect(mResolutionBind, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged);
0337 
0338             l = so->getLabel(frame, true);
0339             w = so->widget();
0340             frame->addRow(l, w);
0341         }
0342 
0343         // Now the "Y-Resolution" setting, if there is a separate one
0344         so = mSaneDevice->getGuiElement(SANE_NAME_SCAN_Y_RESOLUTION, frame);
0345         if (so!=nullptr)
0346         {
0347             // Connection that passes the resolution to the previewer
0348             connect(so, &KScanOption::guiChange, this, &ScanParams::slotNewResolution);
0349 
0350             l = so->getLabel(frame, true);
0351             w = so->widget();
0352             u = so->getUnit(frame);
0353             frame->addRow(l, w, u);
0354         }
0355     } else {
0356         qCWarning(LIBKOOKASCAN_LOG) << "No resolution option available!";
0357     }
0358 
0359     // Scan size setting
0360     mAreaSelect = new ScanSizeSelector(frame, mSaneDevice->getMaxScanSize());
0361     connect(mAreaSelect, &ScanSizeSelector::sizeSelected, this, &ScanParams::slotScanSizeSelected);
0362     l = new QLabel("Scan &area:", frame);       // make sure it gets an accel
0363     l->setBuddy(mAreaSelect->focusProxy());
0364     frame->addRow(l, mAreaSelect, nullptr, Qt::AlignTop);
0365 
0366     // Insert another beautification line
0367     frame->addGroup(new KScanGroup(frame, QString()));
0368 
0369     // Scan destination
0370     const int originalRow = frame->currentRow();    // note what has been built so far
0371     createScanDestinationGUI(frame);            // call out to the application
0372     if (frame->currentRow()>originalRow)        // did that create anything?
0373     {                           // if so then add a separator
0374         frame->addGroup(new KScanGroup(frame, QString()));
0375     }
0376 
0377     // Source selection
0378     mSourceSelect = mSaneDevice->getGuiElement(SANE_NAME_SCAN_SOURCE, frame);
0379     if (mSourceSelect != nullptr) {
0380         connect(mSourceSelect, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged);
0381 
0382         l = mSourceSelect->getLabel(frame, true);
0383         w = mSourceSelect->widget();
0384         frame->addRow(l, w);
0385 
0386         //  TODO: enable the "Advanced" dialogue, because that
0387         //  contains other ADF options.  They are not implemented at the moment
0388         //  but they may be some day...
0389         //QPushButton *pb = new QPushButton( i18n("Source && ADF Options..."), frame);
0390         //connect(pb, &QAbstractButton::clicked, this, &ScanParams::slotSourceSelect);
0391         //lay->addWidget(pb,row,2,1,-1,Qt::AlignRight);
0392         //++row;
0393     }
0394 
0395     // SANE testing options, for the "test" device
0396     so = mSaneDevice->getGuiElement(SANE_NAME_TEST_PICTURE, frame);
0397     if (so!=nullptr)
0398     {
0399         connect(so, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged);
0400 
0401         l = so->getLabel(frame);
0402         w = so->widget();
0403         frame->addRow(l, w);
0404 
0405         // This control allows the image type presented to the application to be
0406         // tested.  It does not actually affect the format of the scanned image,
0407         // so it will only affect the image type in Kooka's "Save Assistant"
0408         // dialogue if it is set to ask for a filename before the scan starts.
0409         // The setting is not persistent.
0410         QComboBox *fitCombo = new QComboBox(frame);
0411         fitCombo->addItem(i18n("Default (from scan settings)"), ScanImage::None);
0412         fitCombo->addItem(i18n("Black/white bitmap"), ScanImage::BlackWhite);
0413         fitCombo->addItem(i18n("Grey scale"), ScanImage::Greyscale);
0414         fitCombo->addItem(i18n("Low colour"), ScanImage::LowColour);
0415         fitCombo->addItem(i18n("High colour"), ScanImage::HighColour);
0416         fitCombo->setCurrentIndex(0);
0417 
0418         connect(fitCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
0419                 this, [=](int index)
0420                 {
0421                     ScanImage::ImageType fmt = static_cast<ScanImage::ImageType>(fitCombo->currentData().toInt());
0422                     mSaneDevice->setTestFormat(fmt);
0423                 });
0424 
0425         l = new QLabel(i18n("Force image format:"), frame);
0426         frame->addRow(l, fitCombo);
0427     }
0428 
0429     // Now all of the other options which have not been accounted for yet.
0430     // Split them up into "Other" and "Advanced".
0431     const QList<QByteArray> opts = mSaneDevice->getAllOptions();
0432     for (QList<QByteArray>::const_iterator it = opts.constBegin();
0433             it != opts.constEnd(); ++it) {
0434         const QByteArray opt = (*it);
0435 
0436         if (opt == SANE_NAME_SCAN_TL_X ||       // ignore these (scan area)
0437             opt == SANE_NAME_SCAN_TL_Y ||
0438             opt == SANE_NAME_SCAN_BR_X ||
0439             opt == SANE_NAME_SCAN_BR_Y)
0440         {
0441             continue;
0442         }
0443 
0444         so = mSaneDevice->getExistingGuiElement(opt);   // see if already created
0445         if (so != nullptr) {
0446             continue;    // if so ignore, don't duplicate
0447         }
0448 
0449         so = mSaneDevice->getGuiElement(opt, frame);
0450         if (so != nullptr) {
0451             //qCDebug(LIBKOOKASCAN_LOG) << "creating" << (so->isCommonOption() ? "OTHER" : "ADVANCED") << "option" << opt;
0452             connect(so, &KScanOption::guiChange, this, &ScanParams::slotOptionChanged);
0453 
0454             if (so->isCommonOption()) {
0455                 frame = otherFrame;
0456             } else {
0457                 frame = advancedFrame;
0458             }
0459 
0460             w = so->widget();
0461             if (so->isGroup()) {
0462                 frame->addGroup(w);
0463             } else {
0464                 l = so->getLabel(frame);
0465                 u = so->getUnit(frame);
0466                 frame->addRow(l, w, u);
0467             }
0468 
0469             // Some special things to do for particular options
0470             if (opt == SANE_NAME_BIT_DEPTH)
0471             {
0472                 connect(so, &KScanOption::guiChange, this, &ScanParams::slotNewScanMode);
0473             }
0474             else if (opt == SANE_NAME_CUSTOM_GAMMA)
0475             {
0476                 mGammaEditButt = new QPushButton(i18n("Edit Gamma Table..."), this);
0477                 mGammaEditButt->setIcon(QIcon::fromTheme("document-edit"));
0478                 connect(mGammaEditButt, &QPushButton::clicked, this, &ScanParams::slotEditCustGamma);
0479                 // Enabling/disabling the edit button is handled by
0480                 // slotOptionChanged() calling setEditCustomGammaTableState()
0481                 setEditCustomGammaTableState();
0482 
0483                 frame->addRow(nullptr, mGammaEditButt, nullptr, Qt::AlignRight);
0484             }
0485         }
0486     }
0487 
0488     basicFrame->lastRow();              // final stretch row
0489     if (!otherFrame->lastRow()) {
0490         tw->setTabEnabled(1, false);
0491     }
0492     if (!advancedFrame->lastRow()) {
0493         tw->setTabEnabled(2, false);
0494     }
0495 
0496     return (tw);                    // top-level (tab) widget
0497 }
0498 
0499 
0500 void ScanParams::initStartupArea(bool dontRestore)
0501 {
0502 // TODO: restore area a user preference
0503 #ifdef RESTORE_AREA
0504     if (dontRestore)                    // no saved options available
0505 #endif
0506     {
0507         applyRect(QRect());             // set maximum scan area
0508         return;
0509     }
0510     // set scan area from saved
0511     KScanOption *tl_x = mSaneDevice->getOption(SANE_NAME_SCAN_TL_X);
0512     KScanOption *tl_y = mSaneDevice->getOption(SANE_NAME_SCAN_TL_Y);
0513     KScanOption *br_x = mSaneDevice->getOption(SANE_NAME_SCAN_BR_X);
0514     KScanOption *br_y = mSaneDevice->getOption(SANE_NAME_SCAN_BR_Y);
0515 
0516     QRect rect;
0517     int val1, val2;
0518     tl_x->get(&val1); rect.setLeft(val1);       // pass area to previewer
0519     br_x->get(&val2); rect.setWidth(val2 - val1);
0520     tl_y->get(&val1); rect.setTop(val1);
0521     br_y->get(&val2); rect.setHeight(val2 - val1);
0522     emit newCustomScanSize(rect);
0523 
0524     mAreaSelect->selectSize(rect);          // set selector to match
0525 }
0526 
0527 void ScanParams::createNoScannerMsg(bool galleryMode)
0528 {
0529     QWidget *lab;
0530     if (galleryMode) {
0531         lab = messageScannerNotSelected();
0532     } else {
0533         lab = messageScannerProblem();
0534     }
0535 
0536     QGridLayout *lay = dynamic_cast<QGridLayout *>(layout());
0537     if (lay != nullptr) {
0538         lay->addWidget(lab, 0, 0, Qt::AlignTop);
0539     }
0540 }
0541 
0542 QWidget *ScanParams::messageScannerNotSelected()
0543 {
0544     if (mNoScannerMessage==nullptr)
0545     {
0546         mNoScannerMessage = new KMessageWidget(
0547             xi18nc("@info",
0548                    "<title>No scanner selected</title>"
0549                    "<nl/><nl/>"
0550                    "Select a scanner device to perform scanning."));
0551 
0552         mNoScannerMessage->setMessageType(KMessageWidget::Information);
0553         mNoScannerMessage->setIcon(QIcon::fromTheme("dialog-information"));
0554         mNoScannerMessage->setCloseButtonVisible(false);
0555         mNoScannerMessage->setWordWrap(true);
0556     }
0557 
0558     return (mNoScannerMessage);
0559 }
0560 
0561 
0562 QWidget *ScanParams::messageScannerProblem()
0563 {
0564     if (mProblemMessage==nullptr)
0565     {
0566         mProblemMessage = new KMessageWidget(
0567             xi18nc("@info",
0568                    "<title>No scanner found, or unable to access it</title>"
0569                    "<nl/><nl/>"
0570                    "There was a problem using the SANE (Scanner Access Now Easy) library to access "
0571                    "the scanner device.  There may be a problem with your SANE installation, or it "
0572                    "may not be configured to support your scanner."
0573                    "<nl/><nl/>"
0574                    "Check that SANE is correctly installed and configured on your system, and "
0575                    "also that the scanner device name and settings are correct."
0576                    "<nl/><nl/>"
0577                    "See the SANE project home page "
0578                    "(<link url=\"http://www.sane-project.org\">www.sane-project.org</link>) "
0579                    "for more information on SANE installation and setup."));
0580 
0581         mProblemMessage->setMessageType(KMessageWidget::Warning);
0582         mProblemMessage->setIcon(QIcon::fromTheme("dialog-warning"));
0583         mProblemMessage->setCloseButtonVisible(false);
0584         mProblemMessage->setWordWrap(true);
0585         connect(mProblemMessage, &KMessageWidget::linkActivated,
0586                 [](const QString &link){ QDesktopServices::openUrl(QUrl(link)); });
0587     }
0588 
0589     return (mProblemMessage);
0590 }
0591 
0592 
0593 void ScanParams::slotSourceSelect()
0594 {
0595 #if 0
0596 // TODO: port/update
0597     AdfBehaviour adf = ADF_OFF;
0598 
0599     if (mSourceSelect == nullptr) {
0600         return;    // no source selection GUI
0601     }
0602     if (!mSourceSelect->isValid()) {
0603         return;    // no option on scanner
0604     }
0605 
0606     const QByteArray &currSource = mSourceSelect->get();
0607     //qCDebug(LIBKOOKASCAN_LOG) << "Current source is" << currSource;
0608 
0609     QList<QByteArray> sources = mSourceSelect->getList();
0610 #ifdef DEBUG_ADF
0611     if (!sources.contains("Automatic Document Feeder")) {
0612         sources.append("Automatic Document Feeder");
0613     }
0614 #endif
0615 
0616     // TODO: the 'sources' list has exactly the same options as the
0617     // scan source combo (apart from the debugging hack above), so
0618     // what's the point of repeating it in this dialogue?
0619     ScanSourceDialog d(this, sources, adf);
0620     d.slotSetSource(currSource);
0621 
0622     if (d.exec() != QDialog::Accepted) {
0623         return;
0624     }
0625 
0626     QString sel_source = d.getText();
0627     adf = d.getAdfBehave();
0628     //qCDebug(LIBKOOKASCAN_LOG) << "new source" << sel_source << "ADF" << adf;
0629 
0630     /* set the selected Document source, the behavior is stored in a membervar */
0631     mSourceSelect->set(sel_source.toLatin1());      // TODO: FIX in ScanSourceDialog, then here
0632     mSourceSelect->apply();
0633     mSourceSelect->reload();
0634     mSourceSelect->redrawWidget();
0635 #endif
0636 }
0637 
0638 /* Slot which is called if the user switches in the gui between
0639  *  the SANE-Debug-Mode and the qt image reading
0640  */
0641 void ScanParams::slotVirtScanModeSelect(int but)
0642 {
0643     if (but == 0) mScanMode = ScanParams::SaneDebugMode;    // SANE Debug
0644     else mScanMode = ScanParams::VirtualScannerMode;        // Virtual Scanner
0645     const bool enable = (mScanMode == ScanParams::SaneDebugMode);
0646 
0647     mSaneDevice->guiSetEnabled(SANE_NAME_HAND_SCANNER, enable);
0648     mSaneDevice->guiSetEnabled(SANE_NAME_THREE_PASS, enable);
0649     mSaneDevice->guiSetEnabled(SANE_NAME_GRAYIFY, enable);
0650     mSaneDevice->guiSetEnabled(SANE_NAME_CONTRAST, enable);
0651     mSaneDevice->guiSetEnabled(SANE_NAME_BRIGHTNESS, enable);
0652     mSaneDevice->guiSetEnabled(SANE_NAME_SCAN_RESOLUTION, enable);
0653     mSaneDevice->guiSetEnabled(SANE_NAME_SCAN_X_RESOLUTION, enable);
0654     mSaneDevice->guiSetEnabled(SANE_NAME_SCAN_Y_RESOLUTION, enable);
0655 
0656     mAreaSelect->setEnabled(enable);
0657 }
0658 
0659 KScanDevice::Status ScanParams::prepareScan(QString *vfp)
0660 {
0661     qCDebug(LIBKOOKASCAN_LOG) << "scan mode=" << mScanMode;
0662 
0663     setScanDestination(KLocalizedString());     // reset progress display
0664 
0665     // Check compatibility of scan settings
0666     int format;
0667     int depth;
0668     mSaneDevice->getCurrentFormat(&format, &depth);
0669     if (depth == 1 && format != SANE_FRAME_GRAY) {  // 1-bit scan depth in colour?
0670         KMessageBox::error(this, i18n("1-bit depth scan cannot be done in color"));
0671         return (KScanDevice::ParamError);
0672     } else if (depth == 16) {
0673         KMessageBox::error(this, i18n("16-bit depth scans are not supported"));
0674         return (KScanDevice::ParamError);
0675     }
0676 
0677     QString virtfile;
0678     if (mScanMode == ScanParams::SaneDebugMode || mScanMode == ScanParams::VirtualScannerMode) {
0679         if (mVirtualFile != nullptr) {
0680             virtfile = QFile::decodeName(mVirtualFile->get());
0681         }
0682         if (virtfile.isEmpty()) {
0683             KMessageBox::error(this, i18n("A file must be entered for testing or virtual scanning"));
0684             return (KScanDevice::ParamError);
0685         }
0686 
0687         QFileInfo fi(virtfile);
0688         if (!fi.exists()) {
0689             KMessageBox::error(this, xi18nc("@info", "The testing or virtual file <filename>%1</filename><nl/>was not found or is not readable.", virtfile));
0690             return (KScanDevice::ParamError);
0691         }
0692 
0693         if (mScanMode == ScanParams::SaneDebugMode) {
0694             QMimeDatabase db;
0695             QMimeType mime = db.mimeTypeForFile(virtfile);
0696             if (!(mime.inherits("image/x-portable-bitmap") ||
0697                   mime.inherits("image/x-portable-greymap") ||
0698                   mime.inherits("image/x-portable-pixmap"))) {
0699                 KMessageBox::error(this, xi18nc("@info", "SANE Debug can only read PNM files.<nl/>"
0700                                               "The specified file is type <icode>%1</icode>.", mime.name()));
0701                 return (KScanDevice::ParamError);
0702             }
0703         }
0704     }
0705 
0706     if (vfp != nullptr) {
0707         *vfp = virtfile;
0708     }
0709     return (KScanDevice::Ok);
0710 }
0711 
0712 
0713 void ScanParams::setScanDestination(const KLocalizedString &dest)
0714 {
0715     KLocalizedString labelText;
0716     if (dest.isEmpty()) labelText = kxi18n("Scan in progress");
0717     else labelText = kxi18n("Scan in progress<nl/><nl/>%1").subs(dest);
0718     mProgressDialog->setLabelText(labelText.toString(Kuit::RichText));
0719 }
0720 
0721 
0722 void ScanParams::slotScanProgress(int value)
0723 {
0724     mProgressDialog->setValue(value);
0725 }
0726 
0727 
0728 /* Slot called to start acquiring a preview */
0729 void ScanParams::slotAcquirePreview()
0730 {
0731 
0732     // TODO: should be able to preview in Virtual Scanner mode, it just means
0733     // that the preview image will be the same size as the final image (which
0734     // doesn't matter).
0735 
0736     if (mScanMode == ScanParams::VirtualScannerMode) {
0737         KMessageBox::error(this, i18n("Cannot preview in Virtual Scanner mode"));
0738         return;
0739     }
0740 
0741     QString virtfile;
0742     KScanDevice::Status stat = prepareScan(&virtfile);
0743     if (stat != KScanDevice::Ok) {
0744         return;
0745     }
0746 
0747     //qCDebug(LIBKOOKASCAN_LOG) << "scan mode=" << mScanMode << "virtfile" << virtfile;
0748 
0749     KScanOption *greyPreview = mSaneDevice->getExistingGuiElement(SANE_NAME_GRAY_PREVIEW);
0750     int gp = 0;
0751     if (greyPreview != nullptr) {
0752         greyPreview->get(&gp);
0753     }
0754 
0755     setMaximalScanSize();               // always preview at maximal size
0756     mAreaSelect->selectCustomSize(QRect());     // reset selector to reflect that
0757 
0758     stat = mSaneDevice->acquirePreview(gp);
0759     if (stat != KScanDevice::Ok) {
0760         qCWarning(LIBKOOKASCAN_LOG) << "Error, preview status " << stat;
0761     }
0762 }
0763 
0764 /* Slot called to start scanning */
0765 void ScanParams::slotStartScan()
0766 {
0767     QString virtfile;
0768     KScanDevice::Status stat = prepareScan(&virtfile);
0769     if (stat != KScanDevice::Ok) {
0770         return;
0771     }
0772 
0773     //qCDebug(LIBKOOKASCAN_LOG) << "scan mode=" << mScanMode << "virtfile" << virtfile;
0774 
0775     if (mScanMode != ScanParams::VirtualScannerMode) {  // acquire via SANE
0776 #if 0
0777 // TODO: port/update
0778         if (adf == ADF_OFF) {
0779 #endif
0780             qCDebug(LIBKOOKASCAN_LOG) << "Start to acquire image";
0781             stat = mSaneDevice->acquireScan();
0782 #if 0
0783         } else {
0784             //qCDebug(LIBKOOKASCAN_LOG) << "ADF Scan not yet implemented :-/";
0785             // stat = performADFScan();
0786         }
0787 #endif
0788     } else {                    // acquire via Qt-IMGIO
0789         qCDebug(LIBKOOKASCAN_LOG) << "Acquiring from virtual file";
0790         stat = mSaneDevice->acquireScan(virtfile);
0791     }
0792 
0793     if (stat != KScanDevice::Ok) {
0794         qCDebug(LIBKOOKASCAN_LOG) << "Error, scan status " << stat;
0795     }
0796 }
0797 
0798 bool ScanParams::getGammaTableFrom(const QByteArray &opt, KGammaTable *gt)
0799 {
0800     KScanOption *so = mSaneDevice->getOption(opt, false);
0801     if (so == nullptr) {
0802         return (false);
0803     }
0804 
0805     if (!so->get(gt)) {
0806         return (false);
0807     }
0808 
0809     qCDebug(LIBKOOKASCAN_LOG) << "got from" << so->getName() << "=" << gt->toString();
0810     return (true);
0811 }
0812 
0813 bool ScanParams::setGammaTableTo(const QByteArray &opt, const KGammaTable *gt)
0814 {
0815     KScanOption *so = mSaneDevice->getOption(opt, false);
0816     if (so == nullptr) {
0817         return (false);
0818     }
0819 
0820     qCDebug(LIBKOOKASCAN_LOG) << "set" << so->getName() << "=" << gt->toString();
0821     so->set(gt);
0822     return (so->apply());
0823 }
0824 
0825 void ScanParams::slotEditCustGamma()
0826 {
0827     KGammaTable gt;                 // start with default values
0828 
0829     // Get the current gamma table from either the combined gamma table
0830     // option, or any one of the colour channel gamma tables.
0831     if (!getGammaTableFrom(SANE_NAME_GAMMA_VECTOR, &gt)) {
0832         if (!getGammaTableFrom(SANE_NAME_GAMMA_VECTOR_R, &gt)) {
0833             if (!getGammaTableFrom(SANE_NAME_GAMMA_VECTOR_G, &gt)) {
0834                 if (!getGammaTableFrom(SANE_NAME_GAMMA_VECTOR_B, &gt)) {
0835                     // Should not happen... but if it does, no problem
0836                     // the dialogue will just use the default values
0837                     // for an empty gamma table.
0838                     qCWarning(LIBKOOKASCAN_LOG) << "no existing/active gamma option!";
0839                 }
0840             }
0841         }
0842     }
0843     qCDebug(LIBKOOKASCAN_LOG) << "initial gamma table" << gt.toString();
0844 
0845 // TODO; Maybe need to have a separate GUI widget for each gamma table, a
0846 // little preview of the gamma curve (a GammaWidget) with an edit button.
0847 // Will avoid the special case for the SANE_NAME_CUSTOM_GAMMA button followed
0848 // by the edit button, and will allow separate editing of the R/G/B gamma
0849 // tables if the scanner has them.
0850 
0851     GammaDialog gdiag(&gt, this);
0852     connect(&gdiag, &GammaDialog::gammaToApply, this, &ScanParams::slotApplyGamma);
0853     gdiag.exec();
0854 }
0855 
0856 void ScanParams::slotApplyGamma(const KGammaTable *gt)
0857 {
0858     if (gt == nullptr) {
0859         return;
0860     }
0861 
0862     bool reload = false;
0863 
0864     KScanOption *so = mSaneDevice->getOption(SANE_NAME_CUSTOM_GAMMA);
0865     if (so != nullptr) {                // do we have a gamma switch?
0866         int cg = 0;
0867         if (so->get(&cg) && !cg) {          // yes, see if already on
0868             // if not, set it on now
0869             qCDebug(LIBKOOKASCAN_LOG) << "Setting gamma switch on";
0870             so->set(true);
0871             reload = so->apply();
0872         }
0873     }
0874 
0875     qCDebug(LIBKOOKASCAN_LOG) << "Applying gamma table" << gt->toString();
0876     reload |= setGammaTableTo(SANE_NAME_GAMMA_VECTOR, gt);
0877     reload |= setGammaTableTo(SANE_NAME_GAMMA_VECTOR_R, gt);
0878     reload |= setGammaTableTo(SANE_NAME_GAMMA_VECTOR_G, gt);
0879     reload |= setGammaTableTo(SANE_NAME_GAMMA_VECTOR_B, gt);
0880 
0881     if (reload) {
0882         mSaneDevice->reloadAllOptions();        // reload is needed
0883     }
0884 }
0885 
0886 // The user has changed an option.  Apply that;  as a result of doing so,
0887 // it may be necessary to reload every other scanner option apart from
0888 // this one.
0889 
0890 void ScanParams::slotOptionChanged(KScanOption *so)
0891 {
0892     if (so == nullptr || mSaneDevice == nullptr) {
0893         return;
0894     }
0895     mSaneDevice->applyOption(so);
0896 
0897     // Update the gamma edit button state, if the option exists
0898     setEditCustomGammaTableState();
0899 }
0900 
0901 // Enable editing of the gamma tables if any one of the gamma tables
0902 // exists and is currently active.
0903 
0904 void ScanParams::setEditCustomGammaTableState()
0905 {
0906     if (mSaneDevice == nullptr) {
0907         return;
0908     }
0909     if (mGammaEditButt == nullptr) {
0910         return;
0911     }
0912 
0913     bool butState = false;
0914 
0915     KScanOption *so = mSaneDevice->getOption(SANE_NAME_CUSTOM_GAMMA, false);
0916     if (so != nullptr) {
0917         butState = so->isActive();
0918     }
0919 
0920     if (!butState) {
0921         KScanOption *so = mSaneDevice->getOption(SANE_NAME_GAMMA_VECTOR, false);
0922         if (so != nullptr) {
0923             butState = so->isActive();
0924         }
0925     }
0926 
0927     if (!butState) {
0928         KScanOption *so = mSaneDevice->getOption(SANE_NAME_GAMMA_VECTOR_R, false);
0929         if (so != nullptr) {
0930             butState = so->isActive();
0931         }
0932     }
0933 
0934     if (!butState) {
0935         KScanOption *so = mSaneDevice->getOption(SANE_NAME_GAMMA_VECTOR_G, false);
0936         if (so != nullptr) {
0937             butState = so->isActive();
0938         }
0939     }
0940 
0941     if (!butState) {
0942         KScanOption *so = mSaneDevice->getOption(SANE_NAME_GAMMA_VECTOR_B, false);
0943         if (so != nullptr) {
0944             butState = so->isActive();
0945         }
0946     }
0947 
0948     qCDebug(LIBKOOKASCAN_LOG) << "Set state to" << butState;
0949     mGammaEditButt->setEnabled(butState);
0950 }
0951 
0952 // This assumes that the SANE unit for the scan area is millimetres.
0953 // All scanners out there appear to do this.
0954 void ScanParams::applyRect(const QRect &rect)
0955 {
0956     qCDebug(LIBKOOKASCAN_LOG) << "rect=" << rect;
0957 
0958     KScanOption *tl_x = mSaneDevice->getOption(SANE_NAME_SCAN_TL_X);
0959     KScanOption *tl_y = mSaneDevice->getOption(SANE_NAME_SCAN_TL_Y);
0960     KScanOption *br_x = mSaneDevice->getOption(SANE_NAME_SCAN_BR_X);
0961     KScanOption *br_y = mSaneDevice->getOption(SANE_NAME_SCAN_BR_Y);
0962 
0963     double min1, max1;
0964     double min2, max2;
0965 
0966     if (!rect.isValid()) {              // set full scan area
0967         tl_x->getRange(&min1, &max1); tl_x->set(min1);
0968         br_x->getRange(&min1, &max1); br_x->set(max1);
0969         tl_y->getRange(&min2, &max2); tl_y->set(min2);
0970         br_y->getRange(&min2, &max2); br_y->set(max2);
0971 
0972         qCDebug(LIBKOOKASCAN_LOG) << "setting full area" << min1 << min2 << "-" << max1 << max2;
0973     } else {
0974         double tlx = rect.left();
0975         double tly = rect.top();
0976         double brx = rect.right();
0977         double bry = rect.bottom();
0978 
0979         tl_x->getRange(&min1, &max1);
0980         if (tlx < min1) {
0981             brx += (min1 - tlx);
0982             tlx = min1;
0983         }
0984         tl_x->set(tlx); br_x->set(brx);
0985 
0986         tl_y->getRange(&min2, &max2);
0987         if (tly < min2) {
0988             bry += (min2 - tly);
0989             tly = min2;
0990         }
0991         tl_y->set(tly); br_y->set(bry);
0992 
0993         qCDebug(LIBKOOKASCAN_LOG) << "setting area" << tlx << tly << "-" << brx << bry;
0994     }
0995 
0996     tl_x->apply();
0997     tl_y->apply();
0998     br_x->apply();
0999     br_y->apply();
1000 }
1001 
1002 //  The previewer is telling us that the user has drawn or auto-selected a
1003 //  new preview area (specified in millimetres).
1004 void ScanParams::slotNewPreviewRect(const QRect &rect)
1005 {
1006     qCDebug(LIBKOOKASCAN_LOG) << "rect=" << rect;
1007     applyRect(rect);
1008     mAreaSelect->selectSize(rect);
1009 }
1010 
1011 //  A new preset scan size or orientation chosen by the user
1012 void ScanParams::slotScanSizeSelected(const QRect &rect)
1013 {
1014     qCDebug(LIBKOOKASCAN_LOG) << "rect=" << rect << "full=" << !rect.isValid();
1015     applyRect(rect);
1016     emit newCustomScanSize(rect);
1017 }
1018 
1019 /*
1020  * sets the scan area to the default, which is the whole area.
1021  */
1022 void ScanParams::setMaximalScanSize()
1023 {
1024     qCDebug(LIBKOOKASCAN_LOG) << "Setting to default";
1025     slotScanSizeSelected(QRect());
1026 }
1027 
1028 void ScanParams::slotNewResolution(KScanOption *opt)
1029 {
1030     KScanOption *opt_x = mSaneDevice->getExistingGuiElement(SANE_NAME_SCAN_X_RESOLUTION);
1031     if (opt_x == nullptr) {
1032         opt_x = mSaneDevice->getExistingGuiElement(SANE_NAME_SCAN_RESOLUTION);
1033     }
1034     KScanOption *opt_y = mSaneDevice->getExistingGuiElement(SANE_NAME_SCAN_Y_RESOLUTION);
1035 
1036     int x_res = 0;                                      // get the X resolution
1037     if (opt_x != nullptr && opt_x->isValid()) {
1038         opt_x->get(&x_res);
1039     }
1040 
1041     int y_res = 0;                                      // get the Y resolution
1042     if (opt_y != nullptr && opt_y->isValid()) {
1043         opt_y->get(&y_res);
1044     }
1045 
1046     qCDebug(LIBKOOKASCAN_LOG) << "X/Y resolution" << x_res << y_res;
1047 
1048     if (y_res == 0) {
1049         y_res = x_res;                  // use X res if Y unavailable
1050     }
1051     if (x_res == 0) {
1052         x_res = y_res;                  // unlikely, but orthogonal
1053     }
1054 
1055     if (x_res == 0 && y_res == 0) {
1056         qCWarning(LIBKOOKASCAN_LOG) << "resolution not available!";
1057     } else {
1058         emit scanResolutionChanged(x_res, y_res);
1059     }
1060 }
1061 
1062 void ScanParams::slotNewScanMode()
1063 {
1064     int format = SANE_FRAME_RGB;
1065     int depth = 8;
1066     mSaneDevice->getCurrentFormat(&format, &depth);
1067 
1068     int strips = (format == SANE_FRAME_GRAY ? 1 : 3);
1069     qCDebug(LIBKOOKASCAN_LOG) << "format" << format << "depth" << depth << "-> strips" << strips;
1070 
1071     if (strips == 1 && depth == 1) {            // bitmap scan
1072         emit scanModeChanged(0);            // 8 pixels per byte
1073     } else {
1074         // bytes per pixel
1075         emit scanModeChanged(strips * (depth == 16 ? 2 : 1));
1076     }
1077 }
1078 
1079 KScanDevice::Status ScanParams::performADFScan()
1080 {
1081     KScanDevice::Status stat = KScanDevice::Ok;
1082     bool          scan_on = true;
1083 
1084     /* The scan source should be set to ADF by the SourceSelect-Dialog */
1085 
1086     while (scan_on) {
1087         scan_on = false;
1088     }
1089     return (stat);
1090 }