1 /************************************************************************
2  *									*
3  *  This file is part of Kooka, a scanning/OCR application using	*
4  *  Qt <http://www.qt.io> and KDE Frameworks <http://www.kde.org>.	*
5  *									*
6  *  Copyright (C) 2003-2016 Klaas Freitag <freitag@suse.de>		*
7  *                          Jonathan Marten <jjm@keelhaul.me.uk>	*
8  *									*
9  *  Kooka is free software; you can redistribute it and/or modify it	*
10  *  under the terms of the GNU Library General Public License as	*
11  *  published by the Free Software Foundation and appearing in the	*
12  *  file COPYING included in the packaging of this file;  either	*
13  *  version 2 of the License, or (at your option) any later version.	*
14  *									*
15  *  As a special exception, permission is given to link this program	*
16  *  with any version of the KADMOS OCR/ICR engine (a product of		*
17  *  reRecognition GmbH, Kreuzlingen), and distribute the resulting	*
18  *  executable without including the source code for KADMOS in the	*
19  *  source distribution.						*
20  *									*
21  *  This program is distributed in the hope that it will be useful,	*
22  *  but WITHOUT ANY WARRANTY; without even the implied warranty of	*
23  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the	*
24  *  GNU General Public License for more details.			*
25  *									*
26  *  You should have received a copy of the GNU General Public		*
27  *  License along with this program;  see the file COPYING.  If		*
28  *  not, see <http://www.gnu.org/licenses/>.				*
29  *									*
30  ************************************************************************/
31 
32 #include "ocrocraddialog.h"
33 
34 #include <qlabel.h>
35 #include <qregexp.h>
36 #include <qcombobox.h>
37 #include <qcheckbox.h>
38 #include <qspinbox.h>
39 #include <qlayout.h>
40 #include <qprogressbar.h>
41 #include <qdebug.h>
42 
43 #include <klocalizedstring.h>
44 #include <kurlrequester.h>
45 #include <kprocess.h>
46 
47 #include "kookaimage.h"
48 #include "kookapref.h"
49 #include "kookasettings.h"
50 #include "kscancontrols.h"
51 #include "dialogbase.h"
52 
53 #include "ocrocradengine.h"
54 
55 
OcrOcradDialog(AbstractOcrEngine * plugin,QWidget * pnt)56 OcrOcradDialog::OcrOcradDialog(AbstractOcrEngine *plugin, QWidget *pnt)
57     : AbstractOcrDialogue(plugin, pnt),
58       m_setupWidget(nullptr),
59       m_orfUrlRequester(nullptr),
60       m_layoutMode(nullptr),
61       m_ocrCmd(QString()),
62       m_versionNum(0),
63       m_versionStr(QString())
64 {
65 }
66 
67 
setupGui()68 bool OcrOcradDialog::setupGui()
69 {
70     AbstractOcrDialogue::setupGui();			// build the standard GUI
71 
72     // Options available vary with the OCRAD version.  So we need to find
73     // the OCRAD binary and get its version before creating the GUI.
74     m_ocrCmd = engine()->findExecutable(&KookaSettings::ocrOcradBinary, KookaSettings::self()->ocrOcradBinaryItem());
75 
76     if (!m_ocrCmd.isEmpty()) getVersion(m_ocrCmd);	// found, get its version
77     else						// not found or invalid
78     {
79         engine()->setErrorText(i18n("The OCRAD executable is not configured or is not available."));
80     }
81 
82     QWidget *w = addExtraSetupWidget();
83     QGridLayout *gl = new QGridLayout(w);
84 
85     // Layout detection mode, dependent on OCRAD version
86     KConfigSkeletonItem *ski = KookaSettings::self()->ocrOcradLayoutDetectionItem();
87     Q_ASSERT(ski!=nullptr);
88     QLabel *l = new QLabel(ski->label(), w);
89     gl->addWidget(l, 0, 0);
90 
91     m_layoutMode = new QComboBox(w);
92     m_layoutMode->addItem(i18n("No Layout Detection"), 0);
93     if (m_versionNum >= 18) {           // OCRAD 0.18 or later
94         // has only on/off
95         m_layoutMode->addItem(i18n("Layout Detection"), 1);
96     } else {                    // OCRAD 0.17 or earlier
97         // had these 3 options
98         m_layoutMode->addItem(i18n("Column Detection"), 1);
99         m_layoutMode->addItem(i18n("Full Layout Detection"), 2);
100     }
101 
102     m_layoutMode->setCurrentIndex(KookaSettings::ocrOcradLayoutDetection());
103     m_layoutMode->setToolTip(ski->toolTip());
104     gl->addWidget(m_layoutMode, 0, 1);
105     l->setBuddy(m_layoutMode);
106 
107     gl->setRowMinimumHeight(1, DialogBase::verticalSpacing());
108 
109     // Character set, auto detected values
110     QStringList vals = getValidValues("charset");
111     ski = KookaSettings::self()->ocrOcradCharsetItem();
112     Q_ASSERT(ski!=nullptr);
113     l = new QLabel(ski->label(), w);
114     gl->addWidget(l, 2, 0);
115     m_characterSet = new QComboBox(w);
116     m_characterSet->setToolTip(ski->toolTip());
117     m_characterSet->addItem(i18n("(default)"), false);
118     for (QStringList::const_iterator it = vals.constBegin(); it != vals.constEnd(); ++it) {
119         m_characterSet->addItem(*it, true);
120     }
121 
122     if (vals.count() == 0) m_characterSet->setEnabled(false);
123     else {
124         int ix = m_characterSet->findText(KookaSettings::ocrOcradCharset());
125         if (ix != -1) m_characterSet->setCurrentIndex(ix);
126     }
127     gl->addWidget(m_characterSet, 2, 1);
128     l->setBuddy(m_characterSet);
129 
130     // Filter, auto detected values
131     vals = getValidValues("filter");
132     ski = KookaSettings::self()->ocrOcradFilterItem();
133     Q_ASSERT(ski!=nullptr);
134     l = new QLabel(ski->label(), w);
135     gl->addWidget(l, 3, 0);
136     m_filter = new QComboBox(w);
137     m_filter->setToolTip(ski->toolTip());
138     m_filter->addItem(i18n("(default)"), false);
139     for (QStringList::const_iterator it = vals.constBegin(); it != vals.constEnd(); ++it) {
140         m_filter->addItem(*it, true);
141     }
142 
143     if (vals.count() == 0) m_filter->setEnabled(false);
144     else {
145         int ix = m_filter->findText(KookaSettings::ocrOcradFilter());
146         if (ix != -1) m_filter->setCurrentIndex(ix);
147     }
148     gl->addWidget(m_filter, 3, 1);
149     l->setBuddy(m_filter);
150 
151     // Transform, auto detected values
152     vals = getValidValues("transform");
153     ski = KookaSettings::self()->ocrOcradTransformItem();
154     Q_ASSERT(ski!=nullptr);
155     l = new QLabel(ski->label(), w);
156     gl->addWidget(l, 4, 0);
157     m_transform = new QComboBox(w);
158     m_transform->setToolTip(ski->toolTip());
159     m_transform->addItem(i18n("(default)"), false);
160     for (QStringList::const_iterator it = vals.constBegin(); it != vals.constEnd(); ++it) {
161         m_transform->addItem(*it, true);
162     }
163 
164     if (vals.count() == 0) m_transform->setEnabled(false);
165     else {
166         int ix = m_transform->findText(KookaSettings::ocrOcradTransform());
167         if (ix != -1) m_transform->setCurrentIndex(ix);
168     }
169     gl->addWidget(m_transform, 4, 1);
170     l->setBuddy(m_transform);
171 
172     gl->setRowMinimumHeight(5, DialogBase::verticalSpacing());
173 
174     // Invert option, on/off
175     ski = KookaSettings::self()->ocrOcradInvertItem();
176     Q_ASSERT(ski!=nullptr);
177     m_invert = new QCheckBox(ski->label(), w);
178     m_invert->setChecked(KookaSettings::ocrOcradInvert());
179     m_invert->setToolTip(ski->toolTip());
180     gl->addWidget(m_invert, 6, 1, Qt::AlignLeft);
181 
182     gl->setRowMinimumHeight(7, DialogBase::verticalSpacing());
183 
184     // Threshold, on/off and slider
185     ski = KookaSettings::self()->ocrOcradThresholdEnableItem();
186     Q_ASSERT(ski!=nullptr);
187     m_thresholdEnable = new QCheckBox(ski->label(), w);
188     m_thresholdEnable->setChecked(KookaSettings::ocrOcradThresholdEnable());
189     m_thresholdEnable->setToolTip(ski->toolTip());
190     gl->addWidget(m_thresholdEnable, 8, 1, Qt::AlignLeft);
191 
192     ski = KookaSettings::self()->ocrOcradThresholdValueItem();
193     Q_ASSERT(ski!=nullptr);
194     m_thresholdSlider = new KScanSlider(w, ski->label(), 0, 100);
195     m_thresholdSlider->setValue(KookaSettings::ocrOcradThresholdValue());
196     m_thresholdSlider->setToolTip(ski->toolTip());
197     m_thresholdSlider->spinBox()->setSuffix("%");
198     gl->addWidget(m_thresholdSlider, 9, 1);
199 
200     l = new QLabel(m_thresholdSlider->label(), w);
201     gl->addWidget(l, 9, 0);
202     l->setBuddy(m_thresholdSlider);
203 
204     connect(m_thresholdEnable, &QCheckBox::toggled, m_thresholdSlider, &KScanSlider::setEnabled);
205     m_thresholdSlider->setEnabled(m_thresholdEnable->isChecked());
206 
207     gl->setRowStretch(10, 1);				// for top alignment
208     gl->setColumnStretch(1, 1);
209 
210     ocrShowInfo(m_ocrCmd, m_versionStr);		// show the binary and version
211     progressBar()->setMaximum(0);			// progress animation only
212 
213     m_setupWidget = w;
214     return (!m_ocrCmd.isEmpty());
215 }
216 
217 
slotWriteConfig()218 void OcrOcradDialog::slotWriteConfig()
219 {
220     AbstractOcrDialogue::slotWriteConfig();
221 
222     KookaSettings::setOcrOcradBinary(getOCRCmd());
223     KookaSettings::setOcrOcradLayoutDetection(m_layoutMode->currentIndex());
224 
225     int ix = m_characterSet->currentIndex();
226     QString value = (m_characterSet->itemData(ix).toBool() ? m_characterSet->currentText() : QString());
227     KookaSettings::setOcrOcradCharset(value);
228 
229     ix = m_filter->currentIndex();
230     value = (m_filter->itemData(ix).toBool() ? m_filter->currentText() : QString());
231     KookaSettings::setOcrOcradFilter(value);
232 
233     ix = m_transform->currentIndex();
234     value = (m_transform->itemData(ix).toBool() ? m_transform->currentText() : QString());
235     KookaSettings::setOcrOcradTransform(value);
236 
237     KookaSettings::setOcrOcradInvert(m_invert->isChecked());
238     KookaSettings::setOcrOcradThresholdEnable(m_thresholdEnable->isChecked());
239     KookaSettings::setOcrOcradThresholdValue(m_thresholdSlider->value());
240 }
241 
242 
enableFields(bool enable)243 void OcrOcradDialog::enableFields(bool enable)
244 {
245     m_setupWidget->setEnabled(enable);
246 }
247 
248 
249 /* Later: Allow interactive loading of ORF files */
orfUrl() const250 QString OcrOcradDialog::orfUrl() const
251 {
252     if (m_orfUrlRequester != nullptr) {
253         return (m_orfUrlRequester->url().url());
254     } else {
255         return (QString());
256     }
257 }
258 
259 
getVersion(const QString & bin)260 void OcrOcradDialog::getVersion(const QString &bin)
261 {
262     //qDebug() << "of" << bin;
263     if (bin.isEmpty()) {
264         return;
265     }
266 
267     KProcess proc;
268     proc.setOutputChannelMode(KProcess::MergedChannels);
269     proc << bin << "-V";
270 
271     int status = proc.execute(5000);
272     if (status == 0) {
273         QByteArray output = proc.readAllStandardOutput();
274         QRegExp rx("GNU Ocrad (version )?([\\d\\.]+)");
275         if (rx.indexIn(output) > -1) {
276             m_ocrCmd = bin;
277             m_versionStr = rx.cap(2);
278             m_versionNum = m_versionStr.mid(2).toInt();
279             //qDebug() << "version" << m_versionStr << "=" << m_versionNum;
280         }
281     } else {
282         //qDebug() << "failed with status" << status;
283         m_versionStr = i18n("Error");
284     }
285 }
286 
getValidValues(const QString & opt)287 QStringList OcrOcradDialog::getValidValues(const QString &opt)
288 {
289     QStringList result;
290 
291     KConfigSkeletonItem *ski = KookaSettings::self()->ocrOcradValidValuesItem();
292     Q_ASSERT(ski!=nullptr);
293     QString groupName = QString("%1_v%2").arg(ski->group()).arg(m_versionStr);
294     KConfigGroup grp = KookaSettings::self()->config()->group(groupName);
295 
296     if (grp.hasKey(opt)) {				// values in config already
297         //qDebug() << "option" << opt << "already in config";
298         result = grp.readEntry(opt, QStringList());
299     } else {						// not in config, need to extract
300         if (!m_ocrCmd.isEmpty()) {
301             KProcess proc;
302             proc.setOutputChannelMode(KProcess::MergedChannels);
303             proc << m_ocrCmd << QString("--%1=help").arg(opt);
304 
305             proc.execute(5000);
306             // Ignore return status, because '--OPTION=help' returns exit code 1
307             QByteArray output = proc.readAllStandardOutput();
308             QRegExp rx("Valid .*are:([^\n]+)");
309             if (rx.indexIn(output) > -1) {
310                 QString values = rx.cap(1);
311                 result = rx.cap(1).split(QRegExp("\\s+"), QString::SkipEmptyParts);
312             } else {
313                 //qDebug() << "cannot get values, no match in" << output;
314             }
315         } else {
316             //qDebug() << "cannot get values, no binary";
317         }
318     }
319 
320     //qDebug() << "values for" << opt << "=" << result.join(",");
321     if (!result.isEmpty()) {
322         grp.writeEntry(opt, result);			// save for next time
323         grp.sync();
324     }
325 
326     return (result);
327 }
328