File indexing completed on 2024-04-28 15:39:39

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) 2000-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 "abstractocrdialogue.h"
0033 
0034 #include <qlabel.h>
0035 #include <qgroupbox.h>
0036 #include <qcheckbox.h>
0037 #include <qlayout.h>
0038 #include <qprogressbar.h>
0039 #include <qapplication.h>
0040 #include <qradiobutton.h>
0041 #include <qpushbutton.h>
0042 #include <qicon.h>
0043 #include <qstandardpaths.h>
0044 
0045 #include <klocalizedstring.h>
0046 #include <kstandardguiitem.h>
0047 #include <kseparator.h>
0048 
0049 #include <kio/job.h>
0050 #include <kio/previewjob.h>
0051 
0052 #include <sonnet/configdialog.h>
0053 
0054 #include "kookasettings.h"
0055 
0056 #include "imagecanvas.h"
0057 #include "dialogbase.h"
0058 #include "pluginmanager.h"
0059 #include "ocr_logging.h"
0060 
0061 
0062 AbstractOcrDialogue::AbstractOcrDialogue(AbstractOcrEngine *plugin, QWidget *pnt)
0063     : KPageDialog(pnt),
0064       m_plugin(plugin),
0065       m_setupPage(nullptr),
0066       m_sourcePage(nullptr),
0067       m_enginePage(nullptr),
0068       m_spellPage(nullptr),
0069       m_debugPage(nullptr),
0070       m_previewPix(nullptr),
0071       m_previewLabel(nullptr),
0072       m_wantDebugCfg(true),
0073       m_cbRetainFiles(nullptr),
0074       m_cbVerboseDebug(nullptr),
0075       m_retainFiles(false),
0076       m_verboseDebug(false),
0077       m_lVersion(nullptr),
0078       m_progress(nullptr)
0079 {
0080     setModal(true);
0081 
0082     // The original buttons used in KDE4 were User1=Start, User2=Stop, Close.
0083     // Because the button actions must not simply accept or reject the dialogue
0084     // (closing it in both cases), we need to carefully choose the standard
0085     // buttons so that they do not perform those actions.  This means that the
0086     // button cannot have an AcceptRole, RejectRole, YesRole or NoRole because
0087     // those all either accept or reject the dialogue.  The dialogue needs to
0088     // stay open while OCR is in progress, because it shows the progress and
0089     // has the "Stop OCR" button.
0090     //
0091     // The buttons chosen also affect the placement, but the dialogue actions
0092     // are more important!
0093     //
0094     // So the buttons used with Qt5 are Discard=Start, Apply=Stop, Close.  This
0095     // at least places the buttons in the intended order (in the standard KDE
0096     // style), even though the buttons used bear no relation to their function.
0097 
0098     QDialogButtonBox *bb = buttonBox();
0099     setStandardButtons(QDialogButtonBox::Discard|QDialogButtonBox::Apply|QDialogButtonBox::Close);
0100     bb->button(QDialogButtonBox::Discard)->setDefault(true);
0101     setWindowTitle(i18n("Optical Character Recognition"));
0102 
0103     KGuiItem::assign(bb->button(QDialogButtonBox::Discard), KGuiItem(i18n("Start OCR"), "system-run", i18n("Start the Optical Character Recognition process")));
0104     KGuiItem::assign(bb->button(QDialogButtonBox::Apply), KGuiItem(i18n("Stop OCR"), "process-stop", i18n("Stop the Optical Character Recognition process")));
0105 
0106     // Signals which tell our caller what the user is doing
0107     connect(bb->button(QDialogButtonBox::Discard), &QAbstractButton::clicked, this, &AbstractOcrDialogue::slotStartOCR);
0108     connect(bb->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, this, &AbstractOcrDialogue::signalOcrStop);
0109     connect(this, &QDialog::rejected, this, &AbstractOcrDialogue::signalOcrClose);
0110 
0111     m_previewSize.setWidth(380);            // minimum preview size
0112     m_previewSize.setHeight(250);
0113 
0114     bb->button(QDialogButtonBox::Discard)->setEnabled(true);    // Start OCR
0115     bb->button(QDialogButtonBox::Apply)->setEnabled(false); // Stop OCR
0116     bb->button(QDialogButtonBox::Close)->setEnabled(true);  // Close
0117 
0118     // This appears to be necessary to ensure that "Start OCR" becomes the
0119     // default button.
0120     bb->button(QDialogButtonBox::Discard)->setFocus(Qt::OtherFocusReason);
0121 }
0122 
0123 
0124 bool AbstractOcrDialogue::setupGui()
0125 {
0126     setupSetupPage();
0127     setupSpellPage();
0128     setupSourcePage();
0129     setupEnginePage();
0130     // TODO: preferences option for whether debug is shown
0131     if (m_wantDebugCfg) setupDebugPage();
0132 
0133     return (true);
0134 }
0135 
0136 
0137 void AbstractOcrDialogue::setupSetupPage()
0138 {
0139     QWidget *w = new QWidget(this);
0140     QGridLayout *gl = new QGridLayout(w);
0141     Q_UNUSED(gl);                   // retrieved via layout()
0142 
0143     m_progress = new QProgressBar(this);
0144     m_progress->setVisible(false);
0145 
0146     m_setupPage = addPage(w, i18n("Setup"));
0147 
0148     const AbstractPluginInfo *info = engine()->pluginInfo();
0149     m_setupPage->setHeader(i18n("Optical Character Recognition using %1", info->name));
0150     m_setupPage->setIcon(QIcon::fromTheme("ocr"));
0151 }
0152 
0153 
0154 QWidget *AbstractOcrDialogue::addExtraPageWidget(KPageWidgetItem *page, QWidget *wid, bool stretchBefore)
0155 {
0156     QGridLayout *gl = static_cast<QGridLayout *>(page->widget()->layout());
0157     int nextrow = gl->rowCount();
0158     // rowCount() seems to return 1 even if the layout is empty...
0159     if (gl->itemAtPosition(0, 0) == nullptr) {
0160         nextrow = 0;
0161     }
0162 
0163     if (stretchBefore) {                // stretch before new row
0164         gl->setRowStretch(nextrow, 1);
0165         ++nextrow;
0166     } else if (nextrow > 0) {           // something there already,
0167         // add separator line
0168         gl->addWidget(new KSeparator(Qt::Horizontal, this), nextrow, 0, 1, 2);
0169         ++nextrow;
0170     }
0171 
0172     if (wid == nullptr) {
0173         wid = new QWidget(this);
0174     }
0175     gl->addWidget(wid, nextrow, 0, 1, 2);
0176 
0177     return (wid);
0178 }
0179 
0180 
0181 QWidget *AbstractOcrDialogue::addExtraSetupWidget(QWidget *wid, bool stretchBefore)
0182 {
0183     return (addExtraPageWidget(m_setupPage, wid, stretchBefore));
0184 }
0185 
0186 
0187 void AbstractOcrDialogue::ocrShowInfo(const QString &binary, const QString &version)
0188 {
0189     QWidget *w = addExtraEngineWidget();        // engine path/version/icon
0190     QGridLayout *gl = new QGridLayout(w);
0191 
0192     QLabel *l = new QLabel(i18n("Executable:"), w);
0193     gl->addWidget(l, 0, 0, Qt::AlignLeft | Qt::AlignTop);
0194 
0195     l = new QLabel((!binary.isEmpty() ? xi18nc("@info", "<filename>%1</filename>", binary) : i18n("Not found")), w);
0196     gl->addWidget(l, 0, 1, Qt::AlignLeft | Qt::AlignTop);
0197 
0198     l = new QLabel(i18n("Version:"), w);
0199     gl->addWidget(l, 1, 0, Qt::AlignLeft | Qt::AlignTop);
0200 
0201     m_lVersion = new QLabel((!version.isEmpty() ? version : i18n("Unknown")), w);
0202     gl->addWidget(m_lVersion, 1, 1, Qt::AlignLeft | Qt::AlignTop);
0203 
0204     // Find the logo and display it if available
0205     const AbstractPluginInfo *info = engine()->pluginInfo();
0206     QString logoFile = KIconLoader::global()->iconPath(info->icon, KIconLoader::NoGroup, true);
0207     if (!logoFile.isNull())
0208     {
0209         QLabel *l = new QLabel(w);
0210         l->setPixmap(QPixmap(logoFile));
0211         gl->addWidget(l, 0, 3, 3, 1, Qt::AlignRight);
0212     }
0213 
0214     gl->setColumnStretch(2, 1);
0215 }
0216 
0217 
0218 void AbstractOcrDialogue::ocrShowVersion(const QString &version)
0219 {
0220     if (m_lVersion != nullptr) {
0221         m_lVersion->setText(version);
0222     }
0223 }
0224 
0225 
0226 void AbstractOcrDialogue::setupSourcePage()
0227 {
0228     QWidget *w = new QWidget(this);
0229     QGridLayout *gl = new QGridLayout(w);
0230 
0231     // These labels are filled with the preview pixmap and image
0232     // information in introduceImage()
0233     m_previewPix = new QLabel(i18n("No preview available"), w);
0234     m_previewPix->setPixmap(QPixmap());
0235     m_previewPix->setMinimumSize(m_previewSize.width() + 2*DialogBase::horizontalSpacing(),
0236                                  m_previewSize.height() + 2*DialogBase::verticalSpacing());
0237     m_previewPix->setAlignment(Qt::AlignCenter);
0238     m_previewPix->setFrameStyle(QFrame::Panel | QFrame::Sunken);
0239     gl->addWidget(m_previewPix, 0, 0);
0240     gl->setRowStretch(0, 1);
0241 
0242     m_previewLabel = new QLabel(i18n("No information available"), w);
0243     gl->addWidget(m_previewLabel, 1, 0, Qt::AlignHCenter);
0244 
0245     m_sourcePage = addPage(w, i18n("Source"));
0246     m_sourcePage->setHeader(i18n("Source Image Information"));
0247     m_sourcePage->setIcon(QIcon::fromTheme("dialog-information"));
0248 }
0249 
0250 
0251 void AbstractOcrDialogue::setupEnginePage()
0252 {
0253     QWidget *w = new QWidget(this);         // engine title/logo/description
0254     QGridLayout *gl = new QGridLayout(w);
0255 
0256     const AbstractPluginInfo *info = engine()->pluginInfo();
0257     QLabel *l = new QLabel(info->description, w);
0258     l->setWordWrap(true);
0259     l->setOpenExternalLinks(true);
0260     gl->addWidget(l, 0, 0, 1, 2, Qt::AlignTop);
0261 
0262     gl->setRowStretch(2, 1);
0263     gl->setColumnStretch(0, 1);
0264 
0265     m_enginePage = addPage(w, i18n("OCR Engine"));
0266     m_enginePage->setHeader(i18n("OCR Engine Information"));
0267     m_enginePage->setIcon(QIcon::fromTheme("application-x-executable"));
0268 }
0269 
0270 
0271 QWidget *AbstractOcrDialogue::addExtraEngineWidget(QWidget *wid, bool stretchBefore)
0272 {
0273     return (addExtraPageWidget(m_enginePage, wid, stretchBefore));
0274 }
0275 
0276 
0277 void AbstractOcrDialogue::setupSpellPage()
0278 {
0279     QWidget *w = new QWidget(this);
0280     QGridLayout *gl = new QGridLayout(w);
0281 
0282     // row 0: background checking group box
0283     m_gbBackgroundCheck = new QGroupBox(i18n("Highlight misspelled words"), w);
0284     m_gbBackgroundCheck->setCheckable(true);
0285 
0286     QGridLayout *gl1 = new QGridLayout(m_gbBackgroundCheck);
0287     m_gbBackgroundCheck->setLayout(gl1);
0288 
0289     m_rbGlobalSpellSettings = new QRadioButton(i18n("Use the system spell configuration"), w);
0290     gl1->addWidget(m_rbGlobalSpellSettings, 0, 0);
0291     m_rbCustomSpellSettings = new QRadioButton(i18n("Use custom spell configuration"), w);
0292     gl1->addWidget(m_rbCustomSpellSettings, 1, 0);
0293     m_pbCustomSpellDialog = new QPushButton(i18n("Custom Spell Configuration..."), w);
0294     gl1->addWidget(m_pbCustomSpellDialog, 2, 0, Qt::AlignRight);
0295     connect(m_rbCustomSpellSettings, &QAbstractButton::toggled, m_pbCustomSpellDialog, &QWidget::setEnabled);
0296     connect(m_pbCustomSpellDialog, &QAbstractButton::clicked, this, &AbstractOcrDialogue::slotCustomSpellDialog);
0297     gl->addWidget(m_gbBackgroundCheck, 0, 0);
0298 
0299     // row 1: space
0300     gl->setRowMinimumHeight(1, 2*DialogBase::verticalSpacing());
0301 
0302     // row 2: interactive checking group box
0303     m_gbInteractiveCheck = new QGroupBox(i18n("Start interactive spell check"), w);
0304     m_gbInteractiveCheck->setCheckable(true);
0305 
0306     QGridLayout *gl2 = new QGridLayout(m_gbInteractiveCheck);
0307     m_gbInteractiveCheck->setLayout(gl2);
0308 
0309     QLabel *l = new QLabel(i18n("Custom spell settings above do not affect this spelling check, use the language setting in the dialog to change the dictionary language."), w);
0310     l->setWordWrap(true);
0311     gl2->addWidget(l, 0, 0);
0312 
0313     gl->addWidget(m_gbInteractiveCheck, 2, 0);
0314 
0315     // row 3: stretch
0316     gl->setRowStretch(3, 1);
0317 
0318     // Apply settings
0319     m_gbBackgroundCheck->setChecked(KookaSettings::ocrSpellBackgroundCheck());
0320     m_gbInteractiveCheck->setChecked(KookaSettings::ocrSpellInteractiveCheck());
0321 
0322 #ifndef KF5
0323     const bool customSettings = KookaSettings::ocrSpellCustomSettings();
0324 #else
0325     const bool customSettings = false;
0326     m_rbCustomSpellSettings->setEnabled(false);
0327 #endif
0328     m_rbGlobalSpellSettings->setChecked(!customSettings);
0329     m_rbCustomSpellSettings->setChecked(customSettings);
0330     m_pbCustomSpellDialog->setEnabled(customSettings);
0331 
0332     m_spellPage = addPage(w, i18n("Spell Check"));
0333     m_spellPage->setHeader(i18n("OCR Result Spell Checking"));
0334     m_spellPage->setIcon(QIcon::fromTheme("tools-check-spelling"));
0335 }
0336 
0337 
0338 void AbstractOcrDialogue::setupDebugPage()
0339 {
0340     QWidget *w = new QWidget(this);
0341     QGridLayout *gl = new QGridLayout(w);
0342 
0343     m_cbRetainFiles = new QCheckBox(i18n("Retain temporary files"), w);
0344     gl->addWidget(m_cbRetainFiles, 0, 0, Qt::AlignTop);
0345 
0346     m_cbVerboseDebug = new QCheckBox(i18n("Verbose message output"), w);
0347     gl->addWidget(m_cbVerboseDebug, 1, 0, Qt::AlignTop);
0348 
0349     gl->setRowStretch(2, 1);
0350 
0351     m_debugPage = addPage(w, i18n("Debugging"));
0352     m_debugPage->setHeader(i18n("OCR Debugging"));
0353     m_debugPage->setIcon(QIcon::fromTheme("tools-report-bug"));
0354 }
0355 
0356 
0357 void AbstractOcrDialogue::stopAnimation()
0358 {
0359     if (m_progress != nullptr) {
0360         m_progress->setVisible(false);
0361     }
0362 }
0363 
0364 
0365 void AbstractOcrDialogue::startAnimation()
0366 {
0367     // If the progress bar range has been set to (0,0) then the engine will
0368     // not be providing a detailed progress percentage, only a busy indication.
0369     // In this case, set the value to 1 now to start the animation.
0370     // If there is a maximum then there will be a detailed progress
0371     // percentage, so set the initial value to the minimum.
0372     m_progress->setValue(m_progress->maximum()==0 ? 1 : m_progress->minimum());
0373 
0374     if (!m_progress->isVisible()) {         // progress bar not added yet
0375         addExtraSetupWidget(m_progress, true);
0376         m_progress->setVisible(true);
0377     }
0378 }
0379 
0380 // Not sure why this uses an asynchronous preview job for the image thumbnail
0381 // (if it is possible, i.e. the image is file bound) as opposed to just scaling
0382 // the image (which is always loaded at this point, i.e. it is already in memory).
0383 // Possibly because scaling a potentially very large image could introduce a
0384 // significant delay in opening the dialogue box, so making the GUI appear
0385 // less responsive.  So we'll keep the preview job for now.
0386 //
0387 // We now bring you a mild rant...
0388 //
0389 // What on earth happened to KFileMetaInfo in KDE4?  This used to have a fairly
0390 // reasonable API, returning a list of key-value pairs grouped into sensible
0391 // categories with readable strings available for each.  Now the groups have
0392 // gone (so for example methods such as preferredGroups(), albeit being marked as
0393 // 'deprecated', return an empty list!) and the key of each entry is an ontology
0394 // URL.  Not sure what to do with this URL (although I'm sure it must be of
0395 // interest to something), and it doesn't even return the minimal useful
0396 // information (e.g. the size/depth) for many image file types anyway.
0397 //
0398 // Could this be why the "Meta Info" tab of the file properties dialogue also
0399 // seems to have disappeared?
0400 //
0401 // So forget about KFileMetaInfo here, just display a simple label with the
0402 // image size and depth (which information we already have available).
0403 
0404 void AbstractOcrDialogue::introduceImage(ScanImage::Ptr img)
0405 {
0406     if (img.isNull())
0407     {
0408         if (m_previewLabel!=nullptr) m_previewLabel->setText(i18n("No image"));
0409         return;
0410     }
0411 
0412     qCDebug(OCR_LOG) << "url" << img->url() << "filebound" << img->isFileBound();
0413 
0414     if (img->isFileBound()) {           // image backed by a file
0415         /* Start to create a preview job for the thumb */
0416         KFileItemList fileItems;
0417         fileItems.append(KFileItem(img->url()));
0418 
0419         KIO::PreviewJob *job = KIO::filePreview(fileItems, QSize(m_previewSize.width(), m_previewSize.height()));
0420         if (job!=nullptr) {
0421             job->setIgnoreMaximumSize();
0422             connect(job, &KIO::PreviewJob::gotPreview, this, &AbstractOcrDialogue::slotGotPreview);
0423         }
0424     } else {                    // selection only in memory,
0425         // do the preview ourselves
0426         QImage qimg = img->scaled(m_previewSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
0427         slotGotPreview(KFileItem(), QPixmap::fromImage(qimg));
0428     }
0429 
0430     if (m_previewLabel != nullptr) {
0431         KLocalizedString str = img->isFileBound() ? ki18n("Image: %1") : ki18n("Selection: %1");
0432         m_previewLabel->setText(str.subs(ImageCanvas::imageInfoString(img.data())).toString());
0433     }
0434 }
0435 
0436 
0437 bool AbstractOcrDialogue::keepTempFiles() const
0438 {
0439     return (m_retainFiles);
0440 }
0441 
0442 
0443 bool AbstractOcrDialogue::verboseDebug() const
0444 {
0445     return (m_verboseDebug);
0446 }
0447 
0448 
0449 void AbstractOcrDialogue::slotGotPreview(const KFileItem &item, const QPixmap &newPix)
0450 {
0451     qCDebug(OCR_LOG) << "pixmap" << newPix.size();
0452     if (m_previewPix != nullptr) {
0453         m_previewPix->setText(QString());
0454         m_previewPix->setPixmap(newPix);
0455     }
0456 }
0457 
0458 
0459 void AbstractOcrDialogue::slotWriteConfig()
0460 {
0461     KookaSettings::setOcrSpellBackgroundCheck(m_gbBackgroundCheck->isChecked());
0462     KookaSettings::setOcrSpellInteractiveCheck(m_gbInteractiveCheck->isChecked());
0463     KookaSettings::setOcrSpellCustomSettings(m_rbCustomSpellSettings->isChecked());
0464     KookaSettings::self()->save();
0465     // deliberately not saving the OCR debug configuration
0466 }
0467 
0468 
0469 void AbstractOcrDialogue::slotStartOCR()
0470 {
0471     setCurrentPage(m_setupPage);            // force back to first page
0472 
0473     m_retainFiles = (m_cbRetainFiles != nullptr && m_cbRetainFiles->isChecked());
0474     m_verboseDebug = (m_cbVerboseDebug != nullptr && m_cbVerboseDebug->isChecked());
0475 
0476     slotWriteConfig();                  // save configuration
0477     emit signalOcrStart();              // start the OCR process
0478 }
0479 
0480 
0481 void AbstractOcrDialogue::enableGUI(bool running)
0482 {
0483     m_sourcePage->setEnabled(!running);
0484     m_enginePage->setEnabled(!running);
0485     if (m_spellPage != nullptr) m_spellPage->setEnabled(!running);
0486     if (m_debugPage != nullptr) m_debugPage->setEnabled(!running);
0487     enableFields(!running);             // engine's GUI widgets
0488 
0489     if (running) startAnimation();          // start our progress bar
0490     else stopAnimation();               // stop our progress bar
0491 
0492     QDialogButtonBox *bb = buttonBox();
0493     bb->button(QDialogButtonBox::Discard)->setEnabled(!running);    // Start OCR
0494     bb->button(QDialogButtonBox::Apply)->setEnabled(running);       // Stop OCR
0495     bb->button(QDialogButtonBox::Close)->setEnabled(!running);      // Close
0496 
0497     QApplication::processEvents();          // ensure GUI up-to-date
0498 }
0499 
0500 
0501 bool AbstractOcrDialogue::wantInteractiveSpellCheck() const
0502 {
0503     return (m_gbInteractiveCheck->isChecked());
0504 }
0505 
0506 
0507 bool AbstractOcrDialogue::wantBackgroundSpellCheck() const
0508 {
0509     return (m_gbBackgroundCheck->isChecked());
0510 }
0511 
0512 
0513 QString AbstractOcrDialogue::customSpellConfigFile() const
0514 {
0515     if (m_rbCustomSpellSettings->isChecked()) {     // our application config
0516         return (KSharedConfig::openConfig()->name());
0517     }
0518     return ("sonnetrc");                // Sonnet global settings
0519 }
0520 
0521 
0522 QProgressBar *AbstractOcrDialogue::progressBar() const
0523 {
0524     return (m_progress);
0525 }
0526 
0527 
0528 void AbstractOcrDialogue::slotCustomSpellDialog()
0529 {
0530 #ifndef KF5
0531     // TODO: Sonnet in KF5 appears to no longer allow a custom configuration,
0532     // QSettings("KDE","Sonnet") is hardwired in Settings::restore() in
0533     // sonnet/src/core/settings.cpp
0534     // See also KookaView::slotSetOcrSpellConfig()
0535     // It may be possible, though, to configure only the language;
0536     // see http://api.kde.org/frameworks-api/frameworks5-apidocs/sonnet/html/classSonnet_1_1ConfigDialog.html
0537     Sonnet::ConfigDialog d(this);
0538 //    Sonnet::ConfigDialog d(KSharedConfig::openConfig().data(), this);
0539     d.exec();                       // save to our application config
0540 #endif
0541 }