From 9a666c1b071a84de25a06a8edef7c55584f5a25b Mon Sep 17 00:00:00 2001 From: Sunyuxuan <3107278279@qq.com> Date: Wed, 8 Jan 2025 09:47:59 +0800 Subject: [PATCH] CustomDocument --- source/src/customDocument.h | 33 +++++++---- source/src/customdocument.cpp | 105 ++++++++++++++++++++++++++-------- 2 files changed, 104 insertions(+), 34 deletions(-) diff --git a/source/src/customDocument.h b/source/src/customDocument.h index bf57806..7f613b7 100644 --- a/source/src/customDocument.h +++ b/source/src/customDocument.h @@ -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 parseMarkdownUrlsFromText(const QString &text); - QUrl getUrlUnderMouse(); - void moveBlockUp(); - void moveBlockDown(); + //该函数值得学习 + //首先是每个可能链接的正则表达式 + //其次是对于引用链接,如何进行发现、描述和储存 + QMap 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 diff --git a/source/src/customdocument.cpp b/source/src/customdocument.cpp index 587af4c..3f9b0de 100644 --- a/source/src/customdocument.cpp +++ b/source/src/customdocument.cpp @@ -1,15 +1,15 @@ #include "customDocument.h" #include -#include -#include -#include +#include //应用程序 +#include //文本光标 +#include //消息框 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(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(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(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 %1 does not exist." + //其中 %1 被替换为实际的文件路径 trimmed,使用 标签以突出显示 + //提示我们可以在输出的deBug中使用html等标签语言,增加信息的分辨度,以更快的进行筛选错误 QMessageBox::warning( nullptr, tr("File not found"), tr("The file %1 does not exist.").arg(trimmed)); @@ -196,7 +233,8 @@ bool CustomDocument::openLinkAtCursorPosition() tr("The file %1 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 CustomDocument::parseMarkdownUrlsFromText(const QString &text) { QMap urlMap; @@ -251,6 +303,7 @@ QMap CustomDocument::parseMarkdownUrlsFromText(const QString & // match urls like this: // re = QRegularExpression("(<(.+?:\\/\\/.+?)>)"); + //匹配链接类型为 regex = QRegularExpression(QStringLiteral("(<(.+?)>)")); iterator = regex.globalMatch(text); while (iterator.hasNext()) { @@ -259,7 +312,7 @@ QMap 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 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 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 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 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();