File indexing completed on 2024-05-12 15:55:07

0001 /************************************************************************
0002  *                                  *
0003  *  This file is part of Kooka, a scanning/OCR application using    *
0004  *  Qt <http://www.qt.io> and KDE Frameworks <http://www.kde.org>.  *
0005  *                                  *
0006  *  Copyright (C) 2003-2016 Klaas Freitag <freitag@suse.de>     *
0007  *                          Jonathan Marten <jjm@keelhaul.me.uk>    *
0008  *                                  *
0009  *  Kooka is free software; you can redistribute it and/or modify it    *
0010  *  under the terms of the GNU Library General Public License as    *
0011  *  published by the Free Software Foundation and appearing in the  *
0012  *  file COPYING included in the packaging of this file;  either    *
0013  *  version 2 of the License, or (at your option) any later version.    *
0014  *                                  *
0015  *  As a special exception, permission is given to link this program    *
0016  *  with any version of the KADMOS OCR/ICR engine (a product of     *
0017  *  reRecognition GmbH, Kreuzlingen), and distribute the resulting  *
0018  *  executable without including the source code for KADMOS in the  *
0019  *  source distribution.                        *
0020  *                                  *
0021  *  This program is distributed in the hope that it will be useful, *
0022  *  but WITHOUT ANY WARRANTY; without even the implied warranty of  *
0023  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the   *
0024  *  GNU General Public License for more details.            *
0025  *                                  *
0026  *  You should have received a copy of the GNU General Public       *
0027  *  License along with this program;  see the file COPYING.  If     *
0028  *  not, see <http://www.gnu.org/licenses/>.                *
0029  *                                  *
0030  ************************************************************************/
0031 
0032 #include "ocrocraddialog.h"
0033 
0034 #include <qlabel.h>
0035 #include <qregexp.h>
0036 #include <qcombobox.h>
0037 #include <qcheckbox.h>
0038 #include <qspinbox.h>
0039 #include <qlayout.h>
0040 #include <qprogressbar.h>
0041 
0042 #include <klocalizedstring.h>
0043 #include <kurlrequester.h>
0044 #include <kprocess.h>
0045 
0046 #include "scanimage.h"
0047 #include "kookapref.h"
0048 #include "kookasettings.h"
0049 #include "kscancontrols.h"
0050 #include "dialogbase.h"
0051 
0052 #include "ocrocradengine.h"
0053 #include "ocr_logging.h"
0054 
0055 
0056 OcrOcradDialog::OcrOcradDialog(AbstractOcrEngine *plugin, QWidget *pnt)
0057     : AbstractOcrDialogue(plugin, pnt),
0058       m_setupWidget(nullptr),
0059       m_orfUrlRequester(nullptr),
0060       m_layoutMode(nullptr),
0061       m_ocrCmd(QString()),
0062       m_versionNum(0),
0063       m_versionStr(QString())
0064 {
0065 }
0066 
0067 
0068 bool OcrOcradDialog::setupGui()
0069 {
0070     AbstractOcrDialogue::setupGui();            // build the standard GUI
0071 
0072     // Options available vary with the OCRAD version.  So we need to find
0073     // the OCRAD binary and get its version before creating the GUI.
0074     m_ocrCmd = engine()->findExecutable(&KookaSettings::ocrOcradBinary, KookaSettings::self()->ocrOcradBinaryItem());
0075 
0076     if (!m_ocrCmd.isEmpty()) getVersion(m_ocrCmd);  // found, get its version
0077     else                        // not found or invalid
0078     {
0079         engine()->setErrorText(i18n("The OCRAD executable is not configured or is not available."));
0080     }
0081 
0082     QWidget *w = addExtraSetupWidget();
0083     QGridLayout *gl = new QGridLayout(w);
0084 
0085     // Layout detection mode, dependent on OCRAD version
0086     KConfigSkeletonItem *ski = KookaSettings::self()->ocrOcradLayoutDetectionItem();
0087     Q_ASSERT(ski!=nullptr);
0088     QLabel *l = new QLabel(ski->label(), w);
0089     gl->addWidget(l, 0, 0);
0090 
0091     m_layoutMode = new QComboBox(w);
0092     m_layoutMode->addItem(i18n("No Layout Detection"), 0);
0093     if (m_versionNum >= 18) {           // OCRAD 0.18 or later
0094         // has only on/off
0095         m_layoutMode->addItem(i18n("Layout Detection"), 1);
0096     } else {                    // OCRAD 0.17 or earlier
0097         // had these 3 options
0098         m_layoutMode->addItem(i18n("Column Detection"), 1);
0099         m_layoutMode->addItem(i18n("Full Layout Detection"), 2);
0100     }
0101 
0102     m_layoutMode->setCurrentIndex(KookaSettings::ocrOcradLayoutDetection());
0103     m_layoutMode->setToolTip(ski->toolTip());
0104     gl->addWidget(m_layoutMode, 0, 1);
0105     l->setBuddy(m_layoutMode);
0106 
0107     gl->setRowMinimumHeight(1, DialogBase::verticalSpacing());
0108 
0109     // Character set, auto detected values
0110     QStringList vals = getValidValues("charset");
0111     ski = KookaSettings::self()->ocrOcradCharsetItem();
0112     Q_ASSERT(ski!=nullptr);
0113     l = new QLabel(ski->label(), w);
0114     gl->addWidget(l, 2, 0);
0115     m_characterSet = new QComboBox(w);
0116     m_characterSet->setToolTip(ski->toolTip());
0117     m_characterSet->addItem(i18n("(default)"), false);
0118     for (QStringList::const_iterator it = vals.constBegin(); it != vals.constEnd(); ++it) {
0119         m_characterSet->addItem(*it, true);
0120     }
0121 
0122     if (vals.count() == 0) m_characterSet->setEnabled(false);
0123     else {
0124         int ix = m_characterSet->findText(KookaSettings::ocrOcradCharset());
0125         if (ix != -1) m_characterSet->setCurrentIndex(ix);
0126     }
0127     gl->addWidget(m_characterSet, 2, 1);
0128     l->setBuddy(m_characterSet);
0129 
0130     // Filter, auto detected values
0131     vals = getValidValues("filter");
0132     ski = KookaSettings::self()->ocrOcradFilterItem();
0133     Q_ASSERT(ski!=nullptr);
0134     l = new QLabel(ski->label(), w);
0135     gl->addWidget(l, 3, 0);
0136     m_filter = new QComboBox(w);
0137     m_filter->setToolTip(ski->toolTip());
0138     m_filter->addItem(i18n("(default)"), false);
0139     for (QStringList::const_iterator it = vals.constBegin(); it != vals.constEnd(); ++it) {
0140         m_filter->addItem(*it, true);
0141     }
0142 
0143     if (vals.count() == 0) m_filter->setEnabled(false);
0144     else {
0145         int ix = m_filter->findText(KookaSettings::ocrOcradFilter());
0146         if (ix != -1) m_filter->setCurrentIndex(ix);
0147     }
0148     gl->addWidget(m_filter, 3, 1);
0149     l->setBuddy(m_filter);
0150 
0151     // Transform, auto detected values
0152     vals = getValidValues("transform");
0153     ski = KookaSettings::self()->ocrOcradTransformItem();
0154     Q_ASSERT(ski!=nullptr);
0155     l = new QLabel(ski->label(), w);
0156     gl->addWidget(l, 4, 0);
0157     m_transform = new QComboBox(w);
0158     m_transform->setToolTip(ski->toolTip());
0159     m_transform->addItem(i18n("(default)"), false);
0160     for (QStringList::const_iterator it = vals.constBegin(); it != vals.constEnd(); ++it) {
0161         m_transform->addItem(*it, true);
0162     }
0163 
0164     if (vals.count() == 0) m_transform->setEnabled(false);
0165     else {
0166         int ix = m_transform->findText(KookaSettings::ocrOcradTransform());
0167         if (ix != -1) m_transform->setCurrentIndex(ix);
0168     }
0169     gl->addWidget(m_transform, 4, 1);
0170     l->setBuddy(m_transform);
0171 
0172     gl->setRowMinimumHeight(5, DialogBase::verticalSpacing());
0173 
0174     // Invert option, on/off
0175     ski = KookaSettings::self()->ocrOcradInvertItem();
0176     Q_ASSERT(ski!=nullptr);
0177     m_invert = new QCheckBox(ski->label(), w);
0178     m_invert->setChecked(KookaSettings::ocrOcradInvert());
0179     m_invert->setToolTip(ski->toolTip());
0180     gl->addWidget(m_invert, 6, 1, Qt::AlignLeft);
0181 
0182     gl->setRowMinimumHeight(7, DialogBase::verticalSpacing());
0183 
0184     // Threshold, on/off and slider
0185     ski = KookaSettings::self()->ocrOcradThresholdEnableItem();
0186     Q_ASSERT(ski!=nullptr);
0187     m_thresholdEnable = new QCheckBox(ski->label(), w);
0188     m_thresholdEnable->setChecked(KookaSettings::ocrOcradThresholdEnable());
0189     m_thresholdEnable->setToolTip(ski->toolTip());
0190     gl->addWidget(m_thresholdEnable, 8, 1, Qt::AlignLeft);
0191 
0192     ski = KookaSettings::self()->ocrOcradThresholdValueItem();
0193     Q_ASSERT(ski!=nullptr);
0194     m_thresholdSlider = new KScanSlider(w, ski->label());
0195     m_thresholdSlider->setRange(0, 100, 5, 50);
0196     m_thresholdSlider->setValue(KookaSettings::ocrOcradThresholdValue());
0197     m_thresholdSlider->setToolTip(ski->toolTip());
0198     m_thresholdSlider->spinBox()->setSuffix("%");
0199     gl->addWidget(m_thresholdSlider, 9, 1);
0200 
0201     l = new QLabel(m_thresholdSlider->label(), w);
0202     gl->addWidget(l, 9, 0);
0203     l->setBuddy(m_thresholdSlider);
0204 
0205     connect(m_thresholdEnable, &QCheckBox::toggled, m_thresholdSlider, &KScanSlider::setEnabled);
0206     m_thresholdSlider->setEnabled(m_thresholdEnable->isChecked());
0207 
0208     gl->setRowStretch(10, 1);               // for top alignment
0209     gl->setColumnStretch(1, 1);
0210 
0211     ocrShowInfo(m_ocrCmd, m_versionStr);        // show the binary and version
0212     progressBar()->setMaximum(0);           // progress animation only
0213 
0214     m_setupWidget = w;
0215     return (!m_ocrCmd.isEmpty());
0216 }
0217 
0218 
0219 void OcrOcradDialog::slotWriteConfig()
0220 {
0221     AbstractOcrDialogue::slotWriteConfig();
0222 
0223     KookaSettings::setOcrOcradBinary(getOCRCmd());
0224     KookaSettings::setOcrOcradLayoutDetection(m_layoutMode->currentIndex());
0225 
0226     int ix = m_characterSet->currentIndex();
0227     QString value = (m_characterSet->itemData(ix).toBool() ? m_characterSet->currentText() : QString());
0228     KookaSettings::setOcrOcradCharset(value);
0229 
0230     ix = m_filter->currentIndex();
0231     value = (m_filter->itemData(ix).toBool() ? m_filter->currentText() : QString());
0232     KookaSettings::setOcrOcradFilter(value);
0233 
0234     ix = m_transform->currentIndex();
0235     value = (m_transform->itemData(ix).toBool() ? m_transform->currentText() : QString());
0236     KookaSettings::setOcrOcradTransform(value);
0237 
0238     KookaSettings::setOcrOcradInvert(m_invert->isChecked());
0239     KookaSettings::setOcrOcradThresholdEnable(m_thresholdEnable->isChecked());
0240     KookaSettings::setOcrOcradThresholdValue(m_thresholdSlider->value());
0241 }
0242 
0243 
0244 void OcrOcradDialog::enableFields(bool enable)
0245 {
0246     m_setupWidget->setEnabled(enable);
0247 }
0248 
0249 
0250 /* Later: Allow interactive loading of ORF files */
0251 QString OcrOcradDialog::orfUrl() const
0252 {
0253     if (m_orfUrlRequester != nullptr) {
0254         return (m_orfUrlRequester->url().url());
0255     } else {
0256         return (QString());
0257     }
0258 }
0259 
0260 
0261 void OcrOcradDialog::getVersion(const QString &bin)
0262 {
0263     qCDebug(OCR_LOG) << "of" << bin;
0264     if (bin.isEmpty()) return;
0265 
0266     KProcess proc;
0267     proc.setOutputChannelMode(KProcess::MergedChannels);
0268     proc << bin << "-V";
0269 
0270     int status = proc.execute(5000);
0271     if (status == 0) {
0272         QByteArray output = proc.readAllStandardOutput();
0273         QRegExp rx("GNU [Oo]crad (version )?([\\d\\.]+)");
0274         if (rx.indexIn(output) > -1) {
0275             m_ocrCmd = bin;
0276             m_versionStr = rx.cap(2);
0277             m_versionNum = m_versionStr.mid(2).toInt();
0278             qCDebug(OCR_LOG) << "version" << m_versionStr << "=" << m_versionNum;
0279         }
0280     } else {
0281         qCWarning(OCR_LOG) << "failed with status" << status;
0282         m_versionStr = i18n("Error");
0283     }
0284 }
0285 
0286 QStringList OcrOcradDialog::getValidValues(const QString &opt)
0287 {
0288     QStringList result;
0289 
0290     KConfigSkeletonItem *ski = KookaSettings::self()->ocrOcradValidValuesItem();
0291     Q_ASSERT(ski!=nullptr);
0292     QString groupName = QString("%1_v%2").arg(ski->group()).arg(m_versionStr);
0293     KConfigGroup grp = KookaSettings::self()->config()->group(groupName);
0294 
0295     if (grp.hasKey(opt)) {              // values in config already
0296         qCDebug(OCR_LOG) << "option" << opt << "already in config";
0297         result = grp.readEntry(opt, QStringList());
0298     } else {                        // not in config, need to extract
0299         if (!m_ocrCmd.isEmpty()) {
0300             KProcess proc;
0301             proc.setOutputChannelMode(KProcess::MergedChannels);
0302             proc << m_ocrCmd << QString("--%1=help").arg(opt);
0303 
0304             proc.execute(5000);
0305             // Ignore return status, because '--OPTION=help' returns exit code 1
0306             QByteArray output = proc.readAllStandardOutput();
0307             QRegExp rx("Valid .*(?:are|names):([^\n]+)");
0308             if (rx.indexIn(output) > -1) {
0309                 QString values = rx.cap(1);
0310 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
0311                 result = rx.cap(1).split(QRegExp("\\s+"), Qt::SkipEmptyParts);
0312 #else
0313                 result = rx.cap(1).split(QRegExp("\\s+"), QString::SkipEmptyParts);
0314 #endif
0315             } else {
0316                 qCWarning(OCR_LOG) << "cannot get values, no match in" << output;
0317             }
0318         } else {
0319             qCWarning(OCR_LOG) << "cannot get values, no binary";
0320         }
0321     }
0322 
0323     qCDebug(OCR_LOG) << "values for" << opt << "=" << result.join(",");
0324     if (!result.isEmpty()) {
0325         grp.writeEntry(opt, result);            // save for next time
0326         grp.sync();
0327     }
0328 
0329     return (result);
0330 }