724 lines
24 KiB
C++
724 lines
24 KiB
C++
#include "mainwindow.h"
|
|
|
|
#include "sciedit.h"
|
|
#include "tabwidget.h"
|
|
#include "documentmanager.h"
|
|
#include "appsettings.h"
|
|
#include "editorviewhelper.h"
|
|
#include "findreplacedialog.h"
|
|
|
|
#include <QActionGroup>
|
|
#include <QApplication>
|
|
#include <QCheckBox>
|
|
#include <QCloseEvent>
|
|
#include <QComboBox>
|
|
#include <QDialog>
|
|
#include <QFormLayout>
|
|
#include <QGroupBox>
|
|
#include <QHBoxLayout>
|
|
#include <QLabel>
|
|
#include <QLineEdit>
|
|
#include <QMenu>
|
|
#include <QPushButton>
|
|
#include <QStringBuilder>
|
|
#include <QStatusBar>
|
|
#include <QTabWidget>
|
|
#include <QVBoxLayout>
|
|
#include <QFileDialog>
|
|
#include <QMessageBox>
|
|
|
|
#include <KActionCollection>
|
|
#include <KStandardAction>
|
|
#include <KActionMenu>
|
|
#include <KConfigDialog>
|
|
#include <KColorSchemeManager>
|
|
#include <KColorSchemeMenu>
|
|
#include <QSpinBox>
|
|
|
|
#include <ILexer.h>
|
|
#include <Lexilla.h>
|
|
|
|
MainWindow::MainWindow(QWidget *parent)
|
|
: KXmlGuiWindow(parent)
|
|
, m_documentManager(new DocumentManager(this))
|
|
, m_tabWidget(new TabWidget(m_documentManager, this))
|
|
, m_cursorPosStatusLabel(new QLabel(QString("Ln: ? Col: ?"), this))
|
|
, m_encodingStatusLabel(new QLabel(QString("UTF-8"), this))
|
|
, m_languageStatusLabel(new QLabel(QString("Plain Text"), this))
|
|
{
|
|
setCentralWidget(m_tabWidget);
|
|
|
|
// 设置状态栏
|
|
statusBar()->addPermanentWidget(m_cursorPosStatusLabel);
|
|
statusBar()->addPermanentWidget(m_encodingStatusLabel);
|
|
statusBar()->addPermanentWidget(m_languageStatusLabel);
|
|
|
|
// 连接标签页信号
|
|
connect(m_tabWidget, &TabWidget::currentEditorChanged, this, &MainWindow::onCurrentEditorChanged);
|
|
|
|
// 连接文档管理器信号
|
|
connect(m_documentManager, &DocumentManager::documentModified, this, &MainWindow::onDocumentModified);
|
|
|
|
setupActions();
|
|
|
|
// 创建第一个标签页
|
|
newFile();
|
|
}
|
|
|
|
MainWindow::~MainWindow()
|
|
{
|
|
}
|
|
|
|
void MainWindow::setupActions()
|
|
{
|
|
using namespace Qt::Literals::StringLiterals;
|
|
|
|
// "File" menu
|
|
KStandardAction::openNew(this, &MainWindow::newFile, actionCollection());
|
|
KStandardAction::open(this, &MainWindow::openFile, actionCollection());
|
|
KStandardAction::save(this, &MainWindow::saveFile, actionCollection());
|
|
KStandardAction::close(this, &MainWindow::closeFile, actionCollection());
|
|
KStandardAction::quit(qApp, &QApplication::quit, actionCollection());
|
|
|
|
// 添加另存为动作
|
|
QAction *saveAsAction = KStandardAction::saveAs(this, &MainWindow::saveAsFile, actionCollection());
|
|
|
|
// "Edit" menu
|
|
KStandardAction::undo(this, [this](){
|
|
if (SciEdit *editor = m_tabWidget->currentEditor()) {
|
|
editor->undo();
|
|
}
|
|
}, actionCollection());
|
|
KStandardAction::redo(this, [this](){
|
|
if (SciEdit *editor = m_tabWidget->currentEditor()) {
|
|
editor->redo();
|
|
}
|
|
}, actionCollection());
|
|
KStandardAction::cut(this, [this](){
|
|
if (SciEdit *editor = m_tabWidget->currentEditor()) {
|
|
editor->cut();
|
|
}
|
|
}, actionCollection());
|
|
KStandardAction::copy(this, [this](){
|
|
if (SciEdit *editor = m_tabWidget->currentEditor()) {
|
|
editor->copy();
|
|
}
|
|
}, actionCollection());
|
|
KStandardAction::paste(this, [this](){
|
|
if (SciEdit *editor = m_tabWidget->currentEditor()) {
|
|
editor->paste();
|
|
}
|
|
}, actionCollection());
|
|
|
|
// "Search" menu
|
|
KStandardAction::find(this, [this](){
|
|
showFindReplaceDialog(false);
|
|
}, actionCollection());
|
|
KStandardAction::findNext(this, [this](){
|
|
findNextInCurrentEditor(true, true);
|
|
}, actionCollection());
|
|
KStandardAction::findPrev(this, [this](){
|
|
findNextInCurrentEditor(false, true);
|
|
}, actionCollection());
|
|
KStandardAction::replace(this, [this](){
|
|
showFindReplaceDialog(true);
|
|
}, actionCollection());
|
|
|
|
// "Language" menu
|
|
QAction *lexerNoneAction = new QAction(this);
|
|
lexerNoneAction->setText("None (Normal Text)");
|
|
actionCollection()->addAction(u"lexer_none"_s, lexerNoneAction);
|
|
connect(lexerNoneAction, &QAction::triggered, this, [this](){
|
|
if (SciEdit *editor = m_tabWidget->currentEditor()) {
|
|
editor->setLexer(nullptr);
|
|
const int docId = m_tabWidget->currentDocumentId();
|
|
if (docId >= 0) {
|
|
m_documentManager->setDocumentLanguage(docId, QString());
|
|
}
|
|
updateStatusBar();
|
|
}
|
|
});
|
|
|
|
QMetaObject::invokeMethod(this, [this](){
|
|
auto lexerGroupListActions = QList<QAction*>();
|
|
for (const QChar &group : LexerGroupActionMenu::groups()) {
|
|
auto *lexerGroupMenu = new LexerGroupActionMenu(group.toUpper(), group, this);
|
|
connect(lexerGroupMenu, &LexerGroupActionMenu::lexerSelected, this, &MainWindow::applyLexer);
|
|
lexerGroupListActions.append(lexerGroupMenu);
|
|
}
|
|
unplugActionList("lexer_group_list");
|
|
plugActionList(QStringLiteral("lexer_group_list"), lexerGroupListActions);
|
|
}, Qt::QueuedConnection);
|
|
|
|
// Toolbar actions
|
|
KStandardAction::zoomIn(this, [this](){
|
|
if (SciEdit *editor = m_tabWidget->currentEditor()) {
|
|
editor->zoomIn();
|
|
}
|
|
}, actionCollection());
|
|
KStandardAction::zoomOut(this, [this](){
|
|
if (SciEdit *editor = m_tabWidget->currentEditor()) {
|
|
editor->zoomOut();
|
|
}
|
|
}, actionCollection());
|
|
|
|
KStandardAction::preferences(this, &MainWindow::showSettings, actionCollection());
|
|
|
|
QAction *toggleWrapModeAction = new QAction(this);
|
|
toggleWrapModeAction->setText("Toggle Wrap Mode");
|
|
toggleWrapModeAction->setIcon(QIcon::fromTheme(u"text-wrap"_s));
|
|
toggleWrapModeAction->setCheckable(true);
|
|
actionCollection()->addAction(u"toggle_wrap_mode"_s, toggleWrapModeAction);
|
|
connect(toggleWrapModeAction, &QAction::triggered, this, [this, toggleWrapModeAction](){
|
|
if (SciEdit *editor = m_tabWidget->currentEditor()) {
|
|
bool switchToWrapNone = editor->wrapMode() == SC_WRAP_WORD;
|
|
editor->setWrapMode(switchToWrapNone ? SC_WRAP_NONE : SC_WRAP_WORD);
|
|
toggleWrapModeAction->setChecked(!switchToWrapNone);
|
|
}
|
|
});
|
|
|
|
QAction *toggleWhitespaceVisibilityAction = new QAction(this);
|
|
toggleWhitespaceVisibilityAction->setText("Show All Characters");
|
|
toggleWhitespaceVisibilityAction->setCheckable(true);
|
|
// toggleWhitespaceVisibilityAction->setIcon(QIcon::fromTheme(u"text-wrap"_s));
|
|
actionCollection()->addAction(u"toggle_show_all_characters"_s, toggleWhitespaceVisibilityAction);
|
|
connect(toggleWhitespaceVisibilityAction, &QAction::triggered, this, [this, toggleWhitespaceVisibilityAction](){
|
|
if (SciEdit *editor = m_tabWidget->currentEditor()) {
|
|
bool switchToVisible = editor->viewWS() == SCWS_INVISIBLE;
|
|
editor->setViewWS(switchToVisible ? SCWS_VISIBLEALWAYS : SCWS_INVISIBLE);
|
|
editor->setViewEOL(switchToVisible);
|
|
toggleWhitespaceVisibilityAction->setChecked(switchToVisible);
|
|
}
|
|
});
|
|
|
|
QAction *toggleIndentGuideAction = new QAction(this);
|
|
toggleIndentGuideAction->setText("Toggle Indent Guide");
|
|
toggleIndentGuideAction->setIcon(QIcon::fromTheme(u"show-guides"_s));
|
|
toggleIndentGuideAction->setCheckable(true);
|
|
toggleIndentGuideAction->setChecked(m_indentGuidesEnabled);
|
|
actionCollection()->addAction(u"toggle_indent_guide"_s, toggleIndentGuideAction);
|
|
connect(toggleIndentGuideAction, &QAction::toggled, this, [this](bool checked){
|
|
m_indentGuidesEnabled = checked;
|
|
applySettingsToAllEditors();
|
|
});
|
|
|
|
QAction *toggleEditorDarkThemeAction = new QAction(this);
|
|
toggleEditorDarkThemeAction->setText("Editor Dark Theme");
|
|
toggleEditorDarkThemeAction->setCheckable(true);
|
|
toggleEditorDarkThemeAction->setChecked(AppSettings::editorDarkTheme());
|
|
actionCollection()->addAction(u"toggle_editor_dark_theme"_s, toggleEditorDarkThemeAction);
|
|
connect(toggleEditorDarkThemeAction, &QAction::toggled, this, [this](bool checked){
|
|
AppSettings::setEditorDarkTheme(checked);
|
|
AppSettings::self()->save();
|
|
applySettingsToAllEditors();
|
|
});
|
|
|
|
// Load themes
|
|
KColorSchemeManager *manager = KColorSchemeManager::instance();
|
|
auto *colorSelectionMenu = KColorSchemeMenu::createMenu(manager, this);
|
|
colorSelectionMenu->menu()->setTitle("&Window Color Scheme");
|
|
actionCollection()->addAction(QStringLiteral("colorscheme_menu"), colorSelectionMenu);
|
|
|
|
setupGUI();
|
|
}
|
|
|
|
void MainWindow::showSettings()
|
|
{
|
|
if (KConfigDialog::showDialog(QStringLiteral("settings"))) {
|
|
return;
|
|
}
|
|
KConfigDialog *dialog = new KConfigDialog(this, QStringLiteral("settings"), AppSettings::self());
|
|
dialog->setFaceType(KPageDialog::FlatList);
|
|
dialog->addPage(new SettingsPage<Ui::GeneralSettings>(dialog), "Appearance", "preferences-desktop-theme-global");
|
|
|
|
connect(dialog, &KConfigDialog::settingsChanged, this, [this](const QString &dialogName){
|
|
Q_UNUSED(dialogName)
|
|
applySettingsToAllEditors();
|
|
if (QAction *action = actionCollection()->action(QStringLiteral("toggle_editor_dark_theme"))) {
|
|
action->setChecked(AppSettings::editorDarkTheme());
|
|
}
|
|
});
|
|
|
|
dialog->show();
|
|
}
|
|
|
|
void MainWindow::applyLexer(const QString &lexerName)
|
|
{
|
|
if (SciEdit *editor = m_tabWidget->currentEditor()) {
|
|
Scintilla::ILexer5 * lexer = CreateLexer(lexerName.toStdString().c_str());
|
|
editor->setLexer(lexer);
|
|
const int docId = m_tabWidget->currentDocumentId();
|
|
if (docId >= 0) {
|
|
m_documentManager->setDocumentLanguage(docId, lexerName);
|
|
}
|
|
updateStatusBar();
|
|
}
|
|
}
|
|
|
|
void MainWindow::newFile()
|
|
{
|
|
m_tabWidget->newDocument();
|
|
updateActions();
|
|
}
|
|
|
|
void MainWindow::openFile()
|
|
{
|
|
QString fileName = QFileDialog::getOpenFileName(this,
|
|
tr("Open File"),
|
|
QString(),
|
|
tr("All Files (*.*)"));
|
|
|
|
if (!fileName.isEmpty()) {
|
|
m_tabWidget->openDocument(fileName);
|
|
}
|
|
}
|
|
|
|
void MainWindow::saveFile()
|
|
{
|
|
m_tabWidget->saveCurrentDocument();
|
|
}
|
|
|
|
void MainWindow::saveAsFile()
|
|
{
|
|
m_tabWidget->saveCurrentDocumentAs();
|
|
}
|
|
|
|
void MainWindow::closeFile()
|
|
{
|
|
m_tabWidget->closeCurrentTab();
|
|
}
|
|
|
|
void MainWindow::onCurrentTabChanged()
|
|
{
|
|
updateWindowTitle();
|
|
updateStatusBar();
|
|
updateActions();
|
|
}
|
|
|
|
void MainWindow::onCurrentEditorChanged(SciEdit *editor)
|
|
{
|
|
Q_UNUSED(editor)
|
|
updateWindowTitle();
|
|
updateStatusBar();
|
|
updateActions();
|
|
|
|
// 连接当前编辑器的光标位置变化信号
|
|
if (editor) {
|
|
applySettingsToEditor(editor);
|
|
connect(editor, &SciEdit::cursorPosChanged, this, [this](int line, int column) {
|
|
m_cursorPosStatusLabel->setText(QString("Ln: %1 Col: %2").arg(line).arg(column));
|
|
});
|
|
}
|
|
}
|
|
|
|
void MainWindow::applySettingsToEditor(SciEdit *editor)
|
|
{
|
|
if (!editor) {
|
|
return;
|
|
}
|
|
editor->setTabWidth(AppSettings::tabWidth());
|
|
editor->setEditorFont(AppSettings::editorFont());
|
|
editor->applyTheme(AppSettings::editorDarkTheme());
|
|
editor->setIndentationGuides(m_indentGuidesEnabled ? SC_IV_LOOKBOTH : SC_IV_NONE);
|
|
}
|
|
|
|
void MainWindow::applySettingsToAllEditors()
|
|
{
|
|
for (int i = 0; i < m_tabWidget->count(); ++i) {
|
|
applySettingsToEditor(m_tabWidget->editorAt(i));
|
|
}
|
|
}
|
|
|
|
void MainWindow::onDocumentModified(int docIndex, bool modified)
|
|
{
|
|
Q_UNUSED(docIndex)
|
|
Q_UNUSED(modified)
|
|
updateWindowTitle();
|
|
}
|
|
|
|
void MainWindow::updateWindowTitle()
|
|
{
|
|
QString title = "Pineapple Notepad";
|
|
|
|
if (SciEdit *editor = m_tabWidget->currentEditor()) {
|
|
int docId = m_tabWidget->currentDocumentId();
|
|
if (docId >= 0) {
|
|
QString fileName = m_documentManager->getDocumentTitle(docId);
|
|
bool isModified = m_documentManager->isDocumentModified(docId);
|
|
|
|
title = QString("%1%2")
|
|
.arg(fileName)
|
|
.arg(isModified ? "*" : "");
|
|
}
|
|
}
|
|
|
|
setWindowTitle(title);
|
|
}
|
|
|
|
void MainWindow::updateStatusBar()
|
|
{
|
|
if (SciEdit *editor = m_tabWidget->currentEditor()) {
|
|
int docId = m_tabWidget->currentDocumentId();
|
|
if (docId >= 0) {
|
|
QString encoding = m_documentManager->getDocumentEncoding(docId);
|
|
const QString lexerName = m_documentManager->getDocumentLanguage(docId);
|
|
const QString language = lexerName.isEmpty() ? QStringLiteral("Plain Text") : LexerGroupActionMenu::displayName(lexerName);
|
|
|
|
m_encodingStatusLabel->setText(encoding);
|
|
m_languageStatusLabel->setText(language);
|
|
}
|
|
} else {
|
|
m_encodingStatusLabel->setText("UTF-8");
|
|
m_languageStatusLabel->setText("Plain Text");
|
|
}
|
|
}
|
|
|
|
void MainWindow::updateActions()
|
|
{
|
|
bool hasEditor = (m_tabWidget->currentEditor() != nullptr);
|
|
|
|
// 更新编辑相关的动作状态
|
|
if (QAction *undoAction = actionCollection()->action(KStandardAction::name(KStandardAction::Undo))) {
|
|
undoAction->setEnabled(hasEditor);
|
|
}
|
|
if (QAction *redoAction = actionCollection()->action(KStandardAction::name(KStandardAction::Redo))) {
|
|
redoAction->setEnabled(hasEditor);
|
|
}
|
|
if (QAction *cutAction = actionCollection()->action(KStandardAction::name(KStandardAction::Cut))) {
|
|
cutAction->setEnabled(hasEditor);
|
|
}
|
|
if (QAction *copyAction = actionCollection()->action(KStandardAction::name(KStandardAction::Copy))) {
|
|
copyAction->setEnabled(hasEditor);
|
|
}
|
|
if (QAction *pasteAction = actionCollection()->action(KStandardAction::name(KStandardAction::Paste))) {
|
|
pasteAction->setEnabled(hasEditor && m_tabWidget->currentEditor()->canPaste());
|
|
}
|
|
if (QAction *indentGuideAction = actionCollection()->action(QStringLiteral("toggle_indent_guide"))) {
|
|
indentGuideAction->setEnabled(hasEditor);
|
|
indentGuideAction->setChecked(m_indentGuidesEnabled);
|
|
}
|
|
}
|
|
|
|
void MainWindow::showFindReplaceDialog(bool showReplace)
|
|
{
|
|
if (!m_findReplaceDialog) {
|
|
m_findReplaceDialog = new FindReplaceDialog(this);
|
|
connect(m_findReplaceDialog, &FindReplaceDialog::findRequested, this, [this](bool forward) {
|
|
findNextInCurrentEditor(forward, true);
|
|
});
|
|
connect(m_findReplaceDialog, &FindReplaceDialog::replaceRequested, this, [this]() {
|
|
replaceOneInCurrentEditor(true);
|
|
});
|
|
connect(m_findReplaceDialog, &FindReplaceDialog::replaceAllRequested, this, [this]() {
|
|
replaceAllInCurrentEditor(true);
|
|
});
|
|
}
|
|
|
|
if (SciEdit *editor = m_tabWidget->currentEditor()) {
|
|
const int selStart = static_cast<int>(editor->selectionStart());
|
|
const int selEnd = static_cast<int>(editor->selectionEnd());
|
|
if (selStart != selEnd) {
|
|
const int start = qMin(selStart, selEnd);
|
|
const int end = qMax(selStart, selEnd);
|
|
const QByteArray selected = editor->textRange(start, end);
|
|
m_findReplaceDialog->setFindTextIfNotEmpty(QString::fromUtf8(selected));
|
|
}
|
|
}
|
|
|
|
if (showReplace) {
|
|
m_findReplaceDialog->openReplace();
|
|
} else {
|
|
m_findReplaceDialog->openFind();
|
|
}
|
|
}
|
|
|
|
static sptr_t buildSearchFlags(const FindReplaceDialog *dialog)
|
|
{
|
|
sptr_t flags = 0;
|
|
if (dialog->matchCase()) {
|
|
flags |= SCFIND_MATCHCASE;
|
|
}
|
|
if (dialog->wholeWord()) {
|
|
flags |= SCFIND_WHOLEWORD;
|
|
}
|
|
if (dialog->searchMode() == SearchMode::Regex) {
|
|
flags |= SCFIND_REGEXP;
|
|
}
|
|
return flags;
|
|
}
|
|
|
|
static QString buildFindPattern(const FindReplaceDialog *dialog)
|
|
{
|
|
return dialog->findTextForSearch();
|
|
}
|
|
|
|
static QString buildReplacementText(const FindReplaceDialog *dialog)
|
|
{
|
|
return dialog->replaceTextForReplace();
|
|
}
|
|
|
|
bool MainWindow::findNextInCurrentEditor(bool forward, bool showNotFoundMessage)
|
|
{
|
|
SciEdit *editor = m_tabWidget->currentEditor();
|
|
if (!editor) {
|
|
return false;
|
|
}
|
|
|
|
if (!m_findReplaceDialog) {
|
|
m_findReplaceDialog = new FindReplaceDialog(this);
|
|
connect(m_findReplaceDialog, &FindReplaceDialog::findRequested, this, [this](bool forward) {
|
|
findNextInCurrentEditor(forward, true);
|
|
});
|
|
connect(m_findReplaceDialog, &FindReplaceDialog::replaceRequested, this, [this]() {
|
|
replaceOneInCurrentEditor(true);
|
|
});
|
|
connect(m_findReplaceDialog, &FindReplaceDialog::replaceAllRequested, this, [this]() {
|
|
replaceAllInCurrentEditor(true);
|
|
});
|
|
}
|
|
|
|
const QString needle = buildFindPattern(m_findReplaceDialog);
|
|
if (needle.isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
const QByteArray needleBytes = needle.toUtf8();
|
|
const sptr_t flags = buildSearchFlags(m_findReplaceDialog);
|
|
|
|
const int docLen = static_cast<int>(editor->textLength());
|
|
int rangeStart = 0;
|
|
int rangeEnd = docLen;
|
|
|
|
if (m_findReplaceDialog->inSelection()) {
|
|
if (!m_findInSelectionEnabled) {
|
|
const int selStart = static_cast<int>(editor->selectionStart());
|
|
const int selEnd = static_cast<int>(editor->selectionEnd());
|
|
if (selStart != selEnd) {
|
|
m_findSelectionStart = qMin(selStart, selEnd);
|
|
m_findSelectionEnd = qMax(selStart, selEnd);
|
|
m_findInSelectionEnabled = true;
|
|
}
|
|
}
|
|
} else {
|
|
m_findInSelectionEnabled = false;
|
|
}
|
|
|
|
if (m_findInSelectionEnabled) {
|
|
rangeStart = qBound(0, m_findSelectionStart, docLen);
|
|
rangeEnd = qBound(0, m_findSelectionEnd, docLen);
|
|
if (rangeStart >= rangeEnd) {
|
|
m_findInSelectionEnabled = false;
|
|
rangeStart = 0;
|
|
rangeEnd = docLen;
|
|
}
|
|
}
|
|
|
|
const int selStartNow = static_cast<int>(editor->selectionStart());
|
|
const int selEndNow = static_cast<int>(editor->selectionEnd());
|
|
int caretBase = static_cast<int>(editor->currentPos());
|
|
if (forward && selStartNow != selEndNow) {
|
|
caretBase = qMax(selStartNow, selEndNow);
|
|
}
|
|
caretBase = qBound(rangeStart, caretBase, rangeEnd);
|
|
|
|
auto selectMatch = [&](int start, int end) {
|
|
editor->setSel(start, end);
|
|
editor->scrollCaret();
|
|
};
|
|
|
|
auto showNotFound = [&]() {
|
|
if (!showNotFoundMessage) {
|
|
return;
|
|
}
|
|
QMessageBox::information(this, QStringLiteral("Find"), QStringLiteral("Text not found."));
|
|
};
|
|
|
|
if (forward) {
|
|
const QPair<int, int> found = editor->findText(static_cast<int>(flags), needleBytes.constData(), caretBase, rangeEnd);
|
|
if (found.first >= 0) {
|
|
selectMatch(found.first, found.second);
|
|
return true;
|
|
}
|
|
if (m_findReplaceDialog->wrapAround()) {
|
|
const QPair<int, int> wrapped = editor->findText(static_cast<int>(flags), needleBytes.constData(), rangeStart, rangeEnd);
|
|
if (wrapped.first >= 0) {
|
|
selectMatch(wrapped.first, wrapped.second);
|
|
return true;
|
|
}
|
|
}
|
|
showNotFound();
|
|
return false;
|
|
}
|
|
|
|
auto findLastBefore = [&](int endExclusive) -> QPair<int, int> {
|
|
int bestStart = -1;
|
|
int bestEnd = -1;
|
|
int scanPos = rangeStart;
|
|
while (scanPos < endExclusive) {
|
|
const QPair<int, int> found = editor->findText(static_cast<int>(flags), needleBytes.constData(), scanPos, endExclusive);
|
|
if (found.first < 0 || found.first >= endExclusive) {
|
|
break;
|
|
}
|
|
bestStart = found.first;
|
|
bestEnd = found.second;
|
|
scanPos = found.first + 1;
|
|
}
|
|
return QPair<int, int>(bestStart, bestEnd);
|
|
};
|
|
|
|
QPair<int, int> found = findLastBefore(caretBase);
|
|
if (found.first >= 0) {
|
|
selectMatch(found.first, found.second);
|
|
return true;
|
|
}
|
|
|
|
if (m_findReplaceDialog->wrapAround()) {
|
|
found = findLastBefore(rangeEnd);
|
|
if (found.first >= 0) {
|
|
selectMatch(found.first, found.second);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
showNotFound();
|
|
return false;
|
|
}
|
|
|
|
bool MainWindow::replaceOneInCurrentEditor(bool showNotFoundMessage)
|
|
{
|
|
SciEdit *editor = m_tabWidget->currentEditor();
|
|
if (!editor) {
|
|
return false;
|
|
}
|
|
if (!m_findReplaceDialog) {
|
|
m_findReplaceDialog = new FindReplaceDialog(this);
|
|
connect(m_findReplaceDialog, &FindReplaceDialog::findRequested, this, [this](bool forward) {
|
|
findNextInCurrentEditor(forward, true);
|
|
});
|
|
connect(m_findReplaceDialog, &FindReplaceDialog::replaceRequested, this, [this]() {
|
|
replaceOneInCurrentEditor(true);
|
|
});
|
|
connect(m_findReplaceDialog, &FindReplaceDialog::replaceAllRequested, this, [this]() {
|
|
replaceAllInCurrentEditor(true);
|
|
});
|
|
}
|
|
|
|
const QString needle = buildFindPattern(m_findReplaceDialog);
|
|
if (needle.isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
if (!findNextInCurrentEditor(true, showNotFoundMessage)) {
|
|
return false;
|
|
}
|
|
|
|
const QString replacement = buildReplacementText(m_findReplaceDialog);
|
|
const QByteArray replacementBytes = replacement.toUtf8();
|
|
|
|
editor->targetFromSelection();
|
|
if (m_findReplaceDialog->searchMode() == SearchMode::Regex) {
|
|
editor->replaceTargetRE(replacementBytes.size(), replacementBytes.constData());
|
|
} else {
|
|
editor->replaceTarget(replacementBytes.size(), replacementBytes.constData());
|
|
}
|
|
|
|
findNextInCurrentEditor(true, false);
|
|
return true;
|
|
}
|
|
|
|
int MainWindow::replaceAllInCurrentEditor(bool showNotFoundMessage)
|
|
{
|
|
SciEdit *editor = m_tabWidget->currentEditor();
|
|
if (!editor) {
|
|
return 0;
|
|
}
|
|
if (!m_findReplaceDialog) {
|
|
m_findReplaceDialog = new FindReplaceDialog(this);
|
|
connect(m_findReplaceDialog, &FindReplaceDialog::findRequested, this, [this](bool forward) {
|
|
findNextInCurrentEditor(forward, true);
|
|
});
|
|
connect(m_findReplaceDialog, &FindReplaceDialog::replaceRequested, this, [this]() {
|
|
replaceOneInCurrentEditor(true);
|
|
});
|
|
connect(m_findReplaceDialog, &FindReplaceDialog::replaceAllRequested, this, [this]() {
|
|
replaceAllInCurrentEditor(true);
|
|
});
|
|
}
|
|
|
|
const QString needle = buildFindPattern(m_findReplaceDialog);
|
|
if (needle.isEmpty()) {
|
|
return 0;
|
|
}
|
|
|
|
const QByteArray needleBytes = needle.toUtf8();
|
|
const QString replacement = buildReplacementText(m_findReplaceDialog);
|
|
const QByteArray replacementBytes = replacement.toUtf8();
|
|
const sptr_t flags = buildSearchFlags(m_findReplaceDialog);
|
|
|
|
const int docLen = static_cast<int>(editor->textLength());
|
|
int rangeStart = 0;
|
|
int rangeEnd = docLen;
|
|
|
|
if (m_findReplaceDialog->inSelection()) {
|
|
if (!m_findInSelectionEnabled) {
|
|
const int selStart = static_cast<int>(editor->selectionStart());
|
|
const int selEnd = static_cast<int>(editor->selectionEnd());
|
|
if (selStart != selEnd) {
|
|
m_findSelectionStart = qMin(selStart, selEnd);
|
|
m_findSelectionEnd = qMax(selStart, selEnd);
|
|
m_findInSelectionEnabled = true;
|
|
}
|
|
}
|
|
} else {
|
|
m_findInSelectionEnabled = false;
|
|
}
|
|
|
|
if (m_findInSelectionEnabled) {
|
|
rangeStart = qBound(0, m_findSelectionStart, docLen);
|
|
rangeEnd = qBound(0, m_findSelectionEnd, docLen);
|
|
if (rangeStart >= rangeEnd) {
|
|
m_findInSelectionEnabled = false;
|
|
rangeStart = 0;
|
|
rangeEnd = docLen;
|
|
}
|
|
}
|
|
|
|
editor->setSearchFlags(flags);
|
|
|
|
int replacedCount = 0;
|
|
int start = rangeStart;
|
|
int end = rangeEnd;
|
|
|
|
editor->beginUndoAction();
|
|
while (start <= end) {
|
|
editor->setTargetRange(start, end);
|
|
const sptr_t pos = editor->searchInTarget(needleBytes.size(), needleBytes.constData());
|
|
if (pos < 0) {
|
|
break;
|
|
}
|
|
|
|
const int matchStart = static_cast<int>(editor->targetStart());
|
|
const int matchEnd = static_cast<int>(editor->targetEnd());
|
|
const int matchLen = matchEnd - matchStart;
|
|
|
|
sptr_t replacedLen = 0;
|
|
if (m_findReplaceDialog->searchMode() == SearchMode::Regex) {
|
|
replacedLen = editor->replaceTargetRE(replacementBytes.size(), replacementBytes.constData());
|
|
} else {
|
|
replacedLen = editor->replaceTarget(replacementBytes.size(), replacementBytes.constData());
|
|
}
|
|
|
|
++replacedCount;
|
|
const int delta = static_cast<int>(replacedLen) - matchLen;
|
|
end += delta;
|
|
start = matchStart + static_cast<int>(replacedLen);
|
|
}
|
|
editor->endUndoAction();
|
|
|
|
if (replacedCount == 0 && showNotFoundMessage) {
|
|
QMessageBox::information(this, QStringLiteral("Replace"), QStringLiteral("Text not found."));
|
|
}
|
|
|
|
return replacedCount;
|
|
}
|