diff --git a/src/ksanewidget.cpp b/src/ksanewidget.cpp index 4626f3a..21114c3 100644 --- a/src/ksanewidget.cpp +++ b/src/ksanewidget.cpp @@ -1,911 +1,921 @@ /* ============================================================ * * 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) { //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++) { 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; + } + QString tmp; int i; int ret = 0; 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) { 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 ((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.h b/src/ksanewidget.h index c6b0fe7..163987e 100644 --- a/src/ksanewidget.h +++ b/src/ksanewidget.h @@ -1,325 +1,327 @@ /* ============================================================ * * This file is part of the KDE project * * Date : 2007-09-13 * Description : Sane interface for KDE * * Copyright (C) 2007-2010 by Kare Sars * Copyright (C) 2007 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 . * * ============================================================ */ #ifndef KSANE_H #define KSANE_H #include "ksane_export.h" #include /** This namespace collects all methods and classes in LibKSane. */ namespace KSaneIface { class KSaneWidgetPrivate; /** * This class provides the widget containing the scan options and the preview. * @author Kare Sars */ class KSANE_EXPORT KSaneWidget : public QWidget { Q_OBJECT friend class KSaneWidgetPrivate; public: /** This enumeration describes the type of the returned data. * The number of formats might grow, so it is wise to be prepared fro more.*/ typedef enum { FormatBlackWhite, /**< One bit per pixel 1 = black 0 = white */ FormatGrayScale8, /**< Grayscale with one byte per pixel 0 = black 255 = white */ FormatGrayScale16, /**< Grayscale withtTwo bytes per pixel. * The byte order is the one provided by libsane. */ FormatRGB_8_C, /**< Every pixel consists of three colors in the order Read, * Grean and Blue, with one byte per color (no alpha channel). */ FormatRGB_16_C, /**< Every pixel consists of three colors in the order Read, * Grean and Blue, with two bytes per color(no alpha channel). * The byte order is the one provided by libsane. */ FormatBMP, /**< The image data is returned as a BMP. */ FormatNone = 0xFFFF /**< This enumeration value should never be returned to the user */ } ImageFormat; /** @note There might come more enumerations in the future. */ typedef enum { NoError, /**< The scanning was finished successfully.*/ ErrorCannotSegment, /**< If this error status is returned libksane can not segment the * returned data. Scanning without segmentation should work. * @note segmentation is not implemented yet.*/ ErrorGeneral, /**< The error string should contain an error message. */ Information /**< There is some information to the user. */ } ScanStatus; struct DeviceInfo { QString name; /* unique device name */ QString vendor; /* device vendor string */ QString model; /* device model name */ QString type; /* device type (e.g., "flatbed scanner") */ }; /** This constructor initializes the private class variables, but the widget is left empty. * The options and the preview are added with the call to openDevice(). */ KSaneWidget(QWidget *parent = nullptr); /** Standard destructor */ ~KSaneWidget(); /** This helper method displays a dialog for selecting a scanner. The libsane * device name of the selected scanner device is returned. */ QString selectDevice(QWidget *parent = nullptr); /** * Get the list of available scanning devices. Connect to availableDevices() * which is fired once these devices are known. */ void initGetDeviceList() const; /** This method opens the specified scanner device and adds the scan options to the * KSane widget. * @param device_name is the libsane device name for the scanner to open. * @return 'true' if all goes well and 'false' if the specified scanner can not be opened. */ bool openDevice(const QString &device_name); /** This method closes the currently open scanner device. * @return 'true' if all goes well and 'false' if no device is open. */ bool closeDevice(); KSANE_DEPRECATED bool makeQImage(const QByteArray &, int, int, int, ImageFormat, QImage &); /** * This is a convenience method that can be used to create a QImage from the image data * returned by the imageReady(...) signal. * @note: If the image data has 16 bits/color the * data is truncated to 8 bits/color * since QImage does not support 16 bits/color. A warning message will be shown. * * @param data is the byte data containing the image. * @param width is the width of the image in pixels. * @param height is the height of the image in pixels. * @param bytes_per_line is the number of bytes used per line. This might include padding * and is probably only relevant for 'FormatBlackWhite'. * @param format is the KSane image format of the data. * @return This function returns the provided image data as a QImage. */ QImage toQImage(const QByteArray &data, int width, int height, int bytes_per_line, ImageFormat format); /** * This is a convenience method that can be used to create a QImage from the image data * returned by the imageReady(...) signal. * @note: If the image data has 16 bits/color the * data is truncated to 8 bits/color, but * unlike toQImage() this function will not give a warning. * * @param data is the byte data containing the image. * @param width is the width of the image in pixels. * @param height is the height of the image in pixels. * @param bytes_per_line is the number of bytes used per line. This might include padding * and is probably only relevant for 'FormatBlackWhite'. * @param format is the KSane image format of the data. * @return This function returns the provided image data as a QImage. */ QImage toQImageSilent(const QByteArray &data, int width, int height, int bytes_per_line, ImageFormat format); /** * This is a static version of toQImageSilent() method that requires dpi as additional * argument. Non-static version uses currentDPI() for it. * * @param data is the byte data containing the image. * @param width is the width of the image in pixels. * @param height is the height of the image in pixels. * @param bytes_per_line is the number of bytes used per line. This might include padding * and is probably only relevant for 'FormatBlackWhite'. * @param format is the KSane image format of the data. * @param dpi is the dpi value of the image. * @return This function returns the provided image data as a QImage. */ static QImage toQImageSilent(const QByteArray &data, int width, int height, int bytes_per_line, int dpi, ImageFormat format); /** This method returns the vendor name of the scanner (Same as make). */ QString vendor() const; /** This method returns the make name of the scanner. */ QString make() const; /** This method returns the model of the scanner. */ QString model() const; /** This method returns the current resolution of the acquired image, * in dots per inch. * @note This function should be called from the slot connected * to the imageReady signal. The connection should not be queued. * @return the resolution used for scanning or 0.0 on failure. */ float currentDPI(); /** This method returns the scan area's width in mm * @return Width of the scannable area in mm */ float scanAreaWidth(); /** This method returns the scan area's height in mm * @return Height of the scannable area in mm */ float scanAreaHeight(); /** This method sets the selection according to the given points * @note The points are defined with respect to the scan areas top-left corner in mm * @param topLeft Upper left corner of the selection (in mm) * @param bottomRight Lower right corner of the selection (in mm) */ void setSelection(QPointF topLeft, QPointF bottomRight); /** This function is used to set the preferred resolution for scanning the preview. * @param dpi is the wanted scan resolution for the preview * @note if the set value is not supported, the cloasest one is used * @note setting the value 0 means that the default calculated value should be used */ void setPreviewResolution(float dpi); /** This method reads the available parameters and their values and * returns them in a QMap (Name, value) * @param opts is a QMap with the parameter names and values. */ void getOptVals(QMap &opts); /** This method can be used to write many parameter values at once. * @param opts is a QMap with the parameter names and values. - * @return This function returns the number of successful writes. */ + * @return This function returns the number of successful writes + * or -1 if scanning is in progress. */ int setOptVals(const QMap &opts); /** This function reads one parameter value into a string. * @param optname is the name of the parameter to read. * @param value is the string representation of the value. * @return this function returns true if the read was successful. */ bool getOptVal(const QString &optname, QString &value); /** This function writes one parameter value into a string. * @param optname is the name of the parameter to write. * @param value is the string representation of the value. - * @return this function returns true if the write was successful. */ + * @return this function returns true if the write was successful and + * false if it was unsuccessful or scanning is in progress. */ bool setOptVal(const QString &optname, const QString &value); /** This function sets the label on the final scan button * @param scanLabel is the new label for the button. */ void setScanButtonText(const QString &scanLabel); /** This function sets the label on the preview button * @param previewLabel is the new label for the button. */ void setPreviewButtonText(const QString &previewLabel); /** This function can be used to enable/disable automatic selections on previews. * The default state is enabled. * @param enable specifies if the auto selection should be turned on or off. */ void enableAutoSelect(bool enable); /** This function is used to programatically collapse/restore the options. * @param collapse defines the state to set. */ void setOptionsCollapsed(bool collapse); /** This function is used hide/show the final scan button. * @param hidden defines the state to set. */ void setScanButtonHidden(bool hidden); public Q_SLOTS: /** This method can be used to cancel a scan or prevent an automatic new scan. */ void scanCancel(); /** This method can be used to start a scan (if no GUI is needed). * @note libksane may return one or more images as a result of one invocation of this slot. * If no more images are wanted scanCancel should be called in the slot handling the * imageReady signal. */ void scanFinal(); /** This method can be used to start a preview scan. */ void startPreviewScan(); Q_SIGNALS: /** * This Signal is emitted when a final scan is ready. * @param data is the byte data containing the image. * @param width is the width of the image in pixels. * @param height is the height of the image in pixels. * @param bytes_per_line is the number of bytes used per line. This might include padding * and is probably only relevant for 'FormatBlackWhite'. * @param format is the KSane image format of the data. */ void imageReady(QByteArray &data, int width, int height, int bytes_per_line, int format); /** * This signal is emitted when the scanning has ended. * @param status contains a ScanStatus status code. * @param strStatus If an error has occurred this string will contain an error message. * otherwise the string is empty. */ void scanDone(int status, const QString &strStatus); /** * This signal is emitted when the user is to be notified about something. * @note If no slot is connected to this signal the message will be displayed in a KMessageBox. * @param type contains a ScanStatus code to identify the type of message (error/info/...). * @param strStatus If an error has occurred this string will contain an error message. * otherwise the string is empty. */ void userMessage(int type, const QString &strStatus); /** * This Signal is emitted for progress information during a scan. * The GUI already has a progress bar, but if the GUI is hidden, * this can be used to display a progress bar. * @param percent is the percentage of the scan progress (0-100). */ void scanProgress(int percent); /** * This signal is emitted every time the device list is updated or * after initGetDeviceList() is called. * @param deviceList is a QList of KSaneWidget::DeviceInfo that contain the * device name, model, vendor and type of the attached scanners. * @note The list is only a snapshot of the current available devices. Devices * might be added or removed/opened after the signal is emitted. */ void availableDevices(const QList &deviceList); /** * This Signal is emitted when a hardware button is pressed. * @param optionName is the untranslated technical name of the sane-option. * @param optionLabel is the translated user visible label of the sane-option. * @param pressed indicates if the value is true or false. * @note The SANE standard does not specify hardware buttons and their behaviors, * so this signal is emitted for sane-options that behave like hardware buttons. * That is the sane-options are read-only and type boolean. The naming of hardware * buttons also differ from backend to backend. */ void buttonPressed(const QString &optionName, const QString &optionLabel, bool pressed); private: KSaneWidgetPrivate *const d; }; } // NameSpace KSaneIface #endif // SANE_WIDGET_H