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 }