feat: 回归代码搜索替换和转到行功能;完成回归文本预览;

This commit is contained in:
寂静的羽夏 2025-03-23 22:20:06 +08:00
parent b4a03ae827
commit 77bec99ac5
18 changed files with 824 additions and 755 deletions

View File

@ -177,9 +177,7 @@ set(DIALOG_SRC
src/dialog/showtextdialog.cpp
src/dialog/showtextdialog.h
src/dialog/definitiondownload.cpp
src/dialog/definitiondownload.h
src/dialog/searchdialog.cpp
src/dialog/searchdialog.h)
src/dialog/definitiondownload.h)
set(CONTROL_SRC
src/control/codeedit.h
@ -216,7 +214,13 @@ set(CONTROL_SRC
src/control/popupactionwidget.h
src/control/popupactionwidget.cpp
src/control/settingspopup.cpp
src/control/settingspopup.h)
src/control/settingspopup.h
src/control/searchreplacewidget.cpp
src/control/searchreplacewidget.h
src/control/gotolinewidget.h
src/control/gotolinewidget.cpp
src/control/codeeditcontrolwidget.h
src/control/codeeditcontrolwidget.cpp)
set(CLASS_SRC
src/class/logger.cpp

View File

@ -273,37 +273,37 @@
<message>
<location filename="../../src/dialog/definitiondownload.cpp" line="43"/>
<source>Update Syntax Definitions</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<location filename="../../src/dialog/definitiondownload.cpp" line="72"/>
<source>Updating syntax definitions from online repository...</source>
<translation type="unfinished"></translation>
<translation>线...</translation>
</message>
<message>
<location filename="../../src/dialog/definitiondownload.cpp" line="85"/>
<source>%1:%2 hours</source>
<translation type="unfinished"></translation>
<translation>%1:%2 </translation>
</message>
<message>
<location filename="../../src/dialog/definitiondownload.cpp" line="90"/>
<source>%1:%2 minutes</source>
<translation type="unfinished"></translation>
<translation>%1:%2 </translation>
</message>
<message>
<location filename="../../src/dialog/definitiondownload.cpp" line="97"/>
<source>%1.%2 seconds</source>
<translation type="unfinished"></translation>
<translation>%1:%2 </translation>
</message>
<message>
<location filename="../../src/dialog/definitiondownload.cpp" line="99"/>
<source>%1 ms</source>
<translation type="unfinished"></translation>
<translation>%1 </translation>
</message>
<message>
<location filename="../../src/dialog/definitiondownload.cpp" line="102"/>
<source>Update operation completed (%1)</source>
<translation type="unfinished"></translation>
<translation>%1</translation>
</message>
</context>
<context>
@ -681,6 +681,14 @@
<translation></translation>
</message>
</context>
<context>
<name>GotoLineWidget</name>
<message>
<location filename="../../src/control/gotolinewidget.cpp" line="32"/>
<source>Close</source>
<translation></translation>
</message>
</context>
<context>
<name>GotoWidget</name>
<message>
@ -4874,140 +4882,87 @@
</message>
</context>
<context>
<name>SearchDialog</name>
<name>SearchReplaceWidget</name>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="207"/>
<source>Find and Replace...</source>
<translation type="unfinished"></translation>
<location filename="../../src/control/searchreplacewidget.cpp" line="172"/>
<source>Close</source>
<translation></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="213"/>
<source>Match ca&amp;se</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="215"/>
<source>Match &amp;whole words</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="217"/>
<source>Regular e&amp;xpressions</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="219"/>
<source>&amp;Escape sequences</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="221"/>
<source>Wrap Aro&amp;und</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="232"/>
<source>Find &amp;Next</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="234"/>
<source>Find &amp;Previous</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="236"/>
<source>&amp;Replace</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="238"/>
<source>Replace &amp;All</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="240"/>
<source>&amp;In Selection</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="244"/>
<source>&amp;Close</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="264"/>
<source>&amp;Find:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="269"/>
<source>Replace wit&amp;h:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="520"/>
<location filename="../../src/dialog/searchdialog.cpp" line="581"/>
<source>The specified text was not found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="588"/>
<source>The specified text was not found in the selection</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="617"/>
<source>Successfully replaced %1 matches</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SearchWidget</name>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="51"/>
<location filename="../../src/control/searchreplacewidget.cpp" line="181"/>
<source>Search Settings</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="56"/>
<location filename="../../src/control/searchreplacewidget.cpp" line="186"/>
<source>Match ca&amp;se</source>
<translation type="unfinished"></translation>
<translation>(&amp;S)</translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="58"/>
<location filename="../../src/control/searchreplacewidget.cpp" line="188"/>
<source>Match &amp;whole words</source>
<translation type="unfinished"></translation>
<translation>(&amp;W)</translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="60"/>
<location filename="../../src/control/searchreplacewidget.cpp" line="190"/>
<source>Regular e&amp;xpressions</source>
<translation type="unfinished"></translation>
<translation>(&amp;X)</translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="62"/>
<location filename="../../src/control/searchreplacewidget.cpp" line="192"/>
<source>&amp;Escape sequences</source>
<translation type="unfinished"></translation>
<translation>(&amp;E)</translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="64"/>
<location filename="../../src/control/searchreplacewidget.cpp" line="194"/>
<source>Wrap Aro&amp;und</source>
<translation type="unfinished"></translation>
<translation>(&amp;U)</translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="86"/>
<location filename="../../src/control/searchreplacewidget.cpp" line="197"/>
<source>Replace in selection</source>
<translation></translation>
</message>
<message>
<location filename="../../src/control/searchreplacewidget.cpp" line="220"/>
<source>Find Next</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="91"/>
<location filename="../../src/control/searchreplacewidget.cpp" line="225"/>
<source>Find Previous</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="151"/>
<location filename="../../src/control/searchreplacewidget.cpp" line="237"/>
<source>Replace Current</source>
<translation></translation>
</message>
<message>
<location filename="../../src/control/searchreplacewidget.cpp" line="242"/>
<source>Replace All</source>
<translation></translation>
</message>
<message>
<location filename="../../src/control/searchreplacewidget.cpp" line="258"/>
<source>Replace:</source>
<translation></translation>
</message>
<message>
<location filename="../../src/control/searchreplacewidget.cpp" line="368"/>
<location filename="../../src/control/searchreplacewidget.cpp" line="430"/>
<source>The specified text was not found</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<location filename="../../src/control/searchreplacewidget.cpp" line="375"/>
<source>The specified text was not found in the selection</source>
<translation></translation>
</message>
<message>
<location filename="../../src/control/searchreplacewidget.cpp" line="404"/>
<source>Successfully replaced %1 matches</source>
<translation> %1 </translation>
</message>
</context>
<context>
@ -5069,7 +5024,7 @@
<message>
<location filename="../../src/dialog/showtextdialog.cpp" line="107"/>
<source>Plain Text</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<location filename="../../src/dialog/showtextdialog.cpp" line="116"/>
@ -5080,7 +5035,7 @@
<location filename="../../src/dialog/showtextdialog.cpp" line="121"/>
<location filename="../../src/dialog/showtextdialog.cpp" line="188"/>
<source>Loading...</source>
<translation type="unfinished"></translation>
<translation>...</translation>
</message>
<message>
<location filename="../../src/dialog/showtextdialog.cpp" line="141"/>
@ -5105,7 +5060,7 @@
<message>
<location filename="../../src/dialog/showtextdialog.cpp" line="165"/>
<source>UpdateDefs</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<location filename="../../src/dialog/showtextdialog.cpp" line="175"/>
@ -5116,12 +5071,12 @@
<context>
<name>SkinManager</name>
<message>
<location filename="../../src/class/skinmanager.cpp" line="67"/>
<location filename="../../src/class/skinmanager.cpp" line="78"/>
<source>Dark</source>
<translation></translation>
</message>
<message>
<location filename="../../src/class/skinmanager.cpp" line="68"/>
<location filename="../../src/class/skinmanager.cpp" line="79"/>
<source>Light</source>
<translation></translation>
</message>

View File

@ -273,37 +273,37 @@
<message>
<location filename="../../src/dialog/definitiondownload.cpp" line="43"/>
<source>Update Syntax Definitions</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<location filename="../../src/dialog/definitiondownload.cpp" line="72"/>
<source>Updating syntax definitions from online repository...</source>
<translation type="unfinished"></translation>
<translation>...</translation>
</message>
<message>
<location filename="../../src/dialog/definitiondownload.cpp" line="85"/>
<source>%1:%2 hours</source>
<translation type="unfinished"></translation>
<translation>%1:%2 </translation>
</message>
<message>
<location filename="../../src/dialog/definitiondownload.cpp" line="90"/>
<source>%1:%2 minutes</source>
<translation type="unfinished"></translation>
<translation>%1:%2 </translation>
</message>
<message>
<location filename="../../src/dialog/definitiondownload.cpp" line="97"/>
<source>%1.%2 seconds</source>
<translation type="unfinished"></translation>
<translation>%1:%2 </translation>
</message>
<message>
<location filename="../../src/dialog/definitiondownload.cpp" line="99"/>
<source>%1 ms</source>
<translation type="unfinished"></translation>
<translation>%1 </translation>
</message>
<message>
<location filename="../../src/dialog/definitiondownload.cpp" line="102"/>
<source>Update operation completed (%1)</source>
<translation type="unfinished"></translation>
<translation>%1</translation>
</message>
</context>
<context>
@ -681,6 +681,14 @@
<translation></translation>
</message>
</context>
<context>
<name>GotoLineWidget</name>
<message>
<location filename="../../src/control/gotolinewidget.cpp" line="32"/>
<source>Close</source>
<translation></translation>
</message>
</context>
<context>
<name>GotoWidget</name>
<message>
@ -4874,140 +4882,87 @@
</message>
</context>
<context>
<name>SearchDialog</name>
<name>SearchReplaceWidget</name>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="207"/>
<source>Find and Replace...</source>
<translation type="unfinished"></translation>
<location filename="../../src/control/searchreplacewidget.cpp" line="172"/>
<source>Close</source>
<translation></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="213"/>
<source>Match ca&amp;se</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="215"/>
<source>Match &amp;whole words</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="217"/>
<source>Regular e&amp;xpressions</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="219"/>
<source>&amp;Escape sequences</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="221"/>
<source>Wrap Aro&amp;und</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="232"/>
<source>Find &amp;Next</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="234"/>
<source>Find &amp;Previous</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="236"/>
<source>&amp;Replace</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="238"/>
<source>Replace &amp;All</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="240"/>
<source>&amp;In Selection</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="244"/>
<source>&amp;Close</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="264"/>
<source>&amp;Find:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="269"/>
<source>Replace wit&amp;h:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="520"/>
<location filename="../../src/dialog/searchdialog.cpp" line="581"/>
<source>The specified text was not found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="588"/>
<source>The specified text was not found in the selection</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="617"/>
<source>Successfully replaced %1 matches</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SearchWidget</name>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="51"/>
<location filename="../../src/control/searchreplacewidget.cpp" line="181"/>
<source>Search Settings</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="56"/>
<location filename="../../src/control/searchreplacewidget.cpp" line="186"/>
<source>Match ca&amp;se</source>
<translation type="unfinished"></translation>
<translation>(&amp;S)</translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="58"/>
<location filename="../../src/control/searchreplacewidget.cpp" line="188"/>
<source>Match &amp;whole words</source>
<translation type="unfinished"></translation>
<translation>(&amp;W)</translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="60"/>
<location filename="../../src/control/searchreplacewidget.cpp" line="190"/>
<source>Regular e&amp;xpressions</source>
<translation type="unfinished"></translation>
<translation>(&amp;X)</translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="62"/>
<location filename="../../src/control/searchreplacewidget.cpp" line="192"/>
<source>&amp;Escape sequences</source>
<translation type="unfinished"></translation>
<translation>(&amp;E)</translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="64"/>
<location filename="../../src/control/searchreplacewidget.cpp" line="194"/>
<source>Wrap Aro&amp;und</source>
<translation type="unfinished"></translation>
<translation>(&amp;U)</translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="86"/>
<location filename="../../src/control/searchreplacewidget.cpp" line="197"/>
<source>Replace in selection</source>
<translation></translation>
</message>
<message>
<location filename="../../src/control/searchreplacewidget.cpp" line="220"/>
<source>Find Next</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="91"/>
<location filename="../../src/control/searchreplacewidget.cpp" line="225"/>
<source>Find Previous</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<location filename="../../src/dialog/searchdialog.cpp" line="151"/>
<location filename="../../src/control/searchreplacewidget.cpp" line="237"/>
<source>Replace Current</source>
<translation></translation>
</message>
<message>
<location filename="../../src/control/searchreplacewidget.cpp" line="242"/>
<source>Replace All</source>
<translation></translation>
</message>
<message>
<location filename="../../src/control/searchreplacewidget.cpp" line="258"/>
<source>Replace:</source>
<translation></translation>
</message>
<message>
<location filename="../../src/control/searchreplacewidget.cpp" line="368"/>
<location filename="../../src/control/searchreplacewidget.cpp" line="430"/>
<source>The specified text was not found</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<location filename="../../src/control/searchreplacewidget.cpp" line="375"/>
<source>The specified text was not found in the selection</source>
<translation></translation>
</message>
<message>
<location filename="../../src/control/searchreplacewidget.cpp" line="404"/>
<source>Successfully replaced %1 matches</source>
<translation> %1 </translation>
</message>
</context>
<context>
@ -5069,7 +5024,7 @@
<message>
<location filename="../../src/dialog/showtextdialog.cpp" line="107"/>
<source>Plain Text</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<location filename="../../src/dialog/showtextdialog.cpp" line="116"/>
@ -5080,7 +5035,7 @@
<location filename="../../src/dialog/showtextdialog.cpp" line="121"/>
<location filename="../../src/dialog/showtextdialog.cpp" line="188"/>
<source>Loading...</source>
<translation type="unfinished"></translation>
<translation>...</translation>
</message>
<message>
<location filename="../../src/dialog/showtextdialog.cpp" line="141"/>
@ -5105,7 +5060,7 @@
<message>
<location filename="../../src/dialog/showtextdialog.cpp" line="165"/>
<source>UpdateDefs</source>
<translation type="unfinished"></translation>
<translation></translation>
</message>
<message>
<location filename="../../src/dialog/showtextdialog.cpp" line="175"/>
@ -5116,12 +5071,12 @@
<context>
<name>SkinManager</name>
<message>
<location filename="../../src/class/skinmanager.cpp" line="67"/>
<location filename="../../src/class/skinmanager.cpp" line="78"/>
<source>Dark</source>
<translation></translation>
</message>
<message>
<location filename="../../src/class/skinmanager.cpp" line="68"/>
<location filename="../../src/class/skinmanager.cpp" line="79"/>
<source>Light</source>
<translation></translation>
</message>

View File

@ -61,6 +61,17 @@ SkinManager &SkinManager::instance() {
void SkinManager::setTheme(SkinManager::Theme theme) { m_theme = theme; }
QIcon SkinManager::themeIcon(const QString &name) {
switch (m_theme) {
case Theme::Dark:
return QIcon(QStringLiteral("://dark/") + name +
QStringLiteral(".svg"));
case Theme::Light:
return QIcon(QStringLiteral("://light/") + name +
QStringLiteral(".svg"));
}
}
SkinManager::Theme SkinManager::currentTheme() const { return m_theme; }
void SkinManager::translate() {

View File

@ -37,6 +37,8 @@ public:
void setTheme(Theme theme);
Theme currentTheme() const;
QIcon themeIcon(const QString &name);
private:
Q_DECL_UNUSED void translate();

View File

@ -16,12 +16,15 @@
*/
#include "codeedit.h"
#include "class/qkeysequences.h"
#include "class/scriptsettings.h"
#include "class/skinmanager.h"
#include "control/searchreplacewidget.h"
#include "model/codecompletionmodel.h"
#include <QApplication>
#include <QModelIndex>
#include <QScrollBar>
#include <QShortcut>
#include <KSyntaxHighlighting/Definition>
@ -31,28 +34,49 @@
CodeEdit::CodeEdit(QWidget *parent) : WingCodeEdit(parent) {
connect(this->document(), &QTextDocument::modificationChanged, this,
&CodeEdit::contentModified);
addMoveLineShortCut();
addEditorBasicShortcut();
addMoveLineShortcut();
connect(&ScriptSettings::instance(), &ScriptSettings::editorSettingsUpdate,
this, &CodeEdit::applyEditorSetStyle);
applyEditorSetStyle();
m_searchWidget = new SearchWidget(this, parent);
showSearchBar(false);
m_searchWidget = new SearchReplaceWidget(this);
connect(this, &CodeEdit::textChanged, this, [this] {
if (m_searchWidget->isVisible())
showSearchBar(false);
showSearchReplaceBar(false, false);
});
m_gotoWidget = new GotoLineWidget(this);
connect(this, &CodeEdit::blockCountChanged, m_gotoWidget,
&GotoLineWidget::setTotalLines);
connect(m_gotoWidget, &GotoLineWidget::onGotoLine, this, [this](int line) {
auto doc = document();
auto block = doc->findBlockByNumber(line - 1);
if (block.isValid()) {
QTextCursor cursor(block);
cursor.movePosition(QTextCursor::EndOfBlock);
setTextCursor(cursor);
ensureCursorVisible();
}
});
showSearchReplaceBar(false, false);
showGotoBar(false);
}
void CodeEdit::showSearchBar(bool show) {
void CodeEdit::showSearchReplaceBar(bool show, bool replace) {
if (m_gotoWidget->isVisible()) {
showGotoBar(false);
}
m_searchWidget->setVisible(show);
m_searchWidget->setEnabled(show);
if (show) {
const QTextCursor cursor = textCursor();
if (cursor.hasSelection())
m_searchWidget->setSearchText(cursor.selectedText());
m_searchWidget->setReplaceMode(replace);
m_searchWidget->activate();
} else {
clearLiveSearch();
@ -60,6 +84,15 @@ void CodeEdit::showSearchBar(bool show) {
}
}
void CodeEdit::showGotoBar(bool show) {
if (m_searchWidget->isVisible()) {
showSearchReplaceBar(false, false);
}
m_gotoWidget->setOriginLine(textCursor().blockNumber() + 1);
m_gotoWidget->setVisible(show);
m_gotoWidget->setEnabled(show);
}
void CodeEdit::onCompletion(const QModelIndex &index) {
WingCodeEdit::onCompletion(index);
auto selfdata = index.data(Qt::SelfDataRole).value<CodeInfoTip>();
@ -75,7 +108,23 @@ void CodeEdit::onCompletion(const QModelIndex &index) {
}
}
void CodeEdit::addMoveLineShortCut() {
void CodeEdit::addEditorBasicShortcut() {
auto find = new QShortcut(QKeySequence(QKeySequence::Find), this);
find->setContext(Qt::WidgetShortcut);
connect(find, &QShortcut::activated, this,
[this]() { showSearchReplaceBar(true, false); });
auto replace = new QShortcut(QKeySequence(QKeySequence::Replace), this);
replace->setContext(Qt::WidgetShortcut);
connect(replace, &QShortcut::activated, this,
[this]() { showSearchReplaceBar(true, true); });
auto Goto = new QShortcut(
QKeySequences::instance().keySequence(QKeySequences::Key::GOTO), this);
connect(Goto, &QShortcut::activated, this, [this]() { showGotoBar(true); });
}
void CodeEdit::addMoveLineShortcut() {
auto upLines = new QShortcut(
QKeySequence(Qt::ControlModifier | Qt::AltModifier | Qt::Key_Up), this);
upLines->setContext(Qt::WidgetShortcut);
@ -123,15 +172,55 @@ void CodeEdit::applyEditorSetStyle() {
this->setAutoCloseChar(set.editorAutoCloseChar());
}
SearchReplaceWidget *CodeEdit::searchWidget() const { return m_searchWidget; }
void CodeEdit::resizeEvent(QResizeEvent *event) {
if (event)
WingCodeEdit::resizeEvent(event);
// Move the search widget to the upper-right corner
const QPoint editorPos = this->pos();
QSize searchSize = m_searchWidget->sizeHint();
m_searchWidget->resize(searchSize);
m_searchWidget->move(editorPos.x() + this->viewport()->width() -
searchSize.width() - 16,
editorPos.y());
auto margins = this->contentsMargins();
auto off =
this->width() - margins.right() - this->verticalScrollBar()->width();
auto top = margins.top();
auto size = m_searchWidget->sizeHint();
m_searchWidget->resize(size);
m_searchWidget->move(off - size.width(), top);
size = m_gotoWidget->sizeHint();
m_gotoWidget->resize(size);
m_gotoWidget->move(off - size.width(), top);
}
void CodeEdit::keyPressEvent(QKeyEvent *event) {
bool unHandled = true;
switch (event->key()) {
case Qt::Key_Escape:
if (m_searchWidget->isVisible()) {
m_searchWidget->cancel();
unHandled = false;
}
if (m_gotoWidget->isVisible()) {
m_gotoWidget->cancel();
unHandled = false;
}
break;
case Qt::Key_Return:
case Qt::Key_Enter:
if (m_searchWidget->isVisible()) {
m_searchWidget->accept();
unHandled = false;
}
if (m_gotoWidget->isVisible()) {
m_gotoWidget->accept();
unHandled = false;
}
break;
default:
break;
}
if (unHandled) {
WingCodeEdit::keyPressEvent(event);
}
}

View File

@ -19,7 +19,9 @@
#define CODEEDIT_H
#include "WingCodeEdit/wingcodeedit.h"
#include "dialog/searchdialog.h"
#include "control/gotolinewidget.h"
class SearchReplaceWidget;
class CodeEdit : public WingCodeEdit {
Q_OBJECT
@ -28,7 +30,10 @@ public:
explicit CodeEdit(QWidget *parent = nullptr);
public:
void showSearchBar(bool show);
void showSearchReplaceBar(bool show, bool replace);
void showGotoBar(bool show);
SearchReplaceWidget *searchWidget() const;
signals:
void contentModified(bool b);
@ -37,17 +42,20 @@ protected slots:
virtual void onCompletion(const QModelIndex &index) override;
private:
void addMoveLineShortCut();
void addEditorBasicShortcut();
void addMoveLineShortcut();
// QWidget interface
protected:
virtual void resizeEvent(QResizeEvent *event) override;
virtual void keyPressEvent(QKeyEvent *event) override;
private slots:
void applyEditorSetStyle();
private:
SearchWidget *m_searchWidget;
SearchReplaceWidget *m_searchWidget;
GotoLineWidget *m_gotoWidget;
};
#endif // CODEEDIT_H

View File

@ -0,0 +1,63 @@
#include "codeeditcontrolwidget.h"
#include <QKeyEvent>
#include <QPainter>
CodeEditControlWidget::CodeEditControlWidget(QWidget *parent)
: QWidget{parent} {}
QSize CodeEditControlWidget::sizeHint() const {
// Make the default just a bit wider
const QSize hint = QWidget::sizeHint();
return {(hint.width() * 5) / 4, hint.height()};
}
void CodeEditControlWidget::accept() {
if (m_defaultButton) {
m_defaultButton->animateClick();
}
}
void CodeEditControlWidget::cancel() { close(); }
void CodeEditControlWidget::paintEvent(QPaintEvent *event) {
QPainter painter(this);
// Round the corners of this widget. Not strictly necessary, but
// it looks nicer...
const int arc = 4;
const int circ = 8;
const int h = height() - 1;
const int w = width() - 1;
painter.setPen(palette().color(QPalette::Mid));
const QColor windowColor = palette().color(QPalette::Window);
painter.setBrush(windowColor);
painter.drawEllipse(0, h - circ, circ, circ);
painter.drawEllipse(w - circ, h - circ, circ, circ);
painter.fillRect(0, 0, arc, h - arc, windowColor);
painter.fillRect(w - arc, 0, arc, h - arc, windowColor);
painter.fillRect(arc, h - arc, w - circ, arc, windowColor);
painter.fillRect(arc, 0, w - circ, h - arc, windowColor);
painter.drawLine(0, 0, 0, h - arc);
painter.drawLine(arc, h, w - arc, h);
painter.drawLine(w, 0, w, h - arc);
}
void CodeEditControlWidget::keyPressEvent(QKeyEvent *event) {
if (event->key() == Qt::Key_Escape) {
cancel();
} else if (event->key() == Qt::Key_Enter) {
accept();
} else {
QWidget::keyPressEvent(event);
}
}
QAbstractButton *CodeEditControlWidget::defaultButton() const {
return m_defaultButton;
}
void CodeEditControlWidget::setDefaultButton(
QAbstractButton *newDefaultButton) {
m_defaultButton = newDefaultButton;
}

View File

@ -0,0 +1,28 @@
#ifndef CODEEDITCONTROLWIDGET_H
#define CODEEDITCONTROLWIDGET_H
#include <QAbstractButton>
#include <QWidget>
class CodeEditControlWidget : public QWidget {
Q_OBJECT
public:
explicit CodeEditControlWidget(QWidget *parent = nullptr);
public:
virtual QSize sizeHint() const override;
virtual void accept();
virtual void cancel();
QAbstractButton *defaultButton() const;
void setDefaultButton(QAbstractButton *newDefaultButton);
protected:
virtual void paintEvent(QPaintEvent *event) override;
virtual void keyPressEvent(QKeyEvent *event) override;
private:
QAbstractButton *m_defaultButton = nullptr;
};
#endif // CODEEDITCONTROLWIDGET_H

View File

@ -0,0 +1,74 @@
/*==============================================================================
** Copyright (C) 2024-2027 WingSummer
**
** This program is free software: you can redistribute it and/or modify it under
** the terms of the GNU Affero General Public License as published by the Free
** Software Foundation, version 3.
**
** This program is distributed in the hope that it will be useful, but WITHOUT
** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
** FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
** details.
**
** You should have received a copy of the GNU Affero General Public License
** along with this program. If not, see <https://www.gnu.org/licenses/>.
** =============================================================================
*/
#include "gotolinewidget.h"
#include "class/skinmanager.h"
#include <QHBoxLayout>
#include <QLabel>
#include <QToolButton>
GotoLineWidget::GotoLineWidget(QWidget *parent)
: CodeEditControlWidget(parent) {
auto &skin = SkinManager::instance();
auto closeBtn = new QToolButton(this);
closeBtn->setIcon(skin.themeIcon(QStringLiteral("dialog_close")));
closeBtn->setToolTip(tr("Close"));
closeBtn->setIconSize(QSize(12, 12));
closeBtn->setStyleSheet(QStringLiteral("border:none;"));
connect(closeBtn, &QToolButton::clicked, this, &GotoLineWidget::close);
m_sbline = new QSpinBox(this);
m_sbline->setRange(1, 1);
m_sbline->setMinimumWidth(120);
connect(m_sbline, &QSpinBox::valueChanged, this,
&GotoLineWidget::onGotoLine);
auto tbGoto = new QToolButton(this);
tbGoto->setAutoRaise(true);
tbGoto->setIcon(skin.themeIcon(QStringLiteral("right_arrow")));
connect(tbGoto, &QToolButton::clicked, this, [this]() {
emit onGotoLine(m_sbline->value());
close();
});
auto layout = new QHBoxLayout(this);
layout->setContentsMargins(5, 5, 5, 5);
layout->setSpacing(5);
layout->addWidget(closeBtn);
layout->addWidget(m_sbline, 1);
layout->addWidget(tbGoto);
setLayout(layout);
setDefaultButton(tbGoto);
}
void GotoLineWidget::setTotalLines(int lineTotal) {
m_sbline->setMaximum(lineTotal);
}
void GotoLineWidget::setOriginLine(int line) {
m_sbline->setValue(line);
m_line = m_sbline->value();
}
void GotoLineWidget::cancel() {
emit onGotoLine(m_line);
CodeEditControlWidget::cancel();
}

View File

@ -0,0 +1,47 @@
/*==============================================================================
** Copyright (C) 2024-2027 WingSummer
**
** This program is free software: you can redistribute it and/or modify it under
** the terms of the GNU Affero General Public License as published by the Free
** Software Foundation, version 3.
**
** This program is distributed in the hope that it will be useful, but WITHOUT
** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
** FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
** details.
**
** You should have received a copy of the GNU Affero General Public License
** along with this program. If not, see <https://www.gnu.org/licenses/>.
** =============================================================================
*/
#ifndef GOTOLINEWIDGET_H
#define GOTOLINEWIDGET_H
#include "control/codeeditcontrolwidget.h"
#include <QSpinBox>
class GotoLineWidget : public CodeEditControlWidget {
Q_OBJECT
public:
explicit GotoLineWidget(QWidget *parent = nullptr);
void setTotalLines(int lineTotal);
signals:
void onGotoLine(int line);
public:
void setOriginLine(int line);
// CodeEditControlWidget interface
public:
virtual void cancel() override;
private:
QSpinBox *m_sbline;
int m_line = 1;
};
#endif // GOTOLINEWIDGET_H

View File

@ -131,6 +131,12 @@ bool ScriptEditor::save(const QString &path) {
bool ScriptEditor::reload() { return openFile(m_fileName); }
void ScriptEditor::find() { m_editor->showSearchReplaceBar(true, false); }
void ScriptEditor::replace() { m_editor->showSearchReplaceBar(true, true); }
void ScriptEditor::gotoLine() { m_editor->showGotoBar(true); }
void ScriptEditor::setReadOnly(bool b) {
m_editor->setReadOnly(b);
this->tabWidget()->setIcon(b ? ICONRES("lockon") : QIcon());

View File

@ -41,12 +41,15 @@ signals:
void onFunctionTip(const QString &tip);
public slots:
void setReadOnly(bool b);
bool openFile(const QString &filename);
bool save(const QString &path = QString());
bool reload();
void setReadOnly(bool b);
void find();
void replace();
void gotoLine();
private:
void processTitle();

View File

@ -15,9 +15,9 @@
** =============================================================================
*/
#include "searchdialog.h"
#include "utilities.h"
#include "searchreplacewidget.h"
#include "class/skinmanager.h"
#include "class/wingmessagebox.h"
#include <QCheckBox>
@ -40,288 +40,6 @@
#include <QStyleHints>
#endif
static SearchDialog *s_instance = Q_NULLPTR;
SearchWidget::SearchWidget(WingCodeEdit *editor, QWidget *parent)
: QWidget(parent), m_editor(editor) {
auto tbMenu = new QToolButton(this);
tbMenu->setAutoRaise(true);
tbMenu->setIcon(QIcon::fromTheme(QStringLiteral("theme")));
tbMenu->setToolTip(tr("Search Settings"));
tbMenu->setStyleSheet(
QStringLiteral("QToolButton::menu-indicator { image: none; }"));
auto settingsMenu = new QMenu(this);
m_caseSensitive = settingsMenu->addAction(tr("Match ca&se"));
m_caseSensitive->setCheckable(true);
m_wholeWord = settingsMenu->addAction(tr("Match &whole words"));
m_wholeWord->setCheckable(true);
m_regex = settingsMenu->addAction(tr("Regular e&xpressions"));
m_regex->setCheckable(true);
m_escapes = settingsMenu->addAction(tr("&Escape sequences"));
m_escapes->setCheckable(true);
m_wrapSearch = settingsMenu->addAction(tr("Wrap Aro&und"));
m_wrapSearch->setCheckable(true);
tbMenu->setMenu(settingsMenu);
tbMenu->setPopupMode(QToolButton::InstantPopup);
connect(m_caseSensitive, &QAction::triggered, this,
&SearchWidget::updateSettings);
connect(m_wholeWord, &QAction::triggered, this,
&SearchWidget::updateSettings);
connect(m_regex, &QAction::triggered, this, &SearchWidget::updateSettings);
connect(m_escapes, &QAction::triggered, this,
&SearchWidget::updateSettings);
connect(m_wrapSearch, &QAction::triggered, this,
&SearchWidget::updateSettings);
m_searchText = new QLineEdit(this);
m_searchText->setClearButtonEnabled(true);
setFocusProxy(m_searchText);
auto tbNext = new QToolButton(this);
tbNext->setAutoRaise(true);
tbNext->setIcon(ICONRES(""));
tbNext->setToolTip(tr("Find Next"));
auto tbPrev = new QToolButton(this);
tbPrev->setAutoRaise(true);
tbPrev->setIcon(ICONRES(""));
tbPrev->setToolTip(tr("Find Previous"));
auto layout = new QHBoxLayout(this);
layout->setContentsMargins(5, 5, 5, 5);
layout->setSpacing(5);
layout->addWidget(tbMenu);
layout->addWidget(m_searchText);
layout->addWidget(tbNext);
layout->addWidget(tbPrev);
setLayout(layout);
connect(m_searchText, &QLineEdit::textChanged, this,
[this](const QString &text) {
m_searchParams.searchText =
m_escapes->isChecked()
? SearchDialog::translateEscapes(text)
: text;
m_editor->setLiveSearch(m_searchParams);
});
connect(m_searchText, &QLineEdit::returnPressed, this,
[this] { searchNext(false); });
connect(tbNext, &QToolButton::clicked, this, [this] { searchNext(false); });
connect(tbPrev, &QToolButton::clicked, this, [this] { searchNext(true); });
}
void SearchWidget::setSearchText(const QString &text) {
m_searchText->setText(text);
}
void SearchWidget::activate() {
m_searchParams.caseSensitive = m_caseSensitive->isChecked();
m_searchParams.wholeWord = m_wholeWord->isChecked();
m_searchParams.regex = m_regex->isChecked();
setFocus(Qt::OtherFocusReason);
m_searchText->selectAll();
m_editor->setLiveSearch(m_searchParams);
}
void SearchWidget::searchNext(bool reverse) {
if (!isVisible()) {
setVisible(true);
setEnabled(true);
activate();
}
if (m_searchParams.searchText.isEmpty())
return;
auto searchCursor = m_editor->textSearch(m_editor->textCursor(),
m_searchParams, false, reverse);
if (searchCursor.isNull() && m_wrapSearch->isChecked()) {
QTextCursor wrapCursor = m_editor->textCursor();
wrapCursor.movePosition(reverse ? QTextCursor::End
: QTextCursor::Start);
searchCursor =
m_editor->textSearch(wrapCursor, m_searchParams, true, reverse);
}
if (searchCursor.isNull())
QMessageBox::information(this, QString(),
tr("The specified text was not found"));
else
m_editor->setTextCursor(searchCursor);
}
void SearchWidget::paintEvent(QPaintEvent *) {
QPainter painter(this);
// Round the corners of this widget. Not strictly necessary, but
// it looks nicer...
const int arc = 4;
const int circ = 8;
const int h = height() - 1;
const int w = width() - 1;
painter.setPen(palette().color(QPalette::Mid));
const QColor windowColor = palette().color(QPalette::Window);
painter.setBrush(windowColor);
painter.drawEllipse(0, h - circ, circ, circ);
painter.drawEllipse(w - circ, h - circ, circ, circ);
painter.fillRect(0, 0, arc, h - arc, windowColor);
painter.fillRect(w - arc, 0, arc, h - arc, windowColor);
painter.fillRect(arc, h - arc, w - circ, arc, windowColor);
painter.fillRect(arc, 0, w - circ, h - arc, windowColor);
painter.drawLine(0, 0, 0, h - arc);
painter.drawLine(arc, h, w - arc, h);
painter.drawLine(w, 0, w, h - arc);
}
void SearchWidget::updateSettings() {
m_searchParams.caseSensitive = m_caseSensitive->isChecked();
m_searchParams.wholeWord = m_wholeWord->isChecked();
m_searchParams.regex = m_regex->isChecked();
m_editor->setLiveSearch(m_searchParams);
}
/* Just sets some more sane defaults for QComboBox:
* - Don't auto-insert items (we handle that manually)
* - Disable the completer, since it insists on changing the typed
* item to match another item in the list that differs only in case.
*/
class SearchComboBox : public QComboBox {
public:
explicit SearchComboBox(QWidget *parent) : QComboBox(parent) {
setEditable(true);
setInsertPolicy(QComboBox::NoInsert);
setDuplicatesEnabled(true);
setCompleter(Q_NULLPTR);
}
};
SearchDialog::SearchDialog(QWidget *parent)
: FramelessDialogBase(parent), m_editor() {
s_instance = this;
setAttribute(Qt::WA_DeleteOnClose);
setWindowTitle(tr("Find and Replace..."));
setWindowIcon(ICONRES("edit-find-replace"));
m_searchText = new SearchComboBox(this);
m_replaceText = new SearchComboBox(this);
m_caseSensitive = new QCheckBox(tr("Match ca&se"), this);
m_caseSensitive->setChecked(false);
m_wholeWord = new QCheckBox(tr("Match &whole words"), this);
m_wholeWord->setChecked(false);
m_regex = new QCheckBox(tr("Regular e&xpressions"), this);
m_regex->setChecked(false);
m_escapes = new QCheckBox(tr("&Escape sequences"), this);
m_escapes->setChecked(false);
m_wrapSearch = new QCheckBox(tr("Wrap Aro&und"), this);
m_wrapSearch->setChecked(false);
// QDialogButtonBox insists on rearranging buttons depending on your
// platform, which would be fine if we only had standard actions, but most
// of our action buttons here are custom.
auto buttonBox = new QWidget(this);
buttonBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::MinimumExpanding);
auto buttonLayout = new QVBoxLayout(buttonBox);
buttonLayout->setContentsMargins(0, 0, 0, 0);
buttonLayout->setSpacing(5);
auto findNext = new QPushButton(tr("Find &Next"), this);
buttonLayout->addWidget(findNext);
auto findPrev = new QPushButton(tr("Find &Previous"), this);
buttonLayout->addWidget(findPrev);
auto replaceNext = new QPushButton(tr("&Replace"), this);
buttonLayout->addWidget(replaceNext);
auto replaceAll = new QPushButton(tr("Replace &All"), this);
buttonLayout->addWidget(replaceAll);
m_replaceSelectionButton = new QPushButton(tr("&In Selection"), this);
buttonLayout->addWidget(m_replaceSelectionButton);
buttonLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum,
QSizePolicy::MinimumExpanding));
auto closeButton = new QPushButton(tr("&Close"), this);
buttonLayout->addWidget(closeButton);
connect(findNext, &QPushButton::clicked, this,
&SearchDialog::searchForward);
connect(findPrev, &QPushButton::clicked, this,
&SearchDialog::searchBackward);
connect(replaceNext, &QPushButton::clicked, this,
&SearchDialog::replaceCurrent);
connect(replaceAll, &QPushButton::clicked, this,
[this] { performReplaceAll(WholeDocument); });
connect(m_replaceSelectionButton, &QPushButton::clicked, this,
[this] { performReplaceAll(InSelection); });
connect(closeButton, &QPushButton::clicked, this, &QDialog::close);
auto w = new QWidget(this);
auto layout = new QGridLayout(w);
layout->setContentsMargins(10, 10, 10, 10);
layout->setVerticalSpacing(5);
layout->setHorizontalSpacing(10);
auto searchLabel = new QLabel(tr("&Find:"), this);
searchLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
searchLabel->setBuddy(m_searchText);
layout->addWidget(searchLabel, 0, 0);
layout->addWidget(m_searchText, 0, 1, 1, 2);
auto replaceLabel = new QLabel(tr("Replace wit&h:"), this);
replaceLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
replaceLabel->setBuddy(m_replaceText);
layout->addWidget(replaceLabel, 1, 0);
layout->addWidget(m_replaceText, 1, 1, 1, 2);
layout->addItem(new QSpacerItem(0, 10, QSizePolicy::MinimumExpanding,
QSizePolicy::Fixed),
2, 0, 1, 3);
layout->addWidget(m_caseSensitive, 3, 1);
layout->addWidget(m_wholeWord, 4, 1);
layout->addWidget(m_regex, 5, 1);
layout->addWidget(m_escapes, 6, 1);
layout->addWidget(m_wrapSearch, 3, 2);
layout->addItem(
new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding),
layout->rowCount(), 0, 1, 3);
layout->addWidget(buttonBox, 0, layout->columnCount(), layout->rowCount(),
1);
buildUpContent(w);
}
SearchDialog::~SearchDialog() {
syncSearchSettings(false);
s_instance = Q_NULLPTR;
}
SearchDialog *SearchDialog::create(WingCodeEdit *editor, QWidget *parent) {
if (s_instance) {
s_instance->raise();
} else {
Q_ASSERT(parent);
s_instance = new SearchDialog(parent);
s_instance->show();
s_instance->raise();
s_instance->activateWindow();
s_instance->m_editor = editor;
connect(s_instance->m_editor, &WingCodeEdit::selectionChanged,
s_instance, [] {
bool hasSelection =
s_instance->m_editor->textCursor().hasSelection();
s_instance->m_replaceSelectionButton->setEnabled(
hasSelection);
});
}
const QTextCursor cursor = s_instance->m_editor->textCursor();
if (cursor.hasSelection()) {
s_instance->m_searchText->setCurrentText(cursor.selectedText());
s_instance->m_searchText->lineEdit()->selectAll();
}
s_instance->m_replaceSelectionButton->setEnabled(cursor.hasSelection());
return s_instance;
}
static QString translateCharEscape(QStringView digits, int *advance) {
Q_ASSERT(digits.size() > 0);
Q_ASSERT(advance);
@ -374,7 +92,7 @@ static QString translateCharEscape(QStringView digits, int *advance) {
}
}
QString SearchDialog::translateEscapes(const QString &text) {
QString translateEscapes(const QString &text) {
QString result;
result.reserve(text.size());
int start = 0;
@ -445,8 +163,287 @@ QString SearchDialog::translateEscapes(const QString &text) {
return result;
}
QString SearchDialog::regexReplace(const QString &text,
const QRegularExpressionMatch &regexMatch) {
SearchReplaceWidget::SearchReplaceWidget(CodeEdit *editor)
: CodeEditControlWidget(editor), m_editor(editor) {
auto &skin = SkinManager::instance();
auto closeBtn = new QToolButton(this);
closeBtn->setIcon(skin.themeIcon(QStringLiteral("dialog_close")));
closeBtn->setToolTip(tr("Close"));
closeBtn->setIconSize(QSize(12, 12));
closeBtn->setStyleSheet(QStringLiteral("border:none;"));
connect(closeBtn, &QToolButton::clicked, this,
&SearchReplaceWidget::cancel);
auto tbMenu = new QToolButton(this);
tbMenu->setAutoRaise(true);
tbMenu->setIcon(skin.themeIcon(QStringLiteral("menu")));
tbMenu->setToolTip(tr("Search Settings"));
tbMenu->setStyleSheet(
QStringLiteral("QToolButton::menu-indicator { image: none; }"));
auto settingsMenu = new QMenu(this);
m_caseSensitive = settingsMenu->addAction(tr("Match ca&se"));
m_caseSensitive->setCheckable(true);
m_wholeWord = settingsMenu->addAction(tr("Match &whole words"));
m_wholeWord->setCheckable(true);
m_regex = settingsMenu->addAction(tr("Regular e&xpressions"));
m_regex->setCheckable(true);
m_escapes = settingsMenu->addAction(tr("&Escape sequences"));
m_escapes->setCheckable(true);
m_wrapSearch = settingsMenu->addAction(tr("Wrap Aro&und"));
m_wrapSearch->setCheckable(true);
m_wrapSearch->setChecked(true);
m_replaceSel = settingsMenu->addAction(tr("Replace in selection"));
m_replaceSel->setCheckable(true);
tbMenu->setMenu(settingsMenu);
tbMenu->setPopupMode(QToolButton::InstantPopup);
connect(m_caseSensitive, &QAction::triggered, this,
&SearchReplaceWidget::updateSettings);
connect(m_wholeWord, &QAction::triggered, this,
&SearchReplaceWidget::updateSettings);
connect(m_regex, &QAction::triggered, this,
&SearchReplaceWidget::updateSettings);
connect(m_escapes, &QAction::triggered, this,
&SearchReplaceWidget::updateSettings);
connect(m_wrapSearch, &QAction::triggered, this,
&SearchReplaceWidget::updateSettings);
m_searchText = new QLineEdit(this);
m_searchText->setClearButtonEnabled(true);
setFocusProxy(m_searchText);
auto tbNext = new QToolButton(this);
tbNext->setAutoRaise(true);
tbNext->setIcon(skin.themeIcon(QStringLiteral("right_arrow")));
tbNext->setToolTip(tr("Find Next"));
auto tbPrev = new QToolButton(this);
tbPrev->setAutoRaise(true);
tbPrev->setIcon(skin.themeIcon(QStringLiteral("left_arrow")));
tbPrev->setToolTip(tr("Find Previous"));
auto mlayout = new QVBoxLayout(this);
mlayout->setContentsMargins(5, 5, 5, 5);
mlayout->setSpacing(3);
m_replaceText = new QLineEdit(this);
m_replaceText->setClearButtonEnabled(true);
auto tbReplaceCur = new QToolButton(this);
tbReplaceCur->setAutoRaise(true);
tbReplaceCur->setIcon(skin.themeIcon(QStringLiteral("dialog_apply")));
tbReplaceCur->setToolTip(tr("Replace Current"));
auto tbReplaceAll = new QToolButton(this);
tbReplaceAll->setAutoRaise(true);
tbReplaceAll->setIcon(skin.themeIcon(QStringLiteral("dialog_yes_to_all")));
tbReplaceAll->setToolTip(tr("Replace All"));
auto layout = new QHBoxLayout;
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(5);
layout->addWidget(closeBtn);
layout->addWidget(tbMenu);
layout->addWidget(m_searchText);
layout->addWidget(tbPrev);
layout->addWidget(tbNext);
mlayout->addLayout(layout);
m_replaceWidgets = new QWidget(this);
layout = new QHBoxLayout(m_replaceWidgets);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(5);
layout->addWidget(new QLabel(tr("Replace:"), this));
layout->addWidget(m_replaceText);
layout->addWidget(tbReplaceCur);
layout->addWidget(tbReplaceAll);
mlayout->addWidget(m_replaceWidgets);
setLayout(mlayout);
connect(m_searchText, &QLineEdit::textChanged, this,
[this](const QString &text) {
m_searchParams.searchText =
m_escapes->isChecked() ? translateEscapes(text) : text;
m_editor->setLiveSearch(m_searchParams);
});
connect(m_searchText, &QLineEdit::returnPressed, this,
&SearchReplaceWidget::searchForward);
connect(tbNext, &QToolButton::clicked, this,
&SearchReplaceWidget::searchForward);
connect(tbPrev, &QToolButton::clicked, this,
&SearchReplaceWidget::searchBackward);
connect(tbReplaceCur, &QToolButton::clicked, this,
&SearchReplaceWidget::replaceCurrent);
connect(tbReplaceAll, &QToolButton::clicked, this, [this]() {
});
}
void SearchReplaceWidget::setSearchText(const QString &text) {
m_searchText->setText(text);
}
void SearchReplaceWidget::setReplaceMode(bool b) {
m_replaceWidgets->setVisible(b);
m_replaceWidgets->setEnabled(b);
m_replaceSel->setVisible(b);
m_replaceSel->setEnabled(b);
}
void SearchReplaceWidget::activate() {
resize(sizeHint()); // readjust size (height)
m_searchParams.caseSensitive = m_caseSensitive->isChecked();
m_searchParams.wholeWord = m_wholeWord->isChecked();
m_searchParams.regex = m_regex->isChecked();
setFocus(Qt::OtherFocusReason);
m_searchText->selectAll();
m_editor->setLiveSearch(m_searchParams);
}
void SearchReplaceWidget::cancel() {
m_editor->clearLiveSearch();
CodeEditControlWidget::cancel();
}
void SearchReplaceWidget::searchForward() {
updateSettings();
m_replaceCursor = searchNext(false);
}
void SearchReplaceWidget::searchBackward() {
updateSettings();
m_replaceCursor = searchNext(true);
}
void SearchReplaceWidget::replaceCurrent() {
updateSettings();
Q_ASSERT(m_editor);
const QString searchText = m_searchText->text();
if (searchText.isEmpty())
return;
if (m_replaceCursor.isNull() || m_replaceCursor != m_editor->textCursor()) {
m_replaceCursor = searchNext(false);
return;
}
QString replaceText = m_replaceText->text();
if (m_escapes->isChecked())
replaceText = translateEscapes(replaceText);
m_replaceCursor.beginEditBlock();
m_replaceCursor.removeSelectedText();
if (m_regex->isChecked())
m_replaceCursor.insertText(regexReplace(replaceText, m_regexMatch));
else
m_replaceCursor.insertText(replaceText);
m_replaceCursor.endEditBlock();
m_replaceCursor = searchNext(false);
}
void SearchReplaceWidget::performReplaceAll(ReplaceAllMode mode) {
updateSettings();
Q_ASSERT(m_editor);
const QString searchText = m_searchText->text();
if (searchText.isEmpty())
return;
auto searchCursor = m_editor->textCursor();
if (mode == InSelection)
searchCursor.setPosition(m_editor->textCursor().selectionStart());
else
searchCursor.movePosition(QTextCursor::Start);
searchCursor = m_editor->textSearch(searchCursor, m_searchParams, true,
false, &m_regexMatch);
if (searchCursor.isNull()) {
QMessageBox::information(this, QString(),
tr("The specified text was not found"));
return;
} else if (mode == InSelection &&
searchCursor.selectionEnd() >
m_editor->textCursor().selectionEnd()) {
WingMessageBox::information(
this, QString(),
tr("The specified text was not found in the selection"));
return;
}
QString replaceText = m_replaceText->text();
if (m_escapes->isChecked())
replaceText = translateEscapes(replaceText);
searchCursor.beginEditBlock();
auto replaceCursor = searchCursor;
int replacements = 0;
while (!replaceCursor.isNull()) {
if (mode == InSelection && replaceCursor.selectionEnd() >
m_editor->textCursor().selectionEnd())
break;
replaceCursor.removeSelectedText();
if (m_regex->isChecked())
replaceCursor.insertText(regexReplace(replaceText, m_regexMatch));
else
replaceCursor.insertText(replaceText);
replaceCursor = m_editor->textSearch(replaceCursor, m_searchParams,
false, false, &m_regexMatch);
++replacements;
}
searchCursor.endEditBlock();
WingMessageBox::information(
this, QString(),
tr("Successfully replaced %1 matches").arg(replacements));
}
QTextCursor SearchReplaceWidget::searchNext(bool reverse) {
if (!isVisible()) {
setVisible(true);
setEnabled(true);
activate();
}
Q_ASSERT(m_editor);
if (m_searchParams.searchText.isEmpty())
return QTextCursor();
auto searchCursor = m_editor->textSearch(
m_editor->textCursor(), m_searchParams, false, reverse, &m_regexMatch);
if (searchCursor.isNull() && m_wrapSearch->isChecked()) {
QTextCursor wrapCursor = m_editor->textCursor();
wrapCursor.movePosition(reverse ? QTextCursor::End
: QTextCursor::Start);
searchCursor = m_editor->textSearch(wrapCursor, m_searchParams, true,
reverse, &m_regexMatch);
}
if (searchCursor.isNull())
WingMessageBox::information(this, QString(),
tr("The specified text was not found"));
else
m_editor->setTextCursor(searchCursor);
return searchCursor;
}
void SearchReplaceWidget::updateSettings() {
m_searchParams.caseSensitive = m_caseSensitive->isChecked();
m_searchParams.wholeWord = m_wholeWord->isChecked();
m_searchParams.regex = m_regex->isChecked();
m_editor->setLiveSearch(m_searchParams);
}
QString
SearchReplaceWidget::regexReplace(const QString &text,
const QRegularExpressionMatch &regexMatch) {
QString result;
result.reserve(text.size());
int start = 0;
@ -474,145 +471,3 @@ QString SearchDialog::regexReplace(const QString &text,
result.append(text.sliced(start));
return result;
}
void SearchDialog::syncSearchSettings(bool saveRecent) {
const QString searchText = m_searchText->currentText();
if (!searchText.isEmpty() && saveRecent) {
if (m_searchText->count() == 0 ||
m_searchText->itemText(0) != searchText) {
m_searchText->insertItem(0, searchText);
}
}
m_searchParams.searchText =
m_escapes->isChecked() ? translateEscapes(searchText) : searchText;
const QString replaceText = m_replaceText->currentText();
if (!replaceText.isEmpty() && saveRecent) {
if (m_replaceText->count() == 0 ||
m_replaceText->itemText(0) != replaceText) {
m_replaceText->insertItem(0, replaceText);
}
}
m_searchParams.caseSensitive = m_caseSensitive->isChecked();
m_searchParams.wholeWord = m_wholeWord->isChecked();
m_searchParams.regex = m_regex->isChecked();
}
QTextCursor SearchDialog::searchNext(bool reverse) {
Q_ASSERT(m_editor);
if (m_searchParams.searchText.isEmpty())
return QTextCursor();
auto searchCursor = m_editor->textSearch(
m_editor->textCursor(), m_searchParams, false, reverse, &m_regexMatch);
if (searchCursor.isNull() && m_wrapSearch->isChecked()) {
QTextCursor wrapCursor = m_editor->textCursor();
wrapCursor.movePosition(reverse ? QTextCursor::End
: QTextCursor::Start);
searchCursor = m_editor->textSearch(wrapCursor, m_searchParams, true,
reverse, &m_regexMatch);
}
if (searchCursor.isNull())
WingMessageBox::information(this, QString(),
tr("The specified text was not found"));
else
m_editor->setTextCursor(searchCursor);
return searchCursor;
}
void SearchDialog::searchForward() {
syncSearchSettings(true);
m_replaceCursor = searchNext(false);
}
void SearchDialog::searchBackward() {
syncSearchSettings(true);
m_replaceCursor = searchNext(true);
}
void SearchDialog::replaceCurrent() {
syncSearchSettings(true);
Q_ASSERT(m_editor);
const QString searchText = m_searchText->currentText();
if (searchText.isEmpty())
return;
if (m_replaceCursor.isNull() || m_replaceCursor != m_editor->textCursor()) {
m_replaceCursor = searchNext(false);
return;
}
QString replaceText = m_replaceText->currentText();
if (m_escapes->isChecked())
replaceText = translateEscapes(replaceText);
m_replaceCursor.beginEditBlock();
m_replaceCursor.removeSelectedText();
if (m_regex->isChecked())
m_replaceCursor.insertText(regexReplace(replaceText, m_regexMatch));
else
m_replaceCursor.insertText(replaceText);
m_replaceCursor.endEditBlock();
m_replaceCursor = searchNext(false);
}
void SearchDialog::performReplaceAll(ReplaceAllMode mode) {
syncSearchSettings(true);
Q_ASSERT(m_editor);
const QString searchText = m_searchText->currentText();
if (searchText.isEmpty())
return;
auto searchCursor = m_editor->textCursor();
if (mode == InSelection)
searchCursor.setPosition(m_editor->textCursor().selectionStart());
else
searchCursor.movePosition(QTextCursor::Start);
searchCursor = m_editor->textSearch(searchCursor, m_searchParams, true,
false, &m_regexMatch);
if (searchCursor.isNull()) {
QMessageBox::information(this, QString(),
tr("The specified text was not found"));
return;
} else if (mode == InSelection &&
searchCursor.selectionEnd() >
m_editor->textCursor().selectionEnd()) {
WingMessageBox::information(
this, QString(),
tr("The specified text was not found in the selection"));
return;
}
QString replaceText = m_replaceText->currentText();
if (m_escapes->isChecked())
replaceText = translateEscapes(replaceText);
searchCursor.beginEditBlock();
auto replaceCursor = searchCursor;
int replacements = 0;
while (!replaceCursor.isNull()) {
if (mode == InSelection && replaceCursor.selectionEnd() >
m_editor->textCursor().selectionEnd())
break;
replaceCursor.removeSelectedText();
if (m_regex->isChecked())
replaceCursor.insertText(regexReplace(replaceText, m_regexMatch));
else
replaceCursor.insertText(replaceText);
replaceCursor = m_editor->textSearch(replaceCursor, m_searchParams,
false, false, &m_regexMatch);
++replacements;
}
searchCursor.endEditBlock();
WingMessageBox::information(
this, QString(),
tr("Successfully replaced %1 matches").arg(replacements));
}

View File

@ -22,8 +22,8 @@
#include <QRegularExpressionMatch>
#include <QTextCursor>
#include "dialog/framelessdialogbase.h"
#include "wingcodeedit.h"
#include "codeeditcontrolwidget.h"
#include "control/codeedit.h"
class QLineEdit;
class QComboBox;
@ -31,82 +31,51 @@ class QCheckBox;
class QPushButton;
class QTextCursor;
class SearchWidget : public QWidget {
class SearchReplaceWidget : public CodeEditControlWidget {
Q_OBJECT
public:
explicit SearchWidget(WingCodeEdit *editor, QWidget *parent);
QSize sizeHint() const override {
// Make the default just a bit wider
const QSize parentHint = QWidget::sizeHint();
return {(parentHint.width() * 5) / 4, parentHint.height()};
}
explicit SearchReplaceWidget(CodeEdit *editor);
public:
void setSearchText(const QString &text);
void setReplaceMode(bool b);
void activate();
public Q_SLOTS:
void searchNext(bool reverse);
virtual void cancel() override;
protected:
void paintEvent(QPaintEvent *event) override;
private Q_SLOTS:
private:
void updateSettings();
QTextCursor searchNext(bool reverse);
QString regexReplace(const QString &text,
const QRegularExpressionMatch &regexMatch);
enum ReplaceAllMode { WholeDocument, InSelection };
void replaceCurrent();
void performReplaceAll(ReplaceAllMode mode);
private slots:
void searchForward();
void searchBackward();
private:
QLineEdit *m_searchText;
QLineEdit *m_replaceText;
QAction *m_caseSensitive;
QAction *m_wholeWord;
QAction *m_regex;
QAction *m_escapes;
QAction *m_wrapSearch;
QAction *m_replaceSel;
WingCodeEdit *m_editor;
WingCodeEdit::SearchParams m_searchParams;
};
CodeEdit *m_editor;
CodeEdit::SearchParams m_searchParams;
class SearchDialog : public FramelessDialogBase {
Q_OBJECT
public:
static SearchDialog *create(WingCodeEdit *editor, QWidget *parent);
virtual ~SearchDialog();
static QString translateEscapes(const QString &text);
static QString regexReplace(const QString &text,
const QRegularExpressionMatch &regexMatch);
public Q_SLOTS:
QTextCursor searchNext(bool reverse);
private Q_SLOTS:
void searchForward();
void searchBackward();
void replaceCurrent();
private:
explicit SearchDialog(QWidget *parent);
void syncSearchSettings(bool saveRecent);
enum ReplaceAllMode { WholeDocument, InSelection };
void performReplaceAll(ReplaceAllMode mode);
QComboBox *m_searchText;
QComboBox *m_replaceText;
QCheckBox *m_caseSensitive;
QCheckBox *m_wholeWord;
QCheckBox *m_regex;
QCheckBox *m_escapes;
QCheckBox *m_wrapSearch;
QPushButton *m_replaceSelectionButton;
QWidget *m_replaceWidgets;
QTextCursor m_replaceCursor;
WingCodeEdit *m_editor;
WingCodeEdit::SearchParams m_searchParams;
QRegularExpressionMatch m_regexMatch;
};

View File

@ -1250,21 +1250,21 @@ void ScriptingDialog::on_delete() {
void ScriptingDialog::on_findfile() {
auto e = currentEditor();
if (e) {
// e->editor()->find();
e->find();
}
}
void ScriptingDialog::on_replace() {
auto e = currentEditor();
if (e) {
// e->editor()->replace();
e->replace();
}
}
void ScriptingDialog::on_gotoline() {
auto e = currentEditor();
if (e) {
// e->editor()->gotoLine();
e->gotoLine();
}
}

View File

@ -55,7 +55,7 @@ protected:
}
};
ShowTextDialog::ShowTextDialog(QWidget *parent) : FramelessDialogBase(parent) {
ShowTextDialog::ShowTextDialog(QWidget *parent) : FramelessMainWindow(parent) {
this->setUpdatesEnabled(false);
// build up UI
@ -175,12 +175,12 @@ void ShowTextDialog::on_copyfile() {
tr("CopyToClipBoard"));
}
void ShowTextDialog::on_findfile() { m_edit->showSearchBar(true); }
void ShowTextDialog::on_gotoline() {
// m_edit->editor()->gotoLine();
void ShowTextDialog::on_findfile() {
m_edit->showSearchReplaceBar(true, false);
}
void ShowTextDialog::on_gotoline() { m_edit->showGotoBar(true); }
void ShowTextDialog::on_encoding() {
EncodingDialog d;
if (d.exec()) {

View File

@ -26,14 +26,14 @@
#include "QWingRibbon/ribbontabcontent.h"
#include "control/codeedit.h"
#include "framelessdialogbase.h"
#include "dialog/framelessmainwindow.h"
#include "utilities.h"
#include <QShortcut>
#include <QStatusBar>
#include <QToolButton>
class ShowTextDialog : public FramelessDialogBase {
class ShowTextDialog : public FramelessMainWindow {
Q_OBJECT
public:
explicit ShowTextDialog(QWidget *parent = nullptr);