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