File indexing completed on 2024-06-16 05:24:57

0001 /*
0002     This file is part of the Okteta Kasten module, made within the KDE community.
0003 
0004     SPDX-FileCopyrightText: 2009, 2019 Friedrich W. H. Kossebau <kossebau@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0007 */
0008 
0009 #include "replacejob.hpp"
0010 
0011 // libbytearraysearch
0012 #include <bytearraysearchjob.hpp>
0013 // Okteta Kasten gui
0014 #include <Kasten/Okteta/ByteArrayView>
0015 // Okteta Kasten core
0016 #include <Kasten/Okteta/ByteArrayDocument>
0017 // Okteta core
0018 #include <Okteta/CharCodec>
0019 #include <Okteta/AbstractByteArrayModel>
0020 // Qt
0021 #include <QApplication>
0022 
0023 namespace Kasten {
0024 
0025 ReplaceJob::ReplaceJob(ByteArrayView* byteArrayView, Okteta::AbstractByteArrayModel* byteArrayModel,
0026                         QObject* userQueryAgent,
0027                         QObject* parent)
0028     : QObject(parent)
0029     , m_userQueryAgent(userQueryAgent)
0030     , m_byteArrayView(byteArrayView)
0031     , m_byteArrayModel(byteArrayModel)
0032 {
0033     qRegisterMetaType<Okteta::AddressRange>("Okteta::AddressRange");
0034     auto* replaceUserQueryable = qobject_cast<If::ReplaceUserQueryable*>(m_userQueryAgent);
0035     if (replaceUserQueryable) {
0036         qRegisterMetaType<Kasten::ReplaceBehaviour>();
0037         connect(m_userQueryAgent, SIGNAL(queryContinueFinished(bool)),
0038                 this, SLOT(handleContinueFinished(bool)), Qt::QueuedConnection);
0039         connect(m_userQueryAgent, SIGNAL(queryReplaceCurrentFinished(Kasten::ReplaceBehaviour)),
0040                 this, SLOT(handleReplaceCurrentFinished(Kasten::ReplaceBehaviour)), Qt::QueuedConnection);
0041     } else {
0042         m_userQueryAgent = nullptr;
0043     }
0044 }
0045 
0046 ReplaceJob::~ReplaceJob() = default;
0047 
0048 void ReplaceJob::setSearchData(const QByteArray& searchData)
0049 {
0050     m_searchData = searchData;
0051 }
0052 
0053 void ReplaceJob::setReplaceData(const QByteArray& replaceData)
0054 {
0055     m_replaceData = replaceData;
0056 }
0057 
0058 void ReplaceJob::setCaseSensitivity(Qt::CaseSensitivity caseSensitivity)
0059 {
0060     m_caseSensitivity = caseSensitivity;
0061 }
0062 
0063 void ReplaceJob::setDoPrompt(bool doPrompt)
0064 {
0065     m_doPrompt = doPrompt;
0066 }
0067 
0068 void ReplaceJob::setRange(Okteta::Address replaceRangeStartIndex, Okteta::Address replaceRangeEndIndex,
0069                           FindDirection direction)
0070 {
0071     m_replaceRangeStartIndex = replaceRangeStartIndex;
0072     m_replaceRangeEndIndex = replaceRangeEndIndex;
0073     
0074     m_direction = direction;
0075 
0076     m_currentIndex = (m_direction == FindForward) ? m_replaceRangeStartIndex : m_replaceRangeEndIndex;
0077 
0078     m_doWrap = (m_replaceRangeEndIndex < m_replaceRangeStartIndex);
0079 }
0080 
0081 void ReplaceJob::start()
0082 {
0083     QApplication::setOverrideCursor(Qt::WaitCursor);
0084 
0085     m_noOfReplacements = 0;
0086     m_currentReplaceRangeStartIndex = (!m_doWrap || (m_direction == FindForward)) ? m_replaceRangeStartIndex : 0;
0087     m_currentReplaceRangeEndIndex = (!m_doWrap || (m_direction == FindBackward)) ? m_replaceRangeEndIndex : m_byteArrayModel->size() - 1;
0088 
0089     searchNextPosition();
0090 }
0091 
0092 void ReplaceJob::searchNextPosition()
0093 {
0094     const bool isForward = (m_direction == FindForward);
0095 
0096     // TODO: do not rely on m_searchData.size() once pattern search is implemented
0097     if ((isForward && m_currentIndex > m_currentReplaceRangeEndIndex - m_searchData.size() + 1) ||
0098         (!isForward && (m_currentIndex < m_currentReplaceRangeStartIndex + m_searchData.size() - 1))) {
0099         handleEndReached();
0100         return;
0101     }
0102 
0103     const Okteta::Address endIndex = isForward ? m_currentReplaceRangeEndIndex : m_currentReplaceRangeStartIndex;
0104 
0105     auto* searchJob = new ByteArraySearchJob(m_byteArrayModel, m_searchData, m_currentIndex, endIndex,
0106                                              m_caseSensitivity, m_byteArrayView->charCodingName());
0107     // Qt::QueuedConnection to ensure passing the event loop, so we do not recursively fill the callstack
0108     // as any async calls (query user, search) could fire signal while being invoked
0109     // TODO: optimize for non-user-querying with loop variant
0110     connect(searchJob, &ByteArraySearchJob::finished, this, &ReplaceJob::handleSearchResult, Qt::QueuedConnection);
0111     searchJob->start();
0112 }
0113 
0114 void ReplaceJob::handleSearchResult(Okteta::AddressRange matchRange)
0115 {
0116     if (!matchRange.isValid()) {
0117         handleEndReached();
0118         return;
0119     }
0120 
0121     m_previousFound = true;
0122     m_currentIndex = matchRange.start();
0123     m_currentMatchWidth = matchRange.width();
0124 
0125     if (m_doPrompt && m_userQueryAgent) {
0126         QApplication::restoreOverrideCursor();
0127 
0128         m_byteArrayView->setSelection(matchRange.start(), matchRange.end());
0129 
0130         qobject_cast<If::ReplaceUserQueryable*>(m_userQueryAgent)->queryReplaceCurrent();
0131     } else {
0132         replaceCurrent();
0133     }
0134 }
0135 
0136 void ReplaceJob::handleReplaceCurrentFinished(ReplaceBehaviour replaceBehaviour)
0137 {
0138     QApplication::setOverrideCursor(Qt::WaitCursor);
0139     m_byteArrayView->selectAllData(false);
0140 
0141     bool currentToBeReplaced;
0142     bool isCancelled;
0143 
0144     switch (replaceBehaviour)
0145     {
0146     case ReplaceAll:
0147         m_doPrompt = false;
0148         currentToBeReplaced = true;
0149         isCancelled = false;
0150         break;
0151     case ReplaceCurrent:
0152         currentToBeReplaced = true;
0153         isCancelled = false;
0154         break;
0155     case SkipCurrent:
0156         if (m_direction == FindForward) {
0157             ++m_currentIndex;
0158         } else {
0159             --m_currentIndex;
0160         }
0161         currentToBeReplaced = false;
0162         isCancelled = false;
0163         break;
0164     case CancelReplacing:
0165     default:
0166         currentToBeReplaced = false;
0167         isCancelled = true;
0168         m_doWrap = false;
0169     }
0170 
0171     if (isCancelled) {
0172         finish();
0173     } else if (currentToBeReplaced) {
0174         replaceCurrent();
0175     } else {
0176         searchNextPosition();
0177     }
0178 }
0179 
0180 void ReplaceJob::replaceCurrent()
0181 {
0182     ++m_noOfReplacements;
0183     const Okteta::Size inserted = m_byteArrayModel->replace(m_currentIndex, m_currentMatchWidth,
0184                                                             reinterpret_cast<const Okteta::Byte*>(m_replaceData.constData()),
0185                                                             m_replaceData.size());
0186     const Okteta::Size sizeDiff = inserted - m_currentMatchWidth;
0187 
0188     // TODO: cursors being automatically updated by the model would be nice to have here
0189     if (m_replaceRangeEndIndex >= m_currentIndex) {
0190         m_replaceRangeEndIndex += sizeDiff;
0191         if (m_replaceRangeEndIndex < 0) {
0192             m_replaceRangeEndIndex = 0;
0193         }
0194     }
0195     if (m_currentReplaceRangeEndIndex >= m_currentIndex) {
0196         m_currentReplaceRangeEndIndex += sizeDiff;
0197         if (m_currentReplaceRangeEndIndex < 0) {
0198             m_currentReplaceRangeEndIndex = 0;
0199         }
0200     }
0201     if (m_replaceRangeStartIndex > m_currentIndex) {
0202         m_replaceRangeStartIndex += sizeDiff;
0203         if (m_replaceRangeStartIndex < 0) {
0204             m_replaceRangeStartIndex = 0;
0205         }
0206     }
0207     if (m_currentReplaceRangeStartIndex > m_currentIndex) {
0208         m_currentReplaceRangeStartIndex += sizeDiff;
0209         if (m_currentReplaceRangeStartIndex < 0) {
0210             m_currentReplaceRangeStartIndex = 0;
0211         }
0212     }
0213 
0214     if (m_direction == FindForward) {
0215         m_currentIndex += inserted;
0216     } else {
0217         --m_currentIndex;
0218     }
0219 
0220     searchNextPosition();
0221 }
0222 
0223 void ReplaceJob::handleEndReached()
0224 {
0225     // reached end
0226     if (m_doWrap) {
0227         if (m_userQueryAgent) {
0228             QApplication::restoreOverrideCursor();
0229             qobject_cast<If::ReplaceUserQueryable*>(m_userQueryAgent)->queryContinue(m_direction, m_noOfReplacements);
0230             // TODO: resetting the count as expected due to current already reported in query
0231             // ruins the generic meaning of the value passed finished signal, perhaps add separate totalNo?
0232             m_noOfReplacements = 0;
0233         } else {
0234             wrapAndSearchNextPosition();
0235         }
0236     } else {
0237         finish();
0238     }
0239 }
0240 
0241 void ReplaceJob::handleContinueFinished(bool result)
0242 {
0243     QApplication::setOverrideCursor(Qt::WaitCursor);
0244     if (result) {
0245         wrapAndSearchNextPosition();
0246     } else {
0247         finish();
0248     }
0249 }
0250 
0251 void ReplaceJob::wrapAndSearchNextPosition()
0252 {
0253     m_currentReplaceRangeStartIndex = (m_direction == FindForward) ? 0 : m_replaceRangeStartIndex;
0254     m_currentReplaceRangeEndIndex = (m_direction == FindForward) ? m_replaceRangeEndIndex : m_byteArrayModel->size() - 1;
0255     m_currentIndex = (m_direction == FindForward) ? 0 : m_byteArrayModel->size() - 1;
0256     m_doWrap = false;
0257 
0258     searchNextPosition();
0259 }
0260 
0261 void ReplaceJob::finish()
0262 {
0263     QApplication::restoreOverrideCursor();
0264     Q_EMIT finished(m_previousFound, m_noOfReplacements);
0265 }
0266 
0267 }
0268 
0269 #include "moc_replacejob.cpp"