diff --git a/src/ksanewidget.cpp b/src/ksanewidget.cpp index 21114c3..0bfdd6d 100644 --- a/src/ksanewidget.cpp +++ b/src/ksanewidget.cpp @@ -1,921 +1,949 @@ /* ============================================================ * * This file is part of the KDE project * * Date : 2009-01-24 * Description : Sane interface for KDE * * Copyright (C) 2007-2010 by Kare Sars * Copyright (C) 2009 by Matthias Nagl * Copyright (C) 2009 by Grzegorz Kurtyka * Copyright (C) 2007-2008 by Gilles Caulier * Copyright (C) 2014 by Gregor Mitsch: port to KDE5 frameworks * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program. If not, see . * * ============================================================ */ #include "ksanewidget.h" #include "ksanewidget_p.h" #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_KF5WALLET #include #endif #include "ksaneoption.h" #include "ksaneoptbutton.h" #include "ksaneoptcheckbox.h" #include "ksaneoptcombo.h" #include "ksaneoptentry.h" #include "ksaneoptfslider.h" #include "ksaneoptgamma.h" #include "ksaneoptslider.h" #include "ksanedevicedialog.h" #include "labeledgamma.h" namespace KSaneIface { static int s_objectCount = 0; static QMutex s_objectMutex; static const QString InvetColorsOption = QStringLiteral("KSane::InvertColors"); KSaneWidget::KSaneWidget(QWidget *parent) : QWidget(parent), d(new KSaneWidgetPrivate(this)) { SANE_Int version; SANE_Status status; s_objectMutex.lock(); s_objectCount++; if (s_objectCount == 1) { // only call sane init for the first instance status = sane_init(&version, &KSaneAuth::authorization); if (status != SANE_STATUS_GOOD) { qDebug() << "libksane: sane_init() failed(" << sane_strstatus(status) << ")"; } else { //qDebug() << "Sane Version = " // << SANE_VERSION_MAJOR(version) << "." // << SANE_VERSION_MINORparent(version) << "." // << SANE_VERSION_BUILD(version); } } s_objectMutex.unlock(); // read the device list to get a list of vendor and model info d->m_findDevThread->start(); d->m_readValsTmr.setSingleShot(true); connect(&d->m_readValsTmr, SIGNAL(timeout()), d, SLOT(valReload())); d->m_updProgressTmr.setSingleShot(false); d->m_updProgressTmr.setInterval(300); connect(&d->m_updProgressTmr, SIGNAL(timeout()), d, SLOT(updateProgress())); // Create the static UI // create the preview d->m_previewViewer = new KSaneViewer(&(d->m_previewImg), this); connect(d->m_previewViewer, SIGNAL(newSelection(float,float,float,float)), d, SLOT(handleSelection(float,float,float,float))); d->m_warmingUp = new QLabel; d->m_warmingUp->setText(i18n("Waiting for the scan to start.")); d->m_warmingUp->setAlignment(Qt::AlignCenter); d->m_warmingUp->setAutoFillBackground(true); d->m_warmingUp->setBackgroundRole(QPalette::Highlight); //d->m_warmingUp->setForegroundRole(QPalette::HighlightedText); d->m_warmingUp->hide(); d->m_progressBar = new QProgressBar; d->m_progressBar->setMaximum(100); d->m_cancelBtn = new QPushButton; d->m_cancelBtn->setIcon(QIcon::fromTheme(QStringLiteral("process-stop"))); d->m_cancelBtn->setToolTip(i18n("Cancel current scan operation")); connect(d->m_cancelBtn, SIGNAL(clicked()), this, SLOT(scanCancel())); d->m_activityFrame = new QWidget; QHBoxLayout *progress_lay = new QHBoxLayout(d->m_activityFrame); progress_lay->setContentsMargins(0, 0, 0, 0); progress_lay->addWidget(d->m_progressBar, 100); progress_lay->addWidget(d->m_cancelBtn, 0); d->m_activityFrame->hide(); d->m_zInBtn = new QToolButton(this); d->m_zInBtn->setAutoRaise(true); d->m_zInBtn->setIcon(QIcon::fromTheme(QStringLiteral("zoom-in"))); d->m_zInBtn->setToolTip(i18n("Zoom In")); connect(d->m_zInBtn, SIGNAL(clicked()), d->m_previewViewer, SLOT(zoomIn())); d->m_zOutBtn = new QToolButton(this); d->m_zOutBtn->setAutoRaise(true); d->m_zOutBtn->setIcon(QIcon::fromTheme(QStringLiteral("zoom-out"))); d->m_zOutBtn->setToolTip(i18n("Zoom Out")); connect(d->m_zOutBtn, SIGNAL(clicked()), d->m_previewViewer, SLOT(zoomOut())); d->m_zSelBtn = new QToolButton(this); d->m_zSelBtn->setAutoRaise(true); d->m_zSelBtn->setIcon(QIcon::fromTheme(QStringLiteral("zoom-fit-best"))); d->m_zSelBtn->setToolTip(i18n("Zoom to Selection")); connect(d->m_zSelBtn, SIGNAL(clicked()), d->m_previewViewer, SLOT(zoomSel())); d->m_zFitBtn = new QToolButton(this); d->m_zFitBtn->setAutoRaise(true); d->m_zFitBtn->setIcon(QIcon::fromTheme(QStringLiteral("document-preview"))); d->m_zFitBtn->setToolTip(i18n("Zoom to Fit")); connect(d->m_zFitBtn, SIGNAL(clicked()), d->m_previewViewer, SLOT(zoom2Fit())); d->m_clearSelBtn = new QToolButton(this); d->m_clearSelBtn->setAutoRaise(true); d->m_clearSelBtn->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear"))); d->m_clearSelBtn->setToolTip(i18n("Clear Selections")); connect(d->m_clearSelBtn, SIGNAL(clicked()), d->m_previewViewer, SLOT(clearSelections())); d->m_prevBtn = new QPushButton(this); d->m_prevBtn->setIcon(QIcon::fromTheme(QStringLiteral("document-import"))); d->m_prevBtn->setToolTip(i18n("Scan Preview Image")); d->m_prevBtn->setText(i18nc("Preview button text", "Preview")); connect(d->m_prevBtn, SIGNAL(clicked()), d, SLOT(startPreviewScan())); d->m_scanBtn = new QPushButton(this); d->m_scanBtn->setIcon(QIcon::fromTheme(QStringLiteral("document-save"))); d->m_scanBtn->setToolTip(i18n("Scan Final Image")); d->m_scanBtn->setText(i18nc("Final scan button text", "Scan")); d->m_scanBtn->setFocus(Qt::OtherFocusReason); connect(d->m_scanBtn, SIGNAL(clicked()), d, SLOT(startFinalScan())); d->m_btnFrame = new QWidget; QHBoxLayout *btn_lay = new QHBoxLayout(d->m_btnFrame); btn_lay->setContentsMargins(0, 0, 0, 0); btn_lay->addWidget(d->m_zInBtn); btn_lay->addWidget(d->m_zOutBtn); btn_lay->addWidget(d->m_zSelBtn); btn_lay->addWidget(d->m_zFitBtn); btn_lay->addWidget(d->m_clearSelBtn); btn_lay->addStretch(100); btn_lay->addWidget(d->m_prevBtn); btn_lay->addWidget(d->m_scanBtn); // calculate the height of the waiting/scanning/buttons frames to avoid jumpiness. int minHeight = d->m_btnFrame->sizeHint().height(); if (d->m_activityFrame->sizeHint().height() > minHeight) { minHeight = d->m_activityFrame->sizeHint().height(); } if (d->m_warmingUp->sizeHint().height() > minHeight) { minHeight = d->m_warmingUp->sizeHint().height(); } d->m_btnFrame->setMinimumHeight(minHeight); d->m_activityFrame->setMinimumHeight(minHeight); d->m_warmingUp->setMinimumHeight(minHeight); d->m_previewFrame = new QWidget; QVBoxLayout *preview_layout = new QVBoxLayout(d->m_previewFrame); preview_layout->setContentsMargins(0, 0, 0, 0); preview_layout->addWidget(d->m_previewViewer, 100); preview_layout->addWidget(d->m_warmingUp, 0); preview_layout->addWidget(d->m_activityFrame, 0); preview_layout->addWidget(d->m_btnFrame, 0); // Create Options Widget d->m_optsTabWidget = new QTabWidget(); // Add the basic options tab d->m_basicScrollA = new QScrollArea(); d->m_basicScrollA->setWidgetResizable(true); d->m_basicScrollA->setFrameShape(QFrame::NoFrame); d->m_optsTabWidget->addTab(d->m_basicScrollA, i18n("Basic Options")); // Add the other options tab d->m_otherScrollA = new QScrollArea; d->m_otherScrollA->setWidgetResizable(true); d->m_otherScrollA->setFrameShape(QFrame::NoFrame); d->m_optsTabWidget->addTab(d->m_otherScrollA, i18n("Scanner Specific Options")); d->m_splitter = new QSplitter(this); d->m_splitter->addWidget(d->m_optsTabWidget); d->m_splitter->setStretchFactor(0, 0); d->m_splitter->addWidget(d->m_previewFrame); d->m_splitter->setStretchFactor(1, 100); d->m_optionsCollapser = new SplitterCollapser(d->m_splitter, d->m_optsTabWidget); QHBoxLayout *base_layout = new QHBoxLayout(this); base_layout->addWidget(d->m_splitter); base_layout->setContentsMargins(0, 0, 0, 0); // disable the interface in case no device is opened. d->m_optsTabWidget->setDisabled(true); d->m_previewViewer->setDisabled(true); d->m_btnFrame->setDisabled(true); } KSaneWidget::~KSaneWidget() { while (!closeDevice()) { usleep(1000); } // wait for any thread to exit s_objectMutex.lock(); s_objectCount--; if (s_objectCount <= 0) { // only delete the find-devices and authorization singletons and call sane_exit // if this is the last instance delete d->m_findDevThread; delete d->m_auth; sane_exit(); } s_objectMutex.unlock(); delete d; } QString KSaneWidget::vendor() const { d->m_findDevThread->wait(); d->devListUpdated(); // this is just a wrapped if (m_vendor.isEmpty()) statement if the vendor is known // devListUpdated here is to ensure that we do not come in between finished and the devListUpdated slot return d->m_vendor; } QString KSaneWidget::make() const { return vendor(); } QString KSaneWidget::model() const { d->m_findDevThread->wait(); d->devListUpdated(); // this is just a wrapped if (m_vendor.isEmpty()) statement if the vendor is known // devListUpdated here is to ensure that we do not come in between finished and the devListUpdated slot return d->m_model; } QString KSaneWidget::selectDevice(QWidget *parent) { QString selected_name; QPointer sel = new KSaneDeviceDialog(parent); // set default scanner - perhaps application using libksane should remember that // 2014-01-21: gm: +1 // sel.setDefault(prev_backend); if (sel->exec() == QDialog::Accepted) { selected_name = sel->getSelectedName(); } delete sel; return selected_name; } void KSaneWidget::initGetDeviceList() const { // update the device list if needed to get the vendor and model info if (d->m_findDevThread->devicesList().size() == 0) { //qDebug() << "initGetDeviceList() starting thread..."; d->m_findDevThread->start(); } else { //qDebug() << "initGetDeviceList() have existing data..."; d->signalDevListUpdate(); } } bool KSaneWidget::openDevice(const QString &deviceName) { int i = 0; const SANE_Option_Descriptor *optDesc; SANE_Status status; SANE_Word numSaneOptions; SANE_Int res; KPasswordDialog *dlg; #ifdef HAVE_KF5WALLET KWallet::Wallet *saneWallet; #endif QString myFolderName = QStringLiteral("ksane"); QMap wallet_entry; if (d->m_saneHandle != nullptr) { // this KSaneWidget already has an open device return false; } // don't bother trying to open if the device string is empty if (deviceName.isEmpty()) { return false; } // save the device name d->m_devName = deviceName; // Try to open the device status = sane_open(deviceName.toLatin1().constData(), &d->m_saneHandle); bool password_dialog_ok = true; // prepare wallet for authentication and create password dialog if (status == SANE_STATUS_ACCESS_DENIED) { #ifdef HAVE_KF5WALLET saneWallet = KWallet::Wallet::openWallet(KWallet::Wallet::LocalWallet(), winId()); if (saneWallet) { dlg = new KPasswordDialog(this, KPasswordDialog::ShowUsernameLine | KPasswordDialog::ShowKeepPassword); if (!saneWallet->hasFolder(myFolderName)) { saneWallet->createFolder(myFolderName); } saneWallet->setFolder(myFolderName); saneWallet->readMap(deviceName, wallet_entry); if (!wallet_entry.empty() || true) { dlg->setUsername(wallet_entry[QStringLiteral("username")]); dlg->setPassword(wallet_entry[QStringLiteral("password")]); dlg->setKeepPassword(true); } } else #endif { dlg = new KPasswordDialog(this, KPasswordDialog::ShowUsernameLine); } dlg->setPrompt(i18n("Authentication required for resource: %1", deviceName)); } // sane_open failed due to insufficient authorization // retry opening device with user provided data assisted with kwallet records while (status == SANE_STATUS_ACCESS_DENIED) { password_dialog_ok = dlg->exec(); if (!password_dialog_ok) { delete dlg; d->m_devName.clear(); return false; //the user canceled } // add/update the device user-name and password for authentication d->m_auth->setDeviceAuth(d->m_devName, dlg->username(), dlg->password()); status = sane_open(deviceName.toLatin1().constData(), &d->m_saneHandle); #ifdef HAVE_KF5WALLET // store password in wallet on successful authentication if (dlg->keepPassword() && status != SANE_STATUS_ACCESS_DENIED) { QMap entry; entry[QStringLiteral("username")] = dlg->username(); entry[QStringLiteral("password")] = dlg->password(); if (saneWallet) { saneWallet->writeMap(deviceName, entry); } } #endif } if (status != SANE_STATUS_GOOD) { qDebug() << "sane_open(\"" << deviceName << "\", &handle) failed! status = " << sane_strstatus(status); d->m_auth->clearDeviceAuth(d->m_devName); d->m_devName.clear(); return false; } // update the device list if needed to get the vendor and model info if (d->m_findDevThread->devicesList().size() == 0) { d->m_findDevThread->start(); } else { // use the "old" existing list d->devListUpdated(); // if m_vendor is not updated it means that the list needs to be updated. if (d->m_vendor.isEmpty()) { d->m_findDevThread->start(); } } // Read the options (start with option 0 the number of parameters) optDesc = sane_get_option_descriptor(d->m_saneHandle, 0); if (optDesc == nullptr) { d->m_auth->clearDeviceAuth(d->m_devName); d->m_devName.clear(); return false; } QVarLengthArray data(optDesc->size); status = sane_control_option(d->m_saneHandle, 0, SANE_ACTION_GET_VALUE, data.data(), &res); if (status != SANE_STATUS_GOOD) { d->m_auth->clearDeviceAuth(d->m_devName); d->m_devName.clear(); return false; } numSaneOptions = *reinterpret_cast(data.data()); // read the rest of the options for (i = 1; i < numSaneOptions; ++i) { switch (KSaneOption::optionType(sane_get_option_descriptor(d->m_saneHandle, i))) { case KSaneOption::TYPE_DETECT_FAIL: d->m_optList.append(new KSaneOption(d->m_saneHandle, i)); break; case KSaneOption::TYPE_CHECKBOX: d->m_optList.append(new KSaneOptCheckBox(d->m_saneHandle, i)); break; case KSaneOption::TYPE_SLIDER: d->m_optList.append(new KSaneOptSlider(d->m_saneHandle, i)); break; case KSaneOption::TYPE_F_SLIDER: d->m_optList.append(new KSaneOptFSlider(d->m_saneHandle, i)); break; case KSaneOption::TYPE_COMBO: d->m_optList.append(new KSaneOptCombo(d->m_saneHandle, i)); break; case KSaneOption::TYPE_ENTRY: d->m_optList.append(new KSaneOptEntry(d->m_saneHandle, i)); break; case KSaneOption::TYPE_GAMMA: d->m_optList.append(new KSaneOptGamma(d->m_saneHandle, i)); break; case KSaneOption::TYPE_BUTTON: d->m_optList.append(new KSaneOptButton(d->m_saneHandle, i)); break; } } // do the connections of the option parameters - for (i = 1; i < d->m_optList.size(); ++i) { + for (i = 0; i < d->m_optList.size(); ++i) { //qDebug() << d->m_optList.at(i)->name(); connect(d->m_optList.at(i), SIGNAL(optsNeedReload()), d, SLOT(optReload())); connect(d->m_optList.at(i), SIGNAL(valsNeedReload()), d, SLOT(scheduleValReload())); if (d->m_optList.at(i)->needsPolling()) { //qDebug() << d->m_optList.at(i)->name() << " needs polling"; d->m_pollList.append(d->m_optList.at(i)); KSaneOptCheckBox *buttonOption = qobject_cast(d->m_optList.at(i)); if (buttonOption) { connect(buttonOption, SIGNAL(buttonPressed(QString,QString,bool)), this, SIGNAL(buttonPressed(QString,QString,bool))); } } } // start polling the poll options if (d->m_pollList.size() > 0) { d->m_optionPollTmr.start(); } // Create the preview thread d->m_previewThread = new KSanePreviewThread(d->m_saneHandle, &d->m_previewImg); connect(d->m_previewThread, SIGNAL(finished()), d, SLOT(previewScanDone())); // Create the read thread d->m_scanThread = new KSaneScanThread(d->m_saneHandle, &d->m_scanData); connect(d->m_scanThread, SIGNAL(finished()), d, SLOT(oneFinalScanDone())); // Create the options interface d->createOptInterface(); // try to set KSaneWidget default values d->setDefaultValues(); // Enable the interface d->m_optsTabWidget->setDisabled(false); d->m_previewViewer->setDisabled(false); d->m_btnFrame->setDisabled(false); // estimate the preview size and create an empty image // this is done so that you can select scan area without // having to scan a preview. d->updatePreviewSize(); QTimer::singleShot(1000, d->m_previewViewer, SLOT(zoom2Fit())); return true; } bool KSaneWidget::closeDevice() { if (!d->m_saneHandle) { return true; } if (d->m_scanThread->isRunning()) { d->m_scanThread->cancelScan(); d->m_closeDevicePending = true; return false; } if (d->m_previewThread->isRunning()) { d->m_previewThread->cancelScan(); d->m_closeDevicePending = true; return false; } d->m_auth->clearDeviceAuth(d->m_devName); // else sane_close(d->m_saneHandle); d->m_saneHandle = nullptr; d->clearDeviceOptions(); // disable the interface until a new device is opened. d->m_optsTabWidget->setDisabled(true); d->m_previewViewer->setDisabled(true); d->m_btnFrame->setDisabled(true); return true; } #define inc_pixel(x,y,ppl) { x++; if (x>=ppl) { y++; x=0;} } QImage KSaneWidget::toQImageSilent(const QByteArray &data, int width, int height, int bytes_per_line, ImageFormat format) { return KSaneWidget::toQImageSilent(data, width, height, bytes_per_line, currentDPI(), format); } QImage KSaneWidget::toQImageSilent(const QByteArray &data, int width, int height, int bytes_per_line, int dpi, ImageFormat format) { QImage img; int j = 0; QVector table; QRgb *imgLine; switch (format) { case FormatBlackWhite: img = QImage((uchar *)data.data(), width, height, bytes_per_line, QImage::Format_Mono); // The color table must be set table.append(0xFFFFFFFF); table.append(0xFF000000); img.setColorTable(table); break; case FormatGrayScale8: { img = QImage(width, height, QImage::Format_RGB32); int dI = 0; for (int i = 0; (i < img.height() && dI < data.size()); i++) { imgLine = reinterpret_cast(img.scanLine(i)); for (j = 0; (j < img.width() && dI < data.size()); j++) { imgLine[j] = qRgb(data[dI], data[dI], data[dI]); dI++; } } break; } case FormatGrayScale16: { img = QImage(width, height, QImage::Format_RGB32); int dI = 1; for (int i = 0; (i < img.height() && dI < data.size()); i++) { imgLine = reinterpret_cast(img.scanLine(i)); for (j = 0; (j < img.width() && dI < data.size()); j++) { imgLine[j] = qRgb(data[dI], data[dI], data[dI]); dI += 2; } } break; } case FormatRGB_8_C: { img = QImage(width, height, QImage::Format_RGB32); int dI = 0; for (int i = 0; (i < img.height() && dI < data.size()); i++) { imgLine = reinterpret_cast(img.scanLine(i)); for (j = 0; (j < img.width() && dI < data.size()); j++) { imgLine[j] = qRgb(data[dI], data[dI + 1], data[dI + 2]); dI += 3; } } break; } case FormatRGB_16_C: { img = QImage(width, height, QImage::Format_RGB32); int dI = 1; for (int i = 0; (i < img.height() && dI < data.size()); i++) { imgLine = reinterpret_cast(img.scanLine(i)); for (j = 0; (j < img.width() && dI < data.size()); j++) { imgLine[j] = qRgb(data[dI], data[dI + 2], data[dI + 4]); dI += 6; } } break; } case FormatNone: default: qDebug() << "Unsupported conversion"; break; } float dpm = dpi * (1000.0 / 25.4); img.setDotsPerMeterX(dpm); img.setDotsPerMeterY(dpm); return img; } QImage KSaneWidget::toQImage(const QByteArray &data, int width, int height, int bytes_per_line, ImageFormat format) { if ((format == FormatRGB_16_C) || (format == FormatGrayScale16)) { d->alertUser(KSaneWidget::ErrorGeneral, i18n("The image data contained 16 bits per color, " "but the color depth has been truncated to 8 bits per color.")); } return toQImageSilent(data, width, height, bytes_per_line, format); } void KSaneWidget::scanFinal() { if (d->m_btnFrame->isEnabled()) { d->startFinalScan(); } else { // if the button frame is disabled, there is no open device to scan from emit scanDone(KSaneWidget::ErrorGeneral, QStringLiteral("")); } } void KSaneWidget::startPreviewScan() { if (d->m_btnFrame->isEnabled()) { d->startPreviewScan(); } else { // if the button frame is disabled, there is no open device to scan from emit scanDone(KSaneWidget::ErrorGeneral, QStringLiteral("")); } } void KSaneWidget::scanCancel() { if (d->m_scanThread->isRunning()) { d->m_scanThread->cancelScan(); } if (d->m_previewThread->isRunning()) { d->m_previewThread->cancelScan(); } } void KSaneWidget::setPreviewResolution(float dpi) { d->m_previewDPI = dpi; } void KSaneWidget::getOptVals(QMap &opts) { KSaneOption *option; opts.clear(); QString tmp; - for (int i = 1; i < d->m_optList.size(); i++) { + for (int i = 0; i < d->m_optList.size(); i++) { option = d->m_optList.at(i); if (option->getValue(tmp)) { opts[option->name()] = tmp; } } // Special handling for non sane option opts[InvetColorsOption] = d->m_invertColors->isChecked() ? QStringLiteral("true") : QStringLiteral("false"); } bool KSaneWidget::getOptVal(const QString &optname, QString &value) { KSaneOption *option; if ((option = d->getOption(optname)) != nullptr) { return option->getValue(value); } // Special handling for non sane option if (optname == InvetColorsOption) { value = d->m_invertColors->isChecked() ? QStringLiteral("true") : QStringLiteral("false"); return true; } return false; } int KSaneWidget::setOptVals(const QMap &opts) { if (d->m_scanThread->isRunning() || d->m_previewThread->isRunning()) { return -1; } + QMap optionMapCopy = opts; + QString tmp; int i; int ret = 0; + const QString sourceOptionSaneName = QStringLiteral(SANE_NAME_SCAN_SOURCE); + const QString modeOptionSaneName = QStringLiteral(SANE_NAME_SCAN_MODE); + + KSaneOption *opt; + + // Priorize source option + if (optionMapCopy.contains(sourceOptionSaneName)) { + if ((opt = d->getOption(sourceOptionSaneName)) != nullptr) { + if (opt->setValue(optionMapCopy[sourceOptionSaneName])) { + ret++; + } + } + optionMapCopy.remove(sourceOptionSaneName); + } + + // Priorize mode option + if (optionMapCopy.contains(modeOptionSaneName)) { + if ((opt = d->getOption(modeOptionSaneName)) != nullptr) { + if (opt->setValue(optionMapCopy[modeOptionSaneName])) { + ret++; + } + } + optionMapCopy.remove(modeOptionSaneName); + } + + // Update remaining options for (i = 0; i < d->m_optList.size(); i++) { - if (opts.contains(d->m_optList.at(i)->name())) { - tmp = opts[d->m_optList.at(i)->name()]; - if (d->m_optList.at(i)->setValue(tmp) == false) { + if (optionMapCopy.contains(d->m_optList.at(i)->name())) { + tmp = optionMapCopy[d->m_optList.at(i)->name()]; + if (d->m_optList.at(i)->setValue(tmp)) { ret++; } } } if ((d->m_splitGamChB) && (d->m_optGamR) && (d->m_optGamG) && (d->m_optGamB)) { // check if the current gamma values are identical. if they are identical, // uncheck the "Separate color intensity tables" checkbox QString redGamma; QString greenGamma; QString blueGamma; d->m_optGamR->getValue(redGamma); d->m_optGamG->getValue(greenGamma); d->m_optGamB->getValue(blueGamma); if ((redGamma == greenGamma) && (greenGamma == blueGamma)) { d->m_splitGamChB->setChecked(false); // set the values to the common gamma widget d->m_commonGamma->setValues(redGamma); } else { d->m_splitGamChB->setChecked(true); } } // special handling for non-sane option - if (opts.contains(InvetColorsOption)) { - tmp = opts[InvetColorsOption]; + if (optionMapCopy.contains(InvetColorsOption)) { + tmp = optionMapCopy[InvetColorsOption]; if ((tmp.compare(QStringLiteral("true"), Qt::CaseInsensitive) == 0) || (tmp.compare(QStringLiteral("1")) == 0)) { d->m_invertColors->setChecked(true); } else { d->m_invertColors->setChecked(false); } } return ret; } bool KSaneWidget::setOptVal(const QString &option, const QString &value) { if (d->m_scanThread->isRunning() || d->m_previewThread->isRunning()) { return false; } KSaneOption *opt; if ((opt = d->getOption(option)) != nullptr) { if (opt->setValue(value)) { if ((d->m_splitGamChB) && (d->m_optGamR) && (d->m_optGamG) && (d->m_optGamB) && ((opt == d->m_optGamR) || (opt == d->m_optGamG) || (opt == d->m_optGamB))) { // check if the current gamma values are identical. if they are identical, // uncheck the "Separate color intensity tables" checkbox QString redGamma; QString greenGamma; QString blueGamma; d->m_optGamR->getValue(redGamma); d->m_optGamG->getValue(greenGamma); d->m_optGamB->getValue(blueGamma); if ((redGamma == greenGamma) && (greenGamma == blueGamma)) { d->m_splitGamChB->setChecked(false); // set the values to the common gamma widget d->m_commonGamma->setValues(redGamma); } else { d->m_splitGamChB->setChecked(true); } } return true; } } // special handling for non-sane option if (option == InvetColorsOption) { if ((value.compare(QStringLiteral("true"), Qt::CaseInsensitive) == 0) || (value.compare(QStringLiteral("1")) == 0)) { d->m_invertColors->setChecked(true); } else { d->m_invertColors->setChecked(false); } return true; } return false; } void KSaneWidget::setScanButtonText(const QString &scanLabel) { if (d->m_scanBtn == nullptr) { qCritical() << "setScanButtonText was called before KSaneWidget was initialized"; return; } d->m_scanBtn->setText(scanLabel); } void KSaneWidget::setPreviewButtonText(const QString &previewLabel) { if (d->m_scanBtn == nullptr) { qCritical() << "setPreviewButtonText was called before KSaneWidget was initialized"; return; } d->m_prevBtn->setText(previewLabel); } void KSaneWidget::enableAutoSelect(bool enable) { d->m_autoSelect = enable; } float KSaneWidget::currentDPI() { if (d->m_optRes) { float value; if (d->m_optRes->getValue(value)) { return value; } } return 0.0; } float KSaneWidget::scanAreaWidth() { float result = 0.0; if (d->m_optBrX) { if (d->m_optBrX->getUnit() == SANE_UNIT_PIXEL) { d->m_optBrX->getMaxValue(result); result = result / currentDPI() / 25.4; } else if (d->m_optBrX->getUnit() == SANE_UNIT_MM) { d->m_optBrX->getMaxValue(result); } } return result; } float KSaneWidget::scanAreaHeight() { float result = 0.0; if (d->m_optBrY) { if (d->m_optBrY->getUnit() == SANE_UNIT_PIXEL) { d->m_optBrY->getMaxValue(result); result = result / currentDPI() / 25.4; } else if (d->m_optBrY->getUnit() == SANE_UNIT_MM) { d->m_optBrY->getMaxValue(result); } } return result; } void KSaneWidget::setSelection(QPointF topLeft, QPointF bottomRight) { if (!d->m_optBrX || !d->m_optBrY || !d->m_optTlX || !d->m_optTlY) { return; } float xmax, ymax; d->m_optBrX->getMaxValue(xmax); d->m_optBrY->getMaxValue(ymax); if (topLeft.x() < 0.0 || topLeft.y() < 0.0 || bottomRight.x() < 0.0 || bottomRight.y() < 0.0) { d->m_previewViewer->clearActiveSelection(); return; } float tlxRatio = topLeft.x()/xmax; float tlyRatio = topLeft.y()/ymax; float brxRatio = bottomRight.x()/xmax; float bryRatio = bottomRight.y()/ymax; d->m_previewViewer->setSelection(tlxRatio, tlyRatio, brxRatio, bryRatio); } void KSaneWidget::setOptionsCollapsed(bool collapse) { if (collapse) { QTimer::singleShot(0, d->m_optionsCollapser, SLOT(slotCollapse())); } else { QTimer::singleShot(0, d->m_optionsCollapser, SLOT(slotRestore())); } } void KSaneWidget::setScanButtonHidden(bool hidden) { d->m_scanBtn->setHidden(hidden); } } // NameSpace KSaneIface diff --git a/src/ksanewidget_p.cpp b/src/ksanewidget_p.cpp index f52d5da..5a4d00b 100644 --- a/src/ksanewidget_p.cpp +++ b/src/ksanewidget_p.cpp @@ -1,1178 +1,1179 @@ /* ============================================================ * * This file is part of the KDE project * * Date : 2007-09-13 * Description : Sane interface for KDE * * Copyright (C) 2007-2008 by Kare Sars * Copyright (C) 2007-2008 by Gilles Caulier * Copyright (C) 2014 by Gregor Mitsch: port to KDE5 frameworks * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program. If not, see . * * ============================================================ */ #include "ksanewidget_p.h" #include #include #include #include #include #include #include #include #define SCALED_PREVIEW_MAX_SIDE 400 static const int ActiveSelection = 100000; namespace KSaneIface { KSaneWidgetPrivate::KSaneWidgetPrivate(KSaneWidget *parent): q(parent) { // Device independent UI variables m_optsTabWidget = nullptr; m_basicOptsTab = nullptr; m_otherOptsTab = nullptr; m_zInBtn = nullptr; m_zOutBtn = nullptr; m_zSelBtn = nullptr; m_zFitBtn = nullptr; m_clearSelBtn = nullptr; m_prevBtn = nullptr; m_scanBtn = nullptr; m_cancelBtn = nullptr; m_previewViewer = nullptr; m_autoSelect = true; m_selIndex = ActiveSelection; m_warmingUp = nullptr; m_progressBar = nullptr; // scanning variables m_isPreview = false; m_saneHandle = nullptr; m_previewThread = nullptr; m_scanThread = nullptr; m_splitGamChB = nullptr; m_commonGamma = nullptr; m_previewDPI = 0; m_invertColors = nullptr; m_previewWidth = 0; m_previewHeight = 0; clearDeviceOptions(); m_findDevThread = FindSaneDevicesThread::getInstance(); connect(m_findDevThread, SIGNAL(finished()), this, SLOT(devListUpdated())); connect(m_findDevThread, SIGNAL(finished()), this, SLOT(signalDevListUpdate())); m_auth = KSaneAuth::getInstance(); m_optionPollTmr.setInterval(100); connect(&m_optionPollTmr, SIGNAL(timeout()), this, SLOT(pollPollOptions())); } void KSaneWidgetPrivate::clearDeviceOptions() { m_optSource = nullptr; m_colorOpts = nullptr; m_optNegative = nullptr; m_optFilmType = nullptr; m_optMode = nullptr; m_optDepth = nullptr; m_optRes = nullptr; m_optResX = nullptr; m_optResY = nullptr; m_optTlX = nullptr; m_optTlY = nullptr; m_optBrX = nullptr; m_optBrY = nullptr; m_optGamR = nullptr; m_optGamG = nullptr; m_optGamB = nullptr; m_optPreview = nullptr; m_optWaitForBtn = nullptr; m_scanOngoing = false; m_closeDevicePending = false; // delete all the options in the list. while (!m_optList.isEmpty()) { delete m_optList.takeFirst(); } m_pollList.clear(); m_optionPollTmr.stop(); // remove the remaining layouts/widgets and read thread delete m_basicOptsTab; m_basicOptsTab = nullptr; delete m_otherOptsTab; m_otherOptsTab = nullptr; delete m_previewThread; m_previewThread = nullptr; delete m_scanThread; m_scanThread = nullptr; m_devName.clear(); } void KSaneWidgetPrivate::devListUpdated() { if (m_vendor.isEmpty()) { const QList list = m_findDevThread->devicesList(); if (list.size() == 0) { return; } for (int i = 0; i < list.size(); ++i) { const KSaneWidget::DeviceInfo info = list.at(i); qDebug() << info.name; if (info.name == m_devName) { m_vendor = info.vendor; m_model = info.model; break; } } } } void KSaneWidgetPrivate::signalDevListUpdate() { emit(q->availableDevices(m_findDevThread->devicesList())); } KSaneWidget::ImageFormat KSaneWidgetPrivate::getImgFormat(SANE_Parameters ¶ms) { switch (params.format) { case SANE_FRAME_GRAY: switch (params.depth) { case 1: return KSaneWidget::FormatBlackWhite; case 8: return KSaneWidget::FormatGrayScale8; case 16: return KSaneWidget::FormatGrayScale16; default: return KSaneWidget::FormatNone; } case SANE_FRAME_RGB: case SANE_FRAME_RED: case SANE_FRAME_GREEN: case SANE_FRAME_BLUE: switch (params.depth) { case 8: return KSaneWidget::FormatRGB_8_C; case 16: return KSaneWidget::FormatRGB_16_C; default: return KSaneWidget::FormatNone; } } return KSaneWidget::FormatNone; } int KSaneWidgetPrivate::getBytesPerLines(SANE_Parameters ¶ms) { switch (getImgFormat(params)) { case KSaneWidget::FormatBlackWhite: case KSaneWidget::FormatGrayScale8: case KSaneWidget::FormatGrayScale16: return params.bytes_per_line; case KSaneWidget::FormatRGB_8_C: return params.pixels_per_line * 3; case KSaneWidget::FormatRGB_16_C: return params.pixels_per_line * 6; case KSaneWidget::FormatNone: case KSaneWidget::FormatBMP: // to remove warning (BMP is omly valid in the twain wrapper) return 0; } return 0; } KSaneOption *KSaneWidgetPrivate::getOption(const QString &name) { int i; for (i = 0; i < m_optList.size(); ++i) { KSaneOption * option = m_optList.at(i); if (option->name() == name) { return option; } } return nullptr; } void KSaneWidgetPrivate::createOptInterface() { m_basicOptsTab = new QWidget; m_basicScrollA->setWidget(m_basicOptsTab); QVBoxLayout *basic_layout = new QVBoxLayout(m_basicOptsTab); KSaneOption *option; // Scan Source if ((option = getOption(QStringLiteral(SANE_NAME_SCAN_SOURCE))) != nullptr) { m_optSource = option; option->createWidget(m_basicOptsTab); basic_layout->addWidget(option->widget()); connect(m_optSource, SIGNAL(valueChanged()), this, SLOT(checkInvert()), Qt::QueuedConnection); } // film-type (note: No translation) if ((option = getOption(QStringLiteral("film-type"))) != nullptr) { m_optFilmType = option; option->createWidget(m_basicOptsTab); basic_layout->addWidget(option->widget()); connect(m_optFilmType, SIGNAL(valueChanged()), this, SLOT(checkInvert()), Qt::QueuedConnection); } else if ((option = getOption(QStringLiteral(SANE_NAME_NEGATIVE))) != nullptr) { m_optNegative = option; option->createWidget(m_basicOptsTab); basic_layout->addWidget(option->widget()); } // Scan mode if ((option = getOption(QStringLiteral(SANE_NAME_SCAN_MODE))) != nullptr) { m_optMode = option; option->createWidget(m_basicOptsTab); basic_layout->addWidget(option->widget()); } // Bitdepth if ((option = getOption(QStringLiteral(SANE_NAME_BIT_DEPTH))) != nullptr) { m_optDepth = option; option->createWidget(m_basicOptsTab); basic_layout->addWidget(option->widget()); } // Threshold if ((option = getOption(QStringLiteral(SANE_NAME_THRESHOLD))) != nullptr) { option->createWidget(m_basicOptsTab); basic_layout->addWidget(option->widget()); } // Resolution if ((option = getOption(QStringLiteral(SANE_NAME_SCAN_RESOLUTION))) != nullptr) { m_optRes = option; option->createWidget(m_basicOptsTab); basic_layout->addWidget(option->widget()); } // These two next resolution options are a bit tricky. if ((option = getOption(QStringLiteral(SANE_NAME_SCAN_X_RESOLUTION))) != nullptr) { m_optResX = option; if (!m_optRes) { m_optRes = m_optResX; } option->createWidget(m_basicOptsTab); basic_layout->addWidget(option->widget()); } if ((option = getOption(QStringLiteral(SANE_NAME_SCAN_Y_RESOLUTION))) != nullptr) { m_optResY = option; option->createWidget(m_basicOptsTab); basic_layout->addWidget(option->widget()); } // save a pointer to the preview option if possible if ((option = getOption(QStringLiteral(SANE_NAME_PREVIEW))) != nullptr) { m_optPreview = option; } // save a pointer to the "wait-for-button" option if possible (Note: No translation) if ((option = getOption(QStringLiteral("wait-for-button"))) != nullptr) { m_optWaitForBtn = option; } // scan area (Do not add the widgets) if ((option = getOption(QStringLiteral(SANE_NAME_SCAN_TL_X))) != nullptr) { m_optTlX = option; connect(option, SIGNAL(fValueRead(float)), this, SLOT(setTLX(float))); } if ((option = getOption(QStringLiteral(SANE_NAME_SCAN_TL_Y))) != nullptr) { m_optTlY = option; connect(option, SIGNAL(fValueRead(float)), this, SLOT(setTLY(float))); } if ((option = getOption(QStringLiteral(SANE_NAME_SCAN_BR_X))) != nullptr) { m_optBrX = option; connect(option, SIGNAL(fValueRead(float)), this, SLOT(setBRX(float))); } if ((option = getOption(QStringLiteral(SANE_NAME_SCAN_BR_Y))) != nullptr) { m_optBrY = option; connect(option, SIGNAL(fValueRead(float)), this, SLOT(setBRY(float))); } // Color Options Frame m_colorOpts = new QWidget(m_basicOptsTab); basic_layout->addWidget(m_colorOpts); QVBoxLayout *color_lay = new QVBoxLayout(m_colorOpts); color_lay->setContentsMargins(0, 0, 0, 0); // Add Color correction to the color "frame" if ((option = getOption(QStringLiteral(SANE_NAME_BRIGHTNESS))) != nullptr) { option->createWidget(m_colorOpts); color_lay->addWidget(option->widget()); } if ((option = getOption(QStringLiteral(SANE_NAME_CONTRAST))) != nullptr) { option->createWidget(m_colorOpts); color_lay->addWidget(option->widget()); } // Add gamma tables to the color "frame" QWidget *gamma_frm = new QWidget(m_colorOpts); color_lay->addWidget(gamma_frm); QVBoxLayout *gam_frm_l = new QVBoxLayout(gamma_frm); gam_frm_l->setContentsMargins(0, 0, 0, 0); if ((option = getOption(QStringLiteral(SANE_NAME_GAMMA_VECTOR_R))) != nullptr) { m_optGamR = option; option->createWidget(gamma_frm); gam_frm_l->addWidget(option->widget()); } if ((option = getOption(QStringLiteral(SANE_NAME_GAMMA_VECTOR_G))) != nullptr) { m_optGamG = option; option->createWidget(gamma_frm); gam_frm_l->addWidget(option->widget()); } if ((option = getOption(QStringLiteral(SANE_NAME_GAMMA_VECTOR_B))) != nullptr) { m_optGamB = option; option->createWidget(gamma_frm); gam_frm_l->addWidget(option->widget()); } if ((m_optGamR != nullptr) && (m_optGamG != nullptr) && (m_optGamB != nullptr)) { LabeledGamma *gamma = reinterpret_cast(m_optGamR->widget()); m_commonGamma = new LabeledGamma(m_colorOpts, i18n(SANE_TITLE_GAMMA_VECTOR), gamma->size(), gamma->maxValue()); color_lay->addWidget(m_commonGamma); m_commonGamma->setToolTip(i18n(SANE_DESC_GAMMA_VECTOR)); connect(m_commonGamma, SIGNAL(gammaChanged(int,int,int)), m_optGamR->widget(), SLOT(setValues(int,int,int))); connect(m_commonGamma, SIGNAL(gammaChanged(int,int,int)), m_optGamG->widget(), SLOT(setValues(int,int,int))); connect(m_commonGamma, SIGNAL(gammaChanged(int,int,int)), m_optGamB->widget(), SLOT(setValues(int,int,int))); m_splitGamChB = new LabeledCheckbox(m_colorOpts, i18n("Separate color intensity tables")); color_lay->addWidget(m_splitGamChB); connect(m_splitGamChB, SIGNAL(toggled(bool)), gamma_frm, SLOT(setVisible(bool))); connect(m_splitGamChB, SIGNAL(toggled(bool)), m_commonGamma, SLOT(setHidden(bool))); gamma_frm->hide(); } if ((option = getOption(QStringLiteral(SANE_NAME_BLACK_LEVEL))) != nullptr) { option->createWidget(m_colorOpts); color_lay->addWidget(option->widget()); } if ((option = getOption(QStringLiteral(SANE_NAME_WHITE_LEVEL))) != nullptr) { option->createWidget(m_colorOpts); color_lay->addWidget(option->widget()); } m_invertColors = new LabeledCheckbox(m_colorOpts, i18n("Invert colors")); color_lay->addWidget(m_invertColors); m_invertColors->setChecked(false); connect(m_invertColors, SIGNAL(toggled(bool)), this, SLOT(invertPreview())); // add a stretch to the end to keep the parameters at the top basic_layout->addStretch(); // Remaining (un known) options go to the "Other Options" m_otherOptsTab = new QWidget; m_otherScrollA->setWidget(m_otherOptsTab); QVBoxLayout *other_layout = new QVBoxLayout(m_otherOptsTab); // add the remaining parameters for (int i = 0; i < m_optList.size(); ++i) { KSaneOption *option = m_optList.at(i); if ((option->widget() == nullptr) && (option->name() != QStringLiteral(SANE_NAME_SCAN_TL_X)) && (option->name() != QStringLiteral(SANE_NAME_SCAN_TL_Y)) && (option->name() != QStringLiteral(SANE_NAME_SCAN_BR_X)) && (option->name() != QStringLiteral(SANE_NAME_SCAN_BR_Y)) && (option->name() != QStringLiteral(SANE_NAME_PREVIEW)) && (option->hasGui())) { option->createWidget(m_otherOptsTab); other_layout->addWidget(option->widget()); } } // add a stretch to the end to keep the parameters at the top other_layout->addStretch(); // calculate label widths int labelWidth = 0; KSaneOptionWidget *tmpOption; // Basic Options for (int i = 0; i < basic_layout->count(); ++i) { if (basic_layout->itemAt(i) && basic_layout->itemAt(i)->widget()) { tmpOption = qobject_cast(basic_layout->itemAt(i)->widget()); if (tmpOption) { labelWidth = qMax(labelWidth, tmpOption->labelWidthHint()); } } } // Color Options for (int i = 0; i < basic_layout->count(); ++i) { if (basic_layout->itemAt(i) && basic_layout->itemAt(i)->widget()) { tmpOption = qobject_cast(basic_layout->itemAt(i)->widget()); if (tmpOption) { labelWidth = qMax(labelWidth, tmpOption->labelWidthHint()); } } } // Set label widths for (int i = 0; i < basic_layout->count(); ++i) { if (basic_layout->itemAt(i) && basic_layout->itemAt(i)->widget()) { tmpOption = qobject_cast(basic_layout->itemAt(i)->widget()); if (tmpOption) { tmpOption->setLabelWidth(labelWidth); } } } for (int i = 0; i < color_lay->count(); ++i) { if (color_lay->itemAt(i) && color_lay->itemAt(i)->widget()) { tmpOption = qobject_cast(color_lay->itemAt(i)->widget()); if (tmpOption) { tmpOption->setLabelWidth(labelWidth); } } } // Other Options labelWidth = 0; for (int i = 0; i < other_layout->count(); ++i) { if (other_layout->itemAt(i) && other_layout->itemAt(i)->widget()) { tmpOption = qobject_cast(other_layout->itemAt(i)->widget()); if (tmpOption) { labelWidth = qMax(labelWidth, tmpOption->labelWidthHint()); } } } for (int i = 0; i < other_layout->count(); ++i) { if (other_layout->itemAt(i) && other_layout->itemAt(i)->widget()) { tmpOption = qobject_cast(other_layout->itemAt(i)->widget()); if (tmpOption) { tmpOption->setLabelWidth(labelWidth); } } } // ensure that we do not get a scrollbar at the bottom of the option of the options int min_width = m_basicOptsTab->sizeHint().width(); if (min_width < m_otherOptsTab->sizeHint().width()) { min_width = m_otherOptsTab->sizeHint().width(); } m_optsTabWidget->setMinimumWidth(min_width + m_basicScrollA->verticalScrollBar()->sizeHint().width() + 5); } void KSaneWidgetPrivate::setDefaultValues() { KSaneOption *option; // Try to get Color mode by default if ((option = getOption(QStringLiteral(SANE_NAME_SCAN_MODE))) != nullptr) { option->setValue(i18n(SANE_VALUE_SCAN_MODE_COLOR)); } // Try to set 8 bit color if ((option = getOption(QStringLiteral(SANE_NAME_BIT_DEPTH))) != nullptr) { option->setValue(8); } // Try to set Scan resolution to 600 DPI if (m_optRes != nullptr) { m_optRes->setValue(600); } } void KSaneWidgetPrivate::scheduleValReload() { m_readValsTmr.start(5); } void KSaneWidgetPrivate::optReload() { int i; for (i = 0; i < m_optList.size(); ++i) { m_optList.at(i)->readOption(); // Also read the values m_optList.at(i)->readValue(); } // Gamma table special case if (m_optGamR && m_optGamG && m_optGamB) { m_commonGamma->setHidden(m_optGamR->state() == KSaneOption::STATE_HIDDEN); m_splitGamChB->setHidden(m_optGamR->state() == KSaneOption::STATE_HIDDEN); } // estimate the preview size and create an empty image // this is done so that you can select scan area without // having to scan a preview. updatePreviewSize(); // ensure that we do not get a scrollbar at the bottom of the option of the options int min_width = m_basicOptsTab->sizeHint().width(); if (min_width < m_otherOptsTab->sizeHint().width()) { min_width = m_otherOptsTab->sizeHint().width(); } m_optsTabWidget->setMinimumWidth(min_width + m_basicScrollA->verticalScrollBar()->sizeHint().width() + 5); m_previewViewer->zoom2Fit(); } void KSaneWidgetPrivate::valReload() { int i; QString tmp; for (i = 0; i < m_optList.size(); ++i) { m_optList.at(i)->readValue(); } } void KSaneWidgetPrivate::handleSelection(float tl_x, float tl_y, float br_x, float br_y) { if ((m_optTlX == nullptr) || (m_optTlY == nullptr) || (m_optBrX == nullptr) || (m_optBrY == nullptr)) { // clear the selection since we can not set one m_previewViewer->setTLX(0); m_previewViewer->setTLY(0); m_previewViewer->setBRX(0); m_previewViewer->setBRY(0); return; } float max_x, max_y; if ((m_previewImg.width() == 0) || (m_previewImg.height() == 0)) { return; } m_optBrX->getMaxValue(max_x); m_optBrY->getMaxValue(max_y); float ftl_x = tl_x * max_x; float ftl_y = tl_y * max_y; float fbr_x = br_x * max_x; float fbr_y = br_y * max_y; m_optTlX->setValue(ftl_x); m_optTlY->setValue(ftl_y); m_optBrX->setValue(fbr_x); m_optBrY->setValue(fbr_y); } void KSaneWidgetPrivate::setTLX(float ftlx) { // ignore this during an active scan if (m_previewThread->isRunning()) { return; } if (m_scanThread->isRunning()) { return; } if (m_scanOngoing) { return; } float max, ratio; //qDebug() << "setTLX " << ftlx; m_optBrX->getMaxValue(max); ratio = ftlx / max; //qDebug() << " -> " << ratio; m_previewViewer->setTLX(ratio); } void KSaneWidgetPrivate::setTLY(float ftly) { // ignore this during an active scan if (m_previewThread->isRunning()) { return; } if (m_scanThread->isRunning()) { return; } if (m_scanOngoing) { return; } float max, ratio; //qDebug() << "setTLY " << ftly; m_optBrY->getMaxValue(max); ratio = ftly / max; //qDebug() << " -> " << ratio; m_previewViewer->setTLY(ratio); } void KSaneWidgetPrivate::setBRX(float fbrx) { // ignore this during an active scan if (m_previewThread->isRunning()) { return; } if (m_scanThread->isRunning()) { return; } if (m_scanOngoing) { return; } float max, ratio; //qDebug() << "setBRX " << fbrx; m_optBrX->getMaxValue(max); ratio = fbrx / max; //qDebug() << " -> " << ratio; m_previewViewer->setBRX(ratio); } void KSaneWidgetPrivate::setBRY(float fbry) { // ignore this during an active scan if (m_previewThread->isRunning()) { return; } if (m_scanThread->isRunning()) { return; } if (m_scanOngoing) { return; } float max, ratio; //qDebug() << "setBRY " << fbry; m_optBrY->getMaxValue(max); ratio = fbry / max; //qDebug() << " -> " << ratio; m_previewViewer->setBRY(ratio); } void KSaneWidgetPrivate::updatePreviewSize() { float max_x = 0, max_y = 0; float ratio; int x, y; // check if an update is necessary if (m_optBrX != nullptr) { m_optBrX->getMaxValue(max_x); } if (m_optBrY != nullptr) { m_optBrY->getMaxValue(max_y); } if ((max_x == m_previewWidth) && (max_y == m_previewHeight)) { //qDebug() << "no preview size change"; return; } // The preview size has changed m_previewWidth = max_x; m_previewHeight = max_y; // set the scan area to the whole area m_previewViewer->clearSelections(); if (m_optTlX != nullptr) { m_optTlX->setValue(0); } if (m_optTlY != nullptr) { m_optTlY->setValue(0); } if (m_optBrX != nullptr) { m_optBrX->setValue(max_x); } if (m_optBrY != nullptr) { m_optBrY->setValue(max_y); } // create a "scaled" image of the preview ratio = max_x / max_y; if (ratio < 1) { x = SCALED_PREVIEW_MAX_SIDE; y = (int)(SCALED_PREVIEW_MAX_SIDE / ratio); } else { y = SCALED_PREVIEW_MAX_SIDE; x = (int)(SCALED_PREVIEW_MAX_SIDE / ratio); } const qreal dpr = q->devicePixelRatioF(); m_previewImg = QImage(QSize(x, y) * dpr, QImage::Format_RGB32); m_previewImg.setDevicePixelRatio(dpr); m_previewImg.fill(0xFFFFFFFF); // set the new image m_previewViewer->setQImage(&m_previewImg); } void KSaneWidgetPrivate::startPreviewScan() { if (m_scanOngoing) { return; } m_scanOngoing = true; SANE_Status status; float max_x, max_y; float dpi; // store the current settings of parameters to be changed if (m_optDepth != nullptr) { m_optDepth->storeCurrentData(); } if (m_optRes != nullptr) { m_optRes->storeCurrentData(); } if (m_optResX != nullptr) { m_optResX->storeCurrentData(); } if (m_optResY != nullptr) { m_optResY->storeCurrentData(); } if (m_optPreview != nullptr) { m_optPreview->storeCurrentData(); } // check if we can modify the selection if ((m_optTlX != nullptr) && (m_optTlY != nullptr) && (m_optBrX != nullptr) && (m_optBrY != nullptr)) { // get maximums m_optBrX->getMaxValue(max_x); m_optBrY->getMaxValue(max_y); // select the whole area m_optTlX->setValue(0); m_optTlY->setValue(0); m_optBrX->setValue(max_x); m_optBrY->setValue(max_y); } else { // no use to try auto selections if you can not use them m_autoSelect = false; } if (m_optRes != nullptr) { if (m_previewDPI >= 25.0) { m_optRes->setValue(m_previewDPI); if ((m_optResY != nullptr) && (m_optRes->name() == QStringLiteral(SANE_NAME_SCAN_X_RESOLUTION))) { m_optResY->setValue(m_previewDPI); } } else { // set the resolution to getMinValue and increase if necessary SANE_Parameters params; m_optRes->getMinValue(dpi); do { m_optRes->setValue(dpi); if ((m_optResY != nullptr) && (m_optRes->name() == QStringLiteral(SANE_NAME_SCAN_X_RESOLUTION))) { m_optResY->setValue(dpi); } //check what image size we would get in a scan status = sane_get_parameters(m_saneHandle, ¶ms); if (status != SANE_STATUS_GOOD) { qDebug() << "sane_get_parameters=" << sane_strstatus(status); previewScanDone(); return; } if (dpi > 600) { break; } // Increase the dpi value dpi += 25.0; } while ((params.pixels_per_line < 300) || ((params.lines > 0) && (params.lines < 300))); if (params.pixels_per_line == 0) { // This is a security measure for broken backends m_optRes->getMinValue(dpi); m_optRes->setValue(dpi); qDebug() << "Setting minimum DPI value for a broken back-end"; } } } // set preview option to true if possible if (m_optPreview != nullptr) { m_optPreview->setValue(SANE_TRUE); } // execute valReload if there is a pending value reload while (m_readValsTmr.isActive()) { m_readValsTmr.stop(); valReload(); } // clear the preview m_previewViewer->clearHighlight(); m_previewViewer->clearSelections(); m_previewImg.fill(0xFFFFFFFF); updatePreviewSize(); setBusy(true); m_progressBar->setValue(0); m_isPreview = true; m_previewThread->setPreviewInverted(m_invertColors->isChecked()); m_previewThread->start(); m_updProgressTmr.start(); } void KSaneWidgetPrivate::previewScanDone() { // even if the scan is finished successfully we need to call sane_cancel() sane_cancel(m_saneHandle); if (m_closeDevicePending) { setBusy(false); sane_close(m_saneHandle); m_saneHandle = nullptr; clearDeviceOptions(); emit(q->scanDone(KSaneWidget::NoError, QStringLiteral(""))); return; } // restore the original settings of the changed parameters if (m_optDepth != nullptr) { m_optDepth->restoreSavedData(); } if (m_optRes != nullptr) { m_optRes->restoreSavedData(); } if (m_optResX != nullptr) { m_optResX->restoreSavedData(); } if (m_optResY != nullptr) { m_optResY->restoreSavedData(); } if (m_optPreview != nullptr) { m_optPreview->restoreSavedData(); } m_previewViewer->setQImage(&m_previewImg); m_previewViewer->zoom2Fit(); if ((m_previewThread->saneStatus() != SANE_STATUS_GOOD) && (m_previewThread->saneStatus() != SANE_STATUS_EOF)) { alertUser(KSaneWidget::ErrorGeneral, i18n(sane_strstatus(m_previewThread->saneStatus()))); } else if (m_autoSelect) { m_previewViewer->findSelections(); } setBusy(false); m_scanOngoing = false; m_updProgressTmr.stop(); emit(q->scanDone(KSaneWidget::NoError, QStringLiteral(""))); return; } void KSaneWidgetPrivate::startFinalScan() { if (m_scanOngoing) { return; } m_scanOngoing = true; m_isPreview = false; float x1 = 0, y1 = 0, x2 = 0, y2 = 0, max_x, max_y; m_selIndex = 0; if ((m_optTlX != nullptr) && (m_optTlY != nullptr) && (m_optBrX != nullptr) && (m_optBrY != nullptr)) { // get maximums m_optBrX->getMaxValue(max_x); m_optBrY->getMaxValue(max_y); // read the selection from the viewer m_previewViewer->selectionAt(m_selIndex, x1, y1, x2, y2); m_previewViewer->setHighlightArea(x1, y1, x2, y2); m_selIndex++; // calculate the option values x1 *= max_x; y1 *= max_y; x2 *= max_x; y2 *= max_y; // now set the selection m_optTlX->setValue(x1); m_optTlY->setValue(y1); m_optBrX->setValue(x2); m_optBrY->setValue(y2); } // execute a pending value reload while (m_readValsTmr.isActive()) { m_readValsTmr.stop(); valReload(); } setBusy(true); m_updProgressTmr.start(); m_scanThread->setImageInverted(m_invertColors->isChecked()); m_scanThread->start(); } void KSaneWidgetPrivate::oneFinalScanDone() { m_updProgressTmr.stop(); updateProgress(); if (m_closeDevicePending) { setBusy(false); sane_close(m_saneHandle); m_saneHandle = nullptr; clearDeviceOptions(); return; } if (m_scanThread->frameStatus() == KSaneScanThread::READ_READY) { // scan finished OK SANE_Parameters params = m_scanThread->saneParameters(); int lines = params.lines; if (lines == -1) { // this is probably a handscanner -> calculate the size from the read data int bytesPerLine = qMax(getBytesPerLines(params), 1); // ensure no div by 0 lines = m_scanData.size() / bytesPerLine; } emit(q->imageReady(m_scanData, params.pixels_per_line, lines, getBytesPerLines(params), (int)getImgFormat(params))); // now check if we should have automatic ADF batch scanning if (m_optSource) { QString source; m_optSource->getValue(source); if (source.contains(QStringLiteral("Automatic Document Feeder")) || - source.contains(QStringLiteral("ADF"))) { + source.contains(QStringLiteral("ADF")) || + source.contains(QStringLiteral("Duplex"))) { // in batch mode only one area can be scanned per page //qDebug() << "source == " << source; m_updProgressTmr.start(); m_scanThread->start(); return; } } // Check if we have a "wait for button" batch scanning if (m_optWaitForBtn) { qDebug() << m_optWaitForBtn->name(); QString wait; m_optWaitForBtn->getValue(wait); qDebug() << "wait ==" << wait; if (wait == QStringLiteral("true")) { // in batch mode only one area can be scanned per page //qDebug() << "source == \"Automatic Document Feeder\""; m_updProgressTmr.start(); m_scanThread->start(); return; } } // not batch scan, call sane_cancel to be able to change parameters. sane_cancel(m_saneHandle); //qDebug() << "index=" << m_selIndex << "size=" << m_previewViewer->selListSize(); // check if we have multiple selections. if (m_previewViewer->selListSize() > m_selIndex) { if ((m_optTlX != nullptr) && (m_optTlY != nullptr) && (m_optBrX != nullptr) && (m_optBrY != nullptr)) { float x1 = 0, y1 = 0, x2 = 0, y2 = 0, max_x, max_y; // get maximums m_optBrX->getMaxValue(max_x); m_optBrY->getMaxValue(max_y); // read the selection from the viewer m_previewViewer->selectionAt(m_selIndex, x1, y1, x2, y2); // set the highlight m_previewViewer->setHighlightArea(x1, y1, x2, y2); // calculate the option values x1 *= max_x; y1 *= max_y; x2 *= max_x; y2 *= max_y; // now set the selection m_optTlX->setValue(x1); m_optTlY->setValue(y1); m_optBrX->setValue(x2); m_optBrY->setValue(y2); m_selIndex++; // execute a pending value reload while (m_readValsTmr.isActive()) { m_readValsTmr.stop(); valReload(); } m_updProgressTmr.start(); m_scanThread->start(); return; } } emit(q->scanDone(KSaneWidget::NoError, QStringLiteral(""))); } else { switch (m_scanThread->saneStatus()) { case SANE_STATUS_GOOD: case SANE_STATUS_CANCELLED: case SANE_STATUS_EOF: break; case SANE_STATUS_NO_DOCS: emit(q->scanDone(KSaneWidget::Information, i18n(sane_strstatus(m_scanThread->saneStatus())))); alertUser(KSaneWidget::Information, i18n(sane_strstatus(m_scanThread->saneStatus()))); break; case SANE_STATUS_UNSUPPORTED: case SANE_STATUS_IO_ERROR: case SANE_STATUS_NO_MEM: case SANE_STATUS_INVAL: case SANE_STATUS_JAMMED: case SANE_STATUS_COVER_OPEN: case SANE_STATUS_DEVICE_BUSY: case SANE_STATUS_ACCESS_DENIED: emit(q->scanDone(KSaneWidget::ErrorGeneral, i18n(sane_strstatus(m_scanThread->saneStatus())))); alertUser(KSaneWidget::ErrorGeneral, i18n(sane_strstatus(m_scanThread->saneStatus()))); break; } } sane_cancel(m_saneHandle); // clear the highlight m_previewViewer->setHighlightArea(0, 0, 1, 1); setBusy(false); m_scanOngoing = false; } void KSaneWidgetPrivate::setBusy(bool busy) { if (busy) { m_warmingUp->show(); m_activityFrame->hide(); m_btnFrame->hide(); m_optionPollTmr.stop(); emit(q->scanProgress(0)); } else { m_warmingUp->hide(); m_activityFrame->hide(); m_btnFrame->show(); if (m_pollList.size() > 0) { m_optionPollTmr.start(); } emit(q->scanProgress(100)); } m_optsTabWidget->setDisabled(busy); m_previewViewer->setDisabled(busy); m_scanBtn->setFocus(Qt::OtherFocusReason); } void KSaneWidgetPrivate::checkInvert() { if (!m_optSource) { return; } if (!m_optFilmType) { return; } if (m_scanOngoing) { return; } QString source; QString filmtype; m_optSource->getValue(source); m_optFilmType->getValue(filmtype); if ((source.contains(i18nc("This is compared to the option string returned by sane", "Transparency"), Qt::CaseInsensitive)) && (filmtype.contains(i18nc("This is compared to the option string returned by sane", "Negative"), Qt::CaseInsensitive))) { m_invertColors->setChecked(true); } else { m_invertColors->setChecked(false); } } void KSaneWidgetPrivate::invertPreview() { m_previewImg.invertPixels(); m_previewViewer->updateImage(); } void KSaneWidgetPrivate::updateProgress() { int progress; if (m_isPreview) { progress = m_previewThread->scanProgress(); if (m_previewThread->saneStartDone()) { if (!m_progressBar->isVisible() || m_previewThread->imageResized()) { m_warmingUp->hide(); m_activityFrame->show(); // the image size might have changed m_previewThread->imgMutex.lock(); m_previewViewer->setQImage(&m_previewImg); m_previewViewer->zoom2Fit(); m_previewThread->imgMutex.unlock(); } else { m_previewThread->imgMutex.lock(); m_previewViewer->updateImage(); m_previewThread->imgMutex.unlock(); } } } else { if (!m_progressBar->isVisible() && (m_scanThread->saneStartDone())) { m_warmingUp->hide(); m_activityFrame->show(); } progress = m_scanThread->scanProgress(); m_previewViewer->setHighlightShown(progress); } m_progressBar->setValue(progress); emit(q->scanProgress(progress)); } void KSaneWidgetPrivate::alertUser(int type, const QString &strStatus) { if (q->receivers(SIGNAL(userMessage(int,QString))) == 0) { switch (type) { case KSaneWidget::ErrorGeneral: QMessageBox::critical(nullptr, i18nc("@title:window", "General Error"), strStatus); break; default: QMessageBox::information(nullptr, i18nc("@title:window", "Information"), strStatus); break; } } else { emit(q->userMessage(type, strStatus)); } } void KSaneWidgetPrivate::pollPollOptions() { for (int i = 1; i < m_pollList.size(); ++i) { m_pollList.at(i)->readValue(); } } } // NameSpace KSaneIface diff --git a/src/options/ksaneoptcombo.cpp b/src/options/ksaneoptcombo.cpp index 7dd079d..7156b1a 100644 --- a/src/options/ksaneoptcombo.cpp +++ b/src/options/ksaneoptcombo.cpp @@ -1,393 +1,390 @@ /* ============================================================ * * This file is part of the KDE project * * Date : 2009-01-21 * Description : Sane interface for KDE * * Copyright (C) 2009 by Kare Sars * Copyright (C) 2014 by Gregor Mitsch: port to KDE5 frameworks * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program. If not, see . * * ============================================================ */ #include "ksaneoptcombo.h" #include "labeledcombo.h" #include #include #include namespace KSaneIface { static const char tmp_binary[] = "Binary"; KSaneOptCombo::KSaneOptCombo(const SANE_Handle handle, const int index) : KSaneOption(handle, index), m_combo(nullptr) { } void KSaneOptCombo::createWidget(QWidget *parent) { if (m_widget) { return; } m_widget = m_combo = new LabeledCombo(parent, QStringLiteral(""), QStringList()); readOption(); m_widget->setToolTip(sane_i18n(m_optDesc->desc)); connect(m_combo, QOverload::of(&LabeledCombo::activated), this, &KSaneOptCombo::comboboxChangedIndex); readValue(); } void KSaneOptCombo::readValue() { if (state() == STATE_HIDDEN) { return; } // read that current value QVarLengthArray data(m_optDesc->size); SANE_Status status; SANE_Int res; status = sane_control_option(m_handle, m_index, SANE_ACTION_GET_VALUE, data.data(), &res); if (status != SANE_STATUS_GOOD) { return; } m_currentText = getSaneComboString(data.data()); if (m_combo != nullptr) { if (m_combo->currentText() != m_currentText) { m_combo->setCurrentText(m_currentText); emit valueChanged(); } } } void KSaneOptCombo::readOption() { KSaneOption::readOption(); if (!m_combo) { return; } QString saved = m_combo->currentText(); m_strList = genComboStringList(); m_combo->clear(); m_combo->setLabelText(sane_i18n(m_optDesc->title)); m_combo->addItems(m_strList); m_combo->setIcon(QIcon::fromTheme(QStringLiteral("color")), getSaneComboString((unsigned char *)SANE_VALUE_SCAN_MODE_COLOR)); m_combo->setIcon(QIcon::fromTheme(QStringLiteral("gray-scale")), getSaneComboString((unsigned char *)SANE_VALUE_SCAN_MODE_GRAY)); m_combo->setIcon(QIcon::fromTheme(QStringLiteral("black-white")), getSaneComboString((unsigned char *)SANE_VALUE_SCAN_MODE_LINEART)); // The epkowa/epson backend uses "Binary" which is the same as "Lineart" m_combo->setIcon(QIcon::fromTheme(QStringLiteral("black-white")), i18n(tmp_binary)); // set the previous value m_combo->setCurrentText(saved); } QStringList &KSaneOptCombo::genComboStringList() { int i; m_strList.clear(); switch (m_optDesc->type) { case SANE_TYPE_INT: for (i = 1; i <= m_optDesc->constraint.word_list[0]; ++i) { m_strList += getSaneComboString((int)m_optDesc->constraint.word_list[i]); } break; case SANE_TYPE_FIXED: for (i = 1; i <= m_optDesc->constraint.word_list[0]; ++i) { m_strList += getSaneComboString((float)SANE_UNFIX(m_optDesc->constraint.word_list[i])); } break; case SANE_TYPE_STRING: i = 0; while (m_optDesc->constraint.string_list[i] != nullptr) { m_strList += getSaneComboString((unsigned char *)m_optDesc->constraint.string_list[i]); i++; } break; default : m_strList += QStringLiteral("NOT HANDELED"); break; } return m_strList; } QString KSaneOptCombo::getSaneComboString(int ival) { switch (m_optDesc->unit) { case SANE_UNIT_NONE: break; case SANE_UNIT_PIXEL: return i18np("%1 Pixel", "%1 Pixels", ival); case SANE_UNIT_BIT: return i18np("%1 Bit", "%1 Bits", ival); case SANE_UNIT_MM: return i18np("%1 mm", "%1 mm", ival); case SANE_UNIT_DPI: return i18np("%1 DPI", "%1 DPI", ival); case SANE_UNIT_PERCENT: return i18np("%1 %", "%1 %", ival); case SANE_UNIT_MICROSECOND: return i18np("%1 µs", "%1 µs", ival); } return QString::number(ival); } QString KSaneOptCombo::getSaneComboString(float fval) { switch (m_optDesc->unit) { case SANE_UNIT_NONE: break; case SANE_UNIT_PIXEL: return i18ncp("Parameter and Unit", "%1 Pixel", "%1 Pixels", fval); case SANE_UNIT_BIT: return i18ncp("Parameter and Unit", "%1 Bit", "%1 Bits", fval); case SANE_UNIT_MM: return i18nc("Parameter and Unit (Millimeter)", "%1 mm", fval); case SANE_UNIT_DPI: return i18nc("Parameter and Unit (Dots Per Inch)", "%1 DPI", fval); case SANE_UNIT_PERCENT: return i18nc("Parameter and Unit (Percentage)", "%1 %", fval); case SANE_UNIT_MICROSECOND: return i18nc("Parameter and Unit (Microseconds)", "%1 µs", fval); } return QString::number(fval, 'F', 4); } QString KSaneOptCombo::getSaneComboString(unsigned char *data) { QString tmp; if (data == nullptr) { return QString(); } switch (m_optDesc->type) { case SANE_TYPE_INT: return getSaneComboString((int)toSANE_Word(data)); case SANE_TYPE_FIXED: return getSaneComboString((float)SANE_UNFIX(toSANE_Word(data))); case SANE_TYPE_STRING: tmp = i18n(reinterpret_cast(data)); tmp = tmp.simplified(); return tmp; default : break; } return QString(); } void KSaneOptCombo::comboboxChangedIndex(int i) { if (m_combo && (m_combo->currentText() == m_currentText)) { return; } unsigned char data[4]; void *dataPtr; switch (m_optDesc->type) { case SANE_TYPE_INT: case SANE_TYPE_FIXED: fromSANE_Word(data, m_optDesc->constraint.word_list[i + 1]); dataPtr = data; break; case SANE_TYPE_STRING: dataPtr = (void *)m_optDesc->constraint.string_list[i]; break; default: qDebug() << "can not handle type:" << m_optDesc->type; return; } writeData(dataPtr); readValue(); emit valueChanged(); } bool KSaneOptCombo::getMinValue(float &val) { if (state() == STATE_HIDDEN) { return false; } switch (m_optDesc->type) { case SANE_TYPE_INT: val = (float)m_optDesc->constraint.word_list[1]; for (int i = 2; i <= m_optDesc->constraint.word_list[0]; i++) { val = qMin((float)m_optDesc->constraint.word_list[i], val); } break; case SANE_TYPE_FIXED: val = (float)SANE_UNFIX(m_optDesc->constraint.word_list[1]); for (int i = 2; i <= m_optDesc->constraint.word_list[0]; i++) { val = qMin((float)SANE_UNFIX(m_optDesc->constraint.word_list[i]), val); } break; default: qDebug() << "can not handle type:" << m_optDesc->type; return false; } return true; } bool KSaneOptCombo::getValue(float &val) { if (state() == STATE_HIDDEN) { return false; } // read that current value QVarLengthArray data(m_optDesc->size); SANE_Status status; SANE_Int res; status = sane_control_option(m_handle, m_index, SANE_ACTION_GET_VALUE, data.data(), &res); if (status != SANE_STATUS_GOOD) { qDebug() << m_optDesc->name << "sane_control_option returned" << status; return false; } switch (m_optDesc->type) { case SANE_TYPE_INT: val = (float)toSANE_Word(data.data()); return true; case SANE_TYPE_FIXED: val = SANE_UNFIX(toSANE_Word(data.data())); return true; default: qDebug() << "Type" << m_optDesc->type << "not supported!"; break; } return false; } bool KSaneOptCombo::setValue(float value) { unsigned char data[4]; float tmp; float minDiff; int i; int minIndex = 1; switch (m_optDesc->type) { case SANE_TYPE_INT: tmp = (float)m_optDesc->constraint.word_list[minIndex]; minDiff = qAbs(value - tmp); for (i = 2; i <= m_optDesc->constraint.word_list[0]; ++i) { tmp = (float)m_optDesc->constraint.word_list[i]; if (qAbs(value - tmp) < minDiff) { minDiff = qAbs(value - tmp); minIndex = i; } } fromSANE_Word(data, m_optDesc->constraint.word_list[minIndex]); writeData(data); readValue(); return (minDiff < 1.0); case SANE_TYPE_FIXED: tmp = (float)SANE_UNFIX(m_optDesc->constraint.word_list[minIndex]); minDiff = qAbs(value - tmp); for (i = 2; i <= m_optDesc->constraint.word_list[0]; ++i) { tmp = (float)SANE_UNFIX(m_optDesc->constraint.word_list[i]); if (qAbs(value - tmp) < minDiff) { minDiff = qAbs(value - tmp); minIndex = i; } } fromSANE_Word(data, m_optDesc->constraint.word_list[minIndex]); writeData(data); readValue(); return (minDiff < 1.0); default: qDebug() << "can not handle type:" << m_optDesc->type; break; } return false; } bool KSaneOptCombo::getValue(QString &val) { if (state() == STATE_HIDDEN) { return false; } val = m_currentText; return true; } bool KSaneOptCombo::setValue(const QString &val) { if (state() == STATE_HIDDEN) { return false; } - if (val == m_currentText) { - return true; - } unsigned char data[4]; void* data_ptr = nullptr; SANE_Word fixed; int i; float f; bool ok; QString tmp; switch (m_optDesc->type) { case SANE_TYPE_INT: tmp = val.left(val.indexOf(QLatin1Char(' '))); // strip the unit // accept float formatting of the string i = (int)(tmp.toFloat(&ok)); if (ok == false) { return false; } fromSANE_Word(data, i); data_ptr = data; break; case SANE_TYPE_FIXED: tmp = val.left(val.indexOf(QLatin1Char(' '))); // strip the unit f = tmp.toFloat(&ok); if (ok == false) { return false; } fixed = SANE_FIX(f); fromSANE_Word(data, fixed); data_ptr = data; break; case SANE_TYPE_STRING: i = 0; while (m_optDesc->constraint.string_list[i] != nullptr) { tmp = getSaneComboString((unsigned char *)m_optDesc->constraint.string_list[i]); if (val == tmp) { data_ptr = (void *)m_optDesc->constraint.string_list[i]; break; } i++; } if (m_optDesc->constraint.string_list[i] == nullptr) { return false; } break; default: qDebug() << "can only handle SANE_TYPE: INT, FIXED and STRING"; return false; } writeData(data_ptr); readValue(); return true; } bool KSaneOptCombo::hasGui() { return true; } } // NameSpace KSaneIface