- 第三栏内容工厂:按拖入类型展示动作,动作按钮本身可接收 drop - 链接分类:http(s) 按扩展名判定图片/音视频/文档/压缩包/网页 - http 链接动作:打开、下载、另存为、复制、Markdown、复制文件名、待办、问小埃 - 非标准 scheme:尝试打开、复制;thunder 解析 base64 真实地址,magnet 解析 dn - 文字动作:ICU 分词、正则提取链接、朗读(spd-say)、复制、待办 - 文件/图片动作:打开、提取文字(占位)、生成描述、保存图片、复制路径、待办 - Downloader:异步下载到 XDG 下载目录 / 另存为,面板 toast 提示 - TodoModel:持久化到 todo.json,供「添加到待办」使用 - 无头自测 gxde-ai-see-selftest:链接分类/分词/提取链接/动作匹配 - 合成器 portal 自测脚本 tests/portal(虚拟指针 + GTK 源/目标)
166 lines
4.1 KiB
C++
166 lines
4.1 KiB
C++
// SPDX-FileCopyrightText: 2026 GXDE OS Team
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
#include "todomodel.h"
|
|
|
|
#include <QDateTime>
|
|
#include <QFile>
|
|
#include <QJsonArray>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QSaveFile>
|
|
#include <QUuid>
|
|
|
|
#include "config.h"
|
|
|
|
TodoModel *TodoModel::instance()
|
|
{
|
|
static TodoModel *self = new TodoModel;
|
|
return self;
|
|
}
|
|
|
|
TodoModel::TodoModel(QObject *parent)
|
|
: QAbstractListModel(parent)
|
|
{
|
|
m_path = Config::instance()->dataDir() + QStringLiteral("/todo.json");
|
|
load();
|
|
}
|
|
|
|
int TodoModel::rowCount(const QModelIndex &parent) const
|
|
{
|
|
return parent.isValid() ? 0 : m_items.size();
|
|
}
|
|
|
|
QVariant TodoModel::data(const QModelIndex &index, int role) const
|
|
{
|
|
if (!index.isValid() || index.row() >= m_items.size()) {
|
|
return QVariant();
|
|
}
|
|
const TodoItem &item = m_items.at(index.row());
|
|
switch (role) {
|
|
case Qt::DisplayRole:
|
|
case TextRole:
|
|
return item.text;
|
|
case DoneRole:
|
|
return item.done;
|
|
case IdRole:
|
|
return item.id;
|
|
default:
|
|
return QVariant();
|
|
}
|
|
}
|
|
|
|
bool TodoModel::removeRows(int row, int count, const QModelIndex &parent)
|
|
{
|
|
if (parent.isValid() || row < 0 || count <= 0 || row + count > m_items.size()) {
|
|
return false;
|
|
}
|
|
beginRemoveRows(QModelIndex(), row, row + count - 1);
|
|
for (int i = 0; i < count; ++i) {
|
|
m_items.removeAt(row);
|
|
}
|
|
endRemoveRows();
|
|
save();
|
|
Q_EMIT countChanged();
|
|
return true;
|
|
}
|
|
|
|
Qt::ItemFlags TodoModel::flags(const QModelIndex &index) const
|
|
{
|
|
return index.isValid() ? (Qt::ItemIsEnabled | Qt::ItemIsSelectable) : Qt::NoItemFlags;
|
|
}
|
|
|
|
void TodoModel::add(const QString &text)
|
|
{
|
|
const QString trimmed = text.trimmed();
|
|
if (trimmed.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
TodoItem item;
|
|
item.id = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
|
item.text = trimmed;
|
|
item.createdAt = QDateTime::currentMSecsSinceEpoch();
|
|
|
|
beginInsertRows(QModelIndex(), 0, 0);
|
|
m_items.prepend(item);
|
|
endInsertRows();
|
|
save();
|
|
Q_EMIT countChanged();
|
|
}
|
|
|
|
void TodoModel::setDone(int row, bool done)
|
|
{
|
|
if (row < 0 || row >= m_items.size() || m_items[row].done == done) {
|
|
return;
|
|
}
|
|
m_items[row].done = done;
|
|
const QModelIndex idx = index(row);
|
|
Q_EMIT dataChanged(idx, idx, { DoneRole });
|
|
save();
|
|
}
|
|
|
|
const TodoItem *TodoModel::itemAt(int row) const
|
|
{
|
|
return (row >= 0 && row < m_items.size()) ? &m_items.at(row) : nullptr;
|
|
}
|
|
|
|
void TodoModel::load()
|
|
{
|
|
QFile file(m_path);
|
|
if (!file.open(QIODevice::ReadOnly)) {
|
|
return;
|
|
}
|
|
const QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
|
|
if (!doc.isObject()) {
|
|
return;
|
|
}
|
|
|
|
QList<TodoItem> items;
|
|
const QJsonArray array = doc.object().value(QStringLiteral("items")).toArray();
|
|
for (const QJsonValue &value : array) {
|
|
const QJsonObject object = value.toObject();
|
|
TodoItem item;
|
|
item.id = object.value(QStringLiteral("id")).toString();
|
|
item.text = object.value(QStringLiteral("text")).toString();
|
|
item.done = object.value(QStringLiteral("done")).toBool();
|
|
item.createdAt =
|
|
static_cast<qint64>(object.value(QStringLiteral("createdAt")).toDouble());
|
|
if (!item.text.isEmpty()) {
|
|
items.append(item);
|
|
}
|
|
}
|
|
|
|
if (items.isEmpty()) {
|
|
return;
|
|
}
|
|
beginResetModel();
|
|
m_items = items;
|
|
endResetModel();
|
|
Q_EMIT countChanged();
|
|
}
|
|
|
|
void TodoModel::save() const
|
|
{
|
|
QJsonArray array;
|
|
for (const TodoItem &item : m_items) {
|
|
QJsonObject object;
|
|
object.insert(QStringLiteral("id"), item.id);
|
|
object.insert(QStringLiteral("text"), item.text);
|
|
object.insert(QStringLiteral("done"), item.done);
|
|
object.insert(QStringLiteral("createdAt"), static_cast<double>(item.createdAt));
|
|
array.append(object);
|
|
}
|
|
|
|
QJsonObject root;
|
|
root.insert(QStringLiteral("version"), 1);
|
|
root.insert(QStringLiteral("items"), array);
|
|
|
|
QSaveFile file(m_path);
|
|
if (!file.open(QIODevice::WriteOnly)) {
|
|
return;
|
|
}
|
|
file.write(QJsonDocument(root).toJson(QJsonDocument::Indented));
|
|
file.commit();
|
|
}
|