2026-08-17 15:34:49 +02:00
|
|
|
// ===================== ComposeView =====================
|
|
|
|
|
|
|
|
|
|
#include "composeview.h"
|
2026-08-23 14:49:58 +02:00
|
|
|
#include "db/dao/templatedao.h"
|
|
|
|
|
#include "ui/templatesmanagerdialog.h"
|
|
|
|
|
#include "ui/signaturemanagerdialog.h"
|
2026-07-05 23:20:35 +02:00
|
|
|
#include <QDialog>
|
2026-08-17 15:34:49 +02:00
|
|
|
#include <QInputDialog>
|
2026-07-05 23:20:35 +02:00
|
|
|
#include <QVBoxLayout>
|
|
|
|
|
#include <QDialogButtonBox>
|
2026-08-17 15:34:49 +02:00
|
|
|
#include <QFileDialog>
|
|
|
|
|
#include <QFileInfo>
|
|
|
|
|
#include <QMessageBox>
|
2026-07-05 23:20:35 +02:00
|
|
|
#include <QLabel>
|
2026-08-17 15:34:49 +02:00
|
|
|
#include <QPushButton>
|
|
|
|
|
#include <QHBoxLayout>
|
|
|
|
|
#include <QVBoxLayout>
|
|
|
|
|
#include <QPixmap>
|
|
|
|
|
#include <QStyle>
|
|
|
|
|
#include <QApplication>
|
|
|
|
|
#include <QFileIconProvider>
|
2026-08-23 14:49:58 +02:00
|
|
|
#include <QMainWindow>
|
|
|
|
|
#include <QSqlDatabase>
|
|
|
|
|
#include <QSqlQuery>
|
2026-06-17 22:44:27 +02:00
|
|
|
ComposeView::ComposeView(QWidget *parent) : QWidget(parent) {
|
|
|
|
|
setupUI();
|
2026-07-05 10:45:44 +02:00
|
|
|
// Connect the rich text editor's signature signal to our slot
|
|
|
|
|
connect(m_bodyEditor, &RichTextEditor::signatureClicked, this, &ComposeView::onSignatureClicked);
|
2026-07-05 15:32:12 +02:00
|
|
|
connect(m_bodyEditor, &RichTextEditor::signatureEditRequested, this, &ComposeView::onSignatureEditRequested);
|
2026-06-17 22:44:27 +02:00
|
|
|
}
|
|
|
|
|
|
2026-07-05 10:45:44 +02:00
|
|
|
ComposeView::~ComposeView() {
|
|
|
|
|
// Destructor implementation
|
|
|
|
|
}
|
2026-08-17 15:34:49 +02:00
|
|
|
|
2026-08-23 14:49:58 +02:00
|
|
|
void ComposeView::onSignatureClicked()
|
|
|
|
|
{
|
|
|
|
|
// Open signature manager dialog
|
|
|
|
|
qint64 accountId = -1;
|
|
|
|
|
if (m_accountService && m_accountCombo && m_accountCombo->currentIndex() > 0) {
|
|
|
|
|
accountId = m_accountCombo->itemData(m_accountCombo->currentIndex()).toLongLong();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SignatureManagerDialog dlg(m_accountService, accountId, this);
|
|
|
|
|
connect(&dlg, &SignatureManagerDialog::signatureSelected, this, [this](const QString& html) {
|
|
|
|
|
if (!html.isEmpty()) {
|
|
|
|
|
QTextCursor cursor = m_bodyEditor->textCursor();
|
|
|
|
|
cursor.insertHtml(html);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
connect(&dlg, &SignatureManagerDialog::signaturesChanged, this, [this]() {
|
|
|
|
|
emit statusMessageRequested(tr("Firmas actualizadas"));
|
|
|
|
|
});
|
|
|
|
|
dlg.exec();
|
2026-07-05 10:45:44 +02:00
|
|
|
}
|
|
|
|
|
|
2026-08-23 14:49:58 +02:00
|
|
|
void ComposeView::onSignatureEditRequested()
|
|
|
|
|
{
|
|
|
|
|
// Same as click - open manager
|
|
|
|
|
onSignatureClicked();
|
2026-07-05 10:45:44 +02:00
|
|
|
}
|
2026-08-17 15:34:49 +02:00
|
|
|
|
|
|
|
|
void ComposeView::setAccountService(AccountService *service) {
|
2026-07-05 10:45:44 +02:00
|
|
|
m_accountService = service;
|
|
|
|
|
populateAccountCombo();
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-17 15:34:49 +02:00
|
|
|
void ComposeView::populateAccountCombo() {
|
2026-07-05 10:45:44 +02:00
|
|
|
if (!m_accountService) {
|
|
|
|
|
qWarning() << "AccountService not set";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_accountCombo->clear();
|
|
|
|
|
m_accountCombo->addItem(tr("Select Account..."), QVariant());
|
|
|
|
|
|
|
|
|
|
QVector<Account> accounts = m_accountService->getAllAccounts();
|
|
|
|
|
for (const Account &account : accounts) {
|
|
|
|
|
m_accountCombo->addItem(account.email(), account.id());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Select the first account by default if there are accounts
|
|
|
|
|
if (accounts.size() > 0) {
|
|
|
|
|
m_accountCombo->setCurrentIndex(1); // Skip the placeholder item
|
|
|
|
|
m_currentAccountId = accounts.first().id();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-17 15:34:49 +02:00
|
|
|
void ComposeView::onAccountChanged(int index) {
|
2026-07-05 10:45:44 +02:00
|
|
|
if (index <= 0) {
|
|
|
|
|
// Placeholder item selected or invalid index
|
|
|
|
|
m_currentAccountId = -1;
|
2026-08-23 14:49:58 +02:00
|
|
|
m_signatureCombo->clear();
|
|
|
|
|
m_signatureCombo->addItem(tr("Sin cuenta seleccionada"), QVariant());
|
2026-07-05 10:45:44 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_currentAccountId = m_accountCombo->itemData(index).toInt();
|
2026-08-23 14:49:58 +02:00
|
|
|
populateSignatureCombo(m_currentAccountId);
|
2026-07-05 10:45:44 +02:00
|
|
|
loadSignatureForCurrentAccount();
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-23 14:49:58 +02:00
|
|
|
void ComposeView::populateSignatureCombo(qint64 accountId)
|
|
|
|
|
{
|
|
|
|
|
m_signatureCombo->clear();
|
|
|
|
|
m_signatureCombo->addItem(tr("Firma automática"), QVariant()); // Index 0 = auto
|
|
|
|
|
|
|
|
|
|
if (!m_accountService || accountId <= 0) {
|
|
|
|
|
m_signatureCombo->addItem(tr("Sin firmas disponibles"), QVariant());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QSqlDatabase db = DatabaseManager::instance().database();
|
|
|
|
|
QSqlQuery query(db);
|
|
|
|
|
|
|
|
|
|
// Get signatures for this account + global
|
|
|
|
|
query.prepare("SELECT id, name FROM Signature WHERE accountId = :accountId OR accountId IS NULL ORDER BY isDefault DESC, name");
|
|
|
|
|
query.bindValue(":accountId", accountId);
|
|
|
|
|
|
|
|
|
|
if (query.exec()) {
|
|
|
|
|
while (query.next()) {
|
|
|
|
|
QString name = query.value("name").toString();
|
|
|
|
|
qint64 id = query.value("id").toLongLong();
|
|
|
|
|
m_signatureCombo->addItem(name, id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set to first item (automatic)
|
|
|
|
|
m_signatureCombo->setCurrentIndex(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ComposeView::onSignatureComboChanged(int index)
|
|
|
|
|
{
|
|
|
|
|
if (index <= 0) {
|
|
|
|
|
// Automatic mode - use default signature
|
|
|
|
|
loadSignatureForCurrentAccount();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qint64 signatureId = m_signatureCombo->itemData(index).toLongLong();
|
|
|
|
|
if (signatureId <= 0) return;
|
|
|
|
|
|
|
|
|
|
QSqlDatabase db = DatabaseManager::instance().database();
|
|
|
|
|
QSqlQuery query(db);
|
|
|
|
|
query.prepare("SELECT html FROM Signature WHERE id = :id");
|
|
|
|
|
query.bindValue(":id", signatureId);
|
|
|
|
|
|
|
|
|
|
if (query.exec() && query.next()) {
|
|
|
|
|
QString html = query.value(0).toString();
|
|
|
|
|
if (!html.isEmpty()) {
|
|
|
|
|
m_bodyEditor->setHtml(html);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ComposeView::loadSignatureForCurrentAccount()
|
|
|
|
|
{
|
2026-07-05 10:45:44 +02:00
|
|
|
if (!m_accountService || m_currentAccountId <= 0) {
|
|
|
|
|
// Clear signature if no account selected
|
|
|
|
|
m_bodyEditor->clear();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-23 14:49:58 +02:00
|
|
|
QString signatureHtml = getDefaultSignatureForAccount(m_currentAccountId);
|
|
|
|
|
if (!signatureHtml.isEmpty()) {
|
|
|
|
|
m_bodyEditor->setHtml(signatureHtml);
|
2026-07-05 10:45:44 +02:00
|
|
|
} else {
|
|
|
|
|
m_bodyEditor->clear();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-23 14:49:58 +02:00
|
|
|
QString ComposeView::getDefaultSignatureForAccount(qint64 accountId)
|
|
|
|
|
{
|
|
|
|
|
QSqlDatabase db = DatabaseManager::instance().database();
|
|
|
|
|
QSqlQuery query(db);
|
|
|
|
|
|
|
|
|
|
// First try to get account-specific default signature
|
|
|
|
|
query.prepare("SELECT html FROM Signature WHERE accountId = :accountId AND name LIKE '%default%' ORDER BY updatedAt DESC LIMIT 1");
|
|
|
|
|
query.bindValue(":accountId", accountId);
|
|
|
|
|
|
|
|
|
|
if (query.exec() && query.next()) {
|
|
|
|
|
return query.value(0).toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fallback to global default signature
|
|
|
|
|
query.prepare("SELECT html FROM Signature WHERE accountId IS NULL AND name LIKE '%default%' ORDER BY updatedAt DESC LIMIT 1");
|
|
|
|
|
|
|
|
|
|
if (query.exec() && query.next()) {
|
|
|
|
|
return query.value(0).toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Last resort: any signature for this account
|
|
|
|
|
query.prepare("SELECT html FROM Signature WHERE accountId = :accountId ORDER BY updatedAt DESC LIMIT 1");
|
|
|
|
|
query.bindValue(":accountId", accountId);
|
|
|
|
|
|
|
|
|
|
if (query.exec() && query.next()) {
|
|
|
|
|
return query.value(0).toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Global any signature
|
|
|
|
|
query.prepare("SELECT html FROM Signature WHERE accountId IS NULL ORDER BY updatedAt DESC LIMIT 1");
|
|
|
|
|
|
|
|
|
|
if (query.exec() && query.next()) {
|
|
|
|
|
return query.value(0).toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return QString();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-17 22:44:27 +02:00
|
|
|
void ComposeView::setupUI() {
|
|
|
|
|
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
|
|
|
|
mainLayout->setContentsMargins(20, 15, 20, 15);
|
|
|
|
|
mainLayout->setSpacing(8);
|
|
|
|
|
|
|
|
|
|
this->setStyleSheet(
|
|
|
|
|
"QLineEdit, QTextEdit { border: 1px solid #d1d1d6; border-radius: 4px; padding: 8px; font-family: 'Segoe UI', Helvetica; font-size: 13px; }"
|
|
|
|
|
"QLineEdit:focus, QTextEdit:focus { border-color: #1976D2; }"
|
|
|
|
|
);
|
|
|
|
|
|
2026-08-17 15:34:49 +02:00
|
|
|
// De: field + Account selector (renamed from From:)
|
2026-07-05 10:45:44 +02:00
|
|
|
QHBoxLayout *fromLayout = new QHBoxLayout();
|
2026-08-17 15:34:49 +02:00
|
|
|
QLabel *fromLabel = new QLabel(tr("De:"));
|
2026-07-05 10:45:44 +02:00
|
|
|
fromLabel->setFixedWidth(40);
|
|
|
|
|
fromLabel->setStyleSheet("font-weight: bold; color: #555;");
|
|
|
|
|
m_accountCombo = new QComboBox();
|
|
|
|
|
m_accountCombo->setFixedWidth(180);
|
|
|
|
|
m_accountCombo->setStyleSheet(
|
|
|
|
|
"QComboBox { border: 1px solid #d1d1d6; border-radius: 4px; padding: 6px 8px; "
|
|
|
|
|
"background: white; min-height: 20px; }"
|
|
|
|
|
"QComboBox:hover { border-color: #bdbdbd; }"
|
|
|
|
|
"QComboBox:focus { border-color: #1976D2; }"
|
|
|
|
|
"QComboBox::drop-down { border: none; width: 20px; }"
|
|
|
|
|
"QComboBox::down-arrow { image: url(:/icons/dropdown.png); width: 10px; height: 10px; }"
|
|
|
|
|
);
|
|
|
|
|
connect(m_accountCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
|
|
|
|
this, &ComposeView::onAccountChanged);
|
|
|
|
|
fromLayout->addWidget(fromLabel);
|
2026-08-17 15:34:49 +02:00
|
|
|
fromLayout->addWidget(m_accountCombo);
|
2026-08-23 14:49:58 +02:00
|
|
|
|
|
|
|
|
// Signature selector
|
|
|
|
|
m_signatureCombo = new QComboBox();
|
|
|
|
|
m_signatureCombo->setFixedWidth(180);
|
|
|
|
|
m_signatureCombo->setStyleSheet(m_accountCombo->styleSheet());
|
|
|
|
|
m_signatureCombo->setToolTip(tr("Seleccionar firma"));
|
|
|
|
|
connect(m_signatureCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
|
|
|
|
this, &ComposeView::onSignatureComboChanged);
|
|
|
|
|
fromLayout->addWidget(m_signatureCombo);
|
|
|
|
|
|
2026-08-17 15:34:49 +02:00
|
|
|
fromLayout->addStretch(); // Este resorte empuja el par (label+combo) a la izquierda
|
2026-07-05 10:45:44 +02:00
|
|
|
mainLayout->addLayout(fromLayout);
|
|
|
|
|
|
2026-08-17 15:34:49 +02:00
|
|
|
// Para: field + Cc/Bcc toggle buttons
|
2026-06-17 22:44:27 +02:00
|
|
|
QHBoxLayout *toLayout = new QHBoxLayout();
|
2026-08-17 15:34:49 +02:00
|
|
|
QLabel *toLabel = new QLabel(tr("Para:"));
|
|
|
|
|
toLabel->setFixedWidth(40);
|
|
|
|
|
toLabel->setStyleSheet("font-weight: bold; color: #555;");
|
|
|
|
|
m_toField = new everload_tags::TagsLineEdit();
|
|
|
|
|
|
2026-06-17 22:44:27 +02:00
|
|
|
toLayout->addWidget(toLabel);
|
|
|
|
|
toLayout->addWidget(m_toField, 1);
|
|
|
|
|
|
|
|
|
|
m_ccButton = new QPushButton("Cc");
|
|
|
|
|
m_ccButton->setFixedWidth(32);
|
|
|
|
|
m_ccButton->setStyleSheet(
|
|
|
|
|
"QPushButton { background: #e0e0e0; border: 1px solid #ccc; border-radius: 3px; font-size: 11px; padding: 2px; }"
|
|
|
|
|
"QPushButton:hover { background: #d0d0d0; }"
|
|
|
|
|
);
|
|
|
|
|
connect(m_ccButton, &QPushButton::clicked, this, &ComposeView::onCcToggle);
|
|
|
|
|
toLayout->addWidget(m_ccButton);
|
|
|
|
|
|
|
|
|
|
m_bccButton = new QPushButton("Bcc");
|
|
|
|
|
m_bccButton->setFixedWidth(36);
|
|
|
|
|
m_bccButton->setStyleSheet(
|
|
|
|
|
"QPushButton { background: #e0e0e0; border: 1px solid #ccc; border-radius: 3px; font-size: 11px; padding: 2px; }"
|
|
|
|
|
"QPushButton:hover { background: #d0d0d0; }"
|
|
|
|
|
);
|
|
|
|
|
connect(m_bccButton, &QPushButton::clicked, this, &ComposeView::onBccToggle);
|
|
|
|
|
toLayout->addWidget(m_bccButton);
|
|
|
|
|
|
|
|
|
|
mainLayout->addLayout(toLayout);
|
|
|
|
|
|
|
|
|
|
// Cc: row (hidden by default)
|
|
|
|
|
m_ccRow = new QWidget();
|
|
|
|
|
QHBoxLayout *ccLayout = new QHBoxLayout(m_ccRow);
|
|
|
|
|
ccLayout->setContentsMargins(0, 0, 0, 0);
|
2026-08-17 15:34:49 +02:00
|
|
|
QLabel *ccLabel = new QLabel(tr("Cc:"));
|
2026-06-17 22:44:27 +02:00
|
|
|
ccLabel->setFixedWidth(40);
|
|
|
|
|
ccLabel->setStyleSheet("color: #555;");
|
2026-08-17 15:34:49 +02:00
|
|
|
m_ccField = new everload_tags::TagsLineEdit();
|
|
|
|
|
|
2026-06-17 22:44:27 +02:00
|
|
|
ccLayout->addWidget(ccLabel);
|
|
|
|
|
ccLayout->addWidget(m_ccField, 1);
|
|
|
|
|
|
2026-08-17 15:34:49 +02:00
|
|
|
m_hideCcButton = new QPushButton("");
|
|
|
|
|
m_hideCcButton->setIcon(QIcon(QStringLiteral(":/icons/resources/icons/SVG/Linear/Essentional, UI/Close Square.svg")));
|
2026-06-17 22:44:27 +02:00
|
|
|
m_hideCcButton->setFixedSize(20, 20);
|
2026-08-17 15:34:49 +02:00
|
|
|
m_hideCcButton->setToolTip(tr("Hide Cc"));
|
2026-06-17 22:44:27 +02:00
|
|
|
m_hideCcButton->setStyleSheet(
|
2026-08-17 15:34:49 +02:00
|
|
|
"QPushButton { background: transparent; border: none; color: blue; font-weight: bold; }"
|
2026-06-17 22:44:27 +02:00
|
|
|
"QPushButton:hover { color: #333; }"
|
|
|
|
|
);
|
|
|
|
|
connect(m_hideCcButton, &QPushButton::clicked, this, &ComposeView::onCcToggle);
|
|
|
|
|
ccLayout->addWidget(m_hideCcButton);
|
|
|
|
|
|
|
|
|
|
m_ccRow->setVisible(false);
|
|
|
|
|
mainLayout->addWidget(m_ccRow);
|
|
|
|
|
|
|
|
|
|
// Bcc: row (hidden by default)
|
|
|
|
|
m_bccRow = new QWidget();
|
|
|
|
|
QHBoxLayout *bccLayout = new QHBoxLayout(m_bccRow);
|
|
|
|
|
bccLayout->setContentsMargins(0, 0, 0, 0);
|
2026-08-17 15:34:49 +02:00
|
|
|
QLabel *bccLabel = new QLabel(tr("Bcc:"));
|
2026-06-17 22:44:27 +02:00
|
|
|
bccLabel->setFixedWidth(40);
|
|
|
|
|
bccLabel->setStyleSheet("color: #555;");
|
2026-08-17 15:34:49 +02:00
|
|
|
m_bccField = new everload_tags::TagsLineEdit();
|
|
|
|
|
|
2026-06-17 22:44:27 +02:00
|
|
|
bccLayout->addWidget(bccLabel);
|
|
|
|
|
bccLayout->addWidget(m_bccField, 1);
|
|
|
|
|
|
2026-08-17 15:34:49 +02:00
|
|
|
m_hideBccButton = new QPushButton("");
|
2026-06-17 22:44:27 +02:00
|
|
|
m_hideBccButton->setFixedSize(20, 20);
|
2026-08-17 15:34:49 +02:00
|
|
|
m_hideBccButton->setIcon(QIcon(QStringLiteral(":/icons/resources/icons/SVG/Linear/Essentional, UI/Close Square.svg")));
|
|
|
|
|
m_hideBccButton->setToolTip(tr("Hide Bcc"));
|
2026-06-17 22:44:27 +02:00
|
|
|
m_hideBccButton->setStyleSheet(
|
2026-08-17 15:34:49 +02:00
|
|
|
"QPushButton { background: transparent; border: none; color: blue; font-weight: bold; }"
|
2026-06-17 22:44:27 +02:00
|
|
|
"QPushButton:hover { color: #333; }"
|
|
|
|
|
);
|
|
|
|
|
connect(m_hideBccButton, &QPushButton::clicked, this, &ComposeView::onBccToggle);
|
|
|
|
|
bccLayout->addWidget(m_hideBccButton);
|
|
|
|
|
|
|
|
|
|
m_bccRow->setVisible(false);
|
|
|
|
|
mainLayout->addWidget(m_bccRow);
|
|
|
|
|
|
2026-08-17 15:34:49 +02:00
|
|
|
// Asunto: field
|
|
|
|
|
QHBoxLayout *subjectLayout = new QHBoxLayout();
|
|
|
|
|
QLabel *subjectLabel = new QLabel(tr("Asunto:"));
|
|
|
|
|
subjectLabel->setFixedWidth(60);
|
|
|
|
|
subjectLabel->setStyleSheet("font-weight: bold; color: #555;");
|
|
|
|
|
m_subjectField = new QLineEdit();
|
|
|
|
|
m_subjectField->setPlaceholderText(tr("Subject"));
|
|
|
|
|
m_subjectField->setFixedHeight(40);
|
|
|
|
|
QFont subjectFont = m_subjectField->font();
|
|
|
|
|
subjectFont.setPointSize(14);
|
|
|
|
|
subjectFont.setBold(true);
|
|
|
|
|
m_subjectField->setFont(subjectFont);
|
|
|
|
|
//subjectLayout->addWidget(subjectLabel);
|
|
|
|
|
subjectLayout->addWidget(m_subjectField, 1);
|
|
|
|
|
|
|
|
|
|
// Detach button placed to the right of the subject line
|
|
|
|
|
m_detachButton = new QPushButton("");//("↥ Detach");
|
|
|
|
|
m_detachButton->setIcon(QIcon(QStringLiteral(":/icons/resources/icons/SVG/Linear/Arrows Action/Square Top Down.svg")));
|
|
|
|
|
m_detachButton->setToolTip(tr("Open compose window in a separate window"));
|
|
|
|
|
m_detachButton->setStyleSheet(
|
|
|
|
|
"QPushButton { background: transparent; border: 1px solid #d1d1d6; border-radius: 4px; padding: 6px 12px; color: #555; font-size: 12px; }"
|
|
|
|
|
"QPushButton:hover { background: #f0f0f0; }"
|
|
|
|
|
);
|
|
|
|
|
connect(m_detachButton, &QPushButton::clicked, this, &ComposeView::onDetachClicked);
|
|
|
|
|
subjectLayout->addWidget(m_detachButton);
|
|
|
|
|
|
|
|
|
|
mainLayout->addLayout(subjectLayout);
|
|
|
|
|
|
|
|
|
|
// Separator line admin@email.com
|
|
|
|
|
QFrame *line1 = new QFrame();
|
|
|
|
|
line1->setFrameShape(QFrame::HLine);
|
|
|
|
|
line1->setStyleSheet("color: #e0e0e0;");
|
|
|
|
|
mainLayout->addWidget(line1);
|
2026-06-17 22:44:27 +02:00
|
|
|
|
|
|
|
|
// Rich text editor with toolbar
|
|
|
|
|
m_bodyEditor = new RichTextEditor();
|
|
|
|
|
m_bodyEditor->setupToolbar(mainLayout);
|
|
|
|
|
mainLayout->addWidget(m_bodyEditor, 1);
|
|
|
|
|
|
|
|
|
|
// Schedule panel
|
|
|
|
|
m_schedulePanel = new QWidget();
|
|
|
|
|
QHBoxLayout *scheduleLayout = new QHBoxLayout(m_schedulePanel);
|
|
|
|
|
scheduleLayout->setContentsMargins(0, 0, 0, 0);
|
2026-08-17 15:34:49 +02:00
|
|
|
QLabel *scheduleLabel = new QLabel(tr("Send at:"));
|
2026-06-17 22:44:27 +02:00
|
|
|
scheduleLabel->setStyleSheet("color: #555; font-weight: bold;");
|
|
|
|
|
m_schedulePicker = new QDateTimeEdit(QDateTime::currentDateTime().addSecs(3600));
|
|
|
|
|
m_schedulePicker->setCalendarPopup(true);
|
|
|
|
|
m_schedulePicker->setDisplayFormat("dd/MM/yyyy hh:mm AP");
|
|
|
|
|
scheduleLayout->addWidget(scheduleLabel);
|
|
|
|
|
scheduleLayout->addWidget(m_schedulePicker);
|
|
|
|
|
scheduleLayout->addStretch();
|
|
|
|
|
|
2026-08-17 15:34:49 +02:00
|
|
|
m_scheduleSendButton = new QPushButton(tr("Schedule Send"));
|
|
|
|
|
m_scheduleSendButton->setFixedWidth(150);
|
2026-06-17 22:44:27 +02:00
|
|
|
m_scheduleSendButton->setStyleSheet(
|
|
|
|
|
"QPushButton { background-color: #1976D2; color: white; border: none; border-radius: 4px; padding: 6px 16px; font-weight: bold; }"
|
|
|
|
|
"QPushButton:hover { background-color: #1565C0; }"
|
|
|
|
|
);
|
|
|
|
|
connect(m_scheduleSendButton, &QPushButton::clicked, this, &ComposeView::onScheduleClicked);
|
2026-08-17 15:34:49 +02:00
|
|
|
scheduleLayout->addWidget(m_scheduleSendButton, 2);
|
|
|
|
|
m_schedulePanel->setVisible(false);
|
2026-06-17 22:44:27 +02:00
|
|
|
m_schedulePanel->setVisible(false);
|
|
|
|
|
mainLayout->addWidget(m_schedulePanel);
|
2026-08-17 15:34:49 +02:00
|
|
|
|
2026-07-05 15:32:12 +02:00
|
|
|
// Attachment section
|
|
|
|
|
QHBoxLayout *attachmentLayout = new QHBoxLayout();
|
|
|
|
|
m_attachmentList = new QListWidget();
|
2026-08-17 15:34:49 +02:00
|
|
|
m_attachmentList->setViewMode(QListView::IconMode);
|
|
|
|
|
m_attachmentList->setResizeMode(QListView::Adjust);
|
|
|
|
|
m_attachmentList->setMovement(QListView::Static);
|
|
|
|
|
m_attachmentList->setGridSize(QSize(300, 50));
|
|
|
|
|
m_attachmentList->setSpacing(10);
|
|
|
|
|
m_attachmentList->setLayoutDirection(Qt::LeftToRight);
|
2026-07-05 15:32:12 +02:00
|
|
|
m_attachmentList->setSelectionMode(QAbstractItemView::SingleSelection);
|
2026-08-17 15:34:49 +02:00
|
|
|
m_attachmentList->setStyleSheet(
|
|
|
|
|
"QListWidget { border: 1px solid #d1d1d6; border-radius: 4px; }"
|
|
|
|
|
"QListWidget::item { border: none; padding: 5px; }"
|
|
|
|
|
"QListWidget::item:selected { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #1976D2, stop:1 #1565C0); border-radius: 4px; }"
|
|
|
|
|
);
|
|
|
|
|
m_attachmentList->setVisible(false);
|
2026-07-05 15:32:12 +02:00
|
|
|
attachmentLayout->addWidget(m_attachmentList, 1);
|
|
|
|
|
|
|
|
|
|
mainLayout->addLayout(attachmentLayout);
|
|
|
|
|
|
2026-06-17 22:44:27 +02:00
|
|
|
// Action buttons row
|
|
|
|
|
QHBoxLayout *actionsLayout = new QHBoxLayout();
|
|
|
|
|
|
2026-08-17 15:34:49 +02:00
|
|
|
// Attachments and Templates buttons (right-aligned)
|
|
|
|
|
QHBoxLayout *buttonRow = new QHBoxLayout();
|
|
|
|
|
m_attachButton = new QToolButton();
|
|
|
|
|
m_attachButton->setIcon(QIcon(QStringLiteral(":/icons/resources/icons/SVG/Outline/Messages, Conversation/Paperclip.svg")));
|
|
|
|
|
m_attachButton->setToolTip(tr("Add attachment"));
|
|
|
|
|
m_attachButton->setIconSize(QSize(20,20));
|
|
|
|
|
m_attachButton->setStyleSheet("QToolButton { border: none; padding: 5px; } QToolButton:hover { background: #e0e0e0; border-radius: 3px; }");
|
|
|
|
|
connect(m_attachButton, &QToolButton::clicked, this, &ComposeView::onAddAttachmentClicked);
|
|
|
|
|
buttonRow->addWidget(m_attachButton);
|
|
|
|
|
|
|
|
|
|
m_templateButton = new QToolButton();
|
|
|
|
|
m_templateButton->setIcon(QIcon(QStringLiteral(":/icons/resources/icons/SVG/Outline/Files/File Text.svg"))); // Assuming you have a template icon
|
|
|
|
|
m_templateButton->setToolTip(tr("Email templates"));
|
|
|
|
|
m_templateButton->setIconSize(QSize(20,20));
|
|
|
|
|
m_templateButton->setStyleSheet("QToolButton { border: none; padding: 5px; } QToolButton:hover { background: #e0e0e0; border-radius: 3px; }");
|
|
|
|
|
connect(m_templateButton, &QToolButton::clicked, this, &ComposeView::onTemplateClicked);
|
|
|
|
|
buttonRow->addWidget(m_templateButton);
|
|
|
|
|
buttonRow->setSpacing(4);
|
|
|
|
|
actionsLayout->addLayout(buttonRow);
|
|
|
|
|
actionsLayout->addStretch(); // Este resorte empuja el par (label+combo) a la izquierda
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
m_discardButton = new QPushButton(tr("Discard"));
|
2026-06-17 22:44:27 +02:00
|
|
|
m_discardButton->setStyleSheet(
|
|
|
|
|
"QPushButton { background: transparent; border: 1px solid #d1d1d6; border-radius: 4px; padding: 8px 20px; color: #555; }"
|
|
|
|
|
"QPushButton:hover { background: #f5f5f5; }"
|
|
|
|
|
);
|
|
|
|
|
connect(m_discardButton, &QPushButton::clicked, this, &ComposeView::discardRequested);
|
2026-08-17 15:34:49 +02:00
|
|
|
actionsLayout->addWidget(m_discardButton);
|
2026-06-17 22:44:27 +02:00
|
|
|
|
|
|
|
|
// Split button for Send / Schedule
|
|
|
|
|
m_sendMenu = new QMenu(this);
|
2026-08-17 15:34:49 +02:00
|
|
|
m_sendNowAction = m_sendMenu->addAction(tr("Send Now"));
|
2026-06-17 22:44:27 +02:00
|
|
|
connect(m_sendNowAction, &QAction::triggered, this, &ComposeView::onSendClicked);
|
2026-08-17 15:34:49 +02:00
|
|
|
m_scheduleAction = m_sendMenu->addAction(tr("Schedule for later..."));
|
2026-06-17 22:44:27 +02:00
|
|
|
connect(m_scheduleAction, &QAction::triggered, [this]() {
|
|
|
|
|
m_schedulePanel->setVisible(true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
m_sendSplit = new QToolButton();
|
2026-08-17 15:34:49 +02:00
|
|
|
m_sendSplit->setText(tr("Send"));
|
|
|
|
|
m_sendSplit->setIcon(QIcon(QStringLiteral(":/icons/resources/icons/SVG/Bold Duotone/Messages, Conversation/Plain.svg")));
|
|
|
|
|
m_templateButton->setIconSize(QSize(20,20));
|
|
|
|
|
//m_sendSplit->setToolButtonStyle(Qt::ToolButtonTextOnly);
|
2026-06-17 22:44:27 +02:00
|
|
|
m_sendSplit->setPopupMode(QToolButton::MenuButtonPopup);
|
|
|
|
|
m_sendSplit->setMenu(m_sendMenu);
|
|
|
|
|
m_sendSplit->setStyleSheet(
|
|
|
|
|
"QToolButton { background-color: #1976D2; color: white; border: none; border-radius: 4px; padding: 8px 24px; font-weight: bold; }"
|
|
|
|
|
"QToolButton:hover { background-color: #1565C0; }"
|
2026-08-17 15:34:49 +02:00
|
|
|
"QToolButton::menu-button { border-left: 1px solid rgba(255,255,255,0.3); padding-left: 8px; padding-right: 8px; width: 20px;}"
|
2026-06-17 22:44:27 +02:00
|
|
|
"QToolButton::menu-button:hover { background-color: #1565C0; border-top-right-radius: 4px; border-bottom-right-radius: 4px; }"
|
|
|
|
|
);
|
|
|
|
|
connect(m_sendSplit, &QToolButton::clicked, this, &ComposeView::onSendClicked);
|
|
|
|
|
actionsLayout->addWidget(m_sendSplit);
|
|
|
|
|
|
|
|
|
|
mainLayout->addLayout(actionsLayout);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ComposeView::onCcToggle() {
|
|
|
|
|
m_ccVisible = !m_ccVisible;
|
|
|
|
|
m_ccRow->setVisible(m_ccVisible);
|
|
|
|
|
m_ccButton->setVisible(!m_ccVisible);
|
|
|
|
|
if (m_ccVisible) m_ccField->setFocus();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ComposeView::onBccToggle() {
|
|
|
|
|
m_bccVisible = !m_bccVisible;
|
|
|
|
|
m_bccRow->setVisible(m_bccVisible);
|
|
|
|
|
m_bccButton->setVisible(!m_bccVisible);
|
|
|
|
|
if (m_bccVisible) m_bccField->setFocus();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ComposeView::onSendClicked() {
|
|
|
|
|
emit sendRequested(
|
2026-08-17 15:34:49 +02:00
|
|
|
m_toField->tags2().join(", "),
|
|
|
|
|
m_ccVisible ? m_ccField->tags2().join(", ") : QString(),
|
|
|
|
|
m_bccVisible ? m_bccField->tags2().join(", ") : QString(),
|
2026-06-17 22:44:27 +02:00
|
|
|
m_subjectField->text(),
|
|
|
|
|
m_bodyEditor->toHtml(),
|
2026-07-05 10:45:44 +02:00
|
|
|
QDateTime(), // null = send now
|
2026-08-17 15:34:49 +02:00
|
|
|
m_currentAccountId > 0 ? m_accountCombo->itemData(m_accountCombo->currentIndex()).toString() : QString(),
|
|
|
|
|
m_attachmentFiles
|
2026-06-17 22:44:27 +02:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ComposeView::onScheduleClicked() {
|
|
|
|
|
emit sendRequested(
|
2026-08-17 15:34:49 +02:00
|
|
|
m_toField->tags2().join(", "),
|
|
|
|
|
m_ccVisible ? m_ccField->tags2().join(", ") : QString(),
|
|
|
|
|
m_bccVisible ? m_bccField->tags2().join(", ") : QString(),
|
2026-06-17 22:44:27 +02:00
|
|
|
m_subjectField->text(),
|
|
|
|
|
m_bodyEditor->toHtml(),
|
2026-07-05 10:45:44 +02:00
|
|
|
m_schedulePicker->dateTime(),
|
2026-08-17 15:34:49 +02:00
|
|
|
m_currentAccountId > 0 ? m_accountCombo->itemData(m_accountCombo->currentIndex()).toString() : QString(),
|
|
|
|
|
m_attachmentFiles
|
2026-06-17 22:44:27 +02:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ComposeView::onDetachClicked() {
|
|
|
|
|
emit detachRequested(this);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-17 15:34:49 +02:00
|
|
|
void ComposeView::setTo(const QString &to) { m_toField->tags(to.split(',', Qt::SkipEmptyParts)); }
|
2026-06-17 22:44:27 +02:00
|
|
|
void ComposeView::setSubject(const QString &subject) { m_subjectField->setText(subject); }
|
2026-06-23 01:34:13 +02:00
|
|
|
void ComposeView::setBody(const QString &body) { m_bodyEditor->setHtml(body); }
|
2026-08-17 15:34:49 +02:00
|
|
|
|
2026-06-23 01:34:13 +02:00
|
|
|
void ComposeView::initializeComposition() {
|
2026-08-17 15:34:49 +02:00
|
|
|
m_toField->tags(QStringList{});
|
|
|
|
|
m_ccField->tags(QStringList{});
|
|
|
|
|
m_bccField->tags(QStringList{});
|
2026-06-23 01:34:13 +02:00
|
|
|
m_subjectField->clear();
|
2026-08-17 15:34:49 +02:00
|
|
|
m_attachmentFiles.clear();
|
|
|
|
|
m_attachmentList->clear();
|
|
|
|
|
m_attachmentList->setVisible(false);
|
2026-06-23 01:34:13 +02:00
|
|
|
m_ccRow->setVisible(false);
|
|
|
|
|
m_bccRow->setVisible(false);
|
|
|
|
|
m_schedulePanel->setVisible(false);
|
2026-08-23 14:49:58 +02:00
|
|
|
|
|
|
|
|
// Load signature for current account
|
|
|
|
|
loadSignatureForCurrentAccount();
|
|
|
|
|
|
2026-06-23 01:34:13 +02:00
|
|
|
m_toField->setFocus();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ComposeView::startNewEmail(const QString &initialRecipient) {
|
2026-08-17 15:34:49 +02:00
|
|
|
initializeComposition();
|
|
|
|
|
if (!initialRecipient.isEmpty())
|
|
|
|
|
m_toField->tags(initialRecipient.split(',', Qt::SkipEmptyParts));
|
|
|
|
|
m_toField->setFocus();
|
|
|
|
|
}
|
2026-07-05 15:32:12 +02:00
|
|
|
|
2026-08-17 15:34:49 +02:00
|
|
|
void ComposeView::onAddAttachmentClicked() {
|
|
|
|
|
qDebug() << "Attach button clicked";
|
|
|
|
|
QStringList files = QFileDialog::getOpenFileNames(this, tr("Select Attachments"), QString(), tr("All Files (*)"));
|
|
|
|
|
if (files.isEmpty())
|
|
|
|
|
return;
|
|
|
|
|
for (const QString &file : files) {
|
|
|
|
|
m_attachmentFiles.append(file);
|
|
|
|
|
QListWidgetItem *item = new QListWidgetItem(m_attachmentList);
|
|
|
|
|
item->setData(Qt::UserRole, file);
|
|
|
|
|
QWidget *widget = new QWidget;
|
|
|
|
|
widget->setFixedSize(300, 50);
|
|
|
|
|
widget->setStyleSheet(
|
|
|
|
|
"background-color: #f8f9fa; "
|
|
|
|
|
"border-radius: 8px; "
|
|
|
|
|
"border: 1px solid #e9ecef;"
|
|
|
|
|
);
|
|
|
|
|
QHBoxLayout *layout = new QHBoxLayout(widget);
|
|
|
|
|
layout->setContentsMargins(8, 4, 8, 4);
|
|
|
|
|
layout->setSpacing(8);
|
|
|
|
|
// Icon from system
|
|
|
|
|
QFileIconProvider provider;
|
|
|
|
|
QIcon icon = provider.icon(QFileInfo(file));
|
|
|
|
|
QLabel *iconLabel = new QLabel;
|
|
|
|
|
QPixmap pix = icon.pixmap(32, 32);
|
|
|
|
|
if (pix.isNull())
|
|
|
|
|
pix = QIcon::fromTheme("unknown").pixmap(32,32);
|
|
|
|
|
iconLabel->setPixmap(pix);
|
|
|
|
|
iconLabel->setFixedSize(32,32);
|
|
|
|
|
layout->addWidget(iconLabel);
|
|
|
|
|
// Text vertical layout
|
|
|
|
|
QVBoxLayout *textLayout = new QVBoxLayout;
|
|
|
|
|
textLayout->setSpacing(2);
|
|
|
|
|
QString fileName = QFileInfo(file).fileName();
|
|
|
|
|
QLabel *nameLabel = new QLabel(fileName);
|
|
|
|
|
nameLabel->setStyleSheet("font-weight: bold;");
|
|
|
|
|
QLabel *sizeLabel = new QLabel(tr("%1 KB").arg(QFileInfo(file).size()/1024));
|
|
|
|
|
sizeLabel->setStyleSheet("color: #666; font-size: 10px;");
|
|
|
|
|
textLayout->addWidget(nameLabel);
|
|
|
|
|
textLayout->addWidget(sizeLabel);
|
|
|
|
|
layout->addLayout(textLayout);
|
|
|
|
|
layout->addStretch();
|
|
|
|
|
// Delete button
|
|
|
|
|
QPushButton *delBtn = new QPushButton;
|
|
|
|
|
QIcon delIcon = QIcon::fromTheme("edit-delete");
|
|
|
|
|
if (delIcon.isNull())
|
|
|
|
|
delIcon = QIcon::fromTheme("list-remove");
|
|
|
|
|
if (delIcon.isNull())
|
|
|
|
|
delIcon = QIcon::fromTheme("gtk-delete");
|
|
|
|
|
delBtn->setIcon(delIcon);
|
|
|
|
|
delBtn->setToolTip(tr("Remove attachment"));
|
|
|
|
|
delBtn->setFixedSize(24,24);
|
|
|
|
|
delBtn->setStyleSheet(
|
|
|
|
|
"QPushButton { border: none; background: transparent; }"
|
|
|
|
|
"QPushButton:hover { background: #e9ecef; border-radius: 4px; }"
|
|
|
|
|
);
|
|
|
|
|
layout->addWidget(delBtn);
|
|
|
|
|
// Connect delete button
|
|
|
|
|
connect(delBtn, &QPushButton::clicked, [this, item]() {
|
|
|
|
|
int row = m_attachmentList->row(item);
|
|
|
|
|
if (row >= 0) {
|
|
|
|
|
QListWidgetItem *it = m_attachmentList->takeItem(row);
|
|
|
|
|
if (it) {
|
|
|
|
|
m_attachmentFiles.removeAt(row);
|
|
|
|
|
delete it;
|
|
|
|
|
}
|
|
|
|
|
m_attachmentList->setVisible(!m_attachmentFiles.isEmpty());
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
m_attachmentList->addItem(item);
|
|
|
|
|
m_attachmentList->setItemWidget(item, widget);
|
|
|
|
|
}
|
|
|
|
|
m_attachmentList->setVisible(!m_attachmentFiles.isEmpty());
|
|
|
|
|
}
|
2026-07-05 15:32:12 +02:00
|
|
|
|
2026-08-17 15:34:49 +02:00
|
|
|
void ComposeView::onRemoveAttachmentClicked() {
|
|
|
|
|
QListWidgetItem *item = m_attachmentList->currentItem();
|
|
|
|
|
if (!item)
|
|
|
|
|
return;
|
|
|
|
|
int row = m_attachmentList->row(item);
|
|
|
|
|
m_attachmentList->takeItem(row);
|
|
|
|
|
m_attachmentFiles.removeAt(row);
|
|
|
|
|
delete item;
|
|
|
|
|
m_attachmentList->setVisible(!m_attachmentFiles.isEmpty());
|
|
|
|
|
}
|
2026-07-05 15:32:12 +02:00
|
|
|
|
2026-08-23 14:49:58 +02:00
|
|
|
void ComposeView::onTemplateClicked()
|
|
|
|
|
{
|
|
|
|
|
// Get current account ID if available
|
|
|
|
|
qint64 accountId = -1;
|
|
|
|
|
if (m_accountService) {
|
|
|
|
|
// Try to get current account from combo
|
|
|
|
|
if (m_accountCombo && m_accountCombo->currentIndex() > 0) {
|
|
|
|
|
accountId = m_accountCombo->itemData(m_accountCombo->currentIndex()).toLongLong();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TemplatesManagerDialog dlg(accountId, this);
|
|
|
|
|
connect(&dlg, &TemplatesManagerDialog::templateSelected, this, [this](const Template& tmpl) {
|
|
|
|
|
applyTemplate(tmpl);
|
|
|
|
|
});
|
|
|
|
|
dlg.exec();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ComposeView::applyTemplate(const Template& tmpl)
|
|
|
|
|
{
|
|
|
|
|
if (!tmpl.isValid()) return;
|
|
|
|
|
|
|
|
|
|
// Set subject
|
|
|
|
|
if (!tmpl.subject.isEmpty()) {
|
|
|
|
|
m_subjectField->setText(tmpl.subject);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set body HTML
|
|
|
|
|
if (!tmpl.bodyHtml.isEmpty()) {
|
|
|
|
|
m_bodyEditor->setHtml(tmpl.bodyHtml);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If there are variables, we could show a dialog to fill them
|
|
|
|
|
// For now, just apply as-is
|
|
|
|
|
emit statusMessageRequested(tr("Plantilla aplicada: %1").arg(tmpl.name));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ComposeView::statusBarMessage(const QString& msg)
|
|
|
|
|
{
|
|
|
|
|
emit statusMessageRequested(msg);
|
2026-08-17 15:34:49 +02:00
|
|
|
}
|
|
|
|
|
|