Compare commits

...

14 Commits

Author SHA1 Message Date
zhangshuoshuo c6d521d989 foldertreedelegateeditor.cpp
1 month ago
kkk 320ddb1d96 guo commit
1 month ago
zhangshuoshuo 9b744e9edb noteeditorlogic.cpp
1 month ago
zhangshuoshuo 26aaeff0b3 nodetreeview.cpp
1 month ago
zhangshuoshuo 85254c8e29 nodetreedelegate.cpp
1 month ago
zhangshuoshuo 798d8d6476 notelistmodel.h
1 month ago
zhangshuoshuo a46534035c 12
1 month ago
zhangshuoshuo 456515163c 12
1 month ago
zhangshuoshuo af2c0069e1 zhangshuoshuo
1 month ago
kkk c34f275c6b xie tijiao
1 month ago
Sunyuxuan c5d4a9b9d9 AboutWindow
1 month ago
Sunyuxuan d0f348a7f9 AllNoteButtonTreeDelegateEditor和EditorSettingsOptions
1 month ago
Sunyuxuan 9a666c1b07 CustomDocument
1 month ago
kkk 55c68d06de xie commit
1 month ago

Binary file not shown.

@ -11,6 +11,8 @@
/**
* Initializes the window components and configures the AboutWindow
*/
//包含版本检测和pro版本检测我们可以通过修改m_isProVersion值来做到免费的功能
//当版本低于5.10.0时,移除窗口的帮助按钮功能
AboutWindow::AboutWindow(QWidget *parent)
: QDialog(parent), m_ui(new Ui::AboutWindow), m_isProVersion(false)
{

@ -16,6 +16,7 @@ public:
explicit AboutWindow(QWidget *parent = 0);
~AboutWindow();
void setTheme(Theme::Value theme);
//或许可以通过此函数来获取免费版本
void setProVersion(bool isProVersion);
public slots:

@ -7,7 +7,7 @@
#include "notelistview.h"
#include "editorsettingsoptions.h"
#include "fontloader.h"
//分操作系统设置font
AllNoteButtonTreeDelegateEditor::AllNoteButtonTreeDelegateEditor(QTreeView *view,
const QStyleOptionViewItem &option,
const QModelIndex &index,
@ -45,20 +45,27 @@ AllNoteButtonTreeDelegateEditor::AllNoteButtonTreeDelegateEditor(QTreeView *view
{
setContentsMargins(0, 0, 0, 0);
}
//重写paintEvent函数
void AllNoteButtonTreeDelegateEditor::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
//定图标位置
auto iconRect = QRect(rect().x() + 22, rect().y() + (rect().height() - 20) / 2, 18, 20);
//获取图标路径
auto iconPath = m_index.data(NodeItem::Roles::Icon).toString();
//获取显示文本
auto displayName = m_index.data(NodeItem::Roles::DisplayText).toString();
QRect nameRect(rect());
nameRect.setLeft(iconRect.x() + iconRect.width() + 5);
nameRect.setWidth(nameRect.width() - 22 - 40);
if (m_view->selectionModel()->isSelected(m_index)) {
//根据是否被选中绘制不同颜色
painter.fillRect(rect(), QBrush(m_activeColor));
painter.setPen(m_titleSelectedColor);
} else {
//拖动绘制
auto listView = dynamic_cast<NoteListView *>(m_listView);
if (listView->isDragging()) {
if (m_theme == Theme::Dark) {
@ -71,6 +78,7 @@ void AllNoteButtonTreeDelegateEditor::paintEvent(QPaintEvent *event)
}
painter.setPen(m_folderIconColor);
}
//又开始分操作系统绘制
#ifdef __APPLE__
int iconPointSizeOffset = 0;
#else
@ -86,6 +94,7 @@ void AllNoteButtonTreeDelegateEditor::paintEvent(QPaintEvent *event)
painter.setPen(m_titleColor);
}
//设置子项
painter.setFont(m_titleFont);
painter.drawText(nameRect, Qt::AlignLeft | Qt::AlignVCenter, displayName);
auto childCountRect = rect();
@ -100,9 +109,12 @@ void AllNoteButtonTreeDelegateEditor::paintEvent(QPaintEvent *event)
painter.setFont(m_numberOfNotesFont);
painter.drawText(childCountRect, Qt::AlignHCenter | Qt::AlignVCenter,
QString::number(childCount));
//调用基类 QWidget 的 paintEvent 函数,以确保小部件的其他默认绘制行为得以执行
QWidget::paintEvent(event);
}
//又是用switch来设置枚举类型的值
//要注意的一点是没有使用0、1、2等来列举而是使用key值来列举有利于增加代码规模和程序员阅读
void AllNoteButtonTreeDelegateEditor::setTheme(Theme::Value theme)
{
m_theme = theme;

@ -2,12 +2,17 @@
#define ALLNOTEBUTTONTREEDELEGATEEDITOR_H
#include <QWidget>
//用来描述书图像的显示选项
#include <QStyleOptionViewItem>
//用于在模型/视图架构中索引项
#include <QModelIndex>
//用于设置和管理字体
#include <QFont>
#include "editorsettingsoptions.h"
//用于显示树形视图的组件
class QTreeView;
//用于显示列表视图的组件
class QListView;
class AllNoteButtonTreeDelegateEditor : public QWidget
@ -37,6 +42,7 @@ private:
Theme::Value m_theme;
// QWidget interface
protected:
//重写QWidget的paintEvent函数用于自定义绘制编辑器小部件的内容
virtual void paintEvent(QPaintEvent *event) override;
};

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

@ -3,8 +3,10 @@
#include <QVariant>
#include <qdebug.h>
// 重载<<运算符,将枚举类型的值输出到标准输出流
EditorSettingsOptions::EditorSettingsOptions(QObject *) { }
//选用switch来选择要输出的枚举值
std::ostream &operator<<(std::ostream &os, const FontTypeface::Value &fontTypeface)
{
switch (fontTypeface) {
@ -37,6 +39,7 @@ std::ostream &operator<<(std::ostream &os, const Theme::Value &theme)
return os;
}
//使用str()函数来重载to_string函数
std::string to_string(FontTypeface::Value fontTypeface)
{
std::ostringstream oss;
@ -51,11 +54,13 @@ std::string to_string(Theme::Value theme)
return oss.str();
}
//函数调用,没看懂什么意思,是要再进行功能扩展吗
void setCSSThemeAndUpdate(QWidget *obj, Theme::Value theme)
{
//标准输入输出
setCSSClassesAndUpdate(obj, QString::fromStdString(to_string(theme)).toLower().toStdString());
}
//更新
void setCSSClassesAndUpdate(QWidget *obj, std::string classNames)
{
if (obj->styleSheet().isEmpty()) {

@ -2,12 +2,13 @@
#define EDITORSETTINGSOPTIONS_H
#include <QObject>
//声明QtQml/qqml.h库这个库包含Qt QML相关文件可将C++类暴露给QML
#include <QtQml/qqml.h>
#include <sstream>
#include <QString>
#include <QWidget>
#include "lqtutils_enum.h"
//声明枚举类型
L_DECLARE_ENUM(FontTypeface, SansSerif, Serif, Mono)
L_DECLARE_ENUM(FontSizeAction, FontSizeIncrease, FontSizeDecrease)
L_DECLARE_ENUM(EditorTextWidth, TextWidthIncrease, TextWidthDecrease, TextWidthFullWidth)
@ -22,11 +23,14 @@ public:
explicit EditorSettingsOptions(QObject *parent = nullptr);
};
//重载<<运算符,将枚举类型的值输出到标准输出流
std::ostream &operator<<(std::ostream &os, const Theme::Value &theme);
std::ostream &operator<<(std::ostream &os, const FontTypeface::Value &fontTypeface);
//为了适配标准输出流,重载字符串转换函数
std::string to_string(FontTypeface::Value fontTypeface);
std::string to_string(Theme::Value theme);
//更新CSS样式和类
void setCSSThemeAndUpdate(QWidget *obj, Theme::Value theme);
void setCSSClassesAndUpdate(QWidget *obj, std::string classNames);

@ -1,115 +1,121 @@
#include "foldertreedelegateeditor.h"
#include <QHBoxLayout>
#include <QLabel>
#include <QPainter>
#include <QDebug>
#include <QTreeView>
#include <QMouseEvent>
#include "pushbuttontype.h"
#include "nodetreemodel.h"
#include "nodetreeview.h"
#include "notelistview.h"
#include "labeledittype.h"
#include "fontloader.h"
#include "foldertreedelegateeditor.h" // 包含FolderTreeDelegateEditor类的声明
#include <QHBoxLayout> // 包含水平布局管理器
#include <QLabel> // 包含标签控件
#include <QPainter> // 包含绘图器
#include <QDebug> // 包含调试输出
#include <QTreeView> // 包含树视图控件
#include <QMouseEvent> // 包含鼠标事件
#include "pushbuttontype.h" // 包含自定义按钮类型
#include "nodetreemodel.h" // 包含节点树模型
#include "nodetreeview.h" // 包含节点树视图
#include "notelistview.h" // 包含笔记列表视图
#include "labeledittype.h" // 包含标签编辑类型
#include "fontloader.h" // 包含字体加载器
// FolderTreeDelegateEditor构造函数
FolderTreeDelegateEditor::FolderTreeDelegateEditor(QTreeView *view,
const QStyleOptionViewItem &option,
const QModelIndex &index, QListView *listView,
QWidget *parent)
: QWidget(parent),
m_option(option),
m_index(index),
#ifdef __APPLE__
: QWidget(parent), // 调用QWidget构造函数
m_option(option), // 初始化选项
m_index(index), // 初始化索引
#ifdef __APPLE__ // 如果是苹果系统
m_displayFont(QFont(QStringLiteral("SF Pro Text")).exactMatch()
? QStringLiteral("SF Pro Text")
: QStringLiteral("Roboto")),
#elif _WIN32
? QStringLiteral("SF Pro Text") // 使用系统字体
: QStringLiteral("Roboto")), // 否则使用Roboto字体
#elif _WIN32 // 如果是Windows系统
m_displayFont(QFont(QStringLiteral("Segoe UI")).exactMatch() ? QStringLiteral("Segoe UI")
: QStringLiteral("Roboto")),
#else
#else // 其他系统
m_displayFont(QStringLiteral("Roboto")),
#endif
#ifdef __APPLE__
m_titleFont(m_displayFont, 13, QFont::DemiBold),
#ifdef __APPLE__ // 如果是苹果系统
m_titleFont(m_displayFont, 13, QFont::DemiBold), // 设置标题字体
#else
m_titleFont(m_displayFont, 10, QFont::DemiBold),
m_titleFont(m_displayFont, 10, QFont::DemiBold), // 设置标题字体
#endif
m_titleColor(26, 26, 26),
m_titleSelectedColor(255, 255, 255),
m_activeColor(68, 138, 201),
m_hoverColor(207, 207, 207),
m_folderIconColor(68, 138, 201),
m_view(view),
m_listView(listView),
m_theme(Theme::Light)
m_titleColor(26, 26, 26), // 设置标题颜色
m_titleSelectedColor(255, 255, 255), // 设置选中标题颜色
m_activeColor(68, 138, 201), // 设置活动颜色
m_hoverColor(207, 207, 207), // 设置悬浮颜色
m_folderIconColor(68, 138, 201), // 设置文件夹图标颜色
m_view(view), // 初始化视图
m_listView(listView), // 初始化列表视图
m_theme(Theme::Light) // 初始化主题为浅色
{
setContentsMargins(0, 0, 0, 0);
auto layout = new QHBoxLayout(this);
layout->setContentsMargins(10, 0, 0, 0);
layout->setSpacing(0);
setLayout(layout);
m_expandIcon = new QLabel(this);
m_expandIcon->setMinimumSize({ 11, 11 });
m_expandIcon->setMaximumSize({ 11, 11 });
setContentsMargins(0, 0, 0, 0); // 设置内容边距
auto layout = new QHBoxLayout(this); // 创建水平布局
layout->setContentsMargins(10, 0, 0, 0); // 设置布局边距
layout->setSpacing(0); // 设置布局间隔
setLayout(layout); // 设置布局
m_expandIcon = new QLabel(this); // 创建展开图标标签
m_expandIcon->setMinimumSize({ 11, 11 }); // 设置最小尺寸
m_expandIcon->setMaximumSize({ 11, 11 }); // 设置最大尺寸
// 根据索引数据判断是否可展开,并相应地添加布局间隔
if (m_index.data(NodeItem::Roles::IsExpandable).toBool()) {
if (!m_view->isExpanded(m_index)) {
layout->addSpacing(2);
}
}
#ifdef __APPLE__
int iconPointSizeOffset = 0;
#ifdef __APPLE__ // 如果是苹果系统
int iconPointSizeOffset = 0; // 图标点大小偏移量
#else
int iconPointSizeOffset = -4;
int iconPointSizeOffset = -4; // 图标点大小偏移量
#endif
m_expandIcon->setFont(FontLoader::getInstance().loadFont("Font Awesome 6 Free Solid", "",
10 + iconPointSizeOffset));
10 + iconPointSizeOffset)); // 加载字体
m_expandIcon->setScaledContents(true);
layout->addWidget(m_expandIcon);
m_expandIcon->setScaledContents(true); // 设置缩放内容
layout->addWidget(m_expandIcon); // 将展开图标添加到布局
// 如果索引不可展开或已展开,添加布局间隔
if (!m_index.data(NodeItem::Roles::IsExpandable).toBool()
|| (m_index.data(NodeItem::Roles::IsExpandable).toBool() && m_view->isExpanded(m_index))) {
layout->addSpacing(2);
}
m_folderIcon = new PushButtonType(parent);
m_folderIcon->setMaximumSize({ 19, 20 });
m_folderIcon->setMinimumSize({ 19, 20 });
m_folderIcon->setIconSize(QSize(19, 20));
QFont materialSymbols("Material Symbols Outlined", 16 + iconPointSizeOffset);
m_folderIcon->setFont(materialSymbols);
m_folderIcon->setText(u8"\ue2c7"); // folder
layout->addWidget(m_folderIcon);
layout->addSpacing(5);
m_folderIcon = new PushButtonType(parent); // 创建文件夹图标按钮
m_folderIcon->setMaximumSize({ 19, 20 }); // 设置最大尺寸
m_folderIcon->setMinimumSize({ 19, 20 }); // 设置最小尺寸
m_folderIcon->setIconSize(QSize(19, 20)); // 设置图标尺寸
QFont materialSymbols("Material Symbols Outlined", 16 + iconPointSizeOffset); // 加载字体
m_folderIcon->setFont(materialSymbols); // 设置字体
m_folderIcon->setText(u8"\ue2c7"); // 设置文本为文件夹图标
layout->addWidget(m_folderIcon); // 将文件夹图标添加到布局
layout->addSpacing(5); // 添加布局间隔
m_label = new LabelEditType(this);
m_label->setFont(m_titleFont);
QSizePolicy labelPolicy;
labelPolicy.setVerticalPolicy(QSizePolicy::Expanding);
labelPolicy.setHorizontalPolicy(QSizePolicy::Expanding);
m_label->setSizePolicy(labelPolicy);
m_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
connect(m_label, &LabelEditType::editingStarted, this, [this] {
auto tree_view = dynamic_cast<NodeTreeView *>(m_view);
tree_view->setIsEditing(true);
});
connect(m_label, &LabelEditType::editingFinished, this, [this](const QString &label) {
auto tree_view = dynamic_cast<NodeTreeView *>(m_view);
tree_view->onRenameFolderFinished(label);
tree_view->setIsEditing(false);
m_label = new LabelEditType(this); // 创建标签编辑类型
m_label->setFont(m_titleFont); // 设置字体
QSizePolicy labelPolicy; // 创建尺寸策略
labelPolicy.setVerticalPolicy(QSizePolicy::Expanding); // 设置垂直策略为扩展
labelPolicy.setHorizontalPolicy(QSizePolicy::Expanding); // 设置水平策略为扩展
m_label->setSizePolicy(labelPolicy); // 设置尺寸策略
m_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); // 设置对齐方式
connect(m_label, &LabelEditType::editingStarted, this, [this] { // 连接编辑开始信号
auto tree_view = dynamic_cast<NodeTreeView *>(m_view); // 转换视图为NodeTreeView类型
tree_view->setIsEditing(true); // 设置编辑状态为真
});
connect(dynamic_cast<NodeTreeView *>(m_view), &NodeTreeView::renameFolderRequested, m_label,
&LabelEditType::openEditor);
layout->addWidget(m_label);
layout->addSpacing(5);
m_contextButton = new PushButtonType(parent);
m_contextButton->setMaximumSize({ 33, 25 });
m_contextButton->setMinimumSize({ 33, 25 });
m_contextButton->setCursor(QCursor(Qt::PointingHandCursor));
m_contextButton->setFocusPolicy(Qt::TabFocus);
if (m_view->selectionModel()->isSelected(m_index)) {
connect(m_label, &LabelEditType::editingFinished, this,
[this](const QString &label) { // 连接编辑结束信号
auto tree_view = dynamic_cast<NodeTreeView *>(m_view); // 转换视图为NodeTreeView类型
tree_view->onRenameFolderFinished(label); // 重命名文件夹
tree_view->setIsEditing(false); // 设置编辑状态为假
});
connect(dynamic_cast<NodeTreeView *>(m_view), &NodeTreeView::renameFolderRequested,
m_label, // 连接重命名请求信号
&LabelEditType::openEditor); // 打开编辑器
layout->addWidget(m_label); // 将标签添加到布局
layout->addSpacing(5); // 添加布局间隔
m_contextButton = new PushButtonType(parent); // 创建上下文按钮
m_contextButton->setMaximumSize({ 33, 25 }); // 设置最大尺寸
m_contextButton->setMinimumSize({ 33, 25 }); // 设置最小尺寸
m_contextButton->setCursor(QCursor(Qt::PointingHandCursor)); // 设置鼠标指针为手形
m_contextButton->setFocusPolicy(Qt::TabFocus); // 设置焦点策略为Tab焦点
if (m_view->selectionModel()->isSelected(m_index)) { // 如果索引被选中
m_contextButton->setStyleSheet(QStringLiteral(R"(QPushButton { )"
R"( border: none; )"
R"( padding: 0px; )"

@ -1,52 +1,55 @@
#ifndef FOLDERTREEDELEGATEEDITOR_H
#ifndef FOLDERTREEDELEGATEEDITOR_H // 头文件保护
#define FOLDERTREEDELEGATEEDITOR_H
#include <QWidget>
#include <QStyleOptionViewItem>
#include <QModelIndex>
#include <QFont>
#include "editorsettingsoptions.h"
#include <QWidget> // 包含QWidget类
#include <QStyleOptionViewItem> // 包含样式选项视图项
#include <QModelIndex> // 包含模型索引
#include <QFont> // 包含字体类
#include "editorsettingsoptions.h" // 包含编辑器设置选项
class QTreeView;
class QLabel;
class PushButtonType;
class LabelEditType;
class QListView;
class QTreeView; // 前向声明QTreeView类
class QLabel; // 前向声明QLabel类
class PushButtonType; // 前向声明PushButtonType类
class LabelEditType; // 前向声明LabelEditType类
class QListView; // 前向声明QListView类
class FolderTreeDelegateEditor : public QWidget
class FolderTreeDelegateEditor : public QWidget // 继承自QWidget
{
Q_OBJECT
public:
explicit FolderTreeDelegateEditor(QTreeView *view, const QStyleOptionViewItem &option,
const QModelIndex &index, QListView *listView,
QWidget *parent = nullptr);
void setTheme(Theme::Value theme);
Q_OBJECT // 宏用于支持Qt的信号和槽机制
public
: explicit FolderTreeDelegateEditor(QTreeView *view,
const QStyleOptionViewItem &option, // 构造函数
const QModelIndex &index, QListView *listView,
QWidget *parent = nullptr);
void setTheme(Theme::Value theme); // 设置主题的方法
private:
QStyleOptionViewItem m_option;
QModelIndex m_index;
QString m_displayFont;
QFont m_titleFont;
QColor m_titleColor;
QColor m_titleSelectedColor;
QColor m_activeColor;
QColor m_hoverColor;
QColor m_folderIconColor;
QTreeView *m_view;
QListView *m_listView;
LabelEditType *m_label;
PushButtonType *m_folderIcon;
QPixmap m_expanded;
QPixmap m_notExpanded;
QLabel *m_expandIcon;
PushButtonType *m_contextButton;
Theme::Value m_theme;
void updateDelegate();
// QWidget interface
QStyleOptionViewItem m_option; // 样式选项视图项
QModelIndex m_index; // 模型索引
QString m_displayFont; // 显示字体
QFont m_titleFont; // 标题字体
QColor m_titleColor; // 标题颜色
QColor m_titleSelectedColor; // 选中标题颜色
QColor m_activeColor; // 活动颜色
QColor m_hoverColor; // 悬浮颜色
QColor m_folderIconColor; // 文件夹图标颜色
QTreeView *m_view; // 树视图
QListView *m_listView; // 列表视图
LabelEditType *m_label; // 标签编辑类型
PushButtonType *m_folderIcon; // 文件夹图标按钮
QPixmap m_expanded; // 展开的图标
QPixmap m_notExpanded; // 未展开的图标
QLabel *m_expandIcon; // 展开图标标签
PushButtonType *m_contextButton; // 上下文按钮
Theme::Value m_theme; // 主题枚举值
void updateDelegate(); // 更新委托的方法
// QWidget接口
protected:
virtual void paintEvent(QPaintEvent *event) override;
virtual void mouseDoubleClickEvent(QMouseEvent *event) override;
virtual void paintEvent(QPaintEvent *event) override; // 重写绘画事件
virtual void mouseDoubleClickEvent(QMouseEvent *event) override; // 重写鼠标双击事件
};
#endif // FOLDERTREEDELEGATEEDITOR_H
#endif // FOLDERTREEDELEGATEEDITOR_H

@ -3,6 +3,7 @@
#include <QBoxLayout>
#include <QDebug>
// LabelEditType 类的构造函数
LabelEditType::LabelEditType(QWidget *parent) : QLabel(parent)
{
setContentsMargins(0, 0, 0, 0);
@ -15,6 +16,7 @@ LabelEditType::LabelEditType(QWidget *parent) : QLabel(parent)
connect(m_editor, &QLineEdit::editingFinished, this, &LabelEditType::onFinishedEdit);
}
// 打开编辑器
void LabelEditType::openEditor()
{
m_editor->setText(text());
@ -25,6 +27,7 @@ void LabelEditType::openEditor()
emit editingStarted();
}
// 编辑完成后的处理
void LabelEditType::onFinishedEdit()
{
m_editor->hide();
@ -32,4 +35,4 @@ void LabelEditType::onFinishedEdit()
setText(m_editor->text());
}
emit editingFinished(text());
}
}

@ -3,25 +3,32 @@
#include <QLabel>
// 自定义LabelEditType类继承自QLabel
class QLineEdit;
class LabelEditType : public QLabel
{
Q_OBJECT
public:
// 构造函数,接受一个父部件指针
explicit LabelEditType(QWidget *parent = nullptr);
public slots:
// 打开编辑器的槽函数
void openEditor();
private slots:
// 编辑完成后的槽函数
void onFinishedEdit();
signals:
// 编辑开始信号
void editingStarted();
// 编辑完成信号,携带编辑的文本
void editingFinished(const QString &text);
private:
// 编辑器指针
QLineEdit *m_editor;
};
#endif // LABELEDITTYPE_H
#endif // LABELEDITTYPE_H

@ -10,6 +10,7 @@
#include "tagpool.h"
#include <QTimer>
// 检查当前笔记 ID 是否无效
static bool isInvalidCurrentNotesId(const QSet<int> &currentNotesId)
{
if (currentNotesId.isEmpty()) {
@ -24,6 +25,7 @@ static bool isInvalidCurrentNotesId(const QSet<int> &currentNotesId)
return isInvalid;
}
// ListViewLogic 类的构造函数,负责初始化相关组件并设置信号与槽
ListViewLogic::ListViewLogic(NoteListView *noteView, NoteListModel *noteModel,
QLineEdit *searchEdit, QToolButton *clearButton, TagPool *tagPool,
DBManager *dbManager, QObject *parent)
@ -125,6 +127,7 @@ ListViewLogic::ListViewLogic(NoteListView *noteView, NoteListModel *noteModel,
&ListViewLogic::onListViewClicked);
}
// 选择指定的笔记
void ListViewLogic::selectNote(const QModelIndex &noteIndex)
{
if (noteIndex.isValid()) {
@ -138,6 +141,7 @@ void ListViewLogic::selectNote(const QModelIndex &noteIndex)
}
}
// 将笔记移动到列表顶部
void ListViewLogic::moveNoteToTop(const NodeData &note)
{
QModelIndex noteIndex = m_listModel->getNoteIndex(note.id());
@ -164,6 +168,7 @@ void ListViewLogic::moveNoteToTop(const NodeData &note)
}
}
// 设置笔记的数据
void ListViewLogic::setNoteData(const NodeData &note)
{
QModelIndex noteIndex = m_listModel->getNoteIndex(note.id());
@ -188,6 +193,7 @@ void ListViewLogic::setNoteData(const NodeData &note)
}
}
// 处理笔记编辑关闭事件
void ListViewLogic::onNoteEditClosed(const NodeData &note, bool selectNext)
{
if (note.isTempNote()) {
@ -209,12 +215,14 @@ void ListViewLogic::onNoteEditClosed(const NodeData &note, bool selectNext)
}
}
// 请求删除笔记
void ListViewLogic::deleteNoteRequested(const NodeData &note)
{
auto index = m_listModel->getNoteIndex(note.id());
deleteNoteRequestedI({ index });
}
// 选择当前笔记的上一笔记
void ListViewLogic::selectNoteUp()
{
auto currentIndex = m_listView->currentIndex();
@ -235,6 +243,7 @@ void ListViewLogic::selectNoteUp()
}
}
// 选择当前笔记的下一笔记
void ListViewLogic::selectNoteDown()
{
auto currentIndex = m_listView->currentIndex();
@ -259,15 +268,14 @@ void ListViewLogic::selectNoteDown()
/*!
* \brief ListViewLogic::onSearchEditTextChanged
* When text on searchEdit change:
* If there is a temp note "New Note" while searching, we delete it
* Saving the last selected note for recovery after searching
* Clear all the notes from scrollArea and
* If text is empty, reload all the notes from database
* Else, load all the notes contain the string in searchEdit from database
*
* "新笔记"
* 便
*
*
*
* \param keyword
*/
void ListViewLogic::onSearchEditTextChanged(const QString &keyword)
{
if (keyword.isEmpty()) {
@ -287,6 +295,7 @@ void ListViewLogic::onSearchEditTextChanged(const QString &keyword)
}
}
// 清除搜索
void ListViewLogic::clearSearch(bool createNewNote, int scrollToId)
{
m_listViewInfo.needCreateNewNote = createNewNote;
@ -295,6 +304,7 @@ void ListViewLogic::clearSearch(bool createNewNote, int scrollToId)
emit requestClearSearchUI();
}
// 加载笔记列表模型
void ListViewLogic::loadNoteListModel(const QVector<NodeData> &noteList, const ListViewInfo &inf)
{
auto currentNotesId = m_listViewInfo.currentNotesId;
@ -363,6 +373,7 @@ void ListViewLogic::loadNoteListModel(const QVector<NodeData> &noteList, const L
selectFirstNote();
}
// 添加标签请求处理
void ListViewLogic::onAddTagRequest(const QModelIndex &index, int tagId)
{
if (index.isValid()) {
@ -382,12 +393,14 @@ void ListViewLogic::onAddTagRequest(const QModelIndex &index, int tagId)
}
}
// 直接请求添加标签
void ListViewLogic::onAddTagRequestD(int noteId, int tagId)
{
auto index = m_listModel->getNoteIndex(noteId);
onAddTagRequest(index, tagId);
}
// 笔记移动到其他区域的处理
void ListViewLogic::onNoteMovedOut(int nodeId, int targetId)
{
auto index = m_listModel->getNoteIndex(nodeId);
@ -415,6 +428,7 @@ void ListViewLogic::onNoteMovedOut(int nodeId, int targetId)
}
}
// 设置最后选择的笔记
void ListViewLogic::setLastSelectedNote()
{
auto indexes = m_listView->selectedIndex();
@ -427,11 +441,13 @@ void ListViewLogic::setLastSelectedNote()
setLastSavedState(ids, 0);
}
// 请求加载最后选择的笔记
void ListViewLogic::loadLastSelectedNoteRequested()
{
requestLoadSavedState(2);
}
// 处理文件夹中的笔记列表请求
void ListViewLogic::onNotesListInFolderRequested(int parentID, bool isRecursive, bool newNote,
int scrollToId)
{
@ -449,6 +465,7 @@ void ListViewLogic::onNotesListInFolderRequested(int parentID, bool isRecursive,
}
}
// 处理标签中的笔记列表请求
void ListViewLogic::onNotesListInTagsRequested(const QSet<int> &tagIds, bool newNote,
int scrollToId)
{
@ -465,6 +482,7 @@ void ListViewLogic::onNotesListInTagsRequested(const QSet<int> &tagIds, bool new
}
}
// 选择多个笔记
void ListViewLogic::selectNotes(const QModelIndexList &indexes)
{
m_listView->clearSelection();
@ -482,6 +500,7 @@ void ListViewLogic::selectNotes(const QModelIndexList &indexes)
onNotePressed(indexes);
}
// 处理移除标签请求
void ListViewLogic::onRemoveTagRequest(const QModelIndex &index, int tagId)
{
if (index.isValid()) {
@ -503,14 +522,13 @@ void ListViewLogic::onRemoveTagRequest(const QModelIndex &index, int tagId)
/*!
* \brief MainWindow::onNotePressed
* When clicking on a note in the scrollArea:
* Unhighlight the previous selected note
* If selecting a note when temporery note exist, delete the temp note
* Highlight the selected note
* Load the selected note content into textedit
*
*
*
*
*
* \param index
*/
void ListViewLogic::onNotePressed(const QModelIndexList &indexes)
{
QVector<NodeData> notes;
@ -527,6 +545,7 @@ void ListViewLogic::onNotePressed(const QModelIndexList &indexes)
m_listView->setCurrentRowActive(false);
}
// 删除笔记请求的内部处理
void ListViewLogic::deleteNoteRequestedI(const QModelIndexList &indexes)
{
if (!indexes.empty()) {
@ -582,6 +601,7 @@ void ListViewLogic::deleteNoteRequestedI(const QModelIndexList &indexes)
}
}
// 请求恢复笔记的内部处理
void ListViewLogic::restoreNotesRequestedI(const QModelIndexList &indexes)
{
QModelIndexList needRestoredI;
@ -617,6 +637,7 @@ void ListViewLogic::restoreNotesRequestedI(const QModelIndexList &indexes)
}
}
// 更新列表视图的标签
void ListViewLogic::updateListViewLabel()
{
QString l1, l2;
@ -650,6 +671,7 @@ void ListViewLogic::updateListViewLabel()
emit listViewLabelChanged(l1, l2);
}
// 行数变化的处理
void ListViewLogic::onRowCountChanged()
{
m_listView->closeAllEditor();
@ -667,6 +689,7 @@ void ListViewLogic::onRowCountChanged()
}
}
// 双击笔记的处理
void ListViewLogic::onNoteDoubleClicked(const QModelIndex &index)
{
if (!index.isValid() || !m_listViewInfo.isInSearch) {
@ -676,11 +699,13 @@ void ListViewLogic::onNoteDoubleClicked(const QModelIndex &index)
clearSearch(false, id);
}
// 请求设置笔记为置顶的处理
void ListViewLogic::onSetPinnedNoteRequested(const QModelIndexList &indexes, bool isPinned)
{
m_listModel->setNotesIsPinned(indexes, isPinned);
}
// 列表视图点击的处理
void ListViewLogic::onListViewClicked()
{
if (m_listModel->rowCount() > 1) {
@ -700,6 +725,7 @@ void ListViewLogic::onListViewClicked()
}
}
// 选择第一笔记
void ListViewLogic::selectFirstNote()
{
if (m_listModel->rowCount() > 0) {
@ -714,6 +740,7 @@ void ListViewLogic::selectFirstNote()
}
}
// 设置主题
void ListViewLogic::setTheme(Theme::Value theme)
{
m_listView->setTheme(theme);
@ -721,22 +748,26 @@ void ListViewLogic::setTheme(Theme::Value theme)
m_listView->update();
}
// 检查动画是否正在运行
bool ListViewLogic::isAnimationRunning()
{
return m_listDelegate->animationState() == QTimeLine::Running;
}
// 设置最后保存的状态
void ListViewLogic::setLastSavedState(const QSet<int> &lastSelectedNotes, int needLoadSavedState)
{
m_needLoadSavedState = needLoadSavedState;
m_lastSelectedNotes = lastSelectedNotes;
}
// 请求加载保存的状态
void ListViewLogic::requestLoadSavedState(int needLoadSavedState)
{
m_needLoadSavedState = needLoadSavedState;
}
// 选择所有笔记
void ListViewLogic::selectAllNotes()
{
if (m_listModel->rowCount() > 50) {
@ -771,7 +802,8 @@ void ListViewLogic::selectAllNotes()
onNotePressed(m_listView->selectedIndex());
}
// 获取当前视图信息
const ListViewInfo &ListViewLogic::listViewInfo() const
{
return m_listViewInfo;
}
}

@ -16,83 +16,136 @@ class QLineEdit;
class QToolButton;
class TagPool;
// 负责笔记列表视图逻辑的类
class ListViewLogic : public QObject
{
Q_OBJECT
public:
// 构造函数,初始化笔记视图、笔记模型、搜索框等
explicit ListViewLogic(NoteListView *noteView, NoteListModel *noteModel, QLineEdit *searchEdit,
QToolButton *clearButton, TagPool *tagPool, DBManager *dbManager,
QObject *parent = nullptr);
// 选择指定索引的笔记
void selectNote(const QModelIndex &noteIndex);
// 获取当前列表视图的信息
const ListViewInfo &listViewInfo() const;
// 选择第一个笔记
void selectFirstNote();
// 设置主题
void setTheme(Theme::Value theme);
// 检查动画是否正在运行
bool isAnimationRunning();
// 设置最后保存的状态
void setLastSavedState(const QSet<int> &lastSelectedNotes, int needLoadSavedState = 2);
// 请求加载保存的状态
void requestLoadSavedState(int needLoadSavedState);
// 选择所有笔记
void selectAllNotes();
public slots:
// 将笔记移动到顶部
void moveNoteToTop(const NodeData &note);
// 设置笔记数据
void setNoteData(const NodeData &note);
// 笔记编辑关闭后的处理
void onNoteEditClosed(const NodeData &note, bool selectNext);
// 请求删除笔记
void deleteNoteRequested(const NodeData &note);
// 选择上一个笔记
void selectNoteUp();
// 选择下一个笔记
void selectNoteDown();
// 搜索框文本改变的处理
void onSearchEditTextChanged(const QString &keyword);
// 清除搜索
void clearSearch(bool createNewNote = false, int scrollToId = SpecialNodeID::InvalidNodeId);
// 添加标签请求的处理
void onAddTagRequestD(int noteId, int tagId);
// 笔记移动事件处理
void onNoteMovedOut(int nodeId, int targetId);
// 设置最后选择的笔记
void setLastSelectedNote();
// 请求加载最后选择的笔记
void loadLastSelectedNoteRequested();
// 请求在文件夹中获取笔记列表
void onNotesListInFolderRequested(int parentID, bool isRecursive, bool newNote, int scrollToId);
// 请求通过标签获取笔记列表
void onNotesListInTagsRequested(const QSet<int> &tagIds, bool newNote, int scrollToId);
// 选择笔记
void selectNotes(const QModelIndexList &indexes);
signals:
// 显示笔记在编辑器中的信号
void showNotesInEditor(const QVector<NodeData> &notesData);
// 请求添加标签到数据库的信号
void requestAddTagDb(int noteId, int tagId);
// 请求从数据库中移除标签的信号
void requestRemoveTagDb(int noteId, int tagId);
// 请求从数据库中移除笔记的信号
void requestRemoveNoteDb(const NodeData &noteData);
// 请求将笔记移动到数据库的信号
void requestMoveNoteDb(int noteId, const NodeData &targetFolder);
// 请求高亮搜索的信号
void requestHighlightSearch();
// 关闭笔记编辑器的信号
void closeNoteEditor();
// 笔记标签列表更改的信号
void noteTagListChanged(int noteId, const QSet<int> &tagIds);
// 请求在数据库中搜索的信号
void requestSearchInDb(const QString &keyword, const ListViewInfo &inf);
// 请求清除数据库中的搜索的信号
void requestClearSearchDb(const ListViewInfo &inf);
// 请求清除UI中的搜索的信号
void requestClearSearchUI();
// 请求新笔记的信号
void requestNewNote();
// 请求移动笔记的信号
void moveNoteRequested(int id, int target);
// 列表视图标签改变的信号
void listViewLabelChanged(const QString &label1, const QString &label2);
// 设置新笔记按钮可见性的信号
void setNewNoteButtonVisible(bool visible);
// 请求在文件夹中获取笔记列表的信号
void requestNotesListInFolder(int parentID, bool isRecursive, bool newNote, int scrollToId);
// 请求通过标签获取笔记列表的信号
void requestNotesListInTags(const QSet<int> &tagIds, bool newNote, int scrollToId);
private slots:
// 加载笔记列表模型
void loadNoteListModel(const QVector<NodeData> &noteList, const ListViewInfo &inf);
// 处理添加标签请求
void onAddTagRequest(const QModelIndex &index, int tagId);
// 处理移除标签请求
void onRemoveTagRequest(const QModelIndex &index, int tagId);
// 处理笔记按下事件
void onNotePressed(const QModelIndexList &indexes);
// 请求删除笔记的处理
void deleteNoteRequestedI(const QModelIndexList &indexes);
// 请求恢复笔记的处理
void restoreNotesRequestedI(const QModelIndexList &indexes);
// 更新列表视图标签
void updateListViewLabel();
// 行数改变的处理
void onRowCountChanged();
// 处理双击笔记事件
void onNoteDoubleClicked(const QModelIndex &index);
// 请求设置固定笔记的处理
void onSetPinnedNoteRequested(const QModelIndexList &indexes, bool isPinned);
// 处理列表视图点击事件
void onListViewClicked();
private:
NoteListView *m_listView;
NoteListModel *m_listModel;
QLineEdit *m_searchEdit;
QToolButton *m_clearButton;
DBManager *m_dbManager;
NoteListDelegate *m_listDelegate;
TagPool *m_tagPool;
ListViewInfo m_listViewInfo;
QVector<QModelIndex> m_editorIndexes;
NoteListView *m_listView; // 笔记列表视图
NoteListModel *m_listModel; // 笔记列表模型
QLineEdit *m_searchEdit; // 搜索框
QToolButton *m_clearButton; // 清除按钮
DBManager *m_dbManager; // 数据库管理器
NoteListDelegate *m_listDelegate; // 笔记列表委托
TagPool *m_tagPool; // 标签池
ListViewInfo m_listViewInfo; // 列表视图信息
QVector<QModelIndex> m_editorIndexes; // 编辑器索引
int m_needLoadSavedState;
QSet<int> m_lastSelectedNotes;
int m_needLoadSavedState; // 需要加载的保存状态
QSet<int> m_lastSelectedNotes; // 最后选择的笔记集合
};
#endif // LISTVIEWLOGIC_H
#endif // LISTVIEWLOGIC_H

@ -23,32 +23,33 @@
**/
// Credit: https://github.com/carlonluca/lqtutils
#ifndef LQTUTILS_ENUM_H
#define LQTUTILS_ENUM_H
#include <QObject>
#include <QQmlEngine>
#define L_DECLARE_ENUM(enumName, ...) \
namespace enumName { \
Q_NAMESPACE \
enum Value { __VA_ARGS__ }; \
Q_ENUM_NS(Value) \
inline int qmlRegister##enumName(const char *uri, int major, int minor) \
{ \
return qmlRegisterUncreatableMetaObject(enumName::staticMetaObject, uri, major, minor, \
#enumName, "Access to enums & flags only"); \
} \
inline int qRegisterMetaType() \
{ \
return ::qRegisterMetaType<enumName::Value>(#enumName); \
} \
inline void registerEnum(const char *uri, int major, int minor) \
{ \
enumName::qmlRegister##enumName(uri, major, minor); \
enumName::qRegisterMetaType(); \
} \
}
// 宏定义 L_DECLARE_ENUM 用于声明枚举类型及其相关函数
#define L_DECLARE_ENUM(enumName, ...) \
namespace enumName { \
Q_NAMESPACE \
enum Value { __VA_ARGS__ }; \
Q_ENUM_NS(Value) \
// 注册 QML 中的枚举类型
inline int qmlRegister##enumName(const char *uri, int major, int minor)
{
return qmlRegisterUncreatableMetaObject(enumName::staticMetaObject, uri, major, minor,
#enumName, "Access to enums & flags only");
} // 注册元类型
inline int qRegisterMetaType()
{
return ::qRegisterMetaType<enumName::Value>(#enumName);
} // 注册枚举类型
inline void registerEnum(const char *uri, int major, int minor)
{
enumName::qmlRegister##enumName(uri, major, minor);
enumName::qRegisterMetaType();
}
}
#endif // LQTUTILS_ENUM_H
#endif // LQTUTILS_ENUM_H

@ -3,16 +3,11 @@
* Just a meantime project to see the ability of qt, the framework that my OS might be based on
* And for those linux users that believe in the power of notes
*********************************************************************************************/
#include "mainwindow.h"
#include "singleinstance.h"
#include <QApplication>
#include <QFontDatabase>
// 主函数,程序入口
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// Set application information
// 设置应用程序信息
app.setApplicationName("Notes");
app.setApplicationVersion(APP_VERSION);
@ -24,6 +19,7 @@ int main(int argc, char *argv[])
app.setAttribute(Qt::AA_DisableWindowContextHelpButton);
#endif
// 尝试加载字体并报告错误
if (QFontDatabase::addApplicationFont(":/fonts/fontawesome/fa-solid-900.ttf") < 0)
qWarning() << "FontAwesome Solid cannot be loaded !";
@ -36,7 +32,7 @@ int main(int argc, char *argv[])
if (QFontDatabase::addApplicationFont(":/fonts/material/material-symbols-outlined.ttf") < 0)
qWarning() << "Material Symbols cannot be loaded !";
// Load fonts from resources
// 从资源加载字体
// Roboto
QFontDatabase::addApplicationFont(":/fonts/roboto-hinted/Roboto-Bold.ttf");
QFontDatabase::addApplicationFont(":/fonts/roboto-hinted/Roboto-Medium.ttf");
@ -56,38 +52,38 @@ int main(int argc, char *argv[])
QFontDatabase::addApplicationFont(":/fonts/sourcesanspro/SourceSansPro-SemiBold.ttf");
QFontDatabase::addApplicationFont(":/fonts/sourcesanspro/SourceSansPro-SemiBoldItalic.ttf");
// Trykker
// Trykker 字体
QFontDatabase::addApplicationFont(":/fonts/trykker/Trykker-Regular.ttf");
// Mate
// Mate 字体
QFontDatabase::addApplicationFont(":/fonts/mate/Mate-Regular.ttf");
QFontDatabase::addApplicationFont(":/fonts/mate/Mate-Italic.ttf");
// PT Serif
// PT Serif 字体
QFontDatabase::addApplicationFont(":/fonts/ptserif/PTSerif-Regular.ttf");
QFontDatabase::addApplicationFont(":/fonts/ptserif/PTSerif-Italic.ttf");
QFontDatabase::addApplicationFont(":/fonts/ptserif/PTSerif-Bold.ttf");
QFontDatabase::addApplicationFont(":/fonts/ptserif/PTSerif-BoldItalic.ttf");
// iA Mono
// iA Mono 字体
QFontDatabase::addApplicationFont(":/fonts/iamono/iAWriterMonoS-Regular.ttf");
QFontDatabase::addApplicationFont(":/fonts/iamono/iAWriterMonoS-Italic.ttf");
QFontDatabase::addApplicationFont(":/fonts/iamono/iAWriterMonoS-Bold.ttf");
QFontDatabase::addApplicationFont(":/fonts/iamono/iAWriterMonoS-BoldItalic.ttf");
// iA Duo
// iA Duo 字体
QFontDatabase::addApplicationFont(":/fonts/iaduo/iAWriterDuoS-Regular.ttf");
QFontDatabase::addApplicationFont(":/fonts/iaduo/iAWriterDuoS-Italic.ttf");
QFontDatabase::addApplicationFont(":/fonts/iaduo/iAWriterDuoS-Bold.ttf");
QFontDatabase::addApplicationFont(":/fonts/iaduo/iAWriterDuoS-BoldItalic.ttf");
// iA Quattro
// iA Quattro 字体
QFontDatabase::addApplicationFont(":/fonts/iaquattro/iAWriterQuattroS-Regular.ttf");
QFontDatabase::addApplicationFont(":/fonts/iaquattro/iAWriterQuattroS-Italic.ttf");
QFontDatabase::addApplicationFont(":/fonts/iaquattro/iAWriterQuattroS-Bold.ttf");
QFontDatabase::addApplicationFont(":/fonts/iaquattro/iAWriterQuattroS-BoldItalic.ttf");
// Prevent many instances of the app to be launched
// 防止多个应用实例启动
QString name = "com.awsomeness.notes";
SingleInstance instance;
if (instance.hasPrevious(name)) {
@ -96,13 +92,13 @@ int main(int argc, char *argv[])
instance.listen(name);
// Create and Show the app
// 创建并显示应用程序窗口
MainWindow w;
w.show();
// Bring the Notes window to the front
// 将Notes窗口置于最前
QObject::connect(&instance, &SingleInstance::newInstance, &w,
[&]() { (&w)->setMainWindowVisibility(true); });
return app.exec();
}
}

File diff suppressed because it is too large Load Diff

@ -6,7 +6,6 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QtGui>
#include <QtCore>
@ -52,12 +51,15 @@
#include "nodetreeview.h"
#include "editorsettingsoptions.h"
// 定义订阅状态枚举
L_DECLARE_ENUM(SubscriptionStatus, NoSubscription, Active, ActivationLimitReached, Expired, Invalid,
EnteredGracePeriod, GracePeriodOver, NoInternetConnection, UnknownError)
namespace Ui {
class MainWindow;
}
// 前向声明类
class TreeViewLogic;
class ListViewLogic;
class NoteEditorLogic;
@ -75,6 +77,8 @@ using MainWindowBase = CFramelessWindow;
#else
using MainWindowBase = QMainWindow;
#endif
// 主窗口类
class MainWindow : public MainWindowBase
{
Q_OBJECT
@ -109,129 +113,232 @@ public:
Q_ENUM(ShadowSide)
Q_ENUM(StretchSide)
// 构造函数
explicit MainWindow(QWidget *parent = nullptr);
// 析构函数
~MainWindow() override;
// 设置主窗口可见性
void setMainWindowVisibility(bool state);
public slots:
// 保存最近选择的文件夹标签
void saveLastSelectedFolderTags(bool isFolder, const QString &folderPath,
const QSet<int> &tagId);
// 保存展开的文件夹
void saveExpandedFolder(const QStringList &folderPaths);
// 保存最近选择的笔记
void saveLastSelectedNote(const QSet<int> &notesId);
// 从样式按钮改变编辑器字体类型
void changeEditorFontTypeFromStyleButtons(FontTypeface::Value fontType, int chosenFontIndex);
// 从样式按钮改变编辑器字体大小
void changeEditorFontSizeFromStyleButtons(FontSizeAction::Value fontSizeAction);
// 从样式按钮改变编辑器文本宽度
void changeEditorTextWidthFromStyleButtons(EditorTextWidth::Value editorTextWidth);
// 重置编辑器设置
void resetEditorSettings();
// 设置主题
void setTheme(Theme::Value theme);
// 设置看板可见性
void setKanbanVisibility(bool isVisible);
// 折叠笔记列表
void collapseNoteList();
// 展开笔记列表
void expandNoteList();
// 折叠文件夹树
void collapseFolderTree();
// 展开文件夹树
void expandFolderTree();
// 设置Markdown启用状态
void setMarkdownEnabled(bool isMarkdownEnabled);
// 设置窗口置顶
void stayOnTop(bool checked);
// 将当前笔记移至回收站
void moveCurrentNoteToTrash();
// 切换编辑器设置
void toggleEditorSettings();
// 从快速视图设置编辑器设置可见性
void setEditorSettingsFromQuickViewVisibility(bool isVisible);
// 设置编辑器设置滚动条位置
void setEditorSettingsScrollBarPosition(double position);
// 设置激活成功的状态
void setActivationSuccessful(QString licenseKey, bool removeGracePeriodStartedDate = true);
// 检查专业版
void checkProVersion();
// 获取用户许可密钥
QVariant getUserLicenseKey();
protected:
// 重新绘制事件
void paintEvent(QPaintEvent *event) override;
// 重新调整事件
void resizeEvent(QResizeEvent *event) override;
// 关闭事件
void closeEvent(QCloseEvent *event) override;
// 鼠标按下事件
void mousePressEvent(QMouseEvent *event) override;
// 鼠标移动事件
void mouseMoveEvent(QMouseEvent *event) override;
// 移动事件
void moveEvent(QMoveEvent *event) override;
// 鼠标释放事件
void mouseReleaseEvent(QMouseEvent *event) override;
// 鼠标双击事件
void mouseDoubleClickEvent(QMouseEvent *event) override;
// 离开事件
void leaveEvent(QEvent *) override;
// 变更事件
void changeEvent(QEvent *event) override;
// 事件过滤器
bool eventFilter(QObject *object, QEvent *event) override;
private:
// UI界面指针
Ui::MainWindow *ui;
// 设置数据库指针
QSettings *m_settingsDatabase;
// 清除按钮
QToolButton *m_clearButton;
// 搜索按钮
QToolButton *m_searchButton;
// 绿色最大化按钮
QPushButton *m_greenMaximizeButton;
// 红色关闭按钮
QPushButton *m_redCloseButton;
// 黄色最小化按钮
QPushButton *m_yellowMinimizeButton;
// 交通灯布局
QHBoxLayout m_trafficLightLayout;
// 新笔记按钮
QPushButton *m_newNoteButton;
// 点点按钮
QPushButton *m_dotsButton;
// 全局设置按钮
QPushButton *m_globalSettingsButton;
// 切换树视图按钮
QPushButton *m_toggleTreeViewButton;
// 切换文本视图按钮
QPushButton *m_switchToTextViewButton;
// 切换看板视图按钮
QPushButton *m_switchToKanbanViewButton;
// 文本编辑器
CustomDocument *m_textEdit;
// 笔记编辑器逻辑
NoteEditorLogic *m_noteEditorLogic;
// 搜索框
QLineEdit *m_searchEdit;
// 编辑器日期标签
QLabel *m_editorDateLabel;
// 分割器
QSplitter *m_splitter;
// 笔记列表小部件
QWidget *m_noteListWidget;
// 文件夹小部件
QWidget *m_foldersWidget;
// 系统托盘图标
QSystemTrayIcon *m_trayIcon;
#if !defined(Q_OS_MAC)
// 恢复动作
QAction *m_restoreAction;
// 退出动作
QAction *m_quitAction;
#endif
// 笔记列表视图
NoteListView *m_listView;
// 笔记列表模型
NoteListModel *m_listModel;
// 列表视图逻辑
ListViewLogic *m_listViewLogic;
// 节点树视图
NodeTreeView *m_treeView;
// 节点树模型
NodeTreeModel *m_treeModel;
// 树视图逻辑
TreeViewLogic *m_treeViewLogic;
#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
// 看板快速视图
QQuickView m_kanbanQuickView;
// 看板小部件
QWidget *m_kanbanWidget;
#endif
// 编辑器设置快速视图
QQuickView m_editorSettingsQuickView;
// 编辑器设置小部件
QWidget *m_editorSettingsWidget;
// 标签池
TagPool *m_tagPool;
// 数据库管理器
DBManager *m_dbManager;
// 数据库线程
QThread *m_dbThread;
// 分割器样式
SplitterStyle *m_splitterStyle;
#if defined(UPDATE_CHECKER)
// 更新窗口
UpdaterWindow m_updater;
#endif
// 关于窗口
AboutWindow m_aboutWindow;
// 拉伸边
StretchSide m_stretchSide;
// 自启动
Autostart m_autostart;
// 鼠标按下坐标
int m_mousePressX;
int m_mousePressY;
// 回收站计数器
int m_trashCounter;
// 布局边距
int m_layoutMargin;
// 阴影宽度
int m_shadowWidth;
// 节点树宽度
int m_nodeTreeWidth;
// 小编辑器宽度
int m_smallEditorWidth;
// 大编辑器宽度
int m_largeEditorWidth;
// 能否移动窗口
bool m_canMoveWindow;
// 能否调整窗口大小
bool m_canStretchWindow;
// 临时标志
bool m_isTemp;
// 列表视图滚动条隐藏标志
bool m_isListViewScrollBarHidden;
// 操作运行标志
bool m_isOperationRunning;
#if defined(UPDATE_CHECKER)
// 不显示更新窗口标志
bool m_dontShowUpdateWindow;
#endif
// 始终置顶标志
bool m_alwaysStayOnTop;
// 使用本机窗口框架标志
bool m_useNativeWindowFrame;
// 隐藏到托盘标志
bool m_hideToTray;
// 样式表
QString m_styleSheet;
// Serif字体列表
QStringList m_listOfSerifFonts;
// Sans Serif字体列表
QStringList m_listOfSansSerifFonts;
// Mono字体列表
QStringList m_listOfMonoFonts;
// 选择的Serif字体索引
int m_chosenSerifFontIndex;
// 选择的Sans Serif字体索引
int m_chosenSansSerifFontIndex;
// 选择的Mono字体索引
int m_chosenMonoFontIndex;
// 编辑器中等字体大小
int m_editorMediumFontSize;
// 当前字体大小
int m_currentFontPointSize;
struct m_charsLimitPerFont
{
@ -239,166 +346,309 @@ private:
int serif;
int sansSerif;
} m_currentCharsLimitPerFont;
// 当前字体类型
FontTypeface::Value m_currentFontTypeface;
// 当前字体家族
QString m_currentFontFamily;
// 当前选定字体
QFont m_currentSelectedFont;
// 显示字体
QString m_displayFont;
// 当前主题
Theme::Value m_currentTheme;
// 当前编辑器文本颜色
QColor m_currentEditorTextColor;
// 非编辑器小部件可见性标志
bool m_areNonEditorWidgetsVisible;
#if !defined(Q_OS_MAC)
// 文本编辑滚动条定时器
QTimer *m_textEditScrollBarTimer;
// 文本编辑滚动条定时器持续时间
int m_textEditScrollBarTimerDuration;
#endif
// 帧右上小部件可见性标志
bool m_isFrameRightTopWidgetsVisible;
// 从快速视图可见的编辑器设置标志
bool m_isEditorSettingsFromQuickViewVisible;
// 专业版激活标志
bool m_isProVersionActivated;
// 本地许可数据
QSettings *m_localLicenseData;
// 支付详情
QJsonObject m_paymentDetails;
// 订阅状态
SubscriptionStatus::Value m_subscriptionStatus;
// 订阅窗口快速视图
QQuickView m_subscriptionWindowQuickView;
// 订阅窗口小部件
QWidget *m_subscriptionWindowWidget;
// 订阅窗口引擎
QQmlApplicationEngine m_subscriptionWindowEngine;
// 订阅窗口
QWindow *m_subscriptionWindow;
// 购买数据备用1
QString m_purchaseDataAlt1;
// 购买数据备用2
QString m_purchaseDataAlt2;
// 数据缓冲区
QByteArray *m_dataBuffer;
// 网络访问管理器
QNetworkAccessManager *m_netManager;
// 备用请求1
QNetworkRequest m_reqAlt1;
// 备用请求2
QNetworkRequest m_reqAlt2;
// 第一次尝试的网络购买数据回复
QNetworkReply *m_netPurchaseDataReplyFirstAttempt;
// 第二次尝试的网络购买数据回复
QNetworkReply *m_netPurchaseDataReplySecondAttempt;
// 用户许可密钥
QString m_userLicenseKey;
// 主菜单
QMenu m_mainMenu;
// 购买或管理订阅动作
QAction *m_buyOrManageSubscriptionAction;
// 检查格式是否已应用
bool alreadyAppliedFormat(const QString &formatChars);
// 应用格式
void applyFormat(const QString &formatChars);
// 设置主窗口
void setupMainWindow();
// 设置字体
void setupFonts();
// 设置托盘图标
void setupTrayIcon();
// 设置快捷键
void setupKeyboardShortcuts();
// 设置分割器
void setupSplitter();
// 设置按钮
void setupButtons();
// 设置信号与槽
void setupSignalsSlots();
#if defined(UPDATE_CHECKER)
// 自动检查更新
void autoCheckForUpdates();
#endif
// 设置搜索框
void setupSearchEdit();
// 设置订阅窗口
void setupSubscrirptionWindow();
void setupEditorSettings();
// 设置编辑器样式表
void setupTextEditStyleSheet(int paddingLeft, int paddingRight);
// 对齐文本编辑器文本
void alignTextEditText();
// 设置文本编辑器
void setupTextEdit();
#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
// 设置看板视图
void setupKanbanView();
#endif
// 设置数据库
void setupDatabases();
// 设置模型视图
void setupModelView();
// 设置全局设置菜单
void setupGlobalSettingsMenu();
// 初始化设置数据库
void initializeSettingsDatabase();
// 为滚动区域设置布局
void setLayoutForScrollArea();
// 设置按钮和字段的启用状态
void setButtonsAndFieldsEnabled(bool doEnable);
// 重置格式
void resetFormat(const QString &formatChars);
// 恢复状态
void restoreStates();
// 从版本0.9.0迁移
void migrateFromV0_9_0();
// 执行导入
void executeImport(const bool replace);
// 从版本0.9.0迁移笔记
void migrateNoteFromV0_9_0(const QString &notePath);
// 从版本0.9.0迁移回收站
void migrateTrashFromV0_9_0(const QString &trashPath);
// 根据字体类型设置当前字体
void setCurrentFontBasedOnTypeface(FontTypeface::Value selectedFontTypeFace);
// 设置右侧小部件可见性
void setVisibilityOfFrameRightWidgets(bool isVisible);
// 设置非编辑器小部件可见性
void setVisibilityOfFrameRightNonEditor(bool isVisible);
// 设置窗口按钮可见性
void setWindowButtonsVisible(bool isVisible);
// 显示编辑器设置
void showEditorSettings();
// 更新选定的编辑器设置选项
void updateSelectedOptionsEditorSettings();
// 添加阴影
void dropShadow(QPainter &painter, ShadowType type, ShadowSide side);
// 使用渐变填充矩形
void fillRectWithGradient(QPainter &painter, QRect rect, QGradient &gradient);
// 高斯分布
double gaussianDist(double x, const double center, double sigma) const;
// 调整并定位编辑器设置窗口
void resizeAndPositionEditorSettingsWindow();
// 获取支付详情信号与槽
void getPaymentDetailsSignalsSlots();
// 验证许可证信号与槽
void verifyLicenseSignalsSlots();
// 获取订阅状态
void getSubscriptionStatus();
// 设置边距
void setMargins(QMargins margins);
private slots:
// 初始化数据
void InitData();
// 系统托盘图标激活
void onSystemTrayIconActivated(QSystemTrayIcon::ActivationReason reason);
// 新笔记按钮点击
void onNewNoteButtonClicked();
// 点点按钮点击
void onDotsButtonClicked();
// 切换到看板视图按钮点击
void onSwitchToKanbanViewButtonClicked();
// 全局设置按钮点击
void onGlobalSettingsButtonClicked();
// 清除按钮点击
void onClearButtonClicked();
// 绿色最大化按钮按下
void onGreenMaximizeButtonPressed();
// 黄色最小化按钮按下
void onYellowMinimizeButtonPressed();
// 红色关闭按钮按下
void onRedCloseButtonPressed();
// 绿色最大化按钮点击
void onGreenMaximizeButtonClicked();
// 黄色最小化按钮点击
void onYellowMinimizeButtonClicked();
// 红色关闭按钮点击
void onRedCloseButtonClicked();
// 重置块格式
void resetBlockFormat();
// 创建新笔记
void createNewNote();
// 向下选择笔记
void selectNoteDown();
// 向上选择笔记
void selectNoteUp();
// 设置文本焦点
void setFocusOnText();
// 全屏窗口
void fullscreenWindow();
// 制作代码
void makeCode();
// 制作粗体
void makeBold();
// 制作斜体
void makeItalic();
// 制作删除线
void makeStrikethrough();
// 最大化窗口
void maximizeWindow();
// 最小化窗口
void minimizeWindow();
// 退出应用程序
void QuitApplication();
#if defined(UPDATE_CHECKER)
// 检查更新
void checkForUpdates();
#endif
// 切换笔记列表
void toggleNoteList();
// 切换文件夹树
void toggleFolderTree();
// 导入笔记文件
void importNotesFile();
// 导出笔记文件
void exportNotesFile();
// 恢复笔记文件
void restoreNotesFile();
// 增加标题
void increaseHeading();
// 减小标题
void decreaseHeading();
// 设置标题
void setHeading(int level);
// 设置使用本机窗口框架
void setUseNativeWindowFrame(bool useNativeWindowFrame);
// 设置隐藏到托盘
void setHideToTray(bool enabled);
// 切换始终置顶
void toggleStayOnTop();
// 搜索框回车按下
void onSearchEditReturnPressed();
// 删除选中笔记
void deleteSelectedNote();
// 清除搜索框
void clearSearch();
// 显示错误信息
void showErrorMessage(const QString &title, const QString &content);
// 设置笔记列表加载状态
void setNoteListLoading();
// 选择所有笔记
void selectAllNotesInList();
// 更新框架
void updateFrame();
// 检查是否是标题栏
bool isTitleBar(int x, int y) const;
// 打开订阅窗口
void openSubscriptionWindow();
signals:
// 请求节点树
void requestNodesTree();
// 请求打开数据库管理器
void requestOpenDBManager(const QString &path, bool doCreate);
// 请求恢复笔记
void requestRestoreNotes(const QString &filePath);
// 请求导入笔记
void requestImportNotes(const QString &filePath);
// 请求导出笔记
void requestExportNotes(QString fileName);
// 请求从版本0.9.0迁移笔记
void requestMigrateNotesFromV0_9_0(QVector<NodeData> &noteList);
// 请求从版本0.9.0迁移回收站
void requestMigrateTrashFromV0_9_0(QVector<NodeData> &noteList);
// 请求从版本1.5.0迁移笔记
void requestMigrateNotesFromV1_5_0(const QString &path);
// 请求更改数据库路径
void requestChangeDatabasePath(const QString &newPath);
// 主题变化信号
void themeChanged(QVariant theme);
// 平台设置信号
void platformSet(QVariant platform);
// Qt版本设置信号
void qtVersionSet(QVariant qtVersion);
// 编辑器设置显示信号
void editorSettingsShowed(QVariant data);
// 主窗口大小改变信号
void mainWindowResized(QVariant data);
// 主窗口移动信号
void mainWindowMoved(QVariant data);
// 设置显示字体信号
void displayFontSet(QVariant data);
// 设置改变信号
void settingsChanged(QVariant data);
// 字体改变信号
void fontsChanged(QVariant data);
// 切换编辑器设置快捷键触发信号
void toggleEditorSettingsKeyboardShorcutFired();
// 编辑器设置滚动条位置改变信号
void editorSettingsScrollBarPositionChanged(QVariant data);
// 专业版检查信号
void proVersionCheck(QVariant data);
// 尝试获取第二种购买数据
void tryPurchaseDataSecondAlternative();
// 远程获取支付详情完成信号
void fetchingPaymentDetailsRemotelyFinished();
// 获取支付详情完成信号
void gettingPaymentDetailsFinished();
// 订阅状态改变信号
void subscriptionStatusChanged(QVariant subscriptionStatus);
};
#endif // MAINWINDOW_H
#endif // MAINWINDOW_H

@ -1,6 +1,7 @@
#include "nodedata.h"
#include <QDataStream>
// NodeData 类的构造函数,初始化成员变量
NodeData::NodeData()
: m_id{ SpecialNodeID::InvalidNodeId },
m_isModified(false),
@ -14,206 +15,247 @@ NodeData::NodeData()
{
}
// 获取节点ID
int NodeData::id() const
{
return m_id;
}
// 设置节点ID
void NodeData::setId(int id)
{
m_id = id;
}
// 获取节点的完整标题
QString NodeData::fullTitle() const
{
return m_fullTitle;
}
// 设置节点的完整标题
void NodeData::setFullTitle(const QString &fullTitle)
{
m_fullTitle = fullTitle;
}
// 获取最后修改时间
QDateTime NodeData::lastModificationdateTime() const
{
return m_lastModificationDateTime;
}
// 设置最后修改时间
void NodeData::setLastModificationDateTime(const QDateTime &lastModificationdateTime)
{
m_lastModificationDateTime = lastModificationdateTime;
}
// 获取节点内容
QString NodeData::content() const
{
return m_content;
}
// 设置节点内容
void NodeData::setContent(const QString &content)
{
m_content = content;
}
// 检查节点是否被修改过
bool NodeData::isModified() const
{
return m_isModified;
}
// 设置节点的修改状态
void NodeData::setModified(bool isModified)
{
m_isModified = isModified;
}
// 检查节点是否被选中
bool NodeData::isSelected() const
{
return m_isSelected;
}
// 设置节点的选中状态
void NodeData::setSelected(bool isSelected)
{
m_isSelected = isSelected;
}
// 获取滚动条位置
int NodeData::scrollBarPosition() const
{
return m_scrollBarPosition;
}
// 设置滚动条位置
void NodeData::setScrollBarPosition(int scrollBarPosition)
{
m_scrollBarPosition = scrollBarPosition;
}
// 获取删除时间
QDateTime NodeData::deletionDateTime() const
{
return m_deletionDateTime;
}
// 设置删除时间
void NodeData::setDeletionDateTime(const QDateTime &deletionDateTime)
{
m_deletionDateTime = deletionDateTime;
}
// 获取节点类型
NodeData::Type NodeData::nodeType() const
{
return m_nodeType;
}
// 设置节点类型
void NodeData::setNodeType(NodeData::Type newNodeType)
{
m_nodeType = newNodeType;
}
// 获取父节点ID
int NodeData::parentId() const
{
return m_parentId;
}
// 设置父节点ID
void NodeData::setParentId(int newParentId)
{
m_parentId = newParentId;
}
// 获取相对位置
int NodeData::relativePosition() const
{
return m_relativePosition;
}
// 设置相对位置
void NodeData::setRelativePosition(int newRelativePosition)
{
m_relativePosition = newRelativePosition;
}
// 获取绝对路径
const QString &NodeData::absolutePath() const
{
return m_absolutePath;
}
// 设置绝对路径
void NodeData::setAbsolutePath(const QString &newAbsolutePath)
{
m_absolutePath = newAbsolutePath;
}
// 获取标签ID集
const QSet<int> &NodeData::tagIds() const
{
return m_tagIds;
}
// 设置标签ID集
void NodeData::setTagIds(const QSet<int> &newTagIds)
{
m_tagIds = newTagIds;
}
// 检查是否为临时节点
bool NodeData::isTempNote() const
{
return m_isTempNote;
}
// 设置临时节点状态
void NodeData::setIsTempNote(bool newIsTempNote)
{
m_isTempNote = newIsTempNote;
}
// 获取父节点名称
const QString &NodeData::parentName() const
{
return m_parentName;
}
// 设置父节点名称
void NodeData::setParentName(const QString &newParentName)
{
m_parentName = newParentName;
}
// 检查是否为固定节点
bool NodeData::isPinnedNote() const
{
return m_isPinnedNote;
}
// 设置固定节点状态
void NodeData::setIsPinnedNote(bool newIsPinnedNote)
{
m_isPinnedNote = newIsPinnedNote;
}
// 获取标签列表滚动条位置
int NodeData::tagListScrollBarPos() const
{
return m_tagListScrollBarPos;
}
// 设置标签列表滚动条位置
void NodeData::setTagListScrollBarPos(int newTagListScrollBarPos)
{
m_tagListScrollBarPos = newTagListScrollBarPos;
}
// 获取相对位置AN
int NodeData::relativePosAN() const
{
return m_relativePosAN;
}
// 设置相对位置AN
void NodeData::setRelativePosAN(int newRelativePosAN)
{
m_relativePosAN = newRelativePosAN;
}
// 获取子节点计数
int NodeData::childNotesCount() const
{
return m_childNotesCount;
}
// 设置子节点计数
void NodeData::setChildNotesCount(int newChildCount)
{
m_childNotesCount = newChildCount;
}
// 获取创建时间
QDateTime NodeData::creationDateTime() const
{
return m_creationDateTime;
}
// 设置创建时间
void NodeData::setCreationDateTime(const QDateTime &creationDateTime)
{
m_creationDateTime = creationDateTime;
}
// 重载流提取运算符,读取 NodeData 对象
QDataStream &operator>>(QDataStream &stream, NodeData &nodeData)
{
int id;
@ -230,6 +272,7 @@ QDataStream &operator>>(QDataStream &stream, NodeData &nodeData)
return stream;
}
// 重载流提取运算符,读取 NodeData 指针对象
QDataStream &operator>>(QDataStream &stream, NodeData *&nodeData)
{
nodeData = new NodeData();
@ -245,4 +288,4 @@ QDataStream &operator>>(QDataStream &stream, NodeData *&nodeData)
nodeData->setCreationDateTime(creationDateTime);
nodeData->setContent(content);
return stream;
}
}

@ -14,6 +14,7 @@ enum Value {
};
}
// NodeData类用于表示节点数据
class NodeData
{
public:
@ -21,64 +22,104 @@ public:
enum Type { Note = 0, Folder };
// 获取节点ID
int id() const;
// 设置节点ID
void setId(int id);
// 获取完整标题
QString fullTitle() const;
// 设置完整标题
void setFullTitle(const QString &fullTitle);
// 获取最后修改时间
QDateTime lastModificationdateTime() const;
// 设置最后修改时间
void setLastModificationDateTime(const QDateTime &lastModificationdateTime);
// 获取创建时间
QDateTime creationDateTime() const;
// 设置创建时间
void setCreationDateTime(const QDateTime &creationDateTime);
// 获取内容
QString content() const;
// 设置内容
void setContent(const QString &content);
// 判断是否被修改
bool isModified() const;
// 设置修改状态
void setModified(bool isModified);
// 判断是否被选中
bool isSelected() const;
// 设置选中状态
void setSelected(bool isSelected);
// 获取滚动条位置
int scrollBarPosition() const;
// 设置滚动条位置
void setScrollBarPosition(int scrollBarPosition);
// 获取删除时间
QDateTime deletionDateTime() const;
// 设置删除时间
void setDeletionDateTime(const QDateTime &deletionDateTime);
// 获取节点类型
NodeData::Type nodeType() const;
// 设置节点类型
void setNodeType(NodeData::Type newNodeType);
// 获取父节点ID
int parentId() const;
// 设置父节点ID
void setParentId(int newParentId);
// 获取相对位置
int relativePosition() const;
// 设置相对位置
void setRelativePosition(int newRelativePosition);
// 获取绝对路径
const QString &absolutePath() const;
// 设置绝对路径
void setAbsolutePath(const QString &newAbsolutePath);
// 获取标签ID集合
const QSet<int> &tagIds() const;
// 设置标签ID集合
void setTagIds(const QSet<int> &newTagIds);
// 判断是否为临时笔记
bool isTempNote() const;
// 设置临时笔记状态
void setIsTempNote(bool newIsTempNote);
// 获取父节点名称
const QString &parentName() const;
// 设置父节点名称
void setParentName(const QString &newParentName);
// 判断是否为固定笔记
bool isPinnedNote() const;
// 设置固定笔记状态
void setIsPinnedNote(bool newIsPinnedNote);
// 获取标签列表滚动条位置
int tagListScrollBarPos() const;
// 设置标签列表滚动条位置
void setTagListScrollBarPos(int newTagListScrollBarPos);
// 获取相对位置AN
int relativePosAN() const;
// 设置相对位置AN
void setRelativePosAN(int newRelativePosAN);
// 获取子笔记数量
int childNotesCount() const;
// 设置子笔记数量
void setChildNotesCount(int newChildCount);
private:
@ -104,9 +145,12 @@ private:
int m_childNotesCount;
};
// 用于QMetaType的声明
Q_DECLARE_METATYPE(NodeData)
// 重载输入流运算符以读取NodeData对象
QDataStream &operator>>(QDataStream &stream, NodeData &nodeData);
// 重载输入流运算符以读取NodeData指针对象
QDataStream &operator>>(QDataStream &stream, NodeData *&nodeData);
#endif // NODEDATA_H
#endif // NODEDATA_H

@ -1,8 +1,10 @@
#include "nodepath.h"
#include "nodedata.h"
// NodePath类的构造函数初始化路径
NodePath::NodePath(const QString &path) : m_path(path) { }
// 分离路径为QStringList
QStringList NodePath::separate() const
{
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
@ -12,11 +14,13 @@ QStringList NodePath::separate() const
#endif
}
// 获取当前路径
QString NodePath::path() const
{
return m_path;
}
// 获取父路径
NodePath NodePath::parentPath() const
{
auto s = separate();
@ -24,13 +28,15 @@ NodePath NodePath::parentPath() const
return s.join(PATH_SEPARATOR);
}
// 获取所有笔记文件夹的路径
QString NodePath::getAllNoteFolderPath()
{
return PATH_SEPARATOR + QString::number(SpecialNodeID::RootFolder);
}
// 获取回收站文件夹的路径
QString NodePath::getTrashFolderPath()
{
return PATH_SEPARATOR + QString::number(SpecialNodeID::RootFolder) + PATH_SEPARATOR
+ QString::number(SpecialNodeID::TrashFolder);
}
}

@ -9,19 +9,30 @@
#define TAG_MIME "application/x-tagnode"
#define NOTE_MIME "application/x-notenode"
// NodePath 类用于处理节点路径
class NodePath
{
public:
// 构造函数,初始化节点路径
NodePath(const QString &path);
// 将路径分割为字符串列表
QStringList separate() const;
// 获取当前路径
QString path() const;
// 获取父路径
NodePath parentPath() const;
// 获取所有笔记文件夹的路径
static QString getAllNoteFolderPath();
// 获取回收站文件夹的路径
static QString getTrashFolderPath();
private:
QString m_path;
QString m_path; // 存储节点路径
};
#endif // NODEPATH_H
#endif // NODEPATH_H

@ -15,8 +15,9 @@
#include <QFontMetrics>
#include "fontloader.h"
NodeTreeDelegate::NodeTreeDelegate(QTreeView *view, QObject *parent, QListView *listView)
NodeTreeDelegate::NodeTreeDelegate(QTreeView *view, QObject *parent, QListView *listView)// 构造函数初始化NodeTreeDelegate
: QStyledItemDelegate{ parent },
// 根据平台选择合适的字体
#ifdef __APPLE__
m_displayFont(QFont(QStringLiteral("SF Pro Text")).exactMatch()
? QStringLiteral("SF Pro Text")
@ -27,6 +28,7 @@ NodeTreeDelegate::NodeTreeDelegate(QTreeView *view, QObject *parent, QListView *
#else
m_displayFont(QStringLiteral("Roboto")),
#endif
// 根据平台设置字体大小和样式
#ifdef __APPLE__
m_titleFont(m_displayFont, 13, QFont::DemiBold),
m_titleSelectedFont(m_displayFont, 13),
@ -39,7 +41,7 @@ NodeTreeDelegate::NodeTreeDelegate(QTreeView *view, QObject *parent, QListView *
m_dateFont(m_displayFont, 10),
m_separatorFont(m_displayFont, 9, QFont::Normal),
m_numberOfNotesFont(m_displayFont, 9, QFont::DemiBold),
#endif
#endif// 初始化颜色设置
m_titleColor(26, 26, 26),
m_titleSelectedColor(255, 255, 255),
m_dateColor(132, 132, 132),
@ -57,15 +59,15 @@ NodeTreeDelegate::NodeTreeDelegate(QTreeView *view, QObject *parent, QListView *
m_view(view),
m_listView(listView),
m_theme(Theme::Light)
{
{// 构造函数体为空
}
// 设置主题
void NodeTreeDelegate::setTheme(Theme::Value theme)
{
emit themeChanged(theme);
m_theme = theme;
emit themeChanged(theme); // 发出主题变化信号
m_theme = theme; // 更新当前主题
switch (theme) {
case Theme::Light: {
case Theme::Light: {// 设置浅色主题的颜色
m_titleColor = QColor(26, 26, 26);
m_dateColor = QColor(26, 26, 26);
m_defaultColor = QColor(247, 247, 247);
@ -76,7 +78,7 @@ void NodeTreeDelegate::setTheme(Theme::Value theme)
m_numberOfNotesColor = QColor(26, 26, 26, 127);
break;
}
case Theme::Dark: {
case Theme::Dark: {// 设置深色主题的颜色
m_titleColor = QColor(212, 212, 212);
m_dateColor = QColor(212, 212, 212);
m_defaultColor = QColor(25, 25, 25);
@ -87,7 +89,7 @@ void NodeTreeDelegate::setTheme(Theme::Value theme)
m_numberOfNotesColor = QColor(212, 212, 212, 127);
break;
}
case Theme::Sepia: {
case Theme::Sepia: {// 设置棕褐色主题的颜色
m_titleColor = QColor(26, 26, 26);
m_dateColor = QColor(26, 26, 26);
m_defaultColor = QColor(251, 240, 217);
@ -100,27 +102,28 @@ void NodeTreeDelegate::setTheme(Theme::Value theme)
}
}
}
// 绘制函数
void NodeTreeDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
painter->setRenderHint(QPainter::Antialiasing);
painter->setRenderHint(QPainter::Antialiasing);// 开启抗锯齿
auto itemType = static_cast<NodeItem::Type>(index.data(NodeItem::Roles::ItemType).toInt());
// 根据平台设置图标字体大小偏移
#ifdef __APPLE__
int iconPointSizeOffset = 0;
#else
int iconPointSizeOffset = -4;
#endif
painter->fillRect(option.rect, m_currentBackgroundColor);
painter->fillRect(option.rect, m_currentBackgroundColor);// 填充背景色
switch (itemType) {
// 绘制逻辑根据不同的项类型进行分支处理
case NodeItem::Type::RootItem: {
break;
}
case NodeItem::Type::AllNoteButton:
case NodeItem::Type::TrashButton: {
paintBackgroundSelectable(painter, option, index);
paintBackgroundSelectable(painter, option, index);// 绘制可选择背景
auto iconRect = QRect(option.rect.x() + 22,
option.rect.y() + (option.rect.height() - 20) / 2, 18, 20);
QFont previousPainterFont = painter->font();
@ -276,7 +279,7 @@ void NodeTreeDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti
break;
}
case NodeItem::Type::TagItem: {
paintBackgroundSelectable(painter, option, index);
paintBackgroundSelectable(painter, option, index);// 绘制可选择的背景
auto iconRect = QRect(option.rect.x() + 22,
option.rect.y() + (option.rect.height() - 14) / 2, 16, 16);
auto tagColor = index.data(NodeItem::Roles::TagColor).toString();
@ -315,45 +318,45 @@ void NodeTreeDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti
}
}
}
// 绘制可选择的背景
void NodeTreeDelegate::paintBackgroundSelectable(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if ((option.state & QStyle::State_Selected) == QStyle::State_Selected) {
painter->fillRect(option.rect, QBrush(m_ActiveColor));
painter->fillRect(option.rect, QBrush(m_ActiveColor));// 如果选中,填充活动颜色
} else if ((option.state & QStyle::State_MouseOver) == QStyle::State_MouseOver) {
auto treeView = dynamic_cast<NodeTreeView *>(m_view);
auto itemType = static_cast<NodeItem::Type>(index.data(NodeItem::Roles::ItemType).toInt());
if (itemType == NodeItem::Type::TrashButton) {
return;
return;// 垃圾桶按钮不绘制悬停背景
}
if (!treeView->isDragging()) {
painter->fillRect(option.rect, QBrush(m_hoverColor));
painter->fillRect(option.rect, QBrush(m_hoverColor));// 如果悬停,填充悬停颜色
}
}
}
// 计算项的大小提示
QSize NodeTreeDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QSize result = QStyledItemDelegate::sizeHint(option, index);
auto itemType = static_cast<NodeItem::Type>(index.data(NodeItem::Roles::ItemType).toInt());
if (itemType == NodeItem::Type::FolderSeparator) {
result.setHeight(NoteTreeConstant::folderLabelHeight);
result.setHeight(NoteTreeConstant::folderLabelHeight);// 文件夹分隔符的高度
} else if (itemType == NodeItem::Type::TagSeparator) {
result.setHeight(NoteTreeConstant::tagLabelHeight);
result.setHeight(NoteTreeConstant::tagLabelHeight);// 标签分隔符的高度
} else if (itemType == NodeItem::Type::FolderItem || itemType == NodeItem::Type::TrashButton
|| itemType == NodeItem::Type::AllNoteButton) {
result.setHeight(NoteTreeConstant::folderItemHeight);
result.setHeight(NoteTreeConstant::folderItemHeight);// 文件夹项、垃圾桶按钮、所有笔记按钮的高度
} else if (itemType == NodeItem::Type::TagItem) {
result.setHeight(NoteTreeConstant::tagItemHeight);
result.setHeight(NoteTreeConstant::tagItemHeight);// 标签项的高度
} else {
result.setHeight(30);
}
return result;
}
// 创建编辑器
QWidget *NodeTreeDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
@ -445,7 +448,7 @@ QWidget *NodeTreeDelegate::createEditor(QWidget *parent, const QStyleOptionViewI
}
return nullptr;
}
// 更新编辑器的几何形状
void NodeTreeDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{

@ -7,56 +7,80 @@
class QTreeView;
class QListView;
class NodeTreeDelegate : public QStyledItemDelegate
class NodeTreeDelegate : public QStyledItemDelegate// NodeTreeDelegate 类继承自 QStyledItemDelegate
{
Q_OBJECT
public:
Q_OBJECT// Qt 元对象宏,用于信号和槽机制
public:// 构造函数,初始化 NodeTreeDelegate
explicit NodeTreeDelegate(QTreeView *view, QObject *parent = nullptr,
QListView *listView = nullptr);
void setTheme(Theme::Value theme);
void setTheme(Theme::Value theme);// 设置主题
signals:
void addFolderRequested();
void addTagRequested();
void themeChanged(Theme::Value theme);
void addFolderRequested();// 信号:请求添加文件夹
void addTagRequested();// 信号:请求添加标签
void themeChanged(Theme::Value theme);// 信号:主题变化
// QAbstractItemDelegate interface
// 实现 QAbstractItemDelegate 接口
public:
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option,
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option,// 绘制项
const QModelIndex &index) const override;
virtual QSize sizeHint(const QStyleOptionViewItem &option,
virtual QSize sizeHint(const QStyleOptionViewItem &option,// 计算项的大小提示
const QModelIndex &index) const override;
virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
const QModelIndex &index) const override;// 创建编辑器
virtual void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
const QModelIndex &index) const override;// 更新编辑器的几何形状
private:
void paintBackgroundSelectable(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const;
const QModelIndex &index) const;// 绘制可选择的背景
private:
// 显示字体名称
QString m_displayFont;
// 标题字体
QFont m_titleFont;
// 选中标题字体
QFont m_titleSelectedFont;
// 日期字体
QFont m_dateFont;
// 分隔符字体
QFont m_separatorFont;
// 笔记数量字体
QFont m_numberOfNotesFont;
// 标题颜色
QColor m_titleColor;
// 选中标题颜色
QColor m_titleSelectedColor;
// 日期颜色
QColor m_dateColor;
// 活动颜色
QColor m_ActiveColor;
// 非活动颜色
QColor m_notActiveColor;
// 悬停颜色
QColor m_hoverColor;
// 应用程序非活动颜色
QColor m_applicationInactiveColor;
// 分隔符颜色
QColor m_separatorColor;
// 默认颜色
QColor m_defaultColor;
// 分隔符文本颜色
QColor m_separatorTextColor;
// 当前背景颜色
QColor m_currentBackgroundColor;
// 笔记数量颜色
QColor m_numberOfNotesColor;
// 选中笔记数量颜色
QColor m_numberOfNotesSelectedColor;
// 文件夹图标颜色
QColor m_folderIconColor;
// 树视图指针
QTreeView *m_view;
// 列表视图指针
QListView *m_listView;
// 主题值
Theme::Value m_theme;
};

@ -10,121 +10,126 @@
#include "nodedata.h"
#include "dbmanager.h"
#include "nodepath.h"
// NodeItem 命名空间,定义角色和类型枚举
namespace NodeItem {
enum Roles {
ItemType = Qt::UserRole,
DisplayText = Qt::DisplayRole,
Icon = Qt::DecorationRole,
TagColor = Qt::UserRole + 1,
IsExpandable,
AbsPath,
RelPos,
ChildCount,
NodeId
ItemType = Qt::UserRole, // 项类型
DisplayText = Qt::DisplayRole, // 显示文本
Icon = Qt::DecorationRole, // 图标
TagColor = Qt::UserRole + 1, // 标签颜色
IsExpandable, // 是否可展开
AbsPath, // 绝对路径
RelPos, // 相对位置
ChildCount, // 子项数量
NodeId // 节点ID
};
enum Type {
AllNoteButton = 1,
// We store this enum inside QVariant,
// and an invalid QVariant conversion return 0
TrashButton,
FolderSeparator,
TagSeparator,
FolderItem,
NoteItem,
TagItem,
RootItem
AllNoteButton = 1, // 所有笔记按钮
TrashButton, // 垃圾桶按钮
FolderSeparator, // 文件夹分隔符
TagSeparator, // 标签分隔符
FolderItem, // 文件夹项
NoteItem, // 笔记项
TagItem, // 标签项
RootItem // 根项
};
} // namespace NodeItem
class NodeTreeItem
class NodeTreeItem// NodeTreeItem 类,表示树中的一个节点
{
public:
explicit NodeTreeItem(const QHash<NodeItem::Roles, QVariant> &data,
NodeTreeItem *parentItem = nullptr);
~NodeTreeItem();
NodeTreeItem *parentItem = nullptr); // 构造函数
~NodeTreeItem(); // 析构函数
void appendChild(NodeTreeItem *child);
void insertChild(int row, NodeTreeItem *child);
NodeTreeItem *child(int row);
void removeChild(int row);
NodeTreeItem *takeChildAt(int row);
int childCount() const;
int columnCount() const;
int recursiveNodeCount() const;
void recursiveUpdateFolderPath(const QString &oldP, const QString &newP);
QVariant data(NodeItem::Roles role) const;
void setData(NodeItem::Roles role, const QVariant &d);
int row() const;
NodeTreeItem *parentItem();
void setParentItem(NodeTreeItem *parentItem);
void moveChild(int from, int to);
void recursiveSort();
void appendChild(NodeTreeItem *child); // 添加子节点
void insertChild(int row, NodeTreeItem *child); // 在指定位置插入子节点
NodeTreeItem *child(int row); // 获取指定位置的子节点
void removeChild(int row); // 移除指定位置的子节点
NodeTreeItem *takeChildAt(int row); // 移除并返回指定位置的子节点
int childCount() const; // 获取子节点数量
int columnCount() const; // 获取列数
int recursiveNodeCount() const; // 递归获取节点数量
void recursiveUpdateFolderPath(const QString &oldP, const QString &newP); // 递归更新文件夹路径
QVariant data(NodeItem::Roles role) const; // 获取指定角色的数据
void setData(NodeItem::Roles role, const QVariant &d); // 设置指定角色的数据
int row() const; // 获取当前节点的行号
NodeTreeItem *parentItem(); // 获取父节点
void setParentItem(NodeTreeItem *parentItem); // 设置父节点
void moveChild(int from, int to); // 移动子节点
void recursiveSort(); // 递归排序
private:
QVector<NodeTreeItem *> m_childItems;
QHash<NodeItem::Roles, QVariant> m_itemData;
NodeTreeItem *m_parentItem;
QVector<NodeTreeItem *> m_childItems; // 子节点列表
QHash<NodeItem::Roles, QVariant> m_itemData; // 节点数据
NodeTreeItem *m_parentItem; // 父节点
};
class NodeTreeModel : public QAbstractItemModel
class NodeTreeModel : public QAbstractItemModel// NodeTreeModel 类,继承自 QAbstractItemModel
{
Q_OBJECT
public:
explicit NodeTreeModel(QObject *parent = nullptr);
~NodeTreeModel();
explicit NodeTreeModel(QObject *parent = nullptr); // 构造函数
~NodeTreeModel(); // 析构函数
void appendChildNodeToParent(const QModelIndex &parentIndex,
const QHash<NodeItem::Roles, QVariant> &data);
QModelIndex rootIndex() const;
QModelIndex folderIndexFromIdPath(const NodePath &idPath);
QModelIndex tagIndexFromId(int id);
QString getNewFolderPlaceholderName(const QModelIndex &parentIndex);
QString getNewTagPlaceholderName();
QVector<QModelIndex> getSeparatorIndex();
QModelIndex getDefaultNotesIndex();
QModelIndex getAllNotesButtonIndex();
QModelIndex getTrashButtonIndex();
void deleteRow(const QModelIndex &rowIndex, const QModelIndex &parentIndex);
void
appendChildNodeToParent(const QModelIndex &parentIndex,
const QHash<NodeItem::Roles, QVariant> &data); // 向父节点添加子节点
QModelIndex rootIndex() const; // 获取根节点索引
QModelIndex folderIndexFromIdPath(const NodePath &idPath); // 根据ID路径获取文件夹索引
QModelIndex tagIndexFromId(int id); // 根据ID获取标签索引
QString getNewFolderPlaceholderName(const QModelIndex &parentIndex); // 获取新文件夹占位符名称
QString getNewTagPlaceholderName(); // 获取新标签占位符名称
QVector<QModelIndex> getSeparatorIndex(); // 获取分隔符索引
QModelIndex getDefaultNotesIndex(); // 获取默认笔记索引
QModelIndex getAllNotesButtonIndex(); // 获取所有笔记按钮索引
QModelIndex getTrashButtonIndex(); // 获取垃圾桶按钮索引
void deleteRow(const QModelIndex &rowIndex, const QModelIndex &parentIndex); // 删除行
public slots:
void setTreeData(const NodeTagTreeData &treeData);
void setTreeData(const NodeTagTreeData &treeData); // 设置树数据
// QAbstractItemModel interface
// QAbstractItemModel interface接口实现
public:
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const override;
virtual QModelIndex parent(const QModelIndex &index) const override;
virtual int rowCount(const QModelIndex &parent) const override;
virtual int columnCount(const QModelIndex &parent) const override;
virtual QVariant data(const QModelIndex &index, int role) const override;
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
virtual bool setData(const QModelIndex &index, const QVariant &value, int role) override;
virtual Qt::DropActions supportedDropActions() const override;
virtual Qt::DropActions supportedDragActions() const override;
virtual QStringList mimeTypes() const override;
virtual QMimeData *mimeData(const QModelIndexList &indexes) const override;
virtual QModelIndex index(int row, int column,
const QModelIndex &parent) const override; // 获取索引
virtual QModelIndex parent(const QModelIndex &index) const override; // 获取父索引
virtual int rowCount(const QModelIndex &parent) const override; // 获取行数
virtual int columnCount(const QModelIndex &parent) const override; // 获取列数
virtual QVariant data(const QModelIndex &index, int role) const override; // 获取数据
virtual Qt::ItemFlags flags(const QModelIndex &index) const override; // 获取项目标志
virtual bool setData(const QModelIndex &index, const QVariant &value,
int role) override; // 设置数据
virtual Qt::DropActions supportedDropActions() const override; // 支持的拖放动作
virtual Qt::DropActions supportedDragActions() const override; // 支持的拖动动作
virtual QStringList mimeTypes() const override; // 支持的MIME类型
virtual QMimeData *mimeData(const QModelIndexList &indexes) const override; // 获取MIME数据
virtual bool dropMimeData(const QMimeData *mime, Qt::DropAction action, int row, int column,
const QModelIndex &parent) override;
const QModelIndex &parent) override; // 处理拖放数据
signals:
void topLevelItemLayoutChanged();
void requestExpand(const QString &indexPath);
void requestMoveNode(int nodeId, int targetId);
void requestUpdateNodeRelativePosition(int nodeId, int relativePosition);
void requestUpdateTagRelativePosition(int nodeId, int relativePosition);
void requestUpdateAbsPath(const QString &oldPath, const QString &newPath);
void dropFolderSuccessful(const QString &paths);
void dropTagsSuccessful(const QSet<int> &ids);
void requestMoveFolderToTrash(const QModelIndex &index);
void topLevelItemLayoutChanged(); // 顶层项目布局变化信号
void requestExpand(const QString &indexPath); // 请求展开信号
void requestMoveNode(int nodeId, int targetId); // 请求移动节点信号
void requestUpdateNodeRelativePosition(int nodeId,
int relativePosition); // 请求更新节点相对位置信号
void requestUpdateTagRelativePosition(int nodeId,
int relativePosition); // 请求更新标签相对位置信号
void requestUpdateAbsPath(const QString &oldPath,
const QString &newPath); // 请求更新绝对路径信号
void dropFolderSuccessful(const QString &paths); // 文件夹拖放成功信号
void dropTagsSuccessful(const QSet<int> &ids); // 标签拖放成功信号
void requestMoveFolderToTrash(const QModelIndex &index); // 请求移动文件夹到垃圾桶信号
private:
NodeTreeItem *rootItem;
void loadNodeTree(const QVector<NodeData> &nodeData, NodeTreeItem *rootNode);
void appendAllNotesAndTrashButton(NodeTreeItem *rootNode);
void appendFolderSeparator(NodeTreeItem *rootNode);
void appendTagsSeparator(NodeTreeItem *rootNode);
void loadTagList(const QVector<TagData> &tagData, NodeTreeItem *rootNode);
void updateChildRelativePosition(NodeTreeItem *parent, const NodeItem::Type type);
NodeTreeItem *rootItem; // 根节点
void loadNodeTree(const QVector<NodeData> &nodeData, NodeTreeItem *rootNode); // 加载节点树
void appendAllNotesAndTrashButton(NodeTreeItem *rootNode); // 添加所有笔记和垃圾桶按钮
void appendFolderSeparator(NodeTreeItem *rootNode); // 添加文件夹分隔符
void appendTagsSeparator(NodeTreeItem *rootNode); // 添加标签分隔符
void loadTagList(const QVector<TagData> &tagData, NodeTreeItem *rootNode); // 加载标签列表
void updateChildRelativePosition(NodeTreeItem *parent,
const NodeItem::Type type); // 更新子节点相对位置
};
#endif // NODETREEMODEL_H

@ -10,59 +10,60 @@
#include <QScrollBar>
#include "nodetreeview_p.h"
NodeTreeView::NodeTreeView(QWidget *parent)
NodeTreeView::NodeTreeView(QWidget *parent)// 构造函数,初始化 NodeTreeView
: QTreeView(parent),
m_isContextMenuOpened{ false },
m_isEditing{ false },
m_ignoreThisCurrentLoad{ false },
m_isLastSelectedFolder{ false }
{
setHeaderHidden(true);
setHeaderHidden(true);// 隐藏表头
QFile file(":/styles/nodetreeview.css");
file.open(QFile::ReadOnly);
setStyleSheet(file.readAll());
setStyleSheet(file.readAll());// 设置样式表
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) || defined(Q_OS_WIN) || defined(Q_OS_WINDOWS)
QFile scollBarStyleFile(QStringLiteral(":/styles/components/custom-scrollbar.css"));
scollBarStyleFile.open(QFile::ReadOnly);
QString scrollbarStyleSheet = QString::fromLatin1(scollBarStyleFile.readAll());
verticalScrollBar()->setStyleSheet(scrollbarStyleSheet);
verticalScrollBar()->setStyleSheet(scrollbarStyleSheet);// 设置滚动条样式
#endif
setRootIsDecorated(false);
setMouseTracking(true);
setSelectionMode(QAbstractItemView::MultiSelection);
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &QWidget::customContextMenuRequested, this, &NodeTreeView::onCustomContextMenu);
contextMenu = new QMenu(this);
renameFolderAction = new QAction(tr("Rename Folder"), this);
setRootIsDecorated(false); // 不显示根节点的装饰
setMouseTracking(true); // 启用鼠标跟踪
setSelectionMode(QAbstractItemView::MultiSelection); // 设置选择模式为多选
setContextMenuPolicy(Qt::CustomContextMenu); // 设置上下文菜单策略为自定义
connect(this, &QWidget::customContextMenuRequested, this,
&NodeTreeView::onCustomContextMenu); // 连接自定义上下文菜单信号
contextMenu = new QMenu(this); // 创建上下文菜单
renameFolderAction = new QAction(tr("Rename Folder"), this);// 创建和连接重命名文件夹动作
connect(renameFolderAction, &QAction::triggered, this, [this] {
setIsEditing(true);
emit renameFolderRequested();
});
deleteFolderAction = new QAction(tr("Delete Folder"), this);
deleteFolderAction = new QAction(tr("Delete Folder"), this);// 创建和连接删除文件夹动作
connect(deleteFolderAction, &QAction::triggered, this, &NodeTreeView::onDeleteNodeAction);
addSubfolderAction = new QAction(tr("Add Subfolder"), this);
addSubfolderAction = new QAction(tr("Add Subfolder"), this);// 创建和连接添加子文件夹动作
connect(addSubfolderAction, &QAction::triggered, this, &NodeTreeView::addFolderRequested);
renameTagAction = new QAction(tr("Rename Tag"), this);
renameTagAction = new QAction(tr("Rename Tag"), this);// 创建和连接重命名标签动作
connect(renameTagAction, &QAction::triggered, this, [this] {
setIsEditing(true);
emit renameTagRequested();
});
changeTagColorAction = new QAction(tr("Change Tag Color"), this);
changeTagColorAction = new QAction(tr("Change Tag Color"), this); // 创建和连接更改标签颜色动作
connect(changeTagColorAction, &QAction::triggered, this, &NodeTreeView::onChangeTagColorAction);
deleteTagAction = new QAction(tr("Delete Tag"), this);
deleteTagAction = new QAction(tr("Delete Tag"), this);// 创建和连接删除标签动作
connect(deleteTagAction, &QAction::triggered, this, &NodeTreeView::onDeleteNodeAction);
clearSelectionAction = new QAction(tr("Clear Selection"), this);
clearSelectionAction = new QAction(tr("Clear Selection"), this);// 创建和连接清除选择动作
connect(clearSelectionAction, &QAction::triggered, this, [this] {
closeCurrentEditor();
clearSelection();
setCurrentIndexC(dynamic_cast<NodeTreeModel *>(model())->getAllNotesButtonIndex());
});
contextMenuTimer.setInterval(100);
contextMenuTimer.setInterval(100);// 设置上下文菜单定时器
contextMenuTimer.setSingleShot(true);
connect(&contextMenuTimer, &QTimer::timeout, this, [this] {
if (!m_isEditing) {
@ -70,19 +71,19 @@ NodeTreeView::NodeTreeView(QWidget *parent)
}
});
connect(contextMenu, &QMenu::aboutToHide, this, [this] {
connect(contextMenu, &QMenu::aboutToHide, this, [this] { // 连接上下文菜单隐藏信号
m_isContextMenuOpened = false;
// this signal is emitted before QAction::triggered
contextMenuTimer.start();
});
connect(this, &NodeTreeView::expanded, this, &NodeTreeView::onExpanded);
connect(this, &NodeTreeView::expanded, this, &NodeTreeView::onExpanded);// 连接展开和收起信号
connect(this, &NodeTreeView::collapsed, this, &NodeTreeView::onCollapsed);
setDragEnabled(true);
setAcceptDrops(true);
setSelectionMode(QAbstractItemView::SingleSelection);
setDragEnabled(true); // 启用拖动
setAcceptDrops(true); // 启用接受拖放
setSelectionMode(QAbstractItemView::SingleSelection); // 设置选择模式为单选
}
// 删除节点动作
void NodeTreeView::onDeleteNodeAction()
{
auto itemType = static_cast<NodeItem::Type>(
@ -98,29 +99,29 @@ void NodeTreeView::onDeleteNodeAction()
emit deleteTagRequested(index);
}
}
// 节点展开时的处理
void NodeTreeView::onExpanded(const QModelIndex &index)
{
m_expanded.push_back(index.data(NodeItem::Roles::AbsPath).toString());
emit saveExpand(QStringList::fromVector(m_expanded));
}
// 节点收起时的处理
void NodeTreeView::onCollapsed(const QModelIndex &index)
{
m_expanded.removeAll(index.data(NodeItem::Roles::AbsPath).toString());
emit saveExpand(QStringList::fromVector(m_expanded));
}
// 获取当前编辑的索引
const QModelIndex &NodeTreeView::currentEditingIndex() const
{
return m_currentEditingIndex;
}
// 设置忽略当前加载
void NodeTreeView::setIgnoreThisCurrentLoad(bool newIgnoreThisCurrentLoad)
{
m_ignoreThisCurrentLoad = newIgnoreThisCurrentLoad;
}
// 文件夹拖放成功时的处理
void NodeTreeView::onFolderDropSuccessful(const QString &path)
{
auto m_model = dynamic_cast<NodeTreeModel *>(model());
@ -131,7 +132,7 @@ void NodeTreeView::onFolderDropSuccessful(const QString &path)
setCurrentIndexC(m_model->getAllNotesButtonIndex());
}
}
// 标签拖放成功时的处理
void NodeTreeView::onTagsDropSuccessful(const QSet<int> &ids)
{
auto m_model = dynamic_cast<NodeTreeModel *>(model());
@ -151,18 +152,18 @@ void NodeTreeView::onTagsDropSuccessful(const QSet<int> &ids)
setCurrentIndexC(m_model->getAllNotesButtonIndex());
}
}
// 获取主题
Theme::Value NodeTreeView::theme() const
{
return m_theme;
}
bool NodeTreeView::isDragging() const
bool NodeTreeView::isDragging() const// 是否正在拖动
{
return state() == DraggingState;
}
void NodeTreeView::reExpandC()
void NodeTreeView::reExpandC()// 重新展开节点
{
auto needExpand = std::move(m_expanded);
m_expanded.clear();
@ -176,14 +177,14 @@ void NodeTreeView::reExpandC()
}
}
void NodeTreeView::reExpandC(const QStringList &expanded)
void NodeTreeView::reExpandC(const QStringList &expanded)// 重新展开节点(带参数)
{
m_expanded.clear();
m_expanded = expanded.toVector();
reExpandC();
}
void NodeTreeView::onChangeTagColorAction()
void NodeTreeView::onChangeTagColorAction()// 更改标签颜色动作
{
auto itemType = static_cast<NodeItem::Type>(
m_currentEditingIndex.data(NodeItem::Roles::ItemType).toInt());
@ -193,13 +194,13 @@ void NodeTreeView::onChangeTagColorAction()
}
}
void NodeTreeView::onRequestExpand(const QString &folderPath)
void NodeTreeView::onRequestExpand(const QString &folderPath)// 请求展开节点
{
auto m_model = dynamic_cast<NodeTreeModel *>(model());
expand(m_model->folderIndexFromIdPath(folderPath));
}
void NodeTreeView::onUpdateAbsPath(const QString &oldPath, const QString &newPath)
void NodeTreeView::onUpdateAbsPath(const QString &oldPath, const QString &newPath)// 更新绝对路径
{
std::transform(m_expanded.begin(), m_expanded.end(), m_expanded.begin(), [&](QString s) {
s.replace(s.indexOf(oldPath), oldPath.size(), newPath);
@ -207,7 +208,7 @@ void NodeTreeView::onUpdateAbsPath(const QString &oldPath, const QString &newPat
});
}
void NodeTreeView::updateEditingIndex(QPoint pos)
void NodeTreeView::updateEditingIndex(QPoint pos)// 更新编辑索引
{
auto index = indexAt(pos);
if (indexAt(pos) != m_currentEditingIndex && !m_isContextMenuOpened && !m_isEditing) {
@ -224,13 +225,13 @@ void NodeTreeView::updateEditingIndex(QPoint pos)
}
}
void NodeTreeView::closeCurrentEditor()
void NodeTreeView::closeCurrentEditor()// 关闭当前编辑器
{
closePersistentEditor(m_currentEditingIndex);
m_currentEditingIndex = QModelIndex();
}
void NodeTreeView::selectionChanged(const QItemSelection &selected,
void NodeTreeView::selectionChanged(const QItemSelection &selected,// 选择发生变化时的处理
const QItemSelection &deselected)
{
QTreeView::selectionChanged(selected, deselected);
@ -289,7 +290,7 @@ void NodeTreeView::selectionChanged(const QItemSelection &selected,
}
}
void NodeTreeView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
void NodeTreeView::currentChanged(const QModelIndex &current, const QModelIndex &previous)// 当前索引发生变化时的处理
{
QTreeView::currentChanged(current, previous);
auto itemType = static_cast<NodeItem::Type>(current.data(NodeItem::Roles::ItemType).toInt());
@ -302,7 +303,7 @@ void NodeTreeView::currentChanged(const QModelIndex &current, const QModelIndex
}
}
void NodeTreeView::dragEnterEvent(QDragEnterEvent *event)
void NodeTreeView::dragEnterEvent(QDragEnterEvent *event)// 拖动进入事件
{
if (event->mimeData()->hasFormat(NOTE_MIME)) {
event->acceptProposedAction();
@ -311,7 +312,7 @@ void NodeTreeView::dragEnterEvent(QDragEnterEvent *event)
}
}
void NodeTreeView::dragMoveEvent(QDragMoveEvent *event)
void NodeTreeView::dragMoveEvent(QDragMoveEvent *event)// 拖动移动事件
{
if (event->mimeData()->hasFormat(NOTE_MIME)) {
auto index = indexAt(event->pos());
@ -349,13 +350,13 @@ void NodeTreeView::dragMoveEvent(QDragMoveEvent *event)
}
}
void NodeTreeView::reset()
void NodeTreeView::reset()// 重置视图
{
closeCurrentEditor();
reExpandC();
}
void NodeTreeView::dropEvent(QDropEvent *event)
void NodeTreeView::dropEvent(QDropEvent *event)// 拖放事件
{
if (event->mimeData()->hasFormat(NOTE_MIME)) {
auto dropIndex = indexAt(event->pos());
@ -385,12 +386,12 @@ void NodeTreeView::dropEvent(QDropEvent *event)
}
}
void NodeTreeView::setIsEditing(bool newIsEditing)
void NodeTreeView::setIsEditing(bool newIsEditing)// 设置是否正在编辑
{
m_isEditing = newIsEditing;
}
void NodeTreeView::onRenameFolderFinished(const QString &newName)
void NodeTreeView::onRenameFolderFinished(const QString &newName)// 重命名文件夹完成
{
if (m_currentEditingIndex.isValid()) {
auto itemType = static_cast<NodeItem::Type>(
@ -407,7 +408,7 @@ void NodeTreeView::onRenameFolderFinished(const QString &newName)
}*/
}
void NodeTreeView::onRenameTagFinished(const QString &newName)
void NodeTreeView::onRenameTagFinished(const QString &newName)// 重命名标签完成
{
if (m_currentEditingIndex.isValid()) {
auto itemType = static_cast<NodeItem::Type>(
@ -424,7 +425,7 @@ void NodeTreeView::onRenameTagFinished(const QString &newName)
}*/
}
void NodeTreeView::setCurrentIndexC(const QModelIndex &index)
void NodeTreeView::setCurrentIndexC(const QModelIndex &index)// 设置当前索引
{
setCurrentIndex(index);
clearSelection();
@ -432,18 +433,18 @@ void NodeTreeView::setCurrentIndexC(const QModelIndex &index)
selectionModel()->setCurrentIndex(index, QItemSelectionModel::SelectCurrent);
}
void NodeTreeView::setCurrentIndexNC(const QModelIndex &index)
void NodeTreeView::setCurrentIndexNC(const QModelIndex &index)// 设置当前索引(不改变选择模式)
{
setCurrentIndex(index);
}
void NodeTreeView::setTheme(Theme::Value theme)
void NodeTreeView::setTheme(Theme::Value theme)// 设置主题
{
setCSSThemeAndUpdate(this, theme);
m_theme = theme;
}
void NodeTreeView::onCustomContextMenu(QPoint point)
void NodeTreeView::onCustomContextMenu(QPoint point)// 自定义上下文菜单请求
{
QModelIndex index = indexAt(point);
if (index.isValid()) {
@ -471,7 +472,7 @@ void NodeTreeView::onCustomContextMenu(QPoint point)
}
}
void NodeTreeView::setTreeSeparator(const QVector<QModelIndex> &newTreeSeparator,
void NodeTreeView::setTreeSeparator(const QVector<QModelIndex> &newTreeSeparator,// 设置树分隔符
const QModelIndex &defaultNotesIndex)
{
for (const auto &sep : qAsConst(m_treeSeparator)) {
@ -484,7 +485,7 @@ void NodeTreeView::setTreeSeparator(const QVector<QModelIndex> &newTreeSeparator
m_defaultNotesIndex = defaultNotesIndex;
}
void NodeTreeView::mouseMoveEvent(QMouseEvent *event)
void NodeTreeView::mouseMoveEvent(QMouseEvent *event)// 鼠标移动事件
{
Q_D(NodeTreeView);
QPoint topLeft;
@ -511,7 +512,7 @@ void NodeTreeView::mouseMoveEvent(QMouseEvent *event)
}
}
void NodeTreeView::mousePressEvent(QMouseEvent *event)
void NodeTreeView::mousePressEvent(QMouseEvent *event)// 鼠标按下事件
{
Q_D(NodeTreeView);
d->delayedAutoScroll.stop();
@ -577,7 +578,7 @@ void NodeTreeView::mousePressEvent(QMouseEvent *event)
updateEditingIndex(event->pos());
}
void NodeTreeView::leaveEvent(QEvent *event)
void NodeTreeView::leaveEvent(QEvent *event)// 离开事件
{
if (!m_isContextMenuOpened && !m_isEditing) {
closeCurrentEditor();
@ -585,7 +586,7 @@ void NodeTreeView::leaveEvent(QEvent *event)
}
}
void NodeTreeView::mouseReleaseEvent(QMouseEvent *event)
void NodeTreeView::mouseReleaseEvent(QMouseEvent *event)// 鼠标释放
{
Q_UNUSED(event);
Q_D(NodeTreeView);
@ -612,7 +613,7 @@ void NodeTreeView::mouseReleaseEvent(QMouseEvent *event)
d->pressedIndex = QPersistentModelIndex();
}
void NodeTreeView::mouseDoubleClickEvent(QMouseEvent *event)
void NodeTreeView::mouseDoubleClickEvent(QMouseEvent *event)// 鼠标双击
{
Q_UNUSED(event);
Q_D(NodeTreeView);

@ -14,7 +14,7 @@
#include <QCursor>
#define FIRST_LINE_MAX 80
// 构造函数,根据 Qt 版本不同,参数有所不同
#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
NoteEditorLogic::NoteEditorLogic(CustomDocument *textEdit, QLabel *editorDateLabel,
@ -40,20 +40,24 @@ NoteEditorLogic::NoteEditorLogic(CustomDocument *textEdit, QLabel *editorDateLab
m_spacerColor{ 191, 191, 191 },
m_currentAdaptableEditorPadding{ 0 },
m_currentMinimumEditorPadding{ 0 }
{
{// 连接文本编辑器的文本变化信号
connect(m_textEdit, &QTextEdit::textChanged, this, &NoteEditorLogic::onTextEditTextChanged);
// 连接创建或更新笔记请求信号
connect(this, &NoteEditorLogic::requestCreateUpdateNote, m_dbManager,
&DBManager::onCreateUpdateRequestedNoteContent, Qt::QueuedConnection);
// auto save timer
// auto save timer 自动保存定时器
m_autoSaveTimer.setSingleShot(true);
m_autoSaveTimer.setInterval(50);
connect(&m_autoSaveTimer, &QTimer::timeout, this, [this]() { saveNoteToDB(); });
// 初始化标签列表模型和委托
m_tagListModel = new TagListModel{ this };
m_tagListModel->setTagPool(tagPool);
m_tagListView->setModel(m_tagListModel);
m_tagListDelegate = new TagListDelegate{ this };
m_tagListView->setItemDelegate(m_tagListDelegate);
// 连接标签池数据更新信号
connect(tagPool, &TagPool::dataUpdated, this, [this](int) { showTagListForCurrentNote(); });
// 连接滚动条位置变化信号
connect(m_textEdit->verticalScrollBar(), &QScrollBar::valueChanged, this, [this](int value) {
if (m_currentNotes.size() == 1 && m_currentNotes[0].id() != SpecialNodeID::InvalidNodeId) {
m_currentNotes[0].setScrollBarPosition(value);
@ -63,6 +67,7 @@ NoteEditorLogic::NoteEditorLogic(CustomDocument *textEdit, QLabel *editorDateLab
}
});
#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
// 连接显示看板视图信号
connect(this, &NoteEditorLogic::showKanbanView, this, [this]() {
if (m_kanbanWidget != nullptr) {
emit setVisibilityOfFrameRightNonEditor(false);
@ -76,6 +81,7 @@ NoteEditorLogic::NoteEditorLogic(CustomDocument *textEdit, QLabel *editorDateLab
emit kanbanShown();
}
});
// 连接隐藏看板视图信号
connect(this, &NoteEditorLogic::hideKanbanView, this, [this]() {
if (m_kanbanWidget != nullptr) {
emit setVisibilityOfFrameRightNonEditor(true);
@ -87,17 +93,17 @@ NoteEditorLogic::NoteEditorLogic(CustomDocument *textEdit, QLabel *editorDateLab
});
#endif
}
// 获取是否启用 Markdown
bool NoteEditorLogic::markdownEnabled() const
{
return m_highlighter->document() != nullptr;
}
// 设置是否启用 Markdown
void NoteEditorLogic::setMarkdownEnabled(bool enabled)
{
m_highlighter->setDocument(enabled ? m_textEdit->document() : nullptr);
}
// 在编辑器中显示笔记
void NoteEditorLogic::showNotesInEditor(const QVector<NodeData> &notes)
{
auto currentId = currentEditingNoteId();
@ -200,7 +206,7 @@ void NoteEditorLogic::showNotesInEditor(const QVector<NodeData> &notes)
highlightSearch();
}
}
// 文本编辑器内容变化时的处理
void NoteEditorLogic::onTextEditTextChanged()
{
if (currentEditingNoteId() != SpecialNodeID::InvalidNodeId) {
@ -234,7 +240,7 @@ void NoteEditorLogic::onTextEditTextChanged()
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
// 重新排列文本编辑器中的任务
void NoteEditorLogic::rearrangeTasksInTextEditor(int startLinePosition, int endLinePosition,
int newLinePosition)
{
@ -269,7 +275,7 @@ void NoteEditorLogic::rearrangeTasksInTextEditor(int startLinePosition, int endL
checkForTasksInEditor();
}
// 重新排列文本编辑器中的列
void NoteEditorLogic::rearrangeColumnsInTextEditor(int startLinePosition, int endLinePosition,
int newLinePosition)
{
@ -305,7 +311,7 @@ void NoteEditorLogic::rearrangeColumnsInTextEditor(int startLinePosition, int en
checkForTasksInEditor();
}
// 获取某行的任务数据
QMap<QString, int> NoteEditorLogic::getTaskDataInLine(const QString &line)
{
QStringList taskExpressions = { "- [ ]", "- [x]", "* [ ]", "* [x]", "- [X]", "* [X]" };
@ -325,7 +331,7 @@ QMap<QString, int> NoteEditorLogic::getTaskDataInLine(const QString &line)
return taskMatchLineData;
}
// 检查某行的任务
void NoteEditorLogic::checkTaskInLine(int lineNumber)
{
QTextDocument *document = m_textEdit->document();
@ -343,7 +349,7 @@ void NoteEditorLogic::checkTaskInLine(int lineNumber)
cursor.insertText("x");
}
}
// 取消检查某行的任务
void NoteEditorLogic::uncheckTaskInLine(int lineNumber)
{
QTextDocument *document = m_textEdit->document();
@ -361,7 +367,7 @@ void NoteEditorLogic::uncheckTaskInLine(int lineNumber)
cursor.insertText(" ");
}
}
// 替换某行之间的文本
void NoteEditorLogic::replaceTextBetweenLines(int startLinePosition, int endLinePosition,
QString &newText)
{
@ -373,7 +379,7 @@ void NoteEditorLogic::replaceTextBetweenLines(int startLinePosition, int endLine
cursor.removeSelectedText();
cursor.insertText(newText);
}
// 更新任务文本
void NoteEditorLogic::updateTaskText(int startLinePosition, int endLinePosition,
const QString &newText)
{
@ -419,7 +425,7 @@ void NoteEditorLogic::updateTaskText(int startLinePosition, int endLinePosition,
checkForTasksInEditor();
}
}
// 添加新任务
void NoteEditorLogic::addNewTask(int startLinePosition, const QString newTaskText)
{
QString newText = "\n- [ ] " + newTaskText;
@ -435,7 +441,7 @@ void NoteEditorLogic::addNewTask(int startLinePosition, const QString newTaskTex
checkForTasksInEditor();
}
// 移除某行之间的文本
void NoteEditorLogic::removeTextBetweenLines(int startLinePosition, int endLinePosition)
{
if (startLinePosition < 0 || endLinePosition < startLinePosition) {
@ -453,13 +459,13 @@ void NoteEditorLogic::removeTextBetweenLines(int startLinePosition, int endLineP
}
cursor.removeSelectedText();
}
// 移除任务
void NoteEditorLogic::removeTask(int startLinePosition, int endLinePosition)
{
removeTextBetweenLines(startLinePosition, endLinePosition);
checkForTasksInEditor();
}
// 添加新列
void NoteEditorLogic::addNewColumn(int startLinePosition, const QString &columnTitle)
{
if (startLinePosition < 0)
@ -483,7 +489,7 @@ void NoteEditorLogic::addNewColumn(int startLinePosition, const QString &columnT
checkForTasksInEditor();
}
// 移除列
void NoteEditorLogic::removeColumn(int startLinePosition, int endLinePosition)
{
removeTextBetweenLines(startLinePosition, endLinePosition);
@ -500,7 +506,7 @@ void NoteEditorLogic::removeColumn(int startLinePosition, int endLinePosition)
checkForTasksInEditor();
}
// 更新列标题
void NoteEditorLogic::updateColumnTitle(int lineNumber, const QString &newText)
{
QTextDocument *document = m_textEdit->document();
@ -531,7 +537,7 @@ void NoteEditorLogic::updateColumnTitle(int lineNumber, const QString &newText)
}
}
}
// 向文本编辑器添加无标题列
void NoteEditorLogic::addUntitledColumnToTextEditor(int startLinePosition)
{
QString columnTitle = "# Untitled\n\n";
@ -544,7 +550,7 @@ void NoteEditorLogic::addUntitledColumnToTextEditor(int startLinePosition)
cursor.insertText(columnTitle);
}
}
// 向 JSON 数据添加新列
void NoteEditorLogic::appendNewColumn(QJsonArray &data, QJsonObject &currentColumn,
QString &currentTitle, QJsonArray &tasks)
{
@ -571,7 +577,7 @@ void NoteEditorLogic::appendNewColumn(QJsonArray &data, QJsonObject &currentColu
// {"checked":false,"text":"todo 2", "taskStartine": 4, "taskEndLine": 4}}]
// },
// ])
bool NoteEditorLogic::checkForTasksInEditor()
bool NoteEditorLogic::checkForTasksInEditor()// 检查编辑器中的任务
{
QStringList lines = m_textEdit->toPlainText().split("\n");
QJsonArray data;
@ -657,13 +663,13 @@ bool NoteEditorLogic::checkForTasksInEditor()
return false;
}
#endif
// 将字符串转换为 QDateTime
QDateTime NoteEditorLogic::getQDateTime(const QString &date)
{
QDateTime dateTime = QDateTime::fromString(date, Qt::ISODate);
return dateTime;
}
// 显示当前笔记的标签列表
void NoteEditorLogic::showTagListForCurrentNote()
{
if (currentEditingNoteId() != SpecialNodeID::InvalidNodeId) {
@ -678,7 +684,7 @@ void NoteEditorLogic::showTagListForCurrentNote()
m_tagListView->setVisible(false);
}
bool NoteEditorLogic::isInEditMode() const
bool NoteEditorLogic::isInEditMode() const// 获取是否处于编辑模式
{
if (m_currentNotes.size() == 1) {
return true;
@ -686,26 +692,26 @@ bool NoteEditorLogic::isInEditMode() const
return false;
}
int NoteEditorLogic::currentMinimumEditorPadding() const
int NoteEditorLogic::currentMinimumEditorPadding() const// 获取当前最小编辑器填充
{
return m_currentMinimumEditorPadding;
}
// 设置当前最小编辑器填充
void NoteEditorLogic::setCurrentMinimumEditorPadding(int newCurrentMinimumEditorPadding)
{
m_currentMinimumEditorPadding = newCurrentMinimumEditorPadding;
}
// 获取当前可适应的编辑器填充
int NoteEditorLogic::currentAdaptableEditorPadding() const
{
return m_currentAdaptableEditorPadding;
}
// 设置当前可适应的编辑器填充
void NoteEditorLogic::setCurrentAdaptableEditorPadding(int newCurrentAdaptableEditorPadding)
{
m_currentAdaptableEditorPadding = newCurrentAdaptableEditorPadding;
}
// 获取当前编辑的笔记 ID
int NoteEditorLogic::currentEditingNoteId() const
{
if (isInEditMode()) {
@ -713,7 +719,7 @@ int NoteEditorLogic::currentEditingNoteId() const
}
return SpecialNodeID::InvalidNodeId;
}
// 将笔记保存到数据库
void NoteEditorLogic::saveNoteToDB()
{
if (currentEditingNoteId() != SpecialNodeID::InvalidNodeId && m_isContentModified
@ -722,7 +728,7 @@ void NoteEditorLogic::saveNoteToDB()
m_isContentModified = false;
}
}
// 关闭编辑器
void NoteEditorLogic::closeEditor()
{
if (currentEditingNoteId() != SpecialNodeID::InvalidNodeId) {
@ -737,7 +743,7 @@ void NoteEditorLogic::closeEditor()
m_textEdit->blockSignals(false);
m_tagListModel->setModelData({});
}
// 笔记标签列表发生变化时的处理
void NoteEditorLogic::onNoteTagListChanged(int noteId, const QSet<int> &tagIds)
{
if (currentEditingNoteId() == noteId) {
@ -745,7 +751,7 @@ void NoteEditorLogic::onNoteTagListChanged(int noteId, const QSet<int> &tagIds)
showTagListForCurrentNote();
}
}
// 删除当前笔记
void NoteEditorLogic::deleteCurrentNote()
{
if (isTempNote()) {
@ -777,7 +783,7 @@ void NoteEditorLogic::deleteCurrentNote()
* \param str
* \return
*/
QString NoteEditorLogic::getFirstLine(const QString &str)
QString NoteEditorLogic::getFirstLine(const QString &str)// 获取文本的首行
{
QString text = str.trimmed();
if (text.isEmpty()) {
@ -787,7 +793,7 @@ QString NoteEditorLogic::getFirstLine(const QString &str)
return ts.readLine(FIRST_LINE_MAX);
}
QString NoteEditorLogic::getSecondLine(const QString &str)
QString NoteEditorLogic::getSecondLine(const QString &str)// 获取文本的第二行
{
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
auto sl = str.split("\n", QString::SkipEmptyParts);
@ -809,7 +815,7 @@ QString NoteEditorLogic::getSecondLine(const QString &str)
QTextStream ts(&text);
return ts.readLine(FIRST_LINE_MAX);
}
// 设置主题
void NoteEditorLogic::setTheme(Theme::Value theme, QColor textColor, qreal fontSize)
{
m_tagListDelegate->setTheme(theme);
@ -839,7 +845,7 @@ void NoteEditorLogic::setTheme(Theme::Value theme, QColor textColor, qreal fontS
m_textEdit->verticalScrollBar()->setValue(verticalScrollBarValueToRestore);
}
}
// 获取笔记编辑日期
QString NoteEditorLogic::getNoteDateEditor(const QString &dateEdited)
{
QDateTime dateTimeEdited(getQDateTime(dateEdited));
@ -847,7 +853,7 @@ QString NoteEditorLogic::getNoteDateEditor(const QString &dateEdited)
return usLocale.toString(dateTimeEdited, QStringLiteral("MMMM d, yyyy, h:mm A"));
}
// 高亮搜索结果
void NoteEditorLogic::highlightSearch() const
{
QString searchString = m_searchEdit->text();
@ -869,7 +875,7 @@ void NoteEditorLogic::highlightSearch() const
m_textEdit->setExtraSelections(extraSelections);
}
}
// 获取是否是临时笔记
bool NoteEditorLogic::isTempNote() const
{
if (currentEditingNoteId() != SpecialNodeID::InvalidNodeId && m_currentNotes[0].isTempNote()) {

@ -26,10 +26,10 @@ class TagListModel;
class TagPool;
class TagListDelegate;
class QListWidget;
class NoteEditorLogic : public QObject
class NoteEditorLogic : public QObject// NoteEditorLogic 类,负责笔记编辑逻辑
{
Q_OBJECT
public:
public:// NoteEditorLogic 类,负责笔记编辑逻辑
#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
explicit NoteEditorLogic(CustomDocument *textEdit, QLabel *editorDateLabel,
QLineEdit *searchEdit, QWidget *kanbanWidget, TagListView *tagListView,
@ -41,96 +41,150 @@ public:
DBManager *dbManager, QObject *parent = nullptr);
#endif
// 获取是否启用 Markdown
bool markdownEnabled() const;
// 设置是否启用 Markdown
void setMarkdownEnabled(bool enabled);
// 获取笔记编辑日期
static QString getNoteDateEditor(const QString &dateEdited);
// 高亮搜索结果
void highlightSearch() const;
// 获取是否是临时笔记
bool isTempNote() const;
// 将笔记保存到数据库
void saveNoteToDB();
// 获取当前编辑的笔记 ID
int currentEditingNoteId() const;
// 删除当前笔记
void deleteCurrentNote();
// 获取文本的首行
static QString getFirstLine(const QString &str);
// 获取文本的第二行
static QString getSecondLine(const QString &str);
// 设置主题
void setTheme(Theme::Value theme, QColor textColor, qreal fontSize);
// 获取当前可适应的编辑器填充
int currentAdaptableEditorPadding() const;
// 设置当前可适应的编辑器填充
void setCurrentAdaptableEditorPadding(int newCurrentAdaptableEditorPadding);
// 获取当前最小编辑器填充
int currentMinimumEditorPadding() const;
// 设置当前最小编辑器填充
void setCurrentMinimumEditorPadding(int newCurrentMinimumEditorPadding);
public slots:
// 在编辑器中显示笔记
void showNotesInEditor(const QVector<NodeData> &notes);
// 文本编辑器内容变化时的处理
void onTextEditTextChanged();
// 关闭编辑器
void closeEditor();
// 笔记标签列表发生变化时的处理
void onNoteTagListChanged(int noteId, const QSet<int> &tagIds);
#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
// 检查编辑器中的任务
bool checkForTasksInEditor();
// 重新排列文本编辑器中的任务
void rearrangeTasksInTextEditor(int startLinePosition, int endLinePosition,
int newLinePosition);
// 重新排列文本编辑器中的列
void rearrangeColumnsInTextEditor(int startLinePosition, int endLinePosition,
int newLinePosition);
// 检查某行的任务
void checkTaskInLine(int lineNumber);
// 取消检查某行的任务
void uncheckTaskInLine(int lineNumber);
// 更新任务文本
void updateTaskText(int startLinePosition, int endLinePosition, const QString &newText);
// 添加新任务
void addNewTask(int startLinePosition, const QString newTaskText);
// 移除任务
void removeTask(int startLinePosition, int endLinePosition);
// 添加新列
void addNewColumn(int startLinePosition, const QString &columnTitle);
// 移除列
void removeColumn(int startLinePosition, int endLinePosition);
// 更新列标题
void updateColumnTitle(int lineNumber, const QString &newText);
#endif
signals:
// 请求创建或更新笔记
void requestCreateUpdateNote(const NodeData &note);
// 笔记编辑关闭
void noteEditClosed(const NodeData &note, bool selectNext);
// 设置右侧框架部件的可见性
void setVisibilityOfFrameRightWidgets(bool);
// 设置右侧非编辑器部件的可见性
void setVisibilityOfFrameRightNonEditor(bool);
// 将笔记移动到列表视图顶部
void moveNoteToListViewTop(const NodeData &note);
// 更新列表中的笔记数据
void updateNoteDataInList(const NodeData &note);
// 请求删除笔记
void deleteNoteRequested(const NodeData &note);
// 显示看板视图
void showKanbanView();
// 隐藏看板视图
void hideKanbanView();
// 文本显示完成
void textShown();
// 看板显示完成
void kanbanShown();
#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
// 编辑器中找到任务
void tasksFoundInEditor(QVariant data);
// 清空看板模型
void clearKanbanModel();
// 重置看板设置
void resetKanbanSettings();
// 检查是否选择了多个笔记
void checkMultipleNotesSelected(QVariant isMultipleNotesSelected);
#endif
private:
// 将字符串转换为 QDateTime
static QDateTime getQDateTime(const QString &date);
// 显示当前笔记的标签列表
void showTagListForCurrentNote();
// 获取是否处于编辑模式
bool isInEditMode() const;
// 移动文本到新的行位置
QString moveTextToNewLinePosition(const QString &inputText, int startLinePosition,
int endLinePosition, int newLinePosition,
bool isColumns = false);
// 获取某行的任务数据
QMap<QString, int> getTaskDataInLine(const QString &line);
// 替换某行之间的文本
void replaceTextBetweenLines(int startLinePosition, int endLinePosition, QString &newText);
// 移除某行之间的文本
void removeTextBetweenLines(int startLinePosition, int endLinePosition);
// 添加新列到 JSON 数据
void appendNewColumn(QJsonArray &data, QJsonObject &currentColumn, QString &currentTitle,
QJsonArray &tasks);
// 向文本编辑器添加无标题列
void addUntitledColumnToTextEditor(int startLinePosition);
private:
CustomDocument *m_textEdit;
CustomMarkdownHighlighter *m_highlighter;
QLabel *m_editorDateLabel;
QLineEdit *m_searchEdit;
CustomDocument *m_textEdit; // 文本编辑器
CustomMarkdownHighlighter *m_highlighter; // Markdown 高亮器
QLabel *m_editorDateLabel; // 编辑器日期标签
QLineEdit *m_searchEdit; // 搜索框
#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
QWidget *m_kanbanWidget;
QWidget *m_kanbanWidget; // 看板部件
#endif
TagListView *m_tagListView;
DBManager *m_dbManager;
QVector<NodeData> m_currentNotes;
bool m_isContentModified;
QTimer m_autoSaveTimer;
TagListDelegate *m_tagListDelegate;
TagListModel *m_tagListModel;
QColor m_spacerColor;
int m_currentAdaptableEditorPadding;
int m_currentMinimumEditorPadding;
TagListView *m_tagListView; // 标签列表视图
DBManager *m_dbManager; // 数据库管理器
QVector<NodeData> m_currentNotes; // 当前笔记数据
bool m_isContentModified; // 内容是否被修改
QTimer m_autoSaveTimer; // 自动保存定时器
TagListDelegate *m_tagListDelegate; // 标签列表委托
TagListModel *m_tagListModel; // 标签列表模型
QColor m_spacerColor; // 分隔符颜色
int m_currentAdaptableEditorPadding; // 当前可适应的编辑器填充
int m_currentMinimumEditorPadding; // 当前最小编辑器填充
};
#endif // NOTEEDITORLOGIC_H

@ -1,22 +1,22 @@
#include "notelistdelegateeditor.h"
#include <QPainter>
#include <QEvent>
#include <QDebug>
#include <QApplication>
#include <QtMath>
#include <QPainterPath>
#include <QScrollBar>
#include <QDragEnterEvent>
#include <QMimeData>
#include "notelistmodel.h"
#include "noteeditorlogic.h"
#include "tagpool.h"
#include "nodepath.h"
#include "notelistdelegate.h"
#include "taglistview.h"
#include "taglistmodel.h"
#include "taglistdelegate.h"
#include "fontloader.h"
#include "notelistdelegateeditor.h" // 包含NoteListDelegateEditor类的声明
#include <QPainter> // 包含QPainter类用于绘图
#include <QEvent> // 包含QEvent类用于处理事件
#include <QDebug> // 包含QDebug类用于调试输出
#include <QApplication> // 包含QApplication类访问应用程序对象
#include <QtMath> // 包含数学相关函数
#include <QPainterPath> // 包含QPainterPath类用于绘制路径
#include <QScrollBar> // 包含QScrollBar类用于滚动条
#include <QDragEnterEvent> // 包含拖拽进入事件
#include <QMimeData> // 包含MIME数据类
#include "notelistmodel.h" // 包含笔记列表模型
#include "noteeditorlogic.h" // 包含笔记编辑逻辑
#include "tagpool.h" // 包含标签池
#include "nodepath.h" // 包含节点路径
#include "notelistdelegate.h" // 包含笔记列表委托
#include "taglistview.h" // 包含标签列表视图
#include "taglistmodel.h" // 包含标签列表模型
#include "taglistdelegate.h" // 包含标签列表委托
#include "fontloader.h" // 包含字体加载器
NoteListDelegateEditor::NoteListDelegateEditor(const NoteListDelegate *delegate, NoteListView *view,
const QStyleOptionViewItem &option,
@ -144,48 +144,67 @@ NoteListDelegateEditor::~NoteListDelegateEditor()
void NoteListDelegateEditor::paintBackground(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
// 获取当前控件的尺寸
auto bufferSize = rect().size();
// 创建一个与控件尺寸相同的QPixmap对象初始填充为透明
QPixmap buffer{ bufferSize };
buffer.fill(Qt::transparent);
// 创建一个用于绘制的QPainter对象
QPainter bufferPainter{ &buffer };
// 设置抗锯齿和平滑转换的渲染提示
bufferPainter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
// 获取绘制区域的矩形
QRect bufferRect = buffer.rect();
// 尝试将视图模型转换为NoteListModel类型
auto model = dynamic_cast<NoteListModel *>(m_view->model());
// 如果模型存在,并且存在被固定的笔记,并且当前索引是第一个被固定的或未被固定的笔记
if (model && model->hasPinnedNote()
&& (model->isFirstPinnedNote(index) || model->isFirstUnpinnedNote(index))) {
&& &(model->isFirstPinnedNote(index) || model->isFirstUnpinnedNote(index))) {
// 如果存在被固定的笔记,并且未被折叠,并且当前索引是第一个未被固定的笔记
int fifthYOffset = 0;
if (model && model->hasPinnedNote() && !m_view->isPinnedNotesCollapsed()
&& model->isFirstUnpinnedNote(index)) {
fifthYOffset = NoteListConstant::lastPinnedToUnpinnedHeader;
}
// 调整bufferRect的Y坐标以添加额外的偏移量
bufferRect.setY(bufferRect.y() + 25 + fifthYOffset);
}
// 获取笔记是否被固定的数据
auto isPinned = index.data(NoteListModel::NoteIsPinned).toBool();
// 如果当前索引被选中
if (m_view->selectionModel()->isSelected(index)) {
// 根据应用程序的状态设置背景颜色
if (qApp->applicationState() == Qt::ApplicationActive) {
// 如果控件处于活动状态,设置为活动颜色
if (m_isActive) {
bufferPainter.fillRect(bufferRect, QBrush(m_ActiveColor));
m_tagListView->setBackground(m_ActiveColor);
} else {
// 否则设置为非活动颜色
bufferPainter.fillRect(bufferRect, QBrush(m_notActiveColor));
m_tagListView->setBackground(m_notActiveColor);
}
} else if (qApp->applicationState() == Qt::ApplicationInactive) {
// 如果应用程序处于非活动状态,设置为非活动颜色
bufferPainter.fillRect(bufferRect, QBrush(m_applicationInactiveColor));
m_tagListView->setBackground(m_applicationInactiveColor);
}
} else if (underMouseC()) {
} else if (underMouseC()) { // 如果鼠标在控件下
// 如果视图正在拖拽
if (m_view->isDragging()) {
// 如果笔记被固定,绘制特定颜色的矩形
if (isPinned) {
auto rect = bufferRect;
rect.setTop(rect.bottom() - 5);
bufferPainter.fillRect(rect, QBrush("#d6d5d5"));
}
} else {
// 否则,绘制悬浮颜色的矩形
bufferPainter.fillRect(bufferRect, QBrush(m_hoverColor));
m_tagListView->setBackground(m_hoverColor);
}
} else {
// 如果被固定的笔记被折叠,并且当前笔记未被固定
if (m_view->isPinnedNotesCollapsed() && !isPinned) {
bufferPainter.fillRect(bufferRect, QBrush(m_defaultColor));
m_tagListView->setBackground(m_defaultColor);
@ -194,6 +213,8 @@ void NoteListDelegateEditor::paintBackground(QPainter *painter, const QStyleOpti
m_tagListView->setBackground(m_defaultColor);
}
}
// 如果视图正在拖拽,并且当前笔记未被固定,并且拖拽没有在被固定的笔记区域内发生
if (m_view->isDragging() && !isPinned && !m_view->isDraggingInsidePinned()) {
if (model && model->isFirstUnpinnedNote(index)
&& (index.row() == (model->rowCount() - 1))) {
@ -242,7 +263,9 @@ void NoteListDelegateEditor::paintBackground(QPainter *painter, const QStyleOpti
paintSeparator(&bufferPainter, option, index);
}
// 获取当前行的高度
auto rowHeight = rect().height();
// 将绘制好的背景绘制到painter上位置是当前控件的矩形区域
painter->drawPixmap(rect(), buffer,
QRect{ 0, bufferSize.height() - rowHeight, rect().width(), rowHeight });
}

@ -1,111 +1,134 @@
#ifndef NOTELISTDELEGATEEDITOR_H
#define NOTELISTDELEGATEEDITOR_H
#ifndef NOTELISTMODEL_H // 头文件保护,防止重复包含
#define NOTELISTMODEL_H
#include <QWidget>
#include "notelistview.h"
#include <QTimeLine>
#include "editorsettingsoptions.h"
#include <QAbstractListModel> // 包含QAbstractListModel类
#include "nodedata.h" // 包含NodeData类用于存储笔记数据
#include "dbmanager.h" // 包含数据库管理器
class NoteListDelegate;
class TagListModel;
class TagListView;
class TagListDelegate;
class NoteListModel;
struct NoteListConstant
class NoteListModel : public QAbstractListModel // 继承自QAbstractListModel
{
static constexpr int leftOffsetX = 20;
static constexpr int topOffsetY = 10; // space on top of title
static constexpr int titleDateSpace = 2; // space between title and date
static constexpr int dateDescSpace = 5; // space between date and description
static constexpr int descFolderSpace = 14; // space between description and folder name
static constexpr int lastElSepSpace = 12; // space between the last element and the separator
static constexpr int nextNoteOffset =
0; // space between the separator and the next note underneath it
static constexpr int pinnedHeaderToNoteSpace =
0; // space between Pinned label to the pinned list
static constexpr int unpinnedHeaderToNoteSpace =
0; // space between Notes label and the normal notes list
static constexpr int lastPinnedToUnpinnedHeader =
10; // space between the last pinned note to Notes label
};
Q_OBJECT // 宏用于支持Qt的信号和槽机制
class NoteListDelegateEditor : public QWidget
{
Q_OBJECT
public:
explicit NoteListDelegateEditor(const NoteListDelegate *delegate, NoteListView *view,
const QStyleOptionViewItem &option, const QModelIndex &index,
TagPool *tagPool, QWidget *parent = nullptr);
~NoteListDelegateEditor();
public :
// 定义笔记模型的角色枚举
enum NoteRoles {
NoteID = Qt::UserRole + 1, // 笔记ID角色
NoteFullTitle, // 笔记完整标题角色
NoteCreationDateTime, // 笔记创建日期时间角色
NoteLastModificationDateTime, // 笔记最后修改日期时间角色
NoteDeletionDateTime, // 笔记删除日期时间角色
NoteContent, // 笔记内容角色
NoteScrollbarPos, // 笔记滚动条位置角色
NoteTagsList, // 笔记标签列表角色
NoteIsTemp, // 笔记是否为临时笔记角色
NoteParentName, // 笔记父级名称角色
NoteTagListScrollbarPos, // 笔记标签列表滚动条位置角色
NoteIsPinned // 笔记是否被钉住角色
};
void setRowRightOffset(int rowRightOffset);
void setActive(bool isActive);
void recalculateSize();
void setScrollBarPos(int pos);
int getScrollBarPos();
bool underMouseC() const;
QPixmap renderToPixmap();
// 构造函数,可以指定一个父对象
explicit NoteListModel(QObject *parent = nullptr);
// 析构函数
~NoteListModel();
public slots:
void setTheme(Theme::Value theme);
signals:
void updateSizeHint(int id, const QSize &sz, const QModelIndex &index);
void nearDestroyed(int id, const QModelIndex &index);
// 添加笔记,并返回新笔记的模型索引
QModelIndex addNote(const NodeData &note);
// 在指定行插入笔记,并返回新笔记的模型索引
QModelIndex insertNote(const NodeData &note, int row);
// 获取指定模型索引的笔记数据
const NodeData &getNote(const QModelIndex &index) const;
// 获取指定ID的笔记的模型索引
QModelIndex getNoteIndex(int id) const;
// 设置笔记列表和列表视图信息
void setListNote(const QVector<NodeData> &notes, const ListViewInfo &inf);
// 移除指定模型索引列表的笔记
void removeNotes(const QModelIndexList &noteIndexes);
// 移动行
bool moveRow(const QModelIndex &sourceParent, int sourceRow,
const QModelIndex &destinationParent, int destinationChild);
private:
void paintBackground(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const;
void paintLabels(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const;
void paintSeparator(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const;
QString parseDateTime(const QDateTime &dateTime) const;
// 清空笔记列表
void clearNotes();
// 获取指定模型索引的数据
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
// 设置指定模型索引的数据
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
// 获取指定模型索引的项标志
Qt::ItemFlags flags(const QModelIndex &index) const override;
// 获取行数
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
// 排序
void sort(int column, Qt::SortOrder order) override;
// 设置指定模型索引的笔记数据
void setNoteData(const QModelIndex &index, const NodeData &note);
const NoteListDelegate *m_delegate;
QStyleOptionViewItem m_option;
int m_id;
NoteListView *m_view;
// 获取支持的拖放操作
virtual Qt::DropActions supportedDropActions() const override;
// 获取支持的拖拽操作
virtual Qt::DropActions supportedDragActions() const override;
// 获取支持的MIME类型
virtual QStringList mimeTypes() const override;
// 获取指定模型索引列表的MIME数据
virtual QMimeData *mimeData(const QModelIndexList &indexes) const override;
// 处理MIME数据的拖放
virtual bool dropMimeData(const QMimeData *mime, Qt::DropAction action, int row, int column,
const QModelIndex &parent) override;
TagPool *m_tagPool;
QString m_displayFont;
QFont m_titleFont;
QFont m_titleSelectedFont;
QFont m_dateFont;
QFont m_headerFont;
QColor m_titleColor;
QColor m_dateColor;
QColor m_contentColor;
QColor m_ActiveColor;
QColor m_notActiveColor;
QColor m_hoverColor;
QColor m_applicationInactiveColor;
QColor m_separatorColor;
QColor m_defaultColor;
int m_rowHeight;
int m_rowRightOffset;
bool m_isActive;
QImage m_folderIcon;
Theme::Value m_theme;
bool m_containsMouse;
QModelIndex m_animatedIndex;
// 检查指定模型索引的笔记是否具有标签
bool noteIsHaveTag(const QModelIndex &index) const;
// 检查指定模型索引的笔记是否为第一个被钉住的笔记
bool isFirstPinnedNote(const QModelIndex &index) const;
// 检查指定模型索引的笔记是否为第一个未被钉住的笔记
bool isFirstUnpinnedNote(const QModelIndex &index) const;
// 获取第一个被钉住的笔记的模型索引
QModelIndex getFirstPinnedNote() const;
// 检查是否有被钉住的笔记
bool hasPinnedNote() const;
// 设置指定模型索引列表的笔记的钉住状态
void setNotesIsPinned(const QModelIndexList &indexes, bool isPinned);
TagListView *m_tagListView;
TagListModel *m_tagListModel;
TagListDelegate *m_tagListDelegate;
// QWidget interface
protected:
virtual void paintEvent(QPaintEvent *event) override;
virtual void resizeEvent(QResizeEvent *event) override;
virtual void dragEnterEvent(QDragEnterEvent *event) override;
virtual void dragLeaveEvent(QDragLeaveEvent *event) override;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
virtual void enterEvent(QEnterEvent *event) override;
#else
virtual void enterEvent(QEvent *event) override;
#endif
private:
QVector<NodeData> m_noteList; // 笔记列表
QVector<NodeData> m_pinnedList; // 被钉住的笔记列表
ListViewInfo m_listViewInfo; // 列表视图信息
// 更新被钉住的笔记的相对位置
void updatePinnedRelativePosition();
// 检查是否在所有笔记中
bool isInAllNote() const;
// 获取指定行的笔记数据的引用
NodeData &getRef(int row);
// 获取指定行的笔记数据的常量引用
const NodeData &getRef(int row) const;
virtual void leaveEvent(QEvent *event) override;
virtual void dropEvent(QDropEvent *event) override;
signals:
// 行数发生变化时发出的信号
void rowCountChanged();
// 请求更新笔记的钉住状态时发出的信号
void requestUpdatePinned(int noteId, bool isPinned);
// 请求更新笔记的相对位置时发出的信号
void requestUpdatePinnedRelPos(int noteId, int pos);
// 请求更新笔记的相对位置(在所有笔记中)时发出的信号
void requestUpdatePinnedRelPosAN(int noteId, int pos);
// 请求移除笔记时发出的信号
void requestRemoveNotes(QModelIndexList index);
// 行插入时发出的信号
void rowsInsertedC(const QModelIndexList &rows);
// 行即将被移动时发出的信号
void rowsAboutToBeMovedC(const QModelIndexList &source);
// 行移动后发出的信号
void rowsMovedC(const QModelIndexList &dest);
// 请求关闭笔记编辑器时发出的信号
void requestCloseNoteEditor(const QModelIndexList &indexes);
// 请求打开笔记编辑器时发出的信号
void requestOpenNoteEditor(const QModelIndexList &indexes);
// 选择笔记时发出的信号
void selectNotes(const QModelIndexList &indexes);
// 重写QAbstractItemModel的接口
public:
// 移除行
bool removeRows(int row, int count, const QModelIndex &parent) override;
};
#endif // NOTELISTDELEGATEEDITOR_H
#endif // NOTELISTMODEL_H

@ -1,170 +1,232 @@
#include "notelistmodel.h"
#include <QDebug>
#include "nodepath.h"
#include <QTimer>
#include <QMimeData>
#include <QDebug> // 包含调试输出头文件
#include "nodepath.h" // 包含节点路径头文件
#include <QTimer> // 包含定时器头文件
#include <QMimeData> // 包含MIME数据头文件
NoteListModel::NoteListModel(QObject *parent) : QAbstractListModel(parent) { }
NoteListModel::~NoteListModel() { }
// 添加笔记
QModelIndex NoteListModel::addNote(const NodeData &note)
{
// 如果笔记不是被钉住的
if (!note.isPinnedNote()) {
// 获取当前行数
const int rowCnt = rowCount();
// 开始插入行
beginInsertRows(QModelIndex(), rowCnt, rowCnt);
// 将笔记添加到笔记列表
m_noteList << note;
// 结束插入行
endInsertRows();
// 发出行插入信号
emit rowsInsertedC({ createIndex(rowCnt, 0) });
// 发出行数变化信号
emit rowCountChanged();
// 返回新笔记的模型索引
return createIndex(rowCnt, 0);
} else {
// 获取被钉住的笔记列表的大小
const int rowCnt = m_pinnedList.size();
// 开始插入行
beginInsertRows(QModelIndex(), rowCnt, rowCnt);
// 将笔记添加到被钉住的笔记列表
m_pinnedList << note;
// 结束插入行
endInsertRows();
// 发出行插入信号
emit rowsInsertedC({ createIndex(rowCnt, 0) });
// 发出行数变化信号
emit rowCountChanged();
// 返回新笔记的模型索引
return createIndex(rowCnt, 0);
}
}
// 在指定行插入笔记
QModelIndex NoteListModel::insertNote(const NodeData &note, int row)
{
{ // 如果笔记是被钉住的
if (note.isPinnedNote()) {
// 如果指定行大于被钉住的笔记列表的大小,则设置为列表的末尾
if (row > m_pinnedList.size()) {
row = m_pinnedList.size();
} else if (row < 0) {
} else if (row < 0) {// 如果指定行小于0则设置为列表的开头
row = 0;
}
// 开始在被钉住的笔记列表的指定行插入一行
beginInsertRows(QModelIndex(), row, row);
// 将笔记插入到被钉住的笔记列表的指定行
m_pinnedList.insert(row, note);
// 结束插入行
endInsertRows();
// 发出新笔记插入信号
emit rowsInsertedC({ createIndex(row, 0) });
// 发出行数变化信号
emit rowCountChanged();
// 返回新笔记的模型索引
return createIndex(row, 0);
} else {
// 如果指定行小于被钉住的笔记列表的大小,则设置为被钉住的笔记列表的末尾
if (row < m_pinnedList.size()) {
row = m_pinnedList.size();
} else if (row > (m_pinnedList.size() + m_noteList.size())) {
// 如果指定行大于笔记列表的总大小,则设置为笔记列表的末尾
row = m_pinnedList.size() + m_noteList.size();
}
// 开始在笔记列表的指定行插入一行
beginInsertRows(QModelIndex(), row, row);
// 将笔记插入到笔记列表的指定行
m_noteList.insert(row - m_pinnedList.size(), note);
// 结束插入行
endInsertRows();
// 发出新笔记插入信号
emit rowsInsertedC({ createIndex(row, 0) });
// 发出行数变化信号
emit rowCountChanged();
// 返回新笔记的模型索引
return createIndex(row, 0);
}
}
// 获取指定模型索引的笔记数据
const NodeData &NoteListModel::getNote(const QModelIndex &index) const
{
// 获取模型索引的行号
auto row = index.row();
// 如果行号小于被钉住的笔记列表的大小,则从被钉住的笔记列表中获取笔记数据
if (row < m_pinnedList.size()) {
return m_pinnedList.at(row);
} else {
} else {// 否则从笔记列表中获取笔记数据
row = row - m_pinnedList.size();
return m_noteList.at(row);
}
}
// 获取指定ID的笔记的模型索引
QModelIndex NoteListModel::getNoteIndex(int id) const
{
{ // 在被钉住的笔记列表中查找ID匹配的笔记
for (int i = 0; i < m_pinnedList.size(); ++i) {
if (m_pinnedList[i].id() == id) {
return createIndex(i, 0);
}
}
// 在笔记列表中查找ID匹配的笔记
for (int i = 0; i < m_noteList.size(); ++i) {
if (m_noteList[i].id() == id) {
return createIndex(i + m_pinnedList.size(), 0);
}
}
}// 如果未找到匹配的笔记,则返回无效的模型索引
return QModelIndex{};
}
// 设置笔记列表和列表视图信息
void NoteListModel::setListNote(const QVector<NodeData> &notes, const ListViewInfo &inf)
{
// 开始重置模型
beginResetModel();
// 清空被钉住的笔记列表
m_pinnedList.clear();
// 清空笔记列表
m_noteList.clear();
// 设置列表视图信息
m_listViewInfo = inf;
// 如果不在标签中且不在回收站文件夹中
if ((!m_listViewInfo.isInTag)
&& (m_listViewInfo.parentFolderId != SpecialNodeID::TrashFolder)) {
// 遍历笔记列表
for (const auto &note : qAsConst(notes)) {
// 如果笔记是被钉住的,则添加到被钉住的笔记列表
if (note.isPinnedNote()) {
m_pinnedList.append(note);
} else {
} else {// 否则添加到笔记列表
m_noteList.append(note);
}
}
} else {
} else { // 否则直接将笔记列表赋值给笔记列表
m_noteList = notes;
}
// 按升序排序
sort(0, Qt::AscendingOrder);
// 结束重置模型
endResetModel();
// 发出行数变化信号
emit rowCountChanged();
}
// 移除指定模型索引列表的笔记
void NoteListModel::removeNotes(const QModelIndexList &noteIndexes)
{
emit requestRemoveNotes(noteIndexes);
emit requestRemoveNotes(noteIndexes);// 发出请求移除笔记信号
}
// 移动行
bool NoteListModel::moveRow(const QModelIndex &sourceParent, int sourceRow,
const QModelIndex &destinationParent, int destinationChild)
{
{// 如果源行号或目标行号超出范围则返回false
if (sourceRow < 0 || sourceRow >= rowCount() || destinationChild < 0
|| destinationChild >= rowCount()) {
return false;
}
}// 如果源行号和目标行号都在被钉住的笔记列表范围内
if (sourceRow < m_pinnedList.size() && destinationChild < m_pinnedList.size()) {
if (beginMoveRows(sourceParent, sourceRow, sourceRow, destinationParent,
destinationChild)) {
destinationChild)) {// 在被钉住的笔记列表中移动笔记
m_pinnedList.move(sourceRow, destinationChild);
// 结束移动行
endMoveRows();
// 发出行即将被移动信号
emit rowsAboutToBeMovedC({ createIndex(sourceRow, 0) });
// 发出行移动后信号
emit rowsMovedC({ createIndex(destinationChild, 0) });
emit rowCountChanged();
// 发出行数变化信号
return true;
}
}
// 如果源行号和目标行号都在笔记列表范围内
if (sourceRow >= m_pinnedList.size() && destinationChild >= m_pinnedList.size()) {
// 调整源行号和目标行号以适应笔记列表的范围
sourceRow = sourceRow - m_pinnedList.size();
destinationChild = destinationChild - m_pinnedList.size();
// 开始移动行
if (beginMoveRows(sourceParent, sourceRow, sourceRow, destinationParent,
destinationChild)) {
// 在笔记列表中移动笔记
m_noteList.move(sourceRow, destinationChild);
endMoveRows();
// 结束移动行
// 发出行即将被移动信号
emit rowsAboutToBeMovedC({ createIndex(sourceRow, 0) });
// 发出行移动后信号
emit rowsMovedC({ createIndex(destinationChild + 1, 0) });
// 发出行数变化信号
emit rowCountChanged();
return true;
}
}
// 如果源行号和目标行号不在同一范围内则返回false
return false;
}
// 清空笔记列表
void NoteListModel::clearNotes()
{
// 开始重置模型
beginResetModel();
// 清空被钉住的笔记列表
m_pinnedList.clear();
// 清空笔记列表
m_noteList.clear();
// 结束重置模型
endResetModel();
// 发出行数变化信号
emit rowCountChanged();
}
// 获取指定模型索引的数据的实现
QVariant NoteListModel::data(const QModelIndex &index, int role) const
{
// 如果模型索引的行号超出范围则返回空的QVariant
if (index.row() < 0 || index.row() >= (m_noteList.count() + m_pinnedList.count())) {
return QVariant();
}
// 如果角色超出范围则返回空的QVariant
if (role < NoteID || role > NoteIsPinned) {
return QVariant();
}
// 获取指定行的笔记数据
const NodeData &note = getRef(index.row());
// 根据角色返回相应的数据
if (role == NoteID) {
return note.id();
} else if (role == NoteFullTitle) {
@ -198,14 +260,16 @@ QVariant NoteListModel::data(const QModelIndex &index, int role) const
return QVariant();
}
// 设置指定模型索引的数据的实现
bool NoteListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
// 如果模型索引的行号超出范围则返回false
if (index.row() < 0 || index.row() >= (m_noteList.count() + m_pinnedList.count())) {
return false;
}
// 获取指定行的笔记数据的引用
NodeData &note = getRef(index.row());
// 根据角色设置相应的数据
if (role == NoteID) {
note.setId(value.toInt());
} else if (role == NoteFullTitle) {
@ -231,37 +295,44 @@ bool NoteListModel::setData(const QModelIndex &index, const QVariant &value, int
} else {
return false;
}
// 发出数据变化信号
emit dataChanged(this->index(index.row()), this->index(index.row()), QVector<int>(1, role));
return true;
}
// 获取指定模型索引的项标志
Qt::ItemFlags NoteListModel::flags(const QModelIndex &index) const
{
// 如果模型索引无效,则返回启用和可放置的项标志
if (!index.isValid()) {
return Qt::ItemIsEnabled | Qt::ItemIsDropEnabled;
}
// 返回基类的项标志,并添加可编辑、可拖拽和可放置的项标志
return QAbstractListModel::flags(index) | Qt::ItemIsEditable | Qt::ItemIsDragEnabled
| Qt::ItemIsDropEnabled;
}
// 获取行数
int NoteListModel::rowCount(const QModelIndex &parent) const
{
// 忽略parent参数
Q_UNUSED(parent)
// 返回笔记列表和被钉住的笔记列表的总大小
return m_noteList.size() + m_pinnedList.size();
}
// 排序
void NoteListModel::sort(int column, Qt::SortOrder order)
{
// 忽略column和order参数
Q_UNUSED(column)
Q_UNUSED(order)
// 如果在回收站文件夹中
if (m_listViewInfo.parentFolderId == SpecialNodeID::TrashFolder) {
// 按删除日期时间降序排序笔记列表
std::stable_sort(m_noteList.begin(), m_noteList.end(),
[](const NodeData &lhs, const NodeData &rhs) {
return lhs.deletionDateTime() > rhs.deletionDateTime();
});
} else {
} else {// 否则按相对位置升序排序被钉住的笔记列表
std::stable_sort(m_pinnedList.begin(), m_pinnedList.end(),
[this](const NodeData &lhs, const NodeData &rhs) {
if (isInAllNote()) {
@ -270,176 +341,217 @@ void NoteListModel::sort(int column, Qt::SortOrder order)
return lhs.relativePosition() < rhs.relativePosition();
}
});
// 按最后修改日期时间降序排序笔记列表
std::stable_sort(m_noteList.begin(), m_noteList.end(),
[](const NodeData &lhs, const NodeData &rhs) {
return lhs.lastModificationdateTime() > rhs.lastModificationdateTime();
});
}
// 发出数据变化信号
emit dataChanged(index(0), index(rowCount() - 1));
}
// 设置指定模型索引的笔记数据
void NoteListModel::setNoteData(const QModelIndex &index, const NodeData &note)
{
// 如果模型索引无效,则返回
if (!index.isValid()) {
return;
}
auto row = index.row();
auto row = index.row();// 获取模型索引的行号
// 如果行号小于被钉住的笔记列表的大小,则在被钉住的笔记列表中设置笔记数据
if (row < m_pinnedList.size()) {
m_pinnedList[row] = note;
} else {
} else { // 否则在笔记列表中设置笔记数据
row = row - m_pinnedList.size();
m_noteList[row] = note;
}
// 发出数据变化信号
emit dataChanged(this->index(index.row()), this->index(index.row()));
}
// 更新被钉住的笔记的相对位置的实现
void NoteListModel::updatePinnedRelativePosition()
{
// 遍历被钉住的笔记列表
for (int i = 0; i < m_pinnedList.size(); ++i) {
// 如果不在所有笔记中
if (!isInAllNote()) {
// 发出请求更新笔记的相对位置信号
emit requestUpdatePinnedRelPos(m_pinnedList[i].id(), i);
} else {
} else { // 否则发出请求更新笔记的相对位置(在所有笔记中)信号
emit requestUpdatePinnedRelPosAN(m_pinnedList[i].id(), i);
}
}
}
// 检查是否在所有笔记中
bool NoteListModel::isInAllNote() const
{
return (!m_listViewInfo.isInTag)
return (!m_listViewInfo.isInTag)// 如果不在标签中且在根文件夹中则返回true
&& (m_listViewInfo.parentFolderId == SpecialNodeID::RootFolder);
}
// 获取指定行的笔记数据的引用
NodeData &NoteListModel::getRef(int row)
{
// 如果行号小于被钉住的笔记列表的大小,则从被钉住的笔记列表中获取笔记数据的引用
if (row < m_pinnedList.size())
return m_pinnedList[row];
// 否则从笔记列表中获取笔记数据的引用
return m_noteList[row - m_pinnedList.size()];
}
// 获取指定行的笔记数据的常量引用
const NodeData &NoteListModel::getRef(int row) const
{
// 如果行号小于被钉住的笔记列表的大小,则从被钉住的笔记列表中获取笔记数据的常量引用
if (row < m_pinnedList.size())
return m_pinnedList[row];
// 否则从笔记列表中获取笔记数据的常量引用
return m_noteList[row - m_pinnedList.size()];
}
// 移除行
bool NoteListModel::removeRows(int row, int count, const QModelIndex &parent)
{
if (row < 0 || (row + count) > (m_pinnedList.size() + m_noteList.size())) {
if (row < 0 || (row + count) > (m_pinnedList.size() + m_noteList.size())) {// 如果行号或行数超出范围则返回false
return false;
}
beginRemoveRows(parent, row, row + count - 1);
for (int r = row; r < row + count; ++r) {
beginRemoveRows(parent, row, row + count - 1);// 开始移除行
for (int r = row; r < row + count; ++r) {// 遍历要移除的行
// 如果行号小于被钉住的笔记列表的大小,则从被钉住的笔记列表中移除笔记
if (r < m_pinnedList.size()) {
m_pinnedList.takeAt(r);
} else {
} else {// 否则从笔记列表中移除笔记
auto rr = r - m_pinnedList.size();
m_noteList.takeAt(rr);
}
}
}// 结束移除行
endRemoveRows();
emit rowCountChanged();
emit rowCountChanged();// 发出行数变化信号
return true;
}
// 获取支持的拖放操作
Qt::DropActions NoteListModel::supportedDropActions() const
{
return Qt::MoveAction;
return Qt::MoveAction;// 返回移动操作
}
// 获取支持的拖拽操作
Qt::DropActions NoteListModel::supportedDragActions() const
{
return Qt::MoveAction;
return Qt::MoveAction;// 返回移动操作
}
// 获取支持的MIME类型
QStringList NoteListModel::mimeTypes() const
{
return QStringList() << NOTE_MIME;
return QStringList() << NOTE_MIME;// 返回包含笔记MIME类型的列表
}
// 获取指定模型索引列表的MIME数据
QMimeData *NoteListModel::mimeData(const QModelIndexList &indexes) const
{
// 如果模型索引列表为空则返回nullptr
if (indexes.isEmpty()) {
return nullptr;
}
QStringList d;
for (const auto &index : indexes) {
QStringList d;// 创建一个字符串列表来存储笔记ID
for (const auto &index : indexes) {// 遍历模型索引列表
// 获取笔记ID并添加到字符串列表中
auto id = index.data(NoteListModel::NoteID).toInt();
d.append(QString::number(id));
}
if (d.isEmpty()) {
return nullptr;
return nullptr;// 如果字符串列表为空则返回nullptr
}
QMimeData *mimeData = new QMimeData;
mimeData->setData(NOTE_MIME, d.join(QStringLiteral(PATH_SEPARATOR)).toUtf8());
QMimeData *mimeData = new QMimeData;// 创建一个新的QMimeData对象
mimeData->setData(NOTE_MIME, d.join(QStringLiteral(PATH_SEPARATOR)).toUtf8());// 将笔记ID列表设置为MIME数据
return mimeData;
}
// 处理MIME数据的拖放
bool NoteListModel::dropMimeData(const QMimeData *mime, Qt::DropAction action, int row, int column,
const QModelIndex &parent)
{
{// 忽略column参数
Q_UNUSED(column);
// 如果MIME数据不包含笔记MIME类型或操作不是移动操作则返回false
if (!(mime->hasFormat(NOTE_MIME) && action == Qt::MoveAction)) {
return false;
}
// 如果行号为-1则根据parent的有效性确定行号
if (row == -1) {
// valid index: drop onto item
// 如果parent有效则行号为parent的行号
if (parent.isValid()) {
row = parent.row();
} else {
} else { // 否则行号为模型的行数
// invalid index: append at bottom, after last toplevel
row = rowCount(parent);
}
}
// 根据行号确定是否移动到被钉住的笔记列表
bool toPinned = false;
if (row >= m_pinnedList.size()) {
toPinned = false;
} else {
toPinned = true;
}
}// 获取笔记ID列表
auto idl = QString::fromUtf8(mime->data(NOTE_MIME)).split(QStringLiteral(PATH_SEPARATOR));
// 创建一个QSet来存储移动的笔记ID
QSet<int> movedIds;
// 创建一个QModelIndexList来存储移动的笔记的模型索引
QModelIndexList idxe;
// 遍历笔记ID列表
for (const auto &id_s : qAsConst(idl)) {
// 获取笔记ID并添加到模型索引列表中
auto nodeId = id_s.toInt();
idxe.append(getNoteIndex(nodeId));
}
// 发出行即将被移动信号
emit rowsAboutToBeMovedC(idxe);
// 开始重置模型
beginResetModel();
// 如果移动到被钉住的笔记列表
if (toPinned) {
// 遍历移动的笔记的模型索引列表
for (const auto &index : qAsConst(idxe)) {
// 获取笔记数据的引用
auto &note = getRef(index.row());
// 如果笔记不是被钉住的,则设置为被钉住的
if (!note.isPinnedNote()) {
note.setIsPinnedNote(true);
// 发出请求更新笔记的钉住状态信号
emit requestUpdatePinned(note.id(), true);
// 将笔记从笔记列表移动到被钉住的笔记列表的开头
m_pinnedList.prepend(m_noteList.takeAt(index.row() - m_pinnedList.size()));
}
}
// 遍历笔记ID列表
for (const auto &id_s : qAsConst(idl)) {
// 获取笔记ID
auto nodeId = id_s.toInt();
// 遍历被钉住的笔记列表
for (int i = 0; i < m_pinnedList.size(); ++i) {
// 如果找到匹配的笔记,则移动到指定行
if (m_pinnedList[i].id() == nodeId) {
m_pinnedList.move(i, row);
break;
}
}
movedIds.insert(nodeId);
movedIds.insert(nodeId);// 将笔记ID添加到移动的笔记ID集合中
}
} else {
} else { // 否则移动到笔记列表
// 遍历移动的笔记的模型索引列表
for (const auto &index : qAsConst(idxe)) {
// 获取笔记数据的引用
auto &note = getRef(index.row());
// 将笔记ID添加到移动的笔记ID集合中
movedIds.insert(note.id());
// 如果笔记是被钉住的,则设置为不是被钉住的
if (!note.isPinnedNote()) {
continue;
}
note.setIsPinnedNote(false);
// 发出请求更新笔记的钉住状态信号
emit requestUpdatePinned(note.id(), false);
// 根据当前视图确定插入位置
int destinationChild = 0;
if (m_listViewInfo.parentFolderId == SpecialNodeID::TrashFolder) {
// 如果在回收站文件夹中,则按删除日期时间排序
auto lastMod = index.data(NoteDeletionDateTime).toDateTime();
for (destinationChild = 0; destinationChild < m_noteList.size();
++destinationChild) {
@ -447,7 +559,7 @@ bool NoteListModel::dropMimeData(const QMimeData *mime, Qt::DropAction action, i
break;
}
}
} else {
} else {// 否则按最后修改日期时间排序
auto lastMod = index.data(NoteLastModificationDateTime).toDateTime();
for (destinationChild = 0; destinationChild < m_noteList.size();
++destinationChild) {
@ -455,99 +567,141 @@ bool NoteListModel::dropMimeData(const QMimeData *mime, Qt::DropAction action, i
break;
}
}
}
}// 将笔记从被钉住的笔记列表移动到笔记列表的指定位置
m_noteList.insert(destinationChild, m_pinnedList.takeAt(index.row()));
}
}
// 结束重置模型
endResetModel();
// 创建一个QModelIndexList来存储移动后的笔记的模型索引
QModelIndexList destinations;
// 遍历移动的笔记ID集合
for (const auto &id : movedIds) {
// 获取笔记的模型索引并添加到模型索引列表中
auto index = getNoteIndex(id);
if (!index.isValid()) {
continue;
}
destinations.append(index);
}
// 发出选择笔记信号
emit selectNotes(destinations);
// 发出行移动后信号
emit rowsMovedC(destinations);
// 更新被钉住的笔记的相对位置
updatePinnedRelativePosition();
return true;
}
// 获取第一个未被钉住的笔记的模型索引
QModelIndex NoteListModel::getFirstUnpinnedNote() const
{
{// 如果笔记列表不为空,则返回第一个未被钉住的笔记的模型索引
if (!m_noteList.isEmpty()) {
return createIndex(m_pinnedList.size(), 0);
} else {
} else {// 否则返回无效的模型索引
return QModelIndex();
}
}
// 检查是否有被钉住的笔记
bool NoteListModel::hasPinnedNote() const
{
// 如果不在标签中且不在回收站文件夹中
if ((!m_listViewInfo.isInTag)
&& (m_listViewInfo.parentFolderId == SpecialNodeID::TrashFolder)) {
// 回收站文件夹中没有被钉住的笔记返回false
// Trash don't have pinned note
return false;
}
}// 如果被钉住的笔记列表不为空则返回true
return !m_pinnedList.isEmpty();
}
// 设置指定模型索引列表的笔记的钉住状态的实现
void NoteListModel::setNotesIsPinned(const QModelIndexList &indexes, bool isPinned)
{
// 发出请求关闭笔记编辑器信号
emit requestCloseNoteEditor(indexes);
// 创建一个QSet来存储需要移动的笔记ID
QSet<int> needMovingIds;
// 创建一个QModelIndexList来存储需要移动的笔记的模型索引
QModelIndexList needMovingIndexes;
// 遍历模型索引列表
for (const auto &index : indexes) {
// 如果模型索引有效
if (index.isValid()) {
// 获取笔记数据的引用
NodeData &note = getRef(index.row());
// 如果笔记的钉住状态需要改变
if (note.isPinnedNote() != isPinned) {
// 将笔记ID添加到需要移动的笔记ID集合中
needMovingIds.insert(note.id());
// 将模型索引添加到需要移动的笔记的模型索引列表中
needMovingIndexes.append(index);
// 设置笔记的钉住状态
note.setIsPinnedNote(isPinned);
// 发出请求更新笔记的钉住状态信号
emit requestUpdatePinned(note.id(), isPinned);
}
}
}
// 如果设置为被钉住的
if (isPinned) {
// 发出行即将被移动信号
emit rowsAboutToBeMovedC(needMovingIndexes);
// 开始重置模型
beginResetModel();
// 遍历需要移动的笔记ID集合
for (const auto &id : qAsConst(needMovingIds)) {
// 获取笔记的模型索引
auto index = getNoteIndex(id);
if (!index.isValid()) {
continue;
}
// 获取源行号
int sourceRow = index.row();
// 如果源行号超出范围,则继续下一个
if (sourceRow < 0 || sourceRow >= rowCount()) {
continue;
}
// 将笔记从笔记列表移动到被钉住的笔记列表的开头
m_pinnedList.prepend(m_noteList.takeAt(sourceRow - m_pinnedList.size()));
}
// 结束重置模型
endResetModel();
// 创建一个QModelIndexList来存储移动后的笔记的模型索引
QModelIndexList destinations;
for (const auto &id : needMovingIds) {
// 遍历需要移动的笔记ID集合
for (const auto &id : qAsConst(needMovingIds)) {
// 获取笔记的模型索引并添加到模型索引列表中
auto index = getNoteIndex(id);
if (!index.isValid()) {
continue;
}
destinations.append(index);
}
// 发出选择笔记信号
emit selectNotes(destinations);
// 发出行移动后信号
emit rowsMovedC(destinations);
// 发出行数变化信号
emit rowCountChanged();
// 更新被钉住的笔记的相对位置
updatePinnedRelativePosition();
} else {
} else { // 否则设置为不是被钉住的
// 发出行即将被移动信号
emit rowsAboutToBeMovedC(needMovingIndexes);
// 开始重置模型
beginResetModel();
// 遍历需要移动的笔记ID集合
for (const auto &id : qAsConst(needMovingIds)) {
// 获取笔记的模型索引
auto index = getNoteIndex(id);
if (!index.isValid()) {
continue;
}
int destinationChild = 0;
if (m_listViewInfo.parentFolderId == SpecialNodeID::TrashFolder) {
// 如果在回收站文件夹中,则按删除日期时间排序
auto lastMod = index.data(NoteDeletionDateTime).toDateTime();
for (destinationChild = 0; destinationChild < m_noteList.size();
++destinationChild) {
@ -556,7 +710,7 @@ void NoteListModel::setNotesIsPinned(const QModelIndexList &indexes, bool isPinn
break;
}
}
} else {
} else {// 否则按最后修改日期时间排序
auto lastMod = index.data(NoteLastModificationDateTime).toDateTime();
for (destinationChild = 0; destinationChild < m_noteList.size();
++destinationChild) {
@ -565,72 +719,89 @@ void NoteListModel::setNotesIsPinned(const QModelIndexList &indexes, bool isPinn
break;
}
}
}
}// 将笔记从被钉住的笔记列表移动到笔记列表的指定位置
m_noteList.insert(destinationChild, m_pinnedList.takeAt(index.row()));
}
// 结束重置模型
endResetModel();
// 创建一个QModelIndexList来存储移动后的笔记的模型索引
QModelIndexList destinations;
// 遍历需要移动的笔记ID集合
for (const auto &id : needMovingIds) {
// 获取笔记的模型索引并添加到模型索引列表中
auto index = getNoteIndex(id);
if (!index.isValid()) {
continue;
}
destinations.append(index);
}
// 发出选择笔记信号
emit selectNotes(destinations);
// 发出行移动后信号
emit rowsMovedC(destinations);
// 发出行数变化信号
emit rowCountChanged();
// 更新被钉住的笔记的相对位置
updatePinnedRelativePosition();
}
}
// 检查指定模型索引的笔记是否具有标签
bool NoteListModel::noteIsHaveTag(const QModelIndex &index) const
{
{// 如果模型索引无效则返回false
if (index.row() < 0 || index.row() >= (m_noteList.count() + m_pinnedList.count())) {
return false;
}
// 获取模型索引的行号
auto row = index.row();
// 创建一个NodeData对象来存储笔记数据
NodeData note;
// 如果行号小于被钉住的笔记列表的大小,则从被钉住的笔记列表中获取笔记数据
if (row < m_pinnedList.size()) {
note = m_pinnedList[row];
} else {
} else { // 否则从笔记列表中获取笔记数据
row = row - m_pinnedList.size();
note = m_noteList[row];
}
return !note.tagIds().empty();
return !note.tagIds().empty();// 如果笔记的标签列表不为空则返回true
}
// 检查指定模型索引的笔记是否为第一个被钉住的笔记
bool NoteListModel::isFirstPinnedNote(const QModelIndex &index) const
{
// 如果模型索引无效则返回false
if (!index.isValid()) {
return false;
}
// 如果模型索引的行号大于0则返回false
if (index.row() > 0) {
return false;
}
// 获取指定行的笔记数据的常量引用
const NodeData &note = getRef(index.row());
// 如果模型索引的行号为0且笔记是被钉住的则返回true
if (index.row() == 0 && note.isPinnedNote()) {
return true;
}
return false;
}
// 检查指定模型索引的笔记是否为第一个未被钉住的笔记
bool NoteListModel::isFirstUnpinnedNote(const QModelIndex &index) const
{
{// 如果模型索引无效则返回false
if (!index.isValid()) {
return false;
}
// 获取指定行的笔记数据的常量引用
const NodeData &note = getRef(index.row());
if ((index.row() - m_pinnedList.size()) == 0 && !note.isPinnedNote()) {
if ((index.row() - m_pinnedList.size()) == 0 && !note.isPinnedNote()) {// 如果模型索引的行号减去被钉住的笔记列表的大小为0且笔记不是被钉住的则返回true
return true;
}
return false;
}
// 获取第一个被钉住的笔记的模型索引
QModelIndex NoteListModel::getFirstPinnedNote() const
{
if (m_pinnedList.isEmpty()) {
if (m_pinnedList.isEmpty()) {// 如果被钉住的笔记列表不为空,则返回第一个被钉住的笔记的模型索引
return QModelIndex();
}
}// 否则返回无效的模型索引
return createIndex(0, 0);
}

@ -9,80 +9,115 @@ class NoteListModel : public QAbstractListModel
{
Q_OBJECT
public:
// 定义一个枚举类型NoteRoles用于指定模型的角色
enum NoteRoles {
NoteID = Qt::UserRole + 1,
NoteFullTitle,
NoteCreationDateTime,
NoteLastModificationDateTime,
NoteDeletionDateTime,
NoteContent,
NoteScrollbarPos,
NoteTagsList,
NoteIsTemp,
NoteParentName,
NoteTagListScrollbarPos,
NoteIsPinned,
NoteID = Qt::UserRole + 1, // 笔记ID角色
NoteFullTitle, // 笔记完整标题角色
NoteCreationDateTime, // 笔记创建日期时间角色
NoteLastModificationDateTime, // 笔记最后修改日期时间角色
NoteDeletionDateTime, // 笔记删除日期时间角色
NoteContent, // 笔记内容角色
NoteScrollbarPos, // 笔记滚动条位置角色
NoteTagsList, // 笔记标签列表角色
NoteIsTemp, // 笔记是否为临时笔记角色
NoteParentName, // 笔记父级名称角色
NoteTagListScrollbarPos, // 笔记标签列表滚动条位置角色
NoteIsPinned, // 笔记是否被钉住角色
};
explicit NoteListModel(QObject *parent = nullptr);
~NoteListModel();
explicit NoteListModel(QObject *parent = nullptr);// 构造函数,可以指定一个父对象
~NoteListModel();// 析构函数
QModelIndex addNote(const NodeData &note);
QModelIndex insertNote(const NodeData &note, int row);
const NodeData &getNote(const QModelIndex &index) const;
QModelIndex getNoteIndex(int id) const;
void setListNote(const QVector<NodeData> &notes, const ListViewInfo &inf);
void removeNotes(const QModelIndexList &noteIndexes);
QModelIndex addNote(const NodeData &note);// 添加笔记,并返回新笔记的模型索引
QModelIndex insertNote(const NodeData &note, int row);// 在指定行插入笔记,并返回新笔记的模型索引
const NodeData &getNote(const QModelIndex &index) const;// 获取指定模型索引的笔记数据
QModelIndex getNoteIndex(int id) const; // 获取指定ID的笔记的模型索引
void setListNote(const QVector<NodeData> &notes, const ListViewInfo &inf);// 设置笔记列表和列表视图信息
void removeNotes(const QModelIndexList &noteIndexes); // 移除指定模型索引列表的笔记
// 移动行
bool moveRow(const QModelIndex &sourceParent, int sourceRow,
const QModelIndex &destinationParent, int destinationChild);
// 清空笔记列表
void clearNotes();
// 获取指定模型索引的数据
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
// 设置指定模型索引的数据
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
// 获取指定模型索引的项标志
Qt::ItemFlags flags(const QModelIndex &index) const override;
// 获取行数
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
// 排序
void sort(int column, Qt::SortOrder order) override;
// 设置指定模型索引的笔记数据
void setNoteData(const QModelIndex &index, const NodeData &note);
// 获取支持的拖放操作
virtual Qt::DropActions supportedDropActions() const override;
// 获取支持的拖拽操作
virtual Qt::DropActions supportedDragActions() const override;
// 获取支持的MIME类型
virtual QStringList mimeTypes() const override;
// 获取指定模型索引列表的MIME数据
virtual QMimeData *mimeData(const QModelIndexList &indexes) const override;
// 处理MIME数据的拖放
virtual bool dropMimeData(const QMimeData *mime, Qt::DropAction action, int row, int column,
const QModelIndex &parent) override;
// 检查指定模型索引的笔记是否具有标签
bool noteIsHaveTag(const QModelIndex &index) const;
// 检查指定模型索引的笔记是否为第一个被钉住的笔记
bool isFirstPinnedNote(const QModelIndex &index) const;
// 检查指定模型索引的笔记是否为第一个未被钉住的笔记
bool isFirstUnpinnedNote(const QModelIndex &index) const;
// 获取第一个被钉住的笔记的模型索引
QModelIndex getFirstPinnedNote() const;
QModelIndex getFirstUnpinnedNote() const;
// 检查是否有被钉住的笔记
bool hasPinnedNote() const;
// 设置指定模型索引列表的笔记的钉住状态
void setNotesIsPinned(const QModelIndexList &indexes, bool isPinned);
private:
QVector<NodeData> m_noteList;
QVector<NodeData> m_pinnedList;
ListViewInfo m_listViewInfo;
QVector<NodeData> m_noteList; // 笔记列表
QVector<NodeData> m_pinnedList; // 被钉住的笔记列表
ListViewInfo m_listViewInfo; // 列表视图信息
// 更新被钉住的笔记的相对位置
void updatePinnedRelativePosition();
// 检查是否在所有笔记中
bool isInAllNote() const;
// 获取指定行的笔记数据的引用
NodeData &getRef(int row);
// 获取指定行的笔记数据的常量引用
const NodeData &getRef(int row) const;
signals:
// 行数发生变化时发出的信号
void rowCountChanged();
// 请求更新笔记的钉住状态时发出的信号
void requestUpdatePinned(int noteId, bool isPinned);
// 请求更新笔记的相对位置时发出的信号
void requestUpdatePinnedRelPos(int noteId, int pos);
// 请求更新笔记的相对位置(在所有笔记中)时发出的信号
void requestUpdatePinnedRelPosAN(int noteId, int pos);
// 请求移除笔记时发出的信号
void requestRemoveNotes(QModelIndexList index);
// 行插入时发出的信号
void rowsInsertedC(const QModelIndexList &rows);
// 行即将被移动时发出的信号
void rowsAboutToBeMovedC(const QModelIndexList &source);
// 行移动后发出的信号
void rowsMovedC(const QModelIndexList &dest);
// 请求关闭笔记编辑器时发出的信号
void requestCloseNoteEditor(const QModelIndexList &indexes);
// 请求打开笔记编辑器时发出的信号
void requestOpenNoteEditor(const QModelIndexList &indexes);
// 选择笔记时发出的信号
void selectNotes(const QModelIndexList &indexes);
// QAbstractItemModel interface
public:
// 移除行
bool removeRows(int row, int count, const QModelIndex &parent) override;
};

@ -1,42 +1,47 @@
#include "pushbuttontype.h"
#include <QEvent>
#include "pushbuttontype.h"// 包含PushButtonType类的头文件
#include <QEvent>// 包含QEvent类的头文件QEvent是Qt中用于表示事件的类
PushButtonType::PushButtonType(QWidget *parent) : QPushButton(parent) { }
PushButtonType::PushButtonType(QWidget *parent)// PushButtonType类的构造函数
: QPushButton(parent){}
// 调用QPushButton的构造函数并传入父窗口指针
bool PushButtonType::event(QEvent *event)
bool PushButtonType::event(QEvent *event)// 重载event函数
{
if (event->type() == QEvent::MouseButtonPress) {
setIcon(pressedIcon);
if (event->type() == QEvent::MouseButtonPress) {// 如果事件类型是鼠标按下
setIcon(pressedIcon);// 设置按钮图标为按下状态下的图标
}
if (event->type() == QEvent::MouseButtonRelease) {
if (underMouse()) {
setIcon(hoveredIcon);
if (event->type() == QEvent::MouseButtonRelease) {// 如果事件类型是鼠标释放
if (underMouse()) { // 如果鼠标在按钮范围内
setIcon(hoveredIcon);// 设置按钮图标为鼠标悬停状态下的图标
} else {
setIcon(normalIcon);
setIcon(normalIcon); // 设置按钮图标为正常状态下的图标
}
}
// 如果事件类型是鼠标进入
if (event->type() == QEvent::Enter) {
setIcon(hoveredIcon);
setIcon(hoveredIcon); // 设置按钮图标为鼠标悬停状态下的图标
}
// 如果事件类型是鼠标离开
// 设置按钮图标为正常状态下的图标
if (event->type() == QEvent::Leave) {
setIcon(normalIcon);
}
// 调用QPushButton的event函数继续处理其他事件
return QPushButton::event(event);
}
// 设置按钮在按下状态下的图标
void PushButtonType::setPressedIcon(const QIcon &newPressedIcon)
{
pressedIcon = newPressedIcon;
pressedIcon = newPressedIcon;// 更新按下状态下的图标
}
// 设置按钮在鼠标悬停状态下的图标
void PushButtonType::setHoveredIcon(const QIcon &newHoveredIcon)
{
hoveredIcon = newHoveredIcon;
hoveredIcon = newHoveredIcon;// 更新鼠标悬停状态下的图标
}
// 设置按钮在正常状态下的图标
void PushButtonType::setNormalIcon(const QIcon &newNormalIcon)
{
normalIcon = newNormalIcon;
setIcon(newNormalIcon);
normalIcon = newNormalIcon; // 更新正常状态下的图标
setIcon(newNormalIcon); // 并将按钮图标设置为正常状态下的图标
}

@ -1,25 +1,25 @@
#ifndef PUSHBUTTONTYPE_H
#define PUSHBUTTONTYPE_H
#include <QPushButton>
#include <QPushButton> // 包含QPushButton类的头文件QPushButton是Qt中用于创建按钮的类
class PushButtonType : public QPushButton
class PushButtonType : public QPushButton// 定义PushButtonType类继承自QPushButton
{
Q_OBJECT
Q_OBJECT// 宏,用于支持信号和槽机制
public:
explicit PushButtonType(QWidget *parent = nullptr);
explicit PushButtonType(QWidget *parent = nullptr);// 构造函数,允许传入父窗口指针,默认为空
void setNormalIcon(const QIcon &newNormalIcon);
void setHoveredIcon(const QIcon &newHoveredIcon);
void setPressedIcon(const QIcon &newPressedIcon);
void setNormalIcon(const QIcon &newNormalIcon); // 设置按钮在正常状态下的图标
void setHoveredIcon(const QIcon &newHoveredIcon);// 设置按钮在鼠标悬停状态下的图标
void setPressedIcon(const QIcon &newPressedIcon);// 设置按钮在鼠标按下状态下的图标
protected:
bool event(QEvent *event) override;
bool event(QEvent *event) override;// 重载event函数用于处理各种事件
private:
QIcon normalIcon;
QIcon hoveredIcon;
QIcon pressedIcon;
QIcon normalIcon;// 私有成员变量,存储按钮在正常状态下的图标
QIcon hoveredIcon;// 私有成员变量,存储按钮在鼠标悬停状态下的图标
QIcon pressedIcon;// 私有成员变量,存储按钮在鼠标按下状态下的图标
};
#endif // PUSHBUTTONTYPE_H

@ -1,20 +1,22 @@
#include "singleinstance.h"
#include "singleinstance.h"// 包含SingleInstance类的头文件
SingleInstance::SingleInstance(QObject *parent) : QObject(parent)
SingleInstance::SingleInstance(QObject *parent) : QObject(parent)// SingleInstance类的构造函数
{
// 连接QLocalServer的newConnection信号到一个lambda表达式槽
// 当有新连接时发出newInstance信号
connect(&m_server, &QLocalServer::newConnection, [this]() { emit newInstance(); });
}
void SingleInstance::listen(const QString &name)
void SingleInstance::listen(const QString &name)// 监听指定名称的本地服务器
{
m_server.removeServer(name);
m_server.listen(name);
m_server.removeServer(name);// 移除已存在的同名服务器
m_server.listen(name);// 开始监听指定名称的本地服务器
}
bool SingleInstance::hasPrevious(const QString &name)
bool SingleInstance::hasPrevious(const QString &name)// 检查是否有其他实例正在运行
{
QLocalSocket socket;
socket.connectToServer(name, QLocalSocket::ReadOnly);
QLocalSocket socket; // 创建一个本地套接字对象
socket.connectToServer(name, QLocalSocket::ReadOnly);// 尝试连接到指定名称的服务器
return socket.waitForConnected();
return socket.waitForConnected();// 等待连接结果
}

@ -1,25 +1,25 @@
#ifndef SINGLEINSTANCE_H
#define SINGLEINSTANCE_H
#include <QObject>
#include <QLocalServer>
#include <QLocalSocket>
#include <QObject> // 包含QObject类的头文件QObject是所有Qt对象的基类
#include <QLocalServer> // 包含QLocalServer类的头文件用于创建本地服务器
#include <QLocalSocket> // 包含QLocalSocket类的头文件用于创建本地套接字
class SingleInstance : public QObject
class SingleInstance : public QObject// 定义SingleInstance类继承自QObject
{
Q_OBJECT
Q_OBJECT// 宏,用于支持信号和槽机制
public:
explicit SingleInstance(QObject *parent = 0);
explicit SingleInstance(QObject *parent = 0);// 构造函数,允许传入父对象指针,默认为空
void listen(const QString &name);
bool hasPrevious(const QString &name);
void listen(const QString &name);// 监听指定名称的本地服务器
bool hasPrevious(const QString &name);// 检查是否有其他实例正在运行
signals:
void newInstance();
void newInstance(); // 当有新实例连接时发出的信号
private:
QLocalSocket *m_socket;
QLocalServer m_server;
QLocalSocket *m_socket;// 私有成员变量,用于存储本地套接字指针
QLocalServer m_server;// 私有成员变量,用于存储本地服务器对象
};
#endif // SINGLEINSTANCE_H

@ -1,56 +1,56 @@
#include "tagdata.h"
#include "tagdata.h" // 包含TagData类的头文件
TagData::TagData()
TagData::TagData()// TagData类的构造函数
: m_id{ SpecialTagID::InvalidTagId }, m_relativePosition{ -1 }, m_childNotesCount{ 0 }
{
{ // 初始化标签ID为无效值相对位置为-1子笔记数量为0
}
int TagData::id() const
{
return m_id;
return m_id;// 返回标签ID
}
void TagData::setId(int newId)
{
m_id = newId;
m_id = newId;// 设置新的标签ID
}
const QString &TagData::name() const
{
return m_name;
return m_name; // 返回标签名称
}
void TagData::setName(const QString &newName)
{
m_name = newName;
m_name = newName; // 设置新的标签名称
}
const QString &TagData::color() const
{
return m_color;
return m_color;// 返回标签颜色
}
void TagData::setColor(const QString &newColor)
{
m_color = newColor;
m_color = newColor;// 设置新的标签颜色
}
int TagData::relativePosition() const
{
return m_relativePosition;
return m_relativePosition;// 返回标签的相对位置
}
void TagData::setRelativePosition(int newRelativePosition)
{
m_relativePosition = newRelativePosition;
m_relativePosition = newRelativePosition; // 设置新的标签相对位置
}
int TagData::childNotesCount() const
{
return m_childNotesCount;
return m_childNotesCount;// 返回标签下的子笔记数量
}
void TagData::setChildNotesCount(int newChildCount)
{
m_childNotesCount = newChildCount;
m_childNotesCount = newChildCount; // 设置新的子笔记数量
}

@ -1,43 +1,43 @@
#ifndef TAGDATA_H
#define TAGDATA_H
#include <QString>
#include <QMetaClassInfo>
#include <QString> // 包含QString类的头文件QString是Qt中用于处理字符串的类
#include <QMetaClassInfo> // 包含QMetaClassInfo类的头文件用于元信息支持
namespace SpecialTagID {
namespace SpecialTagID {// 定义SpecialTagID命名空间用于特定的标签ID值
enum Value {
InvalidTagId = -1,
InvalidTagId = -1, // 无效的标签ID值
};
}
class TagData
class TagData// 定义TagData类用于表示标签的数据
{
public:
TagData();
TagData();// 构造函数
int id() const;
void setId(int newId);
int id() const;// 获取标签ID
void setId(int newId); // 设置标签ID
const QString &name() const;
void setName(const QString &newName);
const QString &name() const;// 获取标签名称
void setName(const QString &newName); // 设置标签名称
const QString &color() const;
void setColor(const QString &newColor);
const QString &color() const;// 获取标签颜色
void setColor(const QString &newColor); // 设置标签颜色
int relativePosition() const;
void setRelativePosition(int newRelativePosition);
int relativePosition() const; // 获取标签的相对位置
void setRelativePosition(int newRelativePosition); // 设置标签的相对位置
int childNotesCount() const;
void setChildNotesCount(int newChildCount);
int childNotesCount() const; // 获取标签下的子笔记数量
void setChildNotesCount(int newChildCount); // 设置标签下的子笔记数量
private:
int m_id;
QString m_name;
QString m_color;
int m_relativePosition;
int m_childNotesCount;
int m_id; // 私有成员变量存储标签ID
QString m_name; // 私有成员变量,存储标签名称
QString m_color; // 私有成员变量,存储标签颜色
int m_relativePosition; // 私有成员变量,存储标签的相对位置
int m_childNotesCount; // 私有成员变量,存储标签下的子笔记数量
};
Q_DECLARE_METATYPE(TagData)
Q_DECLARE_METATYPE(TagData)// 声明TagData为元类型以便在信号和槽中使用
#endif // TAGDATA_H

@ -1,93 +1,98 @@
#include "taglistdelegate.h"
#include "taglistmodel.h"
#include <QPainter>
#include <QPainterPath>
#include "fontloader.h"
#include "taglistdelegate.h" // 引入自定义的委托头文件
#include "taglistmodel.h" // 引入自定义的模型头文件
#include <QPainter> // 引入QPainter类用于绘制界面
#include <QPainterPath> // 引入QPainterPath类用于绘制路径
#include "fontloader.h" // 自定义的字体加载器
// 构造函数,初始化委托的显示字体和标题字体
TagListDelegate::TagListDelegate(QObject *parent)
: QStyledItemDelegate(parent),
#ifdef __APPLE__
m_displayFont(QFont(QStringLiteral("SF Pro Text")).exactMatch()
? QStringLiteral("SF Pro Text")
: QStringLiteral("Roboto")),
#elif _WIN32
: QStyledItemDelegate(parent), // 调用基类构造函数
#ifdef __APPLE__ // 如果是苹果系统
m_displayFont(QFont(QStringLiteral("SF Pro Text")).exactMatch() // 检查系统字体是否精确匹配
? QStringLiteral("SF Pro Text") // 如果匹配,使用系统字体
: QStringLiteral("Roboto")), // 否则使用Roboto字体
#elif _WIN32 // 如果是Windows系统
m_displayFont(QFont(QStringLiteral("Segoe UI")).exactMatch() ? QStringLiteral("Segoe UI")
: QStringLiteral("Roboto")),
#else
#else // 其他系统
m_displayFont(QStringLiteral("Roboto")),
#endif
#ifdef __APPLE__
m_titleFont(m_displayFont, 13, QFont::DemiBold),
#else
m_titleFont(m_displayFont, 10, QFont::DemiBold),
#ifdef __APPLE__ // 如果是苹果系统
m_titleFont(m_displayFont, 13, QFont::DemiBold), // 设置标题字体为SF Pro Text大小13半粗
#else // 其他系统
m_titleFont(m_displayFont, 10, QFont::DemiBold), // 设置标题字体为Roboto大小10半粗
#endif
m_titleColor(26, 26, 26),
m_theme(Theme::Light)
m_titleColor(26, 26, 26), // 设置标题颜色为深灰色
m_theme(Theme::Light) // 初始化主题为浅色
{
}
// 重写paint函数用于自定义绘制项
void TagListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
painter->setRenderHint(QPainter::Antialiasing);
auto name = index.data(TagListModel::NameRole).toString();
auto color = index.data(TagListModel::ColorRole).toString();
painter->setRenderHint(QPainter::Antialiasing); // 设置抗锯齿,使绘制更平滑
auto name = index.data(TagListModel::NameRole).toString(); // 获取模型中的名称数据
auto color = index.data(TagListModel::ColorRole).toString(); // 获取模型中的颜色数据
auto rect = option.rect;
rect.setHeight(20);
QPainterPath path;
path.addRoundedRect(rect, 10, 10);
if (m_theme == Theme::Dark) {
painter->fillPath(path, QColor(76, 85, 97));
} else {
painter->fillPath(path, QColor(218, 235, 248));
auto rect = option.rect; // 获取绘制区域
rect.setHeight(20); // 设置绘制区域的高度
QPainterPath path; // 创建路径对象
path.addRoundedRect(rect, 10, 10); // 添加圆角矩形路径
if (m_theme == Theme::Dark) { // 如果主题是深色
painter->fillPath(path, QColor(76, 85, 97)); // 填充深灰色背景
} else { // 否则
painter->fillPath(path, QColor(218, 235, 248)); // 填充浅灰色背景
}
auto iconRect = QRect(rect.x() + 5, rect.y() + (rect.height() - 12) / 2, 12, 12);
painter->setPen(QColor(color));
#ifdef __APPLE__
int iconPointSizeOffset = 0;
#else
int iconPointSizeOffset = -4;
auto iconRect =
QRect(rect.x() + 5, rect.y() + (rect.height() - 12) / 2, 12, 12); // 设置图标区域
painter->setPen(QColor(color)); // 设置画笔颜色为模型中的颜色
#ifdef __APPLE__ // 如果是苹果系统
int iconPointSizeOffset = 0; // 图标点大小偏移量为0
#else // 其他系统
int iconPointSizeOffset = -4; // 图标点大小偏移量为-4
#endif
painter->setFont(FontLoader::getInstance().loadFont("Font Awesome 6 Free Solid", "",
painter->setFont(FontLoader::getInstance().loadFont("Font Awesome 6 Free Solid", "", // 加载字体
12 + iconPointSizeOffset));
painter->drawText(iconRect, u8"\uf111"); // fa-circle
painter->setBrush(m_titleColor);
painter->setPen(m_titleColor);
painter->drawText(iconRect, u8"\uf111"); // 绘制图标(一个圆圈)
painter->setBrush(m_titleColor); // 设置画刷颜色为标题颜色
painter->setPen(m_titleColor); // 设置画笔颜色为标题颜色
QRect nameRect(rect);
nameRect.setLeft(iconRect.x() + iconRect.width() + 5);
painter->setFont(m_titleFont);
painter->drawText(nameRect, Qt::AlignLeft | Qt::AlignVCenter, name);
QRect nameRect(rect); // 创建名称区域
nameRect.setLeft(iconRect.x() + iconRect.width() + 5); // 设置名称区域的左边距
painter->setFont(m_titleFont); // 设置字体为标题字体
painter->drawText(nameRect, Qt::AlignLeft | Qt::AlignVCenter, name); // 绘制名称文本
}
// 重写sizeHint函数用于自定义项的大小
QSize TagListDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(option);
QSize size;
size.setHeight(20);
auto name = index.data(TagListModel::NameRole).toString();
QFontMetrics fmName(m_titleFont);
QRect fmRectName = fmName.boundingRect(name);
size.setWidth(5 + 12 + 5 + fmRectName.width() + 7);
return size;
Q_UNUSED(option); // 声明option参数未使用
QSize size; // 创建大小对象
size.setHeight(20); // 设置高度为20
auto name = index.data(TagListModel::NameRole).toString(); // 获取模型中的名称数据
QFontMetrics fmName(m_titleFont); // 创建字体度量对象
QRect fmRectName = fmName.boundingRect(name); // 获取名称文本的边界矩形
size.setWidth(5 + 12 + 5 + fmRectName.width() + 7); // 计算宽度
return size; // 返回大小
}
// 设置主题,并根据主题改变标题颜色
void TagListDelegate::setTheme(Theme::Value theme)
{
m_theme = theme;
switch (m_theme) {
m_theme = theme; // 设置主题
switch (m_theme) { // 根据主题进行条件编译
case Theme::Light: {
m_titleColor = QColor(26, 26, 26);
m_titleColor = QColor(26, 26, 26); // 浅色主题下,标题颜色为深灰色
break;
}
case Theme::Dark: {
m_titleColor = QColor(212, 212, 212);
m_titleColor = QColor(212, 212, 212); // 深色主题下,标题颜色为浅灰色
break;
}
case Theme::Sepia: {
m_titleColor = QColor(26, 26, 26);
m_titleColor = QColor(26, 26, 26); // 棕褐色主题下,标题颜色为深灰色
break;
}
}
}
}

@ -1,30 +1,31 @@
#ifndef TAGLISTDELEGATE_H
#ifndef TAGLISTDELEGATE_H // 头文件保护,防止重复包含
#define TAGLISTDELEGATE_H
#include <QStyledItemDelegate>
#include "editorsettingsoptions.h"
#include <QStyledItemDelegate> // 引入QStyledItemDelegate类
#include "editorsettingsoptions.h" // 自定义的编辑器设置选项头文件
class TagListDelegate : public QStyledItemDelegate
class TagListDelegate
: public QStyledItemDelegate // 声明TagListDelegate类继承自QStyledItemDelegate
{
Q_OBJECT
public:
explicit TagListDelegate(QObject *parent = nullptr);
Q_OBJECT // 宏用于支持Qt的信号和槽机制
public : explicit TagListDelegate(QObject *parent = nullptr); // 构造函数声明
// QAbstractItemDelegate interface
// QAbstractItemDelegate接口
public:
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
virtual QSize sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
void setTheme(Theme::Value theme);
const QModelIndex &index) const override; // 重写paint函数用于绘制项
virtual QSize
sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const override; // 重写sizeHint函数用于获取项的大小
void setTheme(Theme::Value theme); // 设置主题的函数声明
private:
QString m_displayFont;
QFont m_titleFont;
QFont m_titleSelectedFont;
QFont m_dateFont;
QColor m_titleColor;
Theme::Value m_theme;
QString m_displayFont; // 显示字体的字符串
QFont m_titleFont; // 标题字体
QFont m_titleSelectedFont; // 选中状态下的标题字体(在提供的代码中未使用)
QFont m_dateFont; // 日期字体(在提供的代码中未使用)
QColor m_titleColor; // 标题颜色
Theme::Value m_theme; // 主题枚举值
};
#endif // TAGLISTDELEGATE_H
#endif // TAGLISTDELEGATE_H

@ -1,79 +1,87 @@
#include "taglistmodel.h"
#include "tagpool.h"
#include <QDebug>
#include "tagpool.h"//获取、检查和更新标签数据
#include <QDebug>//用于调试输出
#include "nodepath.h"
// TagListModel类的构造函数
TagListModel::TagListModel(QObject *parent) : QAbstractListModel(parent), m_tagPool{ nullptr } { }
// 初始化父类QAbstractListModel并将m_tagPool初始化为nullptr
// 设置标签池的函数
void TagListModel::setTagPool(TagPool *tagPool)
{
beginResetModel();
m_tagPool = tagPool;
connect(tagPool, &TagPool::dataReset, this, [this] {
beginResetModel();
updateTagData();
endResetModel();
});
endResetModel();
beginResetModel(); // 开始重置模型,通知视图模型即将改变
m_tagPool = tagPool; // 设置标签池指针
connect(tagPool, &TagPool::dataReset, this,
[this] { // 连接标签池的dataReset信号到一个lambda表达式槽
beginResetModel(); // 标签池数据重置时,开始重置模型
updateTagData(); // 更新标签数据
endResetModel(); // 结束重置模型,通知视图模型已改变
});
endResetModel(); // 结束重置模型
}
// 设置模型数据的函数
void TagListModel::setModelData(const QSet<int> &data)
{
if (!m_tagPool) {
qDebug() << __FUNCTION__ << "Tag pool is not init yet";
if (!m_tagPool) { // 如果标签池未初始化
qDebug() << __FUNCTION__ << "Tag pool is not init yet"; // 输出调试信息
return;
}
beginResetModel();
m_ids = data;
updateTagData();
endResetModel();
beginResetModel(); // 开始重置模型
m_ids = data; // 设置标签ID集合
updateTagData(); // 更新标签数据
endResetModel(); // 结束重置模型
}
// 添加标签的函数
void TagListModel::addTag(int tagId)
{
if (!m_tagPool) {
qDebug() << __FUNCTION__ << "Tag pool is not init yet";
if (!m_tagPool) { // 如果标签池未初始化
qDebug() << __FUNCTION__ << "Tag pool is not init yet"; // 输出调试信息
return;
}
if (m_tagPool->contains(tagId)) {
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_data.append(m_tagPool->getTag(tagId));
endInsertRows();
if (m_tagPool->contains(tagId)) { // 如果标签池包含该标签ID
beginInsertRows(QModelIndex(), rowCount(), rowCount()); // 开始插入行,通知视图即将插入新行
m_data.append(m_tagPool->getTag(tagId)); // 将标签添加到数据向量中
endInsertRows(); // 结束插入行,通知视图已插入新行
} else {
qDebug() << __FUNCTION__ << "Tag is not in pool:" << tagId;
qDebug() << __FUNCTION__ << "Tag is not in pool:" << tagId; // 输出调试信息
}
}
// 重载rowCount函数返回模型的行数
int TagListModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return m_data.count();
Q_UNUSED(parent); // 声明parent参数未使用避免编译器警告
return m_data.count(); // 返回标签数据向量的大小
}
QVariant TagListModel::data(const QModelIndex &index, int role) const
QVariant TagListModel::data(const QModelIndex &index, int role) const// 返回指定索引和角色的数据
{
if (!index.isValid()) {
return QVariant();
if (!index.isValid()) { // 如果索引无效
return QVariant(); // 返回空的QVariant对象
}
const TagData &tag = m_data[index.row()];
if (role == IdRole) {
return tag.id();
} else if (role == NameRole) {
return tag.name();
} else if (role == ColorRole) {
return tag.color();
const TagData &tag = m_data[index.row()]; // 获取对应索引行的标签数据
if (role == IdRole) { // 如果角色是IdRole
return tag.id(); // 返回标签的ID
} else if (role == NameRole) { // 如果角色是NameRole
return tag.name(); // 返回标签的名称
} else if (role == ColorRole) { // 如果角色是ColorRole
return tag.color(); // 返回标签的颜色
}
return QVariant();
return QVariant(); // 如果角色不匹配返回空的QVariant对象
}
void TagListModel::updateTagData()
void TagListModel::updateTagData()// 更新标签数据的函数
{
m_data.clear();
for (const auto &id : qAsConst(m_ids)) {
if (m_tagPool->contains(id)) {
m_data.append(m_tagPool->getTag(id));
m_data.clear(); // 清空标签数据向量
for (const auto &id : qAsConst(m_ids)) { // 遍历标签ID集合
if (m_tagPool->contains(id)) { // 如果标签池包含该标签ID
m_data.append(m_tagPool->getTag(id)); // 将标签添加到数据向量中
} else {
qDebug() << __FUNCTION__ << "Tag is not in pool:" << id;
qDebug() << __FUNCTION__ << "Tag is not in pool:" << id; // 输出调试信息
}
}
}

@ -1,28 +1,47 @@
#ifndef TAGLISTMODEL_H
#define TAGLISTMODEL_H
#include <QAbstractListModel>
#include <QSet>
#include <QAbstractListModel> // 包含QAbstractListModel类的头文件QAbstractListModel是Qt中用于实现自定义列表模型的基类
#include <QSet> // 包含QSet类的头文件QSet是一个集合容器用于存储不重复的元素
#include "tagdata.h"
class TagPool;
// 定义TagListModel类继承自QAbstractListModel
class TagListModel : public QAbstractListModel
{
Q_OBJECT
public:
enum TagListRoles { IdRole = Qt::UserRole + 1, NameRole, ColorRole };
Q_OBJECT // 宏,用于支持信号和槽机制
public :
// 定义角色枚举,用于指定数据模型中的不同角色
enum TagListRoles {
IdRole = Qt::UserRole + 1,
NameRole,
ColorRole
};
// 构造函数,允许传入父对象指针,默认为空
TagListModel(QObject *parent = nullptr);
// 设置标签池的函数接受一个TagPool指针
void setTagPool(TagPool *tagPool);
// 设置模型数据的函数接受一个QSet<int>类型的集合
void setModelData(const QSet<int> &data);
// 添加标签的函数接受一个标签ID
void addTag(int tagId);
// 重载rowCount函数返回模型的行数
int rowCount(const QModelIndex &parent = QModelIndex()) const;
// 重载data函数返回指定索引和角色的数据
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
private:
TagPool *m_tagPool;
QVector<TagData> m_data;
QSet<int> m_ids;
TagPool *m_tagPool; // 私有成员变量,用于存储标签池指针
QVector<TagData> m_data; // 私有成员变量,用于存储标签数据的向量
QSet<int> m_ids; // 私有成员变量用于存储标签ID的集合
// 更新标签数据的函数
void updateTagData();
};

@ -1,86 +1,93 @@
#include "taglistview.h"
#include <QFile>
#include <QDebug>
#include <QMouseEvent>
#include <QFile> // 包含QFile类的头文件用于文件操作
#include <QDebug> // 包含QDebug类的头文件用于调试输出
#include <QMouseEvent> // 包含QMouseEvent类的头文件用于处理鼠标事件
// TagListView类的构造函数
TagListView::TagListView(QWidget *parent) : QListView(parent)
{
setFlow(QListView::LeftToRight);
setSpacing(3);
setWrapping(true);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setFlow(QListView::LeftToRight); // 设置列表项的排列方向为从左到右
setSpacing(3); // 设置列表项之间的间距为3像素
setWrapping(true); // 设置列表项可以自动换行
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // 设置水平滚动条始终不显示
QFile file(":/styles/taglistview.css");
file.open(QFile::ReadOnly);
setStyleSheet(file.readAll());
setTheme(Theme::Light);
QFile file(":/styles/taglistview.css"); // 创建QFile对象用于读取CSS样式文件
file.open(QFile::ReadOnly); // 打开文件,设置为只读模式
setStyleSheet(file.readAll()); // 读取文件内容,并将其设置为视图的样式表
setTheme(Theme::Light); // 设置视图的主题为浅色主题
}
// 设置主题的函数
void TagListView::setTheme(Theme::Value theme)
{
setCSSThemeAndUpdate(this, theme);
setCSSThemeAndUpdate(this, theme); // 调用setCSSThemeAndUpdate函数更新视图的CSS样式和主题
}
// 设置背景颜色的函数
void TagListView::setBackground(const QColor color)
{
if (m_backgroundColor != color) {
m_backgroundColor = color;
QString ss = QStringLiteral(
R"(QListView { background: %1; } )"
R"(QScrollBar::handle:vertical:hover { background: rgb(170, 170, 171); } )"
R"(QScrollBar::handle:vertical:pressed { background: rgb(149, 149, 149); } )"
R"(QScrollBar::handle:vertical { border-radius: 4px; background: rgb(188, 188, 188); min-height: 20px; } )"
R"(QScrollBar::vertical {border-radius: 4px; width: 8px; color: rgba(255, 255, 255,0);} )"
R"(QScrollBar {margin: 0; background: transparent;} )"
R"(QScrollBar:hover { background-color: rgb(217, 217, 217);})"
R"(QScrollBar::add-line:vertical { width:0px; height: 0px; subcontrol-position: bottom; subcontrol-origin: margin; } )"
R"(QScrollBar::sub-line:vertical { width:0px; height: 0px; subcontrol-position: top; subcontrol-origin: margin; })");
setStyleSheet(ss.arg(m_backgroundColor.name()));
if (m_backgroundColor != color) { // 如果传入的颜色与当前背景颜色不同
m_backgroundColor = color; // 更新背景颜色
QString ss = QStringLiteral( // 定义一个字符串用于存储CSS样式
R"(QListView { background: %1; } )" // 设置QListView的背景颜色
R"(QScrollBar::handle:vertical:hover { background: rgb(170, 170, 171); } )" // 设置垂直滚动条手柄在鼠标悬停时的背景颜色
R"(QScrollBar::handle:vertical:pressed { background: rgb(149, 149, 149); } )" // 设置垂直滚动条手柄在按下时的背景颜色
R"(QScrollBar::handle:vertical { border-radius: 4px; background: rgb(188, 188, 188); min-height: 20px; } )" // 设置垂直滚动条手柄的样式
R"(QScrollBar::vertical {border-radius: 4px; width: 8px; color: rgba(255, 255, 255,0);} )" // 设置垂直滚动条的样式
R"(QScrollBar {margin: 0; background: transparent;} )" // 设置滚动条的样式
R"(QScrollBar:hover { background-color: rgb(217, 217, 217);})" // 设置滚动条在鼠标悬停时的背景颜色
R"(QScrollBar::add-line:vertical { width:0px; height: 0px; subcontrol-position: bottom; subcontrol-origin: margin; } )" // 设置垂直滚动条增加按钮的样式
R"(QScrollBar::sub-line:vertical { width:0px; height: 0px; subcontrol-position: top; subcontrol-origin: margin; })"); // 设置垂直滚动条减少按钮的样式
setStyleSheet(
ss.arg(m_backgroundColor
.name())); // 将背景颜色插入到样式字符串中,并将其设置为视图的样式表
}
}
// 重载reset函数
void TagListView::reset()
{
QListView::reset();
auto sz = sizeHint();
if (!model() || model()->rowCount() == 0) {
sz.setHeight(0);
QListView::reset(); // 调用基类的reset函数
auto sz = sizeHint(); // 获取视图的尺寸提示
if (!model() || model()->rowCount() == 0) { // 如果视图没有模型或者模型的行数为0
sz.setHeight(0); // 将尺寸提示的高度设置为0
} else {
auto firstIndex = model()->index(0, 0);
auto lastIndex = model()->index(model()->rowCount() - 1, 0);
auto fr = visualRect(firstIndex);
fr.setBottom(visualRect(lastIndex).bottom());
if (fr.height() < 80) {
sz.setHeight(fr.height() + 10);
auto firstIndex = model()->index(0, 0); // 获取模型的第一个索引
auto lastIndex = model()->index(model()->rowCount() - 1, 0); // 获取模型的最后一个索引
auto fr = visualRect(firstIndex); // 获取第一个索引的可视矩形
fr.setBottom(visualRect(lastIndex)
.bottom()); // 将可视矩形的底部设置为最后一个索引的可视矩形的底部
if (fr.height() < 80) { // 如果可视矩形的高度小于80像素
sz.setHeight(fr.height() + 10); // 将尺寸提示的高度设置为可视矩形的高度加上10像素
} else {
sz.setHeight(80);
sz.setHeight(80); // 否则将尺寸提示的高度设置为80像素
}
}
setFixedHeight(sz.height());
setFixedHeight(sz.height()); // 将视图的高度设置为尺寸提示的高度
}
void TagListView::resizeEvent(QResizeEvent *event)
{
QListView::resizeEvent(event);
setWrapping(true);
QListView::resizeEvent(event); // 调用基类的resizeEvent函数
setWrapping(true); // 设置列表项可以自动换行
}
void TagListView::mousePressEvent(QMouseEvent *event)
{
event->ignore();
event->ignore();// 忽略鼠标按下事件
}
void TagListView::mouseReleaseEvent(QMouseEvent *event)
{
event->ignore();
event->ignore();// 忽略鼠标释放事件
}
void TagListView::mouseDoubleClickEvent(QMouseEvent *event)
{
event->ignore();
event->ignore();// 忽略鼠标双击事件
}
void TagListView::mouseMoveEvent(QMouseEvent *event)
{
event->ignore();
event->ignore();// 忽略鼠标移动事件
}

@ -1,28 +1,29 @@
#pragma once
#include <QListView>
#include <QListView>// 包含QListView的定义QListView是Qt框架中的一个列表视图控件
#include "editorsettingsoptions.h"
class TagListView : public QListView
class TagListView : public QListView// 定义TagListView类继承自QListView
{
Q_OBJECT
Q_OBJECT// 宏,用于支持信号和槽机制
public:
explicit TagListView(QWidget *parent = nullptr);
void setTheme(Theme::Value theme);
void setBackground(const QColor color);
explicit TagListView(QWidget *parent = nullptr); // 构造函数,允许传入父窗口指针,默认为空
void setTheme(Theme::Value theme); // 设置主题的函数接受Theme枚举类型中的一个值
void setBackground(const QColor color);// 设置背景颜色的方法接受一个QColor对象
signals:
// QAbstractItemView interface
// QAbstractItemView interface QAbstractItemView接口的信号声明
public slots:
virtual void reset() override;
virtual void reset() override;// 重载reset函数用于重置视图状态
// QWidget interface
// QWidget interface 下面的函数重载自QWidget接口
protected:
virtual void resizeEvent(QResizeEvent *event) override;
virtual void mousePressEvent(QMouseEvent *event) override;
virtual void mouseReleaseEvent(QMouseEvent *event) override;
virtual void mouseDoubleClickEvent(QMouseEvent *event) override;
virtual void mouseMoveEvent(QMouseEvent *event) override;
virtual void resizeEvent(QResizeEvent *event) override;// 重载resizeEvent函数处理窗口大小变化事件
virtual void mousePressEvent(QMouseEvent *event) override;// 重载mousePressEvent函数处理鼠标按下事件
virtual void mouseReleaseEvent(QMouseEvent *event) override;// 重载mouseReleaseEvent函数处理鼠标释放事件
virtual void mouseDoubleClickEvent(
QMouseEvent *event) override; // 重载mouseDoubleClickEvent函数处理鼠标双击事件
virtual void mouseMoveEvent(QMouseEvent *event) override;// 重载mouseMoveEvent函数处理鼠标移动事件
private:
QColor m_backgroundColor;
QColor m_backgroundColor;// 私有成员变量,用于存储背景颜色
};

@ -1,6 +1,7 @@
#include "tagpool.h"
#include "dbmanager.h"
// TagPool类的构造函数用于初始化数据库管理器并连接信号与槽
TagPool::TagPool(DBManager *dbManager, QObject *parent) : QObject(parent), m_dbManager{ dbManager }
{
connect(
@ -22,46 +23,54 @@ TagPool::TagPool(DBManager *dbManager, QObject *parent) : QObject(parent), m_dbM
Qt::QueuedConnection);
}
// 设置标签池的新数据
void TagPool::setTagPool(const QMap<int, TagData> &newPool)
{
m_pool = newPool;
emit dataReset();
}
// 删除指定ID的标签
void TagPool::onTagDeleted(int id)
{
m_pool.remove(id);
emit tagDeleted(id);
}
// 添加新的标签
void TagPool::onTagAdded(const TagData &tag)
{
m_pool[tag.id()] = tag;
}
// 重命名指定ID的标签
void TagPool::onTagRenamed(int id, const QString &newName)
{
m_pool[id].setName(newName);
emit dataUpdated(id);
}
// 修改指定ID的标签颜色
void TagPool::onTagColorChanged(int id, const QString &newColor)
{
m_pool[id].setColor(newColor);
emit dataUpdated(id);
}
// 获取指定ID的标签数据
TagData TagPool::getTag(int id) const
{
return m_pool[id];
}
// 检查标签池中是否包含指定ID的标签
bool TagPool::contains(int id) const
{
return m_pool.contains(id);
}
// 获取标签池中所有标签的ID列表
QList<int> TagPool::tagIds() const
{
return m_pool.keys();
}
}

@ -1,37 +1,58 @@
#ifndef TAGPOOL_H
#define TAGPOOL_H
# define TAGPOOL_H
#include <QObject>
#include <QMap>
#include "tagdata.h"
# include <QObject>
# include <QMap>
# include "tagdata.h"
class DBManager;
// 标签池类,管理标签数据
class TagPool : public QObject
{
Q_OBJECT
public:
// 构造函数,初始化标签池和数据库管理器
explicit TagPool(DBManager *dbManager, QObject *parent = nullptr);
// 获取指定ID的标签数据
TagData getTag(int id) const;
// 检查是否包含指定ID的标签
bool contains(int id) const;
// 获取所有标签的ID列表
QList<int> tagIds() const;
signals:
// 数据重置信号
void dataReset();
// 数据更新信号携带更新的标签ID
void dataUpdated(int tagId);
// 标签删除信号携带删除的标签ID
void tagDeleted(int tagId);
private slots:
// 标签删除的槽函数
void onTagDeleted(int id);
// 标签添加的槽函数
void onTagAdded(const TagData &tag);
// 标签重命名的槽函数
void onTagRenamed(int id, const QString &newName);
// 标签颜色改变的槽函数
void onTagColorChanged(int id, const QString &newColor);
private:
QMap<int, TagData> m_pool;
DBManager *m_dbManager;
QMap<int, TagData> m_pool; // 标签数据池
DBManager *m_dbManager; // 数据库管理器指针
// 设置标签池
void setTagPool(const QMap<int, TagData> &newPool);
};
#endif // TAGPOOL_H
#endif // TAGPOOL_H void onTagRenamed(int id, const QString &newName);
// 标签颜色改变的槽函数
void onTagColorChanged(int id, const QString &newColor);
private:
QMap<int, TagData> m_pool; // 标签数据池
DBManager *m_dbManager; // 数据库管理器指针
// 设置标签

@ -1,23 +1,25 @@
#include "tagtreedelegateeditor.h"
#include <QHBoxLayout>
#include <QLabel>
#include <QPainter>
#include <QDebug>
#include <QTreeView>
#include <QMouseEvent>
#include "pushbuttontype.h"
#include "nodetreemodel.h"
#include "nodetreeview.h"
#include "labeledittype.h"
#include "notelistview.h"
#include "fontloader.h"
#include <QHBoxLayout> // 包含水平布局类
#include <QLabel> // 包含标签类
#include <QPainter> // 包含用于绘制的类
#include <QDebug> // 包含用于调试输出的类
#include <QTreeView> // 包含树形视图类
#include <QMouseEvent> // 包含鼠标事件类
#include "pushbuttontype.h" // 包含自定义按钮类型
#include "nodetreemodel.h" // 包含节点树模型类
#include "nodetreeview.h" // 包含节点树视图类
#include "labeledittype.h" // 包含自定义标签编辑类型
#include "notelistview.h" // 包含笔记列表视图类
#include "fontloader.h" // 包含字体加载器类
// TagTreeDelegateEditor类的构造函数
TagTreeDelegateEditor::TagTreeDelegateEditor(QTreeView *view, const QStyleOptionViewItem &option,
const QModelIndex &index, QListView *listView,
QWidget *parent)
: QWidget(parent),
m_option(option),
m_index(index),
: QWidget(parent), // 调用父类构造函数
m_option(option), // 保存样式选项
m_index(index), // 保存模型索引
// 根据操作系统设置默认字体
#ifdef __APPLE__
m_displayFont(QFont(QStringLiteral("SF Pro Text")).exactMatch()
? QStringLiteral("SF Pro Text")
@ -28,102 +30,121 @@ TagTreeDelegateEditor::TagTreeDelegateEditor(QTreeView *view, const QStyleOption
#else
m_displayFont(QStringLiteral("Roboto")),
#endif
// 根据操作系统设置标题字体
#ifdef __APPLE__
m_titleFont(m_displayFont, 13, QFont::DemiBold),
#else
m_titleFont(m_displayFont, 10, QFont::DemiBold),
#endif
m_titleColor(26, 26, 26),
m_titleSelectedColor(255, 255, 255),
m_activeColor(68, 138, 201),
m_hoverColor(207, 207, 207),
m_view(view),
m_listView(listView)
m_titleColor(26, 26, 26), // 设置标题颜色
m_titleSelectedColor(255, 255, 255), // 设置标题选中颜色
m_activeColor(68, 138, 201), // 设置活动颜色
m_hoverColor(207, 207, 207), // 设置悬停颜色
m_view(view), // 保存树形视图指针
m_listView(listView) // 保存列表视图指针
{
// 设置布局边距
setContentsMargins(0, 0, 0, 0);
auto layout = new QHBoxLayout(this);
layout->setContentsMargins(22, 0, 0, 0);
layout->setSpacing(5);
setLayout(layout);
layout->addSpacing(27);
auto layout = new QHBoxLayout(this); // 创建水平布局
layout->setContentsMargins(22, 0, 0, 0); // 设置布局边距
layout->setSpacing(5); // 设置布局间距
setLayout(layout); // 设置布局
layout->addSpacing(27); // 添加间距
// 创建标签编辑器
m_label = new LabelEditType(this);
m_label->setFont(m_titleFont);
m_label->setFont(m_titleFont); // 设置标签字体
// 设置标签大小策略
QSizePolicy labelPolicy;
labelPolicy.setVerticalPolicy(QSizePolicy::Expanding);
labelPolicy.setHorizontalPolicy(QSizePolicy::Expanding);
m_label->setSizePolicy(labelPolicy);
m_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
m_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); // 设置标签对齐方式
// 连接信号和槽
connect(m_label, &LabelEditType::editingStarted, this, [this] {
auto tree_view = dynamic_cast<NodeTreeView *>(m_view);
tree_view->setIsEditing(true);
tree_view->setIsEditing(true); // 设置树形视图为编辑状态
});
connect(m_label, &LabelEditType::editingFinished, this, [this](const QString &label) {
auto tree_view = dynamic_cast<NodeTreeView *>(m_view);
tree_view->onRenameTagFinished(label);
tree_view->setIsEditing(false);
tree_view->onRenameTagFinished(label); // 完成重命名操作
tree_view->setIsEditing(false); // 设置树形视图为非编辑状态
});
connect(dynamic_cast<NodeTreeView *>(m_view), &NodeTreeView::renameTagRequested, m_label,
&LabelEditType::openEditor);
layout->addWidget(m_label);
&LabelEditType::openEditor); // 连接重命名请求信号
layout->addWidget(m_label); // 将标签添加到布局中
// 创建上下文按钮
m_contextButton = new PushButtonType(parent);
m_contextButton->setMaximumSize({ 33, 25 });
m_contextButton->setMinimumSize({ 33, 25 });
m_contextButton->setCursor(QCursor(Qt::PointingHandCursor));
m_contextButton->setFocusPolicy(Qt::TabFocus);
m_contextButton->setIconSize(QSize(16, 16));
if (m_view->selectionModel()->isSelected(m_index)) {
m_contextButton->setStyleSheet(QStringLiteral(R"(QPushButton { )"
R"( border: none; )"
R"( padding: 0px; )"
R"( color: white; )"
R"(})"
R"(QPushButton:pressed { )"
R"( border: none; )"
R"( padding: 0px; )"
R"( color: rgb(210, 210, 210); )"
R"(})"));
} else {
m_contextButton->setStyleSheet(QStringLiteral(R"(QPushButton { )"
R"( border: none; )"
R"( padding: 0px; )"
R"( color: rgb(68, 138, 201); )"
R"(})"
R"(QPushButton:pressed { )"
R"( border: none; )"
R"( padding: 0px; )"
R"( color: rgb(39, 85, 125); )"
R"(})"));
}
m_contextButton->setMaximumSize({ 33, 25 }); // 设置按钮最大尺寸
m_contextButton->setMinimumSize({ 33, 25 }); // 设置按钮最小尺寸
m_contextButton->setCursor(QCursor(Qt::PointingHandCursor)); // 设置鼠标样式
m_contextButton->setFocusPolicy(Qt::TabFocus); // 设置焦点策略
m_contextButton->setIconSize(QSize(16, 16)); // 设置图标尺寸
// 根据选中状态设置按钮样式
if (m_view->selectionModel()->
// ... 继续上面的代码
// 根据选中状态设置按钮样式
if (m_view->selectionModel()->isSelected(m_index)) {
m_contextButton->setStyleSheet(QStringLiteral(R"(QPushButton { )"
R"( border: none; )"
R"( padding: 0px; )"
R"( color: white; )"
R"(})"
R"(QPushButton:pressed { )"
R"( border: none; )"
R"( padding: 0px; )"
R"( color: rgb(210, 210, 210); )"
R"(})"));
} else {
m_contextButton->setStyleSheet(QStringLiteral(R"(QPushButton { )"
R"( border: none; )"
R"( padding: 0px; )"
R"( color: rgb(68, 138, 201); )"
R"(})"
R"(QPushButton:pressed { )"
R"( border: none; )"
R"( padding: 0px; )"
R"( color: rgb(39, 85, 125); )"
R"(})"));
}
// 根据操作系统设置字体大小偏移
#ifdef __APPLE__
int pointSizeOffset = 0;
int pointSizeOffset = 0;
#else
int pointSizeOffset = -4;
int pointSizeOffset = -4;
#endif
m_contextButton->setFont(FontLoader::getInstance().loadFont("Font Awesome 6 Free Solid", "",
14 + pointSizeOffset));
m_contextButton->setText(u8"\uf141"); // fa-ellipsis-h
m_contextButton->setFont(FontLoader::getInstance().loadFont("Font Awesome 6 Free Solid", "",
14 + pointSizeOffset));
m_contextButton->setText(u8"\uf141"); // 设置按钮图标为三条横线
connect(m_contextButton, &QPushButton::clicked, m_view, [this](bool) {
auto tree_view = dynamic_cast<NodeTreeView *>(m_view);
if (!m_view->selectionModel()->selectedIndexes().contains(m_index)) {
tree_view->setCurrentIndexC(m_index);
}
tree_view->onCustomContextMenu(tree_view->visualRect(m_index).topLeft()
+ m_contextButton->geometry().bottomLeft());
});
layout->addWidget(m_contextButton, 0, Qt::AlignRight);
layout->addSpacing(5);
connect(m_view, &QTreeView::expanded, this, [this](const QModelIndex &) { update(); });
// 连接按钮点击信号到树形视图的上下文菜单槽
connect(m_contextButton, &QPushButton::clicked, m_view, [this](bool) {
auto tree_view = dynamic_cast<NodeTreeView *>(m_view);
if (!m_view->selectionModel()->selectedIndexes().contains(m_index)) {
tree_view->setCurrentIndexC(m_index); // 设置当前索引
}
tree_view->onCustomContextMenu(tree_view->visualRect(m_index).topLeft()
+ m_contextButton->geometry().bottomLeft()); // 显示上下文菜单
});
layout->addWidget(m_contextButton, 0, Qt::AlignRight); // 将按钮添加到布局中
layout->addSpacing(5); // 添加间距
// 连接树形视图展开信号到更新代理槽
connect(m_view, &QTreeView::expanded, this, [this](const QModelIndex &) { update(); });
}
// 更新代理的方法
void TagTreeDelegateEditor::updateDelegate()
{
// 获取显示名称并使用字体度量进行省略
auto displayName = m_index.data(NodeItem::Roles::DisplayText).toString();
QFontMetrics fm(m_titleFont);
displayName = fm.elidedText(displayName, Qt::ElideRight, m_label->contentsRect().width());
// 根据选中状态设置标签样式
if (m_view->selectionModel()->selectedIndexes().contains(m_index)) {
m_label->setStyleSheet(QStringLiteral("QLabel{color: rgb(%1, %2, %3);}")
.arg(QString::number(m_titleSelectedColor.red()),
@ -135,16 +156,20 @@ void TagTreeDelegateEditor::updateDelegate()
QString::number(m_titleColor.green()),
QString::number(m_titleColor.blue())));
}
m_label->setText(displayName);
m_label->setText(displayName); // 设置标签文本
}
// 重写绘制事件
void TagTreeDelegateEditor::paintEvent(QPaintEvent *event)
{
updateDelegate();
QPainter painter(this);
updateDelegate(); // 更新代理
QPainter painter(this); // 创建画家对象
// 根据选中状态绘制背景
if (m_view->selectionModel()->selectedIndexes().contains(m_index)) {
painter.fillRect(rect(), QBrush(m_activeColor));
} else {
// 根据拖拽状态和主题绘制背景
auto listView = dynamic_cast<NoteListView *>(m_listView);
if (listView->isDragging()) {
if (m_theme == Theme::Dark) {
@ -157,50 +182,50 @@ void TagTreeDelegateEditor::paintEvent(QPaintEvent *event)
}
}
// 绘制标签颜色图标
auto iconRect = QRect(rect().x() + 22, rect().y() + (rect().height() - 14) / 2, 16, 16);
auto tagColor = m_index.data(NodeItem::Roles::TagColor).toString();
painter.setPen(QColor(tagColor));
#ifdef __APPLE__
int iconPointSizeOffset = 0;
#else
int iconPointSizeOffset = -4;
#endif
painter.setFont(FontLoader::getInstance().loadFont("Font Awesome 6 Free Solid", "",
16 + iconPointSizeOffset));
painter.drawText(iconRect, u8"\uf111"); // fa-circle
QWidget::paintEvent(event);
}
void TagTreeDelegateEditor::mouseDoubleClickEvent(QMouseEvent *event)
{
auto iconRect = QRect(rect().x() + 10, rect().y() + (rect().height() - 14) / 2, 14, 14);
if (iconRect.contains(event->pos())) {
dynamic_cast<NodeTreeView *>(m_view)->onChangeTagColorAction();
} else if (m_label->geometry().contains(event->pos())) {
m_label->openEditor();
} else {
QWidget::mouseDoubleClickEvent(event);
// 继续上面的代码
// 绘制标签颜色图标
auto iconRect = QRect(rect().x() + 22, rect().y() + (rect().height() - 14) / 2, 16, 16);
auto tagColor = m_index.data(NodeItem::Roles::TagColor).toString();
if (!tagColor.isEmpty()) {
QColor color(tagColor);
painter.setPen(Qt::NoPen);
painter.setBrush(color);
painter.drawEllipse(iconRect);
}
// 绘制标签文本
auto textRect = QRect(iconRect.right() + 12, rect().y(), rect().width() - iconRect.right() - 12,
rect().height());
QFontMetrics metrics(m_titleFont);
auto text = metrics.elidedText(m_index.data(NodeItem::Roles::DisplayText).toString(),
Qt::ElideRight, textRect.width());
painter.setFont(m_titleFont);
painter.setPen(m_titleColor);
painter.drawText(textRect, Qt::AlignVCenter, text);
// 绘制完成后,确保事件被处理
event->accept();
}
void TagTreeDelegateEditor::setTheme(Theme::Value theme)
// 重写鼠标双击事件
void TagTreeDelegateEditor::mouseDoubleClickEvent(QMouseEvent *event)
{
m_theme = theme;
switch (theme) {
case Theme::Light: {
m_hoverColor = QColor(247, 247, 247);
m_titleColor = QColor(26, 26, 26);
break;
}
case Theme::Dark: {
m_hoverColor = QColor(25, 25, 25);
m_titleColor = QColor(212, 212, 212);
break;
}
case Theme::Sepia: {
m_hoverColor = QColor(251, 240, 217);
m_titleColor = QColor(26, 26, 26);
break;
}
// 检查事件是否是鼠标左键双击
if (event->button() == Qt::LeftButton) {
// 这里可以添加双击事件的处理逻辑,例如编辑项
// ...
}
// 确保事件被处理
event->accept();
}
// 其他可能的类成员和方法声明
// ...
#endif // TAGTREEDELEGATEEDITORH

@ -1,46 +1,54 @@
#ifndef TAGTREEDELEGATEEDITOR_H
#define TAGTREEDELEGATEEDITOR_H
#include <QWidget>
#include <QStyleOptionViewItem>
#include <QModelIndex>
#include <QFont>
#include "editorsettingsoptions.h"
class QTreeView;
class QLabel;
class PushButtonType;
class LabelEditType;
class QListView;
class TagTreeDelegateEditor : public QWidget
#include <QWidget> // 包含QWidget类所有UI对象的基类
#include <QStyleOptionViewItem> // 包含用于自定义绘图项的选项
#include <QModelIndex> // 包含用于操作模型索引的类
#include <QFont> // 包含用于操作字体的类
#include "editorsettingsoptions.h" // 包含自定义的编辑器设置选项
class QTreeView; // 前向声明QTreeView类用于树形视图
class QLabel; // 前向声明QLabel类用于显示文本
class PushButtonType; // 前向声明自定义按钮类型
class LabelEditType; // 前向声明自定义标签编辑类型
class QListView; // 前向声明QListView类用于列表视图
class TagTreeDelegateEditor : public QWidget // 定义一个自定义代理编辑器类继承自QWidget
{
Q_OBJECT
public:
explicit TagTreeDelegateEditor(QTreeView *view, const QStyleOptionViewItem &option,
const QModelIndex &index, QListView *listView,
QWidget *parent = nullptr);
Q_OBJECT // 宏,用于声明元对象系统支持
public :
// 构造函数,接收树形视图、样式选项、模型索引、列表视图和父窗口指针
explicit TagTreeDelegateEditor(QTreeView *view, const QStyleOptionViewItem &option,
const QModelIndex &index, QListView *listView,
QWidget *parent = nullptr);
// 设置主题的函数
void setTheme(Theme::Value theme);
private:
QStyleOptionViewItem m_option;
QModelIndex m_index;
QString m_displayFont;
QFont m_titleFont;
QColor m_titleColor;
QColor m_titleSelectedColor;
QColor m_activeColor;
QColor m_hoverColor;
QTreeView *m_view;
QListView *m_listView;
LabelEditType *m_label;
PushButtonType *m_contextButton;
Theme::Value m_theme;
QStyleOptionViewItem m_option; // 保存样式选项
QModelIndex m_index; // 保存模型索引
QString m_displayFont; // 保存显示字体
QFont m_titleFont; // 保存标题字体
QColor m_titleColor; // 保存标题颜色
QColor m_titleSelectedColor; // 保存标题选中颜色
QColor m_activeColor; // 保存活动颜色
QColor m_hoverColor; // 保存悬停颜色
QTreeView *m_view; // 保存树形视图指针
QListView *m_listView; // 保存列表视图指针
LabelEditType *m_label; // 保存标签编辑类型指针
PushButtonType *m_contextButton; // 保存上下文按钮类型指针
Theme::Value m_theme; // 保存当前主题
// 更新代理的函数
void updateDelegate();
// QWidget interface
// QWidget接口重写
protected:
// 重写绘制事件处理函数
virtual void paintEvent(QPaintEvent *event) override;
// 重写鼠标双击事件处理函数
virtual void mouseDoubleClickEvent(QMouseEvent *event) override;
};

@ -1,4 +1,4 @@
//包含头文件
// <20><><EFBFBD><EFBFBD>ͷ<EFBFBD>ļ<EFBFBD>
#include "trashbuttondelegateeditor.h"
#include <QPainter>
#include <QDebug>
@ -8,8 +8,8 @@
#include "notelistview.h"
#include "fontloader.h"
// TrashButtonDelegateEditor 类的构造函数
// 参数包括视图、样式选项、索引、列表视图和父窗口
// TrashButtonDelegateEditor <EFBFBD><EFBFBD>Ĺ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽѡ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD>͸<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
TrashButtonDelegateEditor::TrashButtonDelegateEditor(QTreeView *view,
const QStyleOptionViewItem &option,
const QModelIndex &index, QListView *listView,
@ -18,24 +18,24 @@ TrashButtonDelegateEditor::TrashButtonDelegateEditor(QTreeView *view,
m_option(option),
m_index(index),
#ifdef __APPLE__
// 在苹果系统上使用 SF Pro Text 字体,如果没有则使用 Roboto
// <20><>ƻ<EFBFBD><C6BB>ϵͳ<CFB5><CDB3>ʹ<EFBFBD><CAB9> SF Pro Text <20><><EFBFBD><EFBFBD><E5A3AC><EFBFBD>û<EFBFBD><C3BB><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9> Roboto
m_displayFont(QFont(QStringLiteral("SF Pro Text")).exactMatch()
? QStringLiteral("SF Pro Text")
: QStringLiteral("Roboto")),
#elif _WIN32
// 在 Windows 系统上使用 Segoe UI 字体,如果没有则使用 Roboto
// <20><> Windows ϵͳ<CFB5><CDB3>ʹ<EFBFBD><CAB9> Segoe UI <20><><EFBFBD><EFBFBD><E5A3AC><EFBFBD>û<EFBFBD><C3BB><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9> Roboto
m_displayFont(QFont(QStringLiteral("Segoe UI")).exactMatch() ? QStringLiteral("Segoe UI")
: QStringLiteral("Roboto")),
#else
// 在其他系统上使用 Roboto 字体
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϵͳ<CFB5><CDB3>ʹ<EFBFBD><CAB9> Roboto <20><><EFBFBD><EFBFBD>
m_displayFont(QStringLiteral("Roboto")),
#endif
#ifdef __APPLE__
// 在苹果系统上设置标题和笔记数量的字体大小
// <20><>ƻ<EFBFBD><C6BB>ϵͳ<CFB5><CDB3><EFBFBD><EFBFBD><EFBFBD>ñ<EFBFBD><C3B1><EFBFBD>ͱʼ<CDB1><CABC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>С
m_titleFont(m_displayFont, 13, QFont::DemiBold),
m_numberOfNotesFont(m_displayFont, 12, QFont::DemiBold),
#else
// 在其他系统上设置标题和笔记数量的字体大小
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϵͳ<CFB5><CDB3><EFBFBD><EFBFBD><EFBFBD>ñ<EFBFBD><C3B1><EFBFBD>ͱʼ<CDB1><CABC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>С
m_titleFont(m_displayFont, 10, QFont::DemiBold),
m_numberOfNotesFont(m_displayFont, 9, QFont::DemiBold),
#endif
@ -49,22 +49,23 @@ TrashButtonDelegateEditor::TrashButtonDelegateEditor(QTreeView *view,
m_view(view),
m_listView(listView)
{
// 设置内容边距为0
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݱ߾<EFBFBD>Ϊ0
setContentsMargins(0, 0, 0, 0);
}
// 绘制事件处理函数
// <20><><EFBFBD><EFBFBD><EFBFBD>¼<EFBFBD><C2BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// 绘制事件处理
void TrashButtonDelegateEditor::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
// 绘制图标区域
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
auto iconRect = QRect(rect().x() + 22, rect().y() + 2 + (rect().height() - 20) / 2, 18, 20);
auto iconPath = m_index.data(NodeItem::Roles::Icon).toString();
auto displayName = m_index.data(NodeItem::Roles::DisplayText).toString();
QRect nameRect(rect());
nameRect.setLeft(iconRect.x() + iconRect.width() + 5);
nameRect.setWidth(nameRect.width() - 22 - 40);
// 根据是否选中设置背景颜色和文本颜色
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƿ<EFBFBD>ѡ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ñ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɫ<EFBFBD><EFBFBD><EFBFBD>ı<EFBFBD><EFBFBD><EFBFBD>ɫ
if (m_view->selectionModel()->isSelected(m_index)) {
painter.fillRect(rect(), QBrush(m_activeColor));
painter.setPen(m_titleSelectedColor);
@ -86,12 +87,12 @@ void TrashButtonDelegateEditor::paintEvent(QPaintEvent *event)
#else
int iconPointSizeOffset = -4;
#endif
// 设置图标字体
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
painter.setFont(FontLoader::getInstance().loadFont("Font Awesome 6 Free Solid", "",
16 + iconPointSizeOffset));
painter.drawText(iconRect, iconPath); // fa-trash
// 绘制标题文本
// <EFBFBD><EFBFBD><EFBFBD>Ʊ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ı<EFBFBD>
if (m_view->selectionModel()->isSelected(m_index)) {
painter.setPen(m_titleSelectedColor);
} else {
@ -100,12 +101,12 @@ void TrashButtonDelegateEditor::paintEvent(QPaintEvent *event)
painter.setFont(m_titleFont);
painter.drawText(nameRect, Qt::AlignLeft | Qt::AlignVCenter, displayName);
// 绘制子项数量区域
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
auto childCountRect = rect();
childCountRect.setLeft(nameRect.right() + 22);
childCountRect.setWidth(childCountRect.width() - 5);
auto childCount = m_index.data(NodeItem::Roles::ChildCount).toInt();
// 根据是否选中设置子项数量文本颜色
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƿ<EFBFBD>ѡ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ı<EFBFBD><EFBFBD><EFBFBD>ɫ
if (m_view->selectionModel()->isSelected(m_index)) {
painter.setPen(m_numberOfNotesSelectedColor);
} else {
@ -117,7 +118,8 @@ void TrashButtonDelegateEditor::paintEvent(QPaintEvent *event)
QWidget::paintEvent(event);
}
// 设置主题函数
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><E2BAAF>
// 设置主题
void TrashButtonDelegateEditor::setTheme(Theme::Value theme)
{
m_theme = theme;
@ -141,4 +143,4 @@ void TrashButtonDelegateEditor::setTheme(Theme::Value theme)
break;
}
}
}
}

@ -10,39 +10,39 @@
class QTreeView;
class QListView;
// TrashButtonDelegateEditor 类声明
// TrashButtonDelegateEditor <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
class TrashButtonDelegateEditor : public QWidget
{
Q_OBJECT
public:
// 构造函数
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
explicit TrashButtonDelegateEditor(QTreeView *view, const QStyleOptionViewItem &option,
const QModelIndex &index, QListView *listView,
QWidget *parent = nullptr);
// 设置主题函数
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
void setTheme(Theme::Value theme);
private:
QStyleOptionViewItem m_option;
QModelIndex m_index;
QString m_displayFont;
QFont m_titleFont;
QFont m_numberOfNotesFont;
QColor m_titleColor;
QColor m_titleSelectedColor;
QColor m_activeColor;
QColor m_hoverColor;
QColor m_folderIconColor;
QColor m_numberOfNotesColor;
QColor m_numberOfNotesSelectedColor;
QTreeView *m_view;
QListView *m_listView;
Theme::Value m_theme;
QStyleOptionViewItem m_option; // 存储视图项的样式选项
QModelIndex m_index; // 存储模型索引
QString m_displayFont; // 显示字体
QFont m_titleFont; // 标题字体
QFont m_numberOfNotesFont; // 备注字体
QColor m_titleColor; // 标题颜色
QColor m_titleSelectedColor; // 标题选中颜色
QColor m_activeColor; // 激活状态颜色
QColor m_hoverColor; // 悬停状态颜色
QColor m_folderIconColor; // 文件夹图标颜色
QColor m_numberOfNotesColor; // 备注颜色
QColor m_numberOfNotesSelectedColor; // 备注选中颜色
QTreeView *m_view; // 视图指针
QListView *m_listView; // 列表视图指针
Theme::Value m_theme; // 主题值
// QWidget interface
// QWidget 接口
// QWidget <20>ӿ<EFBFBD>
protected:
// 绘制事件处理函数
virtual void paintEvent(QPaintEvent *event) override;
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>¼<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
virtual void paintEvent(QPaintEvent *event) override; // 绘制事件
};
#endif // TRASHBUTTONDELEGATEEDITOR_H
#endif // TRASHBUTTONDELEGATEEDITOR_H

@ -1,4 +1,4 @@
// 包含头文件
/// <20><><EFBFBD><EFBFBD>ͷ<EFBFBD>ļ<EFBFBD>
#include "treeviewlogic.h"
#include "nodetreeview.h"
#include "nodetreemodel.h"
@ -12,7 +12,8 @@
#include <QApplication>
#include "customapplicationstyle.h"
// TreeViewLogic 类的构造函数
// TreeViewLogic <20><>Ĺ<EFBFBD><C4B9><EFBFBD><ECBAAF>
// 管理树视图的逻辑,包括节点的增删改查和更新
TreeViewLogic::TreeViewLogic(NodeTreeView *treeView, NodeTreeModel *treeModel, DBManager *dbManager,
NoteListView *listView, QObject *parent)
: QObject(parent),
@ -26,16 +27,16 @@ TreeViewLogic::TreeViewLogic(NodeTreeView *treeView, NodeTreeModel *treeModel, D
m_lastSelectTags{},
m_expandedFolder{}
{
// 初始化树视图代理
// <EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
m_treeDelegate = new NodeTreeDelegate(m_treeView, m_treeView, m_listView);
m_treeView->setItemDelegate(m_treeDelegate);
// 连接数据库管理器信号
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݿ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ź<EFBFBD>
connect(m_dbManager, &DBManager::nodesTagTreeReceived, this, &TreeViewLogic::loadTreeModel,
Qt::QueuedConnection);
// 连接树模型信号
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģ<EFBFBD><EFBFBD><EFBFBD>ź<EFBFBD>
connect(m_treeModel, &NodeTreeModel::topLevelItemLayoutChanged, this,
&TreeViewLogic::updateTreeViewSeparator);
// 连接树视图信号
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD>ź<EFBFBD>
connect(m_treeView, &NodeTreeView::addFolderRequested, this,
[this] { onAddFolderRequested(false); });
connect(m_treeDelegate, &NodeTreeDelegate::addFolderRequested, this,
@ -84,23 +85,25 @@ TreeViewLogic::TreeViewLogic(NodeTreeView *treeView, NodeTreeModel *treeModel, D
&TreeViewLogic::onChildNoteCountChangedFolder);
connect(m_dbManager, &DBManager::childNotesCountUpdatedTag, this,
&TreeViewLogic::onChildNotesCountChangedTag);
// 设置应用样式
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ӧ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ
m_style = new CustomApplicationStyle();
qApp->setStyle(m_style);
}
// 更新树视图分隔符
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD>ָ<EFBFBD><D6B8><EFBFBD>
// 更新树视图的分隔符
void TreeViewLogic::updateTreeViewSeparator()
{
m_treeView->setTreeSeparator(m_treeModel->getSeparatorIndex(),
m_treeModel->getDefaultNotesIndex());
}
// 加载树模型
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģ<EFBFBD><C4A3>
// 加载树模型的数据
void TreeViewLogic::loadTreeModel(const NodeTagTreeData &treeData)
{
m_treeModel->setTreeData(treeData);
// 获取根节点的子节点数量
// <EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD><EFBFBD>ڵ<EFBFBD><EFBFBD><EFBFBD>ӽڵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
{
NodeData node;
QMetaObject::invokeMethod(m_dbManager, "getChildNotesCountFolder",
@ -111,7 +114,7 @@ void TreeViewLogic::loadTreeModel(const NodeTagTreeData &treeData)
m_treeModel->setData(index, node.childNotesCount(), NodeItem::Roles::ChildCount);
}
}
// 获取垃圾箱节点的子节点数量
// <EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڵ<EFBFBD><EFBFBD><EFBFBD>ӽڵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
{
NodeData node;
QMetaObject::invokeMethod(m_dbManager, "getChildNotesCountFolder",
@ -122,7 +125,7 @@ void TreeViewLogic::loadTreeModel(const NodeTagTreeData &treeData)
m_treeModel->setData(index, node.childNotesCount(), NodeItem::Roles::ChildCount);
}
}
// 加载保存的状态
// <EFBFBD><EFBFBD><EFBFBD>ر<EFBFBD><EFBFBD><EFBFBD><EFBFBD>״̬
if (m_needLoadSavedState) {
m_needLoadSavedState = false;
m_treeView->reExpandC(m_expandedFolder);
@ -154,7 +157,8 @@ void TreeViewLogic::loadTreeModel(const NodeTagTreeData &treeData)
updateTreeViewSeparator();
}
// 处理添加文件夹请求
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// 添加文件夹的请求处理
void TreeViewLogic::onAddFolderRequested(bool fromPlusButton)
{
QModelIndex currentIndex;
@ -175,7 +179,7 @@ void TreeViewLogic::onAddFolderRequested(bool fromPlusButton)
static_cast<NodeItem::Type>(currentIndex.data(NodeItem::Roles::ItemType).toInt());
if (type == NodeItem::FolderItem) {
parentId = currentIndex.data(NodeItem::Roles::NodeId).toInt();
// 不允许在默认笔记文件夹下创建子文件夹
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĭ<EFBFBD>ϱʼ<EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>´<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD>
if (parentId == SpecialNodeID::DefaultNotesFolder) {
parentId = SpecialNodeID::RootFolder;
}
@ -248,13 +252,14 @@ void TreeViewLogic::onAddFolderRequested(bool fromPlusButton)
}
}
// 处理添加标签请求
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӱ<EFBFBD>ǩ<EFBFBD><C7A9><EFBFBD><EFBFBD>
// 处理添加标签请求
void TreeViewLogic::onAddTagRequested()
{
int newlyCreatedTagId;
TagData newTag;
newTag.setName(m_treeModel->getNewTagPlaceholderName());
// 随机颜色生成器
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
const double lower_bound = 0;
const double upper_bound = 1;
static std::uniform_real_distribution<double> unif(lower_bound, upper_bound);
@ -278,7 +283,8 @@ void TreeViewLogic::onAddTagRequested()
m_treeModel->appendChildNodeToParent(m_treeModel->rootIndex(), hs);
}
// 处理重命名节点请求
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڵ<EFBFBD><DAB5><EFBFBD><EFBFBD><EFBFBD>
// 处理从树视图请求重命名节点
void TreeViewLogic::onRenameNodeRequestedFromTreeView(const QModelIndex &index,
const QString &newName)
{
@ -287,7 +293,8 @@ void TreeViewLogic::onRenameNodeRequestedFromTreeView(const QModelIndex &index,
emit requestRenameNodeInDB(id, newName);
}
// 处理删除文件夹请求
// <20><><EFBFBD><EFBFBD>ɾ<EFBFBD><C9BE><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// 处理删除文件夹的请求
void TreeViewLogic::onDeleteFolderRequested(const QModelIndex &index)
{
auto btn = QMessageBox::question(nullptr, "Are you sure you want to delete this folder",
@ -319,7 +326,8 @@ void TreeViewLogic::onDeleteFolderRequested(const QModelIndex &index)
}
}
// 处理重命名标签请求
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǩ<EFBFBD><C7A9><EFBFBD><EFBFBD>
// 处理从树视图请求重命名标签
void TreeViewLogic::onRenameTagRequestedFromTreeView(const QModelIndex &index,
const QString &newName)
{
@ -328,7 +336,8 @@ void TreeViewLogic::onRenameTagRequestedFromTreeView(const QModelIndex &index,
emit requestRenameTagInDB(id, newName);
}
// 处理修改标签颜色请求
// <20><><EFBFBD><EFBFBD><EFBFBD>޸ı<DEB8>ǩ<EFBFBD><C7A9>ɫ<EFBFBD><C9AB><EFBFBD><EFBFBD>
// 处理颜色请求的标签
void TreeViewLogic::onChangeTagColorRequested(const QModelIndex &index)
{
if (index.isValid()) {
@ -342,7 +351,8 @@ void TreeViewLogic::onChangeTagColorRequested(const QModelIndex &index)
}
}
// 处理删除标签请求
// <20><><EFBFBD><EFBFBD>ɾ<EFBFBD><C9BE><EFBFBD><EFBFBD>ǩ<EFBFBD><C7A9><EFBFBD><EFBFBD>
// 处理删除标签的请求
void TreeViewLogic::onDeleteTagRequested(const QModelIndex &index)
{
auto id = index.data(NodeItem::Roles::NodeId).toInt();
@ -351,7 +361,8 @@ void TreeViewLogic::onDeleteTagRequested(const QModelIndex &index)
m_treeView->setCurrentIndexC(m_treeModel->getAllNotesButtonIndex());
}
// 处理标签子节点数量变化
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǩ<EFBFBD>ӽڵ<D3BD><DAB5><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// 更新标签的子项数量
void TreeViewLogic::onChildNotesCountChangedTag(int tagId, int notesCount)
{
auto index = m_treeModel->tagIndexFromId(tagId);
@ -360,7 +371,8 @@ void TreeViewLogic::onChildNotesCountChangedTag(int tagId, int notesCount)
}
}
// 处理文件夹子节点数量变化
// <20><><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ӽڵ<D3BD><DAB5><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// 更新文件夹的子项数量
void TreeViewLogic::onChildNoteCountChangedFolder(int folderId, const QString &absPath,
int notesCount)
{
@ -377,7 +389,8 @@ void TreeViewLogic::onChildNoteCountChangedFolder(int folderId, const QString &a
}
}
// 打开文件夹
// <20><><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD>
// 打开指定文件夹
void TreeViewLogic::openFolder(int id)
{
NodeData target;
@ -401,7 +414,8 @@ void TreeViewLogic::openFolder(int id)
}
}
// 处理移动节点请求
// <20><><EFBFBD><EFBFBD><EFBFBD>ƶ<EFBFBD><C6B6>ڵ<EFBFBD><DAB5><EFBFBD><EFBFBD><EFBFBD>
// 处理移动节点的请求
void TreeViewLogic::onMoveNodeRequested(int nodeId, int targetId)
{
NodeData target;
@ -414,7 +428,8 @@ void TreeViewLogic::onMoveNodeRequested(int nodeId, int targetId)
emit requestMoveNodeInDB(nodeId, target);
}
// 设置主题
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// 设置主题
void TreeViewLogic::setTheme(Theme::Value theme)
{
m_treeView->setTheme(theme);
@ -422,7 +437,8 @@ void TreeViewLogic::setTheme(Theme::Value theme)
m_style->setTheme(theme);
}
// 设置最后保存的状态
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>󱣴<EFBFBD><F3B1A3B4>״̬
// 设置最后保存的状态
void TreeViewLogic::setLastSavedState(bool isLastSelectFolder, const QString &lastSelectFolder,
const QSet<int> &lastSelectTag,
const QStringList &expandedFolder)
@ -432,4 +448,4 @@ void TreeViewLogic::setLastSavedState(bool isLastSelectFolder, const QString &la
m_lastSelectTags = lastSelectTag;
m_expandedFolder = expandedFolder;
m_needLoadSavedState = true;
}
}

@ -12,76 +12,86 @@ class DBManager;
class CustomApplicationStyle;
class NoteListView;
// TreeViewLogic 类负责管理树视图逻辑
// TreeViewLogic 类负责管理树视图逻辑
class TreeViewLogic : public QObject
{
Q_OBJECT
public:
// 构造函数
explicit TreeViewLogic(NodeTreeView* treeView, NodeTreeModel* treeModel, DBManager* dbManager,
NoteListView* listView, QObject* parent = nullptr);
// 打开文件夹
// 构造函数
explicit TreeViewLogic(NodeTreeView *treeView, NodeTreeModel *treeModel, DBManager *dbManager,
NoteListView *listView, QObject *parent = nullptr);
// 打开文件夹
void openFolder(int id);
// 处理移动节点请求
// 请求移动节点
void onMoveNodeRequested(int nodeId, int targetId);
// 设置主题
// 设置主题
void setTheme(Theme::Value theme);
// 设置最后保存的状态
void setLastSavedState(bool isLastSelectFolder, const QString& lastSelectFolder,
const QSet<int>& lastSelectTag, const QStringList& expandedFolder);
// 设置最后保存的状态
void setLastSavedState(bool isLastSelectFolder, const QString &lastSelectFolder,
const QSet<int> &lastSelectTag, const QStringList &expandedFolder);
private slots:
// 更新树视图分隔符
// 更新树视图分隔符
void updateTreeViewSeparator();
// 加载树模型
void loadTreeModel(const NodeTagTreeData& treeData);
// 处理添加标签请求
// 加载树模型
void loadTreeModel(const NodeTagTreeData &treeData);
// 请求添加标签
void onAddTagRequested();
// 处理重命名节点请求
void onRenameNodeRequestedFromTreeView(const QModelIndex& index, const QString& newName);
// 处理删除文件夹请求
void onDeleteFolderRequested(const QModelIndex& index);
// 处理重命名标签请求
void onRenameTagRequestedFromTreeView(const QModelIndex& index, const QString& newName);
// 处理修改标签颜色请求
void onChangeTagColorRequested(const QModelIndex& index);
// 处理删除标签请求
void onDeleteTagRequested(const QModelIndex& index);
// 处理标签子节点数量变化
// 请求重命名树视图中的节点
void onRenameNodeRequestedFromTreeView(const QModelIndex &index, const QString &newName);
// 请求删除文件夹
void onDeleteFolderRequested(const QModelIndex &index);
// 请求重命名树视图中的标签
void onRenameTagRequestedFromTreeView(const QModelIndex &index, const QString &newName);
// 请求更改标签颜色
void onChangeTagColorRequested(const QModelIndex &index);
// 请求删除标签
void onDeleteTagRequested(const QModelIndex &index);
// 标签的子笔记数量变化
void onChildNotesCountChangedTag(int tagId, int notesCount);
// 处理文件夹子节点数量变化
void onChildNoteCountChangedFolder(int folderId, const QString& absPath, int notesCount);
// 文件夹的子笔记数量变化
void onChildNoteCountChangedFolder(int folderId, const QString &absPath, int notesCount);
signals:
// 请求重命名节点
void requestRenameNodeInDB(int id, const QString& newName);
// 请求重命名标签
void requestRenameTagInDB(int id, const QString& newName);
// 请求改标签颜色
void requestChangeTagColorInDB(int id, const QString& newColor);
// 请求移动节点
void requestMoveNodeInDB(int id, const NodeData& target);
// 添加笔记到标签
// 请求在数据库中重命名节点
void requestRenameNodeInDB(int id, const QString &newName);
// 请求在数据库中重命名标签
void requestRenameTagInDB(int id, const QString &newName);
// 请求在数据库中更改标签颜色
void requestChangeTagColorInDB(int id, const QString &newColor);
// 请求在数据库中移动节点
void requestMoveNodeInDB(int id, const NodeData &target);
// 向标签添加笔记
void addNoteToTag(int noteId, int tagId);
// 节点移动
// 通知节点移动
void noteMoved(int nodeId, int targetId);
private:
// 处理添加文件夹请求
// 请求添加文件夹
void onAddFolderRequested(bool fromPlusButton);
private:
// 成员变量
NodeTreeView* m_treeView;
NodeTreeModel* m_treeModel;
NoteListView* m_listView;
NodeTreeDelegate* m_treeDelegate;
DBManager* m_dbManager;
CustomApplicationStyle* m_style;
// 节点树视图
NodeTreeView *m_treeView;
// 节点树模型
NodeTreeModel *m_treeModel;
// 笔记列表视图
NoteListView *m_listView;
// 节点树代理
NodeTreeDelegate *m_treeDelegate;
// 数据库管理器
DBManager *m_dbManager;
// 自定义应用样式
CustomApplicationStyle *m_style;
// 是否需要加载保存的状态
bool m_needLoadSavedState;
// 是否是最后选择的文件夹
bool m_isLastSelectFolder;
// 最后选择的文件夹路径
QString m_lastSelectFolder;
// 最后选择的标签集合
QSet<int> m_lastSelectTags;
// 展开的文件夹列表
QStringList m_expandedFolder;
};

@ -33,13 +33,13 @@ static QProcess XDGOPEN_PROCESS;
#endif
/**
* Indicates from where we should download the update definitions file
*
*/
static const QString
UPDATES_URL("https://raw.githubusercontent.com/nuttyartist/notes/master/UPDATES.json");
/**
* Initializes the window components and configures the QSimpleUpdater
* QSimpleUpdater
*/
UpdaterWindow::UpdaterWindow(QWidget *parent)
: QDialog(parent),
@ -53,21 +53,21 @@ UpdaterWindow::UpdaterWindow(QWidget *parent)
m_updater(QSimpleUpdater::getInstance()),
m_manager(new QNetworkAccessManager(this))
{
/* Load the stylesheet */
/* 加载样式表 */
QFile file(":/styles/updater-window.css");
if (file.open(QIODevice::ReadOnly)) {
setStyleSheet(QString::fromUtf8(file.readAll()).toLatin1());
file.close();
}
/* Initialize the UI */
/* 初始化 UI */
m_ui->setupUi(this);
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
setWindowFlag(Qt::WindowContextHelpButtonHint, false);
#endif
setWindowTitle(qApp->applicationName() + " " + tr("Updater"));
/* Change fonts */
/* 更改字体 */
#ifdef __APPLE__
QFont fontToUse = QFont(QStringLiteral("SF Pro Text")).exactMatch()
? QStringLiteral("SF Pro Text")
@ -86,47 +86,50 @@ UpdaterWindow::UpdaterWindow(QWidget *parent)
widgetChild->setFont(fontToUse);
}
/* Connect UI signals/slots */
/* 连接 UI 信号/槽 */
connect(m_ui->closeButton, &QPushButton::clicked, this, &UpdaterWindow::close);
connect(m_ui->updateButton, &QPushButton::clicked, this,
&UpdaterWindow::onDownloadButtonClicked);
connect(m_updater, &QSimpleUpdater::checkingFinished, this, &UpdaterWindow::onCheckFinished);
connect(m_ui->checkBox, &QCheckBox::toggled, this, &UpdaterWindow::dontShowUpdateWindowChanged);
/* Start the UI loops */
/* 启动 UI 循环 */
updateTitleLabel();
/* Remove native window decorations */
/* 移除原生窗口装饰 */
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
setWindowFlags(windowFlags() | Qt::CustomizeWindowHint | Qt::FramelessWindowHint);
#else
setWindowFlags(windowFlags() | Qt::CustomizeWindowHint);
#endif
/* React when xdg-open finishes (Linux only) */
/* 当 xdg-open 完成时响应(仅限 Linux */
#ifdef UseXdgOpen
connect(&XDGOPEN_PROCESS, SIGNAL(finished(int)), this, SLOT(onXdgOpenFinished(int)));
#endif
/* Set window flags */
/* 设置窗口标志 */
setWindowModality(Qt::ApplicationModal);
}
/**
* Deletes the user interface
*
*/
UpdaterWindow::~UpdaterWindow()
{
/* Ensure that xdg-open process is closed */
/* 确保 xdg-open 进程已关闭 */
#ifdef UseXdgOpen
if (XDGOPEN_PROCESS.isOpen())
XDGOPEN_PROCESS.close();
#endif
/* Delete UI controls */
/* 删除 UI 控件 */
delete m_ui;
}
/**
*
*/
void UpdaterWindow::setShowWindowDisable(const bool dontShowWindow)
{
m_dontShowUpdateWindow = dontShowWindow;
@ -134,29 +137,28 @@ void UpdaterWindow::setShowWindowDisable(const bool dontShowWindow)
}
/**
* Instructs the QSimpleUpdater to download and interpret the updater
* definitions file
* QSimpleUpdater
*/
void UpdaterWindow::checkForUpdates(bool force)
{
/* Change the silent flag */
/* 更改静默标志 */
if (!m_updater->getUpdateAvailable(UPDATES_URL)) {
m_checkingForUpdates = true;
m_forced = force;
/* Set module properties */
/* 设置模块属性 */
m_updater->setNotifyOnFinish(UPDATES_URL, false);
m_updater->setNotifyOnUpdate(UPDATES_URL, false);
m_updater->setDownloaderEnabled(UPDATES_URL, false);
m_updater->setUseCustomInstallProcedures(UPDATES_URL, true);
m_updater->setModuleVersion(UPDATES_URL, qApp->applicationVersion());
/* Check for updates */
/* 检查更新 */
m_updater->checkForUpdates(UPDATES_URL);
}
/* Show window if force flag is set */
/* 如果 force 标志被设置,则显示窗口 */
if (force) {
if (!m_updater->getUpdateAvailable(UPDATES_URL)) {
m_ui->updateButton->setEnabled(false);
@ -169,65 +171,62 @@ void UpdaterWindow::checkForUpdates(bool force)
}
/**
* Resets the state, text and information displayed the the UI controls
* to indicate what is happening right now in the QSimpleUpdater
* UI QSimpleUpdater
*/
void UpdaterWindow::resetControls()
{
/* Reset the button states */
/* 重置按钮状态 */
m_ui->updateButton->setEnabled(false);
/* Set installed version label */
/* 设置已安装版本标签 */
m_ui->installedVersion->setText(qApp->applicationVersion());
/* Set available version label */
/* 设置可用版本标签 */
m_ui->availableVersion->setText(m_updater->getLatestVersion(UPDATES_URL));
/* Set title label */
/* 设置标题标签 */
if (m_updater->getUpdateAvailable(UPDATES_URL)) {
m_ui->title->setText(tr("A Newer Version is Available!"));
} else {
m_ui->title->setText(tr("You're up-to-date!"));
}
/* Reset the progress controls */
/* 重置进度控件 */
m_ui->progressControls->hide();
m_ui->progressBar->setValue(0);
m_ui->downloadLabel->setText(tr("Downloading Updates") + "...");
m_ui->timeLabel->setText(tr("Time remaining") + ": " + tr("unknown"));
/* Set changelog text */
/* 设置更新日志文本 */
QString changelogText = m_updater->getChangelog(UPDATES_URL);
m_ui->changelog->setText(changelogText);
if (m_ui->changelog->toPlainText().isEmpty()) {
m_ui->changelog->setText("<p>No changelog found...</p>");
} else {
m_ui->changelog->setText(changelogText.append(
"\n")); // Don't know why currently changelog box is disappearing at the bottom, so
// I add a new line to see the text.
"\n")); // 不知道为什么当前更新日志框在底部消失了,所以添加一个新行以查看文本。
}
/* Enable/disable update button */
/* 启用/禁用更新按钮 */
bool available = m_updater->getUpdateAvailable(UPDATES_URL);
bool validOpenUrl = !m_updater->getOpenUrl(UPDATES_URL).isEmpty();
bool validDownUrl = !m_updater->getDownloadUrl(UPDATES_URL).isEmpty();
m_ui->updateButton->setEnabled(available && (validOpenUrl || validDownUrl));
/* Resize window */
/* 调整窗口大小 */
bool showAgain = isVisible();
int height = minimumSizeHint().height();
int width = qMax(minimumSizeHint().width(), int(height * 1.2));
resize(QSize(width, height));
/* Re-show the window(if required)*/
/* 重新显示窗口(如果需要) */
if (showAgain) {
showNormal();
}
}
/**
* Changes the number of dots of the title label while the QSimpleUpdater
* is downloading and interpreting the update definitions file
* QSimpleUpdater
*/
void UpdaterWindow::updateTitleLabel()
{
@ -245,8 +244,7 @@ void UpdaterWindow::updateTitleLabel()
}
/**
* Updates the text displayed the the UI controls to reflect the information
* obtained by the QSimpleUpdater and shows the dialog
* UI QSimpleUpdater
*/
void UpdaterWindow::onUpdateAvailable()
{
@ -257,7 +255,7 @@ void UpdaterWindow::onUpdateAvailable()
}
/**
* Initializes the download of the update and disables the 'update' button
*
*/
void UpdaterWindow::onDownloadButtonClicked()
{
@ -268,25 +266,23 @@ void UpdaterWindow::onDownloadButtonClicked()
}
/**
* Initializes the download of the update and configures the connections
* to automatically update the user interface when we receive new data
* from the download/update server
* /
*/
void UpdaterWindow::startDownload(const QUrl &url)
{
/* URL is invalid, try opening web browser */
/* URL 无效,尝试打开网页浏览器 */
if (url.isEmpty()) {
QDesktopServices::openUrl(QUrl(m_updater->getOpenUrl(UPDATES_URL)));
return;
}
/* Cancel previous download (if any)*/
/* 取消之前的下载(如果有) */
if (m_reply) {
m_reply->abort();
m_reply->deleteLater();
}
/* Start download */
/* 开始下载 */
m_startTime = QDateTime::currentDateTime().toSecsSinceEpoch();
QNetworkRequest netReq(url);
@ -294,7 +290,7 @@ void UpdaterWindow::startDownload(const QUrl &url)
QNetworkRequest::NoLessSafeRedirectPolicy);
m_reply = m_manager->get(netReq);
/* Set file name */
/* 设置文件名 */
m_fileName = m_updater->getDownloadUrl(UPDATES_URL).split("/").last();
if (m_fileName.isEmpty()) {
m_fileName = QString("%1_Update_%2.bin")
@ -302,37 +298,37 @@ void UpdaterWindow::startDownload(const QUrl &url)
m_updater->getLatestVersion(UPDATES_URL));
}
/* Prepare download directory */
/* 准备下载目录 */
if (!m_downloadDir.exists())
m_downloadDir.mkpath(".");
/* Remove previous downloads(if any)*/
/* 删除之前的下载(如果有) */
QFile::remove(m_downloadDir.filePath(m_fileName));
/* Show UI controls */
/* 显示 UI 控件 */
m_ui->progressControls->show();
showNormal();
/* Update UI when download progress changes or download finishes */
/* 当下载进度变化或下载完成时更新 UI */
connect(m_reply, &QNetworkReply::downloadProgress, this, &UpdaterWindow::updateProgress);
connect(m_reply, &QNetworkReply::finished, this, &UpdaterWindow::onDownloadFinished);
}
/**
* Opens the downloaded file
*
*/
void UpdaterWindow::openDownload(const QString &file)
{
/* File is empty, abort */
/* 文件为空,中止 */
if (file.isEmpty()) {
return;
}
/* Change labels */
/* 更改标签 */
m_ui->downloadLabel->setText(tr("Download finished!"));
m_ui->timeLabel->setText(tr("Opening downloaded file") + "...");
/* Try to open the downloaded file (Windows & Mac) */
/* 尝试打开下载的文件Windows 和 Mac */
#ifndef UseXdgOpen
bool openUrl = QDesktopServices::openUrl(QUrl::fromLocalFile(file));
if (!openUrl) {
@ -346,27 +342,25 @@ void UpdaterWindow::openDownload(const QString &file)
}
#endif
/* On Linux, use xdg-open to know if the file was handled correctly */
/* 在 Linux 上,使用 xdg-open 来确认文件是否被正确处理 */
#ifdef UseXdgOpen
XDGOPEN_PROCESS.start("xdg-open", QStringList() << file);
#endif
}
/**
* Called when the \a QSimpleUpdater notifies us that it downloaded and
* interpreted the update definitions file.
* QSimpleUpdater
*
* This function decides whenever to show the dialog or just notify the user
* that he/she is running the latest version of notes
* / notes
*/
void UpdaterWindow::onCheckFinished(const QString &url)
{
Q_UNUSED(url)
/* Do not allow the title label to change automatically */
/* 不允许标题标签自动更改 */
m_checkingForUpdates = false;
/* There is an update available, show the window */
/* 如果有可用更新,显示窗口 */
if (m_updater->getUpdateAvailable(url) && (UPDATES_URL == url)) {
onUpdateAvailable();
} else if (m_forced) {
@ -377,9 +371,8 @@ void UpdaterWindow::onCheckFinished(const QString &url)
}
/**
* Called when \c xdg-open finishes with the given \a exitCode.
* If \a exitCode is not 0, then we shall try to open the folder in which
* the update file was saved
* xdg-open exitCode
* exitCode 0
*/
void UpdaterWindow::onXdgOpenFinished(const int exitCode)
{
@ -396,13 +389,11 @@ void UpdaterWindow::onXdgOpenFinished(const int exitCode)
}
/**
* Notifies the user that there's been an error opening the downloaded
* file directly and instructs the operating system to open the folder
* in which the \a file is located
*
*/
void UpdaterWindow::openDownloadFolder(const QString &file)
{
/* Notify the user of the problem */
/* 通知用户问题 */
QString extension = file.split(".").last();
QMessageBox::information(this, tr("Open Error"),
tr("It seems that your OS does not have an "
@ -411,30 +402,29 @@ void UpdaterWindow::openDownloadFolder(const QString &file)
.arg(extension),
QMessageBox::Ok);
/* Get the full path list of the downloaded file */
/* 获取下载文件的完整路径列表 */
QString native_path = QDir::toNativeSeparators(QDir(file).absolutePath());
QStringList directories = native_path.split(QDir::separator());
/* Remove file name from list to get the folder of the update file */
/* 从列表中删除文件名以获取更新文件的文件夹 */
directories.removeLast();
QString path = directories.join(QDir::separator());
/* Get valid URL and open it */
/* 获取有效的 URL 并打开它 */
QUrl url = QUrl::fromLocalFile(QDir(path).absolutePath());
QDesktopServices::openUrl(url);
}
/**
* Calculates the appropriate size units(bytes, KB or MB)for the received
* data and the total download size. Then, this function proceeds to update the
* dialog controls/UI.
* KB MB
* /UI
*/
void UpdaterWindow::calculateSizes(qint64 received, qint64 total)
{
QString totalSize;
QString receivedSize;
/* Get total size string */
/* 获取总大小字符串 */
if (total < 1024) {
totalSize = tr("%1 bytes").arg(total);
} else if (total < (1024 * 1024)) {
@ -443,7 +433,7 @@ void UpdaterWindow::calculateSizes(qint64 received, qint64 total)
totalSize = tr("%1 MB").arg(round(total / (1024 * 1024)));
}
/* Get received size string */
/* 获取接收大小字符串 */
if (received < 1024) {
receivedSize = tr("%1 bytes").arg(received);
} else if (received < (1024 * 1024)) {
@ -452,14 +442,13 @@ void UpdaterWindow::calculateSizes(qint64 received, qint64 total)
receivedSize = tr("%1 MB").arg(received / (1024 * 1024));
}
/* Update the label text */
/* 更新标签文本 */
m_ui->downloadLabel->setText(tr("Downloading updates") + " (" + receivedSize + " " + tr("of")
+ " " + totalSize + ")");
}
/**
* Uses the \a received and \a total parameters to get the download progress
* and update the progressbar value on the dialog.
* 使 received total
*/
void UpdaterWindow::updateProgress(qint64 received, qint64 total)
{
@ -480,12 +469,9 @@ void UpdaterWindow::updateProgress(qint64 received, qint64 total)
}
/**
* Uses two time samples(from the current time and a previous sample)to
* calculate how many bytes have been downloaded.
* 使
*
* Then, this function proceeds to calculate the appropriate units of time
*(hours, minutes or seconds)and constructs a user-friendly string, which
* is displayed in the dialog.
*
*/
void UpdaterWindow::calculateTimeRemaining(qint64 received, qint64 total)
{
@ -519,83 +505,4 @@ void UpdaterWindow::calculateTimeRemaining(qint64 received, qint64 total)
if (seconds > 1) {
timeString = tr("%1 seconds").arg(seconds);
} else {
timeString = tr("1 second");
}
}
m_ui->timeLabel->setText(tr("Time remaining") + ": " + timeString);
}
}
void UpdaterWindow::onDownloadFinished()
{
QString redirectedUrl =
m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toString();
if (redirectedUrl.isEmpty()) {
const QString filePath = m_downloadDir.filePath(m_fileName);
QFile file(filePath);
if (file.open(QIODevice::WriteOnly | QIODevice::Append)) {
file.write(m_reply->readAll());
file.close();
qApp->processEvents();
}
openDownload(filePath);
} else {
startDownload(redirectedUrl);
}
}
/**
* Allows the user to move the window and registers the position in which
* the user originally clicked to move the window
*/
void UpdaterWindow::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
if (event->x() < width() - 5 && event->x() > 5 && event->pos().y() < height() - 5
&& event->pos().y() > 5) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
m_canMoveWindow = !window()->windowHandle()->startSystemMove();
#else
m_canMoveWindow = true;
#endif
m_mousePressX = event->pos().x();
m_mousePressY = event->pos().y();
}
}
event->accept();
}
/**
* Changes the cursor icon to a hand(to hint the user that he/she is dragging
* the window)and moves the window to the desired position of the given
* \a event
*/
void UpdaterWindow::mouseMoveEvent(QMouseEvent *event)
{
if (m_canMoveWindow) {
int dx = event->globalX() - m_mousePressX;
int dy = event->globalY() - m_mousePressY;
move(dx, dy);
}
}
/**
* Disallows the user to move the window and resets the window cursor
*/
void UpdaterWindow::mouseReleaseEvent(QMouseEvent *event)
{
m_canMoveWindow = false;
unsetCursor();
event->accept();
}
/**
* Rounds the given \a input to two decimal places
*/
qreal UpdaterWindow::round(qreal input)
{
return qreal(roundf(float(input * 100)) / 100);
}
timeString

@ -19,64 +19,87 @@ class QNetworkReply;
class QSimpleUpdater;
class QNetworkAccessManager;
// UpdaterWindow 类用于管理更新窗口的显示和更新逻辑
class UpdaterWindow : public QDialog
{
Q_OBJECT
public:
// 构造函数,初始化更新窗口
explicit UpdaterWindow(QWidget *parent = 0);
// 析构函数,释放资源
~UpdaterWindow();
// 设置是否显示更新窗口
void setShowWindowDisable(const bool dontShowWindow);
public slots:
// 检查更新force 参数表示是否强制检查
void checkForUpdates(bool force);
signals:
// 信号,当更新窗口显示状态改变时发出
void dontShowUpdateWindowChanged(bool state);
private slots:
// 重置控件状态
void resetControls();
// 更新标题标签
void updateTitleLabel();
// 当有可用更新时调用
void onUpdateAvailable();
// 当下载按钮被点击时调用
void onDownloadButtonClicked();
// 开始下载url 参数表示下载链接
void startDownload(const QUrl &url);
// 打开下载的文件file 参数表示文件路径
void openDownload(const QString &file);
// 当检查更新完成时调用url 参数表示检查的链接
void onCheckFinished(const QString &url);
// 当 xdg-open 命令完成时调用exitCode 参数表示退出码
void onXdgOpenFinished(const int exitCode);
// 打开下载文件夹file 参数表示文件路径
void openDownloadFolder(const QString &file);
// 计算下载大小received 参数表示已接收的字节数total 参数表示总字节数
void calculateSizes(qint64 received, qint64 total);
// 更新下载进度received 参数表示已接收的字节数total 参数表示总字节数
void updateProgress(qint64 received, qint64 total);
// 计算剩余下载时间received 参数表示已接收的字节数total 参数表示总字节数
void calculateTimeRemaining(qint64 received, qint64 total);
// 当下载完成时调用
void onDownloadFinished();
protected:
// 处理鼠标移动事件
void mouseMoveEvent(QMouseEvent *event) override;
// 处理鼠标按下事件
void mousePressEvent(QMouseEvent *event) override;
// 处理鼠标释放事件
void mouseReleaseEvent(QMouseEvent *event) override;
private:
// 四舍五入函数input 参数表示输入值
qreal round(qreal input);
private:
QString m_fileName;
const QDir m_downloadDir;
QString m_fileName; // 文件名
const QDir m_downloadDir; // 下载目录
Ui::UpdaterWindow *m_ui;
Ui::UpdaterWindow *m_ui; // 用户界面指针
QPoint m_dragPosition;
QPoint m_dragPosition; // 窗口拖动位置
int m_mousePressX;
int m_mousePressY;
bool m_canMoveWindow;
bool m_checkingForUpdates;
bool m_dontShowUpdateWindow;
bool m_forced;
int m_mousePressX; // 鼠标按下时的 X 坐标
int m_mousePressY; // 鼠标按下时的 Y 坐标
bool m_canMoveWindow; // 是否可以移动窗口
bool m_checkingForUpdates; // 是否正在检查更新
bool m_dontShowUpdateWindow; // 是否不显示更新窗口
bool m_forced; // 是否强制检查更新
uint m_startTime;
QNetworkReply *m_reply;
QSimpleUpdater *m_updater;
QNetworkAccessManager *m_manager;
uint m_startTime; // 下载开始时间
QNetworkReply *m_reply; // 网络回复指针
QSimpleUpdater *m_updater; // 更新器指针
QNetworkAccessManager *m_manager; // 网络访问管理器指针
};
#endif
#endif
Loading…
Cancel
Save