diff --git a/libkookascan/kscancontrols.cpp b/libkookascan/kscancontrols.cpp index c82f442..bfa65ab 100644 --- a/libkookascan/kscancontrols.cpp +++ b/libkookascan/kscancontrols.cpp @@ -1,446 +1,452 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 2000-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "kscancontrols.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "imagefilter.h" // KScanControl - base class // ------------------------- KScanControl::KScanControl(QWidget *parent, const QString &text) : QWidget(parent) { mLayout = new QHBoxLayout(this); mLayout->setMargin(0); mText = text; if (mText.isEmpty()) { mText = i18n("(Unknown)"); } } -KScanControl::~KScanControl() {} +KScanControl::~KScanControl() +{ +} QString KScanControl::text() const { return (QString()); } -void KScanControl::setText(const QString &text) {} + +void KScanControl::setText(const QString &text) +{ +} int KScanControl::value() const { return (0); } -void KScanControl::setValue(int val) {} + +void KScanControl::setValue(int val) +{ +} QString KScanControl::label() const { return (mText + ":"); } // KScanSlider - slider, spin box and optional reset button // -------------------------------------------------------- KScanSlider::KScanSlider(QWidget *parent, const QString &text, double min, double max, bool haveStdButt, int stdValue) : KScanControl(parent, text) { init(haveStdButt); setRange(min, max, -1, stdValue); } KScanSlider::KScanSlider(QWidget *parent, const QString &text, bool haveStdButt) : KScanControl(parent, text) { init(haveStdButt); } void KScanSlider::init(bool haveStdButt) { mStdButt = nullptr; mSlider = new QSlider(Qt::Horizontal, this); // slider mSlider->setTickPosition(QSlider::TicksBelow); mSlider->setMinimumWidth(140); mLayout->addWidget(mSlider, 1); mSpinbox = new QSpinBox(this); // spin box mSpinbox->setMinimumWidth(60); mLayout->addWidget(mSpinbox); if (haveStdButt) { mStdButt = new QToolButton(this); // reset button mStdButt->setIcon(QIcon::fromTheme("edit-undo")); mLayout->addWidget(mStdButt); } - connect(mSlider, SIGNAL(valueChanged(int)), SLOT(slotSliderSpinboxChange(int))); - connect(mSpinbox, SIGNAL(valueChanged(int)), SLOT(slotSliderSpinboxChange(int))); - if (mStdButt != nullptr) { - connect(mStdButt, SIGNAL(clicked()), SLOT(slotRevertValue())); - } + connect(mSlider, &QSlider::valueChanged, this, &KScanSlider::slotSliderSpinboxChange); + connect(mSpinbox, QOverload::of(&QSpinBox::valueChanged), this, &KScanSlider::slotSliderSpinboxChange); + if (mStdButt!=nullptr) connect(mStdButt, &QPushButton::clicked, this, &KScanSlider::slotRevertValue); setFocusProxy(mSlider); setFocusPolicy(Qt::StrongFocus); } void KScanSlider::setRange(int min, int max, int step, int stdValue) { const double span = max-min; mSlider->setRange(min, max); mSlider->setTickInterval(qMax(qRound(span/10), 1)); mSpinbox->setRange(min, max); if (step==-1) // default step value { mSlider->setSingleStep(qMax(qRound(span/20), 1)); mSlider->setPageStep(qMax(qRound(span/5), 1)); mSpinbox->setSingleStep(1); } else // specified step value { mSlider->setSingleStep(step); mSlider->setPageStep(step*4); mSpinbox->setSingleStep(step); } mStdValue = qBound(stdValue, min, max); // limit default value to range mValue = mStdValue; // set current value to that mSlider->setValue(mValue); mSpinbox->setValue(mValue); if (mStdButt!=nullptr) mStdButt->setToolTip(i18n("Reset this setting to its standard value, %1", QString::number(mStdValue))); } void KScanSlider::setValue(int val) { if (val == mValue) { return; // avoid recursive signals } mValue = val; int spin = mSpinbox->value(); if (spin != val) { mSpinbox->blockSignals(true); mSpinbox->setValue(val); // track in spin box mSpinbox->blockSignals(false); } int slid = mSlider->value(); if (slid != val) { mSlider->blockSignals(true); mSlider->setValue(val); // track in slider mSlider->blockSignals(false); } } int KScanSlider::value() const { return (mValue); } void KScanSlider::slotSliderSpinboxChange(int val) { setValue(val); emit settingChanged(val); } void KScanSlider::slotRevertValue() { // only connected if button exists slotSliderSpinboxChange(mStdValue); } // KScanStringEntry - free text entry field // ---------------------------------------- KScanStringEntry::KScanStringEntry(QWidget *parent, const QString &text) : KScanControl(parent, text) { mEntry = new QLineEdit(this); mLayout->addWidget(mEntry); - connect(mEntry, SIGNAL(textChanged(QString)), SIGNAL(settingChanged(QString))); - connect(mEntry, SIGNAL(returnPressed()), SIGNAL(returnPressed())); + connect(mEntry, &QLineEdit::textChanged, this, QOverload::of(&KScanStringEntry::settingChanged)); + connect(mEntry, &QLineEdit::returnPressed, this, &KScanStringEntry::returnPressed); setFocusProxy(mEntry); setFocusPolicy(Qt::StrongFocus); } QString KScanStringEntry::text() const { return (mEntry->text()); } void KScanStringEntry::setText(const QString &text) { if (text == mEntry->text()) { return; // avoid recursive signals } mEntry->setText(text); } // KScanNumberEntry - number entry field // ------------------------------------- KScanNumberEntry::KScanNumberEntry(QWidget *parent, const QString &text) : KScanControl(parent, text) { mEntry = new QLineEdit(this); mEntry->setValidator(new QIntValidator); mLayout->addWidget(mEntry); - connect(mEntry, SIGNAL(textChanged(QString)), SLOT(slotTextChanged(QString))); - connect(mEntry, SIGNAL(returnPressed()), SIGNAL(returnPressed())); + connect(mEntry, &QLineEdit::textChanged, this, QOverload::of(&KScanNumberEntry::settingChanged)); + connect(mEntry, &QLineEdit::returnPressed, this, &KScanNumberEntry::returnPressed); setFocusProxy(mEntry); setFocusPolicy(Qt::StrongFocus); } int KScanNumberEntry::value() const { return (mEntry->text().toInt()); } void KScanNumberEntry::setValue(int i) { mEntry->setText(QString::number(i)); } void KScanNumberEntry::slotTextChanged(const QString &s) { emit settingChanged(s.toInt()); } // KScanCheckbox - on/off option // ----------------------------- KScanCheckbox::KScanCheckbox(QWidget *parent, const QString &text) : KScanControl(parent, text) { mCheckbox = new QCheckBox(text, this); mLayout->addWidget(mCheckbox); - connect(mCheckbox, SIGNAL(stateChanged(int)), SIGNAL(settingChanged(int))); + connect(mCheckbox, &QCheckBox::stateChanged, this, QOverload::of(&KScanCheckbox::settingChanged)); setFocusProxy(mCheckbox); setFocusPolicy(Qt::StrongFocus); } int KScanCheckbox::value() const { return ((int) mCheckbox->isChecked()); } void KScanCheckbox::setValue(int i) { mCheckbox->setChecked((bool) i); } QString KScanCheckbox::label() const { return (QString()); } // KScanCombo - combo box with list of options // ------------------------------------------- // // This control (and currently only this control) is special, because the // item text is set to the translated value of the option values. But these // values need to reported back unchanged, so the untranslated form is stored // in the itemData and the translated form is seen by the user as itemText. // Any access needs to only set or report the itemData, never the itemText, // which is why the activated(QString) signal cannot be used directly. KScanCombo::KScanCombo(QWidget *parent, const QString &text) : KScanControl(parent, text) { mCombo = new QComboBox(this); mLayout->addWidget(mCombo); - connect(mCombo, SIGNAL(activated(int)), SLOT(slotActivated(int))); + connect(mCombo, QOverload::of(&QComboBox::activated), this, &KScanCombo::slotActivated); setFocusProxy(mCombo); setFocusPolicy(Qt::StrongFocus); } void KScanCombo::setList(const QList &list) { // An optimisation, which may turn out to be not a valid one: // only update the combo box if the number of items has changed. if (list.count()==mCombo->count()) return; //qDebug() << "count" << mCombo->count() << "->" << list.count() << "=" << list; const QString cur = text(); // get current setting const bool bs = mCombo->blockSignals(true); mCombo->clear(); foreach (const QByteArray &item, list) { // See the KI18N Programmer's Guide, "Connecting to Catalogs in Library Code" mCombo->addItem(ki18n(item.constData()).toString("sane-backends"), item); } mCombo->blockSignals(bs); if (!cur.isEmpty()) setText(cur); // try to restore old setting } void KScanCombo::setText(const QString &text) { int i = mCombo->findData(text); // find item with that text if (i == -1) return; // ignore if not present if (i == mCombo->currentIndex()) return; // avoid recursive signals mCombo->setCurrentIndex(i); } void KScanCombo::setIcon(const QIcon &icon, const char *ent) { int i = mCombo->findData(ent); // find item with that text if (i != -1) mCombo->setItemIcon(i, icon); } QString KScanCombo::text() const { return (textAt(mCombo->currentIndex())); } void KScanCombo::setValue(int i) { mCombo->setCurrentIndex(i); } QString KScanCombo::textAt(int i) const { return (i == -1 ? QString() : mCombo->itemData(i).toString()); } int KScanCombo::count() const { return (mCombo->count()); } void KScanCombo::slotActivated(int i) { emit settingChanged(i); emit settingChanged(textAt(i)); } // KScanFileRequester - standard URL requester // ------------------------------------------- KScanFileRequester::KScanFileRequester(QWidget *parent, const QString &text) : KScanControl(parent, text) { mEntry = new KUrlRequester(this); mLayout->addWidget(mEntry); QString filter = i18n("*.pnm *.pbm *.pgm *.ppm|PNM Image Files"); filter += '\n'+ImageFilter::kdeFilter(ImageFilter::Reading); mEntry->setFilter(filter); - connect(mEntry, SIGNAL(textChanged(QString)), SIGNAL(settingChanged(QString))); - connect(mEntry, SIGNAL(returnPressed()), SIGNAL(returnPressed())); + connect(mEntry, QOverload::of(&KUrlRequester::textChanged), this, QOverload::of(&KScanFileRequester::settingChanged)); + connect(mEntry, QOverload<>::of(&KUrlRequester::returnPressed), this, &KScanStringEntry::returnPressed); setFocusProxy(mEntry); setFocusPolicy(Qt::StrongFocus); } QString KScanFileRequester::text() const { return (mEntry->url().url()); } void KScanFileRequester::setText(const QString &text) { if (text == mEntry->url().url()) { return; // avoid recursive signals } mEntry->setUrl(QUrl::fromLocalFile(text)); } // KScanGroup - group separator // ---------------------------- KScanGroup::KScanGroup(QWidget *parent, const QString &text) : KScanControl(parent, text) { mGroup = new QGroupBox(text, this); mGroup->setFlat(true); mLayout->addWidget(mGroup); } QString KScanGroup::label() const { return (QString()); } // KScanPushButton - action button // ------------------------------- KScanPushButton::KScanPushButton(QWidget *parent, const QString &text) : KScanControl(parent, text) { mButton = new QPushButton(text, this); mLayout->addWidget(mButton); - connect(mButton, SIGNAL(clicked()), SIGNAL(returnPressed())); + connect(mButton, &QPushButton::clicked, this, &KScanPushButton::returnPressed); } QString KScanPushButton::label() const { return (QString()); } diff --git a/libkookascan/kscancontrols.h b/libkookascan/kscancontrols.h index f4e33c5..709c14e 100644 --- a/libkookascan/kscancontrols.h +++ b/libkookascan/kscancontrols.h @@ -1,484 +1,484 @@ /* This file is part of the KDE Project Copyright (C) 2000 Klaas Freitag Copyright (C) 2010 Jonathan Marten This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KSCANCONTROLS_H #define KSCANCONTROLS_H #include "kookascan_export.h" #include class QHBoxLayout; class QToolButton; class QSpinBox; class QComboBox; class QCheckBox; class QSlider; class QLineEdit; class QGroupBox; class QPushButton; class KUrlRequester; /** * An abstract base class representing a GUI for a single scan * parameter control. All scanner controls are one of the subclasses * of this, their precise appearance and operation depending on the * SANE type of the parameter. */ class KScanControl : public QWidget { Q_OBJECT public: /** * The internal type of the control (regardless of GUI appearance). * * A @c Text control maintains a string value, a @c Number control a * numeric value. @c Group and @c Button controls do not have values. */ enum ControlType { Text, Number, Group, Button }; /** * Creates a control. * * @param parent The parent widget * @param text Text label for control */ explicit KScanControl(QWidget *parent, const QString &text); /** - * Destructs the control and any child widgets that it uses. + * Destroys the control and any child widgets that it uses. */ virtual ~KScanControl(); /** * Control type. * * @return the control type * @see ControlType */ virtual KScanControl::ControlType type() const = 0; /** * Set the control's text value, for controls of type @c Text. * Ignored for controls of type @c Number or @c Group. * * @param text The new text value */ virtual void setText(const QString &text); /** * Get the control's current text value. * * @return the text value, or @c QString() for a @c Number * or @c Group control. */ virtual QString text() const; /** * Set the control's numeric value, for controls of type @c Number. * Ignored for controls of type @c Text or @c Group. * * @param val The new numeric value */ virtual void setValue(int val); /** * Get the control's current numeric value. * * @return the numeric value, or @c 0 for a @c Text * or @c Group control. */ virtual int value() const; /** * Get a descriptive label text for the control. * * For all controls except check boxes, this is the original @p text * parameter to the constructor with a ":" appended. For a check box, * this is null (a check box has its own label in the usual place, * on the right). * * @return the label string */ virtual QString label() const; protected: QHBoxLayout *mLayout; QString mText; signals: /** * The setting of a @c Text control has changed. * * @param text The new text setting */ void settingChanged(const QString &text); /** * The setting of a @c Number control has changed. * * @param val The new numeric setting */ void settingChanged(int val); /** * The Return/Enter key has been pressed in a @c Text or * @c Number entry field, or a @c Button control has been pressed. * @c Group control types do not provide this signal (nor either of * the above). */ void returnPressed(); }; /** * A slider combined with a spin box, providing the possibility of either * selecting a value with the slider or entering a precise value in the * spin box. There can also optionally be a 'reset' button which returns * the setting to a default value. * * @see QSlider, QSpinBox */ class KOOKASCAN_EXPORT KScanSlider : public KScanControl { Q_OBJECT public: /** * Creates the control. * * @param parent parent widget * @param text descriptive label for the control * @param min minimum slider value * @param max maximum slider value * @param haveStdButt if @c true, the 'reset' button will be present * @param stdValue the value to which the 'reset' button resets the control */ Q_DECL_DEPRECATED_X("Use KScanSlider(QWidget *,const QString &, bool) then setRange()") KScanSlider(QWidget *parent, const QString &text, double min, double max, bool haveStdButt = false, int stdValue = 0); /** * Creates the control. * * @param parent parent widget * @param text descriptive label for the control * @param haveStdButt if @c true, the 'reset' button will be present */ KScanSlider(QWidget *parent, const QString &text, bool haveStdButt = false); KScanControl::ControlType type() const override { return (KScanControl::Number); } int value() const override; void setValue(int val) override; QSpinBox *spinBox() const { return (mSpinbox); } /** * Sets the allowed range and step for the slider. * * @param min minimum slider value * @param max maximum slider value * @param step value step, or -1 for a default setting * @param stdValue the value to which the 'reset' button resets the control * * @note The current control value is set to @p stdValue. */ void setRange(int min, int max, int step = -1, int stdValue = 0); protected slots: void slotSliderSpinboxChange(int val); void slotRevertValue(); private: void init(bool haveStdButt); private: QSlider *mSlider; QSpinBox *mSpinbox; QToolButton *mStdButt; int mValue; int mStdValue; }; /** * A free text entry field. * * @see QLineEdit */ class KOOKASCAN_EXPORT KScanStringEntry : public KScanControl { Q_OBJECT public: /** * Creates the control. * * @param parent parent widget * @param text descriptive label for the control */ KScanStringEntry(QWidget *parent, const QString &text); KScanControl::ControlType type() const override { return (KScanControl::Text); } QString text() const override; void setText(const QString &text) override; private: QLineEdit *mEntry; }; /** * A numeric entry field. * * @see QLineEdit */ class KOOKASCAN_EXPORT KScanNumberEntry : public KScanControl { Q_OBJECT public: /** * Creates the control. * * @param parent parent widget * @param text descriptive label for the control */ KScanNumberEntry(QWidget *parent, const QString &text); KScanControl::ControlType type() const override { return (KScanControl::Number); } int value() const override; void setValue(int i) override; protected slots: void slotTextChanged(const QString &s); private: QLineEdit *mEntry; }; /** * A check box for an on/off option. * * @see QCheckBox */ class KOOKASCAN_EXPORT KScanCheckbox : public KScanControl { Q_OBJECT public: /** * Creates the control. * * @param parent parent widget * @param text descriptive label for the control */ KScanCheckbox(QWidget *parent, const QString &text); KScanControl::ControlType type() const override { return (KScanControl::Number); } int value() const override; void setValue(int i) override; QString label() const override; private: QCheckBox *mCheckbox; }; /** * A combo box for a list of options. * * @see QComboBox */ class KOOKASCAN_EXPORT KScanCombo : public KScanControl { Q_OBJECT public: /** * Creates the control. * * @param parent parent widget * @param text descriptive label for the control */ KScanCombo(QWidget *parent, const QString &text); KScanControl::ControlType type() const override { return (KScanControl::Text); } QString text() const override; void setText(const QString &text) override; void setValue(int i) override; /** * Populate the combo box with a list of values. * * @param list list of options to fill the combo box */ void setList(const QList &list); /** * Get the text at a specified index in the combo box. * * @param i the requested index * @return the text at that index */ QString textAt(int i) const; /** * Count how many combo box entries there are. * * @return the number of entries */ int count() const; /** * Set an icon for an item in the combo box. * * @param pix the pixmap to set * @param ent the entry for which the pixmap should be set */ void setIcon(const QIcon &pix, const char *ent); protected slots: void slotActivated(int i); private: QComboBox *mCombo; }; /** * A standard URL requester for a file name. * * @see KUrlRequester */ class KOOKASCAN_EXPORT KScanFileRequester : public KScanControl { Q_OBJECT public: /** * Creates the control. * * @param parent parent widget * @param text descriptive label for the control */ KScanFileRequester(QWidget *parent, const QString &text); KScanControl::ControlType type() const override { return (KScanControl::Text); } QString text() const override; void setText(const QString &text) override; private: KUrlRequester *mEntry; }; /** * A line separator between option groups. * * @see QGroupBox */ class KOOKASCAN_EXPORT KScanGroup : public KScanControl { Q_OBJECT public: /** * Creates the control. * * @param parent parent widget * @param text descriptive label for the control */ KScanGroup(QWidget *parent, const QString &text); KScanControl::ControlType type() const override { return (KScanControl::Group); } QString label() const override; private: QGroupBox *mGroup; }; /** * A button to perform an action. * * @see QPushButton */ class KOOKASCAN_EXPORT KScanPushButton : public KScanControl { Q_OBJECT public: /** * Creates the control. * * @param parent parent widget * @param text descriptive label for the control */ KScanPushButton(QWidget *parent, const QString &text); KScanControl::ControlType type() const override { return (KScanControl::Button); } QString label() const override; private: QPushButton *mButton; }; #endif // KSCANCONTROLS_H diff --git a/libkookascan/kscanoption.cpp b/libkookascan/kscanoption.cpp index 50bf061..9321639 100644 --- a/libkookascan/kscanoption.cpp +++ b/libkookascan/kscanoption.cpp @@ -1,1079 +1,1079 @@ /* This file is part of the KDE Project Copyright (C) 2000 Klaas Freitag This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kscanoption.h" #include #include #include #include #include #include #include #include #include extern "C" { #include #include } #include "kgammatable.h" #include "kscandevice.h" #include "kscancontrols.h" #include "kscanoptset.h" // Debugging options #undef DEBUG_MEM #undef DEBUG_GETSET #define DEBUG_APPLY #undef DEBUG_RELOAD // This defines the possible resolutions that will be shown by the combo. // Only resolutions from this list falling within the scanner's allowed range // will be included. static const int resList[] = { 50, 75, 100, 150, 200, 300, 600, 900, 1200, 1800, 2400, 4800, 9600, 0 }; KScanOption::KScanOption(const QByteArray &name, KScanDevice *scandev) { mScanDevice = scandev; if (!initOption(name)) { //qDebug() << "initOption for" << name << "failed!"; return; } if (!mIsReadable) return; // no value to read if (mBuffer.isNull()) return; // no buffer for value // read initial value from the scanner SANE_Status sanestat = sane_control_option(mScanDevice->scannerHandle(), mIndex, SANE_ACTION_GET_VALUE, mBuffer.data(), nullptr); if (sanestat==SANE_STATUS_GOOD) mBufferClean = false; } static bool shouldBePriorityOption(const QByteArray &name) { return (name=="source"); } bool KScanOption::initOption(const QByteArray &name) { mDesc = nullptr; mControl = nullptr; mIsGroup = false; mIsReadable = true; mIsPriority = shouldBePriorityOption(name); mWidgetType = KScanOption::Invalid; if (name.isEmpty()) return (false); mName = name; // Look up the option (which must already exist in the map) by name. // // The default-constructed index is 0 for an invalid option, this is OK // because although a SANE option with that index exists it is never // requested by name. mIndex = mScanDevice->getOptionIndex(mName); if (mIndex<=0) { //qDebug() << "no option descriptor for" << mName; return (false); } mDesc = sane_get_option_descriptor(mScanDevice->scannerHandle(), mIndex); if (mDesc==nullptr) return (false); mBuffer.resize(0); mBufferClean = true; mApplied = false; if (mDesc->type==SANE_TYPE_GROUP) mIsGroup = true; if (mIsGroup || mDesc->type==SANE_TYPE_BUTTON) mIsReadable = false; if (!(mDesc->cap & SANE_CAP_SOFT_DETECT)) mIsReadable = false; mGammaTable = nullptr; // for recording gamma values mWidgetType = resolveWidgetType(); // work out the type of widget allocForDesc(); // allocate initial buffer return (true); } KScanOption::~KScanOption() { #ifdef DEBUG_MEM if (!mBuffer.isNull()) //qDebug() << "Freeing" << mBuffer.size() << "bytes for" << mName; #endif // TODO: need to delete mControl here? } void KScanOption::slotWidgetChange(const QString &t) { set(t.toUtf8()); emit guiChange(this); } void KScanOption::slotWidgetChange(int i) { set(i); emit guiChange(this); } void KScanOption::slotWidgetChange() { set(1); emit guiChange(this); } void KScanOption::updateList() { KScanCombo *combo = qobject_cast(mControl); if (combo==nullptr) return; QList list = getList(); combo->setList(list); } /* called on a widget change, if a widget was created. */ void KScanOption::redrawWidget() { if (!isValid() || !isReadable() || mControl==nullptr || mBuffer.isNull()) return; KScanControl::ControlType type = mControl->type(); if (type==KScanControl::Number) // numeric control { int i = 0; if (get(&i)) mControl->setValue(i); } else if (type==KScanControl::Text) // text control { mControl->setText(get()); } } /* Get and update the current setting from the scanner */ void KScanOption::reload() { if (mControl!=nullptr) { if (isGroup()) { mControl->setEnabled(true); // always enabled return; // no more to do } if (!isActive()) { #ifdef DEBUG_RELOAD qDebug() << "not active" << mName; #endif mControl->setEnabled(false); } else if (!isSoftwareSettable()) { #ifdef DEBUG_RELOAD qDebug() << "not settable" << mName; #endif mControl->setEnabled(false); } else mControl->setEnabled(true); } if (!isReadable()) { #ifdef DEBUG_RELOAD qDebug() << "not readable" << mName; #endif return; } if (mBuffer.isNull()) // first get mem if needed { qDebug() << "need to allocate now"; allocForDesc(); // allocate the buffer now } if (!isActive()) return; if (mDesc->size>mBuffer.size()) { //qDebug() << "buffer too small for" << mName << "type" << mDesc->type //<< "size" << mBuffer.size() << "need" << mDesc->size; allocForDesc(); // grow the buffer } // Starting with SANE 1.0.20, the 'test' device needs this dummy call of // sane_get_option_descriptor() before any use of sane_control_option(), // otherwise the latter fails with a SANE_STATUS_INVAL. From the master // repository at http://anonscm.debian.org/gitweb/?p=sane/sane-backends.git: // // author m. allan noah // Thu, 26 Jun 2008 13:14:23 +0000 (13:14 +0000) // commit 8733651c4b07ac6ccbcee0d39eccca0c08057729 // test backend checks for options that have not been loaded before being controlled // // I'm hoping that this is not in general an expensive operation - it certainly // is not so for the 'test' device and a sample of others - so it should not be // necessary to conditionalise it for that device only. const SANE_Option_Descriptor *desc = sane_get_option_descriptor(mScanDevice->scannerHandle(), mIndex); if (desc==nullptr) return; // should never happen SANE_Status sanestat = sane_control_option(mScanDevice->scannerHandle(), mIndex, SANE_ACTION_GET_VALUE, mBuffer.data(), nullptr); if (sanestat!=SANE_STATUS_GOOD) { qWarning() << "Can't get value for" << mName << "status" << sane_strstatus(sanestat); return; } updateList(); // if range changed, update GUI #ifdef DEBUG_RELOAD qDebug() << "reloaded" << mName; #endif mBufferClean = false; } bool KScanOption::apply() { int sane_result = 0; bool reload = false; #ifdef DEBUG_APPLY QString debug = QString("option '%1'").arg(mName.constData()); #endif // DEBUG_APPLY SANE_Status sanestat = SANE_STATUS_GOOD; // See comment in reload() above const SANE_Option_Descriptor *desc = sane_get_option_descriptor(mScanDevice->scannerHandle(), mIndex); if (desc==nullptr) return (false); // should never happen if (mName==SANE_NAME_PREVIEW || mName==SANE_NAME_SCAN_MODE) { sanestat = sane_control_option(mScanDevice->scannerHandle(), mIndex, SANE_ACTION_SET_AUTO, nullptr, &sane_result); /* No return here, please! Carsten, does it still work than for you? */ } if (!isInitialised() || mBuffer.isNull()) { #ifdef DEBUG_APPLY debug += " nobuffer"; #endif // DEBUG_APPLY if (!isAutoSettable()) goto ret; #ifdef DEBUG_APPLY debug += " auto"; #endif // DEBUG_APPLY sanestat = sane_control_option(mScanDevice->scannerHandle(), mIndex, SANE_ACTION_SET_AUTO, nullptr, &sane_result); } else { if (!isActive()) { #ifdef DEBUG_APPLY debug += " notactive"; #endif // DEBUG_APPLY goto ret; } else if (!isSoftwareSettable()) { #ifdef DEBUG_APPLY debug += " notsettable"; #endif // DEBUG_APPLY goto ret; } else { sanestat = sane_control_option(mScanDevice->scannerHandle(), mIndex, SANE_ACTION_SET_VALUE, mBuffer.data(), &sane_result); } } if (sanestat!=SANE_STATUS_GOOD) { //qDebug() << "apply" << mName << "failed, SANE status" << sane_strstatus(sanestat); return (false); } #ifdef DEBUG_APPLY debug += QString(" -> '%1'").arg(get().constData()); #endif // DEBUG_APPLY if (sane_result & SANE_INFO_RELOAD_OPTIONS) { #ifdef DEBUG_APPLY debug += " reload"; #endif // DEBUG_APPLY reload = true; } #ifdef DEBUG_APPLY if (sane_result & SANE_INFO_INEXACT) debug += " inexact"; #endif // DEBUG_APPLY mApplied = true; ret: #ifdef DEBUG_APPLY qDebug() << qPrintable(debug); // no quotes, please #endif // DEBUG_APPLY return (reload); } // The name of the option is checked here to detect options which are // a resolution or a gamma table, and therefore are to be treated // specially. This should hopefully be more reliable then the earlier // heuristics. KScanOption::WidgetType KScanOption::resolveWidgetType() const { if (!isValid()) return (KScanOption::Invalid); KScanOption::WidgetType ret; switch (mDesc->type) { case SANE_TYPE_BOOL: ret = KScanOption::Bool; break; case SANE_TYPE_INT: case SANE_TYPE_FIXED: if (qstrcmp(mDesc->name, SANE_NAME_SCAN_RESOLUTION)==0 || qstrcmp(mDesc->name, SANE_NAME_SCAN_X_RESOLUTION)==0 || qstrcmp(mDesc->name, SANE_NAME_SCAN_Y_RESOLUTION)==0) { ret = KScanOption::Resolution; if (mDesc->unit!=SANE_UNIT_DPI) qDebug() << "expected" << mName << "unit" << mDesc->unit << "to be DPI"; } else if (qstrcmp(mDesc->name, SANE_NAME_GAMMA_VECTOR)==0 || qstrcmp(mDesc->name, SANE_NAME_GAMMA_VECTOR_R)==0 || qstrcmp(mDesc->name, SANE_NAME_GAMMA_VECTOR_G)==0 || qstrcmp(mDesc->name, SANE_NAME_GAMMA_VECTOR_B)==0) { ret = KScanOption::GammaTable; if (mDesc->size!=sizeof(SANE_Byte)) qDebug() << "expected" << mName << "size" << mDesc->size << "to be BYTE"; } else if (mDesc->constraint_type==SANE_CONSTRAINT_RANGE) ret = KScanOption::Range; else if (mDesc->constraint_type==SANE_CONSTRAINT_WORD_LIST) ret = KScanOption::StringList; else if (mDesc->constraint_type==SANE_CONSTRAINT_NONE) ret = KScanOption::SingleValue; else ret = KScanOption::Invalid; break; case SANE_TYPE_STRING: if (qstrcmp(mDesc->name, SANE_NAME_FILE)==0) ret = KScanOption::File; else if (mDesc->constraint_type==SANE_CONSTRAINT_STRING_LIST) ret = KScanOption::StringList; else ret = KScanOption::String; break; case SANE_TYPE_BUTTON: ret = KScanOption::Button; break; case SANE_TYPE_GROUP: ret = KScanOption::Group; break; default: qDebug() << "unsupported SANE type" << mDesc->type; ret = KScanOption::Invalid; break; } #ifdef DEBUG_GETSET qDebug() << "for SANE type" << mDesc->type << "returning" << ret; #endif return (ret); } bool KScanOption::set(int val) { if (!isValid() || mBuffer.isNull()) return (false); #ifdef DEBUG_GETSET qDebug() << "Setting" << mName << "to" << val; #endif int word_size; QVector qa; SANE_Word sw; switch (mDesc->type) { case SANE_TYPE_BUTTON: // Activate a button case SANE_TYPE_BOOL: // Assign a Boolean value sw = (val ? SANE_TRUE : SANE_FALSE); mBuffer = QByteArray(((const char *) &sw),sizeof(SANE_Word)); break; case SANE_TYPE_INT: // Fill the whole buffer with that value word_size = mDesc->size/sizeof(SANE_Word); qa.resize(word_size); sw = static_cast(val); qa.fill(sw); mBuffer = QByteArray(((const char *) qa.data()),mDesc->size); break; case SANE_TYPE_FIXED: // Fill the whole buffer with that value word_size = mDesc->size/sizeof(SANE_Word); qa.resize(word_size); sw = SANE_FIX(static_cast(val)); qa.fill(sw); mBuffer = QByteArray(((const char *) qa.data()),mDesc->size); break; default: //qDebug() << "Can't set" << mName << "with this type"; return (false); } mBufferClean = false; return (true); } bool KScanOption::set(double val) { if (!isValid() || mBuffer.isNull()) return (false); #ifdef DEBUG_GETSET qDebug() << "Setting" << mName << "to" << val; #endif int word_size; QVector qa; SANE_Word sw; switch (mDesc->type) { case SANE_TYPE_BOOL: // Assign a Boolean value sw = (val>0 ? SANE_TRUE : SANE_FALSE); mBuffer = QByteArray(((const char *) &sw),sizeof(SANE_Word)); break; case SANE_TYPE_INT: // Fill the whole buffer with that value word_size = mDesc->size/sizeof(SANE_Word); qa.resize(word_size); sw = static_cast(val); qa.fill(sw); mBuffer = QByteArray(((const char *) qa.data()),mDesc->size); break; case SANE_TYPE_FIXED: // Fill the whole buffer with that value word_size = mDesc->size/sizeof(SANE_Word); qa.resize(word_size); sw = SANE_FIX(val); qa.fill(sw); mBuffer = QByteArray(((const char *) qa.data()),mDesc->size); break; default: //qDebug() << "Can't set" << mName << "with this type"; return (false); } mBufferClean = false; return (true); } bool KScanOption::set(const int *val, int size) { if (!isValid() || mBuffer.isNull()) return (false); if (val==nullptr) return (false); #ifdef DEBUG_GETSET qDebug() << "Setting" << mName << "of size" << size; #endif int offset = 0; int word_size = mDesc->size/sizeof(SANE_Word); QVector qa(1+word_size); /* add 1 in case offset is needed */ #if 0 if( mDesc->constraint_type == SANE_CONSTRAINT_WORD_LIST ) { /* That means that the first entry must contain the size */ //qDebug() << "Size" << size << "word_size" << word_size << "mDescr-size"<< mDesc->size; qa[0] = (SANE_Word) 1+size; //qDebug() << "set length field to" << qa[0]; offset = 1; } #endif switch (mDesc->type) { case SANE_TYPE_INT: for (int i = 0; isize; if (offset) copybyte += sizeof(SANE_Word); //kdDebug(29000) << "Copying " << copybyte << " byte to options buffer" << endl; mBuffer = QByteArray(((const char *) qa.data()),copybyte); mBufferClean = false; return (true); } bool KScanOption::set(const QByteArray &buf) { if (!isValid() || mBuffer.isNull()) return (false); #ifdef DEBUG_GETSET qDebug() << "Setting" << mName << "to" << buf; #endif int val; int origSize; // Check whether the string value looks like a gamma table specification. // If it is, then convert it to a gamma table and set that. KGammaTable gt; if (gt.setFromString(buf)) { #ifdef DEBUG_GETSET qDebug() << "is a gamma table"; #endif return (set(>)); } /* On String-type the buffer gets malloced in Constructor */ switch (mDesc->type) { case SANE_TYPE_STRING: origSize = mBuffer.size(); mBuffer = QByteArray(buf.data(),(buf.length()+1)); mBuffer.resize(origSize); // restore original size break; case SANE_TYPE_INT: case SANE_TYPE_FIXED: bool ok; val = buf.toInt(&ok); if (ok) set(&val,1); else { //qDebug() << "Conversion of string value" << buf << "failed!"; return (false); } break; case SANE_TYPE_BOOL: val = (buf=="true") ? 1 : 0; set(val); break; default: //qDebug() << "Can't set" << mName << "with this type"; return (false); } mBufferClean = false; return (true); } // The parameter here must be 'const'. // Otherwise, a call of set() with a 'const KGammaTable *' argument appears // to be silently resolved to a call of set(int) without warning. bool KScanOption::set(const KGammaTable *gt) { if (!isValid() || mBuffer.isNull()) return (false); // Remember the set values if (mGammaTable!=nullptr) delete mGammaTable; mGammaTable = new KGammaTable(*gt); int size = mDesc->size/sizeof(SANE_Word); // size of scanner gamma table #ifdef DEBUG_GETSET qDebug() << "Setting gamma table for" << mName << "size" << size << "to" << gt->toString(); #endif const int *run = mGammaTable->getTable(size); // get table of that size QVector qa(size); // converted to SANE values switch (mDesc->type) { case SANE_TYPE_INT: for (int i = 0; i(run[i]); break; case SANE_TYPE_FIXED: for (int i = 0; i(run[i])); break; default: //qDebug() << "Can't set" << mName << "with this type"; return (false); } mBuffer = QByteArray(((const char *) (qa.constData())), mDesc->size); mBufferClean = false; return (true); } bool KScanOption::get(int *val) const { if (!isValid() || mBuffer.isNull()) return (false); SANE_Word sane_word; double d; switch (mDesc->type) { case SANE_TYPE_BOOL: /* Buffer has a SANE_Word */ sane_word = *((SANE_Word *) mBuffer.data()); *val = (sane_word==SANE_TRUE ? 1 : 0); break; case SANE_TYPE_INT: /* reading just the first is OK */ sane_word = *((SANE_Word *) mBuffer.data()); *val = sane_word; break; case SANE_TYPE_FIXED: /* reading just the first is OK */ d = SANE_UNFIX(*((SANE_Word *) mBuffer.data())); *val = static_cast(d); break; default: //qDebug() << "Can't get" << mName << "as this type"; return (false); } #ifdef DEBUG_GETSET qDebug() << "Returning" << mName << "as" << *val; #endif return (true); } QByteArray KScanOption::get() const { if (!isValid() || mBuffer.isNull()) return (""); QByteArray retstr; SANE_Word sane_word; /* Handle gamma-table correctly */ if (mWidgetType==KScanOption::GammaTable) { if (mGammaTable!=nullptr) retstr = mGammaTable->toString().toLocal8Bit(); } else { switch (mDesc->type) { case SANE_TYPE_BOOL: sane_word = *((SANE_Word *) mBuffer.data()); retstr = (sane_word==SANE_TRUE ? "true" : "false"); break; case SANE_TYPE_STRING: retstr = (const char *) mBuffer.data(); break; case SANE_TYPE_INT: sane_word = *((SANE_Word *) mBuffer.data()); retstr.setNum(sane_word); break; case SANE_TYPE_FIXED: sane_word = (SANE_Word) SANE_UNFIX(*((SANE_Word *) mBuffer.data())); retstr.setNum(sane_word); break; default: //qDebug() << "Can't get" << mName << "as this type"; retstr = "?"; break; } } #ifdef DEBUG_GETSET qDebug() << "Returning" << mName << "as" << retstr; #endif return (retstr); } bool KScanOption::get(KGammaTable *gt) const { if (mGammaTable==nullptr) return (false); // has not been set gt->setAll(mGammaTable->getGamma(), mGammaTable->getBrightness(), mGammaTable->getContrast()); #ifdef DEBUG_GETSET qDebug() << "Returning" << mName << "as" << gt->toString(); #endif return (true); } QList KScanOption::getList() const { const char **sstring = nullptr; QList strList; if (mDesc==nullptr) return (strList); if (mDesc->constraint_type==SANE_CONSTRAINT_STRING_LIST) { sstring = (const char **)mDesc->constraint.string_list; while (*sstring!=nullptr) { strList.append(*sstring); sstring++; } } else if (mDesc->constraint_type==SANE_CONSTRAINT_WORD_LIST) { const SANE_Int *sint = mDesc->constraint.word_list; const int amount_vals = sint[0]; QString s; for (int i = 1; i<=amount_vals; i++) { if (mDesc->type==SANE_TYPE_FIXED) s = QString::number(SANE_UNFIX(sint[i]), 'f'); else s = QString::number(sint[i]); strList.append(s.toLocal8Bit()); } } else if (mDesc->constraint_type==SANE_CONSTRAINT_RANGE && mWidgetType==KScanOption::Resolution) { double min,max; int imin,imax; getRange( &min, &max); imin = static_cast(min); imax = static_cast(max); for (const int *ip = resList; *ip!=0; ++ip) { if (*ipimax) continue; strList.append(QString::number(*ip).toLocal8Bit()); } } return (strList); } bool KScanOption::getRange(double *minp, double *maxp, double *quantp) const { if (mDesc==nullptr) return (false); double min = 0.0; double max = 0.0; double quant = -1; if (mDesc->constraint_type==SANE_CONSTRAINT_RANGE) { const SANE_Range *r = mDesc->constraint.range; if (mDesc->type==SANE_TYPE_FIXED) { min = SANE_UNFIX(r->min); max = SANE_UNFIX(r->max); quant = SANE_UNFIX(r->quant); } else { min = r->min; max = r->max; quant = r->quant; } } else if (mDesc->constraint_type==SANE_CONSTRAINT_WORD_LIST) { // Originally done in KScanOption::getRangeFromList() const SANE_Int *wl = mDesc->constraint.word_list; const int num = wl[0]; double value; for (int i = 1; i<=num; ++i) { if (mDesc->type==SANE_TYPE_FIXED) value = SANE_UNFIX(wl[i]); else value = wl[i]; if (i==1 || valuemax) max = value; } if (num>=2) quant = (max-min)/(num-1); // synthesise from total range } else { //qDebug() << "Not a range type" << mDesc->name; return (false); } *minp = min; *maxp = max; if (quantp!=nullptr) *quantp = quant; return (true); } KScanControl *KScanOption::createWidget(QWidget *parent) { if (!isValid()) { //qDebug() << "Option is not valid!"; return (nullptr); } delete mControl; mControl = nullptr; // dispose of the old control if (mDesc!=nullptr) mText = i18n(mDesc->title); //qDebug() << "type" << mWidgetType << "text" << mText; KScanControl *w = nullptr; switch (mWidgetType) { case KScanOption::Bool: // toggle button w = new KScanCheckbox(parent, mText); break; case KScanOption::SingleValue: // numeric entry w = new KScanNumberEntry(parent, mText); break; case KScanOption::Range: // slider and spinbox { KScanSlider *kss = new KScanSlider(parent, mText, true); w = kss; // This is the only option type that has an option to reset // the value to the default. SANE does not specify what the // default value is, so it has to be guessed. If the allowable // range includes the value 0 then the default is set to that, // otherwise the minimum value is used. // // Note that in theory the constrained range of a SANE value may // not have endpoints that are integers and it may not even include // any integer; for example, the range could be 12.02 - 12.08 // or even more precisely specified. However, since the GUI // controls used (QSlider and QSpinBox) only work in integers then // the range is rounded to integer values. No problem caused by // doing this has been reported with any existing scanner. double min, max, quant; getRange(&min, &max, &quant); int stdValue = 0; if (stdValue>max || stdValuesetRange(qRound(min), qRound(max), qRound(quant), stdValue); } break; case KScanOption::Resolution: // special resolution combo case KScanOption::StringList: // string list combo w = new KScanCombo(parent, mText); break; case KScanOption::GammaTable: // no widget for this //qDebug() << "GammaTable not implemented here"; break; case KScanOption::String: // free text entry w = new KScanStringEntry(parent, mText); break; case KScanOption::File: // file name requester w = new KScanFileRequester(parent, mText); break; case KScanOption::Group: // group separator w = new KScanGroup(parent, mText); break; case KScanOption::Button: // action button w = new KScanPushButton(parent, mText); break; default: //qDebug() << "unknown control type " << mWidgetType; break; } if (w!=nullptr) { mControl = w; updateList(); // set list for combo box switch (w->type()) { case KScanControl::Number: // numeric control - connect(w, SIGNAL(settingChanged(int)), SLOT(slotWidgetChange(int))); + connect(w, QOverload::of(&KScanControl::settingChanged), this, QOverload::of(&KScanOption::slotWidgetChange)); break; case KScanControl::Text: // text control - connect(w, SIGNAL(settingChanged(const QString &)), SLOT(slotWidgetChange(const QString &))); + connect(w, QOverload::of(&KScanControl::settingChanged), this, QOverload::of(&KScanOption::slotWidgetChange)); break; case KScanControl::Button: // push button - connect(w, SIGNAL(returnPressed()), SLOT(slotWidgetChange())); + connect(w, &KScanControl::returnPressed, this, QOverload<>::of(&KScanOption::slotWidgetChange)); break; case KScanControl::Group: // group separator break; // nothing to do here } if (mDesc!=nullptr) // set tool tip { if (qstrlen(mDesc->desc)>0) // if there is a SANE description { QString tt = i18n(mDesc->desc); // KDE tooltips do not normally end with a full stop, unless // they are multi-sentence. But the SANE descriptions often do, // so trim it off for consistency. Is this a good thing to do // in non-western languages? if (tt.endsWith('.') && tt.count(". ")==0) tt.chop(1); // Force the format to be rich text so that it will be word wrapped // at a sensible width, see documentation for QToolTip. w->setToolTip(""+tt); } } // No accelerators for advanced options, so as not to soak up // too many of the available accelerators for controls that are // rarely going to be used. See also getLabel(). if (!isCommonOption()) KAcceleratorManager::setNoAccel(w); } reload(); // check if active, enabled etc. if (w!=nullptr) redrawWidget(); return (w); } QLabel *KScanOption::getLabel(QWidget *parent, bool alwaysBuddy) const { if (mControl==nullptr) return (nullptr); KSqueezedTextLabel *l = new KSqueezedTextLabel(mControl->label(), parent); if (isCommonOption() || alwaysBuddy) l->setBuddy(mControl->focusProxy()); return (l); } QLabel *KScanOption::getUnit(QWidget *parent) const { if (mControl==nullptr) return (nullptr); QString s; switch (mDesc->unit) { case SANE_UNIT_NONE: break; case SANE_UNIT_PIXEL: s = i18n("pixels"); break; case SANE_UNIT_BIT: s = i18n("bits"); break; case SANE_UNIT_MM: s = i18n("mm"); break; case SANE_UNIT_DPI: s = i18n("dpi"); break; case SANE_UNIT_PERCENT: s = i18n("%"); break; case SANE_UNIT_MICROSECOND: s = i18n("\302\265sec"); break; default: break; } if (s.isEmpty()) return (nullptr); // no unit label QLabel *l = new QLabel(s, parent); return (l); } void KScanOption::allocForDesc() { if (mDesc==nullptr) return; switch (mDesc->type) { case SANE_TYPE_INT: case SANE_TYPE_FIXED: case SANE_TYPE_STRING: allocBuffer(mDesc->size); break; case SANE_TYPE_BOOL: allocBuffer(sizeof(SANE_Word)); break; default: if (mDesc->size>0) allocBuffer(mDesc->size); break; } } void KScanOption::allocBuffer(long size) { if (size<1) return; #ifdef DEBUG_MEM //qDebug() << "Allocating" << size << "bytes for" << name; #endif mBuffer.resize(size); // set buffer size if (mBuffer.isNull()) // check allocation worked??? { qWarning() << "Fatal: Allocating" << size << "bytes for" << mName << "failed!"; return; } mBuffer.fill(0); // clear allocated buffer }