File indexing completed on 2025-01-19 03:52:37

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2022-08-26
0007  * Description : Text converter batch dialog
0008  *
0009  * SPDX-FileCopyrightText: 2008-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  * SPDX-FileCopyrightText: 2022      by Quoc Hung Tran <quochungtran1999 at gmail dot com>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "textconverterdialog.h"
0017 
0018 // C++ includes
0019 
0020 #include <sstream>
0021 
0022 // Qt includes
0023 
0024 #include <QGridLayout>
0025 #include <QTimer>
0026 #include <QStyle>
0027 #include <QApplication>
0028 #include <QMessageBox>
0029 #include <QTabWidget>
0030 #include <QMenu>
0031 #include <QScrollArea>
0032 
0033 // KDE includes
0034 
0035 #include <klocalizedstring.h>
0036 
0037 // Local includes
0038 
0039 #include "digikam_debug.h"
0040 #include "digikam_config.h"
0041 #include "dcombobox.h"
0042 #include "dprogresswdg.h"
0043 #include "textconverterlist.h"
0044 #include "textconvertersettings.h"
0045 #include "ocroptions.h"
0046 #include "dtextedit.h"
0047 #include "dlayoutbox.h"
0048 #include "textconverterthread.h"
0049 #include "textconverteraction.h"
0050 #include "tesseractbinary.h"
0051 #include "dbinarysearch.h"
0052 
0053 using namespace Digikam;
0054 
0055 namespace DigikamGenericTextConverterPlugin
0056 {
0057 
0058 class TextConverterDialog::Private
0059 {
0060 public:
0061 
0062     enum OcrTabs
0063     {
0064         RecognitionTab = 0,
0065         ReviewTab
0066     };
0067 
0068     enum ProcessActions
0069     {
0070         ProcessAll = 0,
0071         ProcessSelected
0072     };
0073 
0074 public:
0075 
0076     Private()
0077       : busy                (false),
0078         progressBar         (nullptr),
0079         thread              (nullptr),
0080         iface               (nullptr),
0081         listView            (nullptr),
0082         ocrSettings         (nullptr),
0083         textedit            (nullptr),
0084         saveTextButton      (nullptr),
0085         currentSelectedItem (nullptr),
0086         binWidget           (nullptr),
0087         tabView             (nullptr)
0088     {
0089     }
0090 
0091     bool                              busy;
0092 
0093     QList<QUrl>                       fileList;
0094 
0095     QMap<QUrl, QString>               textEditList;
0096 
0097     DProgressWdg*                     progressBar;
0098 
0099     TextConverterActionThread*        thread;
0100 
0101     DInfoInterface*                   iface;
0102 
0103     TextConverterList*                listView;
0104 
0105     TextConverterSettings*            ocrSettings;
0106 
0107     DTextEdit*                        textedit;
0108 
0109     QPushButton*                      saveTextButton;
0110 
0111     TextConverterListViewItem*        currentSelectedItem;
0112 
0113     TesseractBinary                   tesseractBin;
0114     DBinarySearch*                    binWidget;
0115 
0116     QTabWidget*                       tabView;
0117 };
0118 
0119 TextConverterDialog::TextConverterDialog(QWidget* const parent, DInfoInterface* const iface)
0120     : DPluginDialog(parent, QLatin1String("Text Converter Dialog")),
0121       d            (new Private)
0122 {
0123     setWindowTitle(i18nc("@title:window", "OCR Text Converter"));
0124     setModal(true);
0125 
0126     d->iface          = iface;
0127 
0128     const int spacing = qMin(QApplication::style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing),
0129                              QApplication::style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing));
0130 
0131     m_buttons->addButton(QDialogButtonBox::Close);
0132     m_buttons->addButton(QDialogButtonBox::Ok);
0133     m_buttons->button(QDialogButtonBox::Ok)->setText(i18nc("@action:button", "&Start OCR"));
0134     m_buttons->button(QDialogButtonBox::Ok)->setDisabled(true);
0135     m_buttons->addButton(QDialogButtonBox::Reset);
0136     m_buttons->button(QDialogButtonBox::Reset)->setText(i18nc("@action: button", "&Default"));
0137     m_buttons->button(QDialogButtonBox::Reset)->setIcon(QIcon::fromTheme(QLatin1String("document-revert")));
0138     m_buttons->button(QDialogButtonBox::Reset)->setToolTip(i18nc("@info:tooltip",
0139                                                            "Revert current settings to default values."));
0140 
0141     QWidget* const mainWidget = new QWidget(this);
0142     QVBoxLayout* const vbx    = new QVBoxLayout(this);
0143     vbx->addWidget(mainWidget);
0144     vbx->addWidget(m_buttons);
0145     setLayout(vbx);
0146 
0147     //-------------------------------------------------------------------------------------------
0148 
0149     QGridLayout* const mainLayout = new QGridLayout(mainWidget);
0150     d->listView                   = new TextConverterList(mainWidget);
0151     d->progressBar                = new DProgressWdg(mainWidget);
0152     d->progressBar->reset();
0153     d->progressBar->setVisible(false);
0154 
0155     d->listView->appendControlButtonsWidget(d->progressBar);
0156     QBoxLayout* const blay        = d->listView->setControlButtonsPlacement(DItemsList::ControlButtonsBelow);
0157     blay->setStretchFactor(d->progressBar, 20);
0158 
0159     d->tabView                    = new QTabWidget(mainWidget);
0160 
0161     // -- Recognition tab
0162 
0163     QScrollArea* const recsv      = new QScrollArea(d->tabView);
0164     DVBox* const recognitionTab   = new DVBox(recsv->viewport());
0165     QLabel* const tesseractLabel  = new QLabel(i18nc("@label", "This tool use the %1 open-source "
0166                                                "engine to perform Optical Characters Recognition. "
0167                                                "Tesseract program and the desired languages modules must "
0168                                                "be installed on your system.",
0169                                                 QString::fromUtf8("<a href='https://github.com/tesseract-ocr/tesseract'>Tesseract</a>")),
0170                                                 recognitionTab);
0171     tesseractLabel->setWordWrap(true);
0172     tesseractLabel->setOpenExternalLinks(true);
0173 
0174     d->binWidget                  = new DBinarySearch(recognitionTab);
0175     d->binWidget->addBinary(d->tesseractBin);
0176 
0177 #ifdef Q_OS_MACOS
0178 
0179     // Std Macports install
0180 
0181     d->binWidget->addDirectory(QLatin1String("/opt/local/bin"));
0182 
0183     // digiKam Bundle PKG install
0184 
0185     d->binWidget->addDirectory(macOSBundlePrefix() + QLatin1String("bin"));
0186 
0187 #endif
0188 
0189 #ifdef Q_OS_WIN
0190 
0191     d->binWidget->addDirectory(QLatin1String("C:/Program Files/Tesseract-OCR"));
0192 
0193 #endif
0194 
0195 #ifdef Q_OS_UNIX
0196 
0197     d->binWidget->addDirectory(QLatin1String("/usr/bin"));
0198     d->binWidget->addDirectory(QLatin1String("/usr/local/bin"));
0199     d->binWidget->addDirectory(QLatin1String("/bin"));
0200 
0201 #endif
0202 
0203     d->ocrSettings                = new TextConverterSettings(recognitionTab);
0204 
0205 
0206     recognitionTab->setContentsMargins(spacing, spacing, spacing, spacing);
0207     recognitionTab->setSpacing(spacing);
0208 
0209     recsv->setFrameStyle(QFrame::NoFrame);
0210     recsv->setWidgetResizable(true);
0211     recsv->setWidget(recognitionTab);
0212 
0213     d->tabView->insertTab(Private::RecognitionTab, recsv, i18nc("@title", "Text Recognition"));
0214 
0215     // --- Review tab --------------------------------------------------------------------------
0216 
0217     DVBox* const reviewTab = new DVBox(d->tabView);
0218     d->textedit            = new DTextEdit(0, reviewTab);
0219     d->textedit->setPlaceholderText(i18nc("@info", "After to process recognition, "
0220                                                    "double-click on one item to "
0221                                                    "display recognized text here. "
0222                                                    "You can review words and fix "
0223                                                    "if necessary. Press the Save "
0224                                                    "button to record your changes."));
0225     reviewTab->setStretchFactor(d->textedit, 100);
0226 
0227     d->saveTextButton      = new QPushButton(reviewTab);
0228     d->saveTextButton->setText(i18nc("@action: button", "Save"));
0229     d->saveTextButton->setEnabled(false);
0230 
0231     reviewTab->setContentsMargins(spacing, spacing, spacing, spacing);
0232     reviewTab->setSpacing(spacing);
0233 
0234     d->tabView->insertTab(Private::ReviewTab, reviewTab, i18nc("@title", "Text Review"));
0235 
0236     //-------------------------------------------------------------------------------------------
0237 
0238     mainLayout->addWidget(d->listView, 0, 0, 1, 1);
0239     mainLayout->addWidget(d->tabView,  0, 1, 1, 1);
0240     mainLayout->setColumnStretch(0, 7);
0241     mainLayout->setColumnStretch(1, 3);
0242     mainLayout->setRowStretch(0, 10);
0243     mainLayout->setContentsMargins(QMargins());
0244 
0245     // ---------------------------------------------------------------
0246 
0247     d->thread = new TextConverterActionThread(this);
0248 
0249     connect(d->thread, SIGNAL(signalStarting(DigikamGenericTextConverterPlugin::TextConverterActionData)),
0250             this, SLOT(slotTextConverterAction(DigikamGenericTextConverterPlugin::TextConverterActionData)));
0251 
0252     connect(d->thread, SIGNAL(signalFinished(DigikamGenericTextConverterPlugin::TextConverterActionData)),
0253             this, SLOT(slotTextConverterAction(DigikamGenericTextConverterPlugin::TextConverterActionData)));
0254 
0255     connect(d->thread, SIGNAL(finished()),
0256             this, SLOT(slotThreadFinished()));
0257 
0258     // ---------------------------------------------------------------
0259 
0260     connect(m_buttons->button(QDialogButtonBox::Ok), SIGNAL(clicked()),
0261             this, SLOT(slotStartStop()));
0262 
0263     connect(m_buttons->button(QDialogButtonBox::Close), SIGNAL(clicked()),
0264             this, SLOT(slotClose()));
0265 
0266     connect(m_buttons->button(QDialogButtonBox::Reset), SIGNAL(clicked()),
0267             this, SLOT(slotDefault()));
0268 
0269     connect(d->progressBar, SIGNAL(signalProgressCanceled()),
0270             this, SLOT(slotStartStop()));
0271 
0272     connect(d->listView->listView(), &DItemsListView::itemDoubleClicked,
0273             this, &TextConverterDialog::slotDoubleClick);
0274 
0275     connect(d->listView->listView(), &DItemsListView::itemSelectionChanged,
0276             this, &TextConverterDialog::slotSetDisable);
0277 
0278     connect(d->saveTextButton, SIGNAL(clicked()),
0279             this, SLOT(slotUpdateText()));
0280 
0281     connect(d->binWidget, SIGNAL(signalBinariesFound(bool)),
0282             this, SLOT(slotTesseractBinaryFound(bool)));
0283 
0284     // ---------------------------------------------------------------
0285 
0286     d->listView->setIface(d->iface);
0287     d->listView->loadImagesFromCurrentSelection();
0288 
0289     d->ocrSettings->readSettings();
0290 
0291     // ---------------------------------------------------------------
0292 
0293     QTimer::singleShot(0, this, SLOT(slotStartFoundTesseract()));
0294 }
0295 
0296 TextConverterDialog::~TextConverterDialog()
0297 {
0298     delete d;
0299 }
0300 
0301 void TextConverterDialog::slotDoubleClick(QTreeWidgetItem* element)
0302 {
0303     TextConverterListViewItem* const item = dynamic_cast<TextConverterListViewItem*>(element);
0304 
0305     if (!item)
0306     {
0307         d->currentSelectedItem = nullptr;
0308         return;
0309     }
0310 
0311     d->currentSelectedItem = item;
0312 
0313     if (d->textEditList.contains(d->currentSelectedItem->url()))
0314     {
0315         d->tabView->setCurrentIndex(Private::ReviewTab);
0316         d->textedit->setText(d->textEditList[d->currentSelectedItem->url()]);
0317         d->saveTextButton->setEnabled(true);
0318     }
0319     else
0320     {
0321         d->textedit->clear();
0322     }
0323 }
0324 
0325 void TextConverterDialog::slotUpdateText()
0326 {
0327     QString newText   = d->textedit->text();
0328     OcrOptions opt    = d->ocrSettings->ocrOptions();
0329     opt.tesseractPath = d->tesseractBin.path();
0330     opt.iface         = d->iface;
0331 
0332     if (!d->textedit->text().isEmpty()           &&
0333         !d->currentSelectedItem->url().isEmpty() &&
0334         !d->currentSelectedItem->destFileName().isEmpty())
0335     {
0336         d->textEditList[d->currentSelectedItem->url()] = newText;
0337         d->currentSelectedItem->setRecognizedWords(QString::fromLatin1("%1").arg(calculateNumberOfWords(newText)));
0338 
0339         MetaEngine::AltLangMap commentsMap;
0340         commentsMap.insert(QLatin1String("x-default"), newText);
0341 
0342         if (opt.isSaveTextFile || opt.isSaveXMP)
0343         {
0344             OcrTesseractEngine::translate(commentsMap, opt.translations);
0345         }
0346 
0347         if (opt.isSaveTextFile)
0348         {
0349             QString outFile = d->currentSelectedItem->destFileName();
0350             OcrTesseractEngine::saveTextFile(d->currentSelectedItem->url().toLocalFile(),
0351                                              outFile,
0352                                              commentsMap);
0353         }
0354 
0355         if (opt.isSaveXMP)
0356         {
0357             OcrTesseractEngine::saveXMP(d->currentSelectedItem->url(),
0358                                         commentsMap,
0359                                         opt.iface);
0360         }
0361     }
0362 }
0363 
0364 void TextConverterDialog::slotSetDisable()
0365 {
0366     d->saveTextButton->setEnabled(false);
0367 }
0368 
0369 void TextConverterDialog::slotTextConverterAction(const DigikamGenericTextConverterPlugin::TextConverterActionData& ad)
0370 {
0371     if (ad.starting)
0372     {
0373         switch (ad.action)
0374         {
0375             case PROCESS:
0376             {
0377                 setBusy(true);
0378                 d->listView->processing(ad.fileUrl);
0379                 d->progressBar->progressStatusChanged(i18nc("@info", "Processing %1", ad.fileUrl.fileName()));
0380                 break;
0381             }
0382 
0383             default:
0384             {
0385                 qCWarning(DIGIKAM_GENERAL_LOG) << "DigikamGenericTextConverterPlugin: Unknown action";
0386                 break;
0387             }
0388         }
0389     }
0390     else
0391     {
0392         if (ad.result != OcrTesseractEngine::PROCESS_COMPLETE)
0393         {
0394             switch (ad.action)
0395             {
0396                 case PROCESS:
0397                 {
0398                     processingFailed(ad.fileUrl, ad.result);
0399                     break;
0400                 }
0401 
0402                 default:
0403                 {
0404                     qCWarning(DIGIKAM_GENERAL_LOG) << "DigikamGenericTextConverterPlugin: Unknown action";
0405                     break;
0406                 }
0407             }
0408         }
0409         else                    // Something is done...
0410         {
0411             switch (ad.action)
0412             {
0413                 case PROCESS:
0414                 {
0415                     d->textEditList[ad.fileUrl] = ad.outputText;
0416                     processed(ad.fileUrl, ad.destPath, ad.outputText);
0417 
0418                     break;
0419                 }
0420 
0421                 default:
0422                 {
0423                     qCWarning(DIGIKAM_GENERAL_LOG) << "DigikamGenericTextConverterPlugin: Unknown action";
0424 
0425                     break;
0426                 }
0427             }
0428         }
0429     }
0430 }
0431 
0432 void TextConverterDialog::processingFailed(const QUrl& url, int result)
0433 {
0434     d->listView->processed(url, false);
0435     d->progressBar->setValue(d->progressBar->value()+1);
0436 
0437     TextConverterListViewItem* const item = dynamic_cast<TextConverterListViewItem*>(d->listView->listView()->findItem(url));
0438 
0439     if (!item)
0440     {
0441         return;
0442     }
0443 
0444     QString status;
0445 
0446     switch (result)
0447     {
0448         case OcrTesseractEngine::PROCESS_FAILED:
0449         {
0450             status = i18nc("@info", "Process failed");
0451             break;
0452         }
0453 
0454         case OcrTesseractEngine::PROCESS_CANCELED:
0455         {
0456             status = i18nc("@info", "Process canceled");
0457             break;
0458         }
0459 
0460         // TODO Tesseract ocr error
0461 
0462         default:
0463         {
0464             status = i18nc("@info", "Internal error");
0465             break;
0466         }
0467     }
0468 
0469     item->setStatus(status);
0470 }
0471 
0472 int TextConverterDialog::calculateNumberOfWords(const QString& text) const
0473 {
0474     if (!text.isEmpty())
0475     {
0476         std::stringstream ss;
0477         ss << text.toStdString();
0478 
0479         int count = 0;
0480         std::string word;
0481 
0482         while (ss >> word)
0483         {
0484             if ((word.length() == 1) && std::ispunct(word[0]))
0485             {
0486                 continue;
0487             }
0488 
0489             count++;
0490         }
0491 
0492         return count;
0493     }
0494 
0495     return 0;
0496 }
0497 
0498 void TextConverterDialog::processed(const QUrl& url,
0499                                     const QString& outputFile,
0500                                     const QString& ocrResult)
0501 {
0502     TextConverterListViewItem* const item = dynamic_cast<TextConverterListViewItem*>(d->listView->listView()->findItem(url));
0503 
0504     if (!item)
0505     {
0506         return;
0507     }
0508 
0509     if (!outputFile.isEmpty())
0510     {
0511         item->setDestFileName(outputFile);
0512     }
0513 
0514     d->listView->processed(url, true);
0515     item->setStatus(i18nc("@info", "Success"));
0516     item->setRecognizedWords(QString::fromLatin1("%1").arg(calculateNumberOfWords(ocrResult)));
0517     d->progressBar->setValue(d->progressBar->value() + 1);
0518 }
0519 
0520 void TextConverterDialog::processAll()
0521 {
0522     OcrOptions opt    = d->ocrSettings->ocrOptions();
0523     opt.tesseractPath = d->tesseractBin.path();
0524     opt.iface         = d->iface;
0525     d->thread->setOcrOptions(opt);
0526     d->thread->ocrProcessFiles(d->fileList);
0527 
0528     if (!d->thread->isRunning())
0529     {
0530         d->thread->start();
0531     }
0532 }
0533 
0534 void TextConverterDialog::slotStartStop()
0535 {
0536     if (!d->busy)
0537     {
0538         QAction* const ac = qobject_cast<QAction*>(sender());
0539 
0540         if (!ac)
0541         {
0542             return;
0543         }
0544 
0545         int ptype = ac->data().toInt();
0546 
0547         d->fileList.clear();
0548 
0549         if (d->listView->listView()->topLevelItemCount() == 0)
0550         {
0551             d->textedit->clear();
0552         }
0553 
0554         QTreeWidgetItemIterator it(d->listView->listView());
0555 
0556         while (*it)
0557         {
0558             TextConverterListViewItem* const lvItem = dynamic_cast<TextConverterListViewItem*>(*it);
0559 
0560             if (lvItem)
0561             {
0562                 if (
0563                     !lvItem->isDisabled()                                   &&
0564                     (lvItem->state() != TextConverterListViewItem::Success) &&
0565                     ((ptype == Private::ProcessAll) ? true : lvItem->isSelected())
0566                    )
0567                 {
0568                     lvItem->setIcon(1, QIcon());
0569                     lvItem->setState(TextConverterListViewItem::Waiting);
0570                     d->fileList.append(lvItem->url());
0571                 }
0572             }
0573 
0574             ++it;
0575         }
0576 
0577         if (d->fileList.empty())
0578         {
0579             QMessageBox::information(this, i18nc("@title:window", "Text Converter"),
0580                                      i18nc("@info", "The list does not contain any digital files to process. "
0581                                            "You need to select them."));
0582             setBusy(false);
0583             slotAborted();
0584 
0585             return;
0586         }
0587 
0588         d->progressBar->setMaximum(d->fileList.count());
0589         d->progressBar->setValue(0);
0590         d->progressBar->setVisible(true);
0591         d->progressBar->progressScheduled(i18nc("@title", "Text Converter"), true, true);
0592         d->progressBar->progressThumbnailChanged(QIcon::fromTheme(QLatin1String("text-x-generic")).pixmap(22, 22));
0593 
0594         processAll();
0595     }
0596     else
0597     {
0598         d->fileList.clear();
0599         d->thread->cancel();
0600         setBusy(false);
0601 
0602         d->listView->cancelProcess();
0603 
0604         QTimer::singleShot(500, this, SLOT(slotAborted()));
0605     }
0606 }
0607 
0608 void TextConverterDialog::closeEvent(QCloseEvent* e)
0609 {
0610     if (!e)
0611     {
0612         return;
0613     }
0614 
0615     // Stop current conversion if necessary
0616 
0617     if (d->busy)
0618     {
0619         slotStartStop();
0620     }
0621 
0622     d->ocrSettings->saveSettings();
0623     d->listView->listView()->clear();
0624     e->accept();
0625 }
0626 
0627 void TextConverterDialog::slotClose()
0628 {
0629     // Stop current conversion if necessary
0630 
0631     if (d->busy)
0632     {
0633         slotStartStop();
0634     }
0635 
0636     d->ocrSettings->saveSettings();
0637     d->listView->listView()->clear();
0638     d->fileList.clear();
0639     accept();
0640 }
0641 
0642 void TextConverterDialog::slotDefault()
0643 {
0644     d->ocrSettings->setDefaultSettings();
0645 }
0646 
0647 void TextConverterDialog::addItems(const QList<QUrl>& itemList)
0648 {
0649     d->listView->slotAddImages(itemList);
0650 }
0651 
0652 void TextConverterDialog::slotThreadFinished()
0653 {
0654     setBusy(false);
0655     slotAborted();
0656 }
0657 
0658 void TextConverterDialog::setBusy(bool busy)
0659 {
0660     d->busy = busy;
0661 
0662     if (d->busy)
0663     {
0664         m_buttons->button(QDialogButtonBox::Ok)->setText(i18nc("@action", "&Abort"));
0665         m_buttons->button(QDialogButtonBox::Ok)->setToolTip(i18nc("@info", "Abort OCR processing of Raw files."));
0666         unplugProcessMenu();
0667     }
0668     else
0669     {
0670         m_buttons->button(QDialogButtonBox::Ok)->setText(i18nc("@action", "&Start OCR"));
0671         m_buttons->button(QDialogButtonBox::Ok)->setToolTip(i18nc("@info", "Start OCR using the current settings."));      
0672         plugProcessMenu();
0673     }
0674 
0675     d->ocrSettings->setEnabled(!d->busy);
0676     d->listView->listView()->viewport()->setEnabled(!d->busy);
0677     d->busy ? setCursor(Qt::WaitCursor) : unsetCursor();
0678 }
0679 
0680 void TextConverterDialog::slotAborted()
0681 {
0682     d->progressBar->setValue(0);
0683     d->progressBar->setVisible(false);
0684     d->progressBar->progressCompleted();
0685 }
0686 
0687 void TextConverterDialog::slotStartFoundTesseract()
0688 {
0689     bool b = d->binWidget->allBinariesFound();
0690     slotTesseractBinaryFound(b);
0691 }
0692 
0693 void TextConverterDialog::slotTesseractBinaryFound(bool found)
0694 {
0695     qCDebug(DIGIKAM_GENERAL_LOG) << "Tesseract binary found:" << found;
0696 
0697     QStringList langs = d->tesseractBin.tesseractLanguages();
0698     d->ocrSettings->populateLanguagesMode(langs);
0699 
0700     bool b = found && !langs.isEmpty();
0701 
0702     setBusy(false);
0703 
0704     // Disable Start button if Tesseract is not found or if no language plugin installed.
0705 
0706     m_buttons->button(QDialogButtonBox::Ok)->setEnabled(b);
0707 
0708     if (b)
0709     {
0710         m_buttons->button(QDialogButtonBox::Ok)->setToolTip(i18nc("@info", "Start OCR using the current settings."));
0711     }
0712     else
0713     {
0714         m_buttons->button(QDialogButtonBox::Ok)->setToolTip(i18nc("@info", "Tesseract program or language modules\n"
0715                                                                   "are not installed on your system."));
0716     }
0717 }
0718 
0719 void TextConverterDialog::plugProcessMenu()
0720 {
0721     if (!m_buttons->button(QDialogButtonBox::Ok)->menu())
0722     {
0723         QMenu* const processMenu = new QMenu(this);
0724         m_buttons->button(QDialogButtonBox::Ok)->setMenu(processMenu);
0725 
0726         connect(processMenu, SIGNAL(aboutToShow()),
0727                 this, SLOT(slotProcessMenu()));
0728     }
0729 }
0730 
0731 void TextConverterDialog::unplugProcessMenu()
0732 {
0733     m_buttons->button(QDialogButtonBox::Ok)->setMenu(nullptr);
0734 }
0735 
0736 void TextConverterDialog::slotProcessMenu()
0737 {
0738     if (!d->busy)
0739     {
0740         QMenu* const processMenu = qobject_cast<QMenu*>(sender());
0741 
0742         if (processMenu)
0743         {
0744             processMenu->clear();
0745 
0746             QAction* ac = nullptr;
0747             ac          = processMenu->addAction(i18nc("@action", "Process All Items"),      this, SLOT(slotStartStop()));
0748             ac->setData(Private::ProcessAll);
0749             ac          = processMenu->addAction(i18nc("@action", "Process Selected Items"), this, SLOT(slotStartStop()));
0750             ac->setData(Private::ProcessSelected);
0751         }
0752     }
0753 }
0754 
0755 } // namespace DigikamGenericTextConverterPlugin
0756 
0757 #include "moc_textconverterdialog.cpp"