#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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include RuleEditorDialog::RuleEditorDialog(const Rule& rule, qint64 accountId, QWidget *parent) : QDialog(parent), m_rule(rule), m_accountId(accountId) { setWindowTitle(rule.isValid() ? tr("Editar regla") : tr("Nueva regla")); resize(800, 600); setupUI(); populateFromRule(); } void RuleEditorDialog::setupUI() { QVBoxLayout* mainLayout = new QVBoxLayout(this); // Form header QGroupBox* headerGroup = new QGroupBox(tr("Información básica")); QFormLayout* headerLayout = new QFormLayout(headerGroup); m_nameEdit = new QLineEdit(); m_nameEdit->setPlaceholderText(tr("Nombre de la regla")); headerLayout->addRow(tr("Nombre:"), m_nameEdit); m_descEdit = new QTextEdit(); m_descEdit->setMaximumHeight(60); m_descEdit->setPlaceholderText(tr("Descripción opcional")); headerLayout->addRow(tr("Descripción:"), m_descEdit); m_accountCombo = new QComboBox(); loadAccounts(); headerLayout->addRow(tr("Cuenta:"), m_accountCombo); QHBoxLayout* optionsLayout = new QHBoxLayout(); m_enabledCheck = new QCheckBox(tr("Activada")); m_enabledCheck->setChecked(true); m_matchAllCheck = new QCheckBox(tr("Todas las condiciones (Y)")); m_matchAllCheck->setChecked(true); m_matchAllCheck->setToolTip(tr("Desactivar = Cualquier condición (O)")); m_prioritySpin = new QSpinBox(); m_prioritySpin->setRange(1, 999); m_prioritySpin->setValue(100); m_prioritySpin->setToolTip(tr("Menor = mayor prioridad")); optionsLayout->addWidget(m_enabledCheck); optionsLayout->addWidget(m_matchAllCheck); optionsLayout->addStretch(); optionsLayout->addWidget(new QLabel(tr("Prioridad:"))); optionsLayout->addWidget(m_prioritySpin); headerLayout->addRow(optionsLayout); mainLayout->addWidget(headerGroup); // Conditions table QGroupBox* condGroup = new QGroupBox(tr("Condiciones")); QVBoxLayout* condLayout = new QVBoxLayout(condGroup); m_conditionsTable = new QTableWidget(); m_conditionsTable->setColumnCount(5); m_conditionsTable->setHorizontalHeaderLabels({tr("Campo"), tr("Operador"), tr("Valor"), tr("Mayúsculas"), ""}); m_conditionsTable->horizontalHeader()->setStretchLastSection(true); m_conditionsTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); m_conditionsTable->setSelectionBehavior(QAbstractItemView::SelectRows); m_conditionsTable->verticalHeader()->setVisible(false); condLayout->addWidget(m_conditionsTable); QHBoxLayout* condBtnLayout = new QHBoxLayout(); QPushButton* btnAddCond = new QPushButton(tr("+ Añadir condición")); btnAddCond->setStyleSheet("QPushButton { background: #1976D2; color: white; padding: 6px 12px; border-radius: 4px; }"); connect(btnAddCond, &QPushButton::clicked, this, &RuleEditorDialog::onAddCondition); QPushButton* btnRemCond = new QPushButton(tr("- Eliminar")); connect(btnRemCond, &QPushButton::clicked, this, &RuleEditorDialog::onRemoveCondition); condBtnLayout->addWidget(btnAddCond); condBtnLayout->addWidget(btnRemCond); condBtnLayout->addStretch(); condLayout->addLayout(condBtnLayout); mainLayout->addWidget(condGroup, 1); // Actions table QGroupBox* actGroup = new QGroupBox(tr("Acciones")); QVBoxLayout* actLayout = new QVBoxLayout(actGroup); m_actionsTable = new QTableWidget(); m_actionsTable->setColumnCount(4); m_actionsTable->setHorizontalHeaderLabels({tr("Tipo"), tr("Parámetro"), tr("Detalles"), ""}); m_actionsTable->horizontalHeader()->setStretchLastSection(true); m_actionsTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); m_actionsTable->setSelectionBehavior(QAbstractItemView::SelectRows); m_actionsTable->verticalHeader()->setVisible(false); actLayout->addWidget(m_actionsTable); QHBoxLayout* actBtnLayout = new QHBoxLayout(); QPushButton* btnAddAct = new QPushButton(tr("+ Añadir acción")); btnAddAct->setStyleSheet("QPushButton { background: #4CAF50; color: white; padding: 6px 12px; border-radius: 4px; }"); connect(btnAddAct, &QPushButton::clicked, this, &RuleEditorDialog::onAddAction); QPushButton* btnRemAct = new QPushButton(tr("- Eliminar")); connect(btnRemAct, &QPushButton::clicked, this, &RuleEditorDialog::onRemoveAction); actBtnLayout->addWidget(btnAddAct); actBtnLayout->addWidget(btnRemAct); actBtnLayout->addStretch(); actLayout->addLayout(actBtnLayout); mainLayout->addWidget(actGroup, 1); // Buttons QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttonBox, &QDialogButtonBox::accepted, this, &RuleEditorDialog::onAccept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); mainLayout->addWidget(buttonBox); } void RuleEditorDialog::loadAccounts() { m_accountCombo->clear(); m_accountCombo->addItem(tr("Global (todas las cuentas)"), -1); // Get accounts via AccountService (singleton doesn't have instance(), use parent's accountService) // For now, we'll just add the global option and let the main window handle account-specific rules if (m_accountId >= 0) { int idx = m_accountCombo->findData(m_accountId); if (idx >= 0) m_accountCombo->setCurrentIndex(idx); } } void RuleEditorDialog::populateFromRule() { if (!m_rule.isValid()) return; m_nameEdit->setText(m_rule.name); m_descEdit->setPlainText(m_rule.description); m_enabledCheck->setChecked(m_rule.enabled); m_matchAllCheck->setChecked(m_rule.matchAll); m_prioritySpin->setValue(m_rule.priority); int idx = m_accountCombo->findData(m_rule.accountId >= 0 ? m_rule.accountId : -1); if (idx >= 0) m_accountCombo->setCurrentIndex(idx); for (const RuleCondition& cond : m_rule.conditions) { int row = m_conditionsTable->rowCount(); m_conditionsTable->insertRow(row); setupConditionRow(row, cond); } for (const RuleAction& act : m_rule.actions) { int row = m_actionsTable->rowCount(); m_actionsTable->insertRow(row); setupActionRow(row, act); } } void RuleEditorDialog::setupConditionRow(int row, const RuleCondition& cond) { // Field combo QComboBox* fieldCombo = new QComboBox(); fieldCombo->addItem(tr("De"), RuleCondition::From); fieldCombo->addItem(tr("Para"), RuleCondition::To); fieldCombo->addItem(tr("CC"), RuleCondition::Cc); fieldCombo->addItem(tr("Asunto"), RuleCondition::Subject); fieldCombo->addItem(tr("Cuerpo"), RuleCondition::Body); fieldCombo->addItem(tr("Tiene adjuntos"), RuleCondition::HasAttachment); fieldCombo->addItem(tr("Tamaño"), RuleCondition::Size); fieldCombo->addItem(tr("Fecha"), RuleCondition::Date); fieldCombo->addItem(tr("Marcado"), RuleCondition::Flagged); fieldCombo->addItem(tr("Leído"), RuleCondition::Read); fieldCombo->setCurrentIndex(fieldCombo->findData(cond.field)); connect(fieldCombo, QOverload::of(&QComboBox::currentIndexChanged), [this, row](int) { onConditionFieldChanged(row); }); m_conditionsTable->setCellWidget(row, 0, fieldCombo); // Operator combo QComboBox* opCombo = new QComboBox(); opCombo->addItem(tr("Contiene"), RuleCondition::Contains); opCombo->addItem(tr("No contiene"), RuleCondition::NotContains); opCombo->addItem(tr("Es igual a"), RuleCondition::Equals); opCombo->addItem(tr("No es igual a"), RuleCondition::NotEquals); opCombo->addItem(tr("Empieza por"), RuleCondition::StartsWith); opCombo->addItem(tr("Termina en"), RuleCondition::EndsWith); opCombo->addItem(tr("Expresión regular"), RuleCondition::Regex); opCombo->addItem(tr("Mayor que"), RuleCondition::GreaterThan); opCombo->addItem(tr("Menor que"), RuleCondition::LessThan); opCombo->addItem(tr("Antes de"), RuleCondition::Before); opCombo->addItem(tr("Después de"), RuleCondition::After); opCombo->setCurrentIndex(opCombo->findData(cond.op)); m_conditionsTable->setCellWidget(row, 1, opCombo); // Value edit QLineEdit* valueEdit = new QLineEdit(cond.value); m_conditionsTable->setCellWidget(row, 2, valueEdit); // Case sensitive checkbox QCheckBox* caseCheck = new QCheckBox(); caseCheck->setChecked(cond.caseSensitive); caseCheck->setStyleSheet("QCheckBox { margin-left: 50%; }"); m_conditionsTable->setCellWidget(row, 3, caseCheck); // Remove button QPushButton* removeBtn = new QPushButton("✕"); removeBtn->setFixedSize(24, 24); removeBtn->setToolTip(tr("Eliminar condición")); removeBtn->setStyleSheet("QPushButton { background: transparent; color: #d32f2f; border: none; font-weight: bold; } QPushButton:hover { background: #fdeaea; border-radius: 3px; }"); connect(removeBtn, &QPushButton::clicked, [this, row]() { m_conditionsTable->removeRow(row); }); m_conditionsTable->setCellWidget(row, 4, removeBtn); onConditionFieldChanged(row); } void RuleEditorDialog::onConditionFieldChanged(int row) { QComboBox* fieldCombo = qobject_cast(m_conditionsTable->cellWidget(row, 0)); QComboBox* opCombo = qobject_cast(m_conditionsTable->cellWidget(row, 1)); QLineEdit* valueEdit = qobject_cast(m_conditionsTable->cellWidget(row, 2)); QCheckBox* caseCheck = qobject_cast(m_conditionsTable->cellWidget(row, 3)); if (!fieldCombo || !opCombo) return; RuleCondition::Field field = static_cast(fieldCombo->currentData().toInt()); // Clear and repopulate operators based on field type opCombo->clear(); bool isTextField = (field == RuleCondition::From || field == RuleCondition::To || field == RuleCondition::Cc || field == RuleCondition::Subject || field == RuleCondition::Body); bool isBooleanField = (field == RuleCondition::HasAttachment || field == RuleCondition::Flagged || field == RuleCondition::Read); bool isNumericField = (field == RuleCondition::Size); bool isDateField = (field == RuleCondition::Date); if (isTextField) { opCombo->addItem(tr("Contiene"), RuleCondition::Contains); opCombo->addItem(tr("No contiene"), RuleCondition::NotContains); opCombo->addItem(tr("Es igual a"), RuleCondition::Equals); opCombo->addItem(tr("No es igual a"), RuleCondition::NotEquals); opCombo->addItem(tr("Empieza por"), RuleCondition::StartsWith); opCombo->addItem(tr("Termina en"), RuleCondition::EndsWith); opCombo->addItem(tr("Expresión regular"), RuleCondition::Regex); valueEdit->setPlaceholderText(tr("Texto a buscar...")); caseCheck->setVisible(true); } else if (isBooleanField) { opCombo->addItem(tr("Es"), RuleCondition::Equals); opCombo->addItem(tr("No es"), RuleCondition::NotEquals); valueEdit->setPlaceholderText("true/false"); valueEdit->setText("true"); caseCheck->setVisible(false); } else if (isNumericField) { opCombo->addItem(tr("Mayor que"), RuleCondition::GreaterThan); opCombo->addItem(tr("Menor que"), RuleCondition::LessThan); opCombo->addItem(tr("Es igual a"), RuleCondition::Equals); valueEdit->setPlaceholderText("Tamaño en bytes"); caseCheck->setVisible(false); } else if (isDateField) { opCombo->addItem(tr("Antes de"), RuleCondition::Before); opCombo->addItem(tr("Después de"), RuleCondition::After); opCombo->addItem(tr("Es igual a"), RuleCondition::Equals); valueEdit->setPlaceholderText("YYYY-MM-DD"); caseCheck->setVisible(false); } } void RuleEditorDialog::setupActionRow(int row, const RuleAction& act) { // Type combo QComboBox* typeCombo = new QComboBox(); typeCombo->addItem(tr("Mover a carpeta"), RuleAction::MoveToFolder); typeCombo->addItem(tr("Marcar como leído/no leído"), RuleAction::MarkAsRead); typeCombo->addItem(tr("Marcar/Desmarcar"), RuleAction::MarkAsFlagged); typeCombo->addItem(tr("Eliminar"), RuleAction::Delete); typeCombo->addItem(tr("Asignar categoría"), RuleAction::AssignCategory); typeCombo->addItem(tr("Quitar categoría"), RuleAction::RemoveCategory); typeCombo->addItem(tr("Reenviar a"), RuleAction::ForwardTo); typeCombo->addItem(tr("Establecer prioridad"), RuleAction::SetPriority); typeCombo->addItem(tr("Detener procesamiento"), RuleAction::StopProcessing); typeCombo->setCurrentIndex(typeCombo->findData(act.type)); connect(typeCombo, QOverload::of(&QComboBox::currentIndexChanged), [this, row](int) { onActionTypeChanged(row); }); m_actionsTable->setCellWidget(row, 0, typeCombo); // Parameter edit (dynamic based on type) QWidget* paramWidget = createParameterWidget(act); m_actionsTable->setCellWidget(row, 1, paramWidget); // Details label QLabel* detailsLabel = new QLabel(); detailsLabel->setStyleSheet("color: #666; font-size: 11px;"); updateActionDetails(act, detailsLabel); m_actionsTable->setCellWidget(row, 2, detailsLabel); // Remove button QPushButton* removeBtn = new QPushButton("✕"); removeBtn->setFixedSize(24, 24); removeBtn->setToolTip(tr("Eliminar acción")); removeBtn->setStyleSheet("QPushButton { background: transparent; color: #d32f2f; border: none; font-weight: bold; } QPushButton:hover { background: #fdeaea; border-radius: 3px; }"); connect(removeBtn, &QPushButton::clicked, [this, row]() { m_actionsTable->removeRow(row); }); m_actionsTable->setCellWidget(row, 3, removeBtn); } QWidget* RuleEditorDialog::createParameterWidget(const RuleAction& act) { QWidget* container = new QWidget(); QHBoxLayout* layout = new QHBoxLayout(container); layout->setContentsMargins(0, 0, 0, 0); switch (act.type) { case RuleAction::MoveToFolder: { QComboBox* folderCombo = new QComboBox(); folderCombo->addItem(tr("Seleccionar carpeta..."), -1); // Load folders from database QSqlDatabase db = DatabaseManager::instance().database(); QSqlQuery query(db); query.exec("SELECT id, name FROM Folder ORDER BY name"); while (query.next()) { folderCombo->addItem(query.value(1).toString(), query.value(0).toLongLong()); } if (act.value.toLongLong() > 0) { int idx = folderCombo->findData(act.value.toLongLong()); if (idx >= 0) folderCombo->setCurrentIndex(idx); } layout->addWidget(folderCombo, 1); break; } case RuleAction::MarkAsRead: { QComboBox* combo = new QComboBox(); combo->addItem(tr("Leído"), "true"); combo->addItem(tr("No leído"), "false"); combo->setCurrentIndex(combo->findData(act.value)); layout->addWidget(combo, 1); break; } case RuleAction::MarkAsFlagged: { QComboBox* combo = new QComboBox(); combo->addItem(tr("Marcado"), "true"); combo->addItem(tr("No marcado"), "false"); combo->setCurrentIndex(combo->findData(act.value)); layout->addWidget(combo, 1); break; } case RuleAction::AssignCategory: case RuleAction::RemoveCategory: { QComboBox* catCombo = new QComboBox(); catCombo->addItem(tr("Seleccionar categoría..."), -1); QVector cats = CategoryDao::findAll(); for (const Category& cat : cats) { catCombo->addItem(cat.name, cat.id); } if (act.value.toLongLong() > 0) { int idx = catCombo->findData(act.value.toLongLong()); if (idx >= 0) catCombo->setCurrentIndex(idx); } layout->addWidget(catCombo, 1); break; } case RuleAction::ForwardTo: { QLineEdit* edit = new QLineEdit(act.value); edit->setPlaceholderText("email@dominio.com"); layout->addWidget(edit, 1); break; } case RuleAction::SetPriority: { QComboBox* combo = new QComboBox(); combo->addItem(tr("Alta"), "high"); combo->addItem(tr("Normal"), "normal"); combo->addItem(tr("Baja"), "low"); combo->setCurrentIndex(combo->findData(act.value)); layout->addWidget(combo, 1); break; } case RuleAction::Delete: case RuleAction::StopProcessing: default: { QLabel* label = new QLabel(tr("(sin parámetros)")); label->setStyleSheet("color: #999; font-style: italic;"); layout->addWidget(label, 1); break; } } return container; } void RuleEditorDialog::updateActionDetails(const RuleAction& act, QLabel* label) { QString details; switch (act.type) { case RuleAction::MoveToFolder: details = tr("Mueve el correo a la carpeta seleccionada"); break; case RuleAction::MarkAsRead: details = tr("Cambia el estado de lectura del correo"); break; case RuleAction::MarkAsFlagged: details = tr("Cambia el estado de marcado/estrella"); break; case RuleAction::Delete: details = tr("Elimina el correo permanentemente"); break; case RuleAction::AssignCategory: details = tr("Añade la categoría al correo"); break; case RuleAction::RemoveCategory: details = tr("Quita la categoría del correo"); break; case RuleAction::ForwardTo: details = tr("Reenvía el correo a la dirección indicada"); break; case RuleAction::SetPriority: details = tr("Marca el correo como alta prioridad (flagged)"); break; case RuleAction::StopProcessing: details = tr("Detiene la evaluación de más reglas para este correo"); break; } label->setText(details); } void RuleEditorDialog::onActionTypeChanged(int row) { QComboBox* typeCombo = qobject_cast(m_actionsTable->cellWidget(row, 0)); if (!typeCombo) return; RuleAction::Type type = static_cast(typeCombo->currentData().toInt()); // Replace parameter widget QWidget* oldWidget = m_actionsTable->cellWidget(row, 1); if (oldWidget) oldWidget->deleteLater(); RuleAction act; act.type = type; QWidget* newWidget = createParameterWidget(act); m_actionsTable->setCellWidget(row, 1, newWidget); // Update details QLabel* detailsLabel = qobject_cast(m_actionsTable->cellWidget(row, 2)); if (detailsLabel) updateActionDetails(act, detailsLabel); } void RuleEditorDialog::onAddCondition() { int row = m_conditionsTable->rowCount(); m_conditionsTable->insertRow(row); setupConditionRow(row); } void RuleEditorDialog::onRemoveCondition() { int row = m_conditionsTable->currentRow(); if (row >= 0) { m_conditionsTable->removeRow(row); } } void RuleEditorDialog::onAddAction() { int row = m_actionsTable->rowCount(); m_actionsTable->insertRow(row); setupActionRow(row); } void RuleEditorDialog::onRemoveAction() { int row = m_actionsTable->currentRow(); if (row >= 0) { m_actionsTable->removeRow(row); } } RuleCondition RuleEditorDialog::conditionFromRow(int row) const { RuleCondition cond; QComboBox* fieldCombo = qobject_cast(m_conditionsTable->cellWidget(row, 0)); QComboBox* opCombo = qobject_cast(m_conditionsTable->cellWidget(row, 1)); QLineEdit* valueEdit = qobject_cast(m_conditionsTable->cellWidget(row, 2)); QCheckBox* caseCheck = qobject_cast(m_conditionsTable->cellWidget(row, 3)); if (fieldCombo) cond.field = static_cast(fieldCombo->currentData().toInt()); if (opCombo) cond.op = static_cast(opCombo->currentData().toInt()); if (valueEdit) cond.value = valueEdit->text(); if (caseCheck) cond.caseSensitive = caseCheck->isChecked(); return cond; } RuleAction RuleEditorDialog::actionFromRow(int row) const { RuleAction act; QComboBox* typeCombo = qobject_cast(m_actionsTable->cellWidget(row, 0)); QWidget* paramWidget = m_actionsTable->cellWidget(row, 1); if (typeCombo) act.type = static_cast(typeCombo->currentData().toInt()); if (!paramWidget) return act; // Extract value based on action type if (act.type == RuleAction::MoveToFolder || act.type == RuleAction::AssignCategory || act.type == RuleAction::RemoveCategory) { QComboBox* combo = paramWidget->findChild(); if (combo && combo->currentData().toLongLong() > 0) { act.value = QString::number(combo->currentData().toLongLong()); } } else if (act.type == RuleAction::MarkAsRead || act.type == RuleAction::MarkAsFlagged || act.type == RuleAction::SetPriority) { QComboBox* combo = paramWidget->findChild(); if (combo) act.value = combo->currentData().toString(); } else if (act.type == RuleAction::ForwardTo) { QLineEdit* edit = paramWidget->findChild(); if (edit) act.value = edit->text(); } return act; } void RuleEditorDialog::onAccept() { if (m_nameEdit->text().trimmed().isEmpty()) { QMessageBox::warning(this, tr("Error"), tr("El nombre de la regla es obligatorio.")); m_nameEdit->setFocus(); return; } m_rule.name = m_nameEdit->text().trimmed(); m_rule.description = m_descEdit->toPlainText().trimmed(); m_rule.enabled = m_enabledCheck->isChecked(); m_rule.matchAll = m_matchAllCheck->isChecked(); m_rule.priority = m_prioritySpin->value(); m_rule.accountId = m_accountCombo->currentData().toLongLong(); if (m_rule.accountId < 0) m_rule.accountId = -1; m_rule.conditions.clear(); for (int i = 0; i < m_conditionsTable->rowCount(); ++i) { m_rule.conditions.append(conditionFromRow(i)); } m_rule.actions.clear(); for (int i = 0; i < m_actionsTable->rowCount(); ++i) { m_rule.actions.append(actionFromRow(i)); } m_rule.updatedAt = QDateTime::currentDateTime(); if (m_rule.isValid()) { if (RuleDao::update(m_rule)) { emit ruleSaved(m_rule); accept(); } else { QMessageBox::critical(this, tr("Error"), tr("No se pudo actualizar la regla.")); } } else { if (RuleDao::insert(m_rule)) { emit ruleSaved(m_rule); accept(); } else { QMessageBox::critical(this, tr("Error"), tr("No se pudo crear la regla.")); } } }