diff --git a/CMakeLists.txt b/CMakeLists.txt
index b1c220f..d8cc9d7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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
diff --git a/icons.qrc b/icons.qrc
index a936e65..c1fc62b 100644
--- a/icons.qrc
+++ b/icons.qrc
@@ -1,21 +1,55 @@
+
resources/icons/app.svg
resources/icons/sync.svg
resources/icons/settings.svg
- resources/icons/minimize.svg
- resources/icons/maximize.svg
- resources/icons/restore.svg
- resources/icons/close.svg
- resources/icons/SVG/Outline/Essentional, UI/Trash Bin Minimalistic.svg
- resources/icons/SVG/Outline/Messages, Conversation/Paperclip.svg
- resources/icons/SVG/Bold Duotone/Messages, Conversation/Plain.svg
- resources/icons/SVG/Outline/Files/File Text.svg
- resources/icons/SVG/Bold/Text Formatting/Text Bold.svg
- resources/icons/SVG/Outline/Text Formatting/Text Italic.svg
- resources/icons/SVG/Outline/Text Formatting/Text Underline.svg
- resources/icons/SVG/Linear/Arrows Action/Square Top Down.svg
- resources/icons/SVG/Linear/Arrows Action/Square Top Up.svg
- resources/icons/SVG/Linear/Essentional, UI/Close Square.svg
+
+
+ resources/icons/SVG/Linear/Arrows Action/Minimize Square Minimalistic.svg
+ resources/icons/SVG/Linear/Arrows Action/Maximize Square Minimalistic.svg
+ resources/icons/SVG/Linear/Arrows Action/Maximize Square 2.svg
+ resources/icons/SVG/Linear/Essentional, UI/Close Square.svg
+
+
+ resources/icons/SVG/Outline/Messages, Conversation/Paperclip.svg
+ resources/icons/SVG/Bold Duotone/Messages, Conversation/Plain.svg
+ resources/icons/SVG/Linear/Arrows Action/Reply.svg
+ resources/icons/SVG/Linear/Arrows Action/Forward.svg
+ resources/icons/SVG/Linear/Essentional, UI/Trash Bin Minimalistic.svg
+ resources/icons/SVG/Linear/Design, Tools/Filters.svg
+ resources/icons/SVG/Linear/Time/Stopwatch Play.svg
+ resources/icons/SVG/Linear/Files/File Text.svg
+
+
+ resources/icons/SVG/Linear/Arrows Action/Square Top Down.svg
+ resources/icons/SVG/Linear/Arrows Action/Square Top Down.svg
+ resources/icons/SVG/Linear/Arrows Action/Import.svg
+ resources/icons/SVG/Linear/Arrows Action/Export.svg
+
+
+ resources/icons/SVG/Bold/Text Formatting/Text Bold.svg
+ resources/icons/SVG/Outline/Text Formatting/Text Italic.svg
+ resources/icons/SVG/Outline/Text Formatting/Text Underline.svg
+
+
+ resources/icons/SVG/Linear/Users/User.svg
+
+
+ resources/icons/SVG/Linear/Time/Calendar.svg
+ resources/icons/SVG/Linear/Time/Calendar Add.svg
+
+
+ resources/icons/SVG/Linear/Arrows Action/Square Top Down.svg
+ resources/icons/SVG/Linear/Arrows Action/Square Top Up.svg
+
+
+ resources/icons/SVG/Linear/Essentional, UI/Menu Dots.svg
+
+
+ resources/icons/SVG/Linear/Design, Tools/Palette.svg
+
+
+ resources/icons/SVG/Linear/Time/Calendar Add.svg
-
+
\ No newline at end of file
diff --git a/src/db/dao/categorydao.cpp b/src/db/dao/categorydao.cpp
index 3be40dd..fc9cce0 100644
--- a/src/db/dao/categorydao.cpp
+++ b/src/db/dao/categorydao.cpp
@@ -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 CategoryDao::findById(qint64 id)
@@ -74,19 +73,20 @@ std::optional 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 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 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 CategoryDao::findByAccount(qint64 accountId)
QVector 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 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 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 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 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 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 CategoryDao::categoriesForMail(qint64 mailCopyId)
+QVector 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 ids;
+ query.prepare("SELECT categoryId FROM MailCategory WHERE mailCopyId = :mailId");
+ query.bindValue(":mailId", mailId);
+
+ QVector catIds;
if (query.exec()) {
while (query.next()) {
- ids.append(query.value(0).toLongLong());
+ catIds.append(query.value("categoryId").toLongLong());
}
}
- return ids;
+ return catIds;
}
-QVector CategoryDao::fullCategoriesForMail(qint64 mailCopyId)
-{
- QVector ids = categoriesForMail(mailCopyId);
- QVector cats;
- for (qint64 id : ids) {
- if (auto cat = findById(id)) {
- cats.append(*cat);
- }
- }
- return cats;
-}
-
-bool CategoryDao::reorder(const QVector& idsInOrder)
+bool CategoryDao::reorder(const QVector& 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;
}
\ No newline at end of file
diff --git a/src/db/dao/categorydao.h b/src/db/dao/categorydao.h
index a510561..b4e7b1c 100644
--- a/src/db/dao/categorydao.h
+++ b/src/db/dao/categorydao.h
@@ -3,22 +3,7 @@
#include "../databasemanager.h"
#include
#include
-#include
-#include
-
-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 findById(qint64 id);
- static QVector findByAccount(qint64 accountId); // accountId = -1 for global
+ static QVector findByAccount(qint64 accountId);
static QVector findGlobal();
static QVector findAll();
static QVector findChildren(qint64 parentId);
- static bool assignToMail(qint64 mailCopyId, qint64 categoryId);
- static bool removeFromMail(qint64 mailCopyId, qint64 categoryId);
- static QVector categoriesForMail(qint64 mailCopyId);
- static QVector fullCategoriesForMail(qint64 mailCopyId);
- static bool reorder(const QVector& idsInOrder);
+ static bool assignToMail(qint64 mailId, qint64 categoryId);
+ static bool removeFromMail(qint64 mailId, qint64 categoryId);
+ static QVector categoriesForMail(qint64 mailId); // Returns category IDs
+ static bool reorder(const QVector& ids);
};
\ No newline at end of file
diff --git a/src/db/dao/common_structs.h b/src/db/dao/common_structs.h
new file mode 100644
index 0000000..3d7a182
--- /dev/null
+++ b/src/db/dao/common_structs.h
@@ -0,0 +1,126 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+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 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 conditions;
+ QVector 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);
+};
\ No newline at end of file
diff --git a/src/db/dao/ruledao.h b/src/db/dao/ruledao.h
index 94663f9..b43d12d 100644
--- a/src/db/dao/ruledao.h
+++ b/src/db/dao/ruledao.h
@@ -3,68 +3,7 @@
#include "../databasemanager.h"
#include
#include
-#include
-#include
-#include
-
-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 conditions;
- QVector 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 findById(qint64 id);
static QVector findByAccount(qint64 accountId); // accountId = -1 for global
static QVector findGlobal();
- static QVector findAllEnabled();
+ static QVector findAll();
+ static QVector findAllEnabled(); // only enabled rules
static bool setEnabled(qint64 id, bool enabled);
static bool updateLastRun(qint64 id, const QDateTime& when, qint64 runCount);
};
\ No newline at end of file
diff --git a/src/db/dao/signaturedao.cpp b/src/db/dao/signaturedao.cpp
new file mode 100644
index 0000000..08c9d91
--- /dev/null
+++ b/src/db/dao/signaturedao.cpp
@@ -0,0 +1,220 @@
+#include "signaturedao.h"
+#include
+#include
+#include
+#include
+
+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 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 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 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 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 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 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 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 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;
+}
\ No newline at end of file
diff --git a/src/db/dao/signaturedao.h b/src/db/dao/signaturedao.h
new file mode 100644
index 0000000..88a72e6
--- /dev/null
+++ b/src/db/dao/signaturedao.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "../databasemanager.h"
+#include
+#include
+#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 findById(qint64 id);
+ static QVector findByAccount(qint64 accountId);
+ static QVector findGlobal();
+ static QVector findAll();
+ static std::optional findDefault(qint64 accountId);
+ static bool setDefault(qint64 id, bool isDefault);
+};
\ No newline at end of file
diff --git a/src/db/dao/templatedao.h b/src/db/dao/templatedao.h
index 4d60f78..149d009 100644
--- a/src/db/dao/templatedao.h
+++ b/src/db/dao/templatedao.h
@@ -6,35 +6,7 @@
#include
#include
#include
-
-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 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
{
diff --git a/src/ui/readerview.cpp b/src/ui/readerview.cpp
index f5fea97..c42a87f 100644
--- a/src/ui/readerview.cpp
+++ b/src/ui/readerview.cpp
@@ -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; }");
diff --git a/src/ui/settingsview.cpp b/src/ui/settingsview.cpp
index 20da9f0..5538e60 100644
--- a/src/ui/settingsview.cpp
+++ b/src/ui/settingsview.cpp
@@ -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
#include
@@ -13,6 +21,19 @@
#include
#include
#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
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::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 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 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 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 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 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 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 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 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 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 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 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 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 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 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;
+}
\ No newline at end of file
diff --git a/src/ui/settingsview.h b/src/ui/settingsview.h
index afc459e..e80203e 100644
--- a/src/ui/settingsview.h
+++ b/src/ui/settingsview.h
@@ -12,7 +12,12 @@
#include
#include
#include
+#include
+#include
+#include
+#include
#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;
};
\ No newline at end of file