Fix build: synchronize Request API, fix GmailSynchronizer, and migrate UI to Qt6 Widgets

This commit is contained in:
2026-06-17 22:44:27 +02:00
parent dcb7c52269
commit 0e9f620fe0
348 changed files with 118736 additions and 1207 deletions
+32
View File
@@ -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);
}
+16
View File
@@ -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();
};
+486
View File
@@ -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); }
+103
View File
@@ -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;
};
+32
View File
@@ -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);
}
+16
View File
@@ -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();
};
+103
View File
@@ -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);
}
+35
View File
@@ -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;
};
+270
View File
@@ -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);
});
}
+79
View File
@@ -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 -4
View File
@@ -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;
+1 -1
View File
@@ -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
+203 -16
View File
@@ -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"
+47 -11
View File
@@ -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
+407
View File
@@ -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(); }
+95
View File
@@ -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
+77
View File
@@ -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());
}
+36
View File
@@ -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;
};
+164
View File
@@ -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;
}
+40
View File
@@ -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;
};