298 lines
12 KiB
C++
298 lines
12 KiB
C++
#include "rulesmanagerdialog.h"
|
|||
|
|
#include "ruleditordialog.h"
|
||
|
|
#include "db/dao/ruledao.h"
|
||
|
|
#include "db/dao/categorydao.h"
|
||
|
|
#include "db/dao/folderdao.h"
|
||
|
|
#include "services/rulesengine.h"
|
||
|
|
#include "services/accountservice.h"
|
||
|
|
#include <QHeaderView>
|
||
|
|
#include <QMessageBox>
|
||
|
|
#include <QInputDialog>
|
||
|
|
#include <QFileDialog>
|
||
|
|
#include <QJsonDocument>
|
||
|
|
#include <QJsonObject>
|
||
|
|
#include <QLabel>
|
||
|
|
#include <QMenu>
|
||
|
|
#include <QSqlDatabase>
|
||
|
|
#include <QSqlQuery>
|
||
|
|
#include <QDebug>
|
||
|
|
|
||
|
|
RulesManagerDialog::RulesManagerDialog(qint64 accountId, QWidget *parent)
|
||
|
|
: QDialog(parent), m_accountId(accountId)
|
||
|
|
{
|
||
|
|
setWindowTitle(m_accountId >= 0 ? tr("Reglas de la cuenta") : tr("Reglas globales"));
|
||
|
|
resize(900, 600);
|
||
|
|
setupUI();
|
||
|
|
loadRules();
|
||
|
|
}
|
||
|
|
|
||
|
|
void RulesManagerDialog::setupUI()
|
||
|
|
{
|
||
|
|
QVBoxLayout* mainLayout = new QVBoxLayout(this);
|
||
|
|
|
||
|
|
// Toolbar
|
||
|
|
QHBoxLayout* toolLayout = new QHBoxLayout();
|
||
|
|
m_btnNew = new QPushButton(tr("Nueva regla"));
|
||
|
|
m_btnRun = new QPushButton(tr("Ejecutar ahora"));
|
||
|
|
m_btnImport = new QPushButton(tr("Importar"));
|
||
|
|
m_btnExport = new QPushButton(tr("Exportar"));
|
||
|
|
m_btnClose = new QPushButton(tr("Cerrar"));
|
||
|
|
|
||
|
|
m_btnNew->setStyleSheet("QPushButton { background: #1976D2; color: white; font-weight: bold; padding: 6px 12px; border-radius: 4px; } QPushButton:hover { background: #1565C0; }");
|
||
|
|
m_btnRun->setStyleSheet("QPushButton { background: #4CAF50; color: white; padding: 6px 12px; border-radius: 4px; } QPushButton:hover { background: #43A047; }");
|
||
|
|
|
||
|
|
toolLayout->addWidget(m_btnNew);
|
||
|
|
toolLayout->addWidget(m_btnRun);
|
||
|
|
toolLayout->addStretch();
|
||
|
|
toolLayout->addWidget(m_btnImport);
|
||
|
|
toolLayout->addWidget(m_btnExport);
|
||
|
|
toolLayout->addWidget(m_btnClose);
|
||
|
|
mainLayout->addLayout(toolLayout);
|
||
|
|
|
||
|
|
// Tree widget
|
||
|
|
m_tree = new QTreeWidget();
|
||
|
|
m_tree->setHeaderLabels({tr("Activa"), tr("Nombre"), tr("Prioridad"), tr("Condiciones"), tr("Acciones"), tr("Cuenta"), tr("Última ejecución"), tr("Veces")});
|
||
|
|
m_tree->setAlternatingRowColors(true);
|
||
|
|
m_tree->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||
|
|
m_tree->setSelectionMode(QAbstractItemView::SingleSelection);
|
||
|
|
m_tree->setContextMenuPolicy(Qt::CustomContextMenu);
|
||
|
|
m_tree->header()->setStretchLastSection(false);
|
||
|
|
m_tree->header()->setSectionResizeMode(1, QHeaderView::Stretch);
|
||
|
|
m_tree->setColumnWidth(0, 60);
|
||
|
|
m_tree->setColumnWidth(2, 80);
|
||
|
|
m_tree->setColumnWidth(3, 200);
|
||
|
|
m_tree->setColumnWidth(4, 200);
|
||
|
|
m_tree->setColumnWidth(5, 100);
|
||
|
|
m_tree->setColumnWidth(6, 140);
|
||
|
|
m_tree->setColumnWidth(7, 60);
|
||
|
|
|
||
|
|
connect(m_tree, &QTreeWidget::customContextMenuRequested, this, [this](const QPoint& pos) {
|
||
|
|
QTreeWidgetItem* item = m_tree->itemAt(pos);
|
||
|
|
if (!item) return;
|
||
|
|
|
||
|
|
QMenu menu(this);
|
||
|
|
QAction* editAct = menu.addAction(tr("Editar"), [this, item]() { onEditRule(item); });
|
||
|
|
QAction* delAct = menu.addAction(tr("Eliminar"), [this, item]() { onDeleteRule(item); });
|
||
|
|
menu.addSeparator();
|
||
|
|
QAction* toggleAct = menu.addAction(item->checkState(0) == Qt::Checked ? tr("Desactivar") : tr("Activar"), [this, item]() { onToggleEnabled(item); });
|
||
|
|
menu.addSeparator();
|
||
|
|
QAction* moveUpAct = menu.addAction(tr("Subir prioridad"), [this, item]() {
|
||
|
|
int idx = m_tree->indexOfTopLevelItem(item);
|
||
|
|
if (idx > 0) {
|
||
|
|
QTreeWidgetItem* prev = m_tree->takeTopLevelItem(idx);
|
||
|
|
m_tree->insertTopLevelItem(idx - 1, prev);
|
||
|
|
// TODO: update priority in DB
|
||
|
|
}
|
||
|
|
});
|
||
|
|
QAction* moveDownAct = menu.addAction(tr("Bajar prioridad"), [this, item]() {
|
||
|
|
int idx = m_tree->indexOfTopLevelItem(item);
|
||
|
|
if (idx < m_tree->topLevelItemCount() - 1) {
|
||
|
|
QTreeWidgetItem* next = m_tree->takeTopLevelItem(idx + 1);
|
||
|
|
m_tree->insertTopLevelItem(idx + 1, item);
|
||
|
|
// TODO: update priority in DB
|
||
|
|
}
|
||
|
|
});
|
||
|
|
menu.exec(m_tree->viewport()->mapToGlobal(pos));
|
||
|
|
});
|
||
|
|
|
||
|
|
connect(m_tree, &QTreeWidget::itemDoubleClicked, this, [this](QTreeWidgetItem* item) { onEditRule(item); });
|
||
|
|
|
||
|
|
mainLayout->addWidget(m_tree, 1);
|
||
|
|
|
||
|
|
// Status
|
||
|
|
QLabel* statusLabel = new QLabel(tr("Doble click para editar. Click derecho para más opciones."));
|
||
|
|
statusLabel->setStyleSheet("color: #666; font-size: 11px; padding: 4px;");
|
||
|
|
mainLayout->addWidget(statusLabel);
|
||
|
|
|
||
|
|
// Connect buttons
|
||
|
|
connect(m_btnNew, &QPushButton::clicked, this, &RulesManagerDialog::onNewRule);
|
||
|
|
connect(m_btnRun, &QPushButton::clicked, this, &RulesManagerDialog::onRunRules);
|
||
|
|
connect(m_btnImport, &QPushButton::clicked, this, [this]() { onImportExport(true); });
|
||
|
|
connect(m_btnExport, &QPushButton::clicked, this, [this]() { onImportExport(false); });
|
||
|
|
connect(m_btnClose, &QPushButton::clicked, this, &QDialog::accept);
|
||
|
|
}
|
||
|
|
|
||
|
|
void RulesManagerDialog::loadRules()
|
||
|
|
{
|
||
|
|
m_tree->clear();
|
||
|
|
|
||
|
|
QVector<Rule> rules = RuleDao::findByAccount(m_accountId);
|
||
|
|
|
||
|
|
for (const Rule& rule : rules) {
|
||
|
|
addRuleToTree(rule);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void RulesManagerDialog::addRuleToTree(const Rule& rule)
|
||
|
|
{
|
||
|
|
QTreeWidgetItem* item = new QTreeWidgetItem(m_tree);
|
||
|
|
item->setData(0, Qt::UserRole, rule.id);
|
||
|
|
item->setCheckState(0, rule.enabled ? Qt::Checked : Qt::Unchecked);
|
||
|
|
item->setText(1, rule.name);
|
||
|
|
item->setText(2, QString::number(rule.priority));
|
||
|
|
|
||
|
|
// Conditions summary
|
||
|
|
QStringList condTexts;
|
||
|
|
for (const RuleCondition& cond : rule.conditions) {
|
||
|
|
QString fieldName;
|
||
|
|
switch (cond.field) {
|
||
|
|
case RuleCondition::From: fieldName = "De"; break;
|
||
|
|
case RuleCondition::To: fieldName = "Para"; break;
|
||
|
|
case RuleCondition::Cc: fieldName = "CC"; break;
|
||
|
|
case RuleCondition::Subject: fieldName = "Asunto"; break;
|
||
|
|
case RuleCondition::Body: fieldName = "Cuerpo"; break;
|
||
|
|
case RuleCondition::HasAttachment: fieldName = "Tiene adjuntos"; break;
|
||
|
|
case RuleCondition::Size: fieldName = "Tamaño"; break;
|
||
|
|
case RuleCondition::Date: fieldName = "Fecha"; break;
|
||
|
|
case RuleCondition::Flagged: fieldName = "Marcado"; break;
|
||
|
|
case RuleCondition::Read: fieldName = "Leído"; break;
|
||
|
|
}
|
||
|
|
QString opName;
|
||
|
|
switch (cond.op) {
|
||
|
|
case RuleCondition::Contains: opName = "contiene"; break;
|
||
|
|
case RuleCondition::NotContains: opName = "no contiene"; break;
|
||
|
|
case RuleCondition::Equals: opName = "es"; break;
|
||
|
|
case RuleCondition::NotEquals: opName = "no es"; break;
|
||
|
|
case RuleCondition::StartsWith: opName = "empieza por"; break;
|
||
|
|
case RuleCondition::EndsWith: opName = "termina en"; break;
|
||
|
|
case RuleCondition::Regex: opName = "regex"; break;
|
||
|
|
case RuleCondition::GreaterThan: opName = ">"; break;
|
||
|
|
case RuleCondition::LessThan: opName = "<"; break;
|
||
|
|
case RuleCondition::Before: opName = "antes de"; break;
|
||
|
|
case RuleCondition::After: opName = "después de"; break;
|
||
|
|
}
|
||
|
|
condTexts.append(QString("%1 %2 \"%3\"").arg(fieldName, opName, cond.value));
|
||
|
|
}
|
||
|
|
item->setText(3, condTexts.join(rule.matchAll ? " Y " : " O "));
|
||
|
|
|
||
|
|
// Actions summary
|
||
|
|
QStringList actTexts;
|
||
|
|
for (const RuleAction& act : rule.actions) {
|
||
|
|
QString actName;
|
||
|
|
switch (act.type) {
|
||
|
|
case RuleAction::MoveToFolder: actName = QString("Mover a carpeta %1").arg(act.value); break;
|
||
|
|
case RuleAction::MarkAsRead: actName = QString("Marcar como %1").arg(act.value == "false" ? "no leído" : "leído"); break;
|
||
|
|
case RuleAction::MarkAsFlagged: actName = QString("Marcar como %1").arg(act.value == "false" ? "no marcado" : "marcado"); break;
|
||
|
|
case RuleAction::Delete: actName = "Eliminar"; break;
|
||
|
|
case RuleAction::AssignCategory: actName = QString("Categoría %1").arg(act.value); break;
|
||
|
|
case RuleAction::RemoveCategory: actName = QString("Quitar categoría %1").arg(act.value); break;
|
||
|
|
case RuleAction::ForwardTo: actName = QString("Reenviar a %1").arg(act.value); break;
|
||
|
|
case RuleAction::SetPriority: actName = QString("Prioridad %1").arg(act.value); break;
|
||
|
|
case RuleAction::StopProcessing: actName = "Detener procesamiento"; break;
|
||
|
|
}
|
||
|
|
actTexts.append(actName);
|
||
|
|
}
|
||
|
|
item->setText(4, actTexts.join(", "));
|
||
|
|
|
||
|
|
item->setText(5, rule.isGlobal() ? tr("Global") : QString::number(rule.accountId));
|
||
|
|
item->setText(6, rule.lastRun.isValid() ? rule.lastRun.toString("dd/MM/yyyy hh:mm") : tr("Nunca"));
|
||
|
|
item->setText(7, QString::number(rule.runCount));
|
||
|
|
|
||
|
|
if (!rule.enabled) {
|
||
|
|
for (int i = 0; i < m_tree->columnCount(); ++i) {
|
||
|
|
item->setForeground(i, QBrush(QColor("#999")));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Rule RulesManagerDialog::itemToRule(QTreeWidgetItem* item) const
|
||
|
|
{
|
||
|
|
qint64 id = item->data(0, Qt::UserRole).toLongLong();
|
||
|
|
auto ruleOpt = RuleDao::findById(id);
|
||
|
|
return ruleOpt.value_or(Rule());
|
||
|
|
}
|
||
|
|
|
||
|
|
void RulesManagerDialog::onNewRule()
|
||
|
|
{
|
||
|
|
RuleEditorDialog dlg(Rule(), m_accountId, this);
|
||
|
|
connect(&dlg, &RuleEditorDialog::ruleSaved, this, &RulesManagerDialog::onRuleSaved);
|
||
|
|
dlg.exec();
|
||
|
|
}
|
||
|
|
|
||
|
|
void RulesManagerDialog::onEditRule(QTreeWidgetItem* item)
|
||
|
|
{
|
||
|
|
Rule rule = itemToRule(item);
|
||
|
|
if (!rule.isValid()) return;
|
||
|
|
|
||
|
|
RuleEditorDialog dlg(rule, m_accountId, this);
|
||
|
|
connect(&dlg, &RuleEditorDialog::ruleSaved, this, &RulesManagerDialog::onRuleSaved);
|
||
|
|
dlg.exec();
|
||
|
|
}
|
||
|
|
|
||
|
|
void RulesManagerDialog::onRuleSaved(const Rule& rule)
|
||
|
|
{
|
||
|
|
refresh();
|
||
|
|
emit rulesChanged();
|
||
|
|
}
|
||
|
|
|
||
|
|
void RulesManagerDialog::onDeleteRule(QTreeWidgetItem* item)
|
||
|
|
{
|
||
|
|
Rule rule = itemToRule(item);
|
||
|
|
if (!rule.isValid()) return;
|
||
|
|
|
||
|
|
if (QMessageBox::question(this, tr("Eliminar regla"),
|
||
|
|
tr("¿Eliminar la regla \"%1\"?").arg(rule.name),
|
||
|
|
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
|
||
|
|
if (RuleDao::remove(rule.id)) {
|
||
|
|
refresh();
|
||
|
|
emit rulesChanged();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void RulesManagerDialog::onToggleEnabled(QTreeWidgetItem* item)
|
||
|
|
{
|
||
|
|
Rule rule = itemToRule(item);
|
||
|
|
if (!rule.isValid()) return;
|
||
|
|
|
||
|
|
bool newState = item->checkState(0) != Qt::Checked;
|
||
|
|
if (RuleDao::setEnabled(rule.id, newState)) {
|
||
|
|
item->setCheckState(0, newState ? Qt::Checked : Qt::Unchecked);
|
||
|
|
if (!newState) {
|
||
|
|
for (int i = 0; i < m_tree->columnCount(); ++i) {
|
||
|
|
item->setForeground(i, QBrush(QColor("#999")));
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
for (int i = 0; i < m_tree->columnCount(); ++i) {
|
||
|
|
item->setForeground(i, QBrush(QColor("#000")));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
emit rulesChanged();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void RulesManagerDialog::onRunRules()
|
||
|
|
{
|
||
|
|
// TODO: Get selected folder/account from main window
|
||
|
|
QMessageBox::information(this, tr("Ejecutar reglas"), tr("Seleccione una carpeta en la ventana principal y pulse 'Ejecutar reglas' en el menú de herramientas."));
|
||
|
|
}
|
||
|
|
|
||
|
|
void RulesManagerDialog::onImportExport(bool import)
|
||
|
|
{
|
||
|
|
if (import) {
|
||
|
|
QString file = QFileDialog::getOpenFileName(this, tr("Importar reglas"), QString(), tr("JSON (*.json)"));
|
||
|
|
if (file.isEmpty()) return;
|
||
|
|
|
||
|
|
// TODO: Parse JSON and create rules
|
||
|
|
QMessageBox::information(this, tr("Importar"), tr("Función de importación en desarrollo."));
|
||
|
|
} else {
|
||
|
|
QString file = QFileDialog::getSaveFileName(this, tr("Exportar reglas"), "rules.json", tr("JSON (*.json)"));
|
||
|
|
if (file.isEmpty()) return;
|
||
|
|
|
||
|
|
QVector<Rule> rules = RuleDao::findByAccount(m_accountId);
|
||
|
|
QJsonArray arr;
|
||
|
|
for (const Rule& rule : rules) arr.append(rule.toJson());
|
||
|
|
|
||
|
|
QJsonDocument doc(arr);
|
||
|
|
QFile f(file);
|
||
|
|
if (f.open(QIODevice::WriteOnly)) {
|
||
|
|
f.write(doc.toJson(QJsonDocument::Indented));
|
||
|
|
QMessageBox::information(this, tr("Exportar"), tr("Reglas exportadas a %1").arg(file));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void RulesManagerDialog::refresh()
|
||
|
|
{
|
||
|
|
loadRules();
|
||
|
|
}
|