一、演示效果
二、核心代码
#include <QMessageBox>
#include <QFileDialog>
#include <QDebug>
#include <QProcess>
#include <QDesktopServices>
#include <QDateTime>
#include <QFontDialog>
#include <QTextBlock>
#include <QFileIconProvider>
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow),
settings("MyNotepad")
{
ui->setupUi(this);
if (!settings.value("wordWrap", true).toBool())
{
ui->actionWord_Wrap_W->setChecked(false);
ui->plainTextEdit->setWordWrapMode(QTextOption::NoWrap);
}
if (!settings.value("statusBar", true).toBool())
{
this->statusBar()->hide();
ui->actionStatus_Bar_S->setChecked(false);
}
QString fs;
if (!(fs = settings.value("font").toString()).isEmpty())
{
QFont f;
f.fromString(fs);
ui->plainTextEdit->setFont(f);
}
posLabel = new QLabel(u8"第 1 行,第 1 列", this);
zoomLabel = new QLabel(u8"100%", this);
lineLabel = new QLabel(u8"Windows (CRLF)", this);
codecLabel = new QLabel(u8"GBK", this);
ui->statusbar->addPermanentWidget(new QLabel(this), 6);
ui->statusbar->addPermanentWidget(posLabel, 3);
ui->statusbar->addPermanentWidget(zoomLabel, 1);
ui->statusbar->addPermanentWidget(lineLabel, 3);
ui->statusbar->addPermanentWidget(codecLabel, 1);
QFileIconProvider ip;
QIcon icon = ip.icon(QFileInfo("C:\\Windows\\System32\\notepad.exe"));
qApp->setWindowIcon(icon);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::openFile(QString path)
{
filePath = path;
if (path.isEmpty())
{
savedContent = "";
fileName = u8"无标题";
}
else
{
QFile file(path);
if (!file.exists())
{
qWarning() << u8"文件不存在";
return ;
}
fileName = QFileInfo(path).baseName();
if (!file.open(QIODevice::ReadOnly))
{
qWarning() << u8"打开文件失败";
return ;
}
savedContent = QString::fromLocal8Bit(file.readAll());
}
ui->plainTextEdit->setPlainText(savedContent);
updateWindowTitle();
}
bool MainWindow::isModified() const
{
return ui->plainTextEdit->toPlainText() != savedContent;
}
bool MainWindow::askSave()
{
if (!isModified())
return true;
int btn = QMessageBox::question(this, u8"记事本", u8"你想更改保存到 " + (fileName.isEmpty() ? u8"无标题" : fileName) + u8" 吗?", u8"保存(&S)", u8"不保存(&N)", u8"取消");
if (btn == 2)
return false;
if (btn == 0)
{
return on_actionSave_triggered();
}
return true;
}
void MainWindow::updateWindowTitle()
{
this->setWindowTitle((isModified() ? "*" : "") + fileName + u8" - 记事本");
}
void MainWindow::createFindDialog()
{
findDialog = new FindDialog(settings, this);
connect(findDialog, &FindDialog::signalShow, this, [=]{
ui->actionFind_Next_N->setEnabled(true);
ui->actionFind_Prev_V->setEnabled(true);
});
connect(findDialog, &FindDialog::signalHide, this, [=]{
ui->actionFind_Next_N->setEnabled(false);
ui->actionFind_Prev_V->setEnabled(false);
});
connect(findDialog, &FindDialog::signalFindNext, this, &MainWindow::on_actionFind_Next_N_triggered);
connect(findDialog, &FindDialog::signalFindPrev, this, &MainWindow::on_actionFind_Prev_V_triggered);
connect(findDialog, &FindDialog::signalReplaceNext, this, [=]{
const QString& findText = findDialog->getFindText();
const QString& replaceText = findDialog->getReplaceText();
if (findText.isEmpty())
return ;
const QString& selectedText = ui->plainTextEdit->textCursor().selectedText();
if ((findDialog->isCaseSensitive() && selectedText != findText)
|| selectedText.toLower() != findText.toLower())
{
on_actionFind_Next_N_triggered();
qInfo() << u8"查找:" << findText;
}
else
{
QTextCursor tc = ui->plainTextEdit->textCursor();
tc.insertText(replaceText);
ui->plainTextEdit->setTextCursor(tc);
qInfo() << u8"替换:" << findText << "->" << replaceText;
on_actionFind_Next_N_triggered();
}
});
connect(findDialog, &FindDialog::signalReplaceAll, this, [=]{
const QString& findText = findDialog->getFindText();
const QString& replaceText = findDialog->getReplaceText();
if (findText.isEmpty())
return ;
qInfo() << u8"全部替换:" << findText << "->" << replaceText;
QString content = ui->plainTextEdit->toPlainText();
QTextCursor tc = ui->plainTextEdit->textCursor();
tc.setPosition(0);
tc.setPosition(content.length(), QTextCursor::KeepAnchor);
content.replace(findText, replaceText);
tc.insertText(content);
});
}
void MainWindow::showEvent(QShowEvent *e)
{
this->restoreGeometry(settings.value("mainwindow/geometry").toByteArray());
this->restoreState(settings.value("mainwindow/state").toByteArray());
QMainWindow::showEvent(e);
}
void MainWindow::closeEvent(QCloseEvent *e)
{
if (!askSave())
{
e->ignore();
return ;
}
settings.setValue("mainwindow/geometry", this->saveGeometry());
settings.setValue("mainwindow/state", this->saveState());
QMainWindow::closeEvent(e);
}
void MainWindow::on_plainTextEdit_textChanged()
{
if (fileName.isEmpty())
fileName = u8"无标题";
updateWindowTitle();
bool empty = ui->plainTextEdit->toPlainText().isEmpty();
ui->actionFind_F->setEnabled(!empty);
ui->actionReplace_R->setEnabled(!empty);
ui->actionFind_Next_N->setEnabled(!empty && findDialog && findDialog->isVisible());
ui->actionFind_Prev_V->setEnabled(!empty && findDialog && findDialog->isVisible());
}
void MainWindow::on_plainTextEdit_cursorPositionChanged()
{
QTextCursor tc = ui->plainTextEdit->textCursor();
QTextLayout* ly = tc.block().layout();
int posInBlock = tc.position() - tc.block().position();
int line = ly->lineForTextPosition(posInBlock).lineNumber() + tc.block().firstLineNumber();
int col = tc.columnNumber();
posLabel->setText(u8"第 " + QString::number(line + 1) + u8" 行,第 " + QString::number(col + 1) + u8" 列");
}
void MainWindow::on_plainTextEdit_undoAvailable(bool b)
{
ui->actionUndo_U->setEnabled(b);
}
void MainWindow::on_plainTextEdit_selectionChanged()
{
bool selected = ui->plainTextEdit->textCursor().hasSelection();
ui->actionSearch_By_Bing->setEnabled(selected);
ui->actionCut_T->setEnabled(selected);
ui->actionCopy_C->setEnabled(selected);
ui->actionDelete_L->setEnabled(selected);
ui->actionReselect_Chinese->setEnabled(selected);
}
void MainWindow::on_actionNew_triggered()
{
if (!askSave())
return ;
openFile("");
}
void MainWindow::on_actionNew_Window_triggered()
{
QProcess p(this);
p.startDetached(QApplication::applicationFilePath(), {"-new"});
}
void MainWindow::on_actionOpen_triggered()
{
if (!askSave())
return ;
QString recentPath = settings.value("recent/filePath").toString();
QString path = QFileDialog::getOpenFileName(this, u8"打开", recentPath, "*.txt");
if (path.isEmpty())
return ;
openFile(path);
}
bool MainWindow::on_actionSave_triggered()
{
if (filePath.isEmpty())
{
QString recentPath = settings.value("recent/filePath").toString();
QString path = QFileDialog::getSaveFileName(this, u8"另存为", recentPath, "*.txt");
if (path.isEmpty())
return false;
settings.setValue("recent/filePath", path);
filePath = path;
fileName = QFileInfo(path).baseName();
}
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly))
{
qWarning() << "打开文件失败";
return false;
}
QTextStream ts(&file);
ts.setCodec("GBK");
savedContent = ui->plainTextEdit->toPlainText();
ts << savedContent;
file.close();
qInfo() << "save:" << filePath << savedContent.length();
updateWindowTitle();
return true;
}
bool MainWindow::on_actionSave_As_triggered()
{
QString temp = filePath;
filePath = "";
if (!on_actionSave_triggered())
filePath = temp;
return true;
}
void MainWindow::on_actionExit_triggered()
{
if (!askSave())
return ;
this->close();
}
void MainWindow::on_actionUndo_U_triggered()
{
ui->plainTextEdit->undo();
}
void MainWindow::on_actionCut_T_triggered()
{
ui->plainTextEdit->cut();
}
void MainWindow::on_actionCopy_C_triggered()
{
ui->plainTextEdit->copy();
}
void MainWindow::on_actionPaste_P_triggered()
{
ui->plainTextEdit->paste();
}
void MainWindow::on_actionDelete_L_triggered()
{
QTextCursor tc = ui->plainTextEdit->textCursor();
int pos = tc.position();
if (pos >= ui->plainTextEdit->toPlainText().length())
return ;
tc.setPosition(pos + 1, QTextCursor::MoveMode::KeepAnchor);
tc.removeSelectedText();
}
void MainWindow::on_actionSearch_By_Bing_triggered()
{
QByteArray key = ui->plainTextEdit->textCursor().selectedText().toUtf8().toPercentEncoding();
QDesktopServices::openUrl(QUrl("https://cn.bing.com/search?q=" + key + "&form=NPCTXT"));
}
void MainWindow::on_actionSelect_All_A_triggered()
{
ui->plainTextEdit->selectAll();
}
void MainWindow::on_actionTime_Date_D_triggered()
{
ui->plainTextEdit->insertPlainText(QDateTime::currentDateTime().toString("hh:mm yyyy/MM/dd"));
}
void MainWindow::on_actionWord_Wrap_W_triggered()
{
if (ui->plainTextEdit->wordWrapMode() == QTextOption::NoWrap)
{
ui->plainTextEdit->setWordWrapMode(QTextOption::WordWrap);
ui->actionWord_Wrap_W->setChecked(true);
settings.setValue("wordWrap", true);
}
else
{
ui->plainTextEdit->setWordWrapMode(QTextOption::NoWrap);
ui->actionWord_Wrap_W->setChecked(false);
settings.setValue("wordWrap", false);
}
}
void MainWindow::on_actionFont_F_triggered()
{
bool ok;
QFont f = QFontDialog::getFont(&ok, ui->plainTextEdit->font(), this, u8"字体");
if (!ok)
return ;
ui->plainTextEdit->setFont(f);
settings.setValue("font", f.toString());
}
void MainWindow::on_actionZoom_In_I_triggered()
{
if (zoomSize >= 500)
return ;
ui->plainTextEdit->zoomIn(1);
zoomSize += 10;
zoomLabel->setText(QString::number(zoomSize) + "%");
}
void MainWindow::on_actionZoom_Out_O_triggered()
{
if (zoomSize <= 10)
return ;
ui->plainTextEdit->zoomOut(1);
zoomSize -= 10;
zoomLabel->setText(QString::number(zoomSize) + "%");
}
void MainWindow::on_actionZoom_Default_triggered()
{
QString fs;
if (!(fs = settings.value("font").toString()).isEmpty())
{
QFont f;
f.fromString(fs);
ui->plainTextEdit->setFont(f);
}
else
{
ui->plainTextEdit->setFont(qApp->font());
}
zoomSize = 100;
zoomLabel->setText(QString::number(zoomSize) + "%");
}
void MainWindow::on_actionStatus_Bar_S_triggered()
{
if (this->statusBar()->isHidden())
{
this->statusBar()->show();
ui->actionStatus_Bar_S->setChecked(true);
settings.setValue("statusBar", true);
}
else
{
this->statusBar()->hide();
ui->actionStatus_Bar_S->setChecked(false);
settings.setValue("statusBar", false);
}
}
void MainWindow::on_actionAbout_A_triggered()
{
QMessageBox::about(this, u8"关于", u8"高仿 Windows 记事本的 Qt 实现方案");
}
void MainWindow::on_actionFind_F_triggered()
{
if (!findDialog)
{
createFindDialog();
}
findDialog->openFind(false);
}
void MainWindow::on_actionFind_Next_N_triggered()
{
const QString& text = findDialog->getFindText();
if (text.isEmpty())
return ;
QTextDocument::FindFlags flags;
if (findDialog->isCaseSensitive())
flags |= QTextDocument::FindCaseSensitively;
bool rst = ui->plainTextEdit->find(text, flags);
if (!rst && findDialog->isLoop()
&& ui->plainTextEdit->toPlainText().contains(text))
{
qInfo() << "从开头查找";
QTextCursor tc = ui->plainTextEdit->textCursor();
tc.setPosition(0);
ui->plainTextEdit->setTextCursor(tc);
on_actionFind_Next_N_triggered();
}
}
void MainWindow::on_actionFind_Prev_V_triggered()
{
const QString& text = findDialog->getFindText();
if (text.isEmpty())
return ;
QTextDocument::FindFlags flags = QTextDocument::FindBackward;
if (findDialog->isCaseSensitive())
flags |= QTextDocument::FindCaseSensitively;
bool rst = ui->plainTextEdit->find(text, flags);
if (!rst && findDialog->isLoop()
&& ui->plainTextEdit->toPlainText().contains(text))
{
qInfo() << "从末尾查找";
QTextCursor tc = ui->plainTextEdit->textCursor();
tc.setPosition(ui->plainTextEdit->toPlainText().length());
ui->plainTextEdit->setTextCursor(tc);
on_actionFind_Prev_V_triggered();
}
}
void MainWindow::on_actionReplace_R_triggered()
{
if (!findDialog)
{
createFindDialog();
}
findDialog->openFind(true);
}
void MainWindow::on_actionGoto_G_triggered()
{
}
void MainWindow::on_actionHelp_triggered()
{
QDesktopServices::openUrl(QUrl("https://github.com/iwxyi/Qt-notepad"));
}
void MainWindow::on_actionFeedback_F_triggered()
{
QDesktopServices::openUrl(QUrl("https://github.com/iwxyi/Qt-notepad/issues"));
}
void MainWindow::on_plainTextEdit_customContextMenuRequested(const QPoint&)
{
QMenu* menu = new QMenu();
QMenu* insertUnicodeControlCharsMenu = new QMenu("插入 Unicode 控制字符(&I)", menu);
QList<QPair<QString, QString>> unicodeControlChars{
{"LRM", "&Left-to-right mark"},
{"RLM", "&Right-to-left mark"},
{"ZWJ", "Zero width joiner"},
{"ZWNJ", "Zero width &non-joiner"},
{"LRE", "Start of left-to-right &embedding"},
{"RLE", "Start of right-to-left e&mbedding"},
{"LRO", "Start of left-to-right &override"},
{"RLO", "Start of right-to-left o&verride"},
{"PDF", "&Pop directional formatting"},
{"NADS", "N&ational digit shapes substitution"},
{"NODS", "Nominal (European) &digit shapes"},
{"ASS", "Activate &symmetric swapping"},
{"ISS", "Inhibit s&ymmetric swapping"},
{"AAFS", "Activate Arabic &form shaping"},
{"IAFS", "Inhibit Arabic form s&haping"},
{"RS", "Record Separator (&Block separator)"},
{"US", "Unit Separator (&Segment separator)"}
};
for (auto p: unicodeControlChars)
{
QAction* action = new QAction(p.first, insertUnicodeControlCharsMenu);
action->setToolTip(p.second);
connect(action, &QAction::triggered, ui->plainTextEdit, [=]{
});
insertUnicodeControlCharsMenu->addAction(action);
}
menu->addAction(ui->actionUndo_U);
menu->addSeparator();
menu->addAction(ui->actionCut_T);
menu->addAction(ui->actionCopy_C);
menu->addAction(ui->actionPaste_P);
menu->addAction(ui->actionDelete_L);
menu->addSeparator();
menu->addAction(ui->actionSelect_All_A);
menu->addSeparator();
menu->addAction(ui->actionRead_Direction);
menu->addAction(ui->actionShow_Unicode_Control_Chars);
menu->addMenu(insertUnicodeControlCharsMenu);
menu->addSeparator();
menu->addAction(ui->actionRead_Mode);
menu->addAction(ui->actionReselect_Chinese);
menu->addSeparator();
menu->addAction(ui->actionSearch_By_Bing);
menu->exec(QCursor::pos());
menu->deleteLater();
}
void MainWindow::on_actionRead_Direction_triggered()
{
auto direction = ui->actionRead_Direction->isChecked() ? Qt::RightToLeft : Qt::LeftToRight;
ui->plainTextEdit->setLayoutDirection(direction);
}
void MainWindow::on_actionRead_Mode_triggered()
{
if (ui->plainTextEdit->isReadOnly())
{
ui->plainTextEdit->setReadOnly(false);
ui->actionRead_Mode->setText(u8"关闭输入法(&L)");
}
else
{
ui->plainTextEdit->setReadOnly(true);
ui->actionRead_Mode->setText(u8"打开输入法(&O)");
}
}
void MainWindow::on_actionShow_Unicode_Control_Chars_triggered()
{
}
void MainWindow::on_actionReselect_Chinese_triggered()
{
}
void MainWindow::on_actionPrefrence_triggered()
{
}
void MainWindow::on_actionPrint_triggered()
{
}
#include "finddialog.h"
#include "ui_finddialog.h"
FindDialog::FindDialog(QSettings &settings, QWidget *parent) :
QDialog(parent),
ui(new Ui::FindDialog),
settings(settings)
{
ui->setupUi(this);
ui->findEdit->setText(settings.value("find/findText").toString());
ui->replaceEdit->setText(settings.value("find/replaceText").toString());
ui->caseSensitiveCheck->setChecked(settings.value("find/caseSensitive").toBool());
ui->loopCheck->setChecked(settings.value("find/loop").toBool());
if (!settings.value("find/down", true).toBool())
ui->upRadio->setChecked(true);
}
FindDialog::~FindDialog()
{
delete ui;
}
void FindDialog::openFind(bool replace)
{
ui->label_3->setVisible(replace);
ui->replaceEdit->setVisible(replace);
ui->replaceButton->setVisible(replace);
ui->replaceAllButton->setVisible(replace);
ui->groupBox->setVisible(!replace);
QDialog::show();
ui->findEdit->setFocus();
ui->findEdit->selectAll();
this->adjustSize();
if (!replace)
setWindowTitle(u8"查找");
else
setWindowTitle(u8"替换");
}
const QString FindDialog::getFindText() const
{
return ui->findEdit->text();
}
const QString FindDialog::getReplaceText() const
{
return ui->replaceEdit->text();
}
bool FindDialog::isCaseSensitive() const
{
return ui->caseSensitiveCheck->isChecked();
}
bool FindDialog::isLoop() const
{
return ui->loopCheck->isChecked();
}
void FindDialog::on_findNextButton_clicked()
{
if (ui->upRadio->isChecked())
emit signalFindPrev();
else
emit signalFindNext();
settings.setValue("find/findText", ui->findEdit->text());
}
void FindDialog::on_replaceButton_clicked()
{
emit signalReplaceNext();
settings.setValue("find/replaceText", ui->replaceEdit->text());
}
void FindDialog::on_replaceAllButton_clicked()
{
emit signalReplaceAll();
settings.setValue("find/replaceText", ui->replaceEdit->text());
}
void FindDialog::on_cancelButton_clicked()
{
this->close();
}
void FindDialog::on_caseSensitiveCheck_clicked()
{
settings.setValue("find/caseSensitive", ui->caseSensitiveCheck->isChecked());
}
void FindDialog::on_loopCheck_clicked()
{
settings.setValue("find/loop", ui->loopCheck->isChecked());
}
void FindDialog::on_upRadio_clicked()
{
settings.setValue("find/down", false);
}
void FindDialog::on_downRadio_clicked()
{
settings.setValue("find/down", true);
}
void FindDialog::showEvent(QShowEvent *event)
{
emit signalShow();
adjustSize();
QWidget::showEvent(event);
}
void FindDialog::hideEvent(QHideEvent *event)
{
emit signalHide();
QWidget::hideEvent(event);
}
void FindDialog::on_findEdit_returnPressed()
{
on_findNextButton_clicked();
}
void FindDialog::on_replaceEdit_returnPressed()
{
on_replaceButton_clicked();
}
三、下载连接
https://download.csdn.net/download/u013083044/89660580