File indexing completed on 2024-04-28 04:32:23

0001 /*
0002  * SPDX-FileCopyrightText: 2007-2008 Kare Sars <kare dot sars at iki dot fi>
0003  * SPDX-FileCopyrightText: 2007-2008 Gilles Caulier <caulier dot gilles at gmail dot com>
0004  * SPDX-FileCopyrightText: 2014 Gregor Mitsch : port to KDE5 frameworks
0005  * SPDX-FileCopyrightText: 2021 Alexander Stippich <a.stippich@gmx.net>
0006  *
0007  * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0008  */
0009 
0010 #include "ksanewidget_p.h"
0011 
0012 #include <QImage>
0013 #include <QScrollArea>
0014 #include <QScrollBar>
0015 #include <QList>
0016 #include <QLabel>
0017 #include <QMessageBox>
0018 #include <QMetaMethod>
0019 #include <QPageSize>
0020 
0021 #include <KLocalizedString>
0022 
0023 #include <ksane_debug.h>
0024 
0025 #define SCALED_PREVIEW_MAX_SIDE 400
0026 
0027 static constexpr int ActiveSelection = 100000;
0028 static constexpr int PageSizeWiggleRoom = 2; // in mm
0029 
0030 namespace KSaneIface
0031 {
0032 
0033 KSaneWidgetPrivate::KSaneWidgetPrivate(KSaneWidget *parent):
0034     q(parent)
0035 {
0036     // Device independent UI variables
0037     m_optsTabWidget = nullptr;
0038     m_basicOptsTab  = nullptr;
0039     m_otherOptsTab  = nullptr;
0040     m_zInBtn        = nullptr;
0041     m_zOutBtn       = nullptr;
0042     m_zSelBtn       = nullptr;
0043     m_zFitBtn       = nullptr;
0044     m_clearSelBtn   = nullptr;
0045     m_prevBtn       = nullptr;
0046     m_scanBtn       = nullptr;
0047     m_cancelBtn     = nullptr;
0048     m_previewViewer = nullptr;
0049     m_autoSelect    = true;
0050     m_selIndex      = ActiveSelection;
0051     m_warmingUp     = nullptr;
0052     m_progressBar   = nullptr;
0053 
0054     // scanning variables
0055     m_isPreview     = false;
0056 
0057     m_splitGamChB   = nullptr;
0058     m_commonGamma   = nullptr;
0059     m_previewDPI    = 0;
0060 
0061     m_previewWidth  = 0;
0062     m_previewHeight = 0;
0063 
0064     m_sizeCodes = {
0065         QPageSize::A4,
0066         QPageSize::A5,
0067         QPageSize::A6,
0068         QPageSize::Letter,
0069         QPageSize::Legal,
0070         QPageSize::Tabloid,
0071         QPageSize::A3,
0072         QPageSize::B3,
0073         QPageSize::B4,
0074         QPageSize::B5,
0075         QPageSize::B6,
0076         QPageSize::C5E,
0077         QPageSize::Comm10E,
0078         QPageSize::DLE,
0079         QPageSize::Executive,
0080         QPageSize::Folio,
0081         QPageSize::Ledger,
0082         QPageSize::JisB3,
0083         QPageSize::JisB4,
0084         QPageSize::JisB5,
0085         QPageSize::JisB6,
0086     };
0087 
0088     clearDeviceOptions();
0089 }
0090 
0091 void KSaneWidgetPrivate::clearDeviceOptions()
0092 {
0093     m_optSource     = nullptr;
0094     m_colorOpts     = nullptr;
0095     m_optNegative   = nullptr;
0096     m_optFilmType   = nullptr;
0097     m_optMode       = nullptr;
0098     m_optDepth      = nullptr;
0099     m_optRes        = nullptr;
0100     m_optResX       = nullptr;
0101     m_optResY       = nullptr;
0102     m_optTlX        = nullptr;
0103     m_optTlY        = nullptr;
0104     m_optBrX        = nullptr;
0105     m_optBrY        = nullptr;
0106     m_optGamR       = nullptr;
0107     m_optGamG       = nullptr;
0108     m_optGamB       = nullptr;
0109     m_optPreview    = nullptr;
0110     m_optWaitForBtn = nullptr;
0111     m_scanOngoing   = false;
0112 
0113     m_handledOptions.clear();
0114 
0115     // remove the remaining layouts/widgets
0116     delete m_basicOptsTab;
0117     m_basicOptsTab = nullptr;
0118 
0119     delete m_otherOptsTab;
0120     m_otherOptsTab = nullptr;
0121 }
0122 
0123 void KSaneWidgetPrivate::signalDevListUpdate(const QList<KSaneCore::DeviceInformation*> &deviceList)
0124 {
0125     QList<KSaneWidget::DeviceInfo> list;
0126     list.reserve(deviceList.count());
0127     for (const auto &device : deviceList) {
0128         KSaneWidget::DeviceInfo newDevice;
0129         newDevice.model = device->model();
0130         newDevice.vendor = device->vendor();
0131         newDevice.name = device->name();
0132         newDevice.type = device->type();
0133         list << newDevice;
0134     }
0135     Q_EMIT q->availableDevices(list);
0136 }
0137 
0138 float KSaneWidgetPrivate::ratioToScanAreaX(float ratio)
0139 {
0140     if (!m_optBrX) {
0141         return 0.0;
0142     }
0143     float max = m_optBrX->maximumValue().toFloat();
0144 
0145     return max * ratio;
0146 }
0147 
0148 float KSaneWidgetPrivate::ratioToScanAreaY(float ratio)
0149 {
0150     if (!m_optBrY) {
0151         return 0.0;
0152     }
0153     float max = m_optBrY->maximumValue().toFloat();
0154 
0155     return max * ratio;
0156 }
0157 
0158 float KSaneWidgetPrivate::scanAreaToRatioX(float scanArea)
0159 {
0160     if (!m_optBrX) {
0161         return 0.0;
0162     }
0163     float max = m_optBrX->maximumValue().toFloat();
0164 
0165     if (scanArea > max) {
0166         return 1.0;
0167     }
0168 
0169     if (max < 0.0001) {
0170         return 0.0;
0171     }
0172 
0173     return scanArea / max;
0174 }
0175 
0176 float KSaneWidgetPrivate::scanAreaToRatioY(float scanArea)
0177 {
0178     if (!m_optBrY) {
0179         return 0.0;
0180     }
0181     float max = m_optBrY->maximumValue().toFloat();
0182 
0183     if (scanArea > max) {
0184         return 1.0;
0185     }
0186 
0187     if (max < 0.0001) {
0188         return 0.0;
0189     }
0190 
0191     return scanArea / max;
0192 }
0193 
0194 static float mmToDispUnit(float mm) {
0195     static QLocale locale;
0196 
0197     if (locale.measurementSystem() == QLocale::MetricSystem) {
0198         return mm;
0199     }
0200     // else
0201     return mm / 25.4;
0202 }
0203 
0204 float KSaneWidgetPrivate::ratioToDispUnitX(float ratio)
0205 {
0206     if (!m_optBrX) {
0207         return 0.0;
0208     }
0209 
0210     float result = ratioToScanAreaX(ratio);
0211 
0212     if (m_optBrX->valueUnit() == KSaneCore::Option::UnitMilliMeter) {
0213         return mmToDispUnit(result);
0214     }
0215     else if (m_optBrX->valueUnit() == KSaneCore::Option::UnitPixel && m_optRes) {
0216         // get current DPI
0217         float dpi = m_optRes->value().toFloat();
0218         if (dpi > 1) {
0219             result = result / (dpi / 25.4);
0220             return mmToDispUnit(result);
0221         }
0222     }
0223     qCDebug(KSANE_LOG) << "Failed to convert scan area to mm";
0224     return ratio;
0225 }
0226 
0227 float KSaneWidgetPrivate::ratioToDispUnitY(float ratio)
0228 {
0229     if (!m_optBrY) {
0230         return 0.0;
0231     }
0232 
0233     float result = ratioToScanAreaY(ratio);
0234 
0235     if (m_optBrY->valueUnit() == KSaneCore::Option::UnitMilliMeter) {
0236         return mmToDispUnit(result);
0237     }
0238     else if (m_optBrY->valueUnit() == KSaneCore::Option::UnitPixel && m_optRes) {
0239         // get current DPI
0240         float dpi = m_optRes->value().toFloat();
0241         if (dpi > 1) {
0242             result = result / (dpi / 25.4);
0243             return mmToDispUnit(result);
0244         }
0245     }
0246     qCDebug(KSANE_LOG) << "Failed to convert scan area to display unit";
0247     return ratio;
0248 }
0249 
0250 float KSaneWidgetPrivate::dispUnitToRatioX(float value)
0251 {
0252     float valueMax = ratioToDispUnitX(1);
0253     if (valueMax < 1) {
0254         return 0.0;
0255     }
0256     return value / valueMax;
0257 }
0258 
0259 float KSaneWidgetPrivate::dispUnitToRatioY(float value)
0260 {
0261     float valueMax = ratioToDispUnitY(1);
0262     if (valueMax < 1) {
0263         return 0.0;
0264     }
0265     return value / valueMax;
0266 }
0267 
0268 KSaneOptionWidget *KSaneWidgetPrivate::createOptionWidget(QWidget *parent, KSaneCore::Option *option)
0269 {
0270     KSaneOptionWidget *widget = nullptr;
0271     switch (option->type()) {
0272         case KSaneCore::Option::TypeBool:
0273             widget = new LabeledCheckbox(parent, option);
0274             break;
0275         case KSaneCore::Option::TypeInteger:
0276             widget = new LabeledSlider(parent, option);
0277             break;
0278         case KSaneCore::Option::TypeDouble:
0279             widget = new LabeledFSlider(parent, option);
0280             break;
0281         case KSaneCore::Option::TypeValueList:
0282             widget = new LabeledCombo(parent, option);
0283             break;
0284         case KSaneCore::Option::TypeString:
0285             widget = new LabeledEntry(parent, option);
0286             break;
0287         case KSaneCore::Option::TypeGamma:
0288             widget = new LabeledGamma(parent, option);
0289             break;
0290         case KSaneCore::Option::TypeAction:
0291             widget = new KSaneButton(parent, option);
0292             break;
0293         default:
0294             widget = new KSaneOptionWidget(parent, option);
0295             break;
0296     }
0297     m_handledOptions.insert(option->name());
0298     return widget;
0299 }
0300 
0301 void KSaneWidgetPrivate::createOptInterface()
0302 {
0303     m_basicOptsTab = new QWidget;
0304     m_basicScrollA->setWidget(m_basicOptsTab);
0305 
0306     QVBoxLayout *basicLayout = new QVBoxLayout(m_basicOptsTab);
0307     KSaneCore::Option *option = m_ksaneCoreInterface->getOption(KSaneCore::Interface::SourceOption);
0308     // Scan Source
0309     if (option != nullptr) {
0310         m_optSource = option;
0311         KSaneOptionWidget *source = createOptionWidget(m_basicOptsTab, option);
0312         basicLayout->addWidget(source);
0313         connect(m_optSource, &KSaneCore::Option::valueChanged, this, &KSaneWidgetPrivate::checkInvert, Qt::QueuedConnection);
0314         connect(m_optSource, &KSaneCore::Option::valueChanged, this, [this]() {
0315             m_previewViewer->setMultiselectionEnabled(!scanSourceADF());
0316         });
0317     }
0318 
0319     // film-type (note: No translation)
0320     if ((option = m_ksaneCoreInterface->getOption(KSaneCore::Interface::FilmTypeOption)) != nullptr) {
0321         m_optFilmType = option;
0322         KSaneOptionWidget *film = createOptionWidget(m_basicOptsTab, option);
0323         basicLayout->addWidget(film);
0324         connect(m_optFilmType, &KSaneCore::Option::valueChanged, this, &KSaneWidgetPrivate::checkInvert, Qt::QueuedConnection);
0325     } else if ((option = m_ksaneCoreInterface->getOption(KSaneCore::Interface::NegativeOption)) != nullptr) {
0326         m_optNegative = option;
0327         KSaneOptionWidget *negative = createOptionWidget(m_basicOptsTab, option);
0328         basicLayout->addWidget(negative);
0329     }
0330     // Scan mode
0331     if ((option = m_ksaneCoreInterface->getOption(KSaneCore::Interface::ScanModeOption)) != nullptr) {
0332         m_optMode = option;
0333         KSaneOptionWidget *mode = createOptionWidget(m_basicOptsTab, option);
0334         basicLayout->addWidget(mode);
0335     }
0336     // Bitdepth
0337     if ((option = m_ksaneCoreInterface->getOption(KSaneCore::Interface::BitDepthOption)) != nullptr) {
0338         m_optDepth = option;
0339         KSaneOptionWidget *bitDepth = createOptionWidget(m_basicOptsTab, option);
0340         basicLayout->addWidget(bitDepth);
0341     }
0342     // Threshold
0343     if ((option = m_ksaneCoreInterface->getOption(KSaneCore::Interface::ThresholdOption)) != nullptr) {
0344         KSaneOptionWidget *threshold = createOptionWidget(m_basicOptsTab, option);
0345         basicLayout->addWidget(threshold);
0346     }
0347     // Resolution
0348     if ((option = m_ksaneCoreInterface->getOption(KSaneCore::Interface::ResolutionOption)) != nullptr) {
0349         m_optRes = option;
0350         KSaneOptionWidget *resolution = createOptionWidget(m_basicOptsTab, option);
0351         basicLayout->addWidget(resolution);
0352     }
0353     // These two next resolution options are a bit tricky.
0354     if ((option = m_ksaneCoreInterface->getOption(KSaneCore::Interface::XResolutionOption)) != nullptr) {
0355         m_optResX = option;
0356         if (!m_optRes) {
0357             m_optRes = m_optResX;
0358         }
0359         KSaneOptionWidget *optResX = createOptionWidget(m_basicOptsTab, option);
0360         basicLayout->addWidget(optResX);
0361     }
0362     if ((option = m_ksaneCoreInterface->getOption(KSaneCore::Interface::YResolutionOption)) != nullptr) {
0363         m_optResY = option;
0364         KSaneOptionWidget *optResY = createOptionWidget(m_basicOptsTab, option);
0365         basicLayout->addWidget(optResY);
0366     }
0367 
0368     // save a pointer to the preview option if possible
0369     if ((option = m_ksaneCoreInterface->getOption(KSaneCore::Interface::PreviewOption)) != nullptr) {
0370         m_optPreview = option;
0371         m_handledOptions.insert(option->name());
0372     }
0373 
0374     // save a pointer to the "wait-for-button" option if possible (Note: No translation)
0375     if ((option = m_ksaneCoreInterface->getOption(KSaneCore::Interface::WaitForButtonOption)) != nullptr) {
0376         m_optWaitForBtn = option;
0377         m_handledOptions.insert(option->name());
0378     }
0379 
0380     // scan area (Do not add the widgets)
0381     if ((option = m_ksaneCoreInterface->getOption(KSaneCore::Interface::TopLeftXOption)) != nullptr) {
0382         m_optTlX = option;
0383         connect(option, &KSaneCore::Option::valueChanged, this, &KSaneWidgetPrivate::setTLX);
0384         m_handledOptions.insert(option->name());
0385         connect(option, &KSaneCore::Option::optionReloaded, this, &KSaneWidgetPrivate::updatePreviewViewer);
0386     }
0387     if ((option = m_ksaneCoreInterface->getOption(KSaneCore::Interface::TopLeftYOption)) != nullptr) {
0388         m_optTlY = option;
0389         connect(option, &KSaneCore::Option::valueChanged, this, &KSaneWidgetPrivate::setTLY);
0390         m_handledOptions.insert(option->name());
0391         connect(option, &KSaneCore::Option::optionReloaded, this, &KSaneWidgetPrivate::updatePreviewViewer);
0392     }
0393     if ((option = m_ksaneCoreInterface->getOption(KSaneCore::Interface::BottomRightXOption)) != nullptr) {
0394         m_optBrX = option;
0395         connect(option, &KSaneCore::Option::valueChanged, this, &KSaneWidgetPrivate::setBRX);
0396         m_handledOptions.insert(option->name());
0397         connect(option, &KSaneCore::Option::optionReloaded, this, &KSaneWidgetPrivate::updatePreviewViewer);
0398     }
0399     if ((option = m_ksaneCoreInterface->getOption(KSaneCore::Interface::BottomRightYOption)) != nullptr) {
0400         m_optBrY = option;
0401         connect(option, &KSaneCore::Option::valueChanged, this, &KSaneWidgetPrivate::setBRY);
0402         m_handledOptions.insert(option->name());
0403         connect(option, &KSaneCore::Option::optionReloaded, this, &KSaneWidgetPrivate::updatePreviewViewer);
0404     }
0405     if ((option = m_ksaneCoreInterface->getOption(KSaneCore::Interface::PageSizeOption)) != nullptr) {
0406         m_handledOptions.insert(option->name());
0407     }
0408 
0409     // Add our own size options
0410     m_scanareaPapersize = new LabeledCombo(m_basicOptsTab, i18n("Scan Area Size"));
0411     connect(m_scanareaPapersize, &LabeledCombo::activated, this, &KSaneWidgetPrivate::setPageSize);
0412     basicLayout->addWidget(m_scanareaPapersize);
0413 
0414     static QLocale locale;
0415     QString unitSuffix = locale.measurementSystem() == QLocale::MetricSystem ? i18n(" mm") : i18n(" inch");
0416 
0417     m_scanareaWidth = new LabeledFSlider(m_basicOptsTab, i18n("Width"), 0.0f, 500.0f, 0.1f);
0418     m_scanareaWidth->setSuffix(unitSuffix);
0419     connect(m_scanareaWidth, &LabeledFSlider::valueChanged, this, &KSaneWidgetPrivate::updateScanSelection);
0420     basicLayout->addWidget(m_scanareaWidth);
0421 
0422     m_scanareaHeight = new LabeledFSlider(m_basicOptsTab, i18n("Height"), 0.0f, 500.0f, 0.1f);
0423     m_scanareaHeight->setSuffix(unitSuffix);
0424     connect(m_scanareaHeight, &LabeledFSlider::valueChanged, this, &KSaneWidgetPrivate::updateScanSelection);
0425     basicLayout->addWidget(m_scanareaHeight);
0426 
0427     m_scanareaX = new LabeledFSlider(m_basicOptsTab, i18n("X Offset"), 0.0f, 500.0f, 0.1f);
0428     m_scanareaX->setSuffix(unitSuffix);
0429     connect(m_scanareaX, &LabeledFSlider::valueChanged, this, &KSaneWidgetPrivate::updateScanSelection);
0430     basicLayout->addWidget(m_scanareaX);
0431 
0432     m_scanareaY = new LabeledFSlider(m_basicOptsTab, i18n("Y Offset"), 0.0f, 500.0f, 0.1f);
0433     m_scanareaY->setSuffix(unitSuffix);
0434     connect(m_scanareaY, &LabeledFSlider::valueChanged, this, &KSaneWidgetPrivate::updateScanSelection);
0435     basicLayout->addWidget(m_scanareaY);
0436 
0437     // add a stretch to the end to keep the parameters at the top
0438     basicLayout->addStretch();
0439 
0440     // more advanced options
0441     m_advancedOptsTab = new QWidget;
0442     m_advancedScrollA->setWidget(m_advancedOptsTab);
0443 
0444     QVBoxLayout *advancedLayout = new QVBoxLayout(m_advancedOptsTab);
0445 
0446     // Color Options Frame
0447     m_colorOpts = new QWidget(m_advancedOptsTab);
0448     advancedLayout->addWidget(m_colorOpts);
0449     QVBoxLayout *colorLayout = new QVBoxLayout(m_colorOpts);
0450     colorLayout->setContentsMargins(0, 0, 0, 0);
0451 
0452     // Add Color correction to the color "frame"
0453     if ((option = m_ksaneCoreInterface->getOption(KSaneCore::Interface::BrightnessOption)) != nullptr) {
0454         KSaneOptionWidget *brightness = createOptionWidget(m_advancedOptsTab, option);
0455         colorLayout->addWidget(brightness);
0456     }
0457 
0458     if ((option = m_ksaneCoreInterface->getOption(KSaneCore::Interface::ContrastOption)) != nullptr) {
0459         KSaneOptionWidget *contrast = createOptionWidget(m_advancedOptsTab, option);
0460         colorLayout->addWidget(contrast);
0461     }
0462 
0463     // Add gamma tables to the color "frame"
0464     QWidget *gamma_frm = new QWidget(m_colorOpts);
0465     colorLayout->addWidget(gamma_frm);
0466     QVBoxLayout *gam_frm_l = new QVBoxLayout(gamma_frm);
0467     gam_frm_l->setContentsMargins(0, 0, 0, 0);
0468     LabeledGamma *gammaR = nullptr;
0469     LabeledGamma *gammaG = nullptr;
0470     LabeledGamma *gammaB = nullptr;
0471     if ((option = m_ksaneCoreInterface->getOption(KSaneCore::Interface::GammaRedOption)) != nullptr) {
0472         m_optGamR = option;
0473         gammaR = new LabeledGamma(gamma_frm, option, Qt::red);
0474         gam_frm_l->addWidget(gammaR);
0475         m_handledOptions.insert(option->name());
0476     }
0477     if ((option = m_ksaneCoreInterface->getOption(KSaneCore::Interface::GammaGreenOption)) != nullptr) {
0478         m_optGamG = option;
0479         gammaG = new LabeledGamma(gamma_frm, option, Qt::green);
0480         gam_frm_l->addWidget(gammaG);
0481         m_handledOptions.insert(option->name());
0482     }
0483     if ((option = m_ksaneCoreInterface->getOption(KSaneCore::Interface::GammaBlueOption)) != nullptr) {
0484         m_optGamB = option;
0485         gammaB = new LabeledGamma(gamma_frm, option, Qt::blue);
0486         gam_frm_l->addWidget(gammaB);
0487         m_handledOptions.insert(option->name());
0488     }
0489 
0490     if ((m_optGamR != nullptr) && (m_optGamG != nullptr) && (m_optGamB != nullptr)
0491         && (gammaR != nullptr) && (gammaG != nullptr) && (gammaB != nullptr)  ) {
0492 
0493         m_commonGamma = new LabeledGamma(m_colorOpts, i18n("Image intensity"), gammaR->maxValue());
0494 
0495         colorLayout->addWidget(m_commonGamma);
0496 
0497         m_commonGamma->setToolTip(i18n("Gamma-correction table. In color mode this option equally " \
0498             "affects the red, green, and blue channels simultaneously (i.e., it is an " \
0499             "intensity gamma table)."));
0500 
0501         connect(m_commonGamma, &LabeledGamma::valuesChanged, gammaR, &LabeledGamma::setValues);
0502         connect(m_commonGamma, &LabeledGamma::valuesChanged, gammaG, &LabeledGamma::setValues);
0503         connect(m_commonGamma, &LabeledGamma::valuesChanged, gammaB, &LabeledGamma::setValues);
0504 
0505         m_splitGamChB = new LabeledCheckbox(m_colorOpts, i18n("Separate color intensity tables"));
0506         colorLayout->addWidget(m_splitGamChB);
0507 
0508         connect(m_splitGamChB, &LabeledCheckbox::toggled, gamma_frm, &QWidget::setVisible);
0509         connect(m_splitGamChB, &LabeledCheckbox::toggled, m_commonGamma, &LabeledGamma::setHidden);
0510 
0511         gamma_frm->hide();
0512     }
0513 
0514     if ((option = m_ksaneCoreInterface->getOption(KSaneCore::Interface::BlackLevelOption)) != nullptr) {
0515         KSaneOptionWidget *blackLevel = createOptionWidget(m_colorOpts, option);
0516         colorLayout->addWidget(blackLevel);
0517     }
0518 
0519     if ((option = m_ksaneCoreInterface->getOption(KSaneCore::Interface::WhiteLevelOption)) != nullptr) {
0520         KSaneOptionWidget *blackLevel = createOptionWidget(m_colorOpts, option);
0521         colorLayout->addWidget(blackLevel);
0522     }
0523 
0524     if ((option = m_ksaneCoreInterface->getOption(KSaneCore::Interface::InvertColorOption)) != nullptr) {
0525         m_optInvert = option;
0526         KSaneOptionWidget *invertColor = createOptionWidget(m_colorOpts, option);
0527         colorLayout->addWidget(invertColor);
0528         connect(m_optInvert, &KSaneCore::Option::valueChanged, this, &KSaneWidgetPrivate::invertPreview);
0529     }
0530 
0531     if ((option = m_ksaneCoreInterface->getOption(KSaneCore::Interface::BatchModeOption)) != nullptr) {
0532         KSaneOptionWidget *batchMode = createOptionWidget(m_advancedOptsTab, option);
0533         colorLayout->addWidget(batchMode);
0534     }
0535 
0536     if ((option = m_ksaneCoreInterface->getOption(KSaneCore::Interface::BatchDelayOption)) != nullptr) {
0537         KSaneOptionWidget *batchDelay = createOptionWidget(m_advancedOptsTab, option);
0538         colorLayout->addWidget(batchDelay);
0539     }
0540     advancedLayout->addStretch();
0541 
0542     // Remaining (un known) options go to the "Other Options"
0543     m_otherOptsTab = new QWidget;
0544     m_otherScrollA->setWidget(m_otherOptsTab);
0545 
0546     QVBoxLayout *otherLayout = new QVBoxLayout(m_otherOptsTab);
0547 
0548     const auto optionsList = m_ksaneCoreInterface->getOptionsList();
0549     // add the remaining parameters
0550     for (const auto option : optionsList) {
0551         if (m_handledOptions.find(option->name()) != m_handledOptions.end()) {
0552             continue;
0553         }
0554         if (option->type() != KSaneCore::Option::TypeDetectFail) {
0555             KSaneOptionWidget *widget = createOptionWidget(m_otherOptsTab, option);
0556             if (widget != nullptr) {
0557                 otherLayout->addWidget(widget);
0558             }
0559         }
0560     }
0561 
0562     // add a stretch to the end to keep the parameters at the top
0563     otherLayout->addStretch();
0564 
0565     // calculate label widths
0566     int labelWidth = 0;
0567     KSaneOptionWidget *tmpOption;
0568     // Basic Options
0569     for (int i = 0; i < basicLayout->count(); ++i) {
0570         if (basicLayout->itemAt(i) && basicLayout->itemAt(i)->widget()) {
0571             tmpOption = qobject_cast<KSaneOptionWidget *>(basicLayout->itemAt(i)->widget());
0572             if (tmpOption) {
0573                 labelWidth = qMax(labelWidth, tmpOption->labelWidthHint());
0574             }
0575         }
0576     }
0577     // Color Options
0578     for (int i = 0; i < colorLayout->count(); ++i) {
0579         if (colorLayout->itemAt(i) && colorLayout->itemAt(i)->widget()) {
0580             tmpOption = qobject_cast<KSaneOptionWidget *>(colorLayout->itemAt(i)->widget());
0581             if (tmpOption) {
0582                 labelWidth = qMax(labelWidth, tmpOption->labelWidthHint());
0583             }
0584         }
0585     }
0586     // Set label widths
0587     for (int i = 0; i < basicLayout->count(); ++i) {
0588         if (basicLayout->itemAt(i) && basicLayout->itemAt(i)->widget()) {
0589             tmpOption = qobject_cast<KSaneOptionWidget *>(basicLayout->itemAt(i)->widget());
0590             if (tmpOption) {
0591                 tmpOption->setLabelWidth(labelWidth);
0592             }
0593         }
0594     }
0595     for (int i = 0; i < colorLayout->count(); ++i) {
0596         if (colorLayout->itemAt(i) && colorLayout->itemAt(i)->widget()) {
0597             tmpOption = qobject_cast<KSaneOptionWidget *>(colorLayout->itemAt(i)->widget());
0598             if (tmpOption) {
0599                 tmpOption->setLabelWidth(labelWidth);
0600             }
0601         }
0602     }
0603     // Other Options
0604     labelWidth = 0;
0605     for (int i = 0; i < otherLayout->count(); ++i) {
0606         if (otherLayout->itemAt(i) && otherLayout->itemAt(i)->widget()) {
0607             tmpOption = qobject_cast<KSaneOptionWidget *>(otherLayout->itemAt(i)->widget());
0608             if (tmpOption) {
0609                 labelWidth = qMax(labelWidth, tmpOption->labelWidthHint());
0610             }
0611         }
0612     }
0613     for (int i = 0; i < otherLayout->count(); ++i) {
0614         if (otherLayout->itemAt(i) && otherLayout->itemAt(i)->widget()) {
0615             tmpOption = qobject_cast<KSaneOptionWidget *>(otherLayout->itemAt(i)->widget());
0616             if (tmpOption) {
0617                 tmpOption->setLabelWidth(labelWidth);
0618             }
0619         }
0620     }
0621 
0622     // ensure that we do not get a scrollbar at the bottom of the option of the options
0623     int min_width = m_basicOptsTab->sizeHint().width();
0624     if (min_width < m_otherOptsTab->sizeHint().width()) {
0625         min_width = m_otherOptsTab->sizeHint().width();
0626     }
0627 
0628     m_optsTabWidget->setMinimumWidth(min_width + m_basicScrollA->verticalScrollBar()->sizeHint().width() + 5);
0629 }
0630 
0631 void KSaneWidgetPrivate::updateCommonGamma()
0632 {
0633     // Gamma table special case
0634     if (m_optGamR && m_optGamG && m_optGamB) {
0635         m_commonGamma->setHidden(m_optGamR->state() == KSaneCore::Option::StateHidden);
0636         m_splitGamChB->setHidden(m_optGamR->state() == KSaneCore::Option::StateHidden);
0637     }
0638 }
0639 
0640 void KSaneWidgetPrivate::updatePreviewViewer()
0641 {
0642     // estimate the preview size and create an empty image
0643     // this is done so that you can select scan area without
0644     // having to scan a preview.
0645     updatePreviewSize();
0646 
0647     // ensure that we do not get a scrollbar at the bottom of the option of the options
0648     int min_width = m_basicOptsTab->sizeHint().width();
0649     if (min_width < m_otherOptsTab->sizeHint().width()) {
0650         min_width = m_otherOptsTab->sizeHint().width();
0651     }
0652 
0653     m_optsTabWidget->setMinimumWidth(min_width + m_basicScrollA->verticalScrollBar()->sizeHint().width() + 5);
0654 
0655     m_previewViewer->zoom2Fit();
0656 }
0657 
0658 void KSaneWidgetPrivate::handleSelection(float tl_x, float tl_y, float br_x, float br_y)
0659 {
0660     if ((m_optTlX == nullptr) || (m_optTlY == nullptr) || (m_optBrX == nullptr) || (m_optBrY == nullptr)) {
0661         // clear the selection since we can not set one
0662         m_previewViewer->setTLX(0);
0663         m_previewViewer->setTLY(0);
0664         m_previewViewer->setBRX(0);
0665         m_previewViewer->setBRY(0);
0666         return;
0667     }
0668 
0669     if ((m_previewImg.width() == 0) || (m_previewImg.height() == 0)) {
0670         m_scanareaX->setValue(0);
0671         m_scanareaY->setValue(0);
0672 
0673         m_scanareaWidth->setValue(ratioToDispUnitX(1));
0674         m_scanareaHeight->setValue(ratioToDispUnitY(1));
0675        return;
0676     }
0677 
0678     if (br_x < 0.0001) {
0679         m_scanareaWidth->setValue(ratioToDispUnitX(1));
0680         m_scanareaHeight->setValue(ratioToDispUnitY(1));
0681     }
0682     else {
0683         m_scanareaWidth->setValue(ratioToDispUnitX(br_x - tl_x));
0684         m_scanareaHeight->setValue(ratioToDispUnitY(br_y - tl_y));
0685     }
0686     m_scanareaX->setValue(ratioToDispUnitX(tl_x));
0687     m_scanareaY->setValue(ratioToDispUnitY(tl_y));
0688 
0689     m_optTlX->setValue(ratioToScanAreaX(tl_x));
0690     m_optTlY->setValue(ratioToScanAreaY(tl_y));
0691     m_optBrX->setValue(ratioToScanAreaX(br_x));
0692     m_optBrY->setValue(ratioToScanAreaY(br_y));
0693 }
0694 
0695 void KSaneWidgetPrivate::setTLX(const QVariant &x)
0696 {
0697     bool ok;
0698     float ftlx = x.toFloat(&ok);
0699     // ignore this when conversion not possible and during an active scan
0700     if (!ok || m_scanOngoing) {
0701         return;
0702     }
0703 
0704     float ratio = scanAreaToRatioX(ftlx);
0705     m_previewViewer->setTLX(ratio);
0706     m_scanareaX->setValue(ratioToDispUnitX(ratio));
0707 }
0708 
0709 void KSaneWidgetPrivate::setTLY(const QVariant &y)
0710 {
0711     bool ok;
0712     float ftly = y.toFloat(&ok);
0713     // ignore this when conversion not possible and during an active scan
0714     if (!ok || m_scanOngoing) {
0715         return;
0716     }
0717 
0718     float ratio = scanAreaToRatioY(ftly);
0719     m_previewViewer->setTLY(ratio);
0720     m_scanareaY->setValue(ratioToDispUnitY(ratio));
0721 }
0722 
0723 void KSaneWidgetPrivate::setBRX(const QVariant &x)
0724 {
0725     bool ok;
0726     float fbrx = x.toFloat(&ok);
0727     // ignore this when conversion not possible and during an active scan
0728     if (!ok || m_scanOngoing) {
0729         return;
0730     }
0731 
0732     float ratio = scanAreaToRatioX(fbrx);
0733     m_previewViewer->setBRX(ratio);
0734 
0735     if (!m_optTlX) {
0736         return;
0737     }
0738 
0739     QVariant tlx = m_optTlX->value();
0740     if (!tlx.isNull()) {
0741         float tlxRatio = scanAreaToRatioX(tlx.toFloat());
0742         m_scanareaWidth->setValue(ratioToDispUnitX(ratio) - ratioToDispUnitX(tlxRatio));
0743     }
0744 }
0745 
0746 void KSaneWidgetPrivate::setBRY(const QVariant &y)
0747 {
0748     bool ok;
0749     float fbry = y.toFloat(&ok);
0750     // ignore this when conversion not possible and during an active scan
0751     if (!ok || m_scanOngoing) {
0752         return;
0753     }
0754 
0755     float ratio = scanAreaToRatioY(fbry);
0756     m_previewViewer->setBRY(ratio);
0757 
0758     if (!m_optTlY) {
0759         return;
0760     }
0761     QVariant tly = m_optTlY->value();
0762     if (!tly.isNull()) {
0763         float tlyRatio = scanAreaToRatioY(tly.toFloat());
0764         m_scanareaHeight->setValue(ratioToDispUnitY(ratio) - ratioToDispUnitY(tlyRatio));
0765     }
0766 }
0767 
0768 void KSaneWidgetPrivate::updatePreviewSize()
0769 {
0770     float max_x = 0;
0771     float max_y = 0;
0772     float ratio;
0773     int x, y;
0774 
0775     // check if an update is necessary
0776     if (m_optBrX != nullptr) {
0777         max_x = m_optBrX->maximumValue().toFloat();
0778     }
0779     if (m_optBrY != nullptr) {
0780         max_y = m_optBrY->maximumValue().toFloat();
0781     }
0782     if ((max_x == m_previewWidth) && (max_y == m_previewHeight)) {
0783         //qCDebug(KSANE_LOG) << "no preview size change";
0784         return;
0785     }
0786 
0787     // The preview size has changed
0788     m_previewWidth  = max_x;
0789     m_previewHeight = max_y;
0790 
0791     // set the scan area to the whole area
0792     m_previewViewer->clearSelections();
0793     if (m_optTlX != nullptr) {
0794         m_optTlX->setValue(0);
0795     }
0796     if (m_optTlY != nullptr) {
0797         m_optTlY->setValue(0);
0798     }
0799 
0800     if (m_optBrX != nullptr) {
0801         m_optBrX->setValue(max_x);
0802     }
0803     if (m_optBrY != nullptr) {
0804         m_optBrY->setValue(max_y);
0805     }
0806 
0807     // Avoid crash if max_y or max_x == 0
0808     if (max_x < 0.0001 || max_y < 0.0001) {
0809         qCWarning(KSANE_LOG) << "Risk for division by 0" << max_x << max_y;
0810         return;
0811     }
0812 
0813     // create a "scaled" image of the preview
0814     ratio = max_x / max_y;
0815     if (ratio < 1) {
0816         x = SCALED_PREVIEW_MAX_SIDE;
0817         y = (int)(SCALED_PREVIEW_MAX_SIDE / ratio);
0818     } else {
0819         y = SCALED_PREVIEW_MAX_SIDE;
0820         x = (int)(SCALED_PREVIEW_MAX_SIDE / ratio);
0821     }
0822 
0823     const qreal dpr = q->devicePixelRatioF();
0824     m_previewImg = QImage(QSize(x, y) * dpr, QImage::Format_RGB32);
0825     m_previewImg.setDevicePixelRatio(dpr);
0826     m_previewImg.fill(0xFFFFFFFF);
0827 
0828     // set the new image
0829     m_previewViewer->setQImage(&m_previewImg);
0830 
0831     // update the scan-area-options
0832     m_scanareaWidth->setRange(0.1, ratioToDispUnitX(1));
0833     m_scanareaWidth->setValue(ratioToDispUnitX(1));
0834 
0835     m_scanareaHeight->setRange(0.1, ratioToDispUnitY(1));
0836     m_scanareaHeight->setValue(ratioToDispUnitY(1));
0837 
0838     m_scanareaX->setRange(0.0, ratioToDispUnitX(1));
0839     m_scanareaY->setRange(0.0, ratioToDispUnitY(1));
0840 
0841     setPossibleScanSizes();
0842 }
0843 
0844 void KSaneWidgetPrivate::startPreviewScan()
0845 {
0846     if (m_scanOngoing) {
0847         return;
0848     }
0849     m_scanOngoing = true;
0850 
0851     int targetPreviewDPI;
0852     float max_x, max_y;
0853 
0854     // store the current settings of parameters to be changed
0855     if (m_optDepth != nullptr) {
0856         m_optDepth->storeCurrentData();
0857     }
0858     if (m_optRes != nullptr) {
0859         m_optRes->storeCurrentData();
0860     }
0861     if (m_optResX != nullptr) {
0862         m_optResX->storeCurrentData();
0863     }
0864     if (m_optResY != nullptr) {
0865         m_optResY->storeCurrentData();
0866     }
0867     if (m_optPreview != nullptr) {
0868         m_optPreview->storeCurrentData();
0869     }
0870 
0871     // check if we can modify the selection
0872     if ((m_optTlX != nullptr) && (m_optTlY != nullptr) &&
0873             (m_optBrX != nullptr) && (m_optBrY != nullptr)) {
0874         // get maximums
0875         max_x = m_optBrX->maximumValue().toFloat();
0876         max_y = m_optBrY->maximumValue().toFloat();
0877         // select the whole area
0878         m_optTlX->setValue(0);
0879         m_optTlY->setValue(0);
0880         m_optBrX->setValue(max_x);
0881         m_optBrY->setValue(max_y);
0882 
0883     } else {
0884         // no use to try auto selections if you can not use them
0885         m_autoSelect = false;
0886     }
0887 
0888     if (m_optRes != nullptr) {
0889         if (m_previewDPI < m_optRes->minimumValue().toFloat()) {
0890             targetPreviewDPI = qMax(m_optRes->minimumValue().toFloat(), 25.0f);
0891             if ((m_optBrX != nullptr) && (m_optBrY != nullptr)) {
0892                 if (m_optBrX->valueUnit() == KSaneCore::Option::UnitMilliMeter) {
0893                     targetPreviewDPI = 300 * 25.4 / (m_optBrX->value().toFloat());
0894                     // always round to a multiple of 25
0895                     int remainder = targetPreviewDPI % 25;
0896                     targetPreviewDPI = targetPreviewDPI + 25 - remainder;
0897                 }
0898             }
0899         } else {
0900             targetPreviewDPI = m_previewDPI;
0901         }
0902         if (m_optRes->type() == KSaneCore::Option::TypeValueList) {
0903             const auto &values = m_optRes->valueList();
0904             if (values.count() <= 0) {
0905                 qCWarning(KSANE_LOG) << "Resolution option is broken and has no entries";
0906                 return;
0907             }
0908             /* if there are discrete values, try to find the one which fits best. */
0909             int minIndex = 0;
0910             int minDistance = abs(values.at(0).toInt() - m_previewDPI);
0911             for (int i = 1; i < values.count(); ++i) {
0912                 int distance = abs(values.at(i).toInt() - m_previewDPI);
0913                 if (distance < minDistance) {
0914                     minIndex = i;
0915                     minDistance = distance;
0916                 }
0917 
0918             }
0919             targetPreviewDPI = values.at(minIndex).toInt();
0920 
0921         }
0922         m_optRes->setValue(targetPreviewDPI);
0923         if ((m_optResY != nullptr) && (m_optRes == m_optResX)) {
0924             m_optResY->setValue(targetPreviewDPI);
0925         }
0926     }
0927 
0928     // set preview option to true if possible
0929     if (m_optPreview != nullptr) {
0930         m_optPreview->setValue(true);
0931     }
0932 
0933     // clear the preview
0934     m_previewViewer->clearHighlight();
0935     m_previewViewer->clearSelections();
0936     m_previewImg.fill(0xFFFFFFFF);
0937     updatePreviewSize();
0938 
0939     setBusy(true);
0940 
0941     m_isPreview = true;
0942     m_cancelMultiScan = false;
0943     m_ksaneCoreInterface->startScan();
0944 }
0945 
0946 void KSaneWidgetPrivate::previewScanDone(KSaneCore::Interface::ScanStatus status, const QString &strStatus)
0947 {
0948     // restore the original settings of the changed parameters
0949     if (m_optDepth != nullptr) {
0950         m_optDepth->restoreSavedData();
0951     }
0952     if (m_optRes != nullptr) {
0953         m_optRes->restoreSavedData();
0954     }
0955     if (m_optResX != nullptr) {
0956         m_optResX->restoreSavedData();
0957     }
0958     if (m_optResY != nullptr) {
0959         m_optResY->restoreSavedData();
0960     }
0961     if (m_optPreview != nullptr) {
0962         m_optPreview->restoreSavedData();
0963     }
0964 
0965     m_previewImg = std::move(*m_ksaneCoreInterface->scanImage());
0966     m_previewViewer->setQImage(&m_previewImg);
0967     m_previewViewer->zoom2Fit();
0968 
0969     if (status != KSaneCore::Interface::ErrorGeneral && m_autoSelect) {
0970         m_previewViewer->findSelections();
0971     }
0972 
0973     setBusy(false);
0974     m_scanOngoing = false;
0975 
0976     Q_EMIT q->scanDone(KSaneWidget::NoError, QString());
0977 
0978     return;
0979 }
0980 
0981 void KSaneWidgetPrivate::startFinalScan()
0982 {
0983     if (m_scanOngoing) {
0984         return;
0985     }
0986     m_scanOngoing = true;
0987 
0988     m_isPreview = false;
0989 
0990     float x1 = 0, y1 = 0, x2 = 0, y2 = 0;
0991 
0992     m_selIndex = 0;
0993 
0994     if ((m_optTlX != nullptr) && (m_optTlY != nullptr) && (m_optBrX != nullptr) && (m_optBrY != nullptr)) {
0995         // read the selection from the viewer
0996         m_previewViewer->selectionAt(m_selIndex, x1, y1, x2, y2);
0997         m_previewViewer->setHighlightArea(x1, y1, x2, y2);
0998         m_selIndex++;
0999 
1000         // now set the selection
1001         m_optTlX->setValue(ratioToScanAreaX(x1));
1002         m_optTlY->setValue(ratioToScanAreaY(y1));
1003         m_optBrX->setValue(ratioToScanAreaX(x2));
1004         m_optBrY->setValue(ratioToScanAreaY(y2));
1005     }
1006 
1007     setBusy(true);
1008     m_cancelMultiScan = false;
1009     m_ksaneCoreInterface->startScan();
1010 }
1011 
1012 void KSaneWidgetPrivate::imageReady(const QImage &image)
1013 {
1014     if (m_isPreview) {
1015         return;
1016     }
1017     Q_EMIT q->scannedImageReady(image);
1018 }
1019 
1020 bool KSaneWidgetPrivate::scanSourceADF()
1021 {
1022     if (!m_optSource) {
1023         return false;
1024     }
1025 
1026     QString source = m_optSource->value().toString();
1027 
1028     return source.contains(QStringLiteral("Automatic Document Feeder")) ||
1029     source.contains(QStringLiteral("ADF")) ||
1030     source.contains(QStringLiteral("Duplex"));
1031 }
1032 
1033 void KSaneWidgetPrivate::scanDone(KSaneCore::Interface::ScanStatus status, const QString &strStatus)
1034 {
1035     if (m_isPreview) {
1036         previewScanDone(status, strStatus);
1037     } else {
1038         oneFinalScanDone(status, strStatus);
1039     }
1040 }
1041 
1042 void KSaneWidgetPrivate::oneFinalScanDone(KSaneCore::Interface::ScanStatus status, const QString &strStatus)
1043 {
1044     // check if we have multiple selections.
1045     if (m_previewViewer->selListSize() > m_selIndex) {
1046         if ((m_optTlX != nullptr) && (m_optTlY != nullptr) && (m_optBrX != nullptr) && (m_optBrY != nullptr)) {
1047             float x1 = 0;
1048             float y1 = 0;
1049             float x2 = 0;
1050             float y2 = 0;
1051 
1052             // read the selection from the viewer
1053             m_previewViewer->selectionAt(m_selIndex, x1, y1, x2, y2);
1054 
1055             // set the highlight
1056             m_previewViewer->setHighlightArea(x1, y1, x2, y2);
1057 
1058             // now set the selection
1059             m_optTlX->setValue(ratioToScanAreaX(x1));
1060             m_optTlY->setValue(ratioToScanAreaY(y1));
1061             m_optBrX->setValue(ratioToScanAreaX(x2));
1062             m_optBrY->setValue(ratioToScanAreaY(y2));
1063             m_selIndex++;
1064 
1065             if (!m_cancelMultiScan) {
1066                 m_ksaneCoreInterface->startScan();
1067                 return;
1068             }
1069         }
1070     } else {
1071         switch (status) {
1072             case KSaneCore::Interface::NoError:
1073                 Q_EMIT q->scanDone(KSaneWidget::NoError, QString());
1074                 break;
1075             case KSaneCore::Interface::Information:
1076                 Q_EMIT q->scanDone(KSaneWidget::Information, strStatus);
1077                 break;
1078             case KSaneCore::Interface::ErrorGeneral:
1079                 Q_EMIT q->scanDone(KSaneWidget::ErrorGeneral, strStatus);
1080                 break;
1081         }
1082     }
1083 
1084     // clear the highlight
1085     m_previewViewer->setHighlightArea(0, 0, 1, 1);
1086     setBusy(false);
1087     m_scanOngoing = false;
1088 }
1089 
1090 void KSaneWidgetPrivate::setBusy(bool busy)
1091 {
1092     if (busy) {
1093         m_btnFrame->hide();
1094         m_activityFrame->show();
1095     } else {
1096         m_btnFrame->show();
1097         m_activityFrame->hide();
1098     }
1099 
1100     m_optsTabWidget->setDisabled(busy);
1101     m_previewViewer->setDisabled(busy);
1102 
1103     m_scanBtn->setFocus(Qt::OtherFocusReason);
1104 }
1105 
1106 void KSaneWidgetPrivate::checkInvert()
1107 {
1108     if (!m_optSource) {
1109         return;
1110     }
1111     if (!m_optFilmType) {
1112         return;
1113     }
1114     if (m_scanOngoing) {
1115         return;
1116     }
1117 
1118     QString source = m_optSource->value().toString();
1119     QString filmtype = m_optFilmType->value().toString();
1120 
1121     if ((source.contains(i18nc("This is compared to the option string returned by sane",
1122                                "Transparency"), Qt::CaseInsensitive)) &&
1123             (filmtype.contains(i18nc("This is compared to the option string returned by sane",
1124                                      "Negative"), Qt::CaseInsensitive))) {
1125         m_optInvert->setValue(true);
1126     } else {
1127         m_optInvert->setValue(false);
1128     }
1129 }
1130 
1131 void KSaneWidgetPrivate::invertPreview()
1132 {
1133     m_previewViewer->updateImage();
1134 }
1135 
1136 void KSaneWidgetPrivate::updateProgress(int progress)
1137 {
1138     if (progress < 0 && !m_warmingUp->isVisible()) {
1139         m_warmingUp->show();
1140         m_progressBar->hide();
1141         m_countDown->hide();
1142     } else {
1143         m_warmingUp->hide();
1144         m_progressBar->show();
1145         m_countDown->hide();
1146     }
1147     if (m_isPreview) {
1148         // the image size might have changed
1149         if (m_ksaneCoreInterface->scanImage()->height() != m_previewViewer->currentImageHeight()
1150             || m_ksaneCoreInterface->scanImage()->width() != m_previewViewer->currentImageWidth() ) {
1151 
1152             m_ksaneCoreInterface->lockScanImage();
1153             m_previewViewer->setQImage(m_ksaneCoreInterface->scanImage());
1154             m_previewViewer->zoom2Fit();
1155             m_ksaneCoreInterface->unlockScanImage();
1156         } else {
1157             m_ksaneCoreInterface->lockScanImage();
1158             m_previewViewer->updateImage();
1159             m_ksaneCoreInterface->unlockScanImage();
1160         }
1161     } else {
1162         m_previewViewer->setHighlightShown(progress);
1163     }
1164 
1165     m_progressBar->setValue(progress);
1166     Q_EMIT q->scanProgress(progress);
1167 }
1168 
1169 void KSaneWidgetPrivate::updateCountDown(int remainingSeconds)
1170 {
1171     m_countDown->setText(i18n("Next scan starts in %1 s.", remainingSeconds));
1172     if (remainingSeconds > 0 && !m_countDown->isVisible()) {
1173         m_countDown->show();
1174         m_warmingUp->hide();
1175         m_progressBar->hide();
1176     }
1177 }
1178 
1179 void KSaneWidgetPrivate::alertUser(KSaneCore::Interface::ScanStatus status, const QString &strStatus)
1180 {
1181     if (!q->isSignalConnected(QMetaMethod::fromSignal(&KSaneWidget::userMessage))) {
1182         switch (status) {
1183         case KSaneCore::Interface::ErrorGeneral:
1184             QMessageBox::critical(nullptr, i18nc("@title:window", "General Error"), strStatus);
1185             break;
1186         default:
1187             QMessageBox::information(nullptr, i18nc("@title:window", "Information"), strStatus);
1188             break;
1189         }
1190     } else {
1191         switch (status) {
1192         case KSaneCore::Interface::NoError:
1193             Q_EMIT q->userMessage(KSaneWidget::NoError, QString());
1194             break;
1195         case KSaneCore::Interface::Information:
1196             Q_EMIT q->userMessage(KSaneWidget::Information, strStatus);
1197             break;
1198         case KSaneCore::Interface::ErrorGeneral:
1199             Q_EMIT q->userMessage(KSaneWidget::ErrorGeneral, strStatus);
1200             break;
1201         }
1202     }
1203 }
1204 
1205 void KSaneWidgetPrivate::updateScanSelection()
1206 {
1207     QVariant maxX;
1208     if (m_optBrX) {
1209         maxX = m_optBrX->maximumValue();
1210     }
1211 
1212     QVariant maxY;
1213     if (m_optBrY) {
1214         maxY = m_optBrY->maximumValue();
1215     }
1216 
1217     float x1 = m_scanareaX->value();
1218     float y1 = m_scanareaY->value();
1219     float w = m_scanareaWidth->value();
1220     float h = m_scanareaHeight->value();
1221 
1222     float x1Max = maxX.toFloat() - w;
1223     m_scanareaX->setRange(0.0, x1Max);
1224     if (x1 > x1Max) {
1225         m_scanareaX->setValue(x1Max);
1226     }
1227 
1228     float y1Max = maxY.toFloat() - h;
1229     m_scanareaY->setRange(0.0, y1Max);
1230     if (y1 > y1Max) {
1231         m_scanareaY->setValue(y1Max);
1232     }
1233 
1234     float wR = dispUnitToRatioX(w);
1235     float hR = dispUnitToRatioY(h);
1236 
1237     float x1R = dispUnitToRatioX(m_scanareaX->value());
1238     float y1R = dispUnitToRatioY(m_scanareaY->value());
1239 
1240     m_previewViewer->setSelection(x1R, y1R, x1R+wR, y1R+hR);
1241 
1242     // Update the page size combo, but not while updating or
1243     // if we already have custom page size.
1244     if (m_settingPageSize ||
1245         m_scanareaPapersize->currentIndex() == m_scanareaPapersize->count() - 1) {
1246         return;
1247     }
1248     QSizeF size = m_scanareaPapersize->currentData().toSizeF();
1249     float pageWidth = mmToDispUnit(size.width());
1250     float pageHeight = mmToDispUnit(size.height());
1251     if (qAbs(pageWidth - w) > (w * 0.001) ||
1252         qAbs(pageHeight - h) > (h * 0.001))
1253     {
1254         // The difference is bigger than 1% -> we have a custom size
1255         m_scanareaPapersize->blockSignals(true);
1256         m_scanareaPapersize->setCurrentIndex(0); // Custom is always first
1257         m_scanareaPapersize->blockSignals(false);
1258     }
1259 }
1260 
1261 void KSaneWidgetPrivate::setPossibleScanSizes()
1262 {
1263     m_scanareaPapersize->clear();
1264     float widthInDispUnit = ratioToDispUnitX(1);
1265     float heightInDispUnit = ratioToDispUnitY(1);
1266 
1267     // Add the custom size first
1268     QSizeF customSize(widthInDispUnit, heightInDispUnit);
1269     m_scanareaPapersize->addItem(i18n("Custom"), customSize);
1270 
1271     // Add portrait page sizes
1272     for (int sizeCode: qAsConst(m_sizeCodes)) {
1273         QSizeF size = QPageSize::size((QPageSize::PageSizeId)sizeCode, QPageSize::Millimeter);
1274         if (mmToDispUnit(size.width() - PageSizeWiggleRoom) > widthInDispUnit) {
1275             continue;
1276         }
1277         if (mmToDispUnit(size.height() - PageSizeWiggleRoom) > heightInDispUnit) {
1278             continue;
1279         }
1280         m_scanareaPapersize->addItem(QPageSize::name((QPageSize::PageSizeId)sizeCode), size);
1281     }
1282 
1283     // Add landscape page sizes
1284     for (int sizeCode: qAsConst(m_sizeCodes)) {
1285         QSizeF size = QPageSize::size((QPageSize::PageSizeId)sizeCode, QPageSize::Millimeter);
1286         size.transpose();
1287         if (mmToDispUnit(size.width() - PageSizeWiggleRoom) > widthInDispUnit) {
1288             continue;
1289         }
1290         if (mmToDispUnit(size.height() - PageSizeWiggleRoom) > heightInDispUnit) {
1291             continue;
1292         }
1293         QString name = QPageSize::name((QPageSize::PageSizeId)sizeCode) +
1294         i18nc("Page size landscape", " Landscape");
1295         m_scanareaPapersize->addItem(name , size);
1296     }
1297 
1298     // Set custom as current
1299     m_scanareaPapersize->blockSignals(true);
1300     m_scanareaPapersize->setCurrentIndex(0);
1301     m_scanareaPapersize->blockSignals(false);
1302 }
1303 
1304 void KSaneWidgetPrivate::setPageSize(int)
1305 {
1306     QSizeF size = m_scanareaPapersize->currentData().toSizeF();
1307     float pageWidth = mmToDispUnit(size.width());
1308     float pageHeight = mmToDispUnit(size.height());
1309 
1310     m_settingPageSize = true;
1311     m_scanareaX->setValue(0);
1312     m_scanareaY->setValue(0);
1313     m_scanareaWidth->setValue(pageWidth);
1314     m_scanareaHeight->setValue(pageHeight);
1315     m_settingPageSize = false;
1316 }
1317 
1318 
1319 }  // NameSpace KSaneIface
1320 
1321 #include "moc_ksanewidget_p.cpp"