feat: email grouping by date + stability fixes
- EmailTreeModel: hierarchical model for grouped email view (QTreeView) - DateGroupProxyModel: proxy model for date-based grouping logic - MailListView: migrated from QTableView to QTreeView with grouping support - ReaderView: subject frame with shadow, avatar moved to header, body inside header - MainWindow: frameless with rounded corners, 1px border, edge resize, no status bar - Stability fixes for grouping mode switch: * Proper tree cleanup (no double-delete of root) * Bounds checking on all array accesses * Null pointer checks in index/parent/data methods * Date validity checks before grouping * Index validation before accessing m_emails
This commit is contained in:
+56
-23
@@ -20,6 +20,7 @@
|
||||
#include <QFileDialog>
|
||||
#include <QDir>
|
||||
#include <functional>
|
||||
#include <QGraphicsDropShadowEffect>
|
||||
#include "db/dao/mailitemdao.h"
|
||||
|
||||
ReaderView::ReaderView(QWidget *parent) : QWidget(parent) {
|
||||
@@ -29,7 +30,7 @@ ReaderView::ReaderView(QWidget *parent) : QWidget(parent) {
|
||||
|
||||
void ReaderView::setupUI() {
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
mainLayout->setContentsMargins(12, 0, 12, 0);
|
||||
mainLayout->setSpacing(0);
|
||||
|
||||
// ===== Toolbar (zoom, find, images) =====
|
||||
@@ -182,23 +183,31 @@ void ReaderView::setupUI() {
|
||||
mainLayout->addWidget(m_toolbar);
|
||||
mainLayout->addWidget(m_findBar);
|
||||
|
||||
// ===== Header Section =====
|
||||
m_headerWidget = new QWidget();
|
||||
m_headerWidget->setStyleSheet("QWidget { background: white; border-bottom: 1px solid #e0e0e0; }");
|
||||
QVBoxLayout *headerLayout = new QVBoxLayout(m_headerWidget);
|
||||
headerLayout->setContentsMargins(16, 12, 16, 12);
|
||||
headerLayout->setSpacing(8);
|
||||
// ===== Subject Section (independent, with shadow) =====
|
||||
QFrame *subjectFrame = new QFrame();
|
||||
subjectFrame->setObjectName("SubjectFrame");
|
||||
subjectFrame->setStyleSheet(
|
||||
"QFrame#SubjectFrame {"
|
||||
" background: white;"
|
||||
" border: none;"
|
||||
" border: 1px solid #e0e0e0;"
|
||||
" border-radius: 10px;"
|
||||
"}"
|
||||
);
|
||||
// Add shadow effect
|
||||
QGraphicsDropShadowEffect *shadowEffect = new QGraphicsDropShadowEffect(this);
|
||||
shadowEffect->setBlurRadius(8);
|
||||
shadowEffect->setOffset(0, 3);
|
||||
shadowEffect->setColor(QColor(0, 0, 0, 40));
|
||||
subjectFrame->setGraphicsEffect(shadowEffect);
|
||||
|
||||
QVBoxLayout *subjectLayout = new QVBoxLayout(subjectFrame);
|
||||
subjectLayout->setContentsMargins(16, 16, 16, 16);
|
||||
subjectLayout->setSpacing(8);
|
||||
|
||||
// Subject row
|
||||
QHBoxLayout *subjectRow = new QHBoxLayout();
|
||||
subjectRow->setSpacing(12);
|
||||
|
||||
m_avatarLabel = new QLabel();
|
||||
m_avatarLabel->setFixedSize(40, 40);
|
||||
m_avatarLabel->setAlignment(Qt::AlignCenter);
|
||||
m_avatarLabel->setStyleSheet("QLabel { background: #1976D2; color: white; border-radius: 20px; font-weight: bold; font-size: 14px; }");
|
||||
subjectRow->addWidget(m_avatarLabel);
|
||||
|
||||
m_subjectLabel = new QLabel();
|
||||
m_subjectLabel->setText("(Sin asunto)");
|
||||
QFont subjectFont = m_subjectLabel->font();
|
||||
@@ -210,12 +219,33 @@ void ReaderView::setupUI() {
|
||||
m_subjectLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
subjectRow->addWidget(m_subjectLabel, 1);
|
||||
|
||||
headerLayout->addLayout(subjectRow);
|
||||
subjectLayout->addLayout(subjectRow);
|
||||
mainLayout->addWidget(subjectFrame);
|
||||
// Add spacing after subject frame so shadow is visible
|
||||
mainLayout->addSpacing(12);
|
||||
|
||||
// From / To / Date row
|
||||
// ===== Header Section =====
|
||||
m_headerWidget = new QWidget();
|
||||
m_headerWidget->setStyleSheet("QWidget { background: white; border-bottom: 1px solid #e0e0e0; }");
|
||||
QVBoxLayout *headerLayout = new QVBoxLayout(m_headerWidget);
|
||||
headerLayout->setContentsMargins(16, 12, 16, 12);
|
||||
headerLayout->setSpacing(8);
|
||||
|
||||
// Avatar + From / To / Date row
|
||||
QHBoxLayout *metaRow = new QHBoxLayout();
|
||||
metaRow->setSpacing(16);
|
||||
|
||||
// Avatar on the left
|
||||
m_avatarLabel = new QLabel();
|
||||
m_avatarLabel->setFixedSize(40, 40);
|
||||
m_avatarLabel->setAlignment(Qt::AlignCenter);
|
||||
m_avatarLabel->setStyleSheet("QLabel { background: #1976D2; color: white; border-radius: 20px; font-weight: bold; font-size: 14px; }");
|
||||
metaRow->addWidget(m_avatarLabel, 0, Qt::AlignTop);
|
||||
|
||||
// From / To / Date on the right of avatar
|
||||
QVBoxLayout *metaRightLayout = new QVBoxLayout();
|
||||
metaRightLayout->setSpacing(2);
|
||||
|
||||
m_fromLabel = new QLabel();
|
||||
m_fromLabel->setStyleSheet("color: #333; font-size: 13px;");
|
||||
m_fromLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
@@ -223,17 +253,19 @@ void ReaderView::setupUI() {
|
||||
connect(m_fromLabel, &QLabel::linkActivated, [this](const QString &link) {
|
||||
QDesktopServices::openUrl(QUrl(link));
|
||||
});
|
||||
metaRow->addWidget(m_fromLabel, 1);
|
||||
metaRightLayout->addWidget(m_fromLabel);
|
||||
|
||||
m_toLabel = new QLabel();
|
||||
m_toLabel->setStyleSheet("color: #666; font-size: 12px;");
|
||||
m_toLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
m_toLabel->setWordWrap(true);
|
||||
metaRow->addWidget(m_toLabel, 2);
|
||||
metaRightLayout->addWidget(m_toLabel);
|
||||
|
||||
m_dateLabel = new QLabel();
|
||||
m_dateLabel->setStyleSheet("color: #888; font-size: 12px;");
|
||||
metaRow->addWidget(m_dateLabel);
|
||||
metaRightLayout->addWidget(m_dateLabel);
|
||||
|
||||
metaRow->addLayout(metaRightLayout, 1);
|
||||
|
||||
headerLayout->addLayout(metaRow);
|
||||
|
||||
@@ -310,16 +342,17 @@ void ReaderView::setupUI() {
|
||||
actionsLayout->addWidget(m_moreButton);
|
||||
|
||||
headerLayout->addLayout(actionsLayout);
|
||||
|
||||
// ===== Body Viewer (inside header widget) =====
|
||||
setupBodyViewer();
|
||||
headerLayout->addWidget(m_scrollArea, 1);
|
||||
|
||||
mainLayout->addWidget(m_headerWidget);
|
||||
|
||||
// ===== Attachments Area =====
|
||||
setupAttachmentsArea();
|
||||
mainLayout->addWidget(m_attachmentsFrame);
|
||||
|
||||
// ===== Body Viewer =====
|
||||
setupBodyViewer();
|
||||
mainLayout->addWidget(m_scrollArea, 1);
|
||||
|
||||
// Shortcuts
|
||||
QShortcut *findShortcut = new QShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_F), this);
|
||||
connect(findShortcut, &QShortcut::activated, [this]() {
|
||||
|
||||
Reference in New Issue
Block a user