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:
@@ -0,0 +1,462 @@
|
||||
#include "richtexteditor.h"
|
||||
#include <QFrame>
|
||||
#include <QFileDialog>
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QDebug>
|
||||
#include <QDialog>
|
||||
#include <QVBoxLayout>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include "../../thirdparty/tags/include/tags_line_edit.hpp"
|
||||
#include <QRegularExpression>
|
||||
#include <QTimer>
|
||||
#include <QTextList>
|
||||
#include <QTextTable>
|
||||
#include <QTextCursor>
|
||||
#include <QTextBlockFormat>
|
||||
#include <QTextCharFormat>
|
||||
#include <QTextImageFormat>
|
||||
#include <QTextTableFormat>
|
||||
#include <QTextLength>
|
||||
#include <QPixmap>
|
||||
#include <QMouseEvent>
|
||||
#include <QContextMenuEvent>
|
||||
#include <QApplication>
|
||||
#include <QMenu>
|
||||
#include <QAction>
|
||||
#include <QWidget>
|
||||
#include <QLabel>
|
||||
#include <QResizeEvent>
|
||||
#include <QTextTableCell>
|
||||
|
||||
// ===================== RichTextEditor =====================
|
||||
|
||||
RichTextEditor::RichTextEditor(QWidget *parent) : QTextEdit(parent) {
|
||||
setAcceptRichText(true);
|
||||
setPlaceholderText("Write your message here...");
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
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; }\n"
|
||||
"QToolButton { padding: 4px 6px; border-radius: 3px; }\n"
|
||||
"QToolButton:hover { background: #e0e0e0; }\n"
|
||||
"QToolButton:checked { background: #bbdefb; }\n"
|
||||
);
|
||||
|
||||
// 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(11);
|
||||
m_fontSizeSpin->setFixedWidth(70);
|
||||
m_fontSizeSpin->setFixedHeight(24);
|
||||
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);
|
||||
boldAct->setIcon(QIcon(QStringLiteral(":/icons/resources/icons/SVG/Bold/Text Formatting/Text Bold.svg")));
|
||||
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);
|
||||
italicAct->setIcon(QIcon(QStringLiteral(":/icons/resources/icons/SVG/Outline/Text Formatting/Text Italic.svg")));
|
||||
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);
|
||||
underlineAct->setIcon(QIcon(QStringLiteral(":/icons/resources/icons/SVG/Outline/Text Formatting/Text Underline.svg")));
|
||||
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; }\n"
|
||||
"QToolButton:hover { background: #e0e0e0; }\n"
|
||||
"QToolButton::menu-button { border-left: 1px solid rgba(0,0,0,0.1); width: 12px; }\n"
|
||||
);
|
||||
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);
|
||||
}
|
||||
|
||||
/* ===================== Mouse events for image/table resizing ===================== */
|
||||
void RichTextEditor::mousePressEvent(QMouseEvent *event) {
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
QPoint pos = event->pos();
|
||||
QTextCursor cursor = cursorForPosition(pos);
|
||||
// Image
|
||||
if (cursor.charFormat().isImageFormat()) {
|
||||
qDebug() << "Mouse press on image";
|
||||
m_resizingImage = true;
|
||||
m_resizeStartPos = pos;
|
||||
QTextImageFormat imgFormat = cursor.charFormat().toImageFormat();
|
||||
m_imageStartSize = QSize(imgFormat.width(), imgFormat.height());
|
||||
if (m_imageStartSize.isEmpty()) {
|
||||
QPixmap pm(imgFormat.name());
|
||||
if (!pm.isNull())
|
||||
m_imageStartSize = pm.size();
|
||||
}
|
||||
// Change cursor to indicate resizing
|
||||
setCursor(Qt::SizeAllCursor);
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
// Table
|
||||
QTextTable *table = cursor.currentTable();
|
||||
if (table) {
|
||||
qDebug() << "Mouse press on table";
|
||||
m_resizingTable = true;
|
||||
m_tableStartPos = pos;
|
||||
m_currentTable = table;
|
||||
QTextTableFormat fmt = table->format();
|
||||
m_tableStartSize = QSizeF(fmt.width().rawValue(), fmt.height().rawValue());
|
||||
setCursor(Qt::SizeAllCursor);
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
}
|
||||
QTextEdit::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void RichTextEditor::mouseMoveEvent(QMouseEvent *event) {
|
||||
if (m_resizingImage) {
|
||||
qDebug() << "Mouse move resizing image";
|
||||
QPoint delta = event->pos() - m_resizeStartPos;
|
||||
int newWidth = qMax(20, m_imageStartSize.width() + delta.x());
|
||||
int newHeight = qMax(20, m_imageStartSize.height() + delta.y());
|
||||
QTextCursor cursor = cursorForPosition(m_resizeStartPos);
|
||||
if (cursor.charFormat().isImageFormat()) {
|
||||
QTextImageFormat fmt = cursor.charFormat().toImageFormat();
|
||||
fmt.setWidth(newWidth);
|
||||
fmt.setHeight(newHeight);
|
||||
cursor.mergeCharFormat(fmt);
|
||||
setTextCursor(cursor);
|
||||
}
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
if (m_resizingTable) {
|
||||
qDebug() << "Mouse move resizing table";
|
||||
QPoint delta = event->pos() - m_tableStartPos;
|
||||
qreal newWidth = qMax(10.0, m_tableStartSize.width() + delta.x());
|
||||
qreal newHeight = qMax(10.0, m_tableStartSize.height() + delta.y());
|
||||
QTextTableFormat fmt = m_currentTable->format();
|
||||
fmt.setWidth(QTextLength(QTextLength::FixedLength, newWidth));
|
||||
fmt.setHeight(QTextLength(QTextLength::FixedLength, newHeight));
|
||||
m_currentTable->setFormat(fmt);
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
QTextEdit::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void RichTextEditor::mouseReleaseEvent(QMouseEvent *event) {
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
if (m_resizingImage) {
|
||||
qDebug() << "Mouse release image resize";
|
||||
m_resizingImage = false;
|
||||
setCursor(Qt::ArrowCursor);
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
if (m_resizingTable) {
|
||||
qDebug() << "Mouse release table resize";
|
||||
m_resizingTable = false;
|
||||
setCursor(Qt::ArrowCursor);
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
}
|
||||
QTextEdit::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
void RichTextEditor::contextMenuEvent(QContextMenuEvent *event) {
|
||||
QPoint pos = event->pos();
|
||||
QTextCursor cursor = cursorForPosition(pos);
|
||||
QTextTable *table = cursor.currentTable();
|
||||
if (table) {
|
||||
showTableContextMenu(pos);
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
QTextEdit::contextMenuEvent(event);
|
||||
}
|
||||
|
||||
/* ===================== Helper functions ===================== */
|
||||
void RichTextEditor::showTableContextMenu(const QPoint &pos) {
|
||||
if (!m_tableContextMenu) {
|
||||
m_tableContextMenu = new QMenu(this);
|
||||
m_tableContextMenu->addAction(tr("Insert row above"), this, &RichTextEditor::addTableRow);
|
||||
m_tableContextMenu->addAction(tr("Insert row below"), this, &RichTextEditor::addTableRow);
|
||||
m_tableContextMenu->addAction(tr("Insert column left"), this, &RichTextEditor::addTableColumn);
|
||||
m_tableContextMenu->addAction(tr("Insert column right"), this, &RichTextEditor::addTableColumn);
|
||||
m_tableContextMenu->addSeparator();
|
||||
m_tableContextMenu->addAction(tr("Delete selected rows"), this, &RichTextEditor::removeTableRow);
|
||||
m_tableContextMenu->addAction(tr("Delete selected columns"), this, &RichTextEditor::removeTableColumn);
|
||||
m_tableContextMenu->addSeparator();
|
||||
m_tableContextMenu->addAction(tr("Border..."), this, &RichTextEditor::modifyTableBorder);
|
||||
}
|
||||
m_tableContextMenu->exec(mapToGlobal(pos));
|
||||
}
|
||||
|
||||
void RichTextEditor::addTableRow() {
|
||||
if (!m_currentTable) return;
|
||||
QTextCursor cursor = textCursor();
|
||||
QTextTable *table = cursor.currentTable();
|
||||
if (!table) return;
|
||||
QTextTableCell cell = table->cellAt(cursor.position());
|
||||
int row = cell.row();
|
||||
table->insertRows(row, 1);
|
||||
}
|
||||
|
||||
void RichTextEditor::removeTableRow() {
|
||||
if (!m_currentTable) return;
|
||||
QTextCursor cursor = textCursor();
|
||||
QTextTable *table = cursor.currentTable();
|
||||
if (!table) return;
|
||||
QTextTableCell cell = table->cellAt(cursor.position());
|
||||
int row = cell.row();
|
||||
table->removeRows(row, 1);
|
||||
}
|
||||
|
||||
void RichTextEditor::addTableColumn() {
|
||||
if (!m_currentTable) return;
|
||||
QTextCursor cursor = textCursor();
|
||||
QTextTable *table = cursor.currentTable();
|
||||
if (!table) return;
|
||||
QTextTableCell cell = table->cellAt(cursor.position());
|
||||
int col = cell.column();
|
||||
table->insertColumns(col, 1);
|
||||
}
|
||||
|
||||
void RichTextEditor::removeTableColumn() {
|
||||
if (!m_currentTable) return;
|
||||
QTextCursor cursor = textCursor();
|
||||
QTextTable *table = cursor.currentTable();
|
||||
if (!table) return;
|
||||
QTextTableCell cell = table->cellAt(cursor.position());
|
||||
int col = cell.column();
|
||||
table->removeColumns(col, 1);
|
||||
}
|
||||
|
||||
void RichTextEditor::modifyTableBorder() {
|
||||
if (!m_currentTable) return;
|
||||
bool ok;
|
||||
int border = QInputDialog::getInt(this, tr("Table Border"), tr("Border width (px):"),
|
||||
m_currentTable->format().border(), 0, 20, 1, &ok);
|
||||
if (!ok) return;
|
||||
QTextTableFormat fmt = m_currentTable->format();
|
||||
fmt.setBorder(border);
|
||||
m_currentTable->setFormat(fmt);
|
||||
}
|
||||
Reference in New Issue
Block a user