CustomDocument

develop
Sunyuxuan 1 month ago
parent 55c68d06de
commit 9a666c1b07

@ -11,26 +11,37 @@ class CustomDocument : public QTextEdit
public:
CustomDocument(QWidget *parent = nullptr);
void setDocumentPadding(int left, int top, int right, int bottom);
//初始化文档边距
void setDocumentPadding(int left, int top, int right, int bottom);// 设置文档边距
//主要是处理按键和鼠标同时按下的鼠标的变化
bool eventFilter(QObject *obj, QEvent *event);
//分为本地文件、文件夹、网页文件和暂存文件
bool openLinkAtCursorPosition();
//该函数的鲁棒性好处理链接长度时很谨慎防止C++常见的溢出问题
QString getMarkdownUrlAtPosition(const QString &text, int position);
bool isValidUrl(const QString &urlString);
//这个函数没有达到预期的要求应该对scheme的类型进行一定的描述提高鲁棒性
bool isValidUrl(const QString &urlString);// 检查URL是否有效
//没有进行检查,直接调用函数库
void openUrl(const QString &urlString);
QMap<QString, QString> parseMarkdownUrlsFromText(const QString &text);
QUrl getUrlUnderMouse();
void moveBlockUp();
void moveBlockDown();
//该函数值得学习
//首先是每个可能链接的正则表达式
//其次是对于引用链接,如何进行发现、描述和储存
QMap<QString, QString> parseMarkdownUrlsFromText(const QString &text);// 解析文本中的Markdown链接
// 这个函数值得学习按照逻辑处理url和text安排的很巧妙
QUrl getUrlUnderMouse(); // 获取鼠标下的URL
//块的移动,提供了一种思路,即先删除,后添加,最后移动游标到指定位置
void moveBlockUp(); // 将当前块向上移动
void moveBlockDown(); // 将当前块向下移动
signals:
void resized();
void mouseMoved();
void resized(); // 信号:当控件大小改变时发出
void mouseMoved(); // 信号:当鼠标移动时发出
// QWidget interface
protected:
void resizeEvent(QResizeEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void resizeEvent(QResizeEvent *event); // 重写QWidget的resizeEvent
void mouseMoveEvent(QMouseEvent *event); // 重写QWidget的mouseMoveEvent
QStringList _ignoredClickUrlSchemata;
QStringList _ignoredClickUrlSchemata; // 忽略点击的URL模式列表
};
#endif // CUSTOMDOCUMENT_H

