File indexing completed on 2024-12-22 04:40:17

0001 /*
0002     SPDX-FileCopyrightText: 2007-2009 Sergio Pistone <sergio_pistone@yahoo.com.ar>
0003     SPDX-FileCopyrightText: 2010-2022 Mladen Milinkovic <max@smoothware.net>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "replacer.h"
0009 #include "core/richtext/richdocument.h"
0010 #include "core/subtitleiterator.h"
0011 
0012 #include <QGroupBox>
0013 #include <QRadioButton>
0014 #include <QGridLayout>
0015 #include <QDialog>
0016 
0017 #include <KFind>
0018 #include <KReplace>
0019 #include <KReplaceDialog>
0020 #include <KLocalizedString>
0021 
0022 #include <ktextwidgets_version.h>
0023 
0024 using namespace SubtitleComposer;
0025 
0026 Replacer::Replacer(QWidget *parent)
0027     : QObject(parent),
0028       m_subtitle(nullptr),
0029       m_translationMode(false),
0030       m_feedingPrimary(false),
0031       m_replace(nullptr),
0032       m_iterator(nullptr)
0033 {
0034     m_dialog = new KReplaceDialog(parent);
0035     m_dialog->setHasSelection(true);
0036     m_dialog->setHasCursor(true);
0037     m_dialog->setOptions(m_dialog->options() | KFind::FromCursor);
0038 
0039     QWidget *mainWidget = m_dialog;//->mainWidget();
0040     QLayout *mainLayout = mainWidget->layout();
0041 
0042     m_targetGroupBox = new QGroupBox(mainWidget);
0043     m_targetGroupBox->setTitle(i18n("Find/Replace In"));
0044 
0045     QGridLayout *targetLayout = new QGridLayout(m_targetGroupBox);
0046     targetLayout->setAlignment(Qt::AlignTop);
0047     targetLayout->setSpacing(5);
0048 
0049     m_targetRadioButtons[Both] = new QRadioButton(m_targetGroupBox);
0050     m_targetRadioButtons[Both]->setChecked(true);
0051     m_targetRadioButtons[Both]->setText(i18n("Both subtitles"));
0052     m_targetRadioButtons[Primary] = new QRadioButton(m_targetGroupBox);
0053     m_targetRadioButtons[Primary]->setText(i18n("Primary subtitle"));
0054     m_targetRadioButtons[Secondary] = new QRadioButton(m_targetGroupBox);
0055     m_targetRadioButtons[Secondary]->setText(i18n("Translation subtitle"));
0056 
0057     targetLayout->addWidget(m_targetRadioButtons[Both], 0, 0);
0058     targetLayout->addWidget(m_targetRadioButtons[Primary], 1, 0);
0059     targetLayout->addWidget(m_targetRadioButtons[Secondary], 2, 0);
0060 
0061     mainLayout->addWidget(m_targetGroupBox);
0062     m_targetGroupBox->hide();
0063 }
0064 
0065 Replacer::~Replacer()
0066 {
0067     delete m_dialog;
0068 
0069     setSubtitle(nullptr);
0070 }
0071 
0072 void
0073 Replacer::invalidate()
0074 {
0075     delete m_replace;
0076     m_replace = nullptr;
0077 
0078     delete m_iterator;
0079     m_iterator = nullptr;
0080 
0081     m_feedingPrimary = false;
0082 }
0083 
0084 QWidget *
0085 Replacer::parentWidget()
0086 {
0087     return static_cast<QWidget *>(parent());
0088 }
0089 
0090 void
0091 Replacer::setSubtitle(Subtitle *subtitle)
0092 {
0093     m_subtitle = subtitle;
0094 
0095     invalidate();
0096 }
0097 
0098 void
0099 Replacer::setTranslationMode(bool enabled)
0100 {
0101     if(m_translationMode != enabled) {
0102         m_translationMode = enabled;
0103 
0104         if(m_translationMode)
0105             m_targetGroupBox->show();
0106         else
0107             m_targetGroupBox->hide();
0108 
0109         invalidate();
0110     }
0111 }
0112 
0113 void
0114 Replacer::replace(const RangeList &selectionRanges, int currentIndex, const QString &text)
0115 {
0116     invalidate();
0117 
0118     if(!m_subtitle || !m_subtitle->linesCount())
0119         return;
0120 
0121     if(!text.isEmpty()) {
0122         QStringList history = m_dialog->findHistory();
0123         history.removeAll(text);
0124         history.prepend(text);
0125         m_dialog->setFindHistory(history);
0126     }
0127 
0128     if(m_dialog->exec() != QDialog::Accepted)
0129         return;
0130 
0131     m_replace = new KReplace(m_dialog->pattern(), m_dialog->replacement(), m_dialog->options(), 0);
0132 
0133     // Connect findNext signal - called when pressing the button in the dialog
0134     connect(m_replace, &KReplace::findNext, this, &Replacer::onFindNext);
0135 
0136     // Connect signals to code which handles highlighting of found text, and on-the-fly replacement
0137 #if KTEXTWIDGETS_VERSION < QT_VERSION_CHECK(5, 81, 0)
0138     connect(m_replace, QOverload<const QString &,int,int>::of(&KReplace::highlight), this, &Replacer::onHighlight);
0139 #else
0140     connect(m_replace, &KReplace::textFound, this, &Replacer::onHighlight);
0141 #endif
0142     // Connect replace signal - called when doing a replacement
0143 #if KTEXTWIDGETS_VERSION < QT_VERSION_CHECK(5, 83, 0)
0144     connect(m_replace, QOverload<const QString &,int,int,int>::of(&KReplace::replace), this, &Replacer::onReplace);
0145 #else
0146     connect(m_replace, &KReplace::textReplaced, this, &Replacer::onReplace);
0147 #endif
0148     if(m_dialog->options() & KFind::SelectedText) {
0149         m_iterator = new SubtitleIterator(*m_subtitle, selectionRanges);
0150         if(m_iterator->index() < 0) // Invalid index means no lines in selectionRanges
0151             return;
0152     } else {
0153         m_iterator = new SubtitleIterator(*m_subtitle);
0154     }
0155 
0156     if(m_dialog->options() & KFind::FromCursor)
0157         m_iterator->toIndex(currentIndex < 0 ? 0 : currentIndex);
0158 
0159     m_firstIndex = m_iterator->index();
0160 
0161     onFindNext();
0162 }
0163 
0164 void
0165 Replacer::onFindNext()
0166 {
0167     KFind::Result res = KFind::NoMatch;
0168 
0169     const bool backwards = m_replace->options() & KFind::FindBackwards;
0170     const int startIndex = backwards ? m_iterator->lastIndex() : m_iterator->firstIndex();
0171     const int finalIndex = backwards ? SubtitleIterator::BehindFirst : SubtitleIterator::AfterLast;
0172 
0173     QDialog *replaceNextDialog = this->replaceNextDialog(); // creates the dialog if it didn't exist before
0174 
0175     do {
0176         if(m_replace->needData()) {
0177             SubtitleLine *dataLine = m_iterator->current();
0178 
0179             if(dataLine) {
0180                 if(!m_translationMode || m_targetRadioButtons[Primary]->isChecked()) {
0181                     m_feedingPrimary = true;
0182                     m_replace->setData(dataLine->primaryDoc()->toPlainText());
0183                 } else if(m_targetRadioButtons[Secondary]->isChecked()) {
0184                     m_feedingPrimary = false;
0185                     m_replace->setData(dataLine->secondaryDoc()->toPlainText());
0186                 } else { // m_translationMode && m_targetRadioButtons[SubtitleLine::Both]->isChecked()
0187                     m_feedingPrimary = !m_feedingPrimary;   // alternate the data source
0188                     m_replace->setData((m_feedingPrimary ? dataLine->primaryDoc() : dataLine->secondaryDoc())->toPlainText());
0189                 }
0190             }
0191         }
0192 
0193         res = m_replace->replace();
0194 
0195         if(res == KFind::NoMatch && (!m_translationMode || !m_targetRadioButtons[Both]->isChecked() || !m_feedingPrimary)) {
0196             if(backwards)
0197                 --(*m_iterator);
0198             else
0199                 ++(*m_iterator);
0200 
0201             if(m_firstIndex == m_iterator->index() || (m_firstIndex == startIndex && m_iterator->index() == finalIndex)) {
0202                 if(replaceNextDialog)
0203                     replaceNextDialog->hide();
0204 
0205                 m_replace->displayFinalDialog();
0206                 m_replace->resetCounts();
0207                 break;
0208             }
0209 
0210             if(m_iterator->index() < 0) {
0211                 if(backwards)
0212                     m_iterator->toLast();
0213                 else
0214                     m_iterator->toFirst();
0215 
0216                 if(!m_replace->shouldRestart()) {
0217                     if(replaceNextDialog)
0218                         replaceNextDialog->hide();
0219 
0220                     m_replace->resetCounts();
0221                     break;
0222                 }
0223             }
0224         }
0225     } while(res != KFind::Match);
0226 }
0227 
0228 QDialog *
0229 Replacer::replaceNextDialog()
0230 {
0231     if(!m_replace)
0232         return nullptr;
0233 
0234     QDialog *dlg = m_replace->replaceNextDialog(false);
0235 
0236     if(!dlg && m_replace->options() & KReplaceDialog::PromptOnReplace) {
0237         dlg = m_replace->replaceNextDialog(true);
0238         dlg->setModal(true);
0239     }
0240 
0241     return dlg;
0242 }
0243 
0244 void
0245 Replacer::onHighlight(const QString &, int matchingIndex, int matchedLength)
0246 {
0247     emit found(m_iterator->current(), m_feedingPrimary, matchingIndex, matchingIndex + matchedLength - 1);
0248 }
0249 
0250 void
0251 Replacer::onReplace(const QString &text, int replacementIndex, int replacedLength, int matchedLength)
0252 {
0253     SubtitleCompositeActionExecutor(m_subtitle.constData(), i18n("Replace"));
0254 
0255     RichDocument *doc = m_feedingPrimary
0256             ? m_iterator->current()->primaryDoc()
0257             : m_iterator->current()->secondaryDoc();
0258     doc->replace(replacementIndex, matchedLength, text.mid(replacementIndex, replacedLength));
0259 }