feat: complete settings view + signatures + signatures DAO + icons
- SettingsView: complete UI with tabs for Accounts, Categories, Rules, Templates, Signatures, General, Appearance - Categories: QTreeWidget with hierarchical view, color picker, parent categories - Rules: QListWidget with RuleEditorDialog integration, toggle enabled - Templates: QListWidget with TemplateEditorDialog integration - Signatures: QListWidget with SignatureEditorDialog integration - SignatureDao: full CRUD with default signature support - common_structs.h: QColor for Category, all structs centralized - icons.qrc: updated with Linear/Outline/Bold icons from resources - Category: QColor instead of QString for color - EmailTreeModel/DateGroupProxyModel: email grouping by date - MailListView: QTreeView with date grouping combo - ReaderView: subject frame with shadow, avatar in header, body inside header - MainWindow: frameless with rounded corners, 1px border, edge resize, no status bar - icons.qrc: complete icon set from resources/icons/SVG
This commit is contained in:
@@ -77,6 +77,7 @@ set(SRC_FILES
|
||||
src/db/dao/categorydao.cpp
|
||||
src/db/dao/ruledao.cpp
|
||||
src/db/dao/templatedao.cpp
|
||||
src/db/dao/signaturedao.cpp
|
||||
src/db/dbchangeprocessor.cpp
|
||||
src/core/eventbus.cpp
|
||||
src/utils/notificationmanager.cpp
|
||||
|
||||
@@ -1,21 +1,55 @@
|
||||
<RCC>
|
||||
<qresource prefix="/icons">
|
||||
<!-- App icons -->
|
||||
<file alias="app.svg">resources/icons/app.svg</file>
|
||||
<file alias="sync.svg">resources/icons/sync.svg</file>
|
||||
<file alias="settings.svg">resources/icons/settings.svg</file>
|
||||
<file alias="minimize.svg">resources/icons/minimize.svg</file>
|
||||
<file alias="maximize.svg">resources/icons/maximize.svg</file>
|
||||
<file alias="restore.svg">resources/icons/restore.svg</file>
|
||||
<file alias="close.svg">resources/icons/close.svg</file>
|
||||
<file>resources/icons/SVG/Outline/Essentional, UI/Trash Bin Minimalistic.svg</file>
|
||||
<file>resources/icons/SVG/Outline/Messages, Conversation/Paperclip.svg</file>
|
||||
<file>resources/icons/SVG/Bold Duotone/Messages, Conversation/Plain.svg</file>
|
||||
<file>resources/icons/SVG/Outline/Files/File Text.svg</file>
|
||||
<file>resources/icons/SVG/Bold/Text Formatting/Text Bold.svg</file>
|
||||
<file>resources/icons/SVG/Outline/Text Formatting/Text Italic.svg</file>
|
||||
<file>resources/icons/SVG/Outline/Text Formatting/Text Underline.svg</file>
|
||||
<file>resources/icons/SVG/Linear/Arrows Action/Square Top Down.svg</file>
|
||||
<file>resources/icons/SVG/Linear/Arrows Action/Square Top Up.svg</file>
|
||||
<file>resources/icons/SVG/Linear/Essentional, UI/Close Square.svg</file>
|
||||
|
||||
<!-- Window controls -->
|
||||
<file alias="minimize.svg">resources/icons/SVG/Linear/Arrows Action/Minimize Square Minimalistic.svg</file>
|
||||
<file alias="maximize.svg">resources/icons/SVG/Linear/Arrows Action/Maximize Square Minimalistic.svg</file>
|
||||
<file alias="restore.svg">resources/icons/SVG/Linear/Arrows Action/Maximize Square 2.svg</file>
|
||||
<file alias="close.svg">resources/icons/SVG/Linear/Essentional, UI/Close Square.svg</file>
|
||||
|
||||
<!-- Mail actions -->
|
||||
<file alias="attachment.svg">resources/icons/SVG/Outline/Messages, Conversation/Paperclip.svg</file>
|
||||
<file alias="send.svg">resources/icons/SVG/Bold Duotone/Messages, Conversation/Plain.svg</file>
|
||||
<file alias="reply.svg">resources/icons/SVG/Linear/Arrows Action/Reply.svg</file>
|
||||
<file alias="forward.svg">resources/icons/SVG/Linear/Arrows Action/Forward.svg</file>
|
||||
<file alias="trash.svg">resources/icons/SVG/Linear/Essentional, UI/Trash Bin Minimalistic.svg</file>
|
||||
<file alias="filter.svg">resources/icons/SVG/Linear/Design, Tools/Filters.svg</file>
|
||||
<file alias="play.svg">resources/icons/SVG/Linear/Time/Stopwatch Play.svg</file>
|
||||
<file alias="file-text.svg">resources/icons/SVG/Linear/Files/File Text.svg</file>
|
||||
|
||||
<!-- UI elements -->
|
||||
<file alias="dropdown.png">resources/icons/SVG/Linear/Arrows Action/Square Top Down.svg</file>
|
||||
<file alias="detach.svg">resources/icons/SVG/Linear/Arrows Action/Square Top Down.svg</file>
|
||||
<file alias="import.svg">resources/icons/SVG/Linear/Arrows Action/Import.svg</file>
|
||||
<file alias="export.svg">resources/icons/SVG/Linear/Arrows Action/Export.svg</file>
|
||||
|
||||
<!-- Bold text formatting -->
|
||||
<file alias="bold.svg">resources/icons/SVG/Bold/Text Formatting/Text Bold.svg</file>
|
||||
<file alias="italic.svg">resources/icons/SVG/Outline/Text Formatting/Text Italic.svg</file>
|
||||
<file alias="underline.svg">resources/icons/SVG/Outline/Text Formatting/Text Underline.svg</file>
|
||||
|
||||
<!-- User/Contact -->
|
||||
<file alias="user.svg">resources/icons/SVG/Linear/Users/User.svg</file>
|
||||
|
||||
<!-- Calendar -->
|
||||
<file alias="calendar.svg">resources/icons/SVG/Linear/Time/Calendar.svg</file>
|
||||
<file alias="calendar-add.svg">resources/icons/SVG/Linear/Time/Calendar Add.svg</file>
|
||||
|
||||
<!-- Sync/Refresh - using existing icons -->
|
||||
<file alias="refresh.svg">resources/icons/SVG/Linear/Arrows Action/Square Top Down.svg</file>
|
||||
<file alias="sync_all.svg">resources/icons/SVG/Linear/Arrows Action/Square Top Up.svg</file>
|
||||
|
||||
<!-- Menu dots -->
|
||||
<file alias="dots.svg">resources/icons/SVG/Linear/Essentional, UI/Menu Dots.svg</file>
|
||||
|
||||
<!-- Design tools -->
|
||||
<file alias="palette.svg">resources/icons/SVG/Linear/Design, Tools/Palette.svg</file>
|
||||
|
||||
<!-- Calendar -->
|
||||
<file alias="calendar-add.svg">resources/icons/SVG/Linear/Time/Calendar Add.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
</RCC>
|
||||
+107
-112
@@ -10,19 +10,20 @@ bool CategoryDao::insert(Category& cat)
|
||||
QSqlQuery query(db);
|
||||
query.prepare(
|
||||
"INSERT INTO Category (accountId, name, color, parentCategoryId, sortOrder, createdAt) "
|
||||
"VALUES (:accountId, :name, :color, :parentCategoryId, :sortOrder, :createdAt)"
|
||||
);
|
||||
"VALUES (:accountId, :name, :color, :parentCategoryId, :sortOrder, :createdAt)");
|
||||
|
||||
query.bindValue(":accountId", cat.accountId >= 0 ? cat.accountId : QVariant());
|
||||
query.bindValue(":name", cat.name);
|
||||
query.bindValue(":color", cat.color.name());
|
||||
query.bindValue(":color", cat.color.name(QColor::HexArgb));
|
||||
query.bindValue(":parentCategoryId", cat.parentCategoryId >= 0 ? cat.parentCategoryId : QVariant());
|
||||
query.bindValue(":sortOrder", cat.sortOrder);
|
||||
query.bindValue(":createdAt", QDateTime::currentDateTime());
|
||||
|
||||
query.bindValue(":createdAt", QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Failed to insert category:" << query.lastError().text();
|
||||
qDebug() << "[CategoryDao] Insert failed:" << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
|
||||
cat.id = query.lastInsertId().toLongLong();
|
||||
return true;
|
||||
}
|
||||
@@ -32,26 +33,23 @@ bool CategoryDao::update(const Category& cat)
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare(
|
||||
"UPDATE Category SET "
|
||||
"accountId = :accountId, "
|
||||
"name = :name, "
|
||||
"color = :color, "
|
||||
"parentCategoryId = :parentCategoryId, "
|
||||
"sortOrder = :sortOrder "
|
||||
"WHERE id = :id"
|
||||
);
|
||||
"UPDATE Category SET accountId = :accountId, name = :name, color = :color, "
|
||||
"parentCategoryId = :parentCategoryId, sortOrder = :sortOrder "
|
||||
"WHERE id = :id");
|
||||
|
||||
query.bindValue(":id", cat.id);
|
||||
query.bindValue(":accountId", cat.accountId >= 0 ? cat.accountId : QVariant());
|
||||
query.bindValue(":name", cat.name);
|
||||
query.bindValue(":color", cat.color.name()); // Use default hex format
|
||||
query.bindValue(":color", cat.color.name(QColor::HexArgb));
|
||||
query.bindValue(":parentCategoryId", cat.parentCategoryId >= 0 ? cat.parentCategoryId : QVariant());
|
||||
query.bindValue(":sortOrder", cat.sortOrder);
|
||||
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Failed to update category:" << query.lastError().text();
|
||||
qDebug() << "[CategoryDao] Update failed:" << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
return query.numRowsAffected() > 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CategoryDao::remove(qint64 id)
|
||||
@@ -60,12 +58,13 @@ bool CategoryDao::remove(qint64 id)
|
||||
QSqlQuery query(db);
|
||||
query.prepare("DELETE FROM Category WHERE id = :id");
|
||||
query.bindValue(":id", id);
|
||||
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Failed to delete category:" << query.lastError().text();
|
||||
qDebug() << "[CategoryDao] Remove failed:" << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
return query.numRowsAffected() > 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<Category> CategoryDao::findById(qint64 id)
|
||||
@@ -74,19 +73,20 @@ std::optional<Category> CategoryDao::findById(qint64 id)
|
||||
QSqlQuery query(db);
|
||||
query.prepare("SELECT id, accountId, name, color, parentCategoryId, sortOrder, createdAt FROM Category WHERE id = :id");
|
||||
query.bindValue(":id", id);
|
||||
|
||||
|
||||
if (!query.exec() || !query.next()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
|
||||
Category cat;
|
||||
cat.id = query.value(0).toLongLong();
|
||||
cat.accountId = query.value(1).toLongLong();
|
||||
cat.name = query.value(2).toString();
|
||||
cat.color = QColor(query.value(3).toString());
|
||||
cat.parentCategoryId = query.value(4).toLongLong();
|
||||
cat.sortOrder = query.value(5).toInt();
|
||||
cat.createdAt = query.value(6).toDateTime();
|
||||
cat.id = query.value("id").toLongLong();
|
||||
cat.accountId = query.value("accountId").isNull() ? -1 : query.value("accountId").toLongLong();
|
||||
cat.name = query.value("name").toString();
|
||||
cat.color = QColor(query.value("color").toString());
|
||||
cat.parentCategoryId = query.value("parentCategoryId").isNull() ? -1 : query.value("parentCategoryId").toLongLong();
|
||||
cat.sortOrder = query.value("sortOrder").toInt();
|
||||
cat.createdAt = QDateTime::fromString(query.value("createdAt").toString(), Qt::ISODate);
|
||||
|
||||
return cat;
|
||||
}
|
||||
|
||||
@@ -94,24 +94,20 @@ QVector<Category> CategoryDao::findByAccount(qint64 accountId)
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
if (accountId >= 0) {
|
||||
query.prepare("SELECT id, accountId, name, color, parentCategoryId, sortOrder, createdAt FROM Category WHERE accountId = :accountId OR accountId IS NULL ORDER BY sortOrder, name");
|
||||
query.bindValue(":accountId", accountId);
|
||||
} else {
|
||||
query.prepare("SELECT id, accountId, name, color, parentCategoryId, sortOrder, createdAt FROM Category WHERE accountId IS NULL ORDER BY sortOrder, name");
|
||||
}
|
||||
|
||||
query.prepare("SELECT id, accountId, name, color, parentCategoryId, sortOrder, createdAt FROM Category WHERE accountId = :accountId ORDER BY sortOrder, name");
|
||||
query.bindValue(":accountId", accountId);
|
||||
|
||||
QVector<Category> cats;
|
||||
if (query.exec()) {
|
||||
while (query.next()) {
|
||||
Category cat;
|
||||
cat.id = query.value(0).toLongLong();
|
||||
cat.accountId = query.value(1).toLongLong();
|
||||
cat.name = query.value(2).toString();
|
||||
cat.color = QColor(query.value(3).toString());
|
||||
cat.parentCategoryId = query.value(4).toLongLong();
|
||||
cat.sortOrder = query.value(5).toInt();
|
||||
cat.createdAt = query.value(6).toDateTime();
|
||||
cat.id = query.value("id").toLongLong();
|
||||
cat.accountId = query.value("accountId").isNull() ? -1 : query.value("accountId").toLongLong();
|
||||
cat.name = query.value("name").toString();
|
||||
cat.color = QColor(query.value("color").toString());
|
||||
cat.parentCategoryId = query.value("parentCategoryId").isNull() ? -1 : query.value("parentCategoryId").toLongLong();
|
||||
cat.sortOrder = query.value("sortOrder").toInt();
|
||||
cat.createdAt = QDateTime::fromString(query.value("createdAt").toString(), Qt::ISODate);
|
||||
cats.append(cat);
|
||||
}
|
||||
}
|
||||
@@ -120,28 +116,42 @@ QVector<Category> CategoryDao::findByAccount(qint64 accountId)
|
||||
|
||||
QVector<Category> CategoryDao::findGlobal()
|
||||
{
|
||||
return findByAccount(-1);
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.exec("SELECT id, accountId, name, color, parentCategoryId, sortOrder, createdAt FROM Category WHERE accountId IS NULL ORDER BY sortOrder, name");
|
||||
|
||||
QVector<Category> cats;
|
||||
while (query.next()) {
|
||||
Category cat;
|
||||
cat.id = query.value("id").toLongLong();
|
||||
cat.accountId = -1;
|
||||
cat.name = query.value("name").toString();
|
||||
cat.color = QColor(query.value("color").toString());
|
||||
cat.parentCategoryId = query.value("parentCategoryId").isNull() ? -1 : query.value("parentCategoryId").toLongLong();
|
||||
cat.sortOrder = query.value("sortOrder").toInt();
|
||||
cat.createdAt = QDateTime::fromString(query.value("createdAt").toString(), Qt::ISODate);
|
||||
cats.append(cat);
|
||||
}
|
||||
return cats;
|
||||
}
|
||||
|
||||
QVector<Category> CategoryDao::findAll()
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare("SELECT id, accountId, name, color, parentCategoryId, sortOrder, createdAt FROM Category ORDER BY accountId, sortOrder, name");
|
||||
|
||||
query.exec("SELECT id, accountId, name, color, parentCategoryId, sortOrder, createdAt FROM Category ORDER BY sortOrder, name");
|
||||
|
||||
QVector<Category> cats;
|
||||
if (query.exec()) {
|
||||
while (query.next()) {
|
||||
Category cat;
|
||||
cat.id = query.value(0).toLongLong();
|
||||
cat.accountId = query.value(1).toLongLong();
|
||||
cat.name = query.value(2).toString();
|
||||
cat.color = QColor(query.value(3).toString());
|
||||
cat.parentCategoryId = query.value(4).toLongLong();
|
||||
cat.sortOrder = query.value(5).toInt();
|
||||
cat.createdAt = query.value(6).toDateTime();
|
||||
cats.append(cat);
|
||||
}
|
||||
while (query.next()) {
|
||||
Category cat;
|
||||
cat.id = query.value("id").toLongLong();
|
||||
cat.accountId = query.value("accountId").isNull() ? -1 : query.value("accountId").toLongLong();
|
||||
cat.name = query.value("name").toString();
|
||||
cat.color = QColor(query.value("color").toString());
|
||||
cat.parentCategoryId = query.value("parentCategoryId").isNull() ? -1 : query.value("parentCategoryId").toLongLong();
|
||||
cat.sortOrder = query.value("sortOrder").toInt();
|
||||
cat.createdAt = QDateTime::fromString(query.value("createdAt").toString(), Qt::ISODate);
|
||||
cats.append(cat);
|
||||
}
|
||||
return cats;
|
||||
}
|
||||
@@ -152,103 +162,88 @@ QVector<Category> CategoryDao::findChildren(qint64 parentId)
|
||||
QSqlQuery query(db);
|
||||
query.prepare("SELECT id, accountId, name, color, parentCategoryId, sortOrder, createdAt FROM Category WHERE parentCategoryId = :parentId ORDER BY sortOrder, name");
|
||||
query.bindValue(":parentId", parentId);
|
||||
|
||||
|
||||
QVector<Category> cats;
|
||||
if (query.exec()) {
|
||||
while (query.next()) {
|
||||
Category cat;
|
||||
cat.id = query.value(0).toLongLong();
|
||||
cat.accountId = query.value(1).toLongLong();
|
||||
cat.name = query.value(2).toString();
|
||||
cat.color = QColor(query.value(3).toString());
|
||||
cat.parentCategoryId = query.value(4).toLongLong();
|
||||
cat.sortOrder = query.value(5).toInt();
|
||||
cat.createdAt = query.value(6).toDateTime();
|
||||
cat.id = query.value("id").toLongLong();
|
||||
cat.accountId = query.value("accountId").isNull() ? -1 : query.value("accountId").toLongLong();
|
||||
cat.name = query.value("name").toString();
|
||||
cat.color = QColor(query.value("color").toString());
|
||||
cat.parentCategoryId = query.value("parentCategoryId").isNull() ? -1 : query.value("parentCategoryId").toLongLong();
|
||||
cat.sortOrder = query.value("sortOrder").toInt();
|
||||
cat.createdAt = QDateTime::fromString(query.value("createdAt").toString(), Qt::ISODate);
|
||||
cats.append(cat);
|
||||
}
|
||||
}
|
||||
return cats;
|
||||
}
|
||||
|
||||
bool CategoryDao::assignToMail(qint64 mailCopyId, qint64 categoryId)
|
||||
bool CategoryDao::assignToMail(qint64 mailId, qint64 categoryId)
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare(
|
||||
"INSERT OR IGNORE INTO MailCategory (mailCopyId, categoryId, assignedAt) "
|
||||
"VALUES (:mailCopyId, :categoryId, :assignedAt)"
|
||||
);
|
||||
query.bindValue(":mailCopyId", mailCopyId);
|
||||
query.prepare("INSERT OR IGNORE INTO MailCategory (mailCopyId, categoryId, assignedAt) VALUES (:mailId, :categoryId, :assignedAt)");
|
||||
query.bindValue(":mailId", mailId);
|
||||
query.bindValue(":categoryId", categoryId);
|
||||
query.bindValue(":assignedAt", QDateTime::currentDateTime());
|
||||
|
||||
query.bindValue(":assignedAt", QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Failed to assign category to mail:" << query.lastError().text();
|
||||
qDebug() << "[CategoryDao] assignToMail failed:" << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CategoryDao::removeFromMail(qint64 mailCopyId, qint64 categoryId)
|
||||
bool CategoryDao::removeFromMail(qint64 mailId, qint64 categoryId)
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare("DELETE FROM MailCategory WHERE mailCopyId = :mailCopyId AND categoryId = :categoryId");
|
||||
query.bindValue(":mailCopyId", mailCopyId);
|
||||
query.prepare("DELETE FROM MailCategory WHERE mailCopyId = :mailId AND categoryId = :categoryId");
|
||||
query.bindValue(":mailId", mailId);
|
||||
query.bindValue(":categoryId", categoryId);
|
||||
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Failed to remove category from mail:" << query.lastError().text();
|
||||
qDebug() << "[CategoryDao] removeFromMail failed:" << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
return query.numRowsAffected() > 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
QVector<qint64> CategoryDao::categoriesForMail(qint64 mailCopyId)
|
||||
QVector<qint64> CategoryDao::categoriesForMail(qint64 mailId)
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare("SELECT categoryId FROM MailCategory WHERE mailCopyId = :mailCopyId");
|
||||
query.bindValue(":mailCopyId", mailCopyId);
|
||||
|
||||
QVector<qint64> ids;
|
||||
query.prepare("SELECT categoryId FROM MailCategory WHERE mailCopyId = :mailId");
|
||||
query.bindValue(":mailId", mailId);
|
||||
|
||||
QVector<qint64> catIds;
|
||||
if (query.exec()) {
|
||||
while (query.next()) {
|
||||
ids.append(query.value(0).toLongLong());
|
||||
catIds.append(query.value("categoryId").toLongLong());
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
return catIds;
|
||||
}
|
||||
|
||||
QVector<Category> CategoryDao::fullCategoriesForMail(qint64 mailCopyId)
|
||||
{
|
||||
QVector<qint64> ids = categoriesForMail(mailCopyId);
|
||||
QVector<Category> cats;
|
||||
for (qint64 id : ids) {
|
||||
if (auto cat = findById(id)) {
|
||||
cats.append(*cat);
|
||||
}
|
||||
}
|
||||
return cats;
|
||||
}
|
||||
|
||||
bool CategoryDao::reorder(const QVector<qint64>& idsInOrder)
|
||||
bool CategoryDao::reorder(const QVector<qint64>& ids)
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
db.transaction();
|
||||
|
||||
QSqlQuery query(db);
|
||||
query.prepare("UPDATE Category SET sortOrder = :order WHERE id = :id");
|
||||
|
||||
for (int i = 0; i < idsInOrder.size(); ++i) {
|
||||
query.bindValue(":id", idsInOrder[i]);
|
||||
query.bindValue(":order", i);
|
||||
|
||||
for (int i = 0; i < ids.size(); ++i) {
|
||||
QSqlQuery query(db);
|
||||
query.prepare("UPDATE Category SET sortOrder = :sortOrder WHERE id = :id");
|
||||
query.bindValue(":sortOrder", i);
|
||||
query.bindValue(":id", ids[i]);
|
||||
if (!query.exec()) {
|
||||
db.rollback();
|
||||
qWarning() << "Failed to reorder category:" << query.lastError().text();
|
||||
qDebug() << "[CategoryDao] reorder failed:" << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return db.commit();
|
||||
|
||||
db.commit();
|
||||
return true;
|
||||
}
|
||||
@@ -3,22 +3,7 @@
|
||||
#include "../databasemanager.h"
|
||||
#include <QVector>
|
||||
#include <optional>
|
||||
#include <QColor>
|
||||
#include <QDateTime>
|
||||
|
||||
struct Category
|
||||
{
|
||||
qint64 id{-1};
|
||||
qint64 accountId{-1}; // -1 = global
|
||||
QString name;
|
||||
QColor color{QColor("#1976D2")};
|
||||
qint64 parentCategoryId{-1};
|
||||
int sortOrder{0};
|
||||
QDateTime createdAt;
|
||||
|
||||
bool isGlobal() const { return accountId < 0; }
|
||||
bool isValid() const { return id >= 0 && !name.isEmpty(); }
|
||||
};
|
||||
#include "common_structs.h"
|
||||
|
||||
class CategoryDao
|
||||
{
|
||||
@@ -27,13 +12,12 @@ public:
|
||||
static bool update(const Category& cat);
|
||||
static bool remove(qint64 id);
|
||||
static std::optional<Category> findById(qint64 id);
|
||||
static QVector<Category> findByAccount(qint64 accountId); // accountId = -1 for global
|
||||
static QVector<Category> findByAccount(qint64 accountId);
|
||||
static QVector<Category> findGlobal();
|
||||
static QVector<Category> findAll();
|
||||
static QVector<Category> findChildren(qint64 parentId);
|
||||
static bool assignToMail(qint64 mailCopyId, qint64 categoryId);
|
||||
static bool removeFromMail(qint64 mailCopyId, qint64 categoryId);
|
||||
static QVector<qint64> categoriesForMail(qint64 mailCopyId);
|
||||
static QVector<Category> fullCategoriesForMail(qint64 mailCopyId);
|
||||
static bool reorder(const QVector<qint64>& idsInOrder);
|
||||
static bool assignToMail(qint64 mailId, qint64 categoryId);
|
||||
static bool removeFromMail(qint64 mailId, qint64 categoryId);
|
||||
static QVector<qint64> categoriesForMail(qint64 mailId); // Returns category IDs
|
||||
static bool reorder(const QVector<qint64>& ids);
|
||||
};
|
||||
@@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QVector>
|
||||
#include <optional>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QColor>
|
||||
|
||||
struct TemplateVariable
|
||||
{
|
||||
QString name;
|
||||
QString label;
|
||||
QString defaultValue;
|
||||
QString type; // "text", "email", "date", "select", "textarea"
|
||||
QStringList options; // for select type
|
||||
};
|
||||
|
||||
struct Template
|
||||
{
|
||||
qint64 id{-1};
|
||||
qint64 accountId{-1}; // -1 = global
|
||||
QString name;
|
||||
QString subject;
|
||||
QString bodyHtml;
|
||||
QString bodyText;
|
||||
QVector<TemplateVariable> variables;
|
||||
bool isDefault{false};
|
||||
QDateTime createdAt;
|
||||
QDateTime updatedAt;
|
||||
|
||||
bool isGlobal() const { return accountId < 0; }
|
||||
bool isValid() const { return id >= 0 && !name.isEmpty(); }
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static Template fromJson(const QJsonObject& obj);
|
||||
};
|
||||
|
||||
struct Signature
|
||||
{
|
||||
qint64 id{-1};
|
||||
qint64 accountId{-1}; // -1 = global
|
||||
QString name;
|
||||
QString html;
|
||||
bool isDefault{false};
|
||||
QDateTime createdAt;
|
||||
QDateTime updatedAt;
|
||||
|
||||
bool isGlobal() const { return accountId < 0; }
|
||||
bool isValid() const { return id >= 0 && !name.isEmpty(); }
|
||||
};
|
||||
|
||||
struct Category
|
||||
{
|
||||
qint64 id{-1};
|
||||
qint64 accountId{-1}; // -1 = global
|
||||
QString name;
|
||||
QColor color; // color
|
||||
qint64 parentCategoryId{-1};
|
||||
int sortOrder{0};
|
||||
QDateTime createdAt;
|
||||
|
||||
bool isGlobal() const { return accountId < 0; }
|
||||
bool isValid() const { return id >= 0 && !name.isEmpty(); }
|
||||
};
|
||||
|
||||
struct RuleCondition
|
||||
{
|
||||
enum Field { From, To, Cc, Subject, Body, HasAttachment, Size, Date, Flagged, Read };
|
||||
enum Operator { Contains, NotContains, Equals, NotEquals, StartsWith, EndsWith, Regex, GreaterThan, LessThan, Before, After };
|
||||
|
||||
Field field{From};
|
||||
Operator op{Contains};
|
||||
QString value;
|
||||
bool caseSensitive{false};
|
||||
|
||||
bool match(const QString& text) const;
|
||||
QJsonObject toJson() const;
|
||||
static RuleCondition fromJson(const QJsonObject& obj);
|
||||
};
|
||||
|
||||
struct RuleAction
|
||||
{
|
||||
enum Type {
|
||||
MoveToFolder, // value = folderId
|
||||
MarkAsRead, // value = "true"/"false"
|
||||
MarkAsFlagged, // value = "true"/"false"
|
||||
Delete, // no value
|
||||
AssignCategory, // value = categoryId
|
||||
RemoveCategory, // value = categoryId
|
||||
ForwardTo, // value = email address
|
||||
ReplyWithTemplate, // value = templateId
|
||||
SetPriority, // value = "high"/"normal"/"low"
|
||||
StopProcessing // no value (stop evaluating more rules)
|
||||
};
|
||||
|
||||
Type type{MoveToFolder};
|
||||
QString value;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static RuleAction fromJson(const QJsonObject& obj);
|
||||
};
|
||||
|
||||
struct Rule
|
||||
{
|
||||
qint64 id{-1};
|
||||
qint64 accountId{-1}; // -1 = global
|
||||
QString name;
|
||||
QString description;
|
||||
bool enabled{true};
|
||||
int priority{100}; // lower = higher priority
|
||||
bool matchAll{true}; // true = AND, false = OR
|
||||
QVector<RuleCondition> conditions;
|
||||
QVector<RuleAction> actions;
|
||||
QDateTime createdAt;
|
||||
QDateTime updatedAt;
|
||||
QDateTime lastRun;
|
||||
qint64 runCount{0};
|
||||
|
||||
bool isGlobal() const { return accountId < 0; }
|
||||
bool isValid() const { return id >= 0 && !name.isEmpty(); }
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static Rule fromJson(const QJsonObject& obj);
|
||||
};
|
||||
+3
-63
@@ -3,68 +3,7 @@
|
||||
#include "../databasemanager.h"
|
||||
#include <QVector>
|
||||
#include <optional>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
|
||||
struct RuleCondition
|
||||
{
|
||||
enum Field { From, To, Cc, Subject, Body, HasAttachment, Size, Date, Flagged, Read };
|
||||
enum Operator { Contains, NotContains, Equals, NotEquals, StartsWith, EndsWith, Regex, GreaterThan, LessThan, Before, After };
|
||||
|
||||
Field field{From};
|
||||
Operator op{Contains};
|
||||
QString value;
|
||||
bool caseSensitive{false};
|
||||
|
||||
bool match(const QString& text) const;
|
||||
QJsonObject toJson() const;
|
||||
static RuleCondition fromJson(const QJsonObject& obj);
|
||||
};
|
||||
|
||||
struct RuleAction
|
||||
{
|
||||
enum Type {
|
||||
MoveToFolder, // value = folderId
|
||||
MarkAsRead, // value = "true"/"false"
|
||||
MarkAsFlagged, // value = "true"/"false"
|
||||
Delete, // no value
|
||||
AssignCategory, // value = categoryId
|
||||
RemoveCategory, // value = categoryId
|
||||
ForwardTo, // value = email address
|
||||
ReplyWithTemplate, // value = templateId
|
||||
SetPriority, // value = "high"/"normal"/"low"
|
||||
StopProcessing // no value (stop evaluating more rules)
|
||||
};
|
||||
|
||||
Type type{MoveToFolder};
|
||||
QString value;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static RuleAction fromJson(const QJsonObject& obj);
|
||||
};
|
||||
|
||||
struct Rule
|
||||
{
|
||||
qint64 id{-1};
|
||||
qint64 accountId{-1}; // -1 = global
|
||||
QString name;
|
||||
QString description;
|
||||
bool enabled{true};
|
||||
int priority{100}; // lower = higher priority
|
||||
bool matchAll{true}; // true = AND, false = OR
|
||||
QVector<RuleCondition> conditions;
|
||||
QVector<RuleAction> actions;
|
||||
QDateTime createdAt;
|
||||
QDateTime updatedAt;
|
||||
QDateTime lastRun;
|
||||
qint64 runCount{0};
|
||||
|
||||
bool isGlobal() const { return accountId < 0; }
|
||||
bool isValid() const { return id >= 0 && !name.isEmpty(); }
|
||||
QJsonObject toJson() const;
|
||||
static Rule fromJson(const QJsonObject& obj);
|
||||
};
|
||||
#include "common_structs.h"
|
||||
|
||||
class RuleDao
|
||||
{
|
||||
@@ -75,7 +14,8 @@ public:
|
||||
static std::optional<Rule> findById(qint64 id);
|
||||
static QVector<Rule> findByAccount(qint64 accountId); // accountId = -1 for global
|
||||
static QVector<Rule> findGlobal();
|
||||
static QVector<Rule> findAllEnabled();
|
||||
static QVector<Rule> findAll();
|
||||
static QVector<Rule> findAllEnabled(); // only enabled rules
|
||||
static bool setEnabled(qint64 id, bool enabled);
|
||||
static bool updateLastRun(qint64 id, const QDateTime& when, qint64 runCount);
|
||||
};
|
||||
@@ -0,0 +1,220 @@
|
||||
#include "signaturedao.h"
|
||||
#include <QSqlQuery>
|
||||
#include <QSqlError>
|
||||
#include <QDebug>
|
||||
#include <QDateTime>
|
||||
|
||||
bool SignatureDao::insert(Signature& sig)
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare(
|
||||
"INSERT INTO Signature (accountId, name, html, isDefault, createdAt, updatedAt) "
|
||||
"VALUES (:accountId, :name, :html, :isDefault, :createdAt, :updatedAt)");
|
||||
|
||||
query.bindValue(":accountId", sig.accountId >= 0 ? sig.accountId : QVariant());
|
||||
query.bindValue(":name", sig.name);
|
||||
query.bindValue(":html", sig.html);
|
||||
query.bindValue(":isDefault", sig.isDefault ? 1 : 0);
|
||||
query.bindValue(":createdAt", QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
|
||||
query.bindValue(":updatedAt", QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
|
||||
|
||||
if (!query.exec()) {
|
||||
qDebug() << "[SignatureDao] Insert failed:" << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
|
||||
sig.id = query.lastInsertId().toLongLong();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SignatureDao::update(const Signature& sig)
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare(
|
||||
"UPDATE Signature SET accountId = :accountId, name = :name, html = :html, "
|
||||
"isDefault = :isDefault, updatedAt = :updatedAt "
|
||||
"WHERE id = :id");
|
||||
|
||||
query.bindValue(":id", sig.id);
|
||||
query.bindValue(":accountId", sig.accountId >= 0 ? sig.accountId : QVariant());
|
||||
query.bindValue(":name", sig.name);
|
||||
query.bindValue(":html", sig.html);
|
||||
query.bindValue(":isDefault", sig.isDefault ? 1 : 0);
|
||||
query.bindValue(":updatedAt", QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
|
||||
|
||||
if (!query.exec()) {
|
||||
qDebug() << "[SignatureDao] Update failed:" << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SignatureDao::remove(qint64 id)
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare("DELETE FROM Signature WHERE id = :id");
|
||||
query.bindValue(":id", id);
|
||||
|
||||
if (!query.exec()) {
|
||||
qDebug() << "[SignatureDao] Remove failed:" << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<Signature> SignatureDao::findById(qint64 id)
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare("SELECT id, accountId, name, html, isDefault, createdAt, updatedAt FROM Signature WHERE id = :id");
|
||||
query.bindValue(":id", id);
|
||||
|
||||
if (!query.exec() || !query.next()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Signature sig;
|
||||
sig.id = query.value("id").toLongLong();
|
||||
sig.accountId = query.value("accountId").isNull() ? -1 : query.value("accountId").toLongLong();
|
||||
sig.name = query.value("name").toString();
|
||||
sig.html = query.value("html").toString();
|
||||
sig.isDefault = query.value("isDefault").toBool();
|
||||
sig.createdAt = QDateTime::fromString(query.value("createdAt").toString(), Qt::ISODate);
|
||||
sig.updatedAt = QDateTime::fromString(query.value("updatedAt").toString(), Qt::ISODate);
|
||||
|
||||
return sig;
|
||||
}
|
||||
|
||||
QVector<Signature> SignatureDao::findByAccount(qint64 accountId)
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
if (accountId >= 0) {
|
||||
query.prepare("SELECT id, accountId, name, html, isDefault, createdAt, updatedAt FROM Signature WHERE accountId = :accountId OR accountId IS NULL ORDER BY name");
|
||||
query.bindValue(":accountId", accountId);
|
||||
} else {
|
||||
query.prepare("SELECT id, accountId, name, html, isDefault, createdAt, updatedAt FROM Signature WHERE accountId IS NULL ORDER BY name");
|
||||
}
|
||||
|
||||
QVector<Signature> sigs;
|
||||
if (query.exec()) {
|
||||
while (query.next()) {
|
||||
Signature sig;
|
||||
sig.id = query.value("id").toLongLong();
|
||||
sig.accountId = query.value("accountId").isNull() ? -1 : query.value("accountId").toLongLong();
|
||||
sig.name = query.value("name").toString();
|
||||
sig.html = query.value("html").toString();
|
||||
sig.isDefault = query.value("isDefault").toBool();
|
||||
sig.createdAt = QDateTime::fromString(query.value("createdAt").toString(), Qt::ISODate);
|
||||
sig.updatedAt = QDateTime::fromString(query.value("updatedAt").toString(), Qt::ISODate);
|
||||
sigs.append(sig);
|
||||
}
|
||||
}
|
||||
return sigs;
|
||||
}
|
||||
|
||||
QVector<Signature> SignatureDao::findGlobal()
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.exec("SELECT id, accountId, name, html, isDefault, createdAt, updatedAt FROM Signature WHERE accountId IS NULL ORDER BY name");
|
||||
|
||||
QVector<Signature> sigs;
|
||||
while (query.next()) {
|
||||
Signature sig;
|
||||
sig.id = query.value("id").toLongLong();
|
||||
sig.accountId = -1;
|
||||
sig.name = query.value("name").toString();
|
||||
sig.html = query.value("html").toString();
|
||||
sig.isDefault = query.value("isDefault").toBool();
|
||||
sig.createdAt = QDateTime::fromString(query.value("createdAt").toString(), Qt::ISODate);
|
||||
sig.updatedAt = QDateTime::fromString(query.value("updatedAt").toString(), Qt::ISODate);
|
||||
sigs.append(sig);
|
||||
}
|
||||
return sigs;
|
||||
}
|
||||
|
||||
QVector<Signature> SignatureDao::findAll()
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.exec("SELECT id, accountId, name, html, isDefault, createdAt, updatedAt FROM Signature ORDER BY name");
|
||||
|
||||
QVector<Signature> sigs;
|
||||
while (query.next()) {
|
||||
Signature sig;
|
||||
sig.id = query.value("id").toLongLong();
|
||||
sig.accountId = query.value("accountId").isNull() ? -1 : query.value("accountId").toLongLong();
|
||||
sig.name = query.value("name").toString();
|
||||
sig.html = query.value("html").toString();
|
||||
sig.isDefault = query.value("isDefault").toBool();
|
||||
sig.createdAt = QDateTime::fromString(query.value("createdAt").toString(), Qt::ISODate);
|
||||
sig.updatedAt = QDateTime::fromString(query.value("updatedAt").toString(), Qt::ISODate);
|
||||
sigs.append(sig);
|
||||
}
|
||||
return sigs;
|
||||
}
|
||||
|
||||
std::optional<Signature> SignatureDao::findDefault(qint64 accountId)
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare(
|
||||
"SELECT id, accountId, name, html, isDefault, createdAt, updatedAt "
|
||||
"FROM Signature "
|
||||
"WHERE (accountId = :accountId OR accountId IS NULL) AND isDefault = 1 "
|
||||
"ORDER BY accountId DESC LIMIT 1");
|
||||
query.bindValue(":accountId", accountId >= 0 ? accountId : QVariant());
|
||||
|
||||
if (!query.exec() || !query.next()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Signature sig;
|
||||
sig.id = query.value("id").toLongLong();
|
||||
sig.accountId = query.value("accountId").isNull() ? -1 : query.value("accountId").toLongLong();
|
||||
sig.name = query.value("name").toString();
|
||||
sig.html = query.value("html").toString();
|
||||
sig.isDefault = query.value("isDefault").toBool();
|
||||
sig.createdAt = QDateTime::fromString(query.value("createdAt").toString(), Qt::ISODate);
|
||||
sig.updatedAt = QDateTime::fromString(query.value("updatedAt").toString(), Qt::ISODate);
|
||||
|
||||
return sig;
|
||||
}
|
||||
|
||||
bool SignatureDao::setDefault(qint64 id, bool isDefault)
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
db.transaction();
|
||||
|
||||
// First unset all defaults for the same account
|
||||
QSqlQuery query(db);
|
||||
query.prepare(
|
||||
"UPDATE Signature SET isDefault = 0 WHERE accountId = "
|
||||
"(SELECT accountId FROM Signature WHERE id = :id)");
|
||||
query.bindValue(":id", id);
|
||||
if (!query.exec()) {
|
||||
db.rollback();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the new default
|
||||
query.prepare("UPDATE Signature SET isDefault = :isDefault, updatedAt = :updatedAt WHERE id = :id");
|
||||
query.bindValue(":id", id);
|
||||
query.bindValue(":isDefault", isDefault ? 1 : 0);
|
||||
query.bindValue(":updatedAt", QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
|
||||
|
||||
if (!query.exec()) {
|
||||
db.rollback();
|
||||
qDebug() << "[SignatureDao] setDefault failed:" << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
|
||||
db.commit();
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "../databasemanager.h"
|
||||
#include <QVector>
|
||||
#include <optional>
|
||||
#include "common_structs.h"
|
||||
|
||||
class SignatureDao
|
||||
{
|
||||
public:
|
||||
static bool insert(Signature& sig);
|
||||
static bool update(const Signature& sig);
|
||||
static bool remove(qint64 id);
|
||||
static std::optional<Signature> findById(qint64 id);
|
||||
static QVector<Signature> findByAccount(qint64 accountId);
|
||||
static QVector<Signature> findGlobal();
|
||||
static QVector<Signature> findAll();
|
||||
static std::optional<Signature> findDefault(qint64 accountId);
|
||||
static bool setDefault(qint64 id, bool isDefault);
|
||||
};
|
||||
@@ -6,35 +6,7 @@
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
|
||||
struct TemplateVariable
|
||||
{
|
||||
QString name;
|
||||
QString label;
|
||||
QString defaultValue;
|
||||
QString type; // "text", "email", "date", "select", "textarea"
|
||||
QStringList options; // for select type
|
||||
};
|
||||
|
||||
struct Template
|
||||
{
|
||||
qint64 id{-1};
|
||||
qint64 accountId{-1}; // -1 = global
|
||||
QString name;
|
||||
QString subject;
|
||||
QString bodyHtml;
|
||||
QString bodyText;
|
||||
QVector<TemplateVariable> variables;
|
||||
bool isDefault{false};
|
||||
QDateTime createdAt;
|
||||
QDateTime updatedAt;
|
||||
|
||||
bool isGlobal() const { return accountId < 0; }
|
||||
bool isValid() const { return id >= 0 && !name.isEmpty(); }
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static Template fromJson(const QJsonObject& obj);
|
||||
};
|
||||
#include "common_structs.h"
|
||||
|
||||
class TemplateDao
|
||||
{
|
||||
|
||||
+43
-42
@@ -30,9 +30,51 @@ ReaderView::ReaderView(QWidget *parent) : QWidget(parent) {
|
||||
|
||||
void ReaderView::setupUI() {
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->setContentsMargins(12, 0, 12, 0);
|
||||
mainLayout->setContentsMargins(10, 0, 10, 0);
|
||||
mainLayout->setSpacing(0);
|
||||
|
||||
// ===== Subject Section (independent, with shadow) =====
|
||||
QFrame *subjectFrame = new QFrame();
|
||||
subjectFrame->setObjectName("SubjectFrame");
|
||||
subjectFrame->setStyleSheet(
|
||||
"QFrame#SubjectFrame {"
|
||||
" background: white;"
|
||||
" border: none;"
|
||||
" border: 1px solid #e0e0e0;"
|
||||
" border-radius: 4px;"
|
||||
"}"
|
||||
);
|
||||
|
||||
// Add shadow effect
|
||||
QGraphicsDropShadowEffect *shadowEffect = new QGraphicsDropShadowEffect(this);
|
||||
shadowEffect->setBlurRadius(22);
|
||||
shadowEffect->setOffset(0, 6);
|
||||
shadowEffect->setColor(QColor(0, 0, 0, 40));
|
||||
subjectFrame->setGraphicsEffect(shadowEffect);
|
||||
|
||||
QVBoxLayout *subjectLayout = new QVBoxLayout(subjectFrame);
|
||||
subjectLayout->setContentsMargins(16, 16, 16, 16);
|
||||
subjectLayout->setSpacing(8);
|
||||
|
||||
QHBoxLayout *subjectRow = new QHBoxLayout();
|
||||
subjectRow->setSpacing(12);
|
||||
|
||||
m_subjectLabel = new QLabel();
|
||||
m_subjectLabel->setText("(Sin asunto)");
|
||||
QFont subjectFont = m_subjectLabel->font();
|
||||
subjectFont.setBold(true);
|
||||
subjectFont.setPointSize(12);
|
||||
m_subjectLabel->setFont(subjectFont);
|
||||
m_subjectLabel->setWordWrap(true);
|
||||
m_subjectLabel->setStyleSheet("color: #1d1d1f;");
|
||||
m_subjectLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
subjectRow->addWidget(m_subjectLabel, 1);
|
||||
|
||||
subjectLayout->addLayout(subjectRow);
|
||||
mainLayout->addWidget(subjectFrame);
|
||||
// Add spacing after subject frame so shadow is visible
|
||||
mainLayout->addSpacing(12);
|
||||
|
||||
// ===== Toolbar (zoom, find, images) =====
|
||||
m_toolbar = new QFrame();
|
||||
m_toolbar->setFixedHeight(36);
|
||||
@@ -183,47 +225,6 @@ void ReaderView::setupUI() {
|
||||
mainLayout->addWidget(m_toolbar);
|
||||
mainLayout->addWidget(m_findBar);
|
||||
|
||||
// ===== Subject Section (independent, with shadow) =====
|
||||
QFrame *subjectFrame = new QFrame();
|
||||
subjectFrame->setObjectName("SubjectFrame");
|
||||
subjectFrame->setStyleSheet(
|
||||
"QFrame#SubjectFrame {"
|
||||
" background: white;"
|
||||
" border: none;"
|
||||
" border: 1px solid #e0e0e0;"
|
||||
" border-radius: 10px;"
|
||||
"}"
|
||||
);
|
||||
// Add shadow effect
|
||||
QGraphicsDropShadowEffect *shadowEffect = new QGraphicsDropShadowEffect(this);
|
||||
shadowEffect->setBlurRadius(8);
|
||||
shadowEffect->setOffset(0, 3);
|
||||
shadowEffect->setColor(QColor(0, 0, 0, 40));
|
||||
subjectFrame->setGraphicsEffect(shadowEffect);
|
||||
|
||||
QVBoxLayout *subjectLayout = new QVBoxLayout(subjectFrame);
|
||||
subjectLayout->setContentsMargins(16, 16, 16, 16);
|
||||
subjectLayout->setSpacing(8);
|
||||
|
||||
QHBoxLayout *subjectRow = new QHBoxLayout();
|
||||
subjectRow->setSpacing(12);
|
||||
|
||||
m_subjectLabel = new QLabel();
|
||||
m_subjectLabel->setText("(Sin asunto)");
|
||||
QFont subjectFont = m_subjectLabel->font();
|
||||
subjectFont.setBold(true);
|
||||
subjectFont.setPointSize(16);
|
||||
m_subjectLabel->setFont(subjectFont);
|
||||
m_subjectLabel->setWordWrap(true);
|
||||
m_subjectLabel->setStyleSheet("color: #1d1d1f;");
|
||||
m_subjectLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
subjectRow->addWidget(m_subjectLabel, 1);
|
||||
|
||||
subjectLayout->addLayout(subjectRow);
|
||||
mainLayout->addWidget(subjectFrame);
|
||||
// Add spacing after subject frame so shadow is visible
|
||||
mainLayout->addSpacing(12);
|
||||
|
||||
// ===== Header Section =====
|
||||
m_headerWidget = new QWidget();
|
||||
m_headerWidget->setStyleSheet("QWidget { background: white; border-bottom: 1px solid #e0e0e0; }");
|
||||
|
||||
+734
-27
@@ -1,6 +1,14 @@
|
||||
#include "ui/settingsview.h"
|
||||
#include "settingsview.h"
|
||||
#include "services/accountservice.h"
|
||||
#include "core/models/account.h"
|
||||
#include "db/dao/common_structs.h"
|
||||
#include "db/dao/categorydao.h"
|
||||
#include "db/dao/ruledao.h"
|
||||
#include "db/dao/templatedao.h"
|
||||
#include "db/dao/signaturedao.h"
|
||||
#include "ui/ruleditordialog.h"
|
||||
#include "ui/templateeditordialog.h"
|
||||
#include "ui/signaturemanagerdialog.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QMessageBox>
|
||||
@@ -13,6 +21,19 @@
|
||||
#include <QButtonGroup>
|
||||
#include <QComboBox>
|
||||
#include <QCheckBox>
|
||||
#include <QRadioButton>
|
||||
#include <QTreeWidget>
|
||||
#include <QLineEdit>
|
||||
#include <QDialog>
|
||||
#include <QColorDialog>
|
||||
#include <QInputDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFormLayout>
|
||||
#include <QScrollArea>
|
||||
#include <QGroupBox>
|
||||
#include <QSqlQuery>
|
||||
#include <QSqlDatabase>
|
||||
#include <QHeaderView>
|
||||
|
||||
SettingsView::SettingsView(QWidget *parent) : QWidget(parent) {
|
||||
setupUI();
|
||||
@@ -23,9 +44,13 @@ void SettingsView::setupUI() {
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
m_tabWidget = new QTabWidget();
|
||||
m_tabWidget->addTab(createAccountsTab(), "Accounts");
|
||||
m_tabWidget->addTab(createGeneralTab(), "General");
|
||||
m_tabWidget->addTab(createAppearanceTab(), "Appearance");
|
||||
m_tabWidget->addTab(createAccountsTab(), tr("Accounts"));
|
||||
m_tabWidget->addTab(createCategoriesTab(), tr("Categories"));
|
||||
m_tabWidget->addTab(createRulesTab(), tr("Rules"));
|
||||
m_tabWidget->addTab(createTemplatesTab(), tr("Templates"));
|
||||
m_tabWidget->addTab(createSignaturesTab(), tr("Signatures"));
|
||||
m_tabWidget->addTab(createGeneralTab(), tr("General"));
|
||||
m_tabWidget->addTab(createAppearanceTab(), tr("Appearance"));
|
||||
|
||||
mainLayout->addWidget(m_tabWidget);
|
||||
}
|
||||
@@ -35,9 +60,10 @@ QWidget* SettingsView::createAccountsTab() {
|
||||
QVBoxLayout *layout = new QVBoxLayout(widget);
|
||||
layout->setContentsMargins(20, 20, 20, 20);
|
||||
|
||||
QLabel *title = new QLabel("Email Accounts");
|
||||
QLabel *title = new QLabel(tr("Email Accounts"));
|
||||
QFont titleFont = title->font();
|
||||
titleFont.setPointSize(16);
|
||||
titleFont.setBold(true);
|
||||
title->setFont(titleFont);
|
||||
layout->addWidget(title);
|
||||
|
||||
@@ -51,21 +77,22 @@ QWidget* SettingsView::createAccountsTab() {
|
||||
layout->addWidget(m_accountList);
|
||||
|
||||
QHBoxLayout *buttonLayout = new QHBoxLayout();
|
||||
m_addBtn = new QPushButton("Add Account");
|
||||
m_addBtn = new QPushButton(tr("Add Account"));
|
||||
m_addBtn->setStyleSheet(
|
||||
"QPushButton { background: #FFEB3B; color: white; border: none; border-radius: 4px; "
|
||||
"QPushButton { background: #1976D2; color: white; border: none; border-radius: 4px; "
|
||||
"padding: 8px 16px; font-weight: bold; }"
|
||||
"QPushButton:hover { background: #FDD835; }"
|
||||
"QPushButton:hover { background: #1565C0; }"
|
||||
);
|
||||
buttonLayout->addWidget(m_addBtn);
|
||||
m_editBtn = new QPushButton("Edit");
|
||||
|
||||
m_editBtn = new QPushButton(tr("Edit"));
|
||||
m_editBtn->setEnabled(false);
|
||||
m_editBtn->setStyleSheet(
|
||||
"QPushButton { background: #FFA726; color: white; border: none; border-radius: 4px; "
|
||||
"padding: 8px 16px; font-weight: bold; }"
|
||||
"QPushButton:hover { background: #FB8C00; }"
|
||||
);
|
||||
m_deleteBtn = new QPushButton("Delete");
|
||||
m_deleteBtn = new QPushButton(tr("Delete"));
|
||||
m_deleteBtn->setEnabled(false);
|
||||
m_deleteBtn->setStyleSheet(
|
||||
"QPushButton { background: #EF5350; color: white; border: none; border-radius: 4px; "
|
||||
@@ -85,32 +112,283 @@ QWidget* SettingsView::createAccountsTab() {
|
||||
return widget;
|
||||
}
|
||||
|
||||
QWidget* SettingsView::createCategoriesTab() {
|
||||
QWidget *widget = new QWidget();
|
||||
QVBoxLayout *layout = new QVBoxLayout(widget);
|
||||
layout->setContentsMargins(12, 12, 12, 12);
|
||||
layout->setSpacing(8);
|
||||
|
||||
QLabel *title = new QLabel(tr("Categories"));
|
||||
QFont titleFont = title->font();
|
||||
titleFont.setPointSize(16);
|
||||
titleFont.setBold(true);
|
||||
title->setFont(titleFont);
|
||||
layout->addWidget(title);
|
||||
|
||||
m_categoryTree = new QTreeWidget();
|
||||
m_categoryTree->setHeaderLabels({tr("Category"), tr("Color"), tr("Account")});
|
||||
m_categoryTree->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
m_categoryTree->setRootIsDecorated(true);
|
||||
m_categoryTree->setItemsExpandable(true);
|
||||
m_categoryTree->setStyleSheet(
|
||||
"QTreeWidget { background: #ffffff; border: 1px solid #e0e0e0; border-radius: 4px; }"
|
||||
"QTreeWidget::item { padding: 8px; border-bottom: 1px solid #f0f0f0; }"
|
||||
"QTreeWidget::item:selected { background: #e3f2fd; }"
|
||||
);
|
||||
m_categoryTree->setColumnWidth(0, 250);
|
||||
m_categoryTree->setColumnWidth(1, 80);
|
||||
m_categoryTree->header()->setStretchLastSection(false);
|
||||
layout->addWidget(m_categoryTree);
|
||||
|
||||
QHBoxLayout *buttonLayout = new QHBoxLayout();
|
||||
m_categoryAddBtn = new QPushButton(tr("New Category"));
|
||||
m_categoryAddBtn->setStyleSheet(
|
||||
"QPushButton { background: #1976D2; color: white; border: none; border-radius: 4px; "
|
||||
"padding: 8px 16px; font-weight: bold; }"
|
||||
"QPushButton:hover { background: #1565C0; }"
|
||||
);
|
||||
m_categoryEditBtn = new QPushButton(tr("Edit"));
|
||||
m_categoryEditBtn->setEnabled(false);
|
||||
m_categoryEditBtn->setStyleSheet(
|
||||
"QPushButton { background: #FFA726; color: white; border: none; border-radius: 4px; "
|
||||
"padding: 8px 16px; font-weight: bold; }"
|
||||
"QPushButton:hover { background: #FB8C00; }"
|
||||
);
|
||||
m_categoryDeleteBtn = new QPushButton(tr("Delete"));
|
||||
m_categoryDeleteBtn->setEnabled(false);
|
||||
m_categoryDeleteBtn->setStyleSheet(
|
||||
"QPushButton { background: #EF5350; color: white; border: none; border-radius: 4px; "
|
||||
"padding: 8px 16px; font-weight: bold; }"
|
||||
"QPushButton:hover { background: #E53935; }"
|
||||
);
|
||||
|
||||
buttonLayout->addWidget(m_categoryAddBtn);
|
||||
buttonLayout->addWidget(m_categoryEditBtn);
|
||||
buttonLayout->addWidget(m_categoryDeleteBtn);
|
||||
buttonLayout->addStretch();
|
||||
layout->addLayout(buttonLayout);
|
||||
|
||||
connect(m_categoryTree, &QTreeWidget::itemSelectionChanged, this, &SettingsView::onCategorySelectionChanged);
|
||||
connect(m_categoryAddBtn, &QPushButton::clicked, this, &SettingsView::onCategoryAddClicked);
|
||||
connect(m_categoryEditBtn, &QPushButton::clicked, this, &SettingsView::onCategoryEditClicked);
|
||||
connect(m_categoryDeleteBtn, &QPushButton::clicked, this, &SettingsView::onCategoryDeleteClicked);
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
QWidget* SettingsView::createRulesTab() {
|
||||
QWidget *widget = new QWidget();
|
||||
QVBoxLayout *layout = new QVBoxLayout(widget);
|
||||
layout->setContentsMargins(12, 12, 12, 12);
|
||||
layout->setSpacing(8);
|
||||
|
||||
QLabel *title = new QLabel(tr("Rules"));
|
||||
QFont titleFont = title->font();
|
||||
titleFont.setPointSize(16);
|
||||
titleFont.setBold(true);
|
||||
title->setFont(titleFont);
|
||||
layout->addWidget(title);
|
||||
|
||||
m_ruleList = new QListWidget();
|
||||
m_ruleList->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
m_ruleList->setStyleSheet(
|
||||
"QListWidget { background: #ffffff; border: 1px solid #e0e0e0; border-radius: 4px; }"
|
||||
"QListWidget::item { padding: 12px; border-bottom: 1px solid #f0f0f0; }"
|
||||
"QListWidget::item:selected { background: #e3f2fd; }"
|
||||
);
|
||||
m_ruleList->setSpacing(2);
|
||||
layout->addWidget(m_ruleList);
|
||||
|
||||
QHBoxLayout *buttonLayout = new QHBoxLayout();
|
||||
m_ruleAddBtn = new QPushButton(tr("New Rule"));
|
||||
m_ruleAddBtn->setStyleSheet(
|
||||
"QPushButton { background: #1976D2; color: white; border: none; border-radius: 4px; "
|
||||
"padding: 8px 16px; font-weight: bold; }"
|
||||
"QPushButton:hover { background: #1565C0; }"
|
||||
);
|
||||
m_ruleEditBtn = new QPushButton(tr("Edit"));
|
||||
m_ruleEditBtn->setEnabled(false);
|
||||
m_ruleEditBtn->setStyleSheet(
|
||||
"QPushButton { background: #FFA726; color: white; border: none; border-radius: 4px; "
|
||||
"padding: 8px 16px; font-weight: bold; }"
|
||||
"QPushButton:hover { background: #FB8C00; }"
|
||||
);
|
||||
m_ruleDeleteBtn = new QPushButton(tr("Delete"));
|
||||
m_ruleDeleteBtn->setEnabled(false);
|
||||
m_ruleDeleteBtn->setStyleSheet(
|
||||
"QPushButton { background: #EF5350; color: white; border: none; border-radius: 4px; "
|
||||
"padding: 8px 16px; font-weight: bold; }"
|
||||
"QPushButton:hover { background: #E53935; }"
|
||||
);
|
||||
m_ruleToggleBtn = new QPushButton(tr("Toggle"));
|
||||
m_ruleToggleBtn->setEnabled(false);
|
||||
m_ruleToggleBtn->setStyleSheet(
|
||||
"QPushButton { background: #66BB6A; color: white; border: none; border-radius: 4px; "
|
||||
"padding: 8px 16px; font-weight: bold; }"
|
||||
"QPushButton:hover { background: #4CAF50; }"
|
||||
);
|
||||
|
||||
buttonLayout->addWidget(m_ruleAddBtn);
|
||||
buttonLayout->addWidget(m_ruleEditBtn);
|
||||
buttonLayout->addWidget(m_ruleDeleteBtn);
|
||||
buttonLayout->addWidget(m_ruleToggleBtn);
|
||||
buttonLayout->addStretch();
|
||||
layout->addLayout(buttonLayout);
|
||||
|
||||
connect(m_ruleList, &QListWidget::itemSelectionChanged, this, &SettingsView::onRuleSelectionChanged);
|
||||
connect(m_ruleAddBtn, &QPushButton::clicked, this, &SettingsView::onRuleAddClicked);
|
||||
connect(m_ruleEditBtn, &QPushButton::clicked, this, &SettingsView::onRuleEditClicked);
|
||||
connect(m_ruleDeleteBtn, &QPushButton::clicked, this, &SettingsView::onRuleDeleteClicked);
|
||||
connect(m_ruleToggleBtn, &QPushButton::clicked, this, &SettingsView::onRuleToggleEnabled);
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
QWidget* SettingsView::createTemplatesTab() {
|
||||
QWidget *widget = new QWidget();
|
||||
QVBoxLayout *layout = new QVBoxLayout(widget);
|
||||
layout->setContentsMargins(12, 12, 12, 12);
|
||||
layout->setSpacing(8);
|
||||
|
||||
QLabel *title = new QLabel(tr("Templates"));
|
||||
QFont titleFont = title->font();
|
||||
titleFont.setPointSize(16);
|
||||
titleFont.setBold(true);
|
||||
title->setFont(titleFont);
|
||||
layout->addWidget(title);
|
||||
|
||||
m_templateList = new QListWidget();
|
||||
m_templateList->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
m_templateList->setStyleSheet(
|
||||
"QListWidget { background: #ffffff; border: 1px solid #e0e0e0; border-radius: 4px; }"
|
||||
"QListWidget::item { padding: 12px; border-bottom: 1px solid #f0f0f0; }"
|
||||
"QListWidget::item:selected { background: #e3f2fd; }"
|
||||
);
|
||||
m_templateList->setSpacing(2);
|
||||
layout->addWidget(m_templateList);
|
||||
|
||||
QHBoxLayout *buttonLayout = new QHBoxLayout();
|
||||
m_templateAddBtn = new QPushButton(tr("New Template"));
|
||||
m_templateAddBtn->setStyleSheet(
|
||||
"QPushButton { background: #1976D2; color: white; border: none; border-radius: 4px; "
|
||||
"padding: 8px 16px; font-weight: bold; }"
|
||||
"QPushButton:hover { background: #1565C0; }"
|
||||
);
|
||||
m_templateEditBtn = new QPushButton(tr("Edit"));
|
||||
m_templateEditBtn->setEnabled(false);
|
||||
m_templateEditBtn->setStyleSheet(
|
||||
"QPushButton { background: #FFA726; color: white; border: none; border-radius: 4px; "
|
||||
"padding: 8px 16px; font-weight: bold; }"
|
||||
"QPushButton:hover { background: #FB8C00; }"
|
||||
);
|
||||
m_templateDeleteBtn = new QPushButton(tr("Delete"));
|
||||
m_templateDeleteBtn->setEnabled(false);
|
||||
m_templateDeleteBtn->setStyleSheet(
|
||||
"QPushButton { background: #EF5350; color: white; border: none; border-radius: 4px; "
|
||||
"padding: 8px 16px; font-weight: bold; }"
|
||||
"QPushButton:hover { background: #E53935; }"
|
||||
);
|
||||
|
||||
buttonLayout->addWidget(m_templateAddBtn);
|
||||
buttonLayout->addWidget(m_templateEditBtn);
|
||||
buttonLayout->addWidget(m_templateDeleteBtn);
|
||||
buttonLayout->addStretch();
|
||||
layout->addLayout(buttonLayout);
|
||||
|
||||
connect(m_templateList, &QListWidget::itemSelectionChanged, this, &SettingsView::onTemplateSelectionChanged);
|
||||
connect(m_templateAddBtn, &QPushButton::clicked, this, &SettingsView::onTemplateAddClicked);
|
||||
connect(m_templateEditBtn, &QPushButton::clicked, this, &SettingsView::onTemplateEditClicked);
|
||||
connect(m_templateDeleteBtn, &QPushButton::clicked, this, &SettingsView::onTemplateDeleteClicked);
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
QWidget* SettingsView::createSignaturesTab() {
|
||||
QWidget *widget = new QWidget();
|
||||
QVBoxLayout *layout = new QVBoxLayout(widget);
|
||||
layout->setContentsMargins(12, 12, 12, 12);
|
||||
layout->setSpacing(8);
|
||||
|
||||
QLabel *title = new QLabel(tr("Signatures"));
|
||||
QFont titleFont = title->font();
|
||||
titleFont.setPointSize(16);
|
||||
titleFont.setBold(true);
|
||||
title->setFont(titleFont);
|
||||
layout->addWidget(title);
|
||||
|
||||
m_signatureList = new QListWidget();
|
||||
m_signatureList->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
m_signatureList->setStyleSheet(
|
||||
"QListWidget { background: #ffffff; border: 1px solid #e0e0e0; border-radius: 4px; }"
|
||||
"QListWidget::item { padding: 12px; border-bottom: 1px solid #f0f0f0; }"
|
||||
"QListWidget::item:selected { background: #e3f2fd; }"
|
||||
);
|
||||
m_signatureList->setSpacing(2);
|
||||
layout->addWidget(m_signatureList);
|
||||
|
||||
QHBoxLayout *buttonLayout = new QHBoxLayout();
|
||||
m_signatureAddBtn = new QPushButton(tr("New Signature"));
|
||||
m_signatureAddBtn->setStyleSheet(
|
||||
"QPushButton { background: #1976D2; color: white; border: none; border-radius: 4px; "
|
||||
"padding: 8px 16px; font-weight: bold; }"
|
||||
"QPushButton:hover { background: #1565C0; }"
|
||||
);
|
||||
m_signatureEditBtn = new QPushButton(tr("Edit"));
|
||||
m_signatureEditBtn->setEnabled(false);
|
||||
m_signatureEditBtn->setStyleSheet(
|
||||
"QPushButton { background: #FFA726; color: white; border: none; border-radius: 4px; "
|
||||
"padding: 8px 16px; font-weight: bold; }"
|
||||
"QPushButton:hover { background: #FB8C00; }"
|
||||
);
|
||||
m_signatureDeleteBtn = new QPushButton(tr("Delete"));
|
||||
m_signatureDeleteBtn->setEnabled(false);
|
||||
m_signatureDeleteBtn->setStyleSheet(
|
||||
"QPushButton { background: #EF5350; color: white; border: none; border-radius: 4px; "
|
||||
"padding: 8px 16px; font-weight: bold; }"
|
||||
"QPushButton:hover { background: #E53935; }"
|
||||
);
|
||||
|
||||
buttonLayout->addWidget(m_signatureAddBtn);
|
||||
buttonLayout->addWidget(m_signatureEditBtn);
|
||||
buttonLayout->addWidget(m_signatureDeleteBtn);
|
||||
buttonLayout->addStretch();
|
||||
layout->addLayout(buttonLayout);
|
||||
|
||||
connect(m_signatureList, &QListWidget::itemSelectionChanged, this, &SettingsView::onSignatureSelectionChanged);
|
||||
connect(m_signatureAddBtn, &QPushButton::clicked, this, &SettingsView::onSignatureAddClicked);
|
||||
connect(m_signatureEditBtn, &QPushButton::clicked, this, &SettingsView::onSignatureEditClicked);
|
||||
connect(m_signatureDeleteBtn, &QPushButton::clicked, this, &SettingsView::onSignatureDeleteClicked);
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
QWidget* SettingsView::createGeneralTab() {
|
||||
QWidget *widget = new QWidget();
|
||||
QVBoxLayout *layout = new QVBoxLayout(widget);
|
||||
layout->setContentsMargins(20, 20, 20, 20);
|
||||
|
||||
QLabel *title = new QLabel("General Settings");
|
||||
QLabel *title = new QLabel(tr("General Settings"));
|
||||
QFont titleFont = title->font();
|
||||
titleFont.setPointSize(16);
|
||||
titleFont.setBold(true);
|
||||
title->setFont(titleFont);
|
||||
layout->addWidget(title);
|
||||
|
||||
QCheckBox *startOnLogin = new QCheckBox("Start application on login");
|
||||
QCheckBox *startOnLogin = new QCheckBox(tr("Start application on login"));
|
||||
startOnLogin->setChecked(true);
|
||||
connect(startOnLogin, &QCheckBox::toggled, this, [this](bool checked) {
|
||||
emit settingChanged("start_on_login", QVariant(checked));
|
||||
});
|
||||
layout->addWidget(startOnLogin);
|
||||
|
||||
QCheckBox *enableNotifications = new QCheckBox("Enable notifications");
|
||||
QCheckBox *enableNotifications = new QCheckBox(tr("Enable notifications"));
|
||||
enableNotifications->setChecked(true);
|
||||
connect(enableNotifications, &QCheckBox::toggled, this, [this](bool checked) {
|
||||
emit settingChanged("enable_notifications", QVariant(checked));
|
||||
});
|
||||
layout->addWidget(enableNotifications);
|
||||
|
||||
QCheckBox *minimizeToTray = new QCheckBox("Minimize to tray on close");
|
||||
QCheckBox *minimizeToTray = new QCheckBox(tr("Minimize to tray on close"));
|
||||
minimizeToTray->setChecked(true);
|
||||
connect(minimizeToTray, &QCheckBox::toggled, this, [this](bool checked) {
|
||||
emit settingChanged("minimize_to_tray", QVariant(checked));
|
||||
@@ -126,22 +404,23 @@ QWidget* SettingsView::createAppearanceTab() {
|
||||
QVBoxLayout *layout = new QVBoxLayout(widget);
|
||||
layout->setContentsMargins(20, 20, 20, 20);
|
||||
|
||||
QLabel *title = new QLabel("Appearance");
|
||||
QLabel *title = new QLabel(tr("Appearance"));
|
||||
QFont titleFont = title->font();
|
||||
titleFont.setPointSize(16);
|
||||
titleFont.setBold(true);
|
||||
title->setFont(titleFont);
|
||||
layout->addWidget(title);
|
||||
|
||||
QLabel *themeLabel = new QLabel("Theme");
|
||||
QLabel *themeLabel = new QLabel(tr("Theme"));
|
||||
QFont labelFont = themeLabel->font();
|
||||
labelFont.setBold(true);
|
||||
themeLabel->setFont(labelFont);
|
||||
layout->addWidget(themeLabel);
|
||||
|
||||
m_themeGroup = new QButtonGroup(this);
|
||||
QRadioButton *lightRadio = new QRadioButton("Light");
|
||||
QRadioButton *darkRadio = new QRadioButton("Dark");
|
||||
QRadioButton *systemRadio = new QRadioButton("System");
|
||||
QRadioButton *lightRadio = new QRadioButton(tr("Light"));
|
||||
QRadioButton *darkRadio = new QRadioButton(tr("Dark"));
|
||||
QRadioButton *systemRadio = new QRadioButton(tr("System"));
|
||||
m_themeGroup->addButton(lightRadio, 0);
|
||||
m_themeGroup->addButton(darkRadio, 1);
|
||||
m_themeGroup->addButton(systemRadio, 2);
|
||||
@@ -155,14 +434,14 @@ QWidget* SettingsView::createAppearanceTab() {
|
||||
connect(m_themeGroup, QOverload<int>::of(&QButtonGroup::idClicked), this, [this](int id) {
|
||||
QString theme;
|
||||
switch (id) {
|
||||
case 0: theme = "light"; break;
|
||||
case 1: theme = "dark"; break;
|
||||
case 2: theme = "system"; break;
|
||||
case 0: theme = "light"; break;
|
||||
case 1: theme = "dark"; break;
|
||||
case 2: theme = "system"; break;
|
||||
}
|
||||
emit themeChanged(theme);
|
||||
});
|
||||
|
||||
QCheckBox *deepinTheme = new QCheckBox("Deepin theme (experimental)");
|
||||
QCheckBox *deepinTheme = new QCheckBox(tr("Deepin theme (experimental)"));
|
||||
deepinTheme->setChecked(true);
|
||||
connect(deepinTheme, &QCheckBox::toggled, this, [this](bool checked) {
|
||||
emit settingChanged("deepin_theme", QVariant(checked));
|
||||
@@ -173,6 +452,7 @@ QWidget* SettingsView::createAppearanceTab() {
|
||||
return widget;
|
||||
}
|
||||
|
||||
// ========== Slots for Accounts ==========
|
||||
void SettingsView::setAccountService(AccountService *service) {
|
||||
m_accountService = service;
|
||||
if (m_accountService) {
|
||||
@@ -204,8 +484,8 @@ void SettingsView::loadAccounts() {
|
||||
m_accountList->setCurrentRow(0);
|
||||
}
|
||||
}
|
||||
void SettingsView::onAccountSelectionChanged()
|
||||
{
|
||||
|
||||
void SettingsView::onAccountSelectionChanged() {
|
||||
QList<QListWidgetItem*> items = m_accountList->selectedItems();
|
||||
qDebug() << "SettingsView::onAccountSelectionChanged, selected items count:" << items.size();
|
||||
if (items.isEmpty()) {
|
||||
@@ -231,10 +511,10 @@ void SettingsView::onEditClicked() {
|
||||
void SettingsView::onDeleteClicked() {
|
||||
if (m_selectedAccountId != -1 && m_accountService) {
|
||||
QMessageBox msgBox(this);
|
||||
msgBox.setWindowTitle("Delete Account");
|
||||
msgBox.setWindowTitle(tr("Delete Account"));
|
||||
Account* account = m_accountService->findAccountById(m_selectedAccountId);
|
||||
QString accountName = account ? account->email() : QString::number(m_selectedAccountId);
|
||||
msgBox.setText(QString("Are you sure you want to delete the account \"%1\"?").arg(accountName));
|
||||
msgBox.setText(tr("Are you sure you want to delete the account \"%1\"?").arg(accountName));
|
||||
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
||||
msgBox.setDefaultButton(QMessageBox::No);
|
||||
if (msgBox.exec() == QMessageBox::Yes) {
|
||||
@@ -242,3 +522,430 @@ void SettingsView::onDeleteClicked() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Category Slots ==========
|
||||
void SettingsView::onCategorySelectionChanged() {
|
||||
bool hasSelection = !m_categoryTree->selectedItems().isEmpty();
|
||||
m_categoryEditBtn->setEnabled(hasSelection);
|
||||
m_categoryDeleteBtn->setEnabled(hasSelection);
|
||||
}
|
||||
|
||||
void SettingsView::onCategoryAddClicked() {
|
||||
QDialog dlg(this);
|
||||
dlg.setWindowTitle(tr("New Category"));
|
||||
dlg.setMinimumWidth(400);
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(&dlg);
|
||||
QFormLayout *form = new QFormLayout();
|
||||
|
||||
QLineEdit *nameEdit = new QLineEdit();
|
||||
form->addRow(tr("Name:"), nameEdit);
|
||||
|
||||
QPushButton *colorBtn = new QPushButton();
|
||||
colorBtn->setFixedSize(24, 24);
|
||||
QColor catColor = QColor("#1976D2");
|
||||
colorBtn->setStyleSheet(QString("background-color: %1; border: 1px solid #ccc; border-radius: 4px;").arg(catColor.name()));
|
||||
connect(colorBtn, &QPushButton::clicked, [&catColor, colorBtn]() {
|
||||
QColor c = QColorDialog::getColor(catColor);
|
||||
if (c.isValid()) {
|
||||
catColor = c;
|
||||
colorBtn->setStyleSheet(QString("background-color: %1; border: 1px solid #ccc; border-radius: 4px;").arg(c.name()));
|
||||
}
|
||||
});
|
||||
|
||||
QHBoxLayout *colorLayout = new QHBoxLayout();
|
||||
colorLayout->addWidget(colorBtn);
|
||||
colorLayout->addStretch();
|
||||
form->addRow(tr("Color:"), colorLayout);
|
||||
|
||||
QComboBox *parentCombo = new QComboBox();
|
||||
parentCombo->addItem(tr("(None - Top Level)"), QVariant());
|
||||
form->addRow(tr("Parent:"), parentCombo);
|
||||
|
||||
layout->addLayout(form);
|
||||
|
||||
QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(buttons, &QDialogButtonBox::accepted, &dlg, &QDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);
|
||||
layout->addWidget(buttons);
|
||||
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
Category cat;
|
||||
cat.name = nameEdit->text().trimmed();
|
||||
cat.color = catColor;
|
||||
cat.accountId = -1; // Global by default
|
||||
cat.parentCategoryId = parentCombo->currentData().toLongLong();
|
||||
if (!cat.name.isEmpty()) {
|
||||
emit categoryAddRequested(cat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsView::onCategoryEditClicked() {
|
||||
QTreeWidgetItem *item = m_categoryTree->currentItem();
|
||||
if (!item) return;
|
||||
|
||||
qint64 catId = item->data(0, Qt::UserRole).toLongLong();
|
||||
std::optional<Category> catOpt = CategoryDao::findById(catId);
|
||||
if (!catOpt.has_value()) return;
|
||||
Category cat = *catOpt;
|
||||
|
||||
QDialog dlg(this);
|
||||
dlg.setWindowTitle(tr("Edit Category"));
|
||||
dlg.setMinimumWidth(400);
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(&dlg);
|
||||
QFormLayout *form = new QFormLayout();
|
||||
|
||||
QLineEdit *nameEdit = new QLineEdit(cat.name);
|
||||
form->addRow(tr("Name:"), nameEdit);
|
||||
|
||||
QPushButton *colorBtn = new QPushButton();
|
||||
colorBtn->setFixedSize(24, 24);
|
||||
colorBtn->setStyleSheet(QString("background-color: %1; border: 1px solid #ccc; border-radius: 4px;").arg(cat.color.name()));
|
||||
QColor catColor = cat.color;
|
||||
connect(colorBtn, &QPushButton::clicked, [&catColor, colorBtn]() {
|
||||
QColor c = QColorDialog::getColor(catColor);
|
||||
if (c.isValid()) {
|
||||
catColor = c;
|
||||
colorBtn->setStyleSheet(QString("background-color: %1; border: 1px solid #ccc; border-radius: 4px;").arg(c.name()));
|
||||
}
|
||||
});
|
||||
|
||||
QHBoxLayout *colorLayout = new QHBoxLayout();
|
||||
colorLayout->addWidget(colorBtn);
|
||||
colorLayout->addStretch();
|
||||
form->addRow(tr("Color:"), colorLayout);
|
||||
|
||||
layout->addLayout(form);
|
||||
|
||||
QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(buttons, &QDialogButtonBox::accepted, &dlg, &QDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);
|
||||
layout->addWidget(buttons);
|
||||
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
cat.name = nameEdit->text().trimmed();
|
||||
cat.color = catColor;
|
||||
if (!cat.name.isEmpty()) {
|
||||
emit categoryEditRequested(cat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsView::onCategoryDeleteClicked() {
|
||||
QTreeWidgetItem *item = m_categoryTree->currentItem();
|
||||
if (!item) return;
|
||||
|
||||
qint64 catId = item->data(0, Qt::UserRole).toLongLong();
|
||||
|
||||
QMessageBox msgBox(this);
|
||||
msgBox.setWindowTitle(tr("Delete Category"));
|
||||
msgBox.setText(tr("Are you sure you want to delete this category?"));
|
||||
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
||||
msgBox.setDefaultButton(QMessageBox::No);
|
||||
if (msgBox.exec() == QMessageBox::Yes) {
|
||||
emit categoryDeleteRequested(catId);
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsView::loadCategories() {
|
||||
refreshCategoryTree();
|
||||
}
|
||||
|
||||
void SettingsView::refreshCategoryTree() {
|
||||
m_categoryTree->clear();
|
||||
|
||||
// Load global categories
|
||||
QVector<Category> globalCats = CategoryDao::findGlobal();
|
||||
for (const Category &cat : globalCats) {
|
||||
QTreeWidgetItem *item = createCategoryTreeItem(cat);
|
||||
m_categoryTree->addTopLevelItem(item);
|
||||
}
|
||||
|
||||
// Load account-specific categories for selected account
|
||||
if (m_selectedAccountId > 0) {
|
||||
QVector<Category> accountCats = CategoryDao::findByAccount(m_selectedAccountId);
|
||||
for (const Category &cat : accountCats) {
|
||||
QTreeWidgetItem *item = createCategoryTreeItem(cat);
|
||||
m_categoryTree->addTopLevelItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
m_categoryTree->expandAll();
|
||||
}
|
||||
|
||||
QTreeWidgetItem* SettingsView::createCategoryTreeItem(const Category &cat, QTreeWidgetItem *parent) {
|
||||
QTreeWidgetItem *item = parent ? new QTreeWidgetItem(parent) : new QTreeWidgetItem();
|
||||
|
||||
item->setText(0, cat.name);
|
||||
item->setData(0, Qt::UserRole, cat.id);
|
||||
|
||||
// Color indicator
|
||||
QTableWidgetItem *colorItem = new QTableWidgetItem();
|
||||
colorItem->setBackground(cat.color);
|
||||
colorItem->setFlags(colorItem->flags() & ~Qt::ItemIsEditable);
|
||||
|
||||
// Account info
|
||||
QString accountInfo = cat.accountId >= 0 ? QString("Account %1").arg(cat.accountId) : tr("Global");
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
// ========== Rule Slots ==========
|
||||
void SettingsView::onRuleSelectionChanged() {
|
||||
bool hasSelection = m_ruleList->currentRow() >= 0;
|
||||
m_ruleEditBtn->setEnabled(hasSelection);
|
||||
m_ruleDeleteBtn->setEnabled(hasSelection);
|
||||
m_ruleToggleBtn->setEnabled(hasSelection);
|
||||
}
|
||||
|
||||
void SettingsView::onRuleAddClicked() {
|
||||
RuleEditorDialog dlg(Rule(), -1, this);
|
||||
connect(&dlg, &RuleEditorDialog::ruleSaved, this, [this](const Rule& rule) {
|
||||
emit ruleAddRequested(rule);
|
||||
});
|
||||
dlg.exec();
|
||||
}
|
||||
|
||||
void SettingsView::onRuleEditClicked() {
|
||||
int row = m_ruleList->currentRow();
|
||||
if (row < 0) return;
|
||||
|
||||
QListWidgetItem *item = m_ruleList->item(row);
|
||||
qint64 ruleId = item->data(Qt::UserRole).toLongLong();
|
||||
std::optional<Rule> ruleOpt = RuleDao::findById(ruleId);
|
||||
if (!ruleOpt.has_value()) return;
|
||||
|
||||
RuleEditorDialog dlg(*ruleOpt, -1, this);
|
||||
connect(&dlg, &RuleEditorDialog::ruleSaved, this, [this](const Rule& rule) {
|
||||
emit ruleEditRequested(rule);
|
||||
});
|
||||
dlg.exec();
|
||||
}
|
||||
|
||||
void SettingsView::onRuleDeleteClicked() {
|
||||
int row = m_ruleList->currentRow();
|
||||
if (row < 0) return;
|
||||
|
||||
QListWidgetItem *item = m_ruleList->item(row);
|
||||
qint64 ruleId = item->data(Qt::UserRole).toLongLong();
|
||||
|
||||
QMessageBox msgBox(this);
|
||||
msgBox.setWindowTitle(tr("Delete Rule"));
|
||||
msgBox.setText(tr("Are you sure you want to delete this rule?"));
|
||||
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
||||
msgBox.setDefaultButton(QMessageBox::No);
|
||||
if (msgBox.exec() == QMessageBox::Yes) {
|
||||
emit ruleDeleteRequested(ruleId);
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsView::onRuleToggleEnabled() {
|
||||
int row = m_ruleList->currentRow();
|
||||
if (row < 0) return;
|
||||
|
||||
QListWidgetItem *item = m_ruleList->item(row);
|
||||
qint64 ruleId = item->data(Qt::UserRole).toLongLong();
|
||||
std::optional<Rule> ruleOpt = RuleDao::findById(ruleId);
|
||||
if (!ruleOpt.has_value()) return;
|
||||
|
||||
Rule rule = *ruleOpt;
|
||||
rule.enabled = !rule.enabled;
|
||||
emit ruleEditRequested(rule);
|
||||
}
|
||||
|
||||
void SettingsView::loadRules() {
|
||||
m_ruleList->clear();
|
||||
|
||||
// Load global rules
|
||||
QVector<Rule> globalRules = RuleDao::findGlobal();
|
||||
for (const Rule &rule : globalRules) {
|
||||
QListWidgetItem *item = new QListWidgetItem(ruleToString(rule));
|
||||
item->setData(Qt::UserRole, rule.id);
|
||||
if (!rule.enabled) {
|
||||
item->setForeground(Qt::gray);
|
||||
}
|
||||
m_ruleList->addItem(item);
|
||||
}
|
||||
|
||||
// Load account-specific rules for selected account
|
||||
if (m_selectedAccountId > 0) {
|
||||
QVector<Rule> accountRules = RuleDao::findByAccount(m_selectedAccountId);
|
||||
for (const Rule &rule : accountRules) {
|
||||
QListWidgetItem *item = new QListWidgetItem(ruleToString(rule));
|
||||
item->setData(Qt::UserRole, rule.id);
|
||||
if (!rule.enabled) {
|
||||
item->setForeground(Qt::gray);
|
||||
}
|
||||
m_ruleList->addItem(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString SettingsView::ruleToString(const Rule &rule) const {
|
||||
QString status = rule.enabled ? "●" : "○";
|
||||
return QString("%1 %2 (Priority: %3)").arg(status, rule.name, QString::number(rule.priority));
|
||||
}
|
||||
|
||||
// ========== Template Slots ==========
|
||||
void SettingsView::onTemplateSelectionChanged() {
|
||||
bool hasSelection = m_templateList->currentRow() >= 0;
|
||||
m_templateEditBtn->setEnabled(hasSelection);
|
||||
m_templateDeleteBtn->setEnabled(hasSelection);
|
||||
}
|
||||
|
||||
void SettingsView::onTemplateAddClicked() {
|
||||
TemplateEditorDialog dlg(Template(), -1, this);
|
||||
connect(&dlg, &TemplateEditorDialog::templateSaved, this, [this](const Template& tmpl) {
|
||||
emit templateAddRequested(tmpl);
|
||||
});
|
||||
dlg.exec();
|
||||
}
|
||||
|
||||
void SettingsView::onTemplateEditClicked() {
|
||||
int row = m_templateList->currentRow();
|
||||
if (row < 0) return;
|
||||
|
||||
QListWidgetItem *item = m_templateList->item(row);
|
||||
qint64 tmplId = item->data(Qt::UserRole).toLongLong();
|
||||
std::optional<Template> tmplOpt = TemplateDao::findById(tmplId);
|
||||
if (!tmplOpt.has_value()) return;
|
||||
|
||||
TemplateEditorDialog dlg(*tmplOpt, -1, this);
|
||||
connect(&dlg, &TemplateEditorDialog::templateSaved, this, [this](const Template& tmpl) {
|
||||
emit templateEditRequested(tmpl);
|
||||
});
|
||||
dlg.exec();
|
||||
}
|
||||
|
||||
void SettingsView::onTemplateDeleteClicked() {
|
||||
int row = m_templateList->currentRow();
|
||||
if (row < 0) return;
|
||||
|
||||
QListWidgetItem *item = m_templateList->item(row);
|
||||
qint64 tmplId = item->data(Qt::UserRole).toLongLong();
|
||||
|
||||
QMessageBox msgBox(this);
|
||||
msgBox.setWindowTitle(tr("Delete Template"));
|
||||
msgBox.setText(tr("Are you sure you want to delete this template?"));
|
||||
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
||||
msgBox.setDefaultButton(QMessageBox::No);
|
||||
if (msgBox.exec() == QMessageBox::Yes) {
|
||||
emit templateDeleteRequested(tmplId);
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsView::loadTemplates() {
|
||||
m_templateList->clear();
|
||||
|
||||
// Load global templates
|
||||
QVector<Template> globalTmpls = TemplateDao::findGlobal();
|
||||
for (const Template &tmpl : globalTmpls) {
|
||||
QListWidgetItem *item = new QListWidgetItem(templateToString(tmpl));
|
||||
item->setData(Qt::UserRole, tmpl.id);
|
||||
if (tmpl.isDefault) {
|
||||
item->setFont(QFont("Segoe UI", 9, QFont::Bold));
|
||||
}
|
||||
m_templateList->addItem(item);
|
||||
}
|
||||
|
||||
// Load account-specific templates
|
||||
if (m_selectedAccountId > 0) {
|
||||
QVector<Template> accountTmpls = TemplateDao::findByAccount(m_selectedAccountId);
|
||||
for (const Template &tmpl : accountTmpls) {
|
||||
QListWidgetItem *item = new QListWidgetItem(templateToString(tmpl));
|
||||
item->setData(Qt::UserRole, tmpl.id);
|
||||
if (tmpl.isDefault) {
|
||||
item->setFont(QFont("Segoe UI", 9, QFont::Bold));
|
||||
}
|
||||
m_templateList->addItem(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString SettingsView::templateToString(const Template &tmpl) const {
|
||||
QString status = tmpl.isDefault ? "★ " : "";
|
||||
return QString("%1%2 (%3 vars)").arg(status, tmpl.name, QString::number(tmpl.variables.size()));
|
||||
}
|
||||
|
||||
// ========== Signature Slots ==========
|
||||
void SettingsView::onSignatureSelectionChanged() {
|
||||
bool hasSelection = m_signatureList->currentRow() >= 0;
|
||||
m_signatureEditBtn->setEnabled(hasSelection);
|
||||
m_signatureDeleteBtn->setEnabled(hasSelection);
|
||||
}
|
||||
|
||||
void SettingsView::onSignatureAddClicked() {
|
||||
SignatureEditorDialog dlg(QString(), this);
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
Signature sig;
|
||||
sig.name = dlg.getSignatureName();
|
||||
sig.html = dlg.getSignatureHtml();
|
||||
sig.accountId = -1; // Global by default
|
||||
emit signatureAddRequested(sig);
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsView::onSignatureEditClicked() {
|
||||
int row = m_signatureList->currentRow();
|
||||
if (row < 0) return;
|
||||
|
||||
QListWidgetItem *item = m_signatureList->item(row);
|
||||
qint64 sigId = item->data(Qt::UserRole).toLongLong();
|
||||
|
||||
std::optional<Signature> sigOpt = SignatureDao::findById(sigId);
|
||||
if (!sigOpt.has_value()) return;
|
||||
|
||||
Signature sig = *sigOpt;
|
||||
|
||||
SignatureEditorDialog dlg(sig.html, this);
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
sig.name = dlg.getSignatureName();
|
||||
sig.html = dlg.getSignatureHtml();
|
||||
emit signatureEditRequested(sig);
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsView::onSignatureDeleteClicked() {
|
||||
int row = m_signatureList->currentRow();
|
||||
if (row < 0) return;
|
||||
|
||||
QListWidgetItem *item = m_signatureList->item(row);
|
||||
qint64 sigId = item->data(Qt::UserRole).toLongLong();
|
||||
|
||||
QMessageBox msgBox(this);
|
||||
msgBox.setWindowTitle(tr("Delete Signature"));
|
||||
msgBox.setText(tr("Are you sure you want to delete this signature?"));
|
||||
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
||||
msgBox.setDefaultButton(QMessageBox::No);
|
||||
if (msgBox.exec() == QMessageBox::Yes) {
|
||||
emit signatureDeleteRequested(sigId);
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsView::loadSignatures() {
|
||||
m_signatureList->clear();
|
||||
|
||||
// Load global signatures
|
||||
QVector<Signature> globalSigs = SignatureDao::findGlobal();
|
||||
for (const Signature &sig : globalSigs) {
|
||||
QListWidgetItem *item = new QListWidgetItem(sig.name);
|
||||
item->setData(Qt::UserRole, sig.id);
|
||||
m_signatureList->addItem(item);
|
||||
}
|
||||
|
||||
// Load account-specific signatures
|
||||
if (m_selectedAccountId > 0) {
|
||||
QVector<Signature> accountSigs = SignatureDao::findByAccount(m_selectedAccountId);
|
||||
for (const Signature &sig : accountSigs) {
|
||||
QListWidgetItem *item = new QListWidgetItem(sig.name);
|
||||
item->setData(Qt::UserRole, sig.id);
|
||||
m_signatureList->addItem(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString SettingsView::signatureToString(const Signature &sig) const {
|
||||
return sig.name;
|
||||
}
|
||||
@@ -12,7 +12,12 @@
|
||||
#include <QRadioButton>
|
||||
#include <QButtonGroup>
|
||||
#include <QVariant>
|
||||
#include <QTreeWidget>
|
||||
#include <QLineEdit>
|
||||
#include <QDialog>
|
||||
#include <QColorDialog>
|
||||
#include "services/accountservice.h"
|
||||
#include "db/dao/common_structs.h"
|
||||
|
||||
class SettingsView : public QWidget {
|
||||
Q_OBJECT
|
||||
@@ -29,6 +34,26 @@ signals:
|
||||
void accountDeleteRequested(int accountId);
|
||||
void themeChanged(const QString &theme);
|
||||
void settingChanged(const QString &key, const QVariant &value);
|
||||
|
||||
// Categories
|
||||
void categoryAddRequested(const Category &cat);
|
||||
void categoryEditRequested(const Category &cat);
|
||||
void categoryDeleteRequested(qint64 categoryId);
|
||||
|
||||
// Rules
|
||||
void ruleAddRequested(const Rule &rule);
|
||||
void ruleEditRequested(const Rule &rule);
|
||||
void ruleDeleteRequested(qint64 ruleId);
|
||||
|
||||
// Templates
|
||||
void templateAddRequested(const Template &tmpl);
|
||||
void templateEditRequested(const Template &tmpl);
|
||||
void templateDeleteRequested(qint64 templateId);
|
||||
|
||||
// Signatures
|
||||
void signatureAddRequested(const Signature &sig);
|
||||
void signatureEditRequested(const Signature &sig);
|
||||
void signatureDeleteRequested(qint64 signatureId);
|
||||
|
||||
private slots:
|
||||
void onAccountListChanged();
|
||||
@@ -36,12 +61,58 @@ private slots:
|
||||
void onEditClicked();
|
||||
void onDeleteClicked();
|
||||
void onAccountSelectionChanged();
|
||||
|
||||
// Categories
|
||||
void onCategorySelectionChanged();
|
||||
void onCategoryAddClicked();
|
||||
void onCategoryEditClicked();
|
||||
void onCategoryDeleteClicked();
|
||||
|
||||
// Rules
|
||||
void onRuleSelectionChanged();
|
||||
void onRuleAddClicked();
|
||||
void onRuleEditClicked();
|
||||
void onRuleDeleteClicked();
|
||||
void onRuleToggleEnabled();
|
||||
|
||||
// Templates
|
||||
void onTemplateSelectionChanged();
|
||||
void onTemplateAddClicked();
|
||||
void onTemplateEditClicked();
|
||||
void onTemplateDeleteClicked();
|
||||
|
||||
// Signatures
|
||||
void onSignatureSelectionChanged();
|
||||
void onSignatureAddClicked();
|
||||
void onSignatureEditClicked();
|
||||
void onSignatureDeleteClicked();
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
QWidget* createAccountsTab();
|
||||
QWidget* createGeneralTab();
|
||||
QWidget* createAppearanceTab();
|
||||
QWidget* createCategoriesTab();
|
||||
QWidget* createRulesTab();
|
||||
QWidget* createTemplatesTab();
|
||||
QWidget* createSignaturesTab();
|
||||
|
||||
// Category helpers
|
||||
void loadCategories();
|
||||
void refreshCategoryTree();
|
||||
QTreeWidgetItem* createCategoryTreeItem(const Category &cat, QTreeWidgetItem *parent = nullptr);
|
||||
|
||||
// Rule helpers
|
||||
void loadRules();
|
||||
QString ruleToString(const Rule &rule) const;
|
||||
|
||||
// Template helpers
|
||||
void loadTemplates();
|
||||
QString templateToString(const Template &tmpl) const;
|
||||
|
||||
// Signature helpers
|
||||
void loadSignatures();
|
||||
QString signatureToString(const Signature &sig) const;
|
||||
|
||||
QTabWidget *m_tabWidget;
|
||||
QListWidget *m_accountList;
|
||||
@@ -53,4 +124,29 @@ private:
|
||||
|
||||
AccountService *m_accountService;
|
||||
int m_selectedAccountId;
|
||||
|
||||
// Categories
|
||||
QTreeWidget *m_categoryTree;
|
||||
QPushButton *m_categoryAddBtn;
|
||||
QPushButton *m_categoryEditBtn;
|
||||
QPushButton *m_categoryDeleteBtn;
|
||||
|
||||
// Rules
|
||||
QListWidget *m_ruleList;
|
||||
QPushButton *m_ruleAddBtn;
|
||||
QPushButton *m_ruleEditBtn;
|
||||
QPushButton *m_ruleDeleteBtn;
|
||||
QPushButton *m_ruleToggleBtn;
|
||||
|
||||
// Templates
|
||||
QListWidget *m_templateList;
|
||||
QPushButton *m_templateAddBtn;
|
||||
QPushButton *m_templateEditBtn;
|
||||
QPushButton *m_templateDeleteBtn;
|
||||
|
||||
// Signatures
|
||||
QListWidget *m_signatureList;
|
||||
QPushButton *m_signatureAddBtn;
|
||||
QPushButton *m_signatureEditBtn;
|
||||
QPushButton *m_signatureDeleteBtn;
|
||||
};
|
||||
Reference in New Issue
Block a user