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"