diff --git a/3rdparty/QConsoleWidget/QConsoleWidget.cpp b/3rdparty/QConsoleWidget/QConsoleWidget.cpp
index 8616eee..695d12e 100644
--- a/3rdparty/QConsoleWidget/QConsoleWidget.cpp
+++ b/3rdparty/QConsoleWidget/QConsoleWidget.cpp
@@ -1,3 +1,20 @@
+/*==============================================================================
+** 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 .
+** =============================================================================
+*/
+
#include "QConsoleWidget.h"
#include "QConsoleIODevice.h"
#include "wingsyntaxhighlighter.h"
diff --git a/3rdparty/QConsoleWidget/QConsoleWidget.h b/3rdparty/QConsoleWidget/QConsoleWidget.h
index 28ea7ef..82af016 100644
--- a/3rdparty/QConsoleWidget/QConsoleWidget.h
+++ b/3rdparty/QConsoleWidget/QConsoleWidget.h
@@ -1,3 +1,20 @@
+/*==============================================================================
+** 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 .
+** =============================================================================
+*/
+
#ifndef _QCONSOLEWIDGET_H_
#define _QCONSOLEWIDGET_H_
diff --git a/3rdparty/QConsoleWidget/commandhistorymanager.cpp b/3rdparty/QConsoleWidget/commandhistorymanager.cpp
index 13e4665..537b327 100644
--- a/3rdparty/QConsoleWidget/commandhistorymanager.cpp
+++ b/3rdparty/QConsoleWidget/commandhistorymanager.cpp
@@ -1,3 +1,20 @@
+/*==============================================================================
+** 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 .
+** =============================================================================
+*/
+
#include "commandhistorymanager.h"
#include "utilities.h"
diff --git a/3rdparty/QConsoleWidget/commandhistorymanager.h b/3rdparty/QConsoleWidget/commandhistorymanager.h
index 1800e0b..28a9d84 100644
--- a/3rdparty/QConsoleWidget/commandhistorymanager.h
+++ b/3rdparty/QConsoleWidget/commandhistorymanager.h
@@ -1,3 +1,20 @@
+/*==============================================================================
+** 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 .
+** =============================================================================
+*/
+
#ifndef COMMANDHISTORYMANAGER_H
#define COMMANDHISTORYMANAGER_H
diff --git a/3rdparty/QHexView/document/buffer/qhexbuffer.cpp b/3rdparty/QHexView/document/buffer/qhexbuffer.cpp
index 9780ef0..8b77eaf 100644
--- a/3rdparty/QHexView/document/buffer/qhexbuffer.cpp
+++ b/3rdparty/QHexView/document/buffer/qhexbuffer.cpp
@@ -24,7 +24,10 @@
QHexBuffer::QHexBuffer(QObject *parent) : QObject(parent) {}
-uchar QHexBuffer::at(qsizetype idx) { return uchar(this->read(idx, 1)[0]); }
+uchar QHexBuffer::at(qsizetype idx) {
+ auto data = this->read(idx, 1);
+ return uchar(data[0]);
+}
bool QHexBuffer::isEmpty() const { return this->length() <= 0; }
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 70a8910..d655ab6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -175,7 +175,11 @@ set(DIALOG_SRC
src/dialog/crashreport.cpp
src/dialog/crashreport.ui
src/dialog/showtextdialog.cpp
- src/dialog/showtextdialog.h)
+ src/dialog/showtextdialog.h
+ src/dialog/definitiondownload.cpp
+ src/dialog/definitiondownload.h
+ src/dialog/searchdialog.cpp
+ src/dialog/searchdialog.h)
set(CONTROL_SRC
src/control/codeedit.h
diff --git a/images/defines.png b/images/defines.png
new file mode 100644
index 0000000..9745a2e
Binary files /dev/null and b/images/defines.png differ
diff --git a/images/search.png b/images/search.png
new file mode 100644
index 0000000..ac85e0c
Binary files /dev/null and b/images/search.png differ
diff --git a/lang/zh_CN/winghex_zh_CN.ts b/lang/zh_CN/winghex_zh_CN.ts
index 88366fa..c31140a 100644
--- a/lang/zh_CN/winghex_zh_CN.ts
+++ b/lang/zh_CN/winghex_zh_CN.ts
@@ -232,7 +232,7 @@
<html><head/><body><p><span style=" font-size:12pt; font-weight:700;">哎呀!程序崩溃了。请将下面的信息发送给开发者寻求帮助:</span></p></body></html>
-
+
CopyToClipBoard
数据已拷贝到粘贴板
@@ -268,6 +268,44 @@
值
+
+ DefinitionDownloadDialog
+
+
+ Update Syntax Definitions
+
+
+
+
+ Updating syntax definitions from online repository...
+
+
+
+
+ %1:%2 hours
+
+
+
+
+ %1:%2 minutes
+
+
+
+
+ %1.%2 seconds
+
+
+
+
+ %1 ms
+
+
+
+
+ Update operation completed (%1)
+
+
+
DockWidgetTab
@@ -1235,7 +1273,7 @@
-
+
NothingToSave
没有保存的数据
@@ -1399,7 +1437,7 @@
-
+
LayoutRestoring...
恢复布局中...
@@ -1410,7 +1448,7 @@
-
+
SaveLayout
保存布局
@@ -1521,13 +1559,13 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
Error
错误
@@ -1544,8 +1582,8 @@
-
-
+
+
FilePermission
因文件权限无法继续!
@@ -1571,7 +1609,7 @@
-
+
ChooseSaveFile
请选择保存文件路径:
@@ -1591,42 +1629,42 @@
搜索数量已到达上限,结果可能不全,建议请按区段搜索。
-
+
SaveLayoutSuccess
保存布局成功
-
+
SaveLayoutError
保存布局失败
-
+
HasClonedView
该编辑页已被克隆编辑,如果关闭,相关联的页也会被关闭,你确认继续吗?
-
+
SaveWorkSpace
保存工作区
-
+
WingHexWorkSpace (*.wingpro)
羽云十六进制工作区 (*.wingpro)
-
+
ConfirmSave
正在关闭未保存的文件或工作区,你确定保存吗?
-
+
Column %1
列 %1
-
+
ConfirmAPPSave
你尝试关闭程序,但仍存在未保存的文件或工作区,你确定保存这些更改吗?
@@ -1636,7 +1674,7 @@
-
+
SaveSuccessfully
保存成功!
@@ -1647,7 +1685,7 @@
保存工作区错误!
-
+
Warn
警告
@@ -1820,7 +1858,7 @@
-
+
NoSelection
没有选区,无法继续的操作!
@@ -1876,83 +1914,83 @@
超出解码字节限制……
-
+
LayoutSaving...
布局保存中...
-
+
PleaseInput
请输入
-
+
LogExporting...
日志导出中...
-
+
ExportLogError
导出日志失败!
-
+
ExportLogSuccess
导出日志成功,路径:
-
+
ClearLogSuccess
清空日志成功!
-
+
BadNetwork
无法与远程服务器的更新检查建立连接,请检查网络。
-
+
NewestVersion
当前软件为最新版本
-
+
OlderVersion
你使用的软件为老版本,建议到 Github 和 Gitee 的仓库发行版下载更新。
-
+
CheckingUpdate
检查更新中……
-
+
Too much opened files
打开的文件过多,无法继续操作!
-
+
FilePermissionSure2Quit
因文件权限无法保存,你确认要退出吗?
-
+
UnknownErrorSure2Quit
因未知错误无法保存,你确认要退出吗?
-
+
WorkSpaceUnSavedSure2Quit
工作区文件无法保存,你确认要退出吗?
-
+
CopyLimit
拷贝字节超出限制
-
+
ErrOpenFileBelow
打开文件出现错误(由于权限不足),如下为打开错误的文件:
@@ -2477,32 +2515,32 @@
制表符宽度
-
+
Default
默认
-
+
IndentMixed
混合缩进
-
+
IndentSpaces
空格缩进
-
+
Console
控制台
-
+
IndentTabs
制表符缩进
-
+
Edit
编辑
@@ -4386,22 +4424,22 @@
ScriptingConsole
-
+
[Info]
【信息】
-
+
[Warn]
【警告】
-
+
[Error]
【错误】
-
+
[Console]
【控制台】
@@ -4835,6 +4873,143 @@
关于 QT
+
+ SearchDialog
+
+
+ Find and Replace...
+
+
+
+
+ Match ca&se
+
+
+
+
+ Match &whole words
+
+
+
+
+ Regular e&xpressions
+
+
+
+
+ &Escape sequences
+
+
+
+
+ Wrap Aro&und
+
+
+
+
+ Find &Next
+
+
+
+
+ Find &Previous
+
+
+
+
+ &Replace
+
+
+
+
+ Replace &All
+
+
+
+
+ &In Selection
+
+
+
+
+ &Close
+
+
+
+
+ &Find:
+
+
+
+
+ Replace wit&h:
+
+
+
+
+
+ The specified text was not found
+
+
+
+
+ The specified text was not found in the selection
+
+
+
+
+ Successfully replaced %1 matches
+
+
+
+
+ SearchWidget
+
+
+ Search Settings
+
+
+
+
+ Match ca&se
+
+
+
+
+ Match &whole words
+
+
+
+
+ Regular e&xpressions
+
+
+
+
+ &Escape sequences
+
+
+
+
+ Wrap Aro&und
+
+
+
+
+ Find Next
+
+
+
+
+ Find Previous
+
+
+
+
+ The specified text was not found
+
+
+
SettingDialog
@@ -4877,57 +5052,63 @@
ShowTextDialog
-
+
General
基本
-
+
Copy
复制
-
+
TextBroswer
文本浏览
+ Plain Text
+ 纯文本
+
+
+
TooHugeFile
文件内容过大
-
+
+
+ Loading...
+
+
+
+
Edit
编辑
-
- View
- 视图
-
-
-
+
Find
查找
-
+
Goto
跳转
-
+
Encoding
编码
-
- Display
- 显示
+
+ UpdateDefs
+
-
+
CopyToClipBoard
数据已拷贝到粘贴板
@@ -4948,7 +5129,7 @@
SyntaxPopup
-
+
Plain Text
纯文本
diff --git a/lang/zh_TW/winghex_zh_TW.ts b/lang/zh_TW/winghex_zh_TW.ts
index 7a97141..05967c1 100644
--- a/lang/zh_TW/winghex_zh_TW.ts
+++ b/lang/zh_TW/winghex_zh_TW.ts
@@ -232,7 +232,7 @@
<html><head/><body><p><span style=" font-size:12pt; font-weight:700;">哎呀!程式崩潰了。請將下麵的資訊發送給開發者尋求幫助:</span></p></body></html>
-
+
CopyToClipBoard
數據已拷貝到粘貼板
@@ -268,6 +268,44 @@
值
+
+ DefinitionDownloadDialog
+
+
+ Update Syntax Definitions
+
+
+
+
+ Updating syntax definitions from online repository...
+
+
+
+
+ %1:%2 hours
+
+
+
+
+ %1:%2 minutes
+
+
+
+
+ %1.%2 seconds
+
+
+
+
+ %1 ms
+
+
+
+
+ Update operation completed (%1)
+
+
+
DockWidgetTab
@@ -1235,7 +1273,7 @@
-
+
NothingToSave
沒有保存的數據
@@ -1399,7 +1437,7 @@
-
+
LayoutRestoring...
恢復佈局中...
@@ -1410,7 +1448,7 @@
-
+
SaveLayout
保存佈局
@@ -1521,13 +1559,13 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
Error
錯誤
@@ -1544,8 +1582,8 @@
-
-
+
+
FilePermission
因檔許可權無法繼續!
@@ -1571,7 +1609,7 @@
-
+
ChooseSaveFile
請選擇保存檔路徑:
@@ -1591,42 +1629,42 @@
搜索數量已到達上限,結果可能不全,建議請按區段搜索。
-
+
SaveLayoutSuccess
保存佈局成功
-
+
SaveLayoutError
保存佈局失敗
-
+
HasClonedView
該編輯頁已被克隆編輯,如果關閉,相關聯的頁也會被關閉,你確認繼續嗎?
-
+
SaveWorkSpace
保存工作區
-
+
WingHexWorkSpace (*.wingpro)
羽雲十六進制工作區 (*.wingpro)
-
+
ConfirmSave
正在關閉未保存的檔或工作區,你確定保存嗎?
-
+
Column %1
列 %1
-
+
ConfirmAPPSave
你嘗試關閉程式,但仍存在未保存的檔或工作區,你確定保存這些更改嗎?
@@ -1636,7 +1674,7 @@
-
+
SaveSuccessfully
保存成功!
@@ -1647,7 +1685,7 @@
保存工作區錯誤!
-
+
Warn
警告
@@ -1820,7 +1858,7 @@
-
+
NoSelection
沒有選區,無法繼續的操作!
@@ -1876,83 +1914,83 @@
超出解碼位元組限制……
-
+
LayoutSaving...
佈局保存中...
-
+
PleaseInput
請輸入
-
+
LogExporting...
日誌導出中...
-
+
ExportLogError
導出日誌失敗!
-
+
ExportLogSuccess
導出日誌成功,路徑:
-
+
ClearLogSuccess
清空日誌成功!
-
+
BadNetwork
無法與遠程伺服器的更新檢查建立連接,請檢查網路。
-
+
NewestVersion
當前軟體為最新版本
-
+
OlderVersion
你使用的軟體為老版本,建議到 Github 和 Gitee 的倉庫發行版下載更新。
-
+
CheckingUpdate
檢查更新中……
-
+
Too much opened files
打開的檔過多,無法繼續操作!
-
+
FilePermissionSure2Quit
因檔許可權無法保存,你確認要退出嗎?
-
+
UnknownErrorSure2Quit
因未知錯誤無法保存,你確認要退出嗎?
-
+
WorkSpaceUnSavedSure2Quit
工作區檔無法保存,你確認要退出嗎?
-
+
CopyLimit
拷貝位元組超出限制
-
+
ErrOpenFileBelow
打開檔出現錯誤(由於許可權不足),如下為打開錯誤的檔:
@@ -2477,32 +2515,32 @@
跳位字元寬度
-
+
Default
默認
-
+
IndentMixed
混合縮進
-
+
IndentSpaces
空格縮進
-
+
Console
控制台
-
+
IndentTabs
定位字元縮進
-
+
Edit
編輯
@@ -4386,22 +4424,22 @@
ScriptingConsole
-
+
[Info]
【資訊】
-
+
[Warn]
【警告】
-
+
[Error]
【錯誤】
-
+
[Console]
【控制臺】
@@ -4835,6 +4873,143 @@
關於 QT
+
+ SearchDialog
+
+
+ Find and Replace...
+
+
+
+
+ Match ca&se
+
+
+
+
+ Match &whole words
+
+
+
+
+ Regular e&xpressions
+
+
+
+
+ &Escape sequences
+
+
+
+
+ Wrap Aro&und
+
+
+
+
+ Find &Next
+
+
+
+
+ Find &Previous
+
+
+
+
+ &Replace
+
+
+
+
+ Replace &All
+
+
+
+
+ &In Selection
+
+
+
+
+ &Close
+
+
+
+
+ &Find:
+
+
+
+
+ Replace wit&h:
+
+
+
+
+
+ The specified text was not found
+
+
+
+
+ The specified text was not found in the selection
+
+
+
+
+ Successfully replaced %1 matches
+
+
+
+
+ SearchWidget
+
+
+ Search Settings
+
+
+
+
+ Match ca&se
+
+
+
+
+ Match &whole words
+
+
+
+
+ Regular e&xpressions
+
+
+
+
+ &Escape sequences
+
+
+
+
+ Wrap Aro&und
+
+
+
+
+ Find Next
+
+
+
+
+ Find Previous
+
+
+
+
+ The specified text was not found
+
+
+
SettingDialog
@@ -4877,57 +5052,63 @@
ShowTextDialog
-
+
General
基本
-
+
Copy
複製
-
+
TextBroswer
文本流覽
+ Plain Text
+ 純文字
+
+
+
TooHugeFile
檔內容過大
-
+
+
+ Loading...
+
+
+
+
Edit
編輯
-
- View
- 視圖
-
-
-
+
Find
查找
-
+
Goto
跳轉
-
+
Encoding
編碼
-
- Display
- 顯示
+
+ UpdateDefs
+
-
+
CopyToClipBoard
數據已拷貝到粘貼板
@@ -4948,7 +5129,7 @@
SyntaxPopup
-
+
Plain Text
純文字
diff --git a/resources.qrc b/resources.qrc
index b75d821..c385d16 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -27,6 +27,7 @@
images/dbgstepout.png
images/dbgstepover.png
images/dbgstop.png
+ images/defines.png
images/del.png
images/devext.png
images/edit.png
@@ -126,6 +127,7 @@
images/paste.png
images/redo.png
images/replace.png
+ images/search.png
images/undo.png
diff --git a/src/angelscript.xml b/src/angelscript.xml
index fb57029..d3f5967 100644
--- a/src/angelscript.xml
+++ b/src/angelscript.xml
@@ -109,7 +109,6 @@
-
@@ -132,8 +131,8 @@
-
-
+
+
diff --git a/src/control/codeedit.cpp b/src/control/codeedit.cpp
index efa3936..414d420 100644
--- a/src/control/codeedit.cpp
+++ b/src/control/codeedit.cpp
@@ -17,11 +17,17 @@
#include "codeedit.h"
#include "class/scriptsettings.h"
+#include "class/skinmanager.h"
#include "model/codecompletionmodel.h"
+#include
#include
#include
+#include
+#include
+#include
+
CodeEdit::CodeEdit(QWidget *parent) : WingCodeEdit(parent) {
connect(this->document(), &QTextDocument::modificationChanged, this,
&CodeEdit::contentModified);
@@ -30,6 +36,28 @@ CodeEdit::CodeEdit(QWidget *parent) : WingCodeEdit(parent) {
connect(&ScriptSettings::instance(), &ScriptSettings::editorSettingsUpdate,
this, &CodeEdit::applyEditorSetStyle);
applyEditorSetStyle();
+
+ m_searchWidget = new SearchWidget(this, parent);
+ showSearchBar(false);
+
+ connect(this, &CodeEdit::textChanged, this, [this] {
+ if (m_searchWidget->isVisible())
+ showSearchBar(false);
+ });
+}
+
+void CodeEdit::showSearchBar(bool show) {
+ m_searchWidget->setVisible(show);
+ m_searchWidget->setEnabled(show);
+ if (show) {
+ const QTextCursor cursor = textCursor();
+ if (cursor.hasSelection())
+ m_searchWidget->setSearchText(cursor.selectedText());
+ m_searchWidget->activate();
+ } else {
+ clearLiveSearch();
+ setFocus(Qt::OtherFocusReason);
+ }
}
void CodeEdit::onCompletion(const QModelIndex &index) {
@@ -65,6 +93,23 @@ void CodeEdit::applyEditorSetStyle() {
auto &set = ScriptSettings::instance();
auto dfont = QFont(set.editorFontFamily());
dfont.setPointSize(set.editorFontSize());
+
+ auto thname = set.editorTheme();
+ if (thname.isEmpty()) {
+ switch (SkinManager::instance().currentTheme()) {
+ case SkinManager::Theme::Dark:
+ this->setTheme(syntaxRepo().defaultTheme(
+ KSyntaxHighlighting::Repository::DarkTheme));
+ break;
+ case SkinManager::Theme::Light:
+ this->setTheme(syntaxRepo().defaultTheme(
+ KSyntaxHighlighting::Repository::LightTheme));
+ break;
+ }
+ } else {
+ this->setTheme(syntaxRepo().theme(thname));
+ }
+
this->setFont(dfont);
this->setTabWidth(set.editorTabWidth());
this->setIndentationMode(WingCodeEdit::IndentationMode(set.editorInden()));
@@ -77,3 +122,16 @@ void CodeEdit::applyEditorSetStyle() {
this->setShowWhitespace(set.editorShowWhiteSpace());
this->setAutoCloseChar(set.editorAutoCloseChar());
}
+
+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());
+}
diff --git a/src/control/codeedit.h b/src/control/codeedit.h
index 7bf83a7..1dfdb14 100644
--- a/src/control/codeedit.h
+++ b/src/control/codeedit.h
@@ -19,6 +19,7 @@
#define CODEEDIT_H
#include "WingCodeEdit/wingcodeedit.h"
+#include "dialog/searchdialog.h"
class CodeEdit : public WingCodeEdit {
Q_OBJECT
@@ -26,6 +27,9 @@ class CodeEdit : public WingCodeEdit {
public:
explicit CodeEdit(QWidget *parent = nullptr);
+public:
+ void showSearchBar(bool show);
+
signals:
void contentModified(bool b);
@@ -35,8 +39,15 @@ protected slots:
private:
void addMoveLineShortCut();
+ // QWidget interface
+protected:
+ virtual void resizeEvent(QResizeEvent *event) override;
+
private slots:
void applyEditorSetStyle();
+
+private:
+ SearchWidget *m_searchWidget;
};
#endif // CODEEDIT_H
diff --git a/src/control/scriptingconsole.cpp b/src/control/scriptingconsole.cpp
index 588a003..4e4f06a 100644
--- a/src/control/scriptingconsole.cpp
+++ b/src/control/scriptingconsole.cpp
@@ -20,6 +20,7 @@
#include "class/logger.h"
#include "class/scriptconsolemachine.h"
#include "class/scriptsettings.h"
+#include "class/skinmanager.h"
#include "model/codecompletionmodel.h"
#include
@@ -28,6 +29,10 @@
#include
#include
+#include
+#include
+#include
+
ScriptingConsole::ScriptingConsole(QWidget *parent)
: ScriptingConsoleBase(parent) {
connect(&ScriptSettings::instance(), &ScriptSettings::consoleSettingUpdate,
@@ -135,6 +140,23 @@ void ScriptingConsole::applyScriptSettings() {
auto &set = ScriptSettings::instance();
auto dfont = QFont(set.consoleFontFamily());
dfont.setPointSize(set.consoleFontSize());
+
+ auto thname = set.editorTheme();
+ if (thname.isEmpty()) {
+ switch (SkinManager::instance().currentTheme()) {
+ case SkinManager::Theme::Dark:
+ setTheme(syntaxRepo().defaultTheme(
+ KSyntaxHighlighting::Repository::DarkTheme));
+ break;
+ case SkinManager::Theme::Light:
+ setTheme(syntaxRepo().defaultTheme(
+ KSyntaxHighlighting::Repository::LightTheme));
+ break;
+ }
+ } else {
+ setTheme(syntaxRepo().theme(thname));
+ }
+
this->setFont(dfont);
this->setTabWidth(set.consoleTabWidth());
this->setIndentationMode(WingCodeEdit::IndentationMode(set.consoleInden()));
diff --git a/src/control/settingspopup.cpp b/src/control/settingspopup.cpp
index 83e9f05..af069b6 100644
--- a/src/control/settingspopup.cpp
+++ b/src/control/settingspopup.cpp
@@ -37,18 +37,7 @@
#include "utilities.h"
TreeFilterEdit::TreeFilterEdit(QWidget *parent) : QLineEdit(parent) {
-#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
- const bool darkTheme =
- (QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark);
-#else
- const bool darkTheme =
- (palette().color(QPalette::Window).lightness() < 128);
-#endif
-
- // TODO
- // m_searchIcon =
- //(QStringLiteral("search-filter"),
- // darkTheme);
+ m_searchIcon = QIcon(QStringLiteral(":/qeditor/images/search.png"));
setClearButtonEnabled(true);
recomputeIconPos();
diff --git a/src/dialog/crashreport.cpp b/src/dialog/crashreport.cpp
index 07471f8..f0d84da 100644
--- a/src/dialog/crashreport.cpp
+++ b/src/dialog/crashreport.cpp
@@ -1,3 +1,20 @@
+/*==============================================================================
+** 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 .
+** =============================================================================
+*/
+
#include "crashreport.h"
#include "ui_crashreport.h"
diff --git a/src/dialog/definitiondownload.cpp b/src/dialog/definitiondownload.cpp
new file mode 100644
index 0000000..aca8f75
--- /dev/null
+++ b/src/dialog/definitiondownload.cpp
@@ -0,0 +1,110 @@
+/*==============================================================================
+** 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 .
+** =============================================================================
+*/
+
+#include "definitiondownload.h"
+
+#include
+#include
+#include
+#include
+
+#include
+
+class ResizedPlainTextEdit : public QPlainTextEdit {
+public:
+ ResizedPlainTextEdit(QWidget *parent) : QPlainTextEdit(parent) {}
+
+protected:
+ QSize sizeHint() const override {
+ const auto &metrics = fontMetrics();
+ return QSize(
+ metrics.boundingRect(QString(60, QLatin1Char('x'))).width(),
+ metrics.height() * 10);
+ }
+};
+
+DefinitionDownloadDialog::DefinitionDownloadDialog(
+ KSyntaxHighlighting::Repository *repository, QWidget *parent)
+ : FramelessDialogBase(parent) {
+ setWindowTitle(tr("Update Syntax Definitions"));
+ setModal(true);
+
+ auto w = new QWidget(this);
+ m_status = new ResizedPlainTextEdit(w);
+ m_status->setReadOnly(true);
+
+ m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, this);
+ m_buttonBox->button(QDialogButtonBox::Close)->setEnabled(false);
+ connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
+ connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
+
+ auto layout = new QVBoxLayout(w);
+ layout->setSpacing(10);
+ layout->addWidget(m_status);
+ layout->addWidget(m_buttonBox);
+ buildUpContent(w);
+
+ m_downloader =
+ new KSyntaxHighlighting::DefinitionDownloader(repository, this);
+ QObject::connect(
+ m_downloader,
+ &KSyntaxHighlighting::DefinitionDownloader::informationMessage,
+ m_status, &QPlainTextEdit::appendPlainText);
+ QObject::connect(m_downloader,
+ &KSyntaxHighlighting::DefinitionDownloader::done, this,
+ &DefinitionDownloadDialog::downloadFinished);
+
+ m_status->appendPlainText(
+ tr("Updating syntax definitions from online repository..."));
+ m_downloader->start();
+ m_timer.start();
+}
+
+void DefinitionDownloadDialog::downloadFinished() {
+ qint64 elapsed = m_timer.elapsed();
+ QString timeStr;
+ if (elapsed >= 60 * 60 * 1000) {
+ qint64 hours = elapsed / (60 * 60 * 1000);
+ elapsed %= (60 * 60 * 1000);
+ qint64 minutes = elapsed / (60 * 1000);
+ timeStr =
+ tr("%1:%2 hours").arg(hours).arg(minutes, 2, 10, QLatin1Char('0'));
+ } else if (elapsed >= 60 * 1000) {
+ qint64 minutes = elapsed / (60 * 1000);
+ elapsed %= (60 * 1000);
+ qint64 seconds = elapsed / 1000;
+ timeStr = tr("%1:%2 minutes")
+ .arg(minutes)
+ .arg(seconds, 2, 10, QLatin1Char('0'));
+ } else if (elapsed >= 1000) {
+ qint64 seconds = elapsed / 1000;
+ elapsed %= 1000;
+ timeStr =
+ tr("%1.%2 seconds").arg(seconds).arg((int)qRound(elapsed / 100.0));
+ } else {
+ timeStr = tr("%1 ms").arg(elapsed);
+ }
+ m_status->appendPlainText(
+ tr("Update operation completed (%1)").arg(timeStr));
+ m_buttonBox->button(QDialogButtonBox::Close)->setEnabled(true);
+ m_downloader->deleteLater();
+}
+
+void DefinitionDownloadDialog::closeEvent(QCloseEvent *e) {
+ if (!m_buttonBox->button(QDialogButtonBox::Close)->isEnabled())
+ e->ignore();
+}
diff --git a/src/dialog/definitiondownload.h b/src/dialog/definitiondownload.h
new file mode 100644
index 0000000..758aa8d
--- /dev/null
+++ b/src/dialog/definitiondownload.h
@@ -0,0 +1,53 @@
+/*==============================================================================
+** 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 .
+** =============================================================================
+*/
+
+#ifndef QTEXTPAD_DEFINITIONDOWNLOAD_H
+#define QTEXTPAD_DEFINITIONDOWNLOAD_H
+
+#include "dialog/framelessdialogbase.h"
+
+#include
+
+class QPlainTextEdit;
+class QDialogButtonBox;
+
+namespace KSyntaxHighlighting {
+class Repository;
+class DefinitionDownloader;
+} // namespace KSyntaxHighlighting
+
+class DefinitionDownloadDialog : public FramelessDialogBase {
+ Q_OBJECT
+
+public:
+ explicit DefinitionDownloadDialog(
+ KSyntaxHighlighting::Repository *repository, QWidget *parent);
+
+public Q_SLOTS:
+ void downloadFinished();
+
+protected:
+ void closeEvent(QCloseEvent *) override;
+
+private:
+ KSyntaxHighlighting::DefinitionDownloader *m_downloader;
+ QPlainTextEdit *m_status;
+ QDialogButtonBox *m_buttonBox;
+ QElapsedTimer m_timer;
+};
+
+#endif // QTEXTPAD_DEFINITIONDOWNLOAD_H
diff --git a/src/dialog/mainwindow.cpp b/src/dialog/mainwindow.cpp
index 9cea2a3..96a29ac 100644
--- a/src/dialog/mainwindow.cpp
+++ b/src/dialog/mainwindow.cpp
@@ -2976,11 +2976,14 @@ void MainWindow::on_selectionChanged() {
}
void MainWindow::on_viewtxt() {
- auto hexeditor = currentHexView();
- if (hexeditor == nullptr) {
+ auto editor = currentEditor();
+ if (editor == nullptr) {
return;
}
- _showtxt->load(hexeditor->document()->buffer());
+ auto hexeditor = editor->hexEditor();
+ QMimeDatabase db;
+ _showtxt->load(hexeditor->document()->buffer(),
+ db.mimeTypeForFile(editor->fileName()).name());
}
void MainWindow::on_fullScreen() {
diff --git a/src/dialog/searchdialog.cpp b/src/dialog/searchdialog.cpp
new file mode 100644
index 0000000..4e532f3
--- /dev/null
+++ b/src/dialog/searchdialog.cpp
@@ -0,0 +1,618 @@
+/*==============================================================================
+** 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 .
+** =============================================================================
+*/
+
+#include "searchdialog.h"
+#include "utilities.h"
+
+#include "class/wingmessagebox.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
+#include
+#include
+#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);
+
+ *advance = 0;
+ if (digits.at(0) == QLatin1Char('x')) {
+ // We only support exactly 2 hex bytes with \x format
+ if (digits.size() < 3)
+ return QString();
+ QByteArray number = digits.mid(1, 2).toLatin1();
+ char *end;
+ ulong ch = strtoul(number.constData(), &end, 16);
+ if (*end != '\0')
+ return QString();
+ *advance = 2;
+ return QString(QChar::fromLatin1(static_cast(ch)));
+ } else if (digits.at(0) == QLatin1Char('u')) {
+ if (digits.size() < 5)
+ return QString();
+ QByteArray number = digits.mid(1, 4).toLatin1();
+ char *end;
+ ulong ch = strtoul(number.constData(), &end, 16);
+ if (*end != '\0')
+ return QString();
+ *advance = 4;
+ return QString(QChar(static_cast(ch)));
+ } else if (digits.at(0) == QLatin1Char('U')) {
+ if (digits.size() < 9)
+ return QString();
+ QByteArray number = digits.mid(1, 8).toLatin1();
+ char *end;
+ ulong ch = strtoul(number.constData(), &end, 16);
+ if (*end != '\0')
+ return QString();
+
+ *advance = 8;
+ if (ch > 0xFFFFU) {
+ const QChar utf16[2] = {
+ QChar::highSurrogate(ch),
+ QChar::lowSurrogate(ch),
+ };
+ return QString(utf16, 2);
+ } else {
+ return QString(QChar(static_cast(ch)));
+ }
+ } else {
+ // Octal no longer supported
+ qFatal("Unsupported character escape prefix");
+ return QString();
+ }
+}
+
+QString SearchDialog::translateEscapes(const QString &text) {
+ QString result;
+ result.reserve(text.size());
+ int start = 0;
+ for (;;) {
+ int pos = text.indexOf(QLatin1Char('\\'), start);
+ if (pos < 0 || pos + 1 >= text.size())
+ break;
+
+ result.append(text.sliced(start, pos - start));
+ QChar next = text.at(pos + 1);
+ start = pos + 2;
+ switch (next.unicode()) {
+ case 'a':
+ result.append(QLatin1Char('\a'));
+ break;
+ case 'b':
+ result.append(QLatin1Char('\b'));
+ break;
+ case 'e':
+ result.append(QLatin1Char('\x1b'));
+ break;
+ case 'f':
+ result.append(QLatin1Char('\f'));
+ break;
+ case 'n':
+ result.append(QLatin1Char('\n'));
+ break;
+ case 'r':
+ result.append(QLatin1Char('\r'));
+ break;
+ case 't':
+ result.append(QLatin1Char('\t'));
+ break;
+ case 'v':
+ result.append(QLatin1Char('\v'));
+ break;
+ case '\\':
+ case '?':
+ case '\'':
+ case '"':
+ result.append(next);
+ break;
+ case 'x': // Hex byte
+ case 'u': // Unicode character (16-bit)
+ case 'U': // Unicode character (32-bit)
+ {
+ int advance;
+ const QString chars =
+ translateCharEscape(QStringView(text).mid(pos + 1), &advance);
+ if (chars.isEmpty()) {
+ // Translation failed
+ result.append(QLatin1Char('\\'));
+ result.append(next);
+ } else {
+ result.append(chars);
+ start += advance;
+ }
+ } break;
+ default:
+ // Just keep unrecognized sequences untranslated
+ result.append(QLatin1Char('\\'));
+ result.append(next);
+ break;
+ }
+ }
+
+ result.append(text.sliced(start));
+ return result;
+}
+
+QString SearchDialog::regexReplace(const QString &text,
+ const QRegularExpressionMatch ®exMatch) {
+ QString result;
+ result.reserve(text.size());
+ int start = 0;
+ for (;;) {
+ int pos = text.indexOf(QLatin1Char('\\'), start);
+ if (pos < 0 || pos + 1 >= text.size())
+ break;
+
+ result.append(text.sliced(start, pos - start));
+ QChar next = text.at(pos + 1);
+ if (next.unicode() >= '0' && next.unicode() <= '9') {
+ // We support up to 99 replacements...
+ QByteArray number = QStringView(text).mid(pos + 1, 2).toLatin1();
+ char *end;
+ ulong ref = strtoul(number.constData(), &end, 10);
+ result.append(regexMatch.captured(ref));
+ start = pos + 1 + (end - number.constData());
+ } else {
+ result.append(QLatin1Char('\\'));
+ result.append(next);
+ start = pos + 2;
+ }
+ }
+
+ 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));
+}
diff --git a/src/dialog/searchdialog.h b/src/dialog/searchdialog.h
new file mode 100644
index 0000000..c02c072
--- /dev/null
+++ b/src/dialog/searchdialog.h
@@ -0,0 +1,113 @@
+/*==============================================================================
+** 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 .
+** =============================================================================
+*/
+
+#ifndef QTEXTPAD_SEARCHDIALOG_H
+#define QTEXTPAD_SEARCHDIALOG_H
+
+#include
+#include
+#include
+
+#include "dialog/framelessdialogbase.h"
+#include "wingcodeedit.h"
+
+class QLineEdit;
+class QComboBox;
+class QCheckBox;
+class QPushButton;
+class QTextCursor;
+
+class SearchWidget : public QWidget {
+ 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()};
+ }
+
+ void setSearchText(const QString &text);
+ void activate();
+
+public Q_SLOTS:
+ void searchNext(bool reverse);
+
+protected:
+ void paintEvent(QPaintEvent *event) override;
+
+private Q_SLOTS:
+ void updateSettings();
+
+private:
+ QLineEdit *m_searchText;
+ QAction *m_caseSensitive;
+ QAction *m_wholeWord;
+ QAction *m_regex;
+ QAction *m_escapes;
+ QAction *m_wrapSearch;
+
+ WingCodeEdit *m_editor;
+ WingCodeEdit::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 ®exMatch);
+
+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;
+ QTextCursor m_replaceCursor;
+
+ WingCodeEdit *m_editor;
+ WingCodeEdit::SearchParams m_searchParams;
+ QRegularExpressionMatch m_regexMatch;
+};
+
+#endif // QTEXTPAD_SEARCHDIALOG_H
diff --git a/src/dialog/showtextdialog.cpp b/src/dialog/showtextdialog.cpp
index 9314831..73453a3 100644
--- a/src/dialog/showtextdialog.cpp
+++ b/src/dialog/showtextdialog.cpp
@@ -23,11 +23,14 @@
#include "control/popupactionwidget.h"
#include "control/settingspopup.h"
#include "control/toast.h"
+#include "dialog/definitiondownload.h"
#include "dialog/encodingdialog.h"
#include
#include
+#include
+
constexpr auto EMPTY_FUNC = [] {};
class SyntaxPopupAction : public PopupActionWidget {
@@ -67,7 +70,6 @@ ShowTextDialog::ShowTextDialog(QWidget *parent) : FramelessDialogBase(parent) {
layout->addWidget(q_check_ptr(m_ribbon));
m_edit = new CodeEdit(this);
- m_edit->setShowLongLineEdge(false);
m_edit->setReadOnly(true);
m_edit->setAcceptDrops(false);
@@ -79,6 +81,13 @@ ShowTextDialog::ShowTextDialog(QWidget *parent) : FramelessDialogBase(parent) {
layout->addWidget(m_status);
buildUpContent(cw);
+ m_syntaxButton = new QToolButton(this);
+ m_syntaxButton->setAutoRaise(true);
+ m_syntaxButton->setPopupMode(QToolButton::InstantPopup);
+ m_syntaxButton->addAction(new SyntaxPopupAction(this));
+
+ m_status->addPermanentWidget(m_syntaxButton);
+
// ok, preparing for starting...
this->setWindowTitle(tr("TextBroswer"));
this->setWindowIcon(ICONRES(QStringLiteral("viewtxt")));
@@ -91,38 +100,45 @@ ShowTextDialog::~ShowTextDialog() {}
void ShowTextDialog::setSyntax(const KSyntaxHighlighting::Definition &syntax) {
m_edit->setSyntax(syntax);
+ if (syntax.isValid()) {
+ m_syntaxButton->setText(syntax.translatedName() + QChar(' ') +
+ QChar(char16_t(0x25BC)));
+ } else {
+ m_syntaxButton->setText(tr("Plain Text") + QChar(' ') +
+ QChar(char16_t(0x25BC)));
+ }
}
-void ShowTextDialog::load(QHexBuffer *buffer, const QString encoding) {
- // auto editor = m_edit->editor();
- // editor->blockSignals(true);
- // editor->setCodec(encoding);
- // load(buffer, offset, size);
- // editor->blockSignals(false);
-}
-
-void ShowTextDialog::load(QHexBuffer *buffer) {
+void ShowTextDialog::load(QHexBuffer *buffer, const QString &mime,
+ const QString &encoding) {
constexpr auto MAXLEN = 1024 * 1024 * 1024;
if (buffer->length() > MAXLEN) {
WingMessageBox::critical(this, this->windowTitle(), tr("TooHugeFile"));
return;
}
- m_edit->setUpdatesEnabled(false);
-
- // auto doc = editor->document();
- // auto orign = offset;
-
- m_edit->setUpdatesEnabled(true);
- // m_status->showMessage(editor->codecName());
show();
+ m_status->showMessage(tr("Loading..."));
+
+ QString enc;
+ auto ecs = Utilities::getEncodings();
+ if (encoding.isEmpty() || !ecs.contains(encoding)) {
+ enc = QStringLiteral("ASCII");
+ } else {
+ enc = encoding;
+ }
+
+ this->buffer = buffer->read(qsizetype(0), buffer->length());
+ m_edit->setPlainText(Utilities::decodingString(this->buffer, enc));
+ m_status->showMessage(enc);
+
+ setSyntax(WingCodeEdit::syntaxRepo().definitionForMimeType(mime));
}
void ShowTextDialog::buildUpRibbonBar() {
m_ribbon = new Ribbon(this);
m_ribbon->setAcceptDrops(false);
buildEditPage(m_ribbon->addTab(tr("Edit")));
- buildViewPage(m_ribbon->addTab(tr("View")));
}
RibbonTabContent *ShowTextDialog::buildEditPage(RibbonTabContent *tab) {
@@ -146,28 +162,20 @@ RibbonTabContent *ShowTextDialog::buildEditPage(RibbonTabContent *tab) {
addPannelAction(pannel, QStringLiteral("encoding"), tr("Encoding"),
&ShowTextDialog::on_encoding,
shortcuts.keySequence(QKeySequences::Key::ENCODING));
+ addPannelAction(pannel, QStringLiteral("defines"), tr("UpdateDefs"),
+ &ShowTextDialog::on_updateDefs);
}
return tab;
}
-RibbonTabContent *ShowTextDialog::buildViewPage(RibbonTabContent *tab) {
- auto shortcuts = QKeySequences::instance();
-
- auto pannel = tab->addGroup(tr("Display"));
-
- return tab;
-}
-
void ShowTextDialog::on_copyfile() {
m_edit->copy();
Toast::toast(this, NAMEICONRES(QStringLiteral("copy")),
tr("CopyToClipBoard"));
}
-void ShowTextDialog::on_findfile() {
- // m_edit->editor()->find();
-}
+void ShowTextDialog::on_findfile() { m_edit->showSearchBar(true); }
void ShowTextDialog::on_gotoline() {
// m_edit->editor()->gotoLine();
@@ -177,8 +185,17 @@ void ShowTextDialog::on_encoding() {
EncodingDialog d;
if (d.exec()) {
auto res = d.getResult();
- // m_edit->editor()->setCodec(res);
+ m_status->showMessage(tr("Loading..."));
+ m_edit->setPlainText(Utilities::decodingString(this->buffer, res));
+ m_status->showMessage(res);
}
}
-void ShowTextDialog::on_cancel() { m_canceled = true; }
+void ShowTextDialog::on_updateDefs() {
+ auto downloadDialog =
+ new DefinitionDownloadDialog(&WingCodeEdit::syntaxRepo(), this);
+ downloadDialog->setAttribute(Qt::WA_DeleteOnClose);
+ downloadDialog->show();
+ downloadDialog->raise();
+ downloadDialog->activateWindow();
+}
diff --git a/src/dialog/showtextdialog.h b/src/dialog/showtextdialog.h
index c2057c8..c036406 100644
--- a/src/dialog/showtextdialog.h
+++ b/src/dialog/showtextdialog.h
@@ -43,14 +43,13 @@ public:
void setSyntax(const KSyntaxHighlighting::Definition &syntax);
public:
- void load(QHexBuffer *buffer, const QString encoding);
- void load(QHexBuffer *buffer);
+ void load(QHexBuffer *buffer, const QString &mime,
+ const QString &encoding = {});
private:
void buildUpRibbonBar();
RibbonTabContent *buildEditPage(RibbonTabContent *tab);
- RibbonTabContent *buildViewPage(RibbonTabContent *tab);
private:
template
@@ -119,21 +118,15 @@ private slots:
void on_findfile();
void on_gotoline();
void on_encoding();
-
- void on_cancel();
+ void on_updateDefs();
private:
Ribbon *m_ribbon = nullptr;
CodeEdit *m_edit = nullptr;
- bool m_canceled = false;
-
QStatusBar *m_status = nullptr;
+ QToolButton *m_syntaxButton = nullptr;
- struct {
- QHexBuffer *buffer = nullptr;
- qsizetype offset = 0;
- qsizetype size = -1;
- } m_last;
+ QByteArray buffer;
};
#endif // SHOWTEXTDIALOG_H
diff --git a/src/settings/qeditconfig.cpp b/src/settings/qeditconfig.cpp
index 90d82ed..0f453e5 100644
--- a/src/settings/qeditconfig.cpp
+++ b/src/settings/qeditconfig.cpp
@@ -1,17 +1,19 @@
-/****************************************************************************
+/*==============================================================================
+** Copyright (C) 2024-2027 WingSummer
**
-** Copyright (C) 2006-2009 fullmetalcoder
+** 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 file is part of the Edyuk project
+** 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.
**
-** This file may be used under the terms of the GNU General Public License
-** version 3 as published by the Free Software Foundation and appearing in the
-** file GPL.txt included in the packaging of this file.
-**
-** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
-** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
-**
-****************************************************************************/
+** You should have received a copy of the GNU Affero General Public License
+** along with this program. If not, see .
+** =============================================================================
+*/
#include "qeditconfig.h"
diff --git a/src/settings/qeditconfig.h b/src/settings/qeditconfig.h
index cef711c..e8e3fea 100644
--- a/src/settings/qeditconfig.h
+++ b/src/settings/qeditconfig.h
@@ -1,3 +1,20 @@
+/*==============================================================================
+** 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 .
+** =============================================================================
+*/
+
#ifndef QEDITCONFIG_H
#define QEDITCONFIG_H