diff --git a/src/ksanewidget.cpp b/src/ksanewidget.cpp --- a/src/ksanewidget.cpp +++ b/src/ksanewidget.cpp @@ -218,6 +218,12 @@ d->m_otherScrollA->setWidgetResizable(true); d->m_otherScrollA->setFrameShape(QFrame::NoFrame); d->m_optsTabWidget->addTab(d->m_otherScrollA, i18n("Scanner Specific Options")); + + // Add the scan area options tab + d->m_areaScrollA = new QScrollArea; + d->m_areaScrollA->setWidgetResizable(true); + d->m_areaScrollA->setFrameShape(QFrame::NoFrame); + d->m_optsTabWidget->addTab(d->m_areaScrollA, i18n("Scan Area Options")); d->m_splitter = new QSplitter(this); d->m_splitter->addWidget(d->m_optsTabWidget); @@ -428,35 +434,51 @@ return false; } numSaneOptions = *reinterpret_cast(data.data()); + + // priorize source -> mode -> rest + + int idx_opt_source = -1; + int idx_opt_mode = -1; + + const QString QS_OPT_SOURCE = QString::fromLatin1(SANE_NAME_SCAN_SOURCE); + const QString QS_OPT_MODE = QString::fromLatin1(SANE_NAME_SCAN_MODE); + + for (i = 1; i < numSaneOptions; ++i) { + const SANE_Option_Descriptor * sod = sane_get_option_descriptor(d->m_saneHandle, i); + if((sod != nullptr) && (sod->name != nullptr)) + { + QString qs_sod_name = QString::fromLatin1(sod->name); + if(qs_sod_name == QS_OPT_SOURCE) + { + //qDebug() << "found " << qs_sod_name << " at position " << i; + idx_opt_source = i; + } + else if(qs_sod_name == QS_OPT_MODE) + { + //qDebug() << "found " << qs_sod_name << " at position " << i; + idx_opt_mode = i; + } + else + { + // do nothing + } + } + } + + if(idx_opt_source > 0) + { + d->createAndAppendOption(idx_opt_source); + } + + if(idx_opt_mode > 0) + { + d->createAndAppendOption(idx_opt_mode); + } // 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; - } + if((i == idx_opt_source) || (i == idx_opt_mode)) continue; + else d->createAndAppendOption(i); } // do the connections of the option parameters @@ -722,17 +744,91 @@ d->m_previewThread->isRunning()) { return -1; } + + const QString QS_OPT_MODE = QString::fromLatin1("mode"); + const QString QS_OPT_SOURCE = QString::fromLatin1("source"); + + bool reorderOptSource = opts.contains(QS_OPT_SOURCE); + bool reorderOptMode = opts.contains(QS_OPT_MODE); QString tmp; int i; int ret = 0; + + // 1. iteration: make sure scan source is set at first to make following options compatible + if(reorderOptSource) + { + //QDebug qdbg = qDebug(); + + for (i = 0; i < d->m_optList.size(); i++) { + QString optName = d->m_optList.at(i)->name(); + if(!optName.compare(QS_OPT_SOURCE)) { + //qdbg << "m_optList[" << i << "]: " << optName; + tmp = opts[optName]; + if (d->m_optList.at(i)->setValue(tmp)) { + //qdbg << " exists and written"; + ret++; + } + else { + //qdbg << " exists but write failed"; + } + + break; + } + + } + } + + // 2. iteration: make sure scan mode is set after source but before remaining options to make following options compatible + if(reorderOptMode) + { + //QDebug qdbg = qDebug(); + + for (i = 0; i < d->m_optList.size(); i++) { + QString optName = d->m_optList.at(i)->name(); + if(!optName.compare(QS_OPT_MODE)) { + //qdbg << "m_optList[" << i << "]: " << optName; + tmp = opts[optName]; + if (d->m_optList.at(i)->setValue(tmp)) { + //qdbg << " exists and written"; + ret++; + } + else { + //qdbg << " exists but write failed"; + } + + break; + } + + } + } + // 3. iteration: set remaining options unordered (i.e. in order of the internal QList container) 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) { + //QDebug qdbg = qDebug(); + + QString optName = d->m_optList.at(i)->name(); + + if(reorderOptSource && !optName.compare(QS_OPT_SOURCE)) { + //qdbg << "skipped source option because it has been set previously"; + continue; + } + + if(reorderOptMode && !optName.compare(QS_OPT_MODE)) { + //qdbg << "skipped mode option because it has been set previously"; + continue; + } + + if (opts.contains(optName)) { + //qdbg << "m_optList[" << i << "]: " << optName; + tmp = opts[optName]; + if (d->m_optList.at(i)->setValue(tmp)) { // bugfix: accumulate successful writes. not failed writes + //qdbg << " exists and written"; ret++; } + else { + //qdbg << " exists but write failed"; + } } } if ((d->m_splitGamChB) && @@ -766,6 +862,8 @@ d->m_invertColors->setChecked(false); } } + + //qDebug() << "set " << ret << "options"; return ret; } diff --git a/src/ksanewidget_p.h b/src/ksanewidget_p.h --- a/src/ksanewidget_p.h +++ b/src/ksanewidget_p.h @@ -46,6 +46,13 @@ #include "ksanewidget.h" #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 "ksaneviewer.h" #include "labeledgamma.h" #include "labeledcheckbox.h" @@ -54,6 +61,8 @@ #include "ksanepreviewthread.h" #include "ksanefinddevicesthread.h" #include "ksaneauth.h" +#include "labeledfslider.h" +#include "labeledcombo.h" #define IMG_DATA_R_SIZE 100000 @@ -74,6 +83,7 @@ KSaneOption *getOption(const QString &name); KSaneWidget::ImageFormat getImgFormat(SANE_Parameters ¶ms); int getBytesPerLines(SANE_Parameters ¶ms); + void createAndAppendOption(int i); public Q_SLOTS: void devListUpdated(); @@ -97,11 +107,22 @@ void checkInvert(); void invertPreview(); void pollPollOptions(); + + void updateScanarea(); public: void alertUser(int type, const QString &strStatus); public: + QMap m_paperSizesMap; + LabeledCombo *m_scanareaPapersize; + LabeledFSlider *m_scanareaWidth; + LabeledFSlider *m_scanareaHeight; + LabeledFSlider *m_scanareaX; + LabeledFSlider *m_scanareaY; + QPushButton *m_scanareaFlip; + LabeledCheckbox *m_scanareaEnableManual; + // backend independent QTabWidget *m_optsTabWidget; QScrollArea *m_basicScrollA; @@ -110,6 +131,9 @@ QScrollArea *m_otherScrollA; QWidget *m_otherOptsTab; LabeledCheckbox *m_invertColors; + + QScrollArea *m_areaScrollA; + QWidget *m_areaOptsTab; QSplitter *m_splitter; SplitterCollapser *m_optionsCollapser; diff --git a/src/ksanewidget_p.cpp b/src/ksanewidget_p.cpp --- a/src/ksanewidget_p.cpp +++ b/src/ksanewidget_p.cpp @@ -38,6 +38,10 @@ #include #include +#include "labeledfslider.h" +#include "labeledcombo.h" +#include "labeledcheckbox.h" + #define SCALED_PREVIEW_MAX_SIDE 400 static const int ActiveSelection = 100000; @@ -480,6 +484,124 @@ } m_optsTabWidget->setMinimumWidth(min_width + m_basicScrollA->verticalScrollBar()->sizeHint().width() + 5); + + + + // scan area options + m_areaOptsTab = new QWidget; + m_areaScrollA->setWidget(m_areaOptsTab); + QVBoxLayout *area_layout = new QVBoxLayout(m_areaOptsTab); + + m_paperSizesMap.clear(); + + const QString QS_DEFAULT_PAPER_SIZE = QString::fromLatin1("A4"); + + m_paperSizesMap[QString::fromLatin1("A3")] = QPointF(297.0f, 420.0f); + m_paperSizesMap[QString::fromLatin1("A4")] = QPointF(210.0f, 297.0f); + m_paperSizesMap[QString::fromLatin1("A5")] = QPointF(148.0f, 210.0f); + m_paperSizesMap[QString::fromLatin1("A6")] = QPointF(105.0f, 148.0f); + + m_paperSizesMap[QString::fromLatin1("B3")] = QPointF(353.0f, 500.0f); + m_paperSizesMap[QString::fromLatin1("B4")] = QPointF(250.0f, 353.0f); + m_paperSizesMap[QString::fromLatin1("B5")] = QPointF(176.0f, 250.0f); + m_paperSizesMap[QString::fromLatin1("B6")] = QPointF(125.0f, 176.0f); + + m_paperSizesMap[QString::fromLatin1("C3")] = QPointF(324.0f, 458.0f); + m_paperSizesMap[QString::fromLatin1("C4")] = QPointF(229.0f, 324.0f); + m_paperSizesMap[QString::fromLatin1("C5")] = QPointF(162.0f, 229.0f); + m_paperSizesMap[QString::fromLatin1("C6")] = QPointF(114.0f, 162.0f); + + m_paperSizesMap[QString::fromLatin1("Letter")] = QPointF(216.0f, 279.0f); + m_paperSizesMap[QString::fromLatin1("Legal")] = QPointF(216.0f, 356.0f); + m_paperSizesMap[QString::fromLatin1("Tabloid")] = QPointF(279.0f, 432.0f); + m_paperSizesMap[QString::fromLatin1("Ledger")] = QPointF(432.0f, 279.0f); + + m_paperSizesMap[QString::fromLatin1("Junior Legal")] = QPointF(127.0f, 203.0f); + m_paperSizesMap[QString::fromLatin1("Half Letter")] = QPointF(140.0f, 216.0f); + m_paperSizesMap[QString::fromLatin1("Government Letter")] = QPointF(203.0f, 267.0f); + m_paperSizesMap[QString::fromLatin1("Government Legal")] = QPointF(216.0f, 330.0f); + + m_scanareaPapersize = new LabeledCombo(m_areaOptsTab, i18n("select paper size"), m_paperSizesMap.keys()); + + m_scanareaWidth = new LabeledFSlider(m_areaOptsTab, i18n("width (mm)"), 0.0f, 500.0f, 0.1f); + m_scanareaHeight = new LabeledFSlider(m_areaOptsTab, i18n("height (mm)"), 0.0f, 500.0f, 0.1f); + + m_scanareaX = new LabeledFSlider(m_areaOptsTab, i18n("x offset (mm)"), 0.0f, 500.0f, 0.1f); + m_scanareaY = new LabeledFSlider(m_areaOptsTab, i18n("y offset (mm)"), 0.0f, 500.0f, 0.1f); + + m_scanareaFlip = new QPushButton(i18n("flip width/height"), m_areaOptsTab); + m_scanareaEnableManual = new LabeledCheckbox(m_areaOptsTab, i18n("enable manual selection")); + + m_scanareaEnableManual->setChecked(false); + + m_scanareaPapersize->setCurrentText(QS_DEFAULT_PAPER_SIZE); + m_scanareaWidth->setValue(m_paperSizesMap[QS_DEFAULT_PAPER_SIZE].x()); + m_scanareaHeight->setValue(m_paperSizesMap[QS_DEFAULT_PAPER_SIZE].y()); + m_scanareaX->setValue(0.0f); + m_scanareaY->setValue(0.0f); + + connect(m_scanareaFlip, &QPushButton::clicked, this, [this]() { + float tmp = this->m_scanareaWidth->value(); + this->m_scanareaWidth->setValue(this->m_scanareaHeight->value()); + this->m_scanareaHeight->setValue(tmp); + }); + + connect(m_scanareaPapersize, &LabeledCombo::activated_str, this, [this](const QString &selStr){ + QPointF selPoint = this->m_paperSizesMap[selStr]; + this->m_scanareaWidth->setValue(selPoint.x()); + this->m_scanareaHeight->setValue(selPoint.y()); + }); + + connect(m_scanareaWidth, SIGNAL(valueChanged(float)), SLOT(updateScanarea())); + connect(m_scanareaHeight, SIGNAL(valueChanged(float)), SLOT(updateScanarea())); + connect(m_scanareaX, SIGNAL(valueChanged(float)), SLOT(updateScanarea())); + connect(m_scanareaY, SIGNAL(valueChanged(float)), SLOT(updateScanarea())); + connect(m_scanareaEnableManual, SIGNAL(toggled(bool)), SLOT(updateScanarea())); + + area_layout->addWidget(m_scanareaPapersize); + area_layout->addWidget(m_scanareaWidth); + area_layout->addWidget(m_scanareaHeight); + + area_layout->addWidget(m_scanareaX); + area_layout->addWidget(m_scanareaY); + + area_layout->addWidget(m_scanareaFlip); + area_layout->addWidget(m_scanareaEnableManual); + + area_layout->addStretch(); +} + +void KSaneWidgetPrivate::updateScanarea() +{ + if(m_scanareaEnableManual->isChecked()) + return; + + float w = m_scanareaWidth->value(); + float h = m_scanareaHeight->value(); + + float x1 = m_scanareaX->value(); + float y1 = m_scanareaY->value(); + + float x2 = x1 + w; + float y2 = y1 + h; + + float max_x = 0.0f; + float max_y = 0.0f; + m_optBrX->getMaxValue(max_x); + m_optBrY->getMaxValue(max_y); + + x1 = std::min(x1, max_x); + x2 = std::min(x2, max_x); + + y1 = std::min(y1, max_y); + y2 = std::min(y2, max_y); + + m_optTlX->setValue(x1); + m_optTlY->setValue(y1); + m_optBrX->setValue(x2); + m_optBrY->setValue(y2); + + m_previewViewer->setSelection(x1 / max_x, y1 / max_y, x2 / max_x, y2 / max_y); } void KSaneWidgetPrivate::setDefaultValues() @@ -534,6 +656,8 @@ } m_optsTabWidget->setMinimumWidth(min_width + m_basicScrollA->verticalScrollBar()->sizeHint().width() + 5); + + updateScanarea(); m_previewViewer->zoom2Fit(); } @@ -546,7 +670,8 @@ for (i = 0; i < m_optList.size(); ++i) { m_optList.at(i)->readValue(); } - + + updateScanarea(); } void KSaneWidgetPrivate::handleSelection(float tl_x, float tl_y, float br_x, float br_y) @@ -962,7 +1087,8 @@ m_optSource->getValue(source); if (source.contains(QStringLiteral("Automatic Document Feeder")) || - source.contains(QStringLiteral("ADF"))) { + source.contains(QStringLiteral("ADF"))|| + source.contains(QStringLiteral("Duplex"))) { // in batch mode only one area can be scanned per page //qDebug() << "source == " << source; m_updProgressTmr.start(); @@ -1173,4 +1299,34 @@ } } +void KSaneWidgetPrivate::createAndAppendOption(int i) +{ + switch (KSaneOption::optionType(sane_get_option_descriptor(m_saneHandle, i))) { + case KSaneOption::TYPE_DETECT_FAIL: + m_optList.append(new KSaneOption(m_saneHandle, i)); + break; + case KSaneOption::TYPE_CHECKBOX: + m_optList.append(new KSaneOptCheckBox(m_saneHandle, i)); + break; + case KSaneOption::TYPE_SLIDER: + m_optList.append(new KSaneOptSlider(m_saneHandle, i)); + break; + case KSaneOption::TYPE_F_SLIDER: + m_optList.append(new KSaneOptFSlider(m_saneHandle, i)); + break; + case KSaneOption::TYPE_COMBO: + m_optList.append(new KSaneOptCombo(m_saneHandle, i)); + break; + case KSaneOption::TYPE_ENTRY: + m_optList.append(new KSaneOptEntry(m_saneHandle, i)); + break; + case KSaneOption::TYPE_GAMMA: + m_optList.append(new KSaneOptGamma(m_saneHandle, i)); + break; + case KSaneOption::TYPE_BUTTON: + m_optList.append(new KSaneOptButton(m_saneHandle, i)); + break; + } +} + } // NameSpace KSaneIface diff --git a/src/options/ksaneoptcombo.cpp b/src/options/ksaneoptcombo.cpp --- a/src/options/ksaneoptcombo.cpp +++ b/src/options/ksaneoptcombo.cpp @@ -34,6 +34,7 @@ #include #include +#include namespace KSaneIface { @@ -69,6 +70,7 @@ SANE_Int res; status = sane_control_option(m_handle, m_index, SANE_ACTION_GET_VALUE, data.data(), &res); if (status != SANE_STATUS_GOOD) { + qDebug() << "readValue returned " << status; return; } @@ -277,6 +279,9 @@ float minDiff; int i; int minIndex = 1; + + // bugfix: enforce update of allowed sane values to prevent discarding of recently allowed values (e.g. different resolutions after switching from ADF to flatbed source) + readOption(); // TODO check if redundant switch (m_optDesc->type) { case SANE_TYPE_INT: @@ -326,11 +331,18 @@ bool KSaneOptCombo::setValue(const QString &val) { if (state() == STATE_HIDDEN) { + //qDebug() << "state() == STATE_HIDDEN"; return false; } + /* if (val == m_currentText) { + qDebug() << "val == m_currentText"; return true; } + */ + + // bugfix: enforce update of allowed sane values to prevent discarding of recently allowed values (e.g. different resolutions after switching from ADF to flatbed source) + readOption(); // TODO check if redundant unsigned char data[4]; void* data_ptr = nullptr; @@ -343,6 +355,7 @@ switch (m_optDesc->type) { case SANE_TYPE_INT: tmp = val.left(val.indexOf(QLatin1Char(' '))); // strip the unit + tmp.remove(QLocale().groupSeparator()); // strip group separator (e.g. 1.200 -> 1200) // accept float formatting of the string i = (int)(tmp.toFloat(&ok)); if (ok == false) { @@ -353,6 +366,7 @@ break; case SANE_TYPE_FIXED: tmp = val.left(val.indexOf(QLatin1Char(' '))); // strip the unit + tmp.remove(QLocale().groupSeparator()); // strip group separator (e.g. 1.200 -> 1200) f = tmp.toFloat(&ok); if (ok == false) { return false; diff --git a/src/options/ksaneoption.cpp b/src/options/ksaneoption.cpp --- a/src/options/ksaneoption.cpp +++ b/src/options/ksaneoption.cpp @@ -31,6 +31,7 @@ #include "ksaneoptionwidget.h" #include +#include namespace KSaneIface { diff --git a/src/widgets/labeledcombo.h b/src/widgets/labeledcombo.h --- a/src/widgets/labeledcombo.h +++ b/src/widgets/labeledcombo.h @@ -88,6 +88,7 @@ Q_SIGNALS: void activated(int); + void activated_str(const QString&); private: QComboBox *m_combo; diff --git a/src/widgets/labeledcombo.cpp b/src/widgets/labeledcombo.cpp --- a/src/widgets/labeledcombo.cpp +++ b/src/widgets/labeledcombo.cpp @@ -42,6 +42,7 @@ m_label->setBuddy(m_combo); connect(m_combo, QOverload::of(&QComboBox::activated), this, &LabeledCombo::activated); + connect(m_combo, QOverload::of(&QComboBox::activated), this, &LabeledCombo::activated_str); m_layout->addWidget(m_combo, 0, 1); m_layout->addWidget(new QWidget(this), 0, 2);