feat: IMAP sync incremental + Account Setup Wizard + robust FETCH parser
- ImapSynchronizer::syncFolder(): detecta eliminados (UIDs locales no en servidor), fetch nuevos (sinceUid), actualiza flags (FLAGS batch) - fetchAllUids(): SELECT + SEARCH ALL para lista completa UIDs servidor - parseAndUpdateFlags(): FETCH FLAGS en lotes 100, update DB si cambió read/flagged - AccountSetupDialog: integra ConnectionWizard para IMAP/POP3 con test de conexión real - IMAP FETCH parser robusto: logging respuesta servidor + fallback UID-by-UID - Fix: FETCH failed logging muestra first/last UID + respuesta truncada
This commit is contained in:
+240
-413
@@ -1,277 +1,21 @@
|
||||
#include "ui/composeview.h"
|
||||
#include <QFrame>
|
||||
#include <QFileDialog>
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QDebug>
|
||||
// ===================== ComposeView =====================
|
||||
|
||||
#include "composeview.h"
|
||||
#include <QDialog>
|
||||
#include <QInputDialog>
|
||||
#include <QVBoxLayout>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFileDialog>
|
||||
#include <QFileInfo>
|
||||
#include <QMessageBox>
|
||||
#include <QLabel>
|
||||
#include "tags_line_edit.hpp"
|
||||
#include <QRegularExpression>
|
||||
#include <QTimer>
|
||||
#include <QTextList>
|
||||
#include <QTextTable>
|
||||
#include <QTextCursor>
|
||||
#include <QTextBlockFormat>
|
||||
#include <QTextCharFormat>
|
||||
|
||||
// ===================== RichTextEditor =====================
|
||||
|
||||
RichTextEditor::RichTextEditor(QWidget *parent) : QTextEdit(parent) {
|
||||
setAcceptRichText(true);
|
||||
setPlaceholderText("Write your message here...");
|
||||
}
|
||||
|
||||
void RichTextEditor::setupToolbar(QVBoxLayout *layout) {
|
||||
m_toolbar = new QToolBar("Formatting");
|
||||
m_toolbar->setIconSize(QSize(16, 16));
|
||||
m_toolbar->setStyleSheet(
|
||||
"QToolBar { background: #f5f5f7; border: 1px solid #d1d1d6; border-radius: 4px; spacing: 2px; padding: 2px; }"
|
||||
"QToolButton { padding: 4px 6px; border-radius: 3px; }"
|
||||
"QToolButton:hover { background: #e0e0e0; }"
|
||||
"QToolButton:checked { background: #bbdefb; }"
|
||||
);
|
||||
|
||||
// Font family combo
|
||||
m_fontCombo = new QFontComboBox();
|
||||
m_fontCombo->setFixedWidth(150);
|
||||
connect(m_fontCombo, &QFontComboBox::currentFontChanged, this, &RichTextEditor::onFontChanged);
|
||||
m_toolbar->addWidget(m_fontCombo);
|
||||
|
||||
// Font size spin
|
||||
m_fontSizeSpin = new QSpinBox();
|
||||
m_fontSizeSpin->setRange(8, 72);
|
||||
m_fontSizeSpin->setValue(14);
|
||||
m_fontSizeSpin->setFixedWidth(50);
|
||||
connect(m_fontSizeSpin, QOverload<int>::of(&QSpinBox::valueChanged), this, &RichTextEditor::onFontSizeChanged);
|
||||
m_toolbar->addWidget(m_fontSizeSpin);
|
||||
|
||||
m_toolbar->addSeparator();
|
||||
|
||||
// Bold / Italic / Underline
|
||||
QAction *boldAct = m_toolbar->addAction("B");
|
||||
boldAct->setCheckable(true);
|
||||
QFont boldFont = boldAct->font(); boldFont.setBold(true); boldAct->setFont(boldFont);
|
||||
connect(boldAct, &QAction::triggered, this, &RichTextEditor::onBold);
|
||||
|
||||
QAction *italicAct = m_toolbar->addAction("I");
|
||||
italicAct->setCheckable(true);
|
||||
QFont italicFont = italicAct->font(); italicFont.setItalic(true); italicAct->setFont(italicFont);
|
||||
connect(italicAct, &QAction::triggered, this, &RichTextEditor::onItalic);
|
||||
|
||||
QAction *underlineAct = m_toolbar->addAction("U");
|
||||
underlineAct->setCheckable(true);
|
||||
QFont uFont = underlineAct->font(); uFont.setUnderline(true); underlineAct->setFont(uFont);
|
||||
connect(underlineAct, &QAction::triggered, this, &RichTextEditor::onUnderline);
|
||||
|
||||
m_toolbar->addSeparator();
|
||||
|
||||
// Alignment
|
||||
QAction *alignLeft = m_toolbar->addAction("L");
|
||||
connect(alignLeft, &QAction::triggered, this, &RichTextEditor::onAlignLeft);
|
||||
|
||||
QAction *alignCenter = m_toolbar->addAction("C");
|
||||
connect(alignCenter, &QAction::triggered, this, &RichTextEditor::onAlignCenter);
|
||||
|
||||
QAction *alignRight = m_toolbar->addAction("R");
|
||||
connect(alignRight, &QAction::triggered, this, &RichTextEditor::onAlignRight);
|
||||
|
||||
QAction *alignJustify = m_toolbar->addAction("J");
|
||||
connect(alignJustify, &QAction::triggered, this, &RichTextEditor::onAlignJustify);
|
||||
|
||||
m_toolbar->addSeparator();
|
||||
|
||||
// Lists
|
||||
QAction *bulletAct = m_toolbar->addAction("Bullets");
|
||||
connect(bulletAct, &QAction::triggered, this, &RichTextEditor::onBulletList);
|
||||
|
||||
QAction *numAct = m_toolbar->addAction("1. List");
|
||||
connect(numAct, &QAction::triggered, this, &RichTextEditor::onNumberedList);
|
||||
|
||||
m_toolbar->addSeparator();
|
||||
|
||||
// Indent / Outdent
|
||||
QAction *indentAct = m_toolbar->addAction("Indent");
|
||||
connect(indentAct, &QAction::triggered, this, &RichTextEditor::onIndent);
|
||||
|
||||
QAction *outdentAct = m_toolbar->addAction("Outdent");
|
||||
connect(outdentAct, &QAction::triggered, this, &RichTextEditor::onOutdent);
|
||||
|
||||
m_toolbar->addSeparator();
|
||||
|
||||
// Insert image
|
||||
QAction *imgAct = m_toolbar->addAction("Img");
|
||||
connect(imgAct, &QAction::triggered, this, &RichTextEditor::onInsertImage);
|
||||
|
||||
// Insert table
|
||||
QAction *tableAct = m_toolbar->addAction("Tbl");
|
||||
connect(tableAct, &QAction::triggered, this, &RichTextEditor::onInsertTable);
|
||||
|
||||
// Signature button
|
||||
m_signatureButton = new QToolButton();
|
||||
m_signatureButton->setText("Signature");
|
||||
m_signatureButton->setToolButtonStyle(Qt::ToolButtonTextOnly);
|
||||
m_signatureButton->setPopupMode(QToolButton::MenuButtonPopup);
|
||||
m_signatureMenu = new QMenu(m_signatureButton);
|
||||
m_signatureButton->setMenu(m_signatureMenu);
|
||||
m_signatureButton->setStyleSheet(
|
||||
"QToolButton { background: #f5f5f7; border: 1px solid #d1d1d6; border-radius: 3px; padding: 4px 6px; }"
|
||||
"QToolButton:hover { background: #e0e0e0; }"
|
||||
"QToolButton::menu-button { border-left: 1px solid rgba(0,0,0,0.1); width: 12px; }"
|
||||
);
|
||||
connect(m_signatureButton, &QToolButton::clicked, this, &RichTextEditor::signatureClicked);
|
||||
m_toolbar->addWidget(m_signatureButton);
|
||||
QAction *editSigAct = m_signatureMenu->addAction(tr("Editar firmas"));
|
||||
connect(editSigAct, &QAction::triggered, this, &RichTextEditor::signatureEditRequested);
|
||||
|
||||
m_toolbar->addSeparator();
|
||||
|
||||
layout->addWidget(m_toolbar);
|
||||
}
|
||||
|
||||
void RichTextEditor::onBold() {
|
||||
QTextCharFormat fmt;
|
||||
fmt.setFontWeight(textCursor().charFormat().fontWeight() == QFont::Bold ? QFont::Normal : QFont::Bold);
|
||||
mergeCurrentCharFormat(fmt);
|
||||
setFocus();
|
||||
}
|
||||
|
||||
void RichTextEditor::onItalic() {
|
||||
QTextCharFormat fmt;
|
||||
fmt.setFontItalic(!textCursor().charFormat().fontItalic());
|
||||
mergeCurrentCharFormat(fmt);
|
||||
setFocus();
|
||||
}
|
||||
|
||||
void RichTextEditor::onUnderline() {
|
||||
QTextCharFormat fmt;
|
||||
fmt.setFontUnderline(!textCursor().charFormat().fontUnderline());
|
||||
mergeCurrentCharFormat(fmt);
|
||||
setFocus();
|
||||
}
|
||||
|
||||
void RichTextEditor::onBulletList() {
|
||||
QTextCursor cursor = textCursor();
|
||||
QTextList *list = cursor.currentList();
|
||||
if (list && list->format().style() == QTextListFormat::ListDisc) {
|
||||
// Remove list
|
||||
QTextBlockFormat bfmt;
|
||||
bfmt.setIndent(0);
|
||||
cursor.setBlockFormat(bfmt);
|
||||
list->remove(cursor.block());
|
||||
} else {
|
||||
QTextListFormat listFormat;
|
||||
listFormat.setStyle(QTextListFormat::ListDisc);
|
||||
cursor.createList(listFormat);
|
||||
}
|
||||
}
|
||||
|
||||
void RichTextEditor::onNumberedList() {
|
||||
QTextCursor cursor = textCursor();
|
||||
QTextList *list = cursor.currentList();
|
||||
if (list && list->format().style() == QTextListFormat::ListDecimal) {
|
||||
QTextBlockFormat bfmt;
|
||||
bfmt.setIndent(0);
|
||||
cursor.setBlockFormat(bfmt);
|
||||
list->remove(cursor.block());
|
||||
} else {
|
||||
QTextListFormat listFormat;
|
||||
listFormat.setStyle(QTextListFormat::ListDecimal);
|
||||
cursor.createList(listFormat);
|
||||
}
|
||||
}
|
||||
|
||||
void RichTextEditor::onIndent() {
|
||||
QTextCursor cursor = textCursor();
|
||||
QTextBlockFormat bfmt = cursor.blockFormat();
|
||||
bfmt.setIndent(bfmt.indent() + 1);
|
||||
cursor.setBlockFormat(bfmt);
|
||||
}
|
||||
|
||||
void RichTextEditor::onOutdent() {
|
||||
QTextCursor cursor = textCursor();
|
||||
QTextBlockFormat bfmt = cursor.blockFormat();
|
||||
int indent = bfmt.indent();
|
||||
if (indent > 0) {
|
||||
bfmt.setIndent(indent - 1);
|
||||
cursor.setBlockFormat(bfmt);
|
||||
}
|
||||
}
|
||||
|
||||
void RichTextEditor::onAlignLeft() {
|
||||
QTextCursor cursor = textCursor();
|
||||
QTextBlockFormat bfmt = cursor.blockFormat();
|
||||
bfmt.setAlignment(Qt::AlignLeft);
|
||||
cursor.setBlockFormat(bfmt);
|
||||
}
|
||||
|
||||
void RichTextEditor::onAlignCenter() {
|
||||
QTextCursor cursor = textCursor();
|
||||
QTextBlockFormat bfmt = cursor.blockFormat();
|
||||
bfmt.setAlignment(Qt::AlignCenter);
|
||||
cursor.setBlockFormat(bfmt);
|
||||
}
|
||||
|
||||
void RichTextEditor::onAlignRight() {
|
||||
QTextCursor cursor = textCursor();
|
||||
QTextBlockFormat bfmt = cursor.blockFormat();
|
||||
bfmt.setAlignment(Qt::AlignRight);
|
||||
cursor.setBlockFormat(bfmt);
|
||||
}
|
||||
|
||||
void RichTextEditor::onAlignJustify() {
|
||||
QTextCursor cursor = textCursor();
|
||||
QTextBlockFormat bfmt = cursor.blockFormat();
|
||||
bfmt.setAlignment(Qt::AlignJustify);
|
||||
cursor.setBlockFormat(bfmt);
|
||||
}
|
||||
|
||||
void RichTextEditor::onFontChanged(const QFont &font) {
|
||||
QTextCharFormat fmt;
|
||||
fmt.setFontFamilies({font.family()});
|
||||
mergeCurrentCharFormat(fmt);
|
||||
}
|
||||
|
||||
void RichTextEditor::onFontSizeChanged(int size) {
|
||||
QTextCharFormat fmt;
|
||||
fmt.setFontPointSize(size);
|
||||
mergeCurrentCharFormat(fmt);
|
||||
}
|
||||
|
||||
void RichTextEditor::onInsertImage() {
|
||||
QString filePath = QFileDialog::getOpenFileName(this, "Insert Image", QString(), "Images (*.png *.jpg *.jpeg *.gif *.bmp)");
|
||||
if (filePath.isEmpty()) return;
|
||||
QTextCursor cursor = textCursor();
|
||||
QTextImageFormat imgFmt;
|
||||
imgFmt.setName(filePath);
|
||||
// Scale down if too large
|
||||
QPixmap pm(filePath);
|
||||
if (pm.width() > 600) {
|
||||
imgFmt.setWidth(600);
|
||||
imgFmt.setHeight(pm.height() * 600 / pm.width());
|
||||
}
|
||||
cursor.insertImage(imgFmt);
|
||||
}
|
||||
|
||||
void RichTextEditor::onInsertTable() {
|
||||
bool ok;
|
||||
int rows = QInputDialog::getInt(this, "Table Rows", "Rows:", 3, 1, 50, 1, &ok);
|
||||
if (!ok) return;
|
||||
int cols = QInputDialog::getInt(this, "Table Columns", "Columns:", 3, 1, 20, 1, &ok);
|
||||
if (!ok) return;
|
||||
|
||||
QTextCursor cursor = textCursor();
|
||||
QTextTableFormat tableFmt;
|
||||
tableFmt.setBorder(1);
|
||||
tableFmt.setBorderStyle(QTextFrameFormat::BorderStyle_Solid);
|
||||
tableFmt.setCellPadding(4);
|
||||
tableFmt.setCellSpacing(0);
|
||||
tableFmt.setWidth(QTextLength(QTextLength::PercentageLength, 100));
|
||||
cursor.insertTable(rows, cols, tableFmt);
|
||||
}
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
#include <QPixmap>
|
||||
#include <QStyle>
|
||||
#include <QApplication>
|
||||
#include <QFileIconProvider>
|
||||
ComposeView::ComposeView(QWidget *parent) : QWidget(parent) {
|
||||
setupUI();
|
||||
// Connect the rich text editor's signature signal to our slot
|
||||
@@ -282,14 +26,14 @@ ComposeView::ComposeView(QWidget *parent) : QWidget(parent) {
|
||||
ComposeView::~ComposeView() {
|
||||
// Destructor implementation
|
||||
}
|
||||
|
||||
void ComposeView::onSignatureClicked() {
|
||||
// This slot is called when the signature button in the rich text editor is clicked
|
||||
// The actual menu handling is done in RichTextEditor, so we just need to
|
||||
// handle any ComposeView-specific logic here if needed
|
||||
}
|
||||
|
||||
void ComposeView::onSignatureEditRequested()
|
||||
{
|
||||
void ComposeView::onSignatureEditRequested() {
|
||||
QDialog dialog(this);
|
||||
dialog.setWindowTitle(tr("Edit Signature"));
|
||||
dialog.setMinimumSize(600, 400);
|
||||
@@ -314,14 +58,13 @@ void ComposeView::onSignatureEditRequested()
|
||||
}
|
||||
}
|
||||
}
|
||||
void ComposeView::setAccountService(AccountService *service)
|
||||
{
|
||||
|
||||
void ComposeView::setAccountService(AccountService *service) {
|
||||
m_accountService = service;
|
||||
populateAccountCombo();
|
||||
}
|
||||
|
||||
void ComposeView::populateAccountCombo()
|
||||
{
|
||||
void ComposeView::populateAccountCombo() {
|
||||
if (!m_accountService) {
|
||||
qWarning() << "AccountService not set";
|
||||
return;
|
||||
@@ -342,8 +85,7 @@ void ComposeView::populateAccountCombo()
|
||||
}
|
||||
}
|
||||
|
||||
void ComposeView::onAccountChanged(int index)
|
||||
{
|
||||
void ComposeView::onAccountChanged(int index) {
|
||||
if (index <= 0) {
|
||||
// Placeholder item selected or invalid index
|
||||
m_currentAccountId = -1;
|
||||
@@ -354,8 +96,7 @@ void ComposeView::onAccountChanged(int index)
|
||||
loadSignatureForCurrentAccount();
|
||||
}
|
||||
|
||||
void ComposeView::loadSignatureForCurrentAccount()
|
||||
{
|
||||
void ComposeView::loadSignatureForCurrentAccount() {
|
||||
if (!m_accountService || m_currentAccountId <= 0) {
|
||||
// Clear signature if no account selected
|
||||
m_bodyEditor->clear();
|
||||
@@ -367,12 +108,12 @@ void ComposeView::loadSignatureForCurrentAccount()
|
||||
// For now, we'll just clear the editor since we don't have a signature field in Account yet
|
||||
// In a real implementation, we would retrieve and display the signature
|
||||
m_bodyEditor->clear();
|
||||
delete account;
|
||||
} else {
|
||||
m_bodyEditor->clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ComposeView::setupUI() {
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->setContentsMargins(20, 15, 20, 15);
|
||||
@@ -383,37 +124,9 @@ void ComposeView::setupUI() {
|
||||
"QLineEdit:focus, QTextEdit:focus { border-color: #1976D2; }"
|
||||
);
|
||||
|
||||
// Header row: Subject + Detach button
|
||||
QHBoxLayout *headerLayout = new QHBoxLayout();
|
||||
m_subjectField = new QLineEdit();
|
||||
m_subjectField->setPlaceholderText("Subject");
|
||||
m_subjectField->setFixedHeight(40);
|
||||
QFont subjectFont = m_subjectField->font();
|
||||
subjectFont.setPointSize(14);
|
||||
subjectFont.setBold(true);
|
||||
m_subjectField->setFont(subjectFont);
|
||||
headerLayout->addWidget(m_subjectField, 1);
|
||||
|
||||
m_detachButton = new QPushButton("\xe2\x87\xa5 Detach");
|
||||
m_detachButton->setToolTip("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);
|
||||
headerLayout->addWidget(m_detachButton);
|
||||
|
||||
mainLayout->addLayout(headerLayout);
|
||||
|
||||
// Separator line
|
||||
QFrame *line1 = new QFrame();
|
||||
line1->setFrameShape(QFrame::HLine);
|
||||
line1->setStyleSheet("color: #e0e0e0;");
|
||||
mainLayout->addWidget(line1);
|
||||
|
||||
// From: field + Account selector
|
||||
// De: field + Account selector (renamed from From:)
|
||||
QHBoxLayout *fromLayout = new QHBoxLayout();
|
||||
QLabel *fromLabel = new QLabel("From:");
|
||||
QLabel *fromLabel = new QLabel(tr("De:"));
|
||||
fromLabel->setFixedWidth(40);
|
||||
fromLabel->setStyleSheet("font-weight: bold; color: #555;");
|
||||
m_accountCombo = new QComboBox();
|
||||
@@ -429,16 +142,17 @@ void ComposeView::setupUI() {
|
||||
connect(m_accountCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||
this, &ComposeView::onAccountChanged);
|
||||
fromLayout->addWidget(fromLabel);
|
||||
fromLayout->addWidget(m_accountCombo, 1);
|
||||
fromLayout->addWidget(m_accountCombo);
|
||||
fromLayout->addStretch(); // Este resorte empuja el par (label+combo) a la izquierda
|
||||
mainLayout->addLayout(fromLayout);
|
||||
|
||||
// To: field + Cc/Bcc toggle buttons
|
||||
// Para: field + Cc/Bcc toggle buttons
|
||||
QHBoxLayout *toLayout = new QHBoxLayout();
|
||||
QLabel *toLabel = new QLabel("To:");
|
||||
toLabel->setFixedWidth(40);
|
||||
toLabel->setStyleSheet("font-weight: bold; color: #555;");
|
||||
m_toField = new QLineEdit();
|
||||
m_toField->setPlaceholderText("Recipients (comma separated)");
|
||||
QLabel *toLabel = new QLabel(tr("Para:"));
|
||||
toLabel->setFixedWidth(40);
|
||||
toLabel->setStyleSheet("font-weight: bold; color: #555;");
|
||||
m_toField = new everload_tags::TagsLineEdit();
|
||||
|
||||
toLayout->addWidget(toLabel);
|
||||
toLayout->addWidget(m_toField, 1);
|
||||
|
||||
@@ -466,19 +180,20 @@ void ComposeView::setupUI() {
|
||||
m_ccRow = new QWidget();
|
||||
QHBoxLayout *ccLayout = new QHBoxLayout(m_ccRow);
|
||||
ccLayout->setContentsMargins(0, 0, 0, 0);
|
||||
QLabel *ccLabel = new QLabel("Cc:");
|
||||
QLabel *ccLabel = new QLabel(tr("Cc:"));
|
||||
ccLabel->setFixedWidth(40);
|
||||
ccLabel->setStyleSheet("color: #555;");
|
||||
m_ccField = new QLineEdit();
|
||||
m_ccField->setPlaceholderText("Carbon copy");
|
||||
m_ccField = new everload_tags::TagsLineEdit();
|
||||
|
||||
ccLayout->addWidget(ccLabel);
|
||||
ccLayout->addWidget(m_ccField, 1);
|
||||
|
||||
m_hideCcButton = new QPushButton("x");
|
||||
m_hideCcButton = new QPushButton("");
|
||||
m_hideCcButton->setIcon(QIcon(QStringLiteral(":/icons/resources/icons/SVG/Linear/Essentional, UI/Close Square.svg")));
|
||||
m_hideCcButton->setFixedSize(20, 20);
|
||||
m_hideCcButton->setToolTip("Hide Cc");
|
||||
m_hideCcButton->setToolTip(tr("Hide Cc"));
|
||||
m_hideCcButton->setStyleSheet(
|
||||
"QPushButton { background: transparent; border: none; color: #999; font-weight: bold; }"
|
||||
"QPushButton { background: transparent; border: none; color: blue; font-weight: bold; }"
|
||||
"QPushButton:hover { color: #333; }"
|
||||
);
|
||||
connect(m_hideCcButton, &QPushButton::clicked, this, &ComposeView::onCcToggle);
|
||||
@@ -491,19 +206,20 @@ void ComposeView::setupUI() {
|
||||
m_bccRow = new QWidget();
|
||||
QHBoxLayout *bccLayout = new QHBoxLayout(m_bccRow);
|
||||
bccLayout->setContentsMargins(0, 0, 0, 0);
|
||||
QLabel *bccLabel = new QLabel("Bcc:");
|
||||
QLabel *bccLabel = new QLabel(tr("Bcc:"));
|
||||
bccLabel->setFixedWidth(40);
|
||||
bccLabel->setStyleSheet("color: #555;");
|
||||
m_bccField = new QLineEdit();
|
||||
m_bccField->setPlaceholderText("Blind carbon copy");
|
||||
m_bccField = new everload_tags::TagsLineEdit();
|
||||
|
||||
bccLayout->addWidget(bccLabel);
|
||||
bccLayout->addWidget(m_bccField, 1);
|
||||
|
||||
m_hideBccButton = new QPushButton("x");
|
||||
m_hideBccButton = new QPushButton("");
|
||||
m_hideBccButton->setFixedSize(20, 20);
|
||||
m_hideBccButton->setToolTip("Hide Bcc");
|
||||
m_hideBccButton->setIcon(QIcon(QStringLiteral(":/icons/resources/icons/SVG/Linear/Essentional, UI/Close Square.svg")));
|
||||
m_hideBccButton->setToolTip(tr("Hide Bcc"));
|
||||
m_hideBccButton->setStyleSheet(
|
||||
"QPushButton { background: transparent; border: none; color: #999; font-weight: bold; }"
|
||||
"QPushButton { background: transparent; border: none; color: blue; font-weight: bold; }"
|
||||
"QPushButton:hover { color: #333; }"
|
||||
);
|
||||
connect(m_hideBccButton, &QPushButton::clicked, this, &ComposeView::onBccToggle);
|
||||
@@ -512,11 +228,39 @@ void ComposeView::setupUI() {
|
||||
m_bccRow->setVisible(false);
|
||||
mainLayout->addWidget(m_bccRow);
|
||||
|
||||
// Separator
|
||||
QFrame *line2 = new QFrame();
|
||||
line2->setFrameShape(QFrame::HLine);
|
||||
line2->setStyleSheet("color: #e0e0e0;");
|
||||
mainLayout->addWidget(line2);
|
||||
// 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);
|
||||
|
||||
// Rich text editor with toolbar
|
||||
m_bodyEditor = new RichTextEditor();
|
||||
@@ -527,7 +271,7 @@ void ComposeView::setupUI() {
|
||||
m_schedulePanel = new QWidget();
|
||||
QHBoxLayout *scheduleLayout = new QHBoxLayout(m_schedulePanel);
|
||||
scheduleLayout->setContentsMargins(0, 0, 0, 0);
|
||||
QLabel *scheduleLabel = new QLabel("Send at:");
|
||||
QLabel *scheduleLabel = new QLabel(tr("Send at:"));
|
||||
scheduleLabel->setStyleSheet("color: #555; font-weight: bold;");
|
||||
m_schedulePicker = new QDateTimeEdit(QDateTime::currentDateTime().addSecs(3600));
|
||||
m_schedulePicker->setCalendarPopup(true);
|
||||
@@ -536,79 +280,91 @@ void ComposeView::setupUI() {
|
||||
scheduleLayout->addWidget(m_schedulePicker);
|
||||
scheduleLayout->addStretch();
|
||||
|
||||
m_scheduleSendButton = new QPushButton("Schedule Send");
|
||||
m_scheduleSendButton = new QPushButton(tr("Schedule Send"));
|
||||
m_scheduleSendButton->setFixedWidth(150);
|
||||
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);
|
||||
scheduleLayout->addWidget(m_scheduleSendButton);
|
||||
|
||||
scheduleLayout->addWidget(m_scheduleSendButton, 2);
|
||||
m_schedulePanel->setVisible(false);
|
||||
m_schedulePanel->setVisible(false);
|
||||
mainLayout->addWidget(m_schedulePanel);
|
||||
|
||||
// Attachment section
|
||||
QHBoxLayout *attachmentLayout = new QHBoxLayout();
|
||||
QLabel *attachmentLabel = new QLabel(tr("Attachments:"));
|
||||
attachmentLabel->setFixedWidth(80);
|
||||
attachmentLabel->setStyleSheet("font-weight: bold; color: #555;");
|
||||
attachmentLayout->addWidget(attachmentLabel);
|
||||
|
||||
m_attachButton = new QToolButton();
|
||||
m_attachButton->setIcon(QIcon(QStringLiteral(":/icons/attachment.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; }");
|
||||
bool connected = connect(m_attachButton, &QToolButton::clicked, this, &ComposeView::onAddAttachmentClicked);
|
||||
qDebug() << "Attach button connected:" << connected;
|
||||
attachmentLayout->addWidget(m_attachButton);
|
||||
|
||||
m_attachmentList = new QListWidget();
|
||||
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);
|
||||
m_attachmentList->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
m_attachmentList->setMaximumHeight(60);
|
||||
m_attachmentList->setStyleSheet("QListWidget { border: 1px solid #d1d1d6; border-radius: 4px; }");
|
||||
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);
|
||||
attachmentLayout->addWidget(m_attachmentList, 1);
|
||||
|
||||
m_removeAttachmentButton = new QToolButton();
|
||||
m_removeAttachmentButton->setIcon(QIcon(QStringLiteral(":/icons/trash.svg")));
|
||||
m_removeAttachmentButton->setToolTip(tr("Remove selected attachment"));
|
||||
m_removeAttachmentButton->setIconSize(QSize(20,20));
|
||||
m_removeAttachmentButton->setStyleSheet("QToolButton { border: none; padding: 5px; } QToolButton:hover { background: #e0e0e0; border-radius: 3px; }");
|
||||
connect(m_removeAttachmentButton, &QToolButton::clicked, this, &ComposeView::onRemoveAttachmentClicked);
|
||||
attachmentLayout->addWidget(m_removeAttachmentButton);
|
||||
|
||||
mainLayout->addLayout(attachmentLayout);
|
||||
|
||||
|
||||
// Action buttons row
|
||||
QHBoxLayout *actionsLayout = new QHBoxLayout();
|
||||
actionsLayout->addStretch();
|
||||
|
||||
m_discardButton = new QPushButton("Discard");
|
||||
// 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"));
|
||||
m_discardButton->setStyleSheet(
|
||||
"QPushButton { background: transparent; border: 1px solid #d1d1d6; border-radius: 4px; padding: 8px 20px; color: #555; }"
|
||||
"QPushButton:hover { background: #f5f5f5; }"
|
||||
);
|
||||
actionsLayout->addWidget(m_discardButton);
|
||||
connect(m_discardButton, &QPushButton::clicked, this, &ComposeView::discardRequested);
|
||||
actionsLayout->addWidget(m_discardButton);
|
||||
|
||||
// Split button for Send / Schedule
|
||||
m_sendMenu = new QMenu(this);
|
||||
m_sendNowAction = m_sendMenu->addAction("Send Now");
|
||||
m_sendNowAction = m_sendMenu->addAction(tr("Send Now"));
|
||||
connect(m_sendNowAction, &QAction::triggered, this, &ComposeView::onSendClicked);
|
||||
m_scheduleAction = m_sendMenu->addAction("Schedule for later...");
|
||||
m_scheduleAction = m_sendMenu->addAction(tr("Schedule for later..."));
|
||||
connect(m_scheduleAction, &QAction::triggered, [this]() {
|
||||
m_schedulePanel->setVisible(true);
|
||||
});
|
||||
|
||||
m_sendSplit = new QToolButton();
|
||||
m_sendSplit->setText("Send");
|
||||
m_sendSplit->setToolButtonStyle(Qt::ToolButtonTextOnly);
|
||||
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);
|
||||
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; }"
|
||||
"QToolButton::menu-button { border-left: 1px solid rgba(255,255,255,0.3); padding-left: 8px; padding-right: 8px; width: 40px;}"
|
||||
"QToolButton::menu-button { border-left: 1px solid rgba(255,255,255,0.3); padding-left: 8px; padding-right: 8px; width: 20px;}"
|
||||
"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);
|
||||
@@ -633,25 +389,27 @@ void ComposeView::onBccToggle() {
|
||||
|
||||
void ComposeView::onSendClicked() {
|
||||
emit sendRequested(
|
||||
m_toField->text(),
|
||||
m_ccVisible ? m_ccField->text() : QString(),
|
||||
m_bccVisible ? m_bccField->text() : QString(),
|
||||
m_toField->tags2().join(", "),
|
||||
m_ccVisible ? m_ccField->tags2().join(", ") : QString(),
|
||||
m_bccVisible ? m_bccField->tags2().join(", ") : QString(),
|
||||
m_subjectField->text(),
|
||||
m_bodyEditor->toHtml(),
|
||||
QDateTime(), // null = send now
|
||||
m_currentAccountId > 0 ? m_accountCombo->itemData(m_accountCombo->currentIndex()).toString() : QString()
|
||||
m_currentAccountId > 0 ? m_accountCombo->itemData(m_accountCombo->currentIndex()).toString() : QString(),
|
||||
m_attachmentFiles
|
||||
);
|
||||
}
|
||||
|
||||
void ComposeView::onScheduleClicked() {
|
||||
emit sendRequested(
|
||||
m_toField->text(),
|
||||
m_ccVisible ? m_ccField->text() : QString(),
|
||||
m_bccVisible ? m_bccField->text() : QString(),
|
||||
m_toField->tags2().join(", "),
|
||||
m_ccVisible ? m_ccField->tags2().join(", ") : QString(),
|
||||
m_bccVisible ? m_bccField->tags2().join(", ") : QString(),
|
||||
m_subjectField->text(),
|
||||
m_bodyEditor->toHtml(),
|
||||
m_schedulePicker->dateTime(),
|
||||
m_currentAccountId > 0 ? m_accountCombo->itemData(m_accountCombo->currentIndex()).toString() : QString()
|
||||
m_currentAccountId > 0 ? m_accountCombo->itemData(m_accountCombo->currentIndex()).toString() : QString(),
|
||||
m_attachmentFiles
|
||||
);
|
||||
}
|
||||
|
||||
@@ -659,15 +417,19 @@ void ComposeView::onDetachClicked() {
|
||||
emit detachRequested(this);
|
||||
}
|
||||
|
||||
void ComposeView::setTo(const QString &to) { m_toField->setText(to); }
|
||||
void ComposeView::setTo(const QString &to) { m_toField->tags(to.split(',', Qt::SkipEmptyParts)); }
|
||||
void ComposeView::setSubject(const QString &subject) { m_subjectField->setText(subject); }
|
||||
void ComposeView::setBody(const QString &body) { m_bodyEditor->setHtml(body); }
|
||||
|
||||
void ComposeView::initializeComposition() {
|
||||
m_toField->clear();
|
||||
m_ccField->clear();
|
||||
m_bccField->clear();
|
||||
m_toField->tags(QStringList{});
|
||||
m_ccField->tags(QStringList{});
|
||||
m_bccField->tags(QStringList{});
|
||||
m_subjectField->clear();
|
||||
m_bodyEditor->clear();
|
||||
m_attachmentFiles.clear();
|
||||
m_attachmentList->clear();
|
||||
m_attachmentList->setVisible(false);
|
||||
m_ccRow->setVisible(false);
|
||||
m_bccRow->setVisible(false);
|
||||
m_schedulePanel->setVisible(false);
|
||||
@@ -675,35 +437,100 @@ void ComposeView::initializeComposition() {
|
||||
}
|
||||
|
||||
void ComposeView::startNewEmail(const QString &initialRecipient) {
|
||||
initializeComposition();
|
||||
if (!initialRecipient.isEmpty())
|
||||
m_toField->setText(initialRecipient);
|
||||
m_toField->setFocus();
|
||||
}
|
||||
initializeComposition();
|
||||
if (!initialRecipient.isEmpty())
|
||||
m_toField->tags(initialRecipient.split(',', Qt::SkipEmptyParts));
|
||||
m_toField->setFocus();
|
||||
}
|
||||
|
||||
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(QFileInfo(file).fileName(), m_attachmentList);
|
||||
item->setToolTip(file);
|
||||
}
|
||||
QMessageBox::information(this, tr("Attachments"), tr("Attached %1 file(s).").arg(files.count()));
|
||||
}
|
||||
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());
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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());
|
||||
}
|
||||
|
||||
#include "composeview.moc"
|
||||
void ComposeView::onTemplateClicked() {
|
||||
// Placeholder for template functionality
|
||||
QMessageBox::information(this, tr("Email Templates"), tr("Email templates feature not yet implemented."));
|
||||
}
|
||||
|
||||
#include "composeview.moc"
|
||||
|
||||
Reference in New Issue
Block a user