|
|
|
@ -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();
|
|
|
|
|