feat: EmailTreeModel grouping modes + MailListView integration

- EmailTreeModel: 4 grouping modes
  * NoGrouping - flat list
  * GroupByDate - chronological groups (Hoy, Ayer, Esta semana...)
  * GroupByThread - conversations with expandable threads (ThreadId from References/In-Reply-To/Message-ID)
  * GroupBySender - groups by sender email/name

- EmailTreeModel thread support:
  * ThreadItem type with expand/collapse
  * ThreadId extraction via ThreadUtils (References > In-Reply-To > Message-ID)
  * Double-click thread node toggles expansion
  * Threads sorted by newest email first, emails within thread oldest first

- EmailTreeModel sender grouping:
  * Groups by sender name/email
  * Senders sorted by newest email first
  * Emails within group newest first

- MailListView: Added 'Agrupar por remitente' to group-by combo
- MailListView: Double-click thread node toggles expansion (not open mail)
- ThreadUtils: parseReferencesHeader, extractThreadId, generateThreadId
This commit is contained in:
2026-08-25 18:53:51 +02:00
parent fa699b5399
commit 6e6fdf9c21
75 changed files with 25631 additions and 12574 deletions
+28 -2
View File
@@ -90,6 +90,8 @@ void MailListView::setupUI()
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; "
@@ -143,6 +145,18 @@ void MailListView::setupUI()
connect(m_treeView, &QTreeView::doubleClicked, this, [this](const QModelIndex &index) {
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);
@@ -270,8 +284,20 @@ void MailListView::onFilterChanged()
void MailListView::onGroupByChanged(int index)
{
bool enabled = (index == 1); // 0 = Sin agrupación, 1 = Agrupar por fecha
m_treeModel->setGroupMode(enabled ? EmailTreeModel::GroupByDate : EmailTreeModel::NoGrouping);
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();
}