Files
wino-mail-dtkqt/src/ui/newmessagedialog.cpp
T

407 lines
15 KiB
C++
Raw Normal View History

#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(); }