File indexing completed on 2024-05-05 11:56:21
0001 /* 0002 SPDX-License-Identifier: GPL-2.0-or-later 0003 SPDX-FileCopyrightText: 2012 Martin Kuettler <martin.kuettler@gmail.com> 0004 */ 0005 0006 #include "searchbar.h" 0007 0008 #include "worksheet.h" 0009 #include "worksheetentry.h" 0010 #include "worksheettextitem.h" 0011 #include "worksheetview.h" 0012 0013 #include <KLocalizedString> 0014 #include <QIcon> 0015 #include <QMenu> 0016 0017 SearchBar::SearchBar(QWidget* parent, Worksheet* worksheet) : QWidget(parent), 0018 m_stdUi(new Ui::StandardSearchBar()), 0019 m_worksheet(worksheet), 0020 m_searchFlags{WorksheetEntry::SearchAll} 0021 { 0022 setupStdUi(); 0023 setStartCursor(worksheet->worksheetCursor()); 0024 setCurrentCursor(m_startCursor); 0025 } 0026 0027 SearchBar::~SearchBar() 0028 { 0029 if (m_stdUi) 0030 delete m_stdUi; 0031 else 0032 delete m_extUi; 0033 if (m_currentCursor.isValid()) { 0034 worksheet()->worksheetView()->setFocus(); 0035 m_currentCursor.entry()->focusEntry(); 0036 } else if (m_startCursor.isValid()) { 0037 worksheet()->worksheetView()->setFocus(); 0038 m_startCursor.entry()->focusEntry(); 0039 } 0040 } 0041 0042 void SearchBar::showStandard() 0043 { 0044 if (m_stdUi) 0045 return; 0046 0047 delete m_extUi; 0048 m_extUi = nullptr; 0049 for (auto* child : children()) 0050 delete child; 0051 0052 delete layout(); 0053 m_stdUi = new Ui::StandardSearchBar(); 0054 setupStdUi(); 0055 } 0056 0057 void SearchBar::showExtended() 0058 { 0059 if (m_extUi) 0060 return; 0061 0062 delete m_stdUi; 0063 m_stdUi = nullptr; 0064 for (auto* child : children()) 0065 delete child; 0066 0067 delete layout(); 0068 m_extUi = new Ui::ExtendedSearchBar(); 0069 setupExtUi(); 0070 0071 } 0072 0073 void SearchBar::next() 0074 { 0075 if (!m_currentCursor.isValid() && !m_currentCursor.entry() && !m_atEnd) 0076 return; 0077 searchForward(true); 0078 } 0079 0080 void SearchBar::prev() 0081 { 0082 if (!m_currentCursor.isValid() && !m_currentCursor.entry() && 0083 !m_atBeginning) 0084 return; 0085 searchBackward(true); 0086 } 0087 0088 void SearchBar::searchBackward(bool skipFirstChar) 0089 { 0090 WorksheetCursor result; 0091 WorksheetEntry* entry; 0092 worksheet()->setWorksheetCursor(WorksheetCursor()); 0093 QTextDocument::FindFlags f = m_qtFlags | QTextDocument::FindBackward; 0094 if (m_currentCursor.isValid()) { 0095 bool atBeginningOfEntry = false; 0096 if (skipFirstChar) { 0097 QTextCursor c = m_currentCursor.textCursor(); 0098 c.movePosition(QTextCursor::PreviousCharacter); 0099 atBeginningOfEntry = (c == m_currentCursor.textCursor()); 0100 setCurrentCursor(WorksheetCursor(m_currentCursor.entry(), 0101 m_currentCursor.textItem(), c)); 0102 } 0103 if (!atBeginningOfEntry) 0104 result = m_currentCursor.entry()->search(m_pattern, m_searchFlags, 0105 f, m_currentCursor); 0106 entry = m_currentCursor.entry()->previous(); 0107 } else if (m_currentCursor.entry() && m_currentCursor.entry()->previous()) { 0108 entry = m_currentCursor.entry()->previous(); 0109 } else { 0110 entry = worksheet()->lastEntry(); 0111 } 0112 setCurrentCursor(WorksheetCursor()); 0113 0114 while (!result.isValid() && entry) { 0115 result = entry->search(m_pattern, m_searchFlags, f); 0116 entry = entry->previous(); 0117 } 0118 if (result.isValid()) { 0119 m_atBeginning = false; 0120 QTextCursor c = result.textCursor(); 0121 if (result.textCursor().hasSelection()) 0122 c.setPosition(result.textCursor().selectionStart()); 0123 setCurrentCursor(WorksheetCursor(result.entry(), result.textItem(), c)); 0124 worksheet()->makeVisible(m_currentCursor); 0125 clearStatus(); 0126 worksheet()->setWorksheetCursor(result); 0127 } else { 0128 if (m_atBeginning) { 0129 m_notFound = true; 0130 setStatus(i18n("Not found")); 0131 } else { 0132 m_atBeginning = true; 0133 setStatus(i18n("Reached beginning")); 0134 } 0135 worksheet()->setWorksheetCursor(m_startCursor); 0136 } 0137 } 0138 0139 void SearchBar::searchForward(bool skipFirstChar) 0140 { 0141 WorksheetCursor result; 0142 WorksheetEntry* entry; 0143 worksheet()->setWorksheetCursor(WorksheetCursor()); 0144 if (m_currentCursor.isValid()) { 0145 if (skipFirstChar) { 0146 QTextCursor c = m_currentCursor.textCursor(); 0147 c.movePosition(QTextCursor::NextCharacter); 0148 setCurrentCursor(WorksheetCursor(m_currentCursor.entry(), 0149 m_currentCursor.textItem(), c)); 0150 } 0151 result = m_currentCursor.entry()->search(m_pattern, m_searchFlags, 0152 m_qtFlags, m_currentCursor); 0153 entry = m_currentCursor.entry()->next(); 0154 } else if (m_currentCursor.entry()) { 0155 entry = m_currentCursor.entry(); 0156 } else { 0157 entry = worksheet()->firstEntry(); 0158 } 0159 setCurrentCursor(WorksheetCursor()); 0160 0161 while (!result.isValid() && entry) { 0162 result = entry->search(m_pattern, m_searchFlags, m_qtFlags); 0163 entry = entry->next(); 0164 } 0165 0166 if (result.isValid()) { 0167 m_atEnd = false; 0168 QTextCursor c = result.textCursor(); 0169 if (result.textCursor().hasSelection()) 0170 c.setPosition(result.textCursor().selectionStart()); 0171 setCurrentCursor(WorksheetCursor(result.entry(), result.textItem(), c)); 0172 worksheet()->makeVisible(m_currentCursor); 0173 clearStatus(); 0174 worksheet()->setWorksheetCursor(result); 0175 } else { 0176 if (m_atEnd) { 0177 m_notFound = true; 0178 setStatus(i18n("Not found")); 0179 } else { 0180 m_atEnd = true; 0181 setStatus(i18n("Reached end")); 0182 } 0183 worksheet()->setWorksheetCursor(m_startCursor); 0184 } 0185 } 0186 0187 void SearchBar::on_close_clicked() 0188 { 0189 deleteLater(); 0190 } 0191 0192 void SearchBar::on_openExtended_clicked() 0193 { 0194 showExtended(); 0195 } 0196 0197 void SearchBar::on_openStandard_clicked() 0198 { 0199 showStandard(); 0200 } 0201 0202 void SearchBar::on_next_clicked() 0203 { 0204 next(); 0205 } 0206 0207 void SearchBar::on_previous_clicked() 0208 { 0209 prev(); 0210 } 0211 0212 void SearchBar::on_replace_clicked() 0213 { 0214 if (!m_currentCursor.isValid()) 0215 return; 0216 0217 QTextCursor cursor = m_currentCursor.textCursor(); 0218 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 0219 m_pattern.length()); 0220 cursor.insertText(m_replacement); 0221 next(); 0222 } 0223 0224 void SearchBar::on_replaceAll_clicked() 0225 { 0226 int count = 0; 0227 WorksheetEntry* entry = worksheet()->firstEntry(); 0228 WorksheetCursor cursor; 0229 for (; entry; entry = entry->next()) { 0230 cursor = entry->search(m_pattern, m_searchFlags, m_qtFlags); 0231 while (cursor.isValid()) { 0232 cursor.textCursor().insertText(m_replacement); 0233 cursor = entry->search(m_pattern, m_searchFlags, m_qtFlags, 0234 cursor); 0235 ++count; 0236 } 0237 } 0238 setStatus(i18np("Replaced %1 instance", "Replaced %1 instances", count)); 0239 } 0240 0241 void SearchBar::on_pattern_textChanged(const QString& p) 0242 { 0243 worksheet()->setWorksheetCursor(WorksheetCursor()); 0244 m_atBeginning = m_atEnd = m_notFound = false; 0245 if (!p.startsWith(m_pattern)) 0246 setCurrentCursor(m_startCursor); 0247 m_pattern = p; 0248 if (!m_pattern.isEmpty()) { 0249 searchForward(); 0250 nextButton()->setEnabled(true); 0251 previousButton()->setEnabled(true); 0252 if (m_extUi) { 0253 m_extUi->replace->setEnabled(true); 0254 m_extUi->replaceAll->setEnabled(true); 0255 } 0256 } else { 0257 worksheet()->setWorksheetCursor(m_startCursor); 0258 nextButton()->setEnabled(false); 0259 previousButton()->setEnabled(false); 0260 if (m_extUi) { 0261 m_extUi->replace->setEnabled(false); 0262 m_extUi->replaceAll->setEnabled(false); 0263 } 0264 } 0265 } 0266 0267 void SearchBar::on_replacement_textChanged(const QString& r) 0268 { 0269 m_replacement = r; 0270 } 0271 0272 void SearchBar::on_removeFlag_clicked() 0273 { 0274 QMenu* menu = new QMenu(this); 0275 fillLocationsMenu(menu, m_searchFlags); 0276 connect(menu, SIGNAL("aboutToHide()"), menu, SLOT("deleteLater()")); 0277 menu->exec(mapToGlobal(m_extUi->removeFlag->geometry().topLeft())); 0278 } 0279 0280 void SearchBar::on_addFlag_clicked() 0281 { 0282 QMenu* menu = new QMenu(this); 0283 fillLocationsMenu(menu, WorksheetEntry::SearchAll ^ m_searchFlags); 0284 connect(menu, SIGNAL("aboutToHide()"), menu, SLOT("deleteLater()")); 0285 menu->exec(mapToGlobal(m_extUi->addFlag->geometry().topLeft())); 0286 } 0287 0288 void SearchBar::invalidateStartCursor() 0289 { 0290 if (!m_startCursor.isValid()) 0291 return; 0292 0293 WorksheetEntry* entry = m_startCursor.entry()->next(); 0294 if (!entry && worksheet()->firstEntry() != m_startCursor.entry()) 0295 entry = worksheet()->firstEntry(); 0296 0297 setStartCursor(WorksheetCursor(entry, nullptr, QTextCursor())); 0298 } 0299 0300 void SearchBar::invalidateCurrentCursor() 0301 { 0302 if (!m_currentCursor.isValid()) 0303 return; 0304 0305 WorksheetEntry* entry = m_currentCursor.entry()->next(); 0306 if (!entry) 0307 entry = worksheet()->firstEntry(); 0308 0309 setCurrentCursor(WorksheetCursor(entry, nullptr, QTextCursor())); 0310 } 0311 0312 void SearchBar::toggleFlag() 0313 { 0314 if (!sender()) 0315 return; 0316 int flag = sender()->property("searchFlag").toInt(); 0317 m_searchFlags ^= flag; 0318 updateSearchLocations(); 0319 } 0320 0321 void SearchBar::on_matchCase_toggled(bool b) 0322 { 0323 m_qtFlags &= ~QTextDocument::FindCaseSensitively; 0324 if (b) 0325 m_qtFlags |= QTextDocument::FindCaseSensitively; 0326 searchForward(); 0327 } 0328 0329 void SearchBar::updateSearchLocations() 0330 { 0331 static QList<QString> names; 0332 if (names.empty()) 0333 names << i18n("Commands") << i18n("Results") << i18n("Errors") 0334 << i18n("Text") << i18n("LaTeX Code"); 0335 0336 QString text = QLatin1String(""); 0337 int flag = 1; 0338 for (int i = 0; flag < WorksheetEntry::SearchAll; flag = (1<<(++i))) { 0339 if (m_searchFlags & flag) { 0340 if (!text.isEmpty()) 0341 text += QLatin1String(", "); 0342 text += names.at(i); 0343 } 0344 } 0345 m_extUi->searchFlagsList->setText(text); 0346 if (m_searchFlags == 0) { 0347 m_extUi->removeFlag->setEnabled(false); 0348 m_extUi->addFlag->setEnabled(true); 0349 } else if (m_searchFlags == WorksheetEntry::SearchAll) { 0350 m_extUi->removeFlag->setEnabled(true); 0351 m_extUi->addFlag->setEnabled(false); 0352 } else { 0353 m_extUi->addFlag->setEnabled(true); 0354 m_extUi->removeFlag->setEnabled(true); 0355 } 0356 } 0357 0358 void SearchBar::fillLocationsMenu(QMenu* menu, int flags) 0359 { 0360 static QList<QString> names; 0361 if (names.empty()) 0362 names << i18n("Commands") << i18n("Results") << i18n("Errors") 0363 << i18n("Text") << i18n("LaTeX Code"); 0364 int flag = 1; 0365 for (int i = 0; flag < WorksheetEntry::SearchAll; flag = (1<<(++i))) { 0366 if (flags & flag) { 0367 QAction* a = menu->addAction(names.at(i), this, SLOT(toggleFlag())); 0368 a->setProperty("searchFlag", flag); 0369 } 0370 } 0371 } 0372 0373 void SearchBar::setStartCursor(WorksheetCursor cursor) 0374 { 0375 if (m_startCursor.entry()) 0376 disconnect(m_startCursor.entry(), SIGNAL(aboutToBeDeleted()), this, 0377 SLOT(invalidateStartCursor())); 0378 if (cursor.entry()) 0379 connect(cursor.entry(), SIGNAL(aboutToBeDeleted()), this, 0380 SLOT(invalidateStartCursor()), Qt::DirectConnection); 0381 m_startCursor = cursor; 0382 } 0383 0384 void SearchBar::setCurrentCursor(WorksheetCursor cursor) 0385 { 0386 if (m_currentCursor.entry()) 0387 disconnect(m_currentCursor.entry(), SIGNAL(aboutToBeDeleted()), this, 0388 SLOT(invalidateCurrentCursor())); 0389 if (cursor.entry()) 0390 connect(cursor.entry(), SIGNAL(aboutToBeDeleted()), this, 0391 SLOT(invalidateCurrentCursor()), Qt::DirectConnection); 0392 m_currentCursor = cursor; 0393 } 0394 0395 void SearchBar::setStatus(QString message) 0396 { 0397 KSqueezedTextLabel* status; 0398 if (m_stdUi) 0399 status = m_stdUi->status; 0400 else 0401 status = m_extUi->status; 0402 0403 status->setText(message); 0404 } 0405 0406 void SearchBar::clearStatus() 0407 { 0408 setStatus(QLatin1String("")); 0409 } 0410 0411 void SearchBar::setupStdUi() 0412 { 0413 if (!m_stdUi) 0414 return; 0415 0416 m_stdUi->setupUi(this); 0417 m_stdUi->close->setIcon(QIcon::fromTheme(QLatin1String("dialog-close"))); 0418 m_stdUi->openExtended->setIcon(QIcon::fromTheme(QLatin1String("arrow-up-double"))); 0419 m_stdUi->pattern->setText(m_pattern); 0420 m_stdUi->matchCase->setChecked(m_qtFlags & QTextDocument::FindCaseSensitively); 0421 m_stdUi->next->setIcon(QIcon::fromTheme(QLatin1String("go-down-search"))); 0422 m_stdUi->previous->setIcon(QIcon::fromTheme(QLatin1String("go-up-search"))); 0423 if (m_pattern.isEmpty()) { 0424 m_stdUi->next->setEnabled(false); 0425 m_stdUi->previous->setEnabled(false); 0426 } 0427 0428 m_stdUi->close->setShortcut(Qt::Key_Escape); 0429 setFocusProxy(m_stdUi->pattern); 0430 } 0431 0432 void SearchBar::setupExtUi() 0433 { 0434 if (!m_extUi) 0435 return; 0436 0437 m_extUi->setupUi(this); 0438 m_extUi->close->setIcon(QIcon::fromTheme(QLatin1String("dialog-close"))); 0439 m_extUi->openStandard->setIcon(QIcon::fromTheme(QLatin1String("arrow-down-double"))); 0440 m_extUi->pattern->setText(m_pattern); 0441 m_extUi->replacement->setText(m_replacement); 0442 m_extUi->matchCase->setChecked(m_qtFlags & QTextDocument::FindCaseSensitively); 0443 m_extUi->next->setIcon(QIcon::fromTheme(QLatin1String("go-down-search"))); 0444 m_extUi->previous->setIcon(QIcon::fromTheme(QLatin1String("go-up-search"))); 0445 if (m_pattern.isEmpty()) { 0446 m_extUi->next->setEnabled(false); 0447 m_extUi->previous->setEnabled(false); 0448 m_extUi->replace->setEnabled(false); 0449 m_extUi->replaceAll->setEnabled(false); 0450 } 0451 0452 m_extUi->addFlag->setIcon(QIcon::fromTheme(QLatin1String("list-add"))); 0453 m_extUi->removeFlag->setIcon(QIcon::fromTheme(QLatin1String("list-remove"))); 0454 0455 m_extUi->close->setShortcut(Qt::Key_Escape); 0456 setFocusProxy(m_extUi->pattern); 0457 updateSearchLocations(); 0458 } 0459 0460 QPushButton* SearchBar::previousButton() 0461 { 0462 if (m_stdUi) 0463 return m_stdUi->previous; 0464 else 0465 return m_extUi->previous; 0466 } 0467 0468 QPushButton* SearchBar::nextButton() 0469 { 0470 if (m_stdUi) 0471 return m_stdUi->next; 0472 else 0473 return m_extUi->next; 0474 } 0475 0476 Worksheet* SearchBar::worksheet() 0477 { 0478 return m_worksheet; 0479 }