├── .gitignore ├── Qt-Notepad.pro ├── README.md ├── finddialog.cpp ├── finddialog.h ├── finddialog.ui ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── mainwindow.ui └── screenshots ├── p1.png ├── p2.png ├── p3.png ├── p4.png └── p5.png /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | .directory 25 | *.debug 26 | Makefile* 27 | *.prl 28 | *.app 29 | moc_*.cpp 30 | ui_*.h 31 | qrc_*.cpp 32 | Thumbs.db 33 | *.res 34 | *.rc 35 | /.qmake.cache 36 | /.qmake.stash 37 | 38 | # qtcreator generated files 39 | *.pro.user* 40 | 41 | # xemacs temporary files 42 | *.flc 43 | 44 | # Vim temporary files 45 | .*.swp 46 | 47 | # Visual Studio generated files 48 | *.ib_pdb_index 49 | *.idb 50 | *.ilk 51 | *.pdb 52 | *.sln 53 | *.suo 54 | *.vcproj 55 | *vcproj.*.*.user 56 | *.ncb 57 | *.sdf 58 | *.opensdf 59 | *.vcxproj 60 | *vcxproj.* 61 | 62 | # MinGW generated files 63 | *.Debug 64 | *.Release 65 | 66 | # Python byte code 67 | *.pyc 68 | 69 | # Binaries 70 | # -------- 71 | *.dll 72 | *.exe 73 | 74 | -------------------------------------------------------------------------------- /Qt-Notepad.pro: -------------------------------------------------------------------------------- 1 | QT += core gui 2 | 3 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 4 | 5 | CONFIG += c++11 6 | 7 | # The following define makes your compiler emit warnings if you use 8 | # any Qt feature that has been marked deprecated (the exact warnings 9 | # depend on your compiler). Please consult the documentation of the 10 | # deprecated API in order to know how to port your code away from it. 11 | DEFINES += QT_DEPRECATED_WARNINGS 12 | 13 | # You can also make your code fail to compile if it uses deprecated APIs. 14 | # In order to do so, uncomment the following line. 15 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 16 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 17 | 18 | SOURCES += \ 19 | finddialog.cpp \ 20 | main.cpp \ 21 | mainwindow.cpp 22 | 23 | HEADERS += \ 24 | finddialog.h \ 25 | mainwindow.h 26 | 27 | FORMS += \ 28 | finddialog.ui \ 29 | mainwindow.ui 30 | 31 | # Default rules for deployment. 32 | qnx: target.path = /tmp/$${TARGET}/bin 33 | else: unix:!android: target.path = /opt/$${TARGET}/bin 34 | !isEmpty(target.path): INSTALLS += target 35 | 36 | DISTFILES += \ 37 | README.md 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Qt 高仿记事本 2 | === 3 | 4 | 完全使用 Qt/C++ 模仿 Windows 记事本的实现方案 5 | 6 | 7 | ![](screenshots/p1.png) 8 | 9 | ![](screenshots/p2.png) 10 | 11 | ![](screenshots/p3.png) 12 | 13 | ![](screenshots/p4.png) 14 | 15 | ![](screenshots/p5.png) 16 | 17 | 18 | ## 已完成 19 | 20 | - 新建 21 | - 新窗口 22 | - 打开 23 | - 保存 24 | - 另存为 25 | - 退出 26 | - 撤销 27 | - 剪切 28 | - 复制 29 | - 删除 30 | - 使用 Bing 搜索 31 | - 全选 32 | - 时间/日期 33 | - 查找 34 | - 查找下一个 35 | - 查找上一个 36 | - 替换 37 | - 全部替换 38 | - 区分大小写 39 | - 循环查找 40 | - 自动换行 41 | - 字体 42 | - 缩放 43 | - 状态栏 44 | - 关于 45 | - 光标行列 46 | - 缩放比例 47 | - 窗口标题 48 | - 命令行打开文件 49 | 50 | 51 | 52 | ## 未完成 53 | 54 | - 自动判断编码 55 | - 页面设置 56 | - 打印 57 | - 从右往左的阅读顺序 58 | - 显示 Unicode 控制字符 59 | - 插入 Unicode 控制字符 60 | - 汉字重选 61 | - 转到 62 | 63 | 64 | 65 | ## 一些说明 66 | 67 | 尽量追求长得相像了,不过一些更细节的功能,比如另存为的设置编码,不太好做,就不做了。 68 | 69 | 菜单右边的快捷键,Windows 记事本是左对齐、Qt 的是右对齐,不知道能不能实现对齐的设置,以及自定义文字(记事本右键菜单是不提示快捷键的,“插入 Unicode 控制字符”的 action 中还带有说明)。 70 | 71 | 以及查找的布局,微调了界面,看起来更整齐。 72 | 73 | 一些更更更深入的,如选中文字后右键菜单,选中区域变成灰色而不是继续保持一开始选中的蓝色,应该没人会这么细吧? 74 | 75 | -------------------------------------------------------------------------------- /finddialog.cpp: -------------------------------------------------------------------------------- 1 | #include "finddialog.h" 2 | #include "ui_finddialog.h" 3 | 4 | FindDialog::FindDialog(QSettings &settings, QWidget *parent) : 5 | QDialog(parent), 6 | ui(new Ui::FindDialog), 7 | settings(settings) 8 | { 9 | ui->setupUi(this); 10 | this->setWindowFlag(Qt::WindowContextHelpButtonHint, false); 11 | 12 | // 读取数据 13 | ui->findEdit->setText(settings.value("find/findText").toString()); 14 | ui->replaceEdit->setText(settings.value("find/replaceText").toString()); 15 | ui->caseSensitiveCheck->setChecked(settings.value("find/caseSensitive").toBool()); 16 | ui->loopCheck->setChecked(settings.value("find/loop").toBool()); 17 | if (!settings.value("find/down", true).toBool()) 18 | ui->upRadio->setChecked(true); 19 | } 20 | 21 | FindDialog::~FindDialog() 22 | { 23 | delete ui; 24 | } 25 | 26 | void FindDialog::openFind(bool replace) 27 | { 28 | ui->label_3->setVisible(replace); 29 | ui->replaceEdit->setVisible(replace); 30 | ui->replaceButton->setVisible(replace); 31 | ui->replaceAllButton->setVisible(replace); 32 | ui->groupBox->setVisible(!replace); 33 | QDialog::show(); // open/exec会导致模态 34 | ui->findEdit->setFocus(); 35 | ui->findEdit->selectAll(); 36 | this->adjustSize(); 37 | 38 | if (!replace) 39 | setWindowTitle("查找"); 40 | else 41 | setWindowTitle("替换"); 42 | } 43 | 44 | const QString FindDialog::getFindText() const 45 | { 46 | return ui->findEdit->text(); 47 | } 48 | 49 | const QString FindDialog::getReplaceText() const 50 | { 51 | return ui->replaceEdit->text(); 52 | } 53 | 54 | bool FindDialog::isCaseSensitive() const 55 | { 56 | return ui->caseSensitiveCheck->isChecked(); 57 | } 58 | 59 | bool FindDialog::isLoop() const 60 | { 61 | return ui->loopCheck->isChecked(); 62 | } 63 | 64 | void FindDialog::on_findNextButton_clicked() 65 | { 66 | if (ui->upRadio->isChecked()) 67 | emit signalFindPrev(); 68 | else 69 | emit signalFindNext(); 70 | settings.setValue("find/findText", ui->findEdit->text()); 71 | } 72 | 73 | void FindDialog::on_replaceButton_clicked() 74 | { 75 | emit signalReplaceNext(); 76 | settings.setValue("find/replaceText", ui->replaceEdit->text()); 77 | } 78 | 79 | void FindDialog::on_replaceAllButton_clicked() 80 | { 81 | emit signalReplaceAll(); 82 | settings.setValue("find/replaceText", ui->replaceEdit->text()); 83 | } 84 | 85 | void FindDialog::on_cancelButton_clicked() 86 | { 87 | this->close(); 88 | } 89 | 90 | void FindDialog::on_caseSensitiveCheck_clicked() 91 | { 92 | settings.setValue("find/caseSensitive", ui->caseSensitiveCheck->isChecked()); 93 | } 94 | 95 | void FindDialog::on_loopCheck_clicked() 96 | { 97 | settings.setValue("find/loop", ui->loopCheck->isChecked()); 98 | } 99 | 100 | void FindDialog::on_upRadio_clicked() 101 | { 102 | settings.setValue("find/down", false); 103 | } 104 | 105 | void FindDialog::on_downRadio_clicked() 106 | { 107 | settings.setValue("find/down", true); 108 | } 109 | 110 | void FindDialog::showEvent(QShowEvent *event) 111 | { 112 | emit signalShow(); 113 | adjustSize(); 114 | QWidget::showEvent(event); 115 | } 116 | 117 | void FindDialog::hideEvent(QHideEvent *event) 118 | { 119 | emit signalHide(); 120 | QWidget::hideEvent(event); 121 | } 122 | 123 | void FindDialog::on_findEdit_returnPressed() 124 | { 125 | on_findNextButton_clicked(); 126 | } 127 | 128 | void FindDialog::on_replaceEdit_returnPressed() 129 | { 130 | on_replaceButton_clicked(); 131 | } 132 | -------------------------------------------------------------------------------- /finddialog.h: -------------------------------------------------------------------------------- 1 | #ifndef FINDDIALOG_H 2 | #define FINDDIALOG_H 3 | 4 | #include 5 | #include 6 | 7 | namespace Ui { 8 | class FindDialog; 9 | } 10 | 11 | class FindDialog : public QDialog 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit FindDialog(QSettings& settings, QWidget *parent = nullptr); 17 | ~FindDialog() override; 18 | 19 | void openFind(bool replace); 20 | 21 | signals: 22 | void signalShow(); 23 | void signalHide(); 24 | // void signalTextChanged(const QString& text); 25 | void signalFindNext(); 26 | void signalFindPrev(); 27 | void signalReplaceNext(); 28 | void signalReplaceAll(); 29 | 30 | public: 31 | const QString getFindText() const; 32 | const QString getReplaceText() const; 33 | bool isCaseSensitive() const; 34 | bool isLoop() const; 35 | 36 | private slots: 37 | void on_findNextButton_clicked(); 38 | 39 | void on_replaceButton_clicked(); 40 | 41 | void on_replaceAllButton_clicked(); 42 | 43 | void on_cancelButton_clicked(); 44 | 45 | void on_caseSensitiveCheck_clicked(); 46 | 47 | void on_loopCheck_clicked(); 48 | 49 | void on_upRadio_clicked(); 50 | 51 | void on_downRadio_clicked(); 52 | 53 | void on_findEdit_returnPressed(); 54 | 55 | void on_replaceEdit_returnPressed(); 56 | 57 | protected: 58 | void showEvent(QShowEvent *event) override; 59 | void hideEvent(QHideEvent *event) override; 60 | 61 | private: 62 | Ui::FindDialog *ui; 63 | QSettings& settings; 64 | }; 65 | 66 | #endif // FINDDIALOG_H 67 | -------------------------------------------------------------------------------- /finddialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | FindDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 462 10 | 214 11 | 12 | 13 | 14 | 查找 15 | 16 | 17 | 18 | 6 19 | 20 | 21 | 12 22 | 23 | 24 | 6 25 | 26 | 27 | 12 28 | 29 | 30 | 31 | 32 | 0 33 | 34 | 35 | 36 | 37 | 0 38 | 39 | 40 | 0 41 | 42 | 43 | 44 | 45 | 9 46 | 47 | 48 | 0 49 | 50 | 51 | 0 52 | 53 | 54 | 0 55 | 56 | 57 | 0 58 | 59 | 60 | 61 | 62 | 查找内容(N): 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 220 71 | 0 72 | 73 | 74 | 75 | padding: 3px; 76 | 77 | 78 | 79 | 80 | 81 | 82 | 替换为(P): 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 220 91 | 0 92 | 93 | 94 | 95 | padding: 3px; 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | padding: 6px; 109 | 110 | 111 | 查找下一个(&F) 112 | 113 | 114 | 115 | 116 | 117 | 118 | padding: 6px; 119 | 120 | 121 | 替换(&R) 122 | 123 | 124 | 125 | 126 | 127 | 128 | padding: 6px; 129 | 130 | 131 | 全部替换(&A) 132 | 133 | 134 | 135 | 136 | 137 | 138 | padding: 6px; 139 | 140 | 141 | 取消 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 0 153 | 154 | 155 | 156 | 157 | 158 | 159 | 区分大小写(&C) 160 | 161 | 162 | 163 | 164 | 165 | 166 | 循环(&R) 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | Qt::Horizontal 176 | 177 | 178 | 179 | 40 180 | 20 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 方向 189 | 190 | 191 | 192 | 193 | 194 | 向上 195 | 196 | 197 | false 198 | 199 | 200 | 201 | 202 | 203 | 204 | 向下 205 | 206 | 207 | true 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "mainwindow.h" 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QApplication a(argc, argv); 8 | 9 | QFont f(a.font()); 10 | f.setFamily("微软雅黑"); 11 | a.setFont(f); 12 | 13 | a.setApplicationName("notepad"); 14 | a.setApplicationVersion("v0.1"); 15 | a.setApplicationDisplayName("记事本"); 16 | 17 | MainWindow w; 18 | w.show(); 19 | 20 | if (argc == 2) 21 | { 22 | QString path = QString::fromLocal8Bit(argv[1]); 23 | w.openFile(path); 24 | } 25 | 26 | return a.exec(); 27 | } 28 | -------------------------------------------------------------------------------- /mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "mainwindow.h" 11 | #include "ui_mainwindow.h" 12 | 13 | MainWindow::MainWindow(QWidget *parent) 14 | : QMainWindow(parent) 15 | , ui(new Ui::MainWindow), 16 | settings("MyNotepad") 17 | { 18 | ui->setupUi(this); 19 | 20 | // 读取设置 21 | if (!settings.value("wordWrap", true).toBool()) 22 | { 23 | ui->actionWord_Wrap_W->setChecked(false); 24 | ui->plainTextEdit->setWordWrapMode(QTextOption::NoWrap); 25 | } 26 | if (!settings.value("statusBar", true).toBool()) 27 | { 28 | this->statusBar()->hide(); 29 | ui->actionStatus_Bar_S->setChecked(false); 30 | } 31 | 32 | // 恢复字体 33 | QString fs; 34 | if (!(fs = settings.value("font").toString()).isEmpty()) 35 | { 36 | QFont f; 37 | f.fromString(fs); 38 | ui->plainTextEdit->setFont(f); 39 | } 40 | 41 | // 状态栏 42 | posLabel = new QLabel("第 1 行,第 1 列", this); 43 | zoomLabel = new QLabel("100%", this); 44 | lineLabel = new QLabel("Windows (CRLF)", this); 45 | codecLabel = new QLabel("GBK", this); 46 | ui->statusbar->addPermanentWidget(new QLabel(this), 6); 47 | ui->statusbar->addPermanentWidget(posLabel, 3); 48 | ui->statusbar->addPermanentWidget(zoomLabel, 1); 49 | ui->statusbar->addPermanentWidget(lineLabel, 3); 50 | ui->statusbar->addPermanentWidget(codecLabel, 1); 51 | 52 | // 设置为系统notepad图标 53 | QFileIconProvider ip; 54 | QIcon icon = ip.icon(QFileInfo("C:\\Windows\\System32\\notepad.exe")); 55 | qApp->setWindowIcon(icon); 56 | } 57 | 58 | MainWindow::~MainWindow() 59 | { 60 | delete ui; 61 | } 62 | 63 | void MainWindow::openFile(QString path) 64 | { 65 | filePath = path; 66 | 67 | if (path.isEmpty()) 68 | { 69 | savedContent = ""; 70 | fileName = "无标题"; 71 | } 72 | else 73 | { 74 | QFile file(path); 75 | if (!file.exists()) 76 | { 77 | qWarning() << "文件不存在"; 78 | return ; 79 | } 80 | fileName = QFileInfo(path).baseName(); 81 | 82 | // 读取文件 83 | if (!file.open(QIODevice::ReadOnly)) 84 | { 85 | qWarning() << "打开文件失败"; 86 | return ; 87 | } 88 | savedContent = QString::fromLocal8Bit(file.readAll()); 89 | } 90 | ui->plainTextEdit->setPlainText(savedContent); 91 | updateWindowTitle(); 92 | } 93 | 94 | bool MainWindow::isModified() const 95 | { 96 | return ui->plainTextEdit->toPlainText() != savedContent; 97 | } 98 | 99 | /** 100 | * @brief MainWindow::askSave 101 | * @return 是否继续 102 | */ 103 | bool MainWindow::askSave() 104 | { 105 | if (!isModified()) 106 | return true; 107 | 108 | // 有未保存的更改 109 | int btn = QMessageBox::question(this, "记事本", "你想更改保存到 " + (fileName.isEmpty() ? "无标题" : fileName) + " 吗?", "保存(&S)", "不保存(&N)", "取消"); 110 | if (btn == 2) // 取消 111 | return false; 112 | if (btn == 0) // 保存 113 | { 114 | return on_actionSave_triggered(); 115 | } 116 | return true; 117 | } 118 | 119 | void MainWindow::updateWindowTitle() 120 | { 121 | this->setWindowTitle((isModified() ? "*" : "") + fileName + " - 记事本"); 122 | } 123 | 124 | void MainWindow::createFindDialog() 125 | { 126 | findDialog = new FindDialog(settings, this); 127 | 128 | connect(findDialog, &FindDialog::signalShow, this, [=]{ 129 | ui->actionFind_Next_N->setEnabled(true); 130 | ui->actionFind_Prev_V->setEnabled(true); 131 | }); 132 | connect(findDialog, &FindDialog::signalHide, this, [=]{ 133 | ui->actionFind_Next_N->setEnabled(false); 134 | ui->actionFind_Prev_V->setEnabled(false); 135 | }); 136 | /* connect(findDialog, &FindDialog::signalTextChanged, this, [=](const QString& text){ 137 | this->findText = text; 138 | }); */ 139 | connect(findDialog, &FindDialog::signalFindNext, this, &MainWindow::on_actionFind_Next_N_triggered); 140 | connect(findDialog, &FindDialog::signalFindPrev, this, &MainWindow::on_actionFind_Prev_V_triggered); 141 | connect(findDialog, &FindDialog::signalReplaceNext, this, [=]{ 142 | const QString& findText = findDialog->getFindText(); 143 | const QString& replaceText = findDialog->getReplaceText(); 144 | if (findText.isEmpty()) 145 | return ; 146 | 147 | // 替换 逻辑上分为:查找、选中、替换 148 | const QString& selectedText = ui->plainTextEdit->textCursor().selectedText(); 149 | if ((findDialog->isCaseSensitive() && selectedText != findText) 150 | || selectedText.toLower() != findText.toLower()) 151 | { 152 | // 如果选中的词不是findText,则查找下一个 153 | on_actionFind_Next_N_triggered(); 154 | qInfo() << "查找:" << findText; 155 | } 156 | else 157 | { 158 | // 已选中,则替换选中的 159 | QTextCursor tc = ui->plainTextEdit->textCursor(); 160 | tc.insertText(replaceText); 161 | ui->plainTextEdit->setTextCursor(tc); 162 | qInfo() << "替换:" << findText << "->" << replaceText; 163 | on_actionFind_Next_N_triggered(); // 查找下一个 164 | } 165 | }); 166 | connect(findDialog, &FindDialog::signalReplaceAll, this, [=]{ 167 | const QString& findText = findDialog->getFindText(); 168 | const QString& replaceText = findDialog->getReplaceText(); 169 | if (findText.isEmpty()) 170 | return ; 171 | qInfo() << "全部替换:" << findText << "->" << replaceText; 172 | 173 | QString content = ui->plainTextEdit->toPlainText(); 174 | QTextCursor tc = ui->plainTextEdit->textCursor(); 175 | tc.setPosition(0); 176 | tc.setPosition(content.length(), QTextCursor::KeepAnchor); 177 | content.replace(findText, replaceText); 178 | tc.insertText(content); 179 | // ui->plainTextEdit->setTextCursor(tc); // 不调用这句话,保留替换之前的位置 180 | // ui->plainTextEdit->setPlainText(content); // 这个会导致无法撤销,而且会重置光标位置到开头 181 | }); 182 | } 183 | 184 | void MainWindow::showEvent(QShowEvent *e) 185 | { 186 | this->restoreGeometry(settings.value("mainwindow/geometry").toByteArray()); 187 | this->restoreState(settings.value("mainwindow/state").toByteArray()); 188 | 189 | QMainWindow::showEvent(e); 190 | } 191 | 192 | void MainWindow::closeEvent(QCloseEvent *e) 193 | { 194 | if (!askSave()) 195 | { 196 | e->ignore(); 197 | return ; 198 | } 199 | settings.setValue("mainwindow/geometry", this->saveGeometry()); 200 | settings.setValue("mainwindow/state", this->saveState()); 201 | 202 | QMainWindow::closeEvent(e); 203 | } 204 | 205 | void MainWindow::on_plainTextEdit_textChanged() 206 | { 207 | if (fileName.isEmpty()) 208 | fileName = "无标题"; 209 | updateWindowTitle(); 210 | 211 | bool empty = ui->plainTextEdit->toPlainText().isEmpty(); 212 | ui->actionFind_F->setEnabled(!empty); 213 | ui->actionReplace_R->setEnabled(!empty); 214 | ui->actionFind_Next_N->setEnabled(!empty && findDialog && findDialog->isVisible()); 215 | ui->actionFind_Prev_V->setEnabled(!empty && findDialog && findDialog->isVisible()); 216 | } 217 | 218 | void MainWindow::on_plainTextEdit_cursorPositionChanged() 219 | { 220 | QTextCursor tc = ui->plainTextEdit->textCursor(); 221 | 222 | QTextLayout* ly = tc.block().layout(); 223 | int posInBlock = tc.position() - tc.block().position(); // 当前光标在block内的相对位置 224 | int line = ly->lineForTextPosition(posInBlock).lineNumber() + tc.block().firstLineNumber(); 225 | 226 | int col = tc.columnNumber(); // 第几列 227 | // int row = tc.blockNumber(); // 第几段,无法识别WordWrap的第几行 228 | posLabel->setText("第 " + QString::number(line + 1) + " 行,第 " + QString::number(col + 1) + " 列"); 229 | } 230 | 231 | void MainWindow::on_plainTextEdit_undoAvailable(bool b) 232 | { 233 | ui->actionUndo_U->setEnabled(b); 234 | } 235 | 236 | void MainWindow::on_plainTextEdit_selectionChanged() 237 | { 238 | bool selected = ui->plainTextEdit->textCursor().hasSelection(); 239 | ui->actionSearch_By_Bing->setEnabled(selected); 240 | ui->actionCut_T->setEnabled(selected); 241 | ui->actionCopy_C->setEnabled(selected); 242 | ui->actionDelete_L->setEnabled(selected); 243 | ui->actionReselect_Chinese->setEnabled(selected); 244 | } 245 | 246 | void MainWindow::on_actionNew_triggered() 247 | { 248 | if (!askSave()) 249 | return ; 250 | 251 | openFile(""); 252 | } 253 | 254 | void MainWindow::on_actionNew_Window_triggered() 255 | { 256 | QProcess p(this); 257 | p.startDetached(QApplication::applicationFilePath(), {"-new"}); 258 | } 259 | 260 | void MainWindow::on_actionOpen_triggered() 261 | { 262 | if (!askSave()) 263 | return ; 264 | 265 | QString recentPath = settings.value("recent/filePath").toString(); 266 | QString path = QFileDialog::getOpenFileName(this, "打开", recentPath, "*.txt"); 267 | if (path.isEmpty()) 268 | return ; 269 | 270 | openFile(path); 271 | } 272 | 273 | bool MainWindow::on_actionSave_triggered() 274 | { 275 | if (filePath.isEmpty()) // 没有路径,另存为 276 | { 277 | QString recentPath = settings.value("recent/filePath").toString(); 278 | QString path = QFileDialog::getSaveFileName(this, "另存为", recentPath, "*.txt"); 279 | if (path.isEmpty()) 280 | return false; 281 | settings.setValue("recent/filePath", path); 282 | filePath = path; 283 | fileName = QFileInfo(path).baseName(); 284 | } 285 | 286 | // 写出文件 287 | QFile file(filePath); 288 | if (!file.open(QIODevice::WriteOnly)) 289 | { 290 | qWarning() << "打开文件失败"; 291 | return false; 292 | } 293 | QTextStream ts(&file); 294 | ts.setCodec("GBK"); 295 | savedContent = ui->plainTextEdit->toPlainText(); 296 | ts << savedContent; 297 | file.close(); 298 | qInfo() << "save:" << filePath << savedContent.length(); 299 | updateWindowTitle(); 300 | return true; 301 | } 302 | 303 | bool MainWindow::on_actionSave_As_triggered() 304 | { 305 | QString temp = filePath; 306 | filePath = ""; 307 | if (!on_actionSave_triggered()) // 直接调用保存的 308 | filePath = temp; 309 | return true; 310 | } 311 | 312 | void MainWindow::on_actionExit_triggered() 313 | { 314 | if (!askSave()) 315 | return ; 316 | 317 | this->close(); 318 | } 319 | 320 | void MainWindow::on_actionUndo_U_triggered() 321 | { 322 | ui->plainTextEdit->undo(); 323 | } 324 | 325 | void MainWindow::on_actionCut_T_triggered() 326 | { 327 | ui->plainTextEdit->cut(); 328 | } 329 | 330 | void MainWindow::on_actionCopy_C_triggered() 331 | { 332 | ui->plainTextEdit->copy(); 333 | } 334 | 335 | void MainWindow::on_actionPaste_P_triggered() 336 | { 337 | ui->plainTextEdit->paste(); 338 | } 339 | 340 | void MainWindow::on_actionDelete_L_triggered() 341 | { 342 | QTextCursor tc = ui->plainTextEdit->textCursor(); 343 | int pos = tc.position(); 344 | if (pos >= ui->plainTextEdit->toPlainText().length()) 345 | return ; 346 | tc.setPosition(pos + 1, QTextCursor::MoveMode::KeepAnchor); 347 | tc.removeSelectedText(); 348 | } 349 | 350 | void MainWindow::on_actionSearch_By_Bing_triggered() 351 | { 352 | // !这里是转到UTF-8,保存到txt是保存为GBK 353 | QByteArray key = ui->plainTextEdit->textCursor().selectedText().toUtf8().toPercentEncoding(); 354 | QDesktopServices::openUrl(QUrl("https://cn.bing.com/search?q=" + key + "&form=NPCTXT")); 355 | } 356 | 357 | 358 | void MainWindow::on_actionSelect_All_A_triggered() 359 | { 360 | ui->plainTextEdit->selectAll(); 361 | } 362 | 363 | void MainWindow::on_actionTime_Date_D_triggered() 364 | { 365 | ui->plainTextEdit->insertPlainText(QDateTime::currentDateTime().toString("hh:mm yyyy/MM/dd")); 366 | } 367 | 368 | void MainWindow::on_actionWord_Wrap_W_triggered() 369 | { 370 | if (ui->plainTextEdit->wordWrapMode() == QTextOption::NoWrap) 371 | { 372 | ui->plainTextEdit->setWordWrapMode(QTextOption::WordWrap); 373 | ui->actionWord_Wrap_W->setChecked(true); 374 | settings.setValue("wordWrap", true); 375 | } 376 | else 377 | { 378 | ui->plainTextEdit->setWordWrapMode(QTextOption::NoWrap); 379 | ui->actionWord_Wrap_W->setChecked(false); 380 | settings.setValue("wordWrap", false); 381 | } 382 | } 383 | 384 | void MainWindow::on_actionFont_F_triggered() 385 | { 386 | bool ok; 387 | QFont f = QFontDialog::getFont(&ok, ui->plainTextEdit->font(), this, "字体"); 388 | if (!ok) 389 | return ; 390 | 391 | ui->plainTextEdit->setFont(f); 392 | settings.setValue("font", f.toString()); 393 | } 394 | 395 | void MainWindow::on_actionZoom_In_I_triggered() 396 | { 397 | if (zoomSize >= 500) 398 | return ; 399 | 400 | ui->plainTextEdit->zoomIn(1); 401 | zoomSize += 10; 402 | zoomLabel->setText(QString::number(zoomSize) + "%"); 403 | } 404 | 405 | void MainWindow::on_actionZoom_Out_O_triggered() 406 | { 407 | if (zoomSize <= 10) 408 | return ; 409 | 410 | ui->plainTextEdit->zoomOut(1); 411 | zoomSize -= 10; 412 | zoomLabel->setText(QString::number(zoomSize) + "%"); 413 | } 414 | 415 | void MainWindow::on_actionZoom_Default_triggered() 416 | { 417 | QString fs; 418 | if (!(fs = settings.value("font").toString()).isEmpty()) 419 | { 420 | QFont f; 421 | f.fromString(fs); 422 | ui->plainTextEdit->setFont(f); 423 | } 424 | else 425 | { 426 | ui->plainTextEdit->setFont(qApp->font()); 427 | } 428 | 429 | zoomSize = 100; 430 | zoomLabel->setText(QString::number(zoomSize) + "%"); 431 | } 432 | 433 | void MainWindow::on_actionStatus_Bar_S_triggered() 434 | { 435 | if (this->statusBar()->isHidden()) 436 | { 437 | this->statusBar()->show(); 438 | ui->actionStatus_Bar_S->setChecked(true); 439 | settings.setValue("statusBar", true); 440 | } 441 | else 442 | { 443 | this->statusBar()->hide(); 444 | ui->actionStatus_Bar_S->setChecked(false); 445 | settings.setValue("statusBar", false); 446 | } 447 | } 448 | 449 | void MainWindow::on_actionAbout_A_triggered() 450 | { 451 | QMessageBox::about(this, "关于", "高仿 Windows 记事本的 Qt 实现方案"); 452 | } 453 | 454 | void MainWindow::on_actionFind_F_triggered() 455 | { 456 | if (!findDialog) 457 | { 458 | createFindDialog(); 459 | } 460 | findDialog->openFind(false); 461 | } 462 | 463 | void MainWindow::on_actionFind_Next_N_triggered() 464 | { 465 | const QString& text = findDialog->getFindText(); 466 | if (text.isEmpty()) 467 | return ; 468 | 469 | QTextDocument::FindFlags flags; 470 | if (findDialog->isCaseSensitive()) 471 | flags |= QTextDocument::FindCaseSensitively; 472 | bool rst = ui->plainTextEdit->find(text, flags); 473 | if (!rst && findDialog->isLoop() 474 | && ui->plainTextEdit->toPlainText().contains(text)) // 没找到,尝试从头开始 475 | { 476 | qInfo() << "从开头查找"; 477 | QTextCursor tc = ui->plainTextEdit->textCursor(); 478 | tc.setPosition(0); 479 | ui->plainTextEdit->setTextCursor(tc); 480 | on_actionFind_Next_N_triggered(); 481 | } 482 | } 483 | 484 | void MainWindow::on_actionFind_Prev_V_triggered() 485 | { 486 | const QString& text = findDialog->getFindText(); 487 | if (text.isEmpty()) 488 | return ; 489 | 490 | QTextDocument::FindFlags flags = QTextDocument::FindBackward; 491 | if (findDialog->isCaseSensitive()) 492 | flags |= QTextDocument::FindCaseSensitively; 493 | bool rst = ui->plainTextEdit->find(text, flags); 494 | if (!rst && findDialog->isLoop() 495 | && ui->plainTextEdit->toPlainText().contains(text)) 496 | { 497 | qInfo() << "从末尾查找"; 498 | QTextCursor tc = ui->plainTextEdit->textCursor(); 499 | tc.setPosition(ui->plainTextEdit->toPlainText().length()); 500 | ui->plainTextEdit->setTextCursor(tc); 501 | on_actionFind_Prev_V_triggered(); 502 | } 503 | } 504 | 505 | void MainWindow::on_actionReplace_R_triggered() 506 | { 507 | if (!findDialog) 508 | { 509 | createFindDialog(); 510 | } 511 | findDialog->openFind(true); 512 | } 513 | 514 | void MainWindow::on_actionGoto_G_triggered() 515 | { 516 | // TODO:转到 517 | // 我的记事本这个选项一直是灰色的 518 | } 519 | 520 | void MainWindow::on_actionHelp_triggered() 521 | { 522 | QDesktopServices::openUrl(QUrl("https://github.com/iwxyi/Qt-notepad")); 523 | } 524 | 525 | void MainWindow::on_actionFeedback_F_triggered() 526 | { 527 | QDesktopServices::openUrl(QUrl("https://github.com/iwxyi/Qt-notepad/issues")); 528 | } 529 | 530 | void MainWindow::on_plainTextEdit_customContextMenuRequested(const QPoint&) 531 | { 532 | QMenu* menu = new QMenu(); 533 | 534 | QMenu* insertUnicodeControlCharsMenu = new QMenu("插入 Unicode 控制字符(&I)", menu); 535 | QList> unicodeControlChars{ 536 | {"LRM", "&Left-to-right mark"}, 537 | {"RLM", "&Right-to-left mark"}, 538 | {"ZWJ", "Zero width joiner"}, 539 | {"ZWNJ", "Zero width &non-joiner"}, 540 | {"LRE", "Start of left-to-right &embedding"}, 541 | {"RLE", "Start of right-to-left e&mbedding"}, 542 | {"LRO", "Start of left-to-right &override"}, 543 | {"RLO", "Start of right-to-left o&verride"}, 544 | {"PDF", "&Pop directional formatting"}, 545 | {"NADS", "N&ational digit shapes substitution"}, 546 | {"NODS", "Nominal (European) &digit shapes"}, 547 | {"ASS", "Activate &symmetric swapping"}, 548 | {"ISS", "Inhibit s&ymmetric swapping"}, 549 | {"AAFS", "Activate Arabic &form shaping"}, 550 | {"IAFS", "Inhibit Arabic form s&haping"}, 551 | {"RS", "Record Separator (&Block separator)"}, 552 | {"US", "Unit Separator (&Segment separator)"} 553 | }; 554 | for (auto p: unicodeControlChars) 555 | { 556 | QAction* action = new QAction(p.first, insertUnicodeControlCharsMenu); 557 | action->setToolTip(p.second); 558 | // TODO: 把提示也显示出来 559 | connect(action, &QAction::triggered, ui->plainTextEdit, [=]{ 560 | // TODO: 插入 Unicode 控制字符 561 | // ui->plainTextEdit->insertPlainText("\u202c"); 562 | }); 563 | insertUnicodeControlCharsMenu->addAction(action); 564 | } 565 | 566 | menu->addAction(ui->actionUndo_U); 567 | menu->addSeparator(); 568 | menu->addAction(ui->actionCut_T); 569 | menu->addAction(ui->actionCopy_C); 570 | menu->addAction(ui->actionPaste_P); 571 | menu->addAction(ui->actionDelete_L); 572 | menu->addSeparator(); 573 | menu->addAction(ui->actionSelect_All_A); 574 | menu->addSeparator(); 575 | menu->addAction(ui->actionRead_Direction); 576 | menu->addAction(ui->actionShow_Unicode_Control_Chars); 577 | menu->addMenu(insertUnicodeControlCharsMenu); 578 | menu->addSeparator(); 579 | menu->addAction(ui->actionRead_Mode); 580 | menu->addAction(ui->actionReselect_Chinese); 581 | menu->addSeparator(); 582 | menu->addAction(ui->actionSearch_By_Bing); 583 | 584 | menu->exec(QCursor::pos()); 585 | menu->deleteLater(); 586 | } 587 | 588 | void MainWindow::on_actionRead_Direction_triggered() 589 | { 590 | auto direction = ui->actionRead_Direction->isChecked() ? Qt::RightToLeft : Qt::LeftToRight; 591 | ui->plainTextEdit->setLayoutDirection(direction); 592 | } 593 | 594 | void MainWindow::on_actionRead_Mode_triggered() 595 | { 596 | if (ui->plainTextEdit->isReadOnly()) 597 | { 598 | ui->plainTextEdit->setReadOnly(false); 599 | ui->actionRead_Mode->setText("关闭输入法(&L)"); 600 | } 601 | else 602 | { 603 | ui->plainTextEdit->setReadOnly(true); 604 | ui->actionRead_Mode->setText("打开输入法(&O)"); 605 | } 606 | } 607 | 608 | void MainWindow::on_actionShow_Unicode_Control_Chars_triggered() 609 | { 610 | // TODO: 显示 Unicode 控制字符 611 | } 612 | 613 | void MainWindow::on_actionReselect_Chinese_triggered() 614 | { 615 | // TODO: 汉字重选 616 | } 617 | 618 | void MainWindow::on_actionPrefrence_triggered() 619 | { 620 | // TODO: 页面设置 621 | } 622 | 623 | void MainWindow::on_actionPrint_triggered() 624 | { 625 | // TODO: 打印 626 | } 627 | -------------------------------------------------------------------------------- /mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "finddialog.h" 8 | 9 | QT_BEGIN_NAMESPACE 10 | namespace Ui { class MainWindow; } 11 | QT_END_NAMESPACE 12 | 13 | class MainWindow : public QMainWindow 14 | { 15 | Q_OBJECT 16 | 17 | public: 18 | MainWindow(QWidget *parent = nullptr); 19 | ~MainWindow() override; 20 | 21 | private slots: 22 | void on_plainTextEdit_textChanged(); 23 | 24 | void on_plainTextEdit_undoAvailable(bool b); 25 | 26 | void on_plainTextEdit_selectionChanged(); 27 | 28 | void on_plainTextEdit_cursorPositionChanged(); 29 | 30 | void on_actionNew_triggered(); 31 | 32 | void on_actionNew_Window_triggered(); 33 | 34 | void on_actionOpen_triggered(); 35 | 36 | bool on_actionSave_triggered(); 37 | 38 | bool on_actionSave_As_triggered(); 39 | 40 | void on_actionExit_triggered(); 41 | 42 | void on_actionUndo_U_triggered(); 43 | 44 | void on_actionCut_T_triggered(); 45 | 46 | void on_actionCopy_C_triggered(); 47 | 48 | void on_actionPaste_P_triggered(); 49 | 50 | void on_actionDelete_L_triggered(); 51 | 52 | void on_actionSearch_By_Bing_triggered(); 53 | 54 | void on_actionSelect_All_A_triggered(); 55 | 56 | void on_actionTime_Date_D_triggered(); 57 | 58 | void on_actionWord_Wrap_W_triggered(); 59 | 60 | void on_actionFont_F_triggered(); 61 | 62 | void on_actionZoom_In_I_triggered(); 63 | 64 | void on_actionZoom_Out_O_triggered(); 65 | 66 | void on_actionZoom_Default_triggered(); 67 | 68 | void on_actionStatus_Bar_S_triggered(); 69 | 70 | void on_actionAbout_A_triggered(); 71 | 72 | void on_actionFind_F_triggered(); 73 | 74 | void on_actionFind_Next_N_triggered(); 75 | 76 | void on_actionFind_Prev_V_triggered(); 77 | 78 | void on_actionReplace_R_triggered(); 79 | 80 | void on_actionGoto_G_triggered(); 81 | 82 | void on_actionHelp_triggered(); 83 | 84 | void on_actionFeedback_F_triggered(); 85 | 86 | void on_plainTextEdit_customContextMenuRequested(const QPoint &); 87 | 88 | void on_actionRead_Direction_triggered(); 89 | 90 | void on_actionRead_Mode_triggered(); 91 | 92 | void on_actionShow_Unicode_Control_Chars_triggered(); 93 | 94 | void on_actionReselect_Chinese_triggered(); 95 | 96 | void on_actionPrefrence_triggered(); 97 | 98 | void on_actionPrint_triggered(); 99 | 100 | public: 101 | void openFile(QString path); 102 | bool isModified() const; 103 | 104 | private: 105 | bool askSave(); 106 | void updateWindowTitle(); 107 | void createFindDialog(); 108 | 109 | protected: 110 | void showEvent(QShowEvent* e) override; 111 | void closeEvent(QCloseEvent* e) override; 112 | 113 | private: 114 | Ui::MainWindow *ui; 115 | QSettings settings; 116 | 117 | QString filePath; 118 | QString fileName; 119 | QString savedContent; 120 | int zoomSize = 100; 121 | 122 | QLabel* posLabel; 123 | QLabel* zoomLabel; 124 | QLabel* lineLabel; 125 | QLabel* codecLabel; 126 | 127 | FindDialog* findDialog = nullptr; 128 | // QString findText; 129 | }; 130 | #endif // MAINWINDOW_H 131 | -------------------------------------------------------------------------------- /mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 602 10 | 481 11 | 12 | 13 | 14 | 无标题 - 记事本 15 | 16 | 17 | 18 | 19 | 0 20 | 21 | 22 | 0 23 | 24 | 25 | 0 26 | 27 | 28 | 0 29 | 30 | 31 | 0 32 | 33 | 34 | 35 | 36 | Qt::CustomContextMenu 37 | 38 | 39 | QPlainTextEdit 40 | { 41 | border: none; 42 | } 43 | 44 | 45 | Qt::ScrollBarAlwaysOn 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 0 55 | 0 56 | 602 57 | 21 58 | 59 | 60 | 61 | 62 | 文件(&F) 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 编辑(&E) 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 格式(&O) 99 | 100 | 101 | 102 | 103 | 104 | 105 | 查看(&V) 106 | 107 | 108 | 109 | 缩放(&Z) 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 帮助(&H) 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 新建(&N) 137 | 138 | 139 | Ctrl+N 140 | 141 | 142 | 143 | 144 | 新窗口(&W) 145 | 146 | 147 | Ctrl+Shift+N 148 | 149 | 150 | 151 | 152 | 打开(&O)... 153 | 154 | 155 | Ctrl+O 156 | 157 | 158 | 159 | 160 | 保存(&S) 161 | 162 | 163 | Ctrl+S 164 | 165 | 166 | 167 | 168 | 另存为(&A) 169 | 170 | 171 | Ctrl+Shift+S 172 | 173 | 174 | 175 | 176 | true 177 | 178 | 179 | 页面设置(&U) 180 | 181 | 182 | 183 | 184 | true 185 | 186 | 187 | 打印(&P) 188 | 189 | 190 | Ctrl+P 191 | 192 | 193 | 194 | 195 | 退出(&X) 196 | 197 | 198 | 199 | 200 | false 201 | 202 | 203 | 撤销(&U) 204 | 205 | 206 | Ctrl+Z 207 | 208 | 209 | 210 | 211 | false 212 | 213 | 214 | 剪切(&T) 215 | 216 | 217 | Ctrl+X 218 | 219 | 220 | 221 | 222 | false 223 | 224 | 225 | 复制(&C) 226 | 227 | 228 | Ctrl+C 229 | 230 | 231 | 232 | 233 | 粘贴(&P) 234 | 235 | 236 | Ctrl+V 237 | 238 | 239 | 240 | 241 | false 242 | 243 | 244 | 删除(&L) 245 | 246 | 247 | Del 248 | 249 | 250 | 251 | 252 | false 253 | 254 | 255 | 使用 Bing 搜索... 256 | 257 | 258 | Ctrl+E 259 | 260 | 261 | 262 | 263 | false 264 | 265 | 266 | 查找(&F) 267 | 268 | 269 | Ctrl+F 270 | 271 | 272 | 273 | 274 | false 275 | 276 | 277 | 查找下一个(&N) 278 | 279 | 280 | F3 281 | 282 | 283 | 284 | 285 | false 286 | 287 | 288 | 查找上一个(&V) 289 | 290 | 291 | Shift+F3 292 | 293 | 294 | 295 | 296 | false 297 | 298 | 299 | 替换(&R) 300 | 301 | 302 | Ctrl+H 303 | 304 | 305 | 306 | 307 | false 308 | 309 | 310 | 转到(&G) 311 | 312 | 313 | Ctrl+G 314 | 315 | 316 | 317 | 318 | 全选(&A) 319 | 320 | 321 | Ctrl+A 322 | 323 | 324 | 325 | 326 | 时间/日期(&D) 327 | 328 | 329 | F5 330 | 331 | 332 | 333 | 334 | 字体(&F) 335 | 336 | 337 | 338 | 339 | true 340 | 341 | 342 | true 343 | 344 | 345 | 状态栏(&S) 346 | 347 | 348 | 349 | 350 | 放大(&I) 351 | 352 | 353 | Ctrl+= 354 | 355 | 356 | 357 | 358 | 缩小(&O) 359 | 360 | 361 | Ctrl+- 362 | 363 | 364 | 365 | 366 | 恢复默认缩放 367 | 368 | 369 | Ctrl+0 370 | 371 | 372 | 373 | 374 | true 375 | 376 | 377 | 查看帮助(&H) 378 | 379 | 380 | 381 | 382 | true 383 | 384 | 385 | 发送反馈(&F) 386 | 387 | 388 | 389 | 390 | 关于记事本(&A) 391 | 392 | 393 | 394 | 395 | true 396 | 397 | 398 | true 399 | 400 | 401 | 自动换行(&W) 402 | 403 | 404 | 405 | 406 | true 407 | 408 | 409 | 从右到左的阅读顺序(&R) 410 | 411 | 412 | 413 | 414 | false 415 | 416 | 417 | 关闭输入法(&L) 418 | 419 | 420 | 421 | 422 | true 423 | 424 | 425 | 显示 Unicode 控制字符 426 | 427 | 428 | 429 | 430 | false 431 | 432 | 433 | 汉字重选(&R) 434 | 435 | 436 | 437 | 438 | 439 | 440 | -------------------------------------------------------------------------------- /screenshots/p1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/Qt-notepad/8d4fc1d62ed255cc1965936c438e5eaf3d3b1e25/screenshots/p1.png -------------------------------------------------------------------------------- /screenshots/p2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/Qt-notepad/8d4fc1d62ed255cc1965936c438e5eaf3d3b1e25/screenshots/p2.png -------------------------------------------------------------------------------- /screenshots/p3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/Qt-notepad/8d4fc1d62ed255cc1965936c438e5eaf3d3b1e25/screenshots/p3.png -------------------------------------------------------------------------------- /screenshots/p4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/Qt-notepad/8d4fc1d62ed255cc1965936c438e5eaf3d3b1e25/screenshots/p4.png -------------------------------------------------------------------------------- /screenshots/p5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/Qt-notepad/8d4fc1d62ed255cc1965936c438e5eaf3d3b1e25/screenshots/p5.png --------------------------------------------------------------------------------