@ -1,15 +1,15 @@
#include "customDocument.h"
#include <QDebug>
#include <QGuiApplication>
#include <QTextCursor>
#include <QMessageBox>
#include <QGuiApplication>//应用程序
#include <QTextCursor>//文本光标
#include <QMessageBox>//消息框
CustomDocument::CustomDocument(QWidget *parent) : QTextEdit(parent)
{
installEventFilter(this);
viewport()->installEventFilter(this);
setMouseTracking(true);
setAttribute(Qt::WidgetAttribute::WA_Hover, true);
installEventFilter(this); // 安装事件过滤器,以便可以处理事件
viewport()->installEventFilter(this); // 安装视口的事件过滤器
setMouseTracking(true); // 开启鼠标追踪,以便可以捕获鼠标移动事件
setAttribute(Qt::WidgetAttribute::WA_Hover, true); // 设置悬停属性,以便可以处理鼠标悬停事件
}
/*!
@ -25,16 +25,16 @@ CustomDocument::CustomDocument(QWidget *parent) : QTextEdit(parent)
*/
void CustomDocument::setDocumentPadding(int left, int top, int right, int bottom)
{
setViewportMargins(left, top, right, bottom);
setViewportMargins(left, top, right, bottom);//设置视口的边距
}
void CustomDocument::resizeEvent(QResizeEvent *event)
void CustomDocument::resizeEvent(QResizeEvent *event)//处理窗口变化
{
QTextEdit::resizeEvent(event);
emit resized();
}
void CustomDocument::mouseMoveEvent(QMouseEvent *event)
void CustomDocument::mouseMoveEvent(QMouseEvent *event)//处理鼠标移动事件
{
QTextEdit::mouseMoveEvent(event);
emit mouseMoved();
@ -43,7 +43,9 @@ void CustomDocument::mouseMoveEvent(QMouseEvent *event)
bool CustomDocument::eventFilter(QObject *obj, QEvent *event)
{
// qDebug() << event->type();
//分情况处理按键和鼠标状况:
//先处理关注的事件,再处理不关注的事件
//先处理不常见的事件——如按键。后处理常见的事件——如鼠标移动或松开
if (event->type() == QEvent::HoverMove) {
// if hovering and the control key is active, check whether the mouse is over a link
if (QGuiApplication::keyboardModifiers() == Qt::ExtraButton24
@ -52,7 +54,7 @@ bool CustomDocument::eventFilter(QObject *obj, QEvent *event)
} else {
viewport()->setCursor(Qt::IBeamCursor);
}
} else if (event->type() == QEvent::KeyPress) {
} else if (event->type() == QEvent::KeyPress) {//检测只按键的事件并做出响应
auto *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Control) {
@ -69,7 +71,7 @@ bool CustomDocument::eventFilter(QObject *obj, QEvent *event)
return true;
}
}
} else if (event->type() == QEvent::MouseButtonRelease) {
} else if (event->type() == QEvent::MouseButtonRelease) {//检测松鼠标的事件并做出响应
auto *mouseEvent = static_cast<QMouseEvent *>(event);
@ -85,7 +87,7 @@ bool CustomDocument::eventFilter(QObject *obj, QEvent *event)
return true;
}
} else if (event->type() == QEvent::KeyRelease) {
} else if (event->type() == QEvent::KeyRelease) {//检测松按键的事件并做出响应
auto *keyEvent = static_cast<QKeyEvent *>(event);
// reset cursor if control key was released
@ -103,6 +105,8 @@ bool CustomDocument::eventFilter(QObject *obj, QEvent *event)
* @param position
* @return url string
*/
//注释表明函数需要用到text和position变量并且需要返回一个string类型的url
//但是在实际操作过程中作者用QString代替了String类型
QString CustomDocument::getMarkdownUrlAtPosition(const QString &text, int position)
{
QString url;
@ -116,7 +120,7 @@ QString CustomDocument::getMarkdownUrlAtPosition(const QString &text, int positi
const int foundPositionStart = text.indexOf(linkText);
if (foundPositionStart >= 0) {
if (foundPositionStart >= 0) {//谨慎处理url的长度引起的溢出问题是健壮性的体现
// calculate end position of found linkText
const int foundPositionEnd = foundPositionStart + linkText.size();
@ -136,19 +140,31 @@ QString CustomDocument::getMarkdownUrlAtPosition(const QString &text, int positi
*
* @return QUrl
*/
//注释表明函数有返回值且返回值是游标下面的url
QUrl CustomDocument::getUrlUnderMouse()
{
// place a temp cursor at the mouse position
//获取当前鼠标光标在全局坐标系中的位置
//并将其映射到视图端口的局部坐标系中
// 存储在 pos变量中。
auto pos = viewport()->mapFromGlobal(QCursor::pos());
//使用 cursorForPosition 方法创建一个 QTextCursor 对象,该对象位于 pos 指定的位置
QTextCursor cursor = cursorForPosition(pos);
//获取当前 QTextCursor 的位置,即光标在文档中的索引位置
const int cursorPosition = cursor.position();
// select the text of the current block
//将 QTextCursor 移动到当前文本块的开始位置
cursor.movePosition(QTextCursor::StartOfBlock);
//计算光标在当前文本块中的索引位置,即光标位置与文本块开始位置的差值
const int indexInBlock = cursorPosition - cursor.position();
//将 QTextCursor 移动到当前文本块的结束位置,并保持起始位置不变
//即选择文本块中的所有文本
cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
//先放到块的首段,再放到块的尾端,即选中文本块中所有文本,并且可以计算出当前游标的位置,即链接的位置
// get the correct link from the selected text, or an empty URL if none found
//调用 getMarkdownUrlAtPosition 函数,传入选中的文本和光标在文本块中的索引位置,获取对应的 Markdown URL并将其转换为 QUrl 对象返回。如果未找到 URL则返回空的 QUrl 对象
return QUrl(getMarkdownUrlAtPosition(cursor.selectedText(), indexInBlock));
}
@ -158,30 +174,51 @@ QUrl CustomDocument::getUrlUnderMouse()
bool CustomDocument::openLinkAtCursorPosition()
{
// find out which url in the selected text was clicked
//先转化成QString变量
QUrl const url = getUrlUnderMouse();
QString const urlString = url.toString();
//检查是否为本地文件类型
const bool isFileUrl = urlString.startsWith(QLatin1String("file://"));
//检查是否为遗留的附件URL?
const bool isLegacyAttachmentUrl = urlString.startsWith(QLatin1String("file://attachments"));
//检查是否为本地文件夹类型
const bool isLocalFilePath = urlString.startsWith(QLatin1String("/"));
//定义一个标志变量,指示是否将本地文件路径转换为 URL。这里硬编码为 true
const bool convertLocalFilepathsToURLs = true;
//如果 URL 是有效的,并且通过 isValidUrl 函数验证,或者是文件 URL、本地文件路径或遗留附件 URL则进入后续处理逻辑
//其中url.isValid()是基本验证,是 QUrl 类的成员函数
//用于检查 URL 是否符合基本的 URL 格式规范
//它会验证 URL 是否包含有效的 scheme、host 和 path 等部分,但不会对 URL 的具体内容进行深入的验证
//而isValidUrl是自定义的函数可以对url内容进行核验
//例如isValidUrl 可能会检查 URL 是否指向一个有效的资源
//或者是否符合特定的 URL 模式(如特定的域名或路径格式)
//它还可以结合应用程序的上下文来判断 URL 是否有效,比如检查数据库中是否存在对应的记录等
// 但是在下面的实现中,很显然作者并没有实现这样的功能,只是检查了基本格式,可能需要完善
//两个检验程序依次递增,符合提高效率的要求
//如不符合基本URL的格式规范就不用调用自定义的函数进行搜索和审查降低了对算力的要求。
if ((url.isValid() && isValidUrl(urlString)) || isFileUrl || isLocalFilePath
|| isLegacyAttachmentUrl) {
//如果 URL 的 scheme协议包含在 _ignoredClickUrlSchemata 集合中,则忽略该 URL 并返回 false
//可能是自定义内容的一部分,可以实现链接的自定义实现
//提高了用户的自定义能力,值得借鉴
if (_ignoredClickUrlSchemata.contains(url.scheme())) {
qDebug() << __func__ << "ignored URL scheme:" << urlString;
return false;
}
// ignore non-existent files
//检查文件URL
if (isFileUrl) {
//从第8个字符开始提取
QString trimmed = urlString.mid(7);
if (!QFile::exists(trimmed)) {
qDebug() << __func__ << ": File does not exist:" << urlString;
// show a message box
//使用 QMessageBox::warning 显示一个警告信息框,告知用户文件不存在
//信息框的标题是 "File not found"
//内容是 "The file <strong>%1</strong> does not exist."
//其中 %1 被替换为实际的文件路径 trimmed使用<strong> 标签以突出显示
//提示我们可以在输出的deBug中使用html等标签语言增加信息的分辨度以更快的进行筛选错误
QMessageBox::warning(
nullptr, tr("File not found"),
tr("The file <strong>%1</strong> does not exist.").arg(trimmed));
@ -196,7 +233,8 @@ bool CustomDocument::openLinkAtCursorPosition()
tr("The file <strong>%1</strong> does not exist.").arg(urlString));
return false;
}
//验证完毕后再统一集中打开链接,有利于对打开链接的种类进行集中调控
//在后期的纠错过程中将打开部分的函数放在一起更有利于代码的纠错和DeBug
if (isLocalFilePath && convertLocalFilepathsToURLs) {
openUrl(QString("file://") + urlString);
} else {
@ -205,7 +243,7 @@ bool CustomDocument::openLinkAtCursorPosition()
return true;
}
//查找到不是URL就返回false其余均为true以保证程序的顺利执行
return false;
}
@ -215,8 +253,20 @@ bool CustomDocument::openLinkAtCursorPosition()
* @param urlString
* @return
*/
//在注释中我们可以了解到函数的作用时间差是否为可达的路径
//函数需要一个urString返回某个值
//实际来看并没有对函数的有效性进行充分验证,函数是失败的
bool CustomDocument::isValidUrl(const QString &urlString)
{
//关于正则表达式
//使用 QRegularExpression 类来创建一个正则表达式对象,并使用该对象的 match 方法来匹配 urlString
//正则表达式中,^表示匹配的开始
//\w+表示有一个或多个单词字符(字母、数字或下划线)
// 这里应该表示的是http或https或file等之类的我认为应该写的更详细一些因为常用的协议就那么些
// 两个反斜杠
//.+表示匹配一个或多个任意字符
// 这里表示的应该是主机及内部的文件路径,没有固定格式
//总的来说,这里的匹配在我看来很糟糕,没有起到多少自定义的作用
const QRegularExpressionMatch match = QRegularExpression(R"(^\w+:\/\/.+)").match(urlString);
return match.hasMatch();
}
@ -230,6 +280,7 @@ bool CustomDocument::isValidUrl(const QString &urlString)
* "/path/to/my/file/QOwnNotes.pdf" if the operating system supports that
* handler
*/
//并没有对函数调用做出一定的检查,直接调用函数库打开链接
void CustomDocument::openUrl(const QString &urlString)
{
qDebug() << "CustomDocument " << __func__ << " - 'urlString': " << urlString;
@ -243,6 +294,7 @@ void CustomDocument::openUrl(const QString &urlString)
* @param text
* @return parsed urls
*/
//主要作用是对输入的文本内的链接进行提取
QMap<QString, QString> CustomDocument::parseMarkdownUrlsFromText(const QString &text)
{
QMap<QString, QString> urlMap;
@ -251,6 +303,7 @@ QMap<QString, QString> CustomDocument::parseMarkdownUrlsFromText(const QString &
// match urls like this: <http://mylink>
// re = QRegularExpression("(<(.+?:\\/\\/.+?)>)");
//匹配链接类型为<http://mylink>
regex = QRegularExpression(QStringLiteral("(<(.+?)>)"));
iterator = regex.globalMatch(text);
while (iterator.hasNext()) {
@ -259,7 +312,7 @@ QMap<QString, QString> CustomDocument::parseMarkdownUrlsFromText(const QString &
QString url = match.captured(2);
urlMap[linkText] = url;
}
//匹配链接类型为[链接文本](链接)
regex = QRegularExpression(R"((\[.*?\]\((.+?)\)))");
iterator = regex.globalMatch(text);
while (iterator.hasNext()) {
@ -270,6 +323,7 @@ QMap<QString, QString> CustomDocument::parseMarkdownUrlsFromText(const QString &
}
// match urls like this: http://mylink
//匹配裸链接
regex = QRegularExpression(R"(\b\w+?:\/\/[^\s]+[^\s>\)])");
iterator = regex.globalMatch(text);
while (iterator.hasNext()) {
@ -279,6 +333,7 @@ QMap<QString, QString> CustomDocument::parseMarkdownUrlsFromText(const QString &
}
// match urls like this: www.github.com
//匹配以www开头的链接
regex = QRegularExpression(R"(\bwww\.[^\s]+\.[^\s]+\b)");
iterator = regex.globalMatch(text);
while (iterator.hasNext()) {
@ -289,6 +344,7 @@ QMap<QString, QString> CustomDocument::parseMarkdownUrlsFromText(const QString &
// match reference urls like this: [this url][1] with this later:
// [1]: http://domain
//匹配引用定义的链接
regex = QRegularExpression(R"((\[.*?\]\[(.+?)\]))");
iterator = regex.globalMatch(text);
while (iterator.hasNext()) {
@ -309,6 +365,8 @@ QMap<QString, QString> CustomDocument::parseMarkdownUrlsFromText(const QString &
return urlMap;
}
//向上移动一个块
//具体操作是,先删除本块内容,再移动到上一块的开始处,插入本块和换行,最后移动光标到指定位置
void CustomDocument::moveBlockUp()
{
QTextCursor cursor = textCursor();
@ -342,6 +400,7 @@ void CustomDocument::moveBlockUp()
}
}
//向下移动一个块,同理
void CustomDocument::moveBlockDown()
{
QTextCursor cursor = textCursor();

Loading…
Cancel
Save