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 }