Files
wino-mail-dtkqt/src/ui/modify_composeview.py
T
javier a004bdd60f 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
2026-08-17 15:34:49 +02:00

387 lines
16 KiB
Python

import sys
import re
with open('composeview.cpp', 'r') as f:
lines = f.readlines()
# 1. Remove AddressInputWidget class definition (lines approx 27-52)
# Find start and end lines.
new_lines = []
i = 0
while i < len(lines):
if lines[i].strip() == '// Address input widget with tokenized entries, delete button, drag-drop reorder, and autocomplete':
# Skip until after the closing brace of the class
# Find the line with '};' after the class
j = i
while j < len(lines) and not lines[j].strip().startswith('};'):
j += 1
# include the '};' line
j += 1
i = j
continue
new_lines.append(lines[i])
i += 1
lines = new_lines
# 2. Replace member variable types
# We'll process line by line, replacing specific patterns.
out_lines = []
for line in lines:
# Replace member declarations
if 'AddressInputWidget *m_toWidget = nullptr;' in line:
line = line.replace('AddressInputWidget *m_toWidget = nullptr;', 'QLineEdit *m_toField = nullptr;')
if 'AddressInputWidget *m_ccWidget = nullptr;' in line:
line = line.replace('AddressInputWidget *m_ccWidget = nullptr;', 'QLineEdit *m_ccField = nullptr;')
if 'AddressInputWidget *m_bccWidget = nullptr;' in line:
line = line.replace('AddressInputWidget *m_bccWidget = nullptr;', 'QLineEdit *m_bccField = nullptr;')
# Remove duplicate m_ccField and m_bccField lines (they are later)
if 'QLineEdit *m_ccField = nullptr; // kept for compatibility? actually not used now' in line:
# skip this line
continue
if 'QLineEdit *m_bccField = nullptr;' in line and '// kept for compatibility' not in line:
# This is the second duplicate; we already have one above; we can keep only one.
# We'll skip this line.
continue
out_lines.append(line)
lines = out_lines
# Now we need to modify setAccountService, setupUI, slots, etc.
# We'll do a more aggressive approach: replace entire sections using markers.
# Let's join back to string and use regex.
text = ''.join(lines)
# Replace setAccountService function
pattern_setAccountService = re.compile(r'void ComposeView::setAccountService\(AccountService \*service\)\s*\{[\s\S]*?\}', re.MULTILINE)
def repl_setAccountService(match):
return '''void ComposeView::setAccountService(AccountService *service)
{
m_accountService = service;
populateAccountCombo();
// Update completer models for address widgets with known emails
if (m_accountService) {
QStringList knownEmails;
const QVector<Account> accounts = m_accountService->getAllAccounts();
for (const Account &acc : accounts) {
knownEmails << acc.email();
}
// Set completion model for each widget
QStringListModel *model = new QStringListModel(knownEmails, this);
QCompleter *comp = new QCompleter(model, this);
comp->setCaseSensitivity(Qt::CaseInsensitive);
if (m_toField) m_toField->setCompleter(comp);
// For cc and bcc we need separate completers but can share model
QCompleter *comp2 = new QCompleter(model, this);
comp2->setCaseSensitivity(Qt::CaseInsensitive);
if (m_ccField) m_ccField->setCompleter(comp2);
QCompleter *comp3 = new QCompleter(model, this);
comp3->setCaseSensitivity(Qt::CaseInsensitive);
if (m_bccField) m_bccField->setCompleter(comp3);
}
}'''
text = re.sub(pattern_setAccountService, repl_setAccountService, text)
# Replace setupCore function: we need to replace the whole function.
# We'll rewrite from scratch using a marker: we will replace the whole function body.
# Let's capture the function and replace.
pattern_setupUI = re.compile(r'void ComposeView::setupUI\(\)\s*\{[\s\S]*?\n\}\s*\n(?=void|\Z)', re.MULTILINE)
def repl_setupUI(match):
return '''void ComposeView::setupUI()
{
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(20,15,20,15);
mainLayout->setSpacing(8);
// Top row: Account selector and detach button
QHBoxLayout *topLayout = new QHBoxLayout();
m_accountLabel = new QLabel(tr(\"From:\"), this);
m_accountLabel->setFixedWidth(40);
m_accountLabel->setStyleSheet(QStringLiteral(\"font-weight: bold; color: #555;\"));
m_accountCombo = new QComboBox(this);
m_accountCombo->setFixedWidth(180);
m_accountCombo->setStyleSheet(
\"QComboBox { border: 1px solid #d1d1d6; border-radius: 4px; padding: 6px 8px; \"
\"background: white; min-height: 20px; }\"\n
\"QComboBox:hover { border-color: #bdbdbd; }\"\n
\"QComboBox:focus { border-color: #1976D2; }\"
);
m_detachButton = new QPushButton(this);
m_detachButton->setToolTip(tr(\"Detach compose window\"));
m_detachButton->setText(QStringLiteral(\"\"));
m_detachButton->setFixedSize(28,28);
m_detachButton->setStyleSheet(
\"QPushButton { background: transparent; border: 1px solid #d1d1d6; border-radius: 4px; \"
\"padding: 6px; color: #555; font-size: 14px; }\"\n
\"QPushButton:hover { background: #f0f0f0; }\"\n
);
connect(m_detachButton, &QPushButton::clicked, this, &ComposeView::onDetachClicked);
topLayout->addWidget(m_accountLabel);
topLayout->addWidget(m_accountCombo, 1);
topLayout->addWidget(m_detachButton);
mainLayout->addLayout(topLayout);
// Separator line
QFrame *line1 = new QFrame(this);
line1->setFrameShape(QFrame::HLine);
line1->setStyleSheet(QStringLiteral(\"color: #e0e0e0;\"));
mainLayout->addWidget(line1);
// To row
QHBoxLayout *toLayout = new QHBoxLayout();
QLabel *toLabel = new QLabel(tr(\"To:\"), this);
toLabel->setFixedWidth(40);
toLabel->setStyleSheet(QStringLiteral(\"font-weight: bold; color: #555;\"));
m_toField = new QLineEdit(this);
m_toField->setPlaceholderText(tr(\"To recipients\"));
toLayout->addWidget(toLabel);
toLayout->addWidget(m_toField, 1);
mainLayout->addLayout(toLayout);
// CC/BCC toggle buttons
QHBoxLayout *toggleLayout = new QHBoxLayout();
m_ccButton = new QPushButton(tr(\"CC\"), this);
m_ccButton->setCheckable(true);
m_ccButton->setChecked(false);
connect(m_ccButton, &QPushButton::toggled, this, &ComposeView::onCcToggled);
m_bccButton = new QPushButton(tr(\"BCC\"), this);
m_bccButton->setCheckable(true);
m_bccButton->setChecked(false);
connect(m_bccButton, &QPushButton::toggled, this, &ComposeView::onBccToggled);
toggleLayout->addWidget(m_ccButton);
toggleLayout->addWidget(m_bccButton);
toggleLayout->addStretch();
mainLayout->addLayout(toggleLayout);
// Cc row (hidden by default)
m_ccRow = new QWidget(this);
QHBoxLayout *ccLayout = new QHBoxLayout(m_ccRow);
ccLayout->setContentsMargins(0,0,0,0);
QLabel *ccLabel = new QLabel(tr(\"Cc:\"), this);
ccLabel->setFixedWidth(40);
ccLabel->setStyleSheet(QStringLiteral(\"color: #555;\"));
m_ccField = new QLineEdit(this);
m_ccField->setPlaceholderText(tr(\"Cc recipients\"));
ccLayout->addWidget(ccLabel);
ccLayout->addWidget(m_ccField, 1);
m_ccRow->setVisible(false);
mainLayout->addWidget(m_ccRow);
// Bcc row (hidden by default)
m_bccRow = new QWidget(this);
QHBoxLayout *bccLayout = new QHBoxLayout(m_bccRow);
bccLayout->setContentsMargins(0,0,0,0);
QLabel *bccLabel = new QLabel(tr(\"Bcc:\"), this);
bccLabel->setFixedWidth(40);
bccLabel->setStyleSheet(QStringLiteral(\"color: #555;\"));
m_bccField = new QLineEdit(this);
m_bccField->setPlaceholderText(tr(\"Bcc recipients\"));
bccLayout->addWidget(bccLabel);
bccLayout->addWidget(m_bccField, 1);
m_bccRow->setVisible(false);
mainLayout->addWidget(m_bccRow);
// Separator
QFrame *line2 = new QFrame(this);
line2->setFrameShape(QFrame::HLine);
line2->setStyleSheet(QStringLiteral(\"color: #e0e0e0;\"));
mainLayout->addWidget(line2);
// Subject field
QLabel *subjectLabel = new QLabel(tr(\"Subject:\"), this);
subjectLabel->setFixedWidth(50);
subjectLabel->setStyleSheet(QStringLiteral(\"font-weight: bold; color: #555;\"));
m_subjectField = new QLineEdit(this);
m_subjectField->setPlaceholderText(tr(\"Subject\"));
m_subjectField->setFixedHeight(36);
QFont subjectFont = m_subjectField->font();
subjectFont.setPointSize(14);
subjectFont.setBold(true);
m_subjectField->setFont(subjectFont);
QHBoxLayout *subjectLayout = new QHBoxLayout();
subjectLayout->addWidget(subjectLabel);
subjectLayout->addWidget(m_subjectField, 1);
mainLayout->addLayout(subjectLayout);
// Body editor with toolbar
m_bodyEditor = new RichTextEditor(this);
m_bodyEditor->setupToolbar(mainLayout);
mainLayout->addWidget(m_bodyEditor, 1);
// Schedule panel (hidden by default)
m_schedulePanel = new QWidget(this);
QHBoxLayout *scheduleLayout = new QHBoxLayout(m_schedulePanel);
scheduleLayout->setContentsMargins(0,0,0,0);
QLabel *scheduleLabel = new QLabel(tr(\"Send at:\"), this);
scheduleLabel->setStyleSheet(QStringLiteral(\"color: #555; font-weight: bold;\"));
m_schedulePicker = new QDateTimeEdit(QDateTime::currentDateTime().addSecs(3600), this);
m_schedulePicker->setCalendarPopup(true);
m_schedulePicker->setDisplayFormat(QStringLiteral(\"dd/MM/yyyy hh:mm AP\"));
scheduleLayout->addWidget(scheduleLabel);
scheduleLayout->addWidget(m_schedulePicker);
scheduleLayout->addStretch();
m_scheduleSendButton = new QPushButton(tr(\"Schedule Send\"), this);
m_scheduleSendButton->setStyleSheet(
\"QPushButton { background-color: #1976D2; color: white; border: none; border-radius: 4px; \"
\"padding: 6px 16px; font-weight: bold; }\"\n
\"QPushButton:hover { background-color: #1565C0; }\"\n
);
connect(m_scheduleSendButton, &QPushButton::clicked, this, &ComposeView::onScheduleClicked);
scheduleLayout->addWidget(m_scheduleSendButton);
m_schedulePanel->setVisible(false);
mainLayout->addWidget(m_schedulePanel);
// Action buttons row
QHBoxLayout *actionsLayout = new QHBoxLayout();
actionsLayout->addStretch();
m_discardButton = new QPushButton(tr(\"Discard\"), this);
m_discardButton->setStyleSheet(
\"QPushButton { background: transparent; border: 1px solid #d1d1d6; border-radius: 4px; \"
\"padding: 8px 20px; color: #555; }\"\n
\"QPushButton:hover { background: #f5f5f5; }\"\n
);
actionsLayout->addWidget(m_discardButton);
connect(m_discardButton, &QPushButton::clicked, this, &ComposeView::discardRequested);
// Split button for Send / Schedule
m_sendMenu = new QMenu(this);
m_sendNowAction = m_sendMenu->addAction(tr(\"Send Now\"));
connect(m_sendNowAction, &QAction::triggered, this, &ComposeView::onSendClicked);
m_scheduleAction = m_sendMenu->addAction(tr(\"Schedule for later...\"));
connect(m_scheduleAction, &QAction::triggered, [this]() {
m_schedulePanel->setVisible(true);
});
m_sendSplit = new QToolButton(this);
m_sendSplit->setText(tr(\"Send\"));
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; }\"\n
\"QToolButton:hover { background-color: #1565C0; }\"\n
\"QToolButton::menu-button { border-left: 1px solid rgba(255,255,255,0.3); padding-left: 8px; padding-right: 8px; }\"\n
\"QToolButton::menu-button:hover { background-color: #1565C0; border-top-right-radius: 4px; border-bottom-right-radius: 4px; }\"\n
);
connect(m_sendSplit, &QToolButton::clicked, this, &ComposeView::onSendClicked);
actionsLayout->addWidget(m_sendSplit);
mainLayout->addLayout(actionsLayout);
// Connect rich text editor signature signals
connect(m_bodyEditor, &RichTextEditor::signatureClicked, this, &ComposeView::onSignatureClicked);
connect(m_bodyEditor, &RichTextEditor::signatureEditRequested, this, &ComposeView::onSignatureEditRequested);
}
'''
text = re.sub(pattern_setupUI, repl_setupUI, text)
# Replace onCcToggle slot (we renamed to onCcToggled with bool)
pattern_onCcToggle = re.compile(r'void ComposeView::onCcToggle\(\)\s*\{[\s\S]*?\n\}\s*\n', re.MULTILINE)
def repl_onCcToggle(match):
return '''void ComposeView::onCcToggled(bool checked)
{
m_ccRow->setVisible(checked);
if (checked) {
m_ccField->setFocus();
}
}
'''
text = re.sub(pattern_onCcToggle, repl_onCcToggle, text)
# Replace onBccToggle similarly
pattern_onBccToggle = re.compile(r'void ComposeView::onBccToggle\(\)\s*\{[\s\S]*?\n\}\s*\n', re.MULTILINE)
def repl_onBccToggle(match):
return '''void ComposeView::onBccToggled(bool checked)
{
m_bccRow->setVisible(checked);
if (checked) {
m_bccField->setFocus();
}
}
'''
text = re.sub(pattern_onBccToggle, repl_onBccToggle, text)
# Replace onSendClicked
pattern_onSendClicked = re.compile(r'void ComposeView::onSendClicked\(\)\s*\{[\s\S]*?\n\}\s*\n', re.MULTILINE)
def repl_onSendClicked(match):
return '''void ComposeView::onSendClicked()
{
QString to = m_toField->text();
QString cc = m_ccButton->isChecked() ? m_ccField->text() : QString();
QString bcc = m_bccButton->isChecked() ? m_bccField->text() : QString();
QString subject = m_subjectField->text();
QString body = m_bodyEditor->toHtml();
QDateTime scheduleTime = m_schedulePanel->isVisible() ? m_schedulePicker->dateTime() : QDateTime();
QString fromAddress;
if (m_currentAccountId > 0 && m_accountCombo->currentIndex() > 0) {
fromAddress = m_accountCombo->currentData().toString();
}
emit sendRequested(to, cc, bcc, subject, body, scheduleTime, fromAddress);
}
'''
text = re.sub(pattern_onSendClicked, repl_onSendClicked, text)
# Replace initializeComposition
pattern_initComp = re.compile(r'void ComposeView::initializeComposition\(\)\s*\{[\s\S]*?\n\}\s*\n', re.MULTILINE)
def repl_initComp(match):
return '''void ComposeView::initializeComposition()
{
m_subjectField->clear();
m_bodyEditor->clear();
m_toField->clear();
m_ccField->clear();
m_bccField->clear();
m_ccRow->setVisible(false);
m_bccRow->setVisible(false);
m_schedulePanel->setVisible(false);
m_subjectField->setFocus();
}
'''
text = re.sub(pattern_initComp, repl_initComp, text)
# Replace startNewEmail
pattern_startNew = re.compile(r'void ComposeView::startNewEmail\(const QString \&initialRecipient\)\s*\{[\s\S]*?\n\}\s*\n', re.MULTILINE)
def repl_startNew(match):
return '''void ComposeView::startNewEmail(const QString &initialRecipient)
{
initializeComposition();
if (!initialRecipient.isEmpty()) {
m_toField->setText(initialRecipient);
}
m_toField->setFocus();
}
'''
text = re.sub(pattern_startNew, repl_startNew, text)
# Replace setTo
pattern_setTo = re.compile(r'void ComposeView::setTo\(const QString \&to\)\s*\{[\s\S]*?\n\}\s*\n', re.MULTILINE)
def repl_setTo(match):
return '''void ComposeView::setTo(const QString &to)
{
if (to.isEmpty()) {
m_toField->clear();
return;
}
// Split by semicolon or comma
QStringList parts = to.split(QRegularExpression(\"[,;]\"), Qt::SkipEmptyParts);
QStringList cleaned;
for (const QString &part : parts) {
QString trimmed = part.trimmed();
if (!trimmed.isEmpty())
cleaned << trimmed;
}
m_toField->setText(cleaned.join(\"; \"));
}
'''
text = re.sub(pattern_setTo, repl_setTo, text)
# We also need to add a helper function splitAddresses if we used it, but we didn't.
# Ensure we have the forward declaration for the slot in header (already done).
# Now write the file.
with open('composeview.cpp', 'w') as f:
f.write(text)