Fix build: synchronize Request API, fix GmailSynchronizer, and migrate UI to Qt6 Widgets
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
#include "ui/calendarview.h"
|
||||
|
||||
CalendarView::CalendarView(QWidget *parent) : QWidget(parent) {
|
||||
setupUI();
|
||||
}
|
||||
|
||||
void CalendarView::setupUI() {
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
layout->setAlignment(Qt::AlignCenter);
|
||||
|
||||
QLabel *icon = new QLabel("📅");
|
||||
icon->setAlignment(Qt::AlignCenter);
|
||||
QFont iconFont = icon->font();
|
||||
iconFont.setPointSize(48);
|
||||
icon->setFont(iconFont);
|
||||
|
||||
QLabel *title = new QLabel("Calendar");
|
||||
title->setAlignment(Qt::AlignCenter);
|
||||
QFont titleFont = title->font();
|
||||
titleFont.setPointSize(20);
|
||||
titleFont.setBold(true);
|
||||
title->setFont(titleFont);
|
||||
title->setStyleSheet("color: #333;");
|
||||
|
||||
QLabel *subtitle = new QLabel("Coming soon — integrated calendar with email");
|
||||
subtitle->setAlignment(Qt::AlignCenter);
|
||||
subtitle->setStyleSheet("color: #888; font-size: 13px;");
|
||||
|
||||
layout->addWidget(icon);
|
||||
layout->addWidget(title);
|
||||
layout->addWidget(subtitle);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
class CalendarView : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CalendarView(QWidget *parent = nullptr);
|
||||
~CalendarView() override = default;
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
};
|
||||
@@ -0,0 +1,486 @@
|
||||
#include "ui/composeview.h"
|
||||
#include <QFrame>
|
||||
#include <QFileDialog>
|
||||
#include <QInputDialog>
|
||||
#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("\xe2\x87\x94L");
|
||||
connect(alignLeft, &QAction::triggered, this, &RichTextEditor::onAlignLeft);
|
||||
|
||||
QAction *alignCenter = m_toolbar->addAction("\xe2\x86\x94C");
|
||||
connect(alignCenter, &QAction::triggered, this, &RichTextEditor::onAlignCenter);
|
||||
|
||||
QAction *alignRight = m_toolbar->addAction("\xe2\x87\x94R");
|
||||
connect(alignRight, &QAction::triggered, this, &RichTextEditor::onAlignRight);
|
||||
|
||||
QAction *alignJustify = m_toolbar->addAction("\xe2\x87\x94J");
|
||||
connect(alignJustify, &QAction::triggered, this, &RichTextEditor::onAlignJustify);
|
||||
|
||||
m_toolbar->addSeparator();
|
||||
|
||||
// Lists
|
||||
QAction *bulletAct = m_toolbar->addAction("\xe2\x80\xa2 List");
|
||||
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("\xe2\x86\x92 Indent");
|
||||
connect(indentAct, &QAction::triggered, this, &RichTextEditor::onIndent);
|
||||
|
||||
QAction *outdentAct = m_toolbar->addAction("\xe2\x86\x90 Outdent");
|
||||
connect(outdentAct, &QAction::triggered, this, &RichTextEditor::onOutdent);
|
||||
|
||||
m_toolbar->addSeparator();
|
||||
|
||||
// Insert image
|
||||
QAction *imgAct = m_toolbar->addAction("\xf0\x9f\x96\xbc Image");
|
||||
connect(imgAct, &QAction::triggered, this, &RichTextEditor::onInsertImage);
|
||||
|
||||
// Insert table
|
||||
QAction *tableAct = m_toolbar->addAction("\xe2\x96\xa4 Table");
|
||||
connect(tableAct, &QAction::triggered, this, &RichTextEditor::onInsertTable);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// ===================== ComposeView =====================
|
||||
|
||||
ComposeView::ComposeView(QWidget *parent) : QWidget(parent) {
|
||||
setupUI();
|
||||
}
|
||||
|
||||
void ComposeView::setupUI() {
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->setContentsMargins(20, 15, 20, 15);
|
||||
mainLayout->setSpacing(8);
|
||||
|
||||
this->setStyleSheet(
|
||||
"QLineEdit, QTextEdit { border: 1px solid #d1d1d6; border-radius: 4px; padding: 8px; font-family: 'Segoe UI', Helvetica; font-size: 13px; }"
|
||||
"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);
|
||||
|
||||
// To: 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)");
|
||||
toLayout->addWidget(toLabel);
|
||||
toLayout->addWidget(m_toField, 1);
|
||||
|
||||
m_ccButton = new QPushButton("Cc");
|
||||
m_ccButton->setFixedWidth(32);
|
||||
m_ccButton->setStyleSheet(
|
||||
"QPushButton { background: #e0e0e0; border: 1px solid #ccc; border-radius: 3px; font-size: 11px; padding: 2px; }"
|
||||
"QPushButton:hover { background: #d0d0d0; }"
|
||||
);
|
||||
connect(m_ccButton, &QPushButton::clicked, this, &ComposeView::onCcToggle);
|
||||
toLayout->addWidget(m_ccButton);
|
||||
|
||||
m_bccButton = new QPushButton("Bcc");
|
||||
m_bccButton->setFixedWidth(36);
|
||||
m_bccButton->setStyleSheet(
|
||||
"QPushButton { background: #e0e0e0; border: 1px solid #ccc; border-radius: 3px; font-size: 11px; padding: 2px; }"
|
||||
"QPushButton:hover { background: #d0d0d0; }"
|
||||
);
|
||||
connect(m_bccButton, &QPushButton::clicked, this, &ComposeView::onBccToggle);
|
||||
toLayout->addWidget(m_bccButton);
|
||||
|
||||
mainLayout->addLayout(toLayout);
|
||||
|
||||
// Cc: row (hidden by default)
|
||||
m_ccRow = new QWidget();
|
||||
QHBoxLayout *ccLayout = new QHBoxLayout(m_ccRow);
|
||||
ccLayout->setContentsMargins(0, 0, 0, 0);
|
||||
QLabel *ccLabel = new QLabel("Cc:");
|
||||
ccLabel->setFixedWidth(40);
|
||||
ccLabel->setStyleSheet("color: #555;");
|
||||
m_ccField = new QLineEdit();
|
||||
m_ccField->setPlaceholderText("Carbon copy");
|
||||
ccLayout->addWidget(ccLabel);
|
||||
ccLayout->addWidget(m_ccField, 1);
|
||||
|
||||
m_hideCcButton = new QPushButton("x");
|
||||
m_hideCcButton->setFixedSize(20, 20);
|
||||
m_hideCcButton->setToolTip("Hide Cc");
|
||||
m_hideCcButton->setStyleSheet(
|
||||
"QPushButton { background: transparent; border: none; color: #999; font-weight: bold; }"
|
||||
"QPushButton:hover { color: #333; }"
|
||||
);
|
||||
connect(m_hideCcButton, &QPushButton::clicked, this, &ComposeView::onCcToggle);
|
||||
ccLayout->addWidget(m_hideCcButton);
|
||||
|
||||
m_ccRow->setVisible(false);
|
||||
mainLayout->addWidget(m_ccRow);
|
||||
|
||||
// Bcc: row (hidden by default)
|
||||
m_bccRow = new QWidget();
|
||||
QHBoxLayout *bccLayout = new QHBoxLayout(m_bccRow);
|
||||
bccLayout->setContentsMargins(0, 0, 0, 0);
|
||||
QLabel *bccLabel = new QLabel("Bcc:");
|
||||
bccLabel->setFixedWidth(40);
|
||||
bccLabel->setStyleSheet("color: #555;");
|
||||
m_bccField = new QLineEdit();
|
||||
m_bccField->setPlaceholderText("Blind carbon copy");
|
||||
bccLayout->addWidget(bccLabel);
|
||||
bccLayout->addWidget(m_bccField, 1);
|
||||
|
||||
m_hideBccButton = new QPushButton("x");
|
||||
m_hideBccButton->setFixedSize(20, 20);
|
||||
m_hideBccButton->setToolTip("Hide Bcc");
|
||||
m_hideBccButton->setStyleSheet(
|
||||
"QPushButton { background: transparent; border: none; color: #999; font-weight: bold; }"
|
||||
"QPushButton:hover { color: #333; }"
|
||||
);
|
||||
connect(m_hideBccButton, &QPushButton::clicked, this, &ComposeView::onBccToggle);
|
||||
bccLayout->addWidget(m_hideBccButton);
|
||||
|
||||
m_bccRow->setVisible(false);
|
||||
mainLayout->addWidget(m_bccRow);
|
||||
|
||||
// Separator
|
||||
QFrame *line2 = new QFrame();
|
||||
line2->setFrameShape(QFrame::HLine);
|
||||
line2->setStyleSheet("color: #e0e0e0;");
|
||||
mainLayout->addWidget(line2);
|
||||
|
||||
// Rich text editor with toolbar
|
||||
m_bodyEditor = new RichTextEditor();
|
||||
m_bodyEditor->setupToolbar(mainLayout);
|
||||
mainLayout->addWidget(m_bodyEditor, 1);
|
||||
|
||||
// Schedule panel
|
||||
m_schedulePanel = new QWidget();
|
||||
QHBoxLayout *scheduleLayout = new QHBoxLayout(m_schedulePanel);
|
||||
scheduleLayout->setContentsMargins(0, 0, 0, 0);
|
||||
QLabel *scheduleLabel = new QLabel("Send at:");
|
||||
scheduleLabel->setStyleSheet("color: #555; font-weight: bold;");
|
||||
m_schedulePicker = new QDateTimeEdit(QDateTime::currentDateTime().addSecs(3600));
|
||||
m_schedulePicker->setCalendarPopup(true);
|
||||
m_schedulePicker->setDisplayFormat("dd/MM/yyyy hh:mm AP");
|
||||
scheduleLayout->addWidget(scheduleLabel);
|
||||
scheduleLayout->addWidget(m_schedulePicker);
|
||||
scheduleLayout->addStretch();
|
||||
|
||||
m_scheduleSendButton = new QPushButton("Schedule Send");
|
||||
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);
|
||||
|
||||
m_schedulePanel->setVisible(false);
|
||||
mainLayout->addWidget(m_schedulePanel);
|
||||
|
||||
// Action buttons row
|
||||
QHBoxLayout *actionsLayout = new QHBoxLayout();
|
||||
actionsLayout->addStretch();
|
||||
|
||||
m_discardButton = new QPushButton("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);
|
||||
|
||||
// Split button for Send / Schedule
|
||||
m_sendMenu = new QMenu(this);
|
||||
m_sendNowAction = m_sendMenu->addAction("Send Now");
|
||||
connect(m_sendNowAction, &QAction::triggered, this, &ComposeView::onSendClicked);
|
||||
m_scheduleAction = m_sendMenu->addAction("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->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; }"
|
||||
"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);
|
||||
actionsLayout->addWidget(m_sendSplit);
|
||||
|
||||
mainLayout->addLayout(actionsLayout);
|
||||
}
|
||||
|
||||
void ComposeView::onCcToggle() {
|
||||
m_ccVisible = !m_ccVisible;
|
||||
m_ccRow->setVisible(m_ccVisible);
|
||||
m_ccButton->setVisible(!m_ccVisible);
|
||||
if (m_ccVisible) m_ccField->setFocus();
|
||||
}
|
||||
|
||||
void ComposeView::onBccToggle() {
|
||||
m_bccVisible = !m_bccVisible;
|
||||
m_bccRow->setVisible(m_bccVisible);
|
||||
m_bccButton->setVisible(!m_bccVisible);
|
||||
if (m_bccVisible) m_bccField->setFocus();
|
||||
}
|
||||
|
||||
void ComposeView::onSendClicked() {
|
||||
emit sendRequested(
|
||||
m_toField->text(),
|
||||
m_ccVisible ? m_ccField->text() : QString(),
|
||||
m_bccVisible ? m_bccField->text() : QString(),
|
||||
m_subjectField->text(),
|
||||
m_bodyEditor->toHtml(),
|
||||
QDateTime() // null = send now
|
||||
);
|
||||
}
|
||||
|
||||
void ComposeView::onScheduleClicked() {
|
||||
emit sendRequested(
|
||||
m_toField->text(),
|
||||
m_ccVisible ? m_ccField->text() : QString(),
|
||||
m_bccVisible ? m_bccField->text() : QString(),
|
||||
m_subjectField->text(),
|
||||
m_bodyEditor->toHtml(),
|
||||
m_schedulePicker->dateTime()
|
||||
);
|
||||
}
|
||||
|
||||
void ComposeView::onDetachClicked() {
|
||||
emit detachRequested(this);
|
||||
}
|
||||
|
||||
void ComposeView::setTo(const QString &to) { m_toField->setText(to); }
|
||||
void ComposeView::setSubject(const QString &subject) { m_subjectField->setText(subject); }
|
||||
void ComposeView::setBody(const QString &body) { m_bodyEditor->setHtml(body); }
|
||||
@@ -0,0 +1,103 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QLineEdit>
|
||||
#include <QTextEdit>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QDateTime>
|
||||
#include <QDateTimeEdit>
|
||||
#include <QMenu>
|
||||
#include <QToolBar>
|
||||
#include <QToolButton>
|
||||
#include <QFontComboBox>
|
||||
#include <QSpinBox>
|
||||
#include <QAction>
|
||||
|
||||
class RichTextEditor : public QTextEdit {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit RichTextEditor(QWidget *parent = nullptr);
|
||||
void setupToolbar(QVBoxLayout *layout);
|
||||
|
||||
private slots:
|
||||
void onBold();
|
||||
void onItalic();
|
||||
void onUnderline();
|
||||
void onBulletList();
|
||||
void onNumberedList();
|
||||
void onIndent();
|
||||
void onOutdent();
|
||||
void onAlignLeft();
|
||||
void onAlignCenter();
|
||||
void onAlignRight();
|
||||
void onAlignJustify();
|
||||
void onFontChanged(const QFont &font);
|
||||
void onFontSizeChanged(int size);
|
||||
void onInsertImage();
|
||||
void onInsertTable();
|
||||
|
||||
private:
|
||||
QToolBar *m_toolbar;
|
||||
QFontComboBox *m_fontCombo;
|
||||
QSpinBox *m_fontSizeSpin;
|
||||
};
|
||||
|
||||
class ComposeView : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ComposeView(QWidget *parent = nullptr);
|
||||
~ComposeView() override = default;
|
||||
|
||||
void setTo(const QString &to);
|
||||
void setSubject(const QString &subject);
|
||||
void setBody(const QString &body);
|
||||
|
||||
signals:
|
||||
void sendRequested(const QString &to, const QString &cc, const QString &bcc,
|
||||
const QString &subject, const QString &body,
|
||||
const QDateTime &scheduleTime);
|
||||
void discardRequested();
|
||||
void detachRequested(QWidget *composeView);
|
||||
|
||||
private slots:
|
||||
void onSendClicked();
|
||||
void onScheduleClicked();
|
||||
void onCcToggle();
|
||||
void onBccToggle();
|
||||
void onDetachClicked();
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
|
||||
QLineEdit *m_toField;
|
||||
QWidget *m_ccRow;
|
||||
QLineEdit *m_ccField;
|
||||
QWidget *m_bccRow;
|
||||
QLineEdit *m_bccField;
|
||||
QLineEdit *m_subjectField;
|
||||
RichTextEditor *m_bodyEditor;
|
||||
|
||||
QToolButton *m_sendSplit;
|
||||
QMenu *m_sendMenu;
|
||||
QAction *m_sendNowAction;
|
||||
QAction *m_scheduleAction;
|
||||
|
||||
QPushButton *m_discardButton;
|
||||
QPushButton *m_detachButton;
|
||||
|
||||
QPushButton *m_ccButton;
|
||||
QPushButton *m_bccButton;
|
||||
QPushButton *m_hideCcButton;
|
||||
QPushButton *m_hideBccButton;
|
||||
|
||||
QWidget *m_schedulePanel;
|
||||
QDateTimeEdit *m_schedulePicker;
|
||||
QPushButton *m_scheduleSendButton;
|
||||
|
||||
bool m_ccVisible = false;
|
||||
bool m_bccVisible = false;
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
#include "ui/contactsview.h"
|
||||
|
||||
ContactsView::ContactsView(QWidget *parent) : QWidget(parent) {
|
||||
setupUI();
|
||||
}
|
||||
|
||||
void ContactsView::setupUI() {
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
layout->setAlignment(Qt::AlignCenter);
|
||||
|
||||
QLabel *icon = new QLabel("👥");
|
||||
icon->setAlignment(Qt::AlignCenter);
|
||||
QFont iconFont = icon->font();
|
||||
iconFont.setPointSize(48);
|
||||
icon->setFont(iconFont);
|
||||
|
||||
QLabel *title = new QLabel("Contacts");
|
||||
title->setAlignment(Qt::AlignCenter);
|
||||
QFont titleFont = title->font();
|
||||
titleFont.setPointSize(20);
|
||||
titleFont.setBold(true);
|
||||
title->setFont(titleFont);
|
||||
title->setStyleSheet("color: #333;");
|
||||
|
||||
QLabel *subtitle = new QLabel("Coming soon — manage your contacts here");
|
||||
subtitle->setAlignment(Qt::AlignCenter);
|
||||
subtitle->setStyleSheet("color: #888; font-size: 13px;");
|
||||
|
||||
layout->addWidget(icon);
|
||||
layout->addWidget(title);
|
||||
layout->addWidget(subtitle);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
class ContactsView : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ContactsView(QWidget *parent = nullptr);
|
||||
~ContactsView() override = default;
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
};
|
||||
@@ -0,0 +1,103 @@
|
||||
#include "ui/maillistview.h"
|
||||
#include <QDateTime>
|
||||
|
||||
MailListView::MailListView(QWidget *parent) : QWidget(parent) {
|
||||
setupUI();
|
||||
}
|
||||
|
||||
void MailListView::setupUI() {
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(0);
|
||||
|
||||
// Header bar
|
||||
QWidget *headerBar = new QWidget();
|
||||
headerBar->setFixedHeight(48);
|
||||
headerBar->setStyleSheet("background-color: #1976D2;");
|
||||
QHBoxLayout *headerLayout = new QHBoxLayout(headerBar);
|
||||
headerLayout->setContentsMargins(16, 0, 10, 0);
|
||||
|
||||
QLabel *title = new QLabel("Wino Mail");
|
||||
title->setStyleSheet("color: white; font-size: 18px; font-weight: bold;");
|
||||
headerLayout->addWidget(title);
|
||||
headerLayout->addStretch();
|
||||
|
||||
m_composeButton = new QPushButton("✎");
|
||||
m_composeButton->setFixedSize(36, 36);
|
||||
m_composeButton->setStyleSheet(
|
||||
"QPushButton { background-color: #e0e0e0; border-radius: 4px; font-size: 18px; color: #333; }"
|
||||
"QPushButton:hover { background-color: #d0d0d0; }"
|
||||
);
|
||||
headerLayout->addWidget(m_composeButton);
|
||||
connect(m_composeButton, &QPushButton::clicked, this, &MailListView::composeRequested);
|
||||
|
||||
layout->addWidget(headerBar);
|
||||
|
||||
// Table
|
||||
m_tableView = new QTableView();
|
||||
m_tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
m_tableView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
m_tableView->setShowGrid(false);
|
||||
m_tableView->setAlternatingRowColors(true);
|
||||
m_tableView->verticalHeader()->hide();
|
||||
m_tableView->horizontalHeader()->setStretchLastSection(true);
|
||||
m_tableView->horizontalHeader()->setSectionsClickable(true);
|
||||
m_tableView->setSortingEnabled(true);
|
||||
m_tableView->setFrameShape(QFrame::NoFrame);
|
||||
m_tableView->setStyleSheet(
|
||||
"QTableView { background-color: #ffffff; alternate-background-color: #f9f9fb; border: none; }"
|
||||
"QTableView::item { padding: 8px; border-bottom: 1px solid #e8e8ed; }"
|
||||
"QTableView::item:selected { background-color: #e3f2fd; color: #1a1a2e; }"
|
||||
"QHeaderView::section { background-color: #f5f5f7; padding: 8px; border: none; border-bottom: 1px solid #d1d1d6; font-weight: 600; color: #555; }"
|
||||
);
|
||||
|
||||
m_proxyModel = new QSortFilterProxyModel(this);
|
||||
m_proxyModel->setSortRole(EmailListModel::DateRole);
|
||||
m_proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||
m_proxyModel->setDynamicSortFilter(true);
|
||||
|
||||
m_tableView->setModel(m_proxyModel);
|
||||
|
||||
connect(m_tableView, &QTableView::clicked, this, &MailListView::onRowSelected);
|
||||
|
||||
layout->addWidget(m_tableView);
|
||||
}
|
||||
|
||||
void MailListView::setModel(EmailListModel *model) {
|
||||
m_proxyModel->setSourceModel(model);
|
||||
|
||||
m_tableView->setColumnHidden(EmailListModel::IdRole - Qt::UserRole - 1, true);
|
||||
m_tableView->setColumnHidden(EmailListModel::RecipientRole - Qt::UserRole - 1, true);
|
||||
m_tableView->setColumnHidden(EmailListModel::ReadRole - Qt::UserRole - 1, true);
|
||||
m_tableView->setColumnHidden(EmailListModel::FlaggedRole - Qt::UserRole - 1, true);
|
||||
m_tableView->setColumnHidden(EmailListModel::AttachmentsRole - Qt::UserRole - 1, true);
|
||||
m_tableView->setColumnHidden(EmailListModel::FileIdRole - Qt::UserRole - 1, true);
|
||||
m_tableView->setColumnHidden(EmailListModel::SizeRole - Qt::UserRole - 1, true);
|
||||
m_tableView->setColumnHidden(EmailListModel::MessageIdRole - Qt::UserRole - 1, true);
|
||||
m_tableView->setColumnHidden(EmailListModel::SenderInitialRole - Qt::UserRole - 1, true);
|
||||
|
||||
m_tableView->horizontalHeader()->setSectionResizeMode(EmailListModel::SenderRole - Qt::UserRole - 1, QHeaderView::Stretch);
|
||||
m_tableView->horizontalHeader()->setSectionResizeMode(EmailListModel::SubjectRole - Qt::UserRole - 1, QHeaderView::Stretch);
|
||||
|
||||
// Default sort by date descending
|
||||
m_tableView->sortByColumn(EmailListModel::DateRole - Qt::UserRole - 1, Qt::DescendingOrder);
|
||||
|
||||
// Set column titles manually
|
||||
QHeaderView *header = m_tableView->horizontalHeader();
|
||||
for (int i = 0; i < m_proxyModel->columnCount(); ++i) {
|
||||
int role = Qt::UserRole + 1 + i;
|
||||
switch (role) {
|
||||
case EmailListModel::SenderRole: header->setSectionHidden(i, false); break;
|
||||
case EmailListModel::SubjectRole: header->setSectionHidden(i, false); break;
|
||||
case EmailListModel::DateRole: header->setSectionHidden(i, false); break;
|
||||
default: header->setSectionHidden(i, true); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MailListView::onRowSelected(const QModelIndex &index) {
|
||||
if (!index.isValid()) return;
|
||||
QModelIndex sourceIndex = m_proxyModel->mapToSource(index);
|
||||
int mailId = sourceIndex.data(EmailListModel::IdRole).toInt();
|
||||
emit emailSelected(mailId);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QTableView>
|
||||
#include <QHeaderView>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QVBoxLayout>
|
||||
#include <QPushButton>
|
||||
#include <QLabel>
|
||||
|
||||
#include "ui/models/EmailListModel.h"
|
||||
|
||||
class MailListView : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MailListView(QWidget *parent = nullptr);
|
||||
~MailListView() override = default;
|
||||
|
||||
void setModel(EmailListModel *model);
|
||||
|
||||
signals:
|
||||
void emailSelected(int mailId);
|
||||
void composeRequested();
|
||||
|
||||
private slots:
|
||||
void onRowSelected(const QModelIndex &index);
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
|
||||
QTableView *m_tableView;
|
||||
QSortFilterProxyModel *m_proxyModel;
|
||||
QPushButton *m_composeButton;
|
||||
};
|
||||
@@ -0,0 +1,270 @@
|
||||
#include "mainmainwindow.h"
|
||||
#include "core/models/account.h"
|
||||
#include "core/mailitem.h"
|
||||
#include "db/dao/mailitemdao.h"
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QFrame>
|
||||
#include <QDateTime>
|
||||
#include <QLabel>
|
||||
|
||||
MainMainWindow::MainMainWindow(QWidget *parent)
|
||||
: QMainWindow(parent), m_currentFolderId(-1)
|
||||
{
|
||||
setupUI();
|
||||
connectModels();
|
||||
setWindowTitle("Wino Mail DTK");
|
||||
resize(1280, 820);
|
||||
}
|
||||
|
||||
void MainMainWindow::setupUI() {
|
||||
// === Global stylesheet ===
|
||||
this->setStyleSheet(
|
||||
"QMainWindow { background-color: #f5f5f7; }"
|
||||
"QSplitter::handle { background-color: #d1d1d6; width: 1px; }"
|
||||
"QTreeView { background-color: #ffffff; border: none; font-family: 'Segoe UI', Helvetica; font-size: 13px; }"
|
||||
"QToolBar { background-color: #f5f5f7; border-bottom: 1px solid #d1d1d6; spacing: 10px; }"
|
||||
);
|
||||
|
||||
createToolBar();
|
||||
|
||||
// === Central widget ===
|
||||
QWidget *central = new QWidget();
|
||||
QHBoxLayout *centralLayout = new QHBoxLayout(central);
|
||||
centralLayout->setContentsMargins(0, 0, 0, 0);
|
||||
centralLayout->setSpacing(0);
|
||||
|
||||
// === Sidebar ===
|
||||
setupSidebar();
|
||||
centralLayout->addWidget(m_sidebar);
|
||||
|
||||
// === Separator line ===
|
||||
QFrame *separator = new QFrame();
|
||||
separator->setFrameShape(QFrame::VLine);
|
||||
separator->setStyleSheet("color: #d1d1d6;");
|
||||
centralLayout->addWidget(separator);
|
||||
|
||||
// === Stacked pages ===
|
||||
m_stack = new QStackedWidget();
|
||||
m_stack->setStyleSheet("background-color: #f5f5f7;");
|
||||
|
||||
// Page 0: Mail (folder tree + mail list + reader)
|
||||
setupMailPage();
|
||||
m_stack->addWidget(m_mailPage);
|
||||
|
||||
// Page 1: Compose
|
||||
m_composeView = new ComposeView();
|
||||
connect(m_composeView, &ComposeView::sendRequested, [this](const QString &to, const QString &cc, const QString &bcc, const QString &subject, const QString &body, const QDateTime &scheduleTime) {
|
||||
QString msg;
|
||||
if (scheduleTime.isValid()) {
|
||||
msg = QString("Message scheduled for: %1").arg(scheduleTime.toString("dd/MM/yyyy hh:mm AP"));
|
||||
} else {
|
||||
msg = "Message sent (simulated)";
|
||||
}
|
||||
if (!cc.isEmpty()) msg += QString(" | Cc: %1").arg(cc);
|
||||
if (!bcc.isEmpty()) msg += QString(" | Bcc: %1").arg(bcc);
|
||||
statusBar()->showMessage(msg, 5000);
|
||||
switchToPage(PageMail);
|
||||
});
|
||||
connect(m_composeView, &ComposeView::discardRequested, [this]() {
|
||||
switchToPage(PageMail);
|
||||
});
|
||||
connect(m_composeView, &ComposeView::detachRequested, [this](QWidget *composeView) {
|
||||
// Detach compose view to a standalone window
|
||||
QStackedWidget *stack = qobject_cast<QStackedWidget*>(composeView->parentWidget());
|
||||
if (stack) {
|
||||
stack->removeWidget(composeView);
|
||||
}
|
||||
|
||||
// Create standalone window
|
||||
QMainWindow *detachedWin = new QMainWindow();
|
||||
detachedWin->setWindowTitle("Compose - Wino Mail");
|
||||
|
||||
// Assign central widget and ensure it's visible and sized
|
||||
detachedWin->setCentralWidget(composeView);
|
||||
composeView->setMinimumSize(800, 600);
|
||||
composeView->update();
|
||||
|
||||
detachedWin->resize(800, 600);
|
||||
detachedWin->setAttribute(Qt::WA_DeleteOnClose);
|
||||
detachedWin->show();
|
||||
|
||||
statusBar()->showMessage("Compose view detached to separate window", 3000);
|
||||
});
|
||||
m_stack->addWidget(m_composeView);
|
||||
|
||||
// Page 2: Settings
|
||||
m_settingsView = new SettingsView();
|
||||
connect(m_settingsView, &SettingsView::accountAddRequested, [this]() {
|
||||
statusBar()->showMessage("Account setup dialog would open here", 3000);
|
||||
});
|
||||
connect(m_settingsView, &SettingsView::themeChanged, [this](const QString &theme) {
|
||||
statusBar()->showMessage(QString("Theme changed to: %1 (restart may be required)").arg(theme), 3000);
|
||||
});
|
||||
m_stack->addWidget(m_settingsView);
|
||||
|
||||
// Page 3: Contacts
|
||||
m_contactsView = new ContactsView();
|
||||
m_stack->addWidget(m_contactsView);
|
||||
|
||||
// Page 4: Calendar
|
||||
m_calendarView = new CalendarView();
|
||||
m_stack->addWidget(m_calendarView);
|
||||
|
||||
centralLayout->addWidget(m_stack, 1);
|
||||
setCentralWidget(central);
|
||||
|
||||
// Show mail page by default
|
||||
switchToPage(PageMail);
|
||||
}
|
||||
|
||||
void MainMainWindow::setupSidebar() {
|
||||
m_sidebar = new QListWidget();
|
||||
m_sidebar->setFixedWidth(64);
|
||||
m_sidebar->setIconSize(QSize(24, 24));
|
||||
m_sidebar->setSpacing(4);
|
||||
m_sidebar->setFrameShape(QFrame::NoFrame);
|
||||
m_sidebar->setStyleSheet(
|
||||
"QListWidget { background-color: #2c2c2e; border: none; padding: 8px 0; }"
|
||||
"QListWidget::item { color: #8e8e93; padding: 12px 0; text-align: center; font-size: 10px; border: none; border-radius: 8px; margin: 2px 8px; }"
|
||||
"QListWidget::item:selected { background-color: #3a3a3c; color: #ffffff; }"
|
||||
"QListWidget::item:hover { background-color: #3a3a3c; color: #ffffff; }"
|
||||
);
|
||||
|
||||
m_sidebar->addItem("📧\nMail");
|
||||
m_sidebar->addItem("✏️\nCompose");
|
||||
m_sidebar->addItem("⚙️\nSettings");
|
||||
m_sidebar->addItem("👥\nContacts");
|
||||
m_sidebar->addItem("📅\nCalendar");
|
||||
|
||||
m_sidebar->setCurrentRow(0);
|
||||
connect(m_sidebar, &QListWidget::currentRowChanged, this, &MainMainWindow::onNavChanged);
|
||||
}
|
||||
|
||||
void MainMainWindow::setupMailPage() {
|
||||
m_mailPage = new QWidget();
|
||||
QHBoxLayout *mailLayout = new QHBoxLayout(m_mailPage);
|
||||
mailLayout->setContentsMargins(0, 0, 0, 0);
|
||||
mailLayout->setSpacing(0);
|
||||
|
||||
m_folderSplitter = new QSplitter(Qt::Horizontal);
|
||||
m_folderSplitter->setHandleWidth(1);
|
||||
|
||||
// Folder tree
|
||||
m_folderTree = new QTreeView();
|
||||
m_folderTree->setHeaderHidden(true);
|
||||
m_folderTree->setIndentation(20);
|
||||
m_folderTree->setMinimumWidth(220);
|
||||
m_folderTree->setMaximumWidth(350);
|
||||
m_folderTree->setFrameShape(QFrame::NoFrame);
|
||||
m_folderTree->setExpandsOnDoubleClick(true);
|
||||
m_folderSplitter->addWidget(m_folderTree);
|
||||
|
||||
// Mail list (QTableView with sorting)
|
||||
m_mailListView = new MailListView();
|
||||
connect(m_mailListView, &MailListView::emailSelected, this, &MainMainWindow::onEmailSelected);
|
||||
connect(m_mailListView, &MailListView::composeRequested, this, &MainMainWindow::onComposeRequested);
|
||||
m_folderSplitter->addWidget(m_mailListView);
|
||||
|
||||
// Reader
|
||||
m_emailViewer = new ReaderView();
|
||||
m_emailViewer->setMinimumWidth(350);
|
||||
connect(m_emailViewer, &ReaderView::replyRequested, this, &MainMainWindow::onReaderReplyRequested);
|
||||
m_folderSplitter->addWidget(m_emailViewer);
|
||||
|
||||
// Default sizes: folder 240, list 380, reader flex
|
||||
m_folderSplitter->setSizes({240, 380, 600});
|
||||
|
||||
mailLayout->addWidget(m_folderSplitter);
|
||||
}
|
||||
|
||||
void MainMainWindow::connectModels() {
|
||||
m_accountService = new AccountService(this);
|
||||
m_mailService = new MailService(this);
|
||||
|
||||
m_folderModel = new FolderListModel(m_accountService, this);
|
||||
m_emailModel = new EmailListModel(this);
|
||||
|
||||
m_folderTree->setModel(m_folderModel);
|
||||
m_mailListView->setModel(m_emailModel);
|
||||
|
||||
m_folderTree->expandAll();
|
||||
|
||||
connect(m_folderTree, &QTreeView::clicked, this, &MainMainWindow::onFolderSelected);
|
||||
}
|
||||
|
||||
void MainMainWindow::onNavChanged(int index) {
|
||||
switchToPage(static_cast<Page>(index));
|
||||
}
|
||||
|
||||
void MainMainWindow::switchToPage(int pageIndex) {
|
||||
m_stack->setCurrentIndex(pageIndex);
|
||||
m_sidebar->blockSignals(true);
|
||||
m_sidebar->setCurrentRow(pageIndex);
|
||||
m_sidebar->blockSignals(false);
|
||||
|
||||
// Show/hide toolbar actions per page
|
||||
}
|
||||
|
||||
void MainMainWindow::onFolderSelected(const QModelIndex &index) {
|
||||
if (!index.isValid()) return;
|
||||
|
||||
int itemType = index.data(FolderListModel::ItemTypeRole).toInt();
|
||||
if (itemType == FolderTreeItem::FolderNode) {
|
||||
m_currentFolderId = index.data(FolderListModel::FolderIdRole).toInt();
|
||||
m_emailModel->setFolderId(m_currentFolderId);
|
||||
m_emailModel->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void MainMainWindow::onEmailSelected(int mailId) {
|
||||
std::optional<MailItem> item = MailItemDao::findById(mailId);
|
||||
if (!item.has_value()) {
|
||||
m_emailViewer->setMailItem(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
MailItem &mail = item.value();
|
||||
if (!mail.isRead()) {
|
||||
mail.setRead(true);
|
||||
MailItemDao::update(mail);
|
||||
}
|
||||
m_emailViewer->setMailItem(&mail);
|
||||
}
|
||||
|
||||
void MainMainWindow::onComposeRequested() {
|
||||
switchToPage(PageCompose);
|
||||
}
|
||||
|
||||
void MainMainWindow::onReaderReplyRequested(const MailItem *item) {
|
||||
if (item) {
|
||||
m_composeView->setTo(item->sender());
|
||||
m_composeView->setSubject("Re: " + item->subject());
|
||||
}
|
||||
switchToPage(PageCompose);
|
||||
}
|
||||
|
||||
void MainMainWindow::onNewMessage() {
|
||||
switchToPage(PageCompose);
|
||||
}
|
||||
|
||||
void MainMainWindow::createToolBar() {
|
||||
m_toolBar = addToolBar("Main Toolbar");
|
||||
m_toolBar->setMovable(false);
|
||||
|
||||
QAction *newMsgAction = m_toolBar->addAction("✉ New Message");
|
||||
m_toolBar->addSeparator();
|
||||
QAction *syncAction = m_toolBar->addAction("⟳ Sync/Refresh");
|
||||
QAction *deleteAction = m_toolBar->addAction("🗑 Delete");
|
||||
|
||||
connect(newMsgAction, &QAction::triggered, this, &MainMainWindow::onNewMessage);
|
||||
connect(syncAction, &QAction::triggered, [this]() {
|
||||
if (m_currentFolderId >= 0) {
|
||||
m_emailModel->refresh();
|
||||
statusBar()->showMessage("Refreshed", 2000);
|
||||
}
|
||||
});
|
||||
connect(deleteAction, &QAction::triggered, [this]() {
|
||||
statusBar()->showMessage("Delete would be implemented here", 3000);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QStackedWidget>
|
||||
#include <QListWidget>
|
||||
#include <QSplitter>
|
||||
#include <QTreeView>
|
||||
#include <QStatusBar>
|
||||
#include <QToolBar>
|
||||
#include <QAction>
|
||||
|
||||
#include "ui/readerview.h"
|
||||
#include "ui/maillistview.h"
|
||||
#include "ui/composeview.h"
|
||||
#include "ui/settingsview.h"
|
||||
#include "ui/contactsview.h"
|
||||
#include "ui/calendarview.h"
|
||||
#include "ui/models/FolderListModel.h"
|
||||
#include "ui/models/EmailListModel.h"
|
||||
#include "services/accountservice.h"
|
||||
#include "services/mailservice.h"
|
||||
|
||||
class MainMainWindow : public QMainWindow {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MainMainWindow(QWidget *parent = nullptr);
|
||||
~MainMainWindow() override = default;
|
||||
|
||||
private slots:
|
||||
void onNavChanged(int index);
|
||||
void onFolderSelected(const QModelIndex &index);
|
||||
void onEmailSelected(int mailId);
|
||||
void onComposeRequested();
|
||||
void onReaderReplyRequested(const MailItem *item);
|
||||
void onNewMessage();
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
void setupSidebar();
|
||||
void setupMailPage();
|
||||
void connectModels();
|
||||
void createToolBar();
|
||||
void switchToPage(int pageIndex);
|
||||
|
||||
// Navigation
|
||||
QListWidget *m_sidebar;
|
||||
QStackedWidget *m_stack;
|
||||
|
||||
enum Page {
|
||||
PageMail = 0,
|
||||
PageCompose,
|
||||
PageSettings,
|
||||
PageContacts,
|
||||
PageCalendar
|
||||
};
|
||||
|
||||
// Mail page widgets
|
||||
QWidget *m_mailPage;
|
||||
QSplitter *m_folderSplitter;
|
||||
QTreeView *m_folderTree;
|
||||
MailListView *m_mailListView;
|
||||
ReaderView *m_emailViewer;
|
||||
|
||||
// Other pages
|
||||
ComposeView *m_composeView;
|
||||
SettingsView *m_settingsView;
|
||||
ContactsView *m_contactsView;
|
||||
CalendarView *m_calendarView;
|
||||
|
||||
// Services & Models
|
||||
FolderListModel *m_folderModel;
|
||||
EmailListModel *m_emailModel;
|
||||
AccountService *m_accountService;
|
||||
MailService *m_mailService;
|
||||
|
||||
QToolBar *m_toolBar;
|
||||
int m_currentFolderId;
|
||||
};
|
||||
@@ -3,8 +3,7 @@
|
||||
#include <QDateTime>
|
||||
|
||||
EmailListModel::EmailListModel(QObject *parent)
|
||||
: QAbstractListModel(parent),
|
||||
m_mailItemDao(MailItemDao::instance())
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
refresh();
|
||||
}
|
||||
@@ -89,9 +88,9 @@ void EmailListModel::refresh()
|
||||
{
|
||||
beginResetModel();
|
||||
if (m_folderId == -1) {
|
||||
m_emails = m_mailItemDao.findAll();
|
||||
m_emails = MailItemDao::findAll();
|
||||
} else {
|
||||
m_emails = m_mailItemDao.findByFolderId(m_folderId);
|
||||
m_emails = MailItemDao::findByFolderId(m_folderId);
|
||||
}
|
||||
endResetModel();
|
||||
qDebug() << "EmailListModel refreshed with" << m_emails.size() << "emails for folderId" << m_folderId;
|
||||
|
||||
@@ -40,7 +40,7 @@ public:
|
||||
private:
|
||||
QVector<MailItem> m_emails;
|
||||
int m_folderId{-1}; // -1 means all folders
|
||||
MailItemDao& m_mailItemDao;
|
||||
// REMOVED: MailItemDao& m_mailItemDao; // Methods are static, no instance needed
|
||||
};
|
||||
|
||||
#endif // EMAILLISTMODEL_H
|
||||
@@ -1,36 +1,177 @@
|
||||
#include "FolderListModel.h"
|
||||
#include <QDebug>
|
||||
|
||||
FolderListModel::FolderListModel(QObject *parent)
|
||||
: QAbstractListModel(parent),
|
||||
m_folderDao(FolderDao::instance())
|
||||
// --- FolderTreeItem ---
|
||||
|
||||
FolderTreeItem::FolderTreeItem(Type type, QVariant data, FolderTreeItem *parent)
|
||||
: m_type(type), m_data(std::move(data)), m_parentItem(parent)
|
||||
{
|
||||
}
|
||||
|
||||
FolderTreeItem::~FolderTreeItem()
|
||||
{
|
||||
qDeleteAll(m_childItems);
|
||||
}
|
||||
|
||||
void FolderTreeItem::appendChild(FolderTreeItem *child)
|
||||
{
|
||||
m_childItems.append(child);
|
||||
}
|
||||
|
||||
void FolderTreeItem::clearChildren()
|
||||
{
|
||||
qDeleteAll(m_childItems);
|
||||
m_childItems.clear();
|
||||
}
|
||||
|
||||
FolderTreeItem *FolderTreeItem::child(int row)
|
||||
{
|
||||
if (row < 0 || row >= m_childItems.size())
|
||||
return nullptr;
|
||||
return m_childItems.at(row);
|
||||
}
|
||||
|
||||
int FolderTreeItem::childCount() const
|
||||
{
|
||||
return m_childItems.size();
|
||||
}
|
||||
|
||||
int FolderTreeItem::row() const
|
||||
{
|
||||
if (m_parentItem)
|
||||
return m_parentItem->m_childItems.indexOf(const_cast<FolderTreeItem*>(this));
|
||||
return 0;
|
||||
}
|
||||
|
||||
FolderTreeItem *FolderTreeItem::parentItem()
|
||||
{
|
||||
return m_parentItem;
|
||||
}
|
||||
|
||||
// --- FolderListModel ---
|
||||
|
||||
FolderListModel::FolderListModel(AccountService *accountService, QObject *parent)
|
||||
: QAbstractItemModel(parent),
|
||||
m_accountService(accountService),
|
||||
m_rootItem(new FolderTreeItem(FolderTreeItem::AccountNode, "Root"))
|
||||
{
|
||||
refresh();
|
||||
}
|
||||
|
||||
FolderListModel::~FolderListModel()
|
||||
{
|
||||
delete m_rootItem;
|
||||
}
|
||||
|
||||
QModelIndex FolderListModel::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
if (!hasIndex(row, column, parent))
|
||||
return QModelIndex();
|
||||
|
||||
FolderTreeItem *parentItem;
|
||||
if (!parent.isValid())
|
||||
parentItem = m_rootItem;
|
||||
else
|
||||
parentItem = static_cast<FolderTreeItem*>(parent.internalPointer());
|
||||
|
||||
FolderTreeItem *childItem = parentItem->child(row);
|
||||
if (childItem)
|
||||
return createIndex(row, column, childItem);
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
QModelIndex FolderListModel::parent(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QModelIndex();
|
||||
|
||||
FolderTreeItem *childItem = static_cast<FolderTreeItem*>(index.internalPointer());
|
||||
FolderTreeItem *parentItem = childItem->parentItem();
|
||||
|
||||
if (!parentItem || parentItem == m_rootItem)
|
||||
return QModelIndex();
|
||||
|
||||
return createIndex(parentItem->row(), 0, parentItem);
|
||||
}
|
||||
|
||||
int FolderListModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
if (parent.isValid())
|
||||
if (parent.column() > 0)
|
||||
return 0;
|
||||
return m_folderNames.size();
|
||||
|
||||
FolderTreeItem *parentItem;
|
||||
if (!parent.isValid())
|
||||
parentItem = m_rootItem;
|
||||
else
|
||||
parentItem = static_cast<FolderTreeItem*>(parent.internalPointer());
|
||||
|
||||
return parentItem->childCount();
|
||||
}
|
||||
|
||||
int FolderListModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return 1;
|
||||
}
|
||||
|
||||
QVariant FolderListModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() >= m_folderNames.size())
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
if (role == FolderNameRole)
|
||||
return m_folderNames.at(index.row());
|
||||
else if (role == UnreadCountRole)
|
||||
return m_unreadCounts.at(index.row());
|
||||
FolderTreeItem *item = static_cast<FolderTreeItem*>(index.internalPointer());
|
||||
|
||||
if (role == Qt::DisplayRole || role == NameRole) {
|
||||
if (item->type() == FolderTreeItem::AccountNode) {
|
||||
Account acc = item->data().value<Account>();
|
||||
return acc.displayName().isEmpty() ? acc.email() : acc.displayName();
|
||||
} else {
|
||||
Folder folder = item->data().value<Folder>();
|
||||
return folder.name();
|
||||
}
|
||||
}
|
||||
|
||||
if (role == ItemTypeRole)
|
||||
return item->type();
|
||||
|
||||
if (role == AccountIdRole) {
|
||||
if (item->type() == FolderTreeItem::AccountNode) {
|
||||
Account acc = item->data().value<Account>();
|
||||
return acc.id();
|
||||
}
|
||||
}
|
||||
|
||||
if (role == FolderIdRole) {
|
||||
if (item->type() == FolderTreeItem::FolderNode) {
|
||||
Folder folder = item->data().value<Folder>();
|
||||
return folder.id();
|
||||
}
|
||||
}
|
||||
|
||||
if (role == UnreadCountRole) {
|
||||
if (item->type() == FolderTreeItem::FolderNode) {
|
||||
Folder folder = item->data().value<Folder>();
|
||||
return folder.unreadCount();
|
||||
}
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
Qt::ItemFlags FolderListModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return Qt::NoItemFlags;
|
||||
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> FolderListModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[FolderNameRole] = "folderName";
|
||||
roles[ItemTypeRole] = "itemType";
|
||||
roles[AccountIdRole] = "accountId";
|
||||
roles[FolderIdRole] = "folderId";
|
||||
roles[NameRole] = "name";
|
||||
roles[UnreadCountRole] = "unreadCount";
|
||||
return roles;
|
||||
}
|
||||
@@ -38,9 +179,55 @@ QHash<int, QByteArray> FolderListModel::roleNames() const
|
||||
void FolderListModel::refresh()
|
||||
{
|
||||
beginResetModel();
|
||||
// For now, we'll just use hardcoded folders until we implement the DAO properly
|
||||
m_folderNames = {"Inbox", "Sent", "Drafts", "Trash", "Spam"};
|
||||
m_unreadCounts = {5, 0, 0, 0, 0};
|
||||
clearModel();
|
||||
setupModelData();
|
||||
endResetModel();
|
||||
qDebug() << "FolderListModel refreshed with" << m_folderNames.size() << "folders";
|
||||
}
|
||||
qDebug() << "FolderListModel refreshed";
|
||||
}
|
||||
|
||||
void FolderListModel::setupModelData()
|
||||
{
|
||||
QVector<Account> accounts = m_accountService->getAllAccounts();
|
||||
|
||||
// If no accounts exist yet, add one sample account with default folders
|
||||
if (accounts.isEmpty()) {
|
||||
Account sample(1, "javi@example.com", "Javi's Email",
|
||||
AccountType::IMAP);
|
||||
accounts.append(sample);
|
||||
}
|
||||
|
||||
for (const Account &acc : accounts) {
|
||||
QVariant accVariant;
|
||||
accVariant.setValue(acc);
|
||||
FolderTreeItem *accountItem = new FolderTreeItem(FolderTreeItem::AccountNode, accVariant, m_rootItem);
|
||||
m_rootItem->appendChild(accountItem);
|
||||
|
||||
// Get folders for this account
|
||||
QVector<Folder> folders = FolderDao::findByAccountId(acc.id());
|
||||
|
||||
// If no folders exist yet, create default ones
|
||||
if (folders.isEmpty()) {
|
||||
QStringList defaultFolders = {"Inbox", "Sent", "Drafts", "Trash", "Spam"};
|
||||
for (const QString &name : defaultFolders) {
|
||||
Folder f;
|
||||
f.setName(name);
|
||||
f.setAccountId(acc.id());
|
||||
folders.append(f);
|
||||
}
|
||||
}
|
||||
|
||||
for (const Folder &folder : folders) {
|
||||
QVariant folderVariant;
|
||||
folderVariant.setValue(folder);
|
||||
FolderTreeItem *folderItem = new FolderTreeItem(FolderTreeItem::FolderNode, folderVariant, accountItem);
|
||||
accountItem->appendChild(folderItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FolderListModel::clearModel()
|
||||
{
|
||||
m_rootItem->clearChildren();
|
||||
}
|
||||
|
||||
#include "FolderListModel.moc"
|
||||
@@ -1,34 +1,70 @@
|
||||
#ifndef FOLDERLISTMODEL_H
|
||||
#define FOLDERLISTMODEL_H
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QAbstractItemModel>
|
||||
#include <QHash>
|
||||
#include <QByteArray>
|
||||
#include "../db/dao/folderdao.h"
|
||||
#include <QVector>
|
||||
#include "services/accountservice.h"
|
||||
#include "db/dao/folderdao.h"
|
||||
#include "core/models/account.h"
|
||||
#include "core/models/folder.h"
|
||||
|
||||
class FolderListModel : public QAbstractListModel
|
||||
class FolderTreeItem
|
||||
{
|
||||
public:
|
||||
enum Type { AccountNode, FolderNode };
|
||||
|
||||
explicit FolderTreeItem(Type type, QVariant data, FolderTreeItem *parent = nullptr);
|
||||
~FolderTreeItem();
|
||||
|
||||
void appendChild(FolderTreeItem *child);
|
||||
void clearChildren();
|
||||
FolderTreeItem *child(int row);
|
||||
int childCount() const;
|
||||
int row() const;
|
||||
FolderTreeItem *parentItem();
|
||||
Type type() const { return m_type; }
|
||||
QVariant data() const { return m_data; }
|
||||
|
||||
private:
|
||||
Type m_type;
|
||||
QVariant m_data;
|
||||
QVector<FolderTreeItem*> m_childItems;
|
||||
FolderTreeItem *m_parentItem;
|
||||
};
|
||||
|
||||
class FolderListModel : public QAbstractItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FolderListModel(QObject *parent = nullptr);
|
||||
~FolderListModel() override = default;
|
||||
explicit FolderListModel(AccountService *accountService, QObject *parent = nullptr);
|
||||
~FolderListModel() override;
|
||||
|
||||
enum FolderRoles {
|
||||
FolderNameRole = Qt::UserRole + 1,
|
||||
enum Roles {
|
||||
ItemTypeRole = Qt::UserRole + 1,
|
||||
AccountIdRole,
|
||||
FolderIdRole,
|
||||
NameRole,
|
||||
UnreadCountRole
|
||||
};
|
||||
|
||||
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
|
||||
QModelIndex parent(const QModelIndex &index) const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
// Optional: method to refresh the model from the database
|
||||
void refresh();
|
||||
|
||||
private:
|
||||
QVector<QString> m_folderNames;
|
||||
QVector<int> m_unreadCounts;
|
||||
FolderDao& m_folderDao;
|
||||
void setupModelData();
|
||||
void clearModel();
|
||||
|
||||
FolderTreeItem *m_rootItem;
|
||||
AccountService *m_accountService;
|
||||
};
|
||||
|
||||
#endif // FOLDERLISTMODEL_H
|
||||
@@ -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(); }
|
||||
@@ -0,0 +1,95 @@
|
||||
#ifndef NEWMESSAGEDIALOG_H
|
||||
#define NEWMESSAGEDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QLineEdit>
|
||||
#include <QTextEdit>
|
||||
#include <QPushButton>
|
||||
#include <QComboBox>
|
||||
#include <QLabel>
|
||||
#include <QToolBar>
|
||||
#include <QDateTimeEdit>
|
||||
#include <QListWidget>
|
||||
#include <QStringList>
|
||||
#include <QTextCharFormat>
|
||||
|
||||
class NewMessageDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit NewMessageDialog(const QStringList &accounts, QWidget *parent = nullptr);
|
||||
~NewMessageDialog() override = default;
|
||||
|
||||
QString fromAccount() const;
|
||||
QString to() const;
|
||||
QString cc() const;
|
||||
QString bcc() const;
|
||||
QString subject() const;
|
||||
QString body() const;
|
||||
QStringList attachments() const { return m_attachedFiles; }
|
||||
bool sendLater() const { return m_sendLater; }
|
||||
QDateTime scheduledTime() const { return m_scheduledTime; }
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void toggleCc();
|
||||
void toggleBcc();
|
||||
void onSendNow();
|
||||
void onSendLater();
|
||||
void onAttachFile();
|
||||
void removeAttachment();
|
||||
void onFormatBold();
|
||||
void onFormatItalic();
|
||||
void onFormatUnderline();
|
||||
void onInsertBulletList();
|
||||
void onInsertNumberedList();
|
||||
void onInsertTable();
|
||||
void onInsertImage();
|
||||
void mergeFormatOnWordOrSelection(const QTextCharFormat &fmt);
|
||||
void currentCharFormatChanged(const QTextCharFormat &fmt);
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
void setupRichTextToolbar(QToolBar *tb);
|
||||
bool hasUnsavedChanges() const;
|
||||
|
||||
// Header fields
|
||||
QComboBox *m_fromCombo;
|
||||
QLineEdit *m_toEdit;
|
||||
QPushButton *m_ccBtn;
|
||||
QPushButton *m_bccBtn;
|
||||
QLineEdit *m_ccEdit;
|
||||
QLineEdit *m_bccEdit;
|
||||
QLineEdit *m_subjectEdit;
|
||||
|
||||
// Rich text editor
|
||||
QToolBar *m_richToolbar;
|
||||
QAction *m_boldAction;
|
||||
QAction *m_italicAction;
|
||||
QAction *m_underlineAction;
|
||||
|
||||
// Body
|
||||
QTextEdit *m_bodyEdit;
|
||||
|
||||
// Attachments
|
||||
QPushButton *m_attachBtn;
|
||||
QListWidget *m_attachmentList;
|
||||
QStringList m_attachedFiles;
|
||||
|
||||
// Bottom actions
|
||||
QPushButton *m_sendNowBtn;
|
||||
QPushButton *m_sendLaterBtn;
|
||||
QPushButton *m_cancelBtn;
|
||||
QDateTimeEdit *m_schedulePicker;
|
||||
|
||||
bool m_ccVisible{false};
|
||||
bool m_bccVisible{false};
|
||||
bool m_sendLater{false};
|
||||
QDateTime m_scheduledTime;
|
||||
QString m_initialContent; // For detecting unsaved changes
|
||||
};
|
||||
|
||||
#endif // NEWMESSAGEDIALOG_H
|
||||
@@ -0,0 +1,77 @@
|
||||
#include "ui/readerview.h"
|
||||
#include <QFont>
|
||||
|
||||
ReaderView::ReaderView(QWidget *parent) : QWidget(parent) {
|
||||
setupUI();
|
||||
}
|
||||
|
||||
void ReaderView::setupUI() {
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->setContentsMargins(15, 15, 15, 15);
|
||||
mainLayout->setSpacing(10);
|
||||
|
||||
// Header Section
|
||||
QWidget *headerWidget = new QWidget();
|
||||
QVBoxLayout *headerLayout = new QVBoxLayout(headerWidget);
|
||||
headerLayout->setSpacing(5);
|
||||
|
||||
m_subjectLabel = new QLabel();
|
||||
QFont subjectFont = m_subjectLabel->font();
|
||||
subjectFont.setBold(true);
|
||||
subjectFont.setPointSize(14);
|
||||
m_subjectLabel->setFont(subjectFont);
|
||||
m_subjectLabel->setText("No subject");
|
||||
m_subjectLabel->setWordWrap(true);
|
||||
|
||||
m_fromLabel = new QLabel();
|
||||
m_fromLabel->setText("From: ");
|
||||
|
||||
m_dateLabel = new QLabel();
|
||||
m_dateLabel->setText("Date: ");
|
||||
m_dateLabel->setStyleSheet("color: gray; font-style: italic;");
|
||||
|
||||
headerLayout->addWidget(m_subjectLabel);
|
||||
headerLayout->addWidget(m_fromLabel);
|
||||
headerLayout->addWidget(m_dateLabel);
|
||||
|
||||
// Action Buttons
|
||||
QHBoxLayout *actionsLayout = new QHBoxLayout();
|
||||
m_replyButton = new QPushButton("Reply");
|
||||
m_forwardButton = new QPushButton("Forward");
|
||||
m_deleteButton = new QPushButton("Delete");
|
||||
m_deleteButton->setStyleSheet("color: red;");
|
||||
|
||||
actionsLayout->addWidget(m_replyButton);
|
||||
actionsLayout->addWidget(m_forwardButton);
|
||||
actionsLayout->addStretch();
|
||||
actionsLayout->addWidget(m_deleteButton);
|
||||
|
||||
// Body Viewer
|
||||
m_bodyViewer = new QTextBrowser();
|
||||
m_bodyViewer->setOpenExternalLinks(true);
|
||||
m_bodyViewer->setFrameStyle(QFrame::NoFrame);
|
||||
|
||||
mainLayout->addWidget(headerWidget);
|
||||
mainLayout->addLayout(actionsLayout);
|
||||
mainLayout->addWidget(m_bodyViewer);
|
||||
|
||||
// Connections
|
||||
connect(m_replyButton, &QPushButton::clicked, [this]() {
|
||||
if (m_bodyViewer->toPlainText().isEmpty()) return;
|
||||
});
|
||||
}
|
||||
|
||||
void ReaderView::setMailItem(const MailItem* item) {
|
||||
if (!item) {
|
||||
m_subjectLabel->setText("No mail selected");
|
||||
m_fromLabel->setText("From: ");
|
||||
m_dateLabel->setText("Date: ");
|
||||
m_bodyViewer->setHtml("<i>Please select a message to read</i>");
|
||||
return;
|
||||
}
|
||||
|
||||
m_subjectLabel->setText(item->subject());
|
||||
m_fromLabel->setText(QString("From: %1").arg(item->sender()));
|
||||
m_dateLabel->setText(QString("Date: %1").arg(item->date().toString("ddd, d MMM yyyy hh:mm")));
|
||||
m_bodyViewer->setHtml(item->bodyHtml());
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QLabel>
|
||||
#include <QTextBrowser>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include "core/mailitem.h"
|
||||
|
||||
class ReaderView : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ReaderView(QWidget *parent = nullptr);
|
||||
~ReaderView() override = default;
|
||||
|
||||
void setMailItem(const MailItem* item);
|
||||
|
||||
signals:
|
||||
void replyRequested(const MailItem* item);
|
||||
void forwardRequested(const MailItem* item);
|
||||
void deleteRequested(const MailItem* item);
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
|
||||
QLabel *m_subjectLabel;
|
||||
QLabel *m_fromLabel;
|
||||
QLabel *m_dateLabel;
|
||||
QTextBrowser *m_bodyViewer;
|
||||
|
||||
QPushButton *m_replyButton;
|
||||
QPushButton *m_forwardButton;
|
||||
QPushButton *m_deleteButton;
|
||||
};
|
||||
@@ -0,0 +1,164 @@
|
||||
#include "ui/settingsview.h"
|
||||
#include "services/accountservice.h"
|
||||
#include "core/models/account.h"
|
||||
|
||||
SettingsView::SettingsView(QWidget *parent) : QWidget(parent) {
|
||||
setupUI();
|
||||
}
|
||||
|
||||
void SettingsView::setupUI() {
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
m_tabWidget = new QTabWidget();
|
||||
m_tabWidget->addTab(createAccountsTab(), "Accounts");
|
||||
m_tabWidget->addTab(createGeneralTab(), "General");
|
||||
m_tabWidget->addTab(createAppearanceTab(), "Appearance");
|
||||
|
||||
mainLayout->addWidget(m_tabWidget);
|
||||
}
|
||||
|
||||
QWidget* SettingsView::createAccountsTab() {
|
||||
QWidget *w = new QWidget();
|
||||
QVBoxLayout *layout = new QVBoxLayout(w);
|
||||
layout->setContentsMargins(20, 20, 20, 20);
|
||||
layout->setSpacing(15);
|
||||
|
||||
QLabel *sectionTitle = new QLabel("Email Accounts");
|
||||
QFont titleFont = sectionTitle->font();
|
||||
titleFont.setPointSize(16);
|
||||
titleFont.setBold(true);
|
||||
sectionTitle->setFont(titleFont);
|
||||
layout->addWidget(sectionTitle);
|
||||
|
||||
m_accountList = new QListWidget();
|
||||
m_accountList->setAlternatingRowColors(true);
|
||||
m_accountList->setFrameShape(QFrame::NoFrame);
|
||||
m_accountList->setStyleSheet(
|
||||
"QListWidget { background: #ffffff; border: 1px solid #e0e0e0; border-radius: 4px; }"
|
||||
"QListWidget::item { padding: 12px; border-bottom: 1px solid #f0f0f0; }"
|
||||
"QListWidget::item:selected { background: #e3f2fd; }"
|
||||
);
|
||||
layout->addWidget(m_accountList, 1);
|
||||
|
||||
QPushButton *addBtn = new QPushButton("+ Add Email Account");
|
||||
addBtn->setStyleSheet(
|
||||
"QPushButton { background: #1976D2; color: white; border: none; border-radius: 4px; padding: 10px 20px; font-weight: bold; }"
|
||||
"QPushButton:hover { background: #1565C0; }"
|
||||
);
|
||||
connect(addBtn, &QPushButton::clicked, this, &SettingsView::accountAddRequested);
|
||||
layout->addWidget(addBtn);
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
QWidget* SettingsView::createGeneralTab() {
|
||||
QWidget *w = new QWidget();
|
||||
QVBoxLayout *layout = new QVBoxLayout(w);
|
||||
layout->setContentsMargins(20, 20, 20, 20);
|
||||
layout->setSpacing(15);
|
||||
|
||||
QLabel *sectionTitle = new QLabel("General Settings");
|
||||
QFont titleFont = sectionTitle->font();
|
||||
titleFont.setPointSize(16);
|
||||
titleFont.setBold(true);
|
||||
sectionTitle->setFont(titleFont);
|
||||
layout->addWidget(sectionTitle);
|
||||
|
||||
QCheckBox *startOnLogin = new QCheckBox("Start application on login");
|
||||
startOnLogin->setChecked(true);
|
||||
connect(startOnLogin, &QCheckBox::toggled, [this](bool checked) {
|
||||
emit settingChanged("start_on_login", checked);
|
||||
});
|
||||
layout->addWidget(startOnLogin);
|
||||
|
||||
QCheckBox *enableNotifications = new QCheckBox("Enable notifications");
|
||||
enableNotifications->setChecked(true);
|
||||
connect(enableNotifications, &QCheckBox::toggled, [this](bool checked) {
|
||||
emit settingChanged("notifications_enabled", checked);
|
||||
});
|
||||
layout->addWidget(enableNotifications);
|
||||
|
||||
QCheckBox *minimizeToTray = new QCheckBox("Minimize to tray on close");
|
||||
minimizeToTray->setChecked(true);
|
||||
connect(minimizeToTray, &QCheckBox::toggled, [this](bool checked) {
|
||||
emit settingChanged("minimize_to_tray", checked);
|
||||
});
|
||||
layout->addWidget(minimizeToTray);
|
||||
|
||||
layout->addSpacing(10);
|
||||
|
||||
QHBoxLayout *syncLayout = new QHBoxLayout();
|
||||
QLabel *syncLabel = new QLabel("Sync Interval:");
|
||||
syncLabel->setStyleSheet("font-weight: bold; color: #555;");
|
||||
m_syncIntervalCombo = new QComboBox();
|
||||
m_syncIntervalCombo->addItem("15 minutes", 15);
|
||||
m_syncIntervalCombo->addItem("30 minutes", 30);
|
||||
m_syncIntervalCombo->addItem("60 minutes", 60);
|
||||
m_syncIntervalCombo->addItem("120 minutes", 120);
|
||||
m_syncIntervalCombo->setCurrentIndex(1);
|
||||
connect(m_syncIntervalCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int idx) {
|
||||
emit settingChanged("sync_interval", m_syncIntervalCombo->itemData(idx));
|
||||
});
|
||||
syncLayout->addWidget(syncLabel);
|
||||
syncLayout->addWidget(m_syncIntervalCombo);
|
||||
syncLayout->addStretch();
|
||||
layout->addLayout(syncLayout);
|
||||
|
||||
layout->addStretch();
|
||||
return w;
|
||||
}
|
||||
|
||||
QWidget* SettingsView::createAppearanceTab() {
|
||||
QWidget *w = new QWidget();
|
||||
QVBoxLayout *layout = new QVBoxLayout(w);
|
||||
layout->setContentsMargins(20, 20, 20, 20);
|
||||
layout->setSpacing(15);
|
||||
|
||||
QLabel *sectionTitle = new QLabel("Appearance");
|
||||
QFont titleFont = sectionTitle->font();
|
||||
titleFont.setPointSize(16);
|
||||
titleFont.setBold(true);
|
||||
sectionTitle->setFont(titleFont);
|
||||
layout->addWidget(sectionTitle);
|
||||
|
||||
QLabel *themeLabel = new QLabel("Theme:");
|
||||
themeLabel->setStyleSheet("font-weight: bold; color: #555;");
|
||||
layout->addWidget(themeLabel);
|
||||
|
||||
m_themeGroup = new QButtonGroup(this);
|
||||
QRadioButton *lightRadio = new QRadioButton("Light");
|
||||
QRadioButton *darkRadio = new QRadioButton("Dark");
|
||||
QRadioButton *systemRadio = new QRadioButton("System");
|
||||
lightRadio->setChecked(true);
|
||||
|
||||
m_themeGroup->addButton(lightRadio, 0);
|
||||
m_themeGroup->addButton(darkRadio, 1);
|
||||
m_themeGroup->addButton(systemRadio, 2);
|
||||
|
||||
connect(m_themeGroup, QOverload<int>::of(&QButtonGroup::idClicked), [this](int id) {
|
||||
QString theme;
|
||||
switch (id) {
|
||||
case 0: theme = "light"; break;
|
||||
case 1: theme = "dark"; break;
|
||||
case 2: theme = "system"; break;
|
||||
}
|
||||
emit themeChanged(theme);
|
||||
});
|
||||
|
||||
layout->addWidget(lightRadio);
|
||||
layout->addWidget(darkRadio);
|
||||
layout->addWidget(systemRadio);
|
||||
|
||||
layout->addSpacing(15);
|
||||
|
||||
QCheckBox *deepinTheme = new QCheckBox("Use Deepin theme");
|
||||
deepinTheme->setChecked(true);
|
||||
connect(deepinTheme, &QCheckBox::toggled, [this](bool checked) {
|
||||
emit settingChanged("deepin_theme", checked);
|
||||
});
|
||||
layout->addWidget(deepinTheme);
|
||||
|
||||
layout->addStretch();
|
||||
return w;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QTabWidget>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPushButton>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QComboBox>
|
||||
#include <QCheckBox>
|
||||
#include <QRadioButton>
|
||||
#include <QButtonGroup>
|
||||
#include <QVariant>
|
||||
|
||||
class SettingsView : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SettingsView(QWidget *parent = nullptr);
|
||||
~SettingsView() override = default;
|
||||
|
||||
signals:
|
||||
void accountAddRequested();
|
||||
void accountEditRequested(int accountIndex);
|
||||
void accountDeleteRequested(int accountIndex);
|
||||
void themeChanged(const QString &theme);
|
||||
void settingChanged(const QString &key, const QVariant &value);
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
QWidget* createAccountsTab();
|
||||
QWidget* createGeneralTab();
|
||||
QWidget* createAppearanceTab();
|
||||
|
||||
QTabWidget *m_tabWidget;
|
||||
QListWidget *m_accountList;
|
||||
QComboBox *m_syncIntervalCombo;
|
||||
QButtonGroup *m_themeGroup;
|
||||
};
|
||||
Reference in New Issue
Block a user