diff --git a/libkookascan/kscanoption.cpp b/libkookascan/kscanoption.cpp index 006b1cc..09cde09 100644 --- a/libkookascan/kscanoption.cpp +++ b/libkookascan/kscanoption.cpp @@ -1,1107 +1,1057 @@ /* 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 + delete mControl; mControl = nullptr; // dispose of the old control if (mDesc!=nullptr) mText = i18n(mDesc->title); //qDebug() << "type" << mWidgetType << "text" << mText; KScanControl *w = nullptr; + double min, max, quant; // range for a slider + switch (mWidgetType) { case KScanOption::Bool: - w = createToggleButton(parent, mText); // toggle button + w = new KScanCheckbox(parent, mText);; // toggle button break; case KScanOption::SingleValue: - w = createNumberEntry(parent, mText); // numeric entry + w = new KScanNumberEntry(parent, mText); // numeric entry break; case KScanOption::Range: - w = createSlider(parent, mText); // slider and spinbox + getRange(&min, &max); // slider and spinbox + w = new KScanSlider(parent, mText, min, max, true); break; -case KScanOption::Resolution: - w = createComboBox(parent, mText); // special resolution combo +case KScanOption::Resolution: // special resolution combo +case KScanOption::StringList: // string list combo + w = new KScanCombo(parent, mText); break; case KScanOption::GammaTable: //qDebug() << "GammaTable not implemented here"; break; // no widget for this -case KScanOption::StringList: - w = createComboBox(parent, mText); // string list combo - break; - case KScanOption::String: - w = createStringEntry(parent, mText); // free text entry + w = new KScanStringEntry(parent, mText); // free text entry break; case KScanOption::File: - w = createFileField(parent, mText); // file name requester + w = new KScanFileRequester(parent, mText); // file name requester break; case KScanOption::Group: - w = createGroupSeparator(parent, mText); // group separator + w = new KScanGroup(parent, mText); // group separator break; case KScanOption::Button: - w = createActionButton(parent, mText); // button to do action + w = new KScanPushButton(parent, mText); // action button 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))); break; case KScanControl::Text: // text control connect(w, SIGNAL(settingChanged(const QString &)), SLOT(slotWidgetChange(const QString &))); break; case KScanControl::Button: // push button connect(w, SIGNAL(returnPressed()), SLOT(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); } -inline KScanControl *KScanOption::createToggleButton(QWidget *parent, const QString &text) -{ - return (new KScanCheckbox(parent, text)); -} - - -inline KScanControl *KScanOption::createComboBox(QWidget *parent, const QString &text) -{ - return (new KScanCombo(parent, text)); -} - - -inline KScanControl *KScanOption::createStringEntry(QWidget *parent, const QString &text) -{ - return (new KScanStringEntry(parent, text)); -} - - -inline KScanControl *KScanOption::createNumberEntry(QWidget *parent, const QString &text) -{ - return (new KScanNumberEntry(parent, text)); -} - - -inline KScanControl *KScanOption::createSlider(QWidget *parent, const QString &text) -{ - double min, max; - getRange(&min, &max); - return (new KScanSlider(parent, text, min, max, true)); -} - - -inline KScanControl *KScanOption::createFileField(QWidget *parent, const QString &text) -{ - return (new KScanFileRequester(parent, text)); -} - - -inline KScanControl *KScanOption::createGroupSeparator(QWidget *parent, const QString &text) -{ - return (new KScanGroup(parent, text)); -} - - -inline KScanControl *KScanOption::createActionButton(QWidget *parent, const QString &text) -{ - return (new KScanPushButton(parent, text)); -} - - 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 } diff --git a/libkookascan/kscanoption.h b/libkookascan/kscanoption.h index 73b32af..0692a6a 100644 --- a/libkookascan/kscanoption.h +++ b/libkookascan/kscanoption.h @@ -1,525 +1,516 @@ /* This file is part of the KDE Project -*- mode:c++; -*- 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. */ #ifndef KSCANOPTION_H #define KSCANOPTION_H #include "kookascan_export.h" #include #include #include "kscandevice.h" extern "C" { #include } class QLabel; class KGammaTable; class KScanControl; class KScanDevice; /** * @short A single scanner parameter. * * A scanner may support any number of these parameters, some are * well-known and common to almost all scanners while others may be * model-specific. * * Most options have an associated GUI element (a @c KScanControl), precisely * which sort of control depending on the type and constraint of the * scanner parameter. * * Only one KScanOption for each scanner parameter may exist. All options * for a particular scanner are owned by a KScanDevice, and options may only * be created by KScanDevice::getOption(). This ensures that all accesses * to a scanner parameter are consistent. * * KScanOption implements an internal memory buffer as an intermediary between * the scanner and its caller. There are four basic operations implemented to * access and control the scanner parameters: * * - @c set - Copy data from a variable or structure of an appropriate type * into the internal memory buffer. * * - @c apply - Send the data from the internal memory buffer to the scanner. * * - @c reload - Fetch the scanner data into the internal memory buffer. * * - @c get - Read the data from the internal memory buffer into a variable * or structure of an appropriate type. * * @author Klaas Freitag * @author Jonathan Marten **/ class KOOKASCAN_EXPORT KScanOption : public QObject { Q_OBJECT public: /** * Check whether the option is valid: that is, the parameter is known * by the scanner. * * @return @c true if the option is valid **/ bool isValid() const { return (mDesc != nullptr); } /** * Check whether the option is initialised: that is, if the initial * value of the parameter has been read from the scanner. * * @return @c true if the option has been initialised **/ bool isInitialised() const { return (!mBufferClean); } /** * Check whether the option is a group, if so this is a title only * and many of the operations will not be available. * * @return @c true if the option is a group **/ bool isGroup() const { return (mIsGroup); } /** * Check whether the option value can be read from the scanner * (SANE_CAP_SOFT_SELECT with some special exceptions). Some * options that cannot be read may still be able to be set, * use @c isSoftwareSettable() to check. * * @return @c true if the option is readable * @see isSoftwareSettable **/ bool isReadable() const { return (mIsReadable); } /** * Check whether the option is auto settable (SANE_CAP_AUTOMATIC): * that is, if the scanner can choose a setting for the option * automatically. * * @return @c true if the option can be set automatically **/ bool isAutoSettable() const { return (mDesc!=nullptr && (mDesc->cap & SANE_CAP_AUTOMATIC)); } /** * Check whether the option is a common option (not SANE_CAP_ADVANCED). * * @return @c true if the option is a common option, * @c false if it is an advanced option. **/ bool isCommonOption() const { return (mDesc!=nullptr && !(mDesc->cap & SANE_CAP_ADVANCED)); } /** * Check whether the option should be set as a priority, before any * other non-priority options. This a workaround for some scanners * which may reset other options when this option is changed. **/ bool isPriorityOption() const { return (mIsPriority); } /** * Check whether the option is currently active (SANE_OPTION_IS_ACTIVE). * This may change at runtime depending on the settings of other options. * * @return @c true if the option is currently active **/ bool isActive() const { return (mDesc!=nullptr && SANE_OPTION_IS_ACTIVE(mDesc->cap)); } /** * Check whether the option is can be set by software * (SANE_OPTION_IS_SETTABLE). Some scanner options cannot be set, * use @c isReadable() to check if they can be read. * * @return @c true if the option can be set * @see isReadable **/ bool isSoftwareSettable() const { return (mDesc!=nullptr && SANE_OPTION_IS_SETTABLE(mDesc->cap)); } /** * Check whether the option has an associated GUI element * (not all types of options do). * * @return @c true if the option has a GUI widget **/ bool isGuiElement() const { return (mControl != nullptr); } /** * Check whether the option value has been sent to the scanner: * that is, whether @c apply() has been used. This is used by * @c KScanDevice to maintain its list of "dirty" options. * * @return @c true if the option has been applied * @see KScanDevice * @see setApplied **/ bool isApplied() const { return (mApplied); } /** * Set or clear the "applied" flag. * * @param app New value for the flag * @see isApplied * @see apply **/ void setApplied(bool app = true) { mApplied = app; } /** * Set the option value. * * @param val A new integer value * @return @c true if the value was set successfully **/ bool set(int val); /** * Set the option value. * * @param val A new @c double floating point value * @return @c true if the value was set successfully **/ bool set(double val); /** * Set the option value. * * @param val A new array of integer values * @param size The length of the array * @return @c true if the value was set successfully **/ bool set(const int *val, int size); /** * Set the option value. * * @param val A new formatted string value * @return @c true if the value was set successfully **/ bool set(const QByteArray &val); /** * Set the option value. * * @param val A new boolean value * @return @c true if the value was set successfully **/ bool set(bool val) { return (set(val ? 1 : 0)); } /** * Set the option value. * * @param gt A new gamma table * @return @c true if the value was set successfully **/ bool set(const KGammaTable *gt); /** * Retrieve the option value. * * @param val An integer to receive the value read * @return @c true if the value was read successfully * * @note If the scanner parameter is an array, only the first value * is retrieved from it. **/ bool get(int *val) const; /** * Retrieve the option value. * * @param gt A gamma table to receive the value read * @return @c true in all cases (unless the @p gt parameter is @c nullptr) **/ bool get(KGammaTable *gt) const; /** * Retrieve the option value. * * @return The formatted string value, or @c "?" if the value could * not be read. **/ QByteArray get() const; /** * Retrieve a list of all possible option values. * * @return A list of formatted string values (as would be returned * by @c get()) * * @note This works for options with SANE_CONSTRAINT_STRING_LIST, * SANE_CONSTRAINT_WORD_LIST and for a SANE_CONSTRAINT_RANGE which is a * resolution setting. To retrieve the range for other constraint * types, use @c getRange(). **/ QList getList() const; /** * Retrieve the range of possible numeric values. * * @param minp A @c double to receive the minimum value * @param maxp A @c double to receive the maximum value * @param quantp A @c double to receive the step, if it is not @c nullptr. * @return @c true if the option is a range type * * @note For an option with SANE_CONSTRAINT_WORD_LIST, the minimum * and maximum values are those found in the word list and the step * is the range divided by the number of possible values. This does * not imply that any intermediate values calculated from these are valid. **/ bool getRange(double *minp, double *maxp, double *quantp = nullptr) const; /** * Send the data (previously set by @c set()) to the scanner, if this * is possible - if the option is initialised, software settable and * active. * * @return @c true if a reload of other parameters is required * (@c sane_control_option() returned SANE_INFO_RELOAD_OPTIONS). **/ bool apply(); /** * Retrieve the current data from the scanner, so that it can be * accessed by @c get(). If the option has an associated GUI control, * the enabled/disabled state of that is set appropriately. **/ void reload(); /** * Create a GUI widget for the scanner option, depending on its type. * * - Boolean options have a check box (a @c KScanCheckbox). * - Numeric ranges have a slider/spinbox combination (a @c KScanSlider), * except for the special case of a resolution option which generates * a combo box (@c KScanCombo) - this is done for user convenience. * - String and numeric lists generate a combo box (@c KScanCombo). * - Unconstrained string and numeric options generate an entry box * (@c KScanStringEntry or @c KScanNumberEntry respectively). * - An option which is a file name (present in the SANE "pnm" test device) * generates a file requester (@c KScanFileRequester). * - Group options generate a separator line (@c KScanGroup), although * obviously no interaction is allowed. * - Button options generate a clickable button (@c KScanPushButton). * * Ownership of the widget is retained by the @c KScanOption object, so it * should not be deleted by the caller. * * @param parent Parent for the created widget. * @return The created widget, or @c nullptr if it could not be created. **/ KScanControl *createWidget(QWidget *parent); /** * Create a label widget for the scanner option. @c createWidget() must * have been used to create the control first. * * If the option is a common option (i.e. not advanced), then the label's * buddy will be set to the control. * @param parent Parent widget for the created label. * @param alwaysBuddy If this is @c true, the label's buddy will always * be set even if this is an advanced option. * @return The created label widget. **/ QLabel *getLabel(QWidget *parent, bool alwaysBuddy = false) const; /** * Create a label widget for the SANE unit of this option. * @c createWidget() must have been used to create the control first. * * @param parent Parent widget for the created label. * @return The created label widget, or @c nullptr if no unit is * applicable. **/ QLabel *getUnit(QWidget *parent) const; /** * Get the name of the option. * * @return The name of the option **/ QByteArray getName() const { return (mName); } /** * Get the SANE capabilities for the option. * * @return The capabilities, or 0 if the option is not valid **/ int getCapabilities() const { return (mDesc!=nullptr ? mDesc->cap : 0); } /** * Get the GUI widget for the option, if applicable and one has been * created by @c createWidget()). * * @return The widget, or @c nullptr if there is none. **/ KScanControl *widget() const { return (mControl); } /** * Update the GUI widget to reflect the current value of the option. **/ void redrawWidget(); protected slots: /** * Called when the contents of the GUI widget has changed, if the * option has a GUI element (not all have). This slot is used for * options that have a @c KScanControl of type @c KScanControl::Text. * * @param t New string contents of the widget **/ void slotWidgetChange(const QString &t); /** * Called when the contents of the GUI widget has changed, if the * option has a GUI element (not all have). This slot is used for * options that have a @c KScanControl of type @c KScanControl::Number. * * @param i New index or setting of the widget **/ void slotWidgetChange(int i); /** * Called when a GUI widget is activated, if the option has a GUI * element (not all have). This slot is used for options that have * a @c KScanControl of type @c KScanControl::Button. **/ void slotWidgetChange(); signals: /** * Emitted when the user changes the GUI setting of the option. * The new setting will have been @c set(), but not yet @c apply()'ed. * * @param so The @c KScanOption which has changed **/ void guiChange(KScanOption *so); private: /** * Create a new object for the named option belonging to the * specified scanner device. After construction, if the option * is valid it will initially contain the current parameter value * retrieved from the scanner. * * @param name Name of the scanner option * @param scandev Scanner device * * @note This constructor is private. All @c KScanOption's are * created by @c KScanDevice; a new or existing @c KScanOption can * be obtained via @c KScanDevice::getOption(). **/ KScanOption(const QByteArray &name, KScanDevice *scandev); friend KScanOption *KScanDevice::getOption(const QByteArray &, bool); /** * Destructor. * * @note This destructor is private. All @c KScanOption's are * owned by @c KScanDevice, and are deleted when the scan device * is closed. **/ ~KScanOption(); friend void KScanDevice::closeDevice(); /** * The type of an associated GUI widget (if there is one). **/ enum WidgetType { Invalid, Bool, SingleValue, Range, GammaTable, StringList, String, Resolution, File, Group, Button }; bool initOption(const QByteArray &name); KScanOption::WidgetType resolveWidgetType() const; void allocForDesc(); void allocBuffer(long size); void updateList(); - KScanControl *createToggleButton(QWidget *parent, const QString &text); - KScanControl *createStringEntry(QWidget *parent, const QString &text); - KScanControl *createNumberEntry(QWidget *parent, const QString &text); - KScanControl *createSlider(QWidget *parent, const QString &text); - KScanControl *createComboBox(QWidget *parent, const QString &text); - KScanControl *createFileField(QWidget *parent, const QString &text); - KScanControl *createGroupSeparator(QWidget *parent, const QString &text); - KScanControl *createActionButton(QWidget *parent, const QString &text); - KScanDevice *mScanDevice; int mIndex; const SANE_Option_Descriptor *mDesc; QByteArray mName; QString mText; bool mIsGroup; bool mIsReadable; bool mIsPriority; KScanControl *mControl; KScanOption::WidgetType mWidgetType; QByteArray mBuffer; bool mBufferClean; bool mApplied; KGammaTable *mGammaTable; }; #endif // KSCANOPTION_H