Fix build: synchronize Request API, fix GmailSynchronizer, and migrate UI to Qt6 Widgets
This commit is contained in:
@@ -0,0 +1,407 @@
|
||||
#include "newmessagedialog.h"
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QFormLayout>
|
||||
#include <QMessageBox>
|
||||
#include <QFileDialog>
|
||||
#include <QDateTime>
|
||||
#include <QTextList>
|
||||
#include <QTextTable>
|
||||
#include <QFileInfo>
|
||||
#include <QCloseEvent>
|
||||
|
||||
NewMessageDialog::NewMessageDialog(const QStringList &accounts, QWidget *parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
setWindowTitle("New Message");
|
||||
setMinimumSize(750, 600);
|
||||
resize(850, 700);
|
||||
|
||||
setupUI();
|
||||
|
||||
// Init state
|
||||
m_ccEdit->hide();
|
||||
m_bccEdit->hide();
|
||||
m_schedulePicker->hide();
|
||||
|
||||
// Capture initial content for dirty detection
|
||||
m_initialContent = m_bodyEdit->toPlainText();
|
||||
|
||||
// Signals
|
||||
connect(m_sendNowBtn, &QPushButton::clicked, this, &NewMessageDialog::onSendNow);
|
||||
connect(m_sendLaterBtn, &QPushButton::clicked, this, &NewMessageDialog::onSendLater);
|
||||
}
|
||||
|
||||
void NewMessageDialog::setupUI()
|
||||
{
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->setContentsMargins(16, 12, 16, 12);
|
||||
mainLayout->setSpacing(8);
|
||||
|
||||
// ===== HEADER FIELDS =====
|
||||
QVBoxLayout *headerLayout = new QVBoxLayout();
|
||||
headerLayout->setSpacing(6);
|
||||
|
||||
// From
|
||||
QHBoxLayout *fromRow = new QHBoxLayout();
|
||||
m_fromCombo = new QComboBox(this);
|
||||
m_fromCombo->setMinimumWidth(400);
|
||||
fromRow->addWidget(new QLabel("From:", this));
|
||||
fromRow->addWidget(m_fromCombo, 1);
|
||||
headerLayout->addLayout(fromRow);
|
||||
|
||||
// To + CC/CCO buttons
|
||||
QHBoxLayout *toRow = new QHBoxLayout();
|
||||
m_toEdit = new QLineEdit(this);
|
||||
m_toEdit->setPlaceholderText("To");
|
||||
toRow->addWidget(new QLabel("To:", this));
|
||||
toRow->addWidget(m_toEdit, 1);
|
||||
m_ccBtn = new QPushButton("CC", this);
|
||||
m_ccBtn->setFixedWidth(48);
|
||||
m_ccBtn->setStyleSheet("QPushButton { border: 1px solid #ccc; border-radius: 4px; padding: 4px 8px; font-size: 11px; background: #f0f0f0; } QPushButton:hover { background: #e0e0e0; }");
|
||||
m_bccBtn = new QPushButton("BCC", this);
|
||||
m_bccBtn->setFixedWidth(48);
|
||||
m_bccBtn->setStyleSheet(m_ccBtn->styleSheet());
|
||||
toRow->addWidget(m_ccBtn);
|
||||
toRow->addWidget(m_bccBtn);
|
||||
headerLayout->addLayout(toRow);
|
||||
|
||||
connect(m_ccBtn, &QPushButton::clicked, this, &NewMessageDialog::toggleCc);
|
||||
connect(m_bccBtn, &QPushButton::clicked, this, &NewMessageDialog::toggleBcc);
|
||||
|
||||
// CC
|
||||
m_ccEdit = new QLineEdit(this);
|
||||
m_ccEdit->setPlaceholderText("Cc");
|
||||
QHBoxLayout *ccRow = new QHBoxLayout();
|
||||
ccRow->addWidget(new QLabel("Cc:", this));
|
||||
ccRow->addWidget(m_ccEdit, 1);
|
||||
headerLayout->addLayout(ccRow);
|
||||
|
||||
// BCC
|
||||
m_bccEdit = new QLineEdit(this);
|
||||
m_bccEdit->setPlaceholderText("Bcc");
|
||||
QHBoxLayout *bccRow = new QHBoxLayout();
|
||||
bccRow->addWidget(new QLabel("Bcc:", this));
|
||||
bccRow->addWidget(m_bccEdit, 1);
|
||||
headerLayout->addLayout(bccRow);
|
||||
|
||||
// Subject
|
||||
QHBoxLayout *subjRow = new QHBoxLayout();
|
||||
m_subjectEdit = new QLineEdit(this);
|
||||
m_subjectEdit->setPlaceholderText("Subject");
|
||||
subjRow->addWidget(new QLabel("Subject:", this));
|
||||
subjRow->addWidget(m_subjectEdit, 1);
|
||||
headerLayout->addLayout(subjRow);
|
||||
|
||||
mainLayout->addLayout(headerLayout);
|
||||
|
||||
// ===== RICH TEXT TOOLBAR =====
|
||||
m_richToolbar = new QToolBar("Format", this);
|
||||
m_richToolbar->setIconSize(QSize(16, 16));
|
||||
m_richToolbar->setStyleSheet(
|
||||
"QToolBar { border: 1px solid #d1d1d6; border-radius: 4px; background: #f9f9f9; spacing: 4px; padding: 2px; }"
|
||||
);
|
||||
setupRichTextToolbar(m_richToolbar);
|
||||
mainLayout->addWidget(m_richToolbar);
|
||||
|
||||
// ===== BODY EDITOR =====
|
||||
m_bodyEdit = new QTextEdit(this);
|
||||
m_bodyEdit->setAcceptRichText(true);
|
||||
m_bodyEdit->setPlaceholderText("Write your message...");
|
||||
m_bodyEdit->setFrameShape(QFrame::StyledPanel);
|
||||
m_bodyEdit->setStyleSheet(
|
||||
"QTextEdit { border: 1px solid #d1d1d6; border-radius: 4px; padding: 8px; font-size: 13px; font-family: 'Segoe UI', Roboto, Helvetica; }"
|
||||
"QTextEdit:focus { border-color: #0078d4; }"
|
||||
);
|
||||
mainLayout->addWidget(m_bodyEdit, 1);
|
||||
|
||||
connect(m_bodyEdit, &QTextEdit::currentCharFormatChanged, this, &NewMessageDialog::currentCharFormatChanged);
|
||||
|
||||
// ===== ATTACHMENTS =====
|
||||
QHBoxLayout *attachRow = new QHBoxLayout();
|
||||
m_attachBtn = new QPushButton("Attach Files...", this);
|
||||
m_attachBtn->setStyleSheet("QPushButton { border: 1px solid #ccc; border-radius: 4px; padding: 6px 12px; font-size: 12px; background: #f0f0f0; } QPushButton:hover { background: #e0e0e0; }");
|
||||
m_attachmentList = new QListWidget(this);
|
||||
m_attachmentList->setMaximumHeight(60);
|
||||
m_attachmentList->setMinimumWidth(300);
|
||||
m_attachmentList->setStyleSheet("QListWidget { border: 1px solid #e0e0e0; border-radius: 4px; font-size: 11px; }");
|
||||
m_attachmentList->setVisible(false);
|
||||
attachRow->addWidget(m_attachBtn);
|
||||
attachRow->addWidget(m_attachmentList, 1);
|
||||
mainLayout->addLayout(attachRow);
|
||||
|
||||
connect(m_attachBtn, &QPushButton::clicked, this, &NewMessageDialog::onAttachFile);
|
||||
connect(m_attachmentList, &QListWidget::itemDoubleClicked, this, &NewMessageDialog::removeAttachment);
|
||||
|
||||
// ===== BOTTOM ACTION BAR =====
|
||||
QHBoxLayout *actionBar = new QHBoxLayout();
|
||||
|
||||
// Schedule picker
|
||||
m_schedulePicker = new QDateTimeEdit(QDateTime::currentDateTime().addSecs(3600), this);
|
||||
m_schedulePicker->setCalendarPopup(true);
|
||||
m_schedulePicker->setDisplayFormat("dd/MM/yyyy hh:mm AP");
|
||||
m_schedulePicker->setStyleSheet("QDateTimeEdit { border: 1px solid #ccc; border-radius: 4px; padding: 6px 8px; font-size: 12px; }");
|
||||
|
||||
actionBar->addStretch();
|
||||
|
||||
m_cancelBtn = new QPushButton("Discard", this);
|
||||
m_cancelBtn->setStyleSheet("QPushButton { border: 1px solid #ccc; border-radius: 6px; padding: 8px 20px; font-size: 13px; font-weight: bold; background: #f0f0f0; color: #333; } QPushButton:hover { background: #e0e0e0; }");
|
||||
|
||||
m_sendLaterBtn = new QPushButton("Schedule...", this);
|
||||
m_sendLaterBtn->setStyleSheet("QPushButton { border: 1px solid #0078d4; border-radius: 6px; padding: 8px 20px; font-size: 13px; font-weight: bold; background: #ffffff; color: #0078d4; } QPushButton:hover { background: #e8f4ff; }");
|
||||
|
||||
m_sendNowBtn = new QPushButton("Send Now", this);
|
||||
m_sendNowBtn->setStyleSheet("QPushButton { border: none; border-radius: 6px; padding: 8px 24px; font-size: 13px; font-weight: bold; background: #0078d4; color: white; } QPushButton:hover { background: #106ebe; }");
|
||||
|
||||
actionBar->addWidget(m_cancelBtn);
|
||||
actionBar->addWidget(m_sendLaterBtn);
|
||||
actionBar->addWidget(m_sendNowBtn);
|
||||
mainLayout->addLayout(actionBar);
|
||||
|
||||
connect(m_cancelBtn, &QPushButton::clicked, this, &QDialog::reject);
|
||||
}
|
||||
|
||||
void NewMessageDialog::setupRichTextToolbar(QToolBar *tb)
|
||||
{
|
||||
m_boldAction = tb->addAction("B");
|
||||
m_boldAction->setCheckable(true);
|
||||
m_boldAction->setToolTip("Bold (Ctrl+B)");
|
||||
m_boldAction->setFont(QFont("Segoe UI", 11, QFont::Bold));
|
||||
|
||||
m_italicAction = tb->addAction("I");
|
||||
m_italicAction->setCheckable(true);
|
||||
m_italicAction->setToolTip("Italic (Ctrl+I)");
|
||||
m_italicAction->setFont(QFont("Segoe UI", 11, QFont::StyleItalic));
|
||||
|
||||
m_underlineAction = tb->addAction("U");
|
||||
m_underlineAction->setCheckable(true);
|
||||
m_underlineAction->setToolTip("Underline (Ctrl+U)");
|
||||
QFont uFont("Segoe UI", 11);
|
||||
uFont.setUnderline(true);
|
||||
m_underlineAction->setFont(uFont);
|
||||
|
||||
tb->addSeparator();
|
||||
|
||||
QAction *bulletAction = tb->addAction("• List");
|
||||
bulletAction->setToolTip("Bullet list");
|
||||
|
||||
QAction *numAction = tb->addAction("1. List");
|
||||
numAction->setToolTip("Numbered list");
|
||||
|
||||
tb->addSeparator();
|
||||
|
||||
QAction *tableAction = tb->addAction("▦ Table");
|
||||
tableAction->setToolTip("Insert table");
|
||||
|
||||
QAction *imageAction = tb->addAction("🖼 Image");
|
||||
imageAction->setToolTip("Insert image");
|
||||
|
||||
connect(m_boldAction, &QAction::triggered, this, &NewMessageDialog::onFormatBold);
|
||||
connect(m_italicAction, &QAction::triggered, this, &NewMessageDialog::onFormatItalic);
|
||||
connect(m_underlineAction, &QAction::triggered, this, &NewMessageDialog::onFormatUnderline);
|
||||
connect(bulletAction, &QAction::triggered, this, &NewMessageDialog::onInsertBulletList);
|
||||
connect(numAction, &QAction::triggered, this, &NewMessageDialog::onInsertNumberedList);
|
||||
connect(tableAction, &QAction::triggered, this, &NewMessageDialog::onInsertTable);
|
||||
connect(imageAction, &QAction::triggered, this, &NewMessageDialog::onInsertImage);
|
||||
|
||||
tb->addSeparator();
|
||||
QAction *undoAction = tb->addAction("↶");
|
||||
undoAction->setToolTip("Undo");
|
||||
connect(undoAction, &QAction::triggered, m_bodyEdit, &QTextEdit::undo);
|
||||
}
|
||||
|
||||
// ===== FORMAT SLOTS =====
|
||||
|
||||
void NewMessageDialog::mergeFormatOnWordOrSelection(const QTextCharFormat &fmt)
|
||||
{
|
||||
QTextCursor cursor = m_bodyEdit->textCursor();
|
||||
if (!cursor.hasSelection())
|
||||
cursor.select(QTextCursor::WordUnderCursor);
|
||||
cursor.mergeCharFormat(fmt);
|
||||
m_bodyEdit->mergeCurrentCharFormat(fmt);
|
||||
}
|
||||
|
||||
void NewMessageDialog::currentCharFormatChanged(const QTextCharFormat &fmt)
|
||||
{
|
||||
m_boldAction->setChecked(fmt.fontWeight() >= QFont::Bold);
|
||||
m_italicAction->setChecked(fmt.fontItalic());
|
||||
m_underlineAction->setChecked(fmt.fontUnderline());
|
||||
}
|
||||
|
||||
void NewMessageDialog::onFormatBold()
|
||||
{
|
||||
QTextCharFormat fmt;
|
||||
fmt.setFontWeight(m_boldAction->isChecked() ? QFont::Bold : QFont::Normal);
|
||||
mergeFormatOnWordOrSelection(fmt);
|
||||
}
|
||||
|
||||
void NewMessageDialog::onFormatItalic()
|
||||
{
|
||||
QTextCharFormat fmt;
|
||||
fmt.setFontItalic(m_italicAction->isChecked());
|
||||
mergeFormatOnWordOrSelection(fmt);
|
||||
}
|
||||
|
||||
void NewMessageDialog::onFormatUnderline()
|
||||
{
|
||||
QTextCharFormat fmt;
|
||||
fmt.setFontUnderline(m_underlineAction->isChecked());
|
||||
mergeFormatOnWordOrSelection(fmt);
|
||||
}
|
||||
|
||||
void NewMessageDialog::onInsertBulletList()
|
||||
{
|
||||
QTextCursor cursor = m_bodyEdit->textCursor();
|
||||
cursor.insertList(QTextListFormat::ListDisc);
|
||||
}
|
||||
|
||||
void NewMessageDialog::onInsertNumberedList()
|
||||
{
|
||||
QTextCursor cursor = m_bodyEdit->textCursor();
|
||||
cursor.insertList(QTextListFormat::ListDecimal);
|
||||
}
|
||||
|
||||
void NewMessageDialog::onInsertTable()
|
||||
{
|
||||
QTextCursor cursor = m_bodyEdit->textCursor();
|
||||
QTextTable *table = cursor.insertTable(3, 3);
|
||||
Q_UNUSED(table);
|
||||
}
|
||||
|
||||
void NewMessageDialog::onInsertImage()
|
||||
{
|
||||
QString filePath = QFileDialog::getOpenFileName(this, "Insert Image", QString(),
|
||||
"Images (*.png *.jpg *.jpeg *.gif *.bmp)");
|
||||
if (filePath.isEmpty()) return;
|
||||
|
||||
QImage image(filePath);
|
||||
if (image.isNull()) return;
|
||||
|
||||
// Scale large images to fit
|
||||
if (image.width() > 600)
|
||||
image = image.scaledToWidth(600, Qt::SmoothTransformation);
|
||||
|
||||
QTextCursor cursor = m_bodyEdit->textCursor();
|
||||
cursor.insertImage(image);
|
||||
}
|
||||
|
||||
// ===== CC / BCC TOGGLES =====
|
||||
|
||||
void NewMessageDialog::toggleCc()
|
||||
{
|
||||
m_ccVisible = !m_ccVisible;
|
||||
m_ccEdit->setVisible(m_ccVisible);
|
||||
m_ccBtn->setStyleSheet(m_ccVisible
|
||||
? "QPushButton { border: 1px solid #0078d4; border-radius: 4px; padding: 4px 8px; font-size: 11px; background: #e8f4ff; color: #0078d4; }"
|
||||
: "QPushButton { border: 1px solid #ccc; border-radius: 4px; padding: 4px 8px; font-size: 11px; background: #f0f0f0; } QPushButton:hover { background: #e0e0e0; }");
|
||||
}
|
||||
|
||||
void NewMessageDialog::toggleBcc()
|
||||
{
|
||||
m_bccVisible = !m_bccVisible;
|
||||
m_bccEdit->setVisible(m_bccVisible);
|
||||
m_bccBtn->setStyleSheet(m_bccVisible
|
||||
? "QPushButton { border: 1px solid #0078d4; border-radius: 4px; padding: 4px 8px; font-size: 11px; background: #e8f4ff; color: #0078d4; }"
|
||||
: "QPushButton { border: 1px solid #ccc; border-radius: 4px; padding: 4px 8px; font-size: 11px; background: #f0f0f0; } QPushButton:hover { background: #e0e0e0; }");
|
||||
}
|
||||
|
||||
// ===== SEND =====
|
||||
|
||||
void NewMessageDialog::onSendNow()
|
||||
{
|
||||
if (m_toEdit->text().trimmed().isEmpty()) {
|
||||
QMessageBox::warning(this, "Missing Recipient", "Please enter at least one recipient.");
|
||||
m_toEdit->setFocus();
|
||||
return;
|
||||
}
|
||||
m_sendLater = false;
|
||||
accept();
|
||||
}
|
||||
|
||||
void NewMessageDialog::onSendLater()
|
||||
{
|
||||
if (m_toEdit->text().trimmed().isEmpty()) {
|
||||
QMessageBox::warning(this, "Missing Recipient", "Please enter at least one recipient.");
|
||||
m_toEdit->setFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_schedulePicker->dateTime() <= QDateTime::currentDateTime()) {
|
||||
QMessageBox::warning(this, "Invalid Time", "Scheduled time must be in the future.");
|
||||
return;
|
||||
}
|
||||
|
||||
m_sendLater = true;
|
||||
m_scheduledTime = m_schedulePicker->dateTime();
|
||||
accept();
|
||||
}
|
||||
|
||||
// ===== ATTACHMENTS =====
|
||||
|
||||
void NewMessageDialog::onAttachFile()
|
||||
{
|
||||
QStringList files = QFileDialog::getOpenFileNames(this, "Attach Files");
|
||||
if (files.isEmpty()) return;
|
||||
|
||||
for (const QString &file : files) {
|
||||
QFileInfo fi(file);
|
||||
m_attachedFiles << file;
|
||||
m_attachmentList->addItem(fi.fileName());
|
||||
}
|
||||
m_attachmentList->setVisible(!m_attachedFiles.isEmpty());
|
||||
}
|
||||
|
||||
void NewMessageDialog::removeAttachment()
|
||||
{
|
||||
QListWidgetItem *item = m_attachmentList->currentItem();
|
||||
if (!item) return;
|
||||
|
||||
int row = m_attachmentList->row(item);
|
||||
m_attachedFiles.removeAt(row);
|
||||
delete m_attachmentList->takeItem(row);
|
||||
m_attachmentList->setVisible(!m_attachedFiles.isEmpty());
|
||||
}
|
||||
|
||||
// ===== CLOSE WITH UNSAVED CHANGES =====
|
||||
|
||||
bool NewMessageDialog::hasUnsavedChanges() const
|
||||
{
|
||||
// Check if anything was typed
|
||||
if (!m_toEdit->text().trimmed().isEmpty()) return true;
|
||||
if (!m_subjectEdit->text().trimmed().isEmpty()) return true;
|
||||
if (m_bodyEdit->toPlainText() != m_initialContent &&
|
||||
!m_bodyEdit->toPlainText().trimmed().isEmpty()) return true;
|
||||
if (!m_attachedFiles.isEmpty()) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void NewMessageDialog::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
if (hasUnsavedChanges()) {
|
||||
QMessageBox::StandardButton reply = QMessageBox::question(
|
||||
this, "Unsaved Changes",
|
||||
"You have unsaved changes. Would you like to save to drafts before closing?",
|
||||
QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel,
|
||||
QMessageBox::Save);
|
||||
|
||||
if (reply == QMessageBox::Save) {
|
||||
// Accept the dialog so the caller can handle saving to drafts
|
||||
accept();
|
||||
return;
|
||||
} else if (reply == QMessageBox::Cancel) {
|
||||
event->ignore();
|
||||
return;
|
||||
}
|
||||
// Discard: fall through to reject
|
||||
}
|
||||
event->accept();
|
||||
}
|
||||
|
||||
// ===== GETTERS =====
|
||||
|
||||
QString NewMessageDialog::fromAccount() const { return m_fromCombo->currentText(); }
|
||||
QString NewMessageDialog::to() const { return m_toEdit->text().trimmed(); }
|
||||
QString NewMessageDialog::cc() const { return m_ccEdit->text().trimmed(); }
|
||||
QString NewMessageDialog::bcc() const { return m_bccEdit->text().trimmed(); }
|
||||
QString NewMessageDialog::subject() const { return m_subjectEdit->text().trimmed(); }
|
||||
QString NewMessageDialog::body() const { return m_bodyEdit->toHtml(); }
|
||||
Reference in New Issue
Block a user