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

401 lines
14 KiB
C++
Raw Normal View History

#include "ui/maillistview.h"
#include <QDateTime>
#include "ui/models/EmailListModel.h"
#include "ui/models/EmailTreeModel.h"
#include <QSortFilterProxyModel>
#include <QVBoxLayout>
#include <QPushButton>
#include <QLabel>
#include <QLineEdit>
#include <QCheckBox>
#include <QHBoxLayout>
#include <QComboBox>
#include <QHeaderView>
#include <QResizeEvent>
#include <QListView>
#include <QStyledItemDelegate>
#include <QScrollBar>
#include "ui/delegates/CompactMailDelegate.h"
MailListView::MailListView(QWidget *parent) : QWidget(parent), m_sourceModel(nullptr)
{
setupUI();
}
void MailListView::setupUI()
{
QVBoxLayout *layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
2026-08-23 14:49:58 +02:00
// Search bar
QWidget *searchBar = new QWidget();
searchBar->setFixedHeight(44);
searchBar->setStyleSheet("background-color: #f5f5f7; border-bottom: 1px solid #d1d1d6;");
QHBoxLayout *searchLayout = new QHBoxLayout(searchBar);
searchLayout->setContentsMargins(12, 4, 12, 4);
searchLayout->setSpacing(8);
m_searchEdit = new QLineEdit();
m_searchEdit->setPlaceholderText("Buscar correos…");
m_searchEdit->setClearButtonEnabled(true);
m_searchEdit->setStyleSheet(
"QLineEdit { background: white; border: 1px solid #d1d1d6; border-radius: 6px; "
"padding: 6px 12px; font-size: 13px; color: #1d1d1f; }"
"QLineEdit:focus { border: 2px solid #0071e3; }"
);
searchLayout->addWidget(m_searchEdit);
connect(m_searchEdit, &QLineEdit::textChanged, this, &MailListView::onSearchTextChanged);
layout->addWidget(searchBar);
// Filter bar
QWidget *filterBar = new QWidget();
filterBar->setFixedHeight(36);
filterBar->setStyleSheet("background-color: #f5f5f7; border-bottom: 1px solid #d1d1d6;");
QHBoxLayout *filterLayout = new QHBoxLayout(filterBar);
filterLayout->setContentsMargins(12, 2, 12, 2);
filterLayout->setSpacing(12);
m_unreadOnlyCheck = new QCheckBox("No leídos");
m_unreadOnlyCheck->setStyleSheet("QCheckBox { font-size: 12px; color: #333; }");
filterLayout->addWidget(m_unreadOnlyCheck);
m_flaggedOnlyCheck = new QCheckBox("Marcados");
m_flaggedOnlyCheck->setStyleSheet("QCheckBox { font-size: 12px; color: #333; }");
filterLayout->addWidget(m_flaggedOnlyCheck);
m_hasAttachmentsCheck = new QCheckBox("Con adjuntos");
m_hasAttachmentsCheck->setStyleSheet("QCheckBox { font-size: 12px; color: #333; }");
filterLayout->addWidget(m_hasAttachmentsCheck);
m_pinnedOnlyCheck = new QCheckBox("Solo fijados");
m_pinnedOnlyCheck->setStyleSheet("QCheckBox { font-size: 12px; color: #333; }");
filterLayout->addWidget(m_pinnedOnlyCheck);
2026-08-23 14:49:58 +02:00
filterLayout->addStretch();
// Display mode combo (Compact/Medium/Spacious)
m_displayModeCombo = new QComboBox();
m_displayModeCombo->addItem("Compacto");
m_displayModeCombo->addItem("Medio");
m_displayModeCombo->addItem("Espacioso");
m_displayModeCombo->setFixedWidth(120);
m_displayModeCombo->setStyleSheet(
"QComboBox { border: 1px solid #d1d1d6; border-radius: 4px; padding: 4px 8px; "
"background: white; min-height: 20px; }"
"QComboBox:hover { border-color: #bdbdbd; }"
"QComboBox:focus { border-color: #1976D2; }"
);
connect(m_displayModeCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &MailListView::onDisplayModeChanged);
filterLayout->addWidget(m_displayModeCombo);
// Group by combo
m_groupByCombo = new QComboBox();
m_groupByCombo->addItem("Sin agrupación");
m_groupByCombo->addItem("Agrupar por fecha");
m_groupByCombo->addItem("Agrupar por conversación");
m_groupByCombo->addItem("Agrupar por remitente");
m_groupByCombo->setFixedWidth(180);
m_groupByCombo->setStyleSheet(
"QComboBox { border: 1px solid #d1d1d6; border-radius: 4px; padding: 4px 8px; "
"background: white; min-height: 20px; }"
"QComboBox:hover { border-color: #bdbdbd; }"
"QComboBox:focus { border-color: #1976D2; }"
);
connect(m_groupByCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &MailListView::onGroupByChanged);
filterLayout->addWidget(m_groupByCombo);
2026-08-23 14:49:58 +02:00
layout->addWidget(filterBar);
// View Stack (TreeView for normal, ListView for compact)
m_viewStack = new QStackedWidget();
layout->addWidget(m_viewStack, 1);
// --- Normal Tree View ---
m_treeView = new QTreeView();
m_treeView->setSelectionBehavior(QAbstractItemView::SelectRows);
m_treeView->setSelectionMode(QAbstractItemView::SingleSelection);
m_treeView->setAlternatingRowColors(true);
m_treeView->header()->setStretchLastSection(false);
m_treeView->header()->setSectionsClickable(true);
m_treeView->setSortingEnabled(false);
m_treeView->setFrameShape(QFrame::NoFrame);
m_treeView->setRootIsDecorated(true);
m_treeView->setItemsExpandable(true);
m_treeView->setUniformRowHeights(true);
m_treeView->setStyleSheet(
"QTreeView { background-color: #ffffff; alternate-background-color: #f9f9fb; border: none; }"
"QTreeView::item { padding: 8px; border-bottom: 1px solid #e8e8ed; }"
"QTreeView::item:selected { background-color: #e3f2fd; color: #1a1a2e; }"
"QTreeView::branch { background: transparent; }"
"QHeaderView::section { background-color: #f5f5f7; padding: 8px; border: none; border-bottom: 1px solid #d1d1d6; font-weight: 600; color: #555; }"
);
m_treeModel = new EmailTreeModel(this);
m_proxyModel = new QSortFilterProxyModel(this);
m_proxyModel->setSourceModel(m_treeModel);
2026-08-23 14:49:58 +02:00
m_proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
m_proxyModel->setFilterKeyColumn(-1);
m_treeView->setModel(m_proxyModel);
m_treeView->header()->setSectionResizeMode(EmailTreeModel::ColSubject, QHeaderView::Stretch);
m_treeView->header()->setSectionResizeMode(EmailTreeModel::ColSender, QHeaderView::Stretch);
m_treeView->header()->setSectionResizeMode(EmailTreeModel::ColDate, QHeaderView::ResizeToContents);
connect(m_treeView, &QTreeView::clicked, this, &MailListView::onRowSelected);
connect(m_treeView, &QTreeView::doubleClicked, this, [this](const QModelIndex &index) {
2026-06-18 18:58:27 +02:00
if (!index.isValid()) return;
QModelIndex proxyIdx = m_proxyModel->mapToSource(index);
// Check if it's a thread node - toggle expansion
if (proxyIdx.isValid() && proxyIdx.data(EmailTreeModel::IsThreadRole).toBool()) {
QString threadId = proxyIdx.data(EmailTreeModel::ThreadIdRole).toString();
if (!threadId.isEmpty()) {
bool expanded = proxyIdx.data(EmailTreeModel::IsThreadExpandedRole).toBool();
m_treeModel->setThreadExpanded(threadId, !expanded);
return;
}
}
// Otherwise open mail
if (proxyIdx.isValid() && proxyIdx.data(EmailTreeModel::MailIdRole).isValid()) {
int mailId = proxyIdx.data(EmailTreeModel::MailIdRole).toInt();
emit emailOpenRequested(mailId);
}
2026-06-18 18:58:27 +02:00
});
m_treeView->expandAll();
// --- Compact List View ---
m_listView = new QListView();
m_listView->setSelectionMode(QAbstractItemView::SingleSelection);
m_listView->setUniformItemSizes(true);
m_listView->setFrameShape(QFrame::NoFrame);
m_listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_listView->setStyleSheet(
"QListView { background-color: #ffffff; border: none; outline: none; }"
"QListView::item { border: none; }"
"QListView::item:selected { background: transparent; }"
"QListView::item:hover { background: transparent; }"
);
m_compactDelegate = new CompactMailDelegate(this);
m_listView->setItemDelegate(m_compactDelegate);
// Connect compact delegate signals
connect(m_compactDelegate, &CompactMailDelegate::flagRequested,
this, &MailListView::onCompactFlagRequested);
connect(m_compactDelegate, &CompactMailDelegate::deleteRequested,
this, &MailListView::onCompactDeleteRequested);
connect(m_compactDelegate, &CompactMailDelegate::categoryRequested,
this, &MailListView::onCompactCategoryRequested);
connect(m_compactDelegate, &CompactMailDelegate::moreRequested,
this, &MailListView::onCompactMoreRequested);
connect(m_compactDelegate, &CompactMailDelegate::mailClicked,
this, &MailListView::onCompactMailClicked);
// Same proxy model for list view (single column)
m_listView->setModel(m_proxyModel);
m_listView->setModelColumn(0); // Use first column (subject)
// Add both views to stack
m_viewStack->addWidget(m_treeView); // index 0 = normal
m_viewStack->addWidget(m_listView); // index 1 = compact
// Show normal by default
m_viewStack->setCurrentIndex(0);
// Connect filter checkboxes
connect(m_unreadOnlyCheck, &QCheckBox::toggled, this, &MailListView::onFilterChanged);
connect(m_flaggedOnlyCheck, &QCheckBox::toggled, this, &MailListView::onFilterChanged);
connect(m_hasAttachmentsCheck, &QCheckBox::toggled, this, &MailListView::onFilterChanged);
connect(m_pinnedOnlyCheck, &QCheckBox::toggled, this, &MailListView::onFilterChanged);
}
void MailListView::setModel(EmailListModel *model)
{
m_sourceModel = model;
// Connect to source model changes to refresh both views
connect(m_sourceModel, &QAbstractItemModel::modelReset, this, &MailListView::refreshViews);
connect(m_sourceModel, &QAbstractItemModel::layoutChanged, this, &MailListView::refreshViews);
connect(m_sourceModel, &QAbstractItemModel::rowsInserted, this, &MailListView::onRowsInserted);
connect(m_sourceModel, &QAbstractItemModel::rowsRemoved, this, &MailListView::refreshViews);
connect(m_sourceModel, &EmailListModel::totalCountChanged, this, &MailListView::onTotalCountChanged);
// Connect tree view scroll for fetchMore
connect(m_treeView->verticalScrollBar(), &QScrollBar::valueChanged, this, &MailListView::onTreeViewScrolled);
connect(m_listView->verticalScrollBar(), &QScrollBar::valueChanged, this, &MailListView::onListViewScrolled);
// Initial population
refreshViews();
}
void MailListView::refreshViews()
{
if (!m_sourceModel) return;
// For paginated model, just update the tree model with currently loaded emails
const QVector<MailItem> &emails = m_sourceModel->emails();
m_treeModel->setEmails(emails);
m_treeView->expandAll();
// List view uses same proxy model, just refresh
m_listView->update();
}
void MailListView::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
updateViewMode();
}
void MailListView::updateViewMode()
{
int viewportWidth = m_viewStack->width(); // available width
bool wantCompact = (viewportWidth < m_compactThreshold);
if (wantCompact != m_compactMode) {
m_compactMode = wantCompact;
m_viewStack->setCurrentIndex(m_compactMode ? 1 : 0);
if (m_compactMode) {
m_treeView->header()->hide();
} else {
m_treeView->header()->show();
}
}
}
void MailListView::onRowSelected(const QModelIndex &index)
{
if (!index.isValid()) return;
QModelIndex proxyIdx = m_proxyModel->mapToSource(index);
if (proxyIdx.isValid() && proxyIdx.data(EmailTreeModel::MailIdRole).isValid()) {
int mailId = proxyIdx.data(EmailTreeModel::MailIdRole).toInt();
emit emailSelected(mailId);
}
2026-08-23 14:49:58 +02:00
}
void MailListView::onSearchTextChanged(const QString &text)
{
m_proxyModel->setFilterFixedString(text);
2026-08-23 14:49:58 +02:00
}
void MailListView::onFilterChanged()
{
if (!m_sourceModel) return;
m_sourceModel->setShowUnreadOnly(m_unreadOnlyCheck->isChecked());
m_sourceModel->setShowFlaggedOnly(m_flaggedOnlyCheck->isChecked());
m_sourceModel->setShowHasAttachments(m_hasAttachmentsCheck->isChecked());
m_sourceModel->setShowPinnedOnly(m_pinnedOnlyCheck->isChecked());
}
void MailListView::onGroupByChanged(int index)
{
switch (index) {
case 0: // Sin agrupación
m_treeModel->setGroupMode(EmailTreeModel::NoGrouping);
break;
case 1: // Agrupar por fecha
m_treeModel->setGroupMode(EmailTreeModel::GroupByDate);
break;
case 2: // Agrupar por conversación/hilo
m_treeModel->setGroupMode(EmailTreeModel::GroupByThread);
break;
case 3: // Agrupar por remitente
m_treeModel->setGroupMode(EmailTreeModel::GroupBySender);
break;
}
m_treeView->expandAll();
}
void MailListView::onCompactFlagRequested(int mailId)
{
emit flagRequested(mailId);
}
void MailListView::onCompactDeleteRequested(int mailId)
{
emit deleteRequested(mailId);
}
void MailListView::onCompactCategoryRequested(int mailId)
{
emit categoryRequested(mailId);
}
void MailListView::onCompactMoreRequested(int mailId, const QPoint &globalPos)
{
emit moreRequested(mailId, globalPos);
}
void MailListView::onCompactMailClicked(int mailId)
{
emit emailSelected(mailId);
}
void MailListView::onCompactReplyRequested(int mailId)
{
emit replyRequested(mailId);
}
void MailListView::onCompactForwardRequested(int mailId)
{
emit forwardRequested(mailId);
}
void MailListView::onCompactMarkUnreadRequested(int mailId)
{
emit markUnreadRequested(mailId);
}
void MailListView::onDisplayModeChanged(int index)
{
CompactMailDelegate::DisplayMode mode = static_cast<CompactMailDelegate::DisplayMode>(index);
m_compactDelegate->setDisplayMode(mode);
m_listView->update();
}
void MailListView::onTotalCountChanged(int count)
{
Q_UNUSED(count);
// Could update a status label showing "X of Y emails"
}
void MailListView::onTreeViewScrolled(int value)
{
if (!m_sourceModel) return;
QScrollBar* scrollBar = m_treeView->verticalScrollBar();
int maxValue = scrollBar->maximum();
if (maxValue > 0 && value >= maxValue * 0.9) { // 90% threshold
m_sourceModel->fetchMore(QModelIndex());
}
}
void MailListView::onListViewScrolled(int value)
{
if (!m_sourceModel) return;
QScrollBar* scrollBar = m_listView->verticalScrollBar();
int maxValue = scrollBar->maximum();
if (maxValue > 0 && value >= maxValue * 0.9) { // 90% threshold
m_sourceModel->fetchMore(QModelIndex());
}
}
void MailListView::onRowsInserted(const QModelIndex &parent, int first, int last)
{
Q_UNUSED(parent);
Q_UNUSED(first);
Q_UNUSED(last);
// New emails loaded via fetchMore - refresh tree model
if (m_sourceModel) {
const QVector<MailItem> &emails = m_sourceModel->emails();
m_treeModel->setEmails(emails);
m_treeView->expandAll();
}
}