feat: Pagination/virtualization (fetchMore) for EmailListModel
- EmailListModel: QAbstractListModel with canFetchMore/fetchMore * Batch loading (default 50 emails per fetch) * Total count query for scrollbar * TotalCountChanged signal * Paginated DB queries: findAllPaginated, findByFolderIdPaginated * Count queries: countAll, countByFolderId - MailItemDao: new paginated methods * findAllPaginated(offset, limit, filters...) * findByFolderIdPaginated(folderId, offset, limit, filters...) * countAll(filters...), countByFolderId(folderId, filters...) - MailListView: scroll-triggered fetchMore * Connects verticalScrollBar valueChanged on both TreeView and ListView * Triggers fetchMore at 90% scroll threshold * onRowsInserted updates EmailTreeModel when new emails arrive * Works in both normal (tree) and compact (list) views
This commit is contained in:
@@ -437,3 +437,201 @@ int MailItemDao::countUnreadByFolderId(int folderId)
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MailItemDao::countAll(const QString& searchFilter, bool unreadOnly, bool flaggedOnly, bool hasAttachments)
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QString sql = "SELECT COUNT(*) FROM MailCopy WHERE 1=1";
|
||||
QSqlQuery query(db);
|
||||
|
||||
if (!searchFilter.isEmpty()) {
|
||||
sql += " AND (subject LIKE :search OR sender LIKE :search OR recipient LIKE :search OR bodyHtml LIKE :search)";
|
||||
query.bindValue(":search", "%" + searchFilter + "%");
|
||||
}
|
||||
if (unreadOnly) {
|
||||
sql += " AND read = 0";
|
||||
}
|
||||
if (flaggedOnly) {
|
||||
sql += " AND flagged = 1";
|
||||
}
|
||||
if (hasAttachments) {
|
||||
sql += " AND hasAttachment = 1";
|
||||
}
|
||||
|
||||
query.prepare(sql);
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Failed to count all mails:" << query.lastError().text();
|
||||
return 0;
|
||||
}
|
||||
if (query.next()) {
|
||||
return query.value(0).toInt();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MailItemDao::countByFolderId(int folderId, const QString& searchFilter, bool unreadOnly, bool flaggedOnly, bool hasAttachments)
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QString sql = "SELECT COUNT(*) FROM MailCopy WHERE folderId = :folderId";
|
||||
QSqlQuery query(db);
|
||||
query.bindValue(":folderId", folderId);
|
||||
|
||||
if (!searchFilter.isEmpty()) {
|
||||
sql += " AND (subject LIKE :search OR sender LIKE :search OR recipient LIKE :search OR bodyHtml LIKE :search)";
|
||||
query.bindValue(":search", "%" + searchFilter + "%");
|
||||
}
|
||||
if (unreadOnly) {
|
||||
sql += " AND read = 0";
|
||||
}
|
||||
if (flaggedOnly) {
|
||||
sql += " AND flagged = 1";
|
||||
}
|
||||
if (hasAttachments) {
|
||||
sql += " AND hasAttachment = 1";
|
||||
}
|
||||
|
||||
query.prepare(sql);
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Failed to count mails by folder:" << query.lastError().text();
|
||||
return 0;
|
||||
}
|
||||
if (query.next()) {
|
||||
return query.value(0).toInt();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
QVector<MailItem> MailItemDao::findAllPaginated(int offset, int limit, const QString& searchFilter, bool unreadOnly, bool flaggedOnly, bool hasAttachments)
|
||||
{
|
||||
QVector<MailItem> items;
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QString sql = "SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid, bodyHtml, toAddr, ccAddr, bccAddr, threadId, inReplyTo, \"references\", isPinned FROM MailCopy WHERE 1=1";
|
||||
|
||||
if (!searchFilter.isEmpty()) {
|
||||
sql += " AND (subject LIKE :search OR sender LIKE :search OR recipient LIKE :search OR bodyHtml LIKE :search)";
|
||||
}
|
||||
if (unreadOnly) {
|
||||
sql += " AND read = 0";
|
||||
}
|
||||
if (flaggedOnly) {
|
||||
sql += " AND flagged = 1";
|
||||
}
|
||||
if (hasAttachments) {
|
||||
sql += " AND hasAttachment = 1";
|
||||
}
|
||||
|
||||
sql += " ORDER BY date DESC LIMIT :limit OFFSET :offset";
|
||||
|
||||
QSqlQuery query(db);
|
||||
query.prepare(sql);
|
||||
|
||||
if (!searchFilter.isEmpty()) {
|
||||
query.bindValue(":search", "%" + searchFilter + "%");
|
||||
}
|
||||
query.bindValue(":limit", limit);
|
||||
query.bindValue(":offset", offset);
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Failed to fetch paginated mails:" << query.lastError().text();
|
||||
return items;
|
||||
}
|
||||
|
||||
while (query.next()) {
|
||||
MailItem item;
|
||||
item.setId(query.value(0).toInt());
|
||||
item.setFolderId(query.value(1).toInt());
|
||||
item.setMessageId(query.value(2).toString());
|
||||
item.setSubject(query.value(3).toString());
|
||||
item.setSender(query.value(4).toString());
|
||||
item.setRecipient(query.value(5).toString());
|
||||
item.setDate(query.value(6).toDateTime());
|
||||
item.setRead(query.value(7).toBool());
|
||||
item.setFlagged(query.value(8).toBool());
|
||||
item.setSize(query.value(10).toLongLong());
|
||||
item.setFileId(query.value(11).toString());
|
||||
item.setUid(query.value(12).toLongLong());
|
||||
item.setBodyHtml(query.value(13).toString());
|
||||
item.setTo(query.value(14).toString());
|
||||
item.setCc(query.value(15).toString());
|
||||
item.setBcc(query.value(16).toString());
|
||||
item.setThreadId(query.value(17).toString());
|
||||
item.setInReplyTo(query.value(18).toString());
|
||||
QString refs = query.value(19).toString();
|
||||
if (!refs.isEmpty()) item.setReferences(refs.split(" ", Qt::SkipEmptyParts));
|
||||
item.setPinned(query.value(20).toBool());
|
||||
QVector<QString> names;
|
||||
for (const auto &attachment : attachmentsForMail(item.id())) names.append(attachment.fileName);
|
||||
item.setAttachments(names);
|
||||
items.append(item);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
QVector<MailItem> MailItemDao::findByFolderIdPaginated(int folderId, int offset, int limit, const QString& searchFilter, bool unreadOnly, bool flaggedOnly, bool hasAttachments)
|
||||
{
|
||||
QVector<MailItem> items;
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QString sql = "SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid, bodyHtml, toAddr, ccAddr, bccAddr, threadId, inReplyTo, \"references\", isPinned FROM MailCopy WHERE folderId = :folderId";
|
||||
|
||||
QSqlQuery query(db);
|
||||
|
||||
if (!searchFilter.isEmpty()) {
|
||||
sql += " AND (subject LIKE :search OR sender LIKE :search OR recipient LIKE :search OR bodyHtml LIKE :search)";
|
||||
query.bindValue(":search", "%" + searchFilter + "%");
|
||||
}
|
||||
if (unreadOnly) {
|
||||
sql += " AND read = 0";
|
||||
}
|
||||
if (flaggedOnly) {
|
||||
sql += " AND flagged = 1";
|
||||
}
|
||||
if (hasAttachments) {
|
||||
sql += " AND hasAttachment = 1";
|
||||
}
|
||||
|
||||
sql += " ORDER BY date DESC LIMIT :limit OFFSET :offset";
|
||||
|
||||
query.prepare(sql);
|
||||
query.bindValue(":folderId", folderId);
|
||||
query.bindValue(":limit", limit);
|
||||
query.bindValue(":offset", offset);
|
||||
|
||||
if (!searchFilter.isEmpty()) {
|
||||
query.bindValue(":search", "%" + searchFilter + "%");
|
||||
}
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Failed to fetch paginated mails by folder:" << query.lastError().text();
|
||||
return items;
|
||||
}
|
||||
|
||||
while (query.next()) {
|
||||
MailItem item;
|
||||
item.setId(query.value(0).toInt());
|
||||
item.setFolderId(query.value(1).toInt());
|
||||
item.setMessageId(query.value(2).toString());
|
||||
item.setSubject(query.value(3).toString());
|
||||
item.setSender(query.value(4).toString());
|
||||
item.setRecipient(query.value(5).toString());
|
||||
item.setDate(query.value(6).toDateTime());
|
||||
item.setRead(query.value(7).toBool());
|
||||
item.setFlagged(query.value(8).toBool());
|
||||
item.setSize(query.value(10).toLongLong());
|
||||
item.setFileId(query.value(11).toString());
|
||||
item.setUid(query.value(12).toLongLong());
|
||||
item.setBodyHtml(query.value(13).toString());
|
||||
item.setTo(query.value(14).toString());
|
||||
item.setCc(query.value(15).toString());
|
||||
item.setBcc(query.value(16).toString());
|
||||
item.setThreadId(query.value(17).toString());
|
||||
item.setInReplyTo(query.value(18).toString());
|
||||
QString refs = query.value(19).toString();
|
||||
if (!refs.isEmpty()) item.setReferences(refs.split(" ", Qt::SkipEmptyParts));
|
||||
item.setPinned(query.value(20).toBool());
|
||||
QVector<QString> names;
|
||||
for (const auto &attachment : attachmentsForMail(item.id())) names.append(attachment.fileName);
|
||||
item.setAttachments(names);
|
||||
items.append(item);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
@@ -33,4 +33,8 @@ public:
|
||||
static bool replaceAttachments(qint64 mailItemId, const QVector<StoredAttachmentRecord>& attachments);
|
||||
static QVector<StoredAttachmentRecord> attachmentsForMail(qint64 mailItemId);
|
||||
static int countUnreadByFolderId(int folderId);
|
||||
static int countAll(const QString& searchFilter, bool unreadOnly, bool flaggedOnly, bool hasAttachments);
|
||||
static int countByFolderId(int folderId, const QString& searchFilter, bool unreadOnly, bool flaggedOnly, bool hasAttachments);
|
||||
static QVector<MailItem> findAllPaginated(int offset, int limit, const QString& searchFilter, bool unreadOnly, bool flaggedOnly, bool hasAttachments);
|
||||
static QVector<MailItem> findByFolderIdPaginated(int folderId, int offset, int limit, const QString& searchFilter, bool unreadOnly, bool flaggedOnly, bool hasAttachments);
|
||||
};
|
||||
|
||||
+47
-1
@@ -14,6 +14,7 @@
|
||||
#include <QResizeEvent>
|
||||
#include <QListView>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QScrollBar>
|
||||
#include "ui/delegates/CompactMailDelegate.h"
|
||||
|
||||
MailListView::MailListView(QWidget *parent) : QWidget(parent), m_sourceModel(nullptr)
|
||||
@@ -217,8 +218,13 @@ void MailListView::setModel(EmailListModel *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::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();
|
||||
@@ -228,6 +234,7 @@ 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();
|
||||
@@ -346,4 +353,43 @@ 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();
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,10 @@ private slots:
|
||||
void onCompactForwardRequested(int mailId);
|
||||
void onCompactMarkUnreadRequested(int mailId);
|
||||
void onDisplayModeChanged(int index);
|
||||
void onTotalCountChanged(int count);
|
||||
void onTreeViewScrolled(int value);
|
||||
void onListViewScrolled(int value);
|
||||
void onRowsInserted(const QModelIndex &parent, int first, int last);
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <QDateTime>
|
||||
|
||||
EmailListModel::EmailListModel(QObject *parent)
|
||||
: QAbstractTableModel(parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
refresh();
|
||||
}
|
||||
@@ -15,12 +15,6 @@ int EmailListModel::rowCount(const QModelIndex &parent) const
|
||||
return m_emails.size();
|
||||
}
|
||||
|
||||
int EmailListModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return ColCount;
|
||||
}
|
||||
|
||||
QVariant EmailListModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() >= m_emails.size())
|
||||
@@ -30,16 +24,7 @@ QVariant EmailListModel::data(const QModelIndex &index, int role) const
|
||||
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
case Qt::EditRole:
|
||||
switch (index.column()) {
|
||||
case ColSubject:
|
||||
return item.subject();
|
||||
case ColSender:
|
||||
return item.sender();
|
||||
case ColDate:
|
||||
return item.date();
|
||||
}
|
||||
break;
|
||||
return item.subject();
|
||||
case IdRole:
|
||||
return item.id();
|
||||
case SubjectRole:
|
||||
@@ -68,24 +53,14 @@ QVariant EmailListModel::data(const QModelIndex &index, int role) const
|
||||
return QString();
|
||||
return QString(sender.at(0)).toUpper();
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant EmailListModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
|
||||
return QVariant();
|
||||
|
||||
switch (section) {
|
||||
case ColSubject:
|
||||
return tr("Asunto");
|
||||
case ColSender:
|
||||
return tr("De");
|
||||
case ColDate:
|
||||
return tr("Fecha");
|
||||
case ThreadIdRole:
|
||||
return item.threadId();
|
||||
case InReplyToRole:
|
||||
return item.inReplyTo();
|
||||
case ReferencesRole:
|
||||
return item.references().join(" ");
|
||||
case IsPinnedRole:
|
||||
return item.isPinned();
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
@@ -106,9 +81,48 @@ QHash<int, QByteArray> EmailListModel::roleNames() const
|
||||
roles[SizeRole] = "size";
|
||||
roles[MessageIdRole] = "messageId";
|
||||
roles[SenderInitialRole] = "senderInitial";
|
||||
roles[ThreadIdRole] = "threadId";
|
||||
roles[InReplyToRole] = "inReplyTo";
|
||||
roles[ReferencesRole] = "references";
|
||||
roles[IsPinnedRole] = "isPinned";
|
||||
return roles;
|
||||
}
|
||||
|
||||
bool EmailListModel::canFetchMore(const QModelIndex &parent) const
|
||||
{
|
||||
if (parent.isValid())
|
||||
return false;
|
||||
return m_loadedCount < m_totalCount;
|
||||
}
|
||||
|
||||
void EmailListModel::fetchMore(const QModelIndex &parent)
|
||||
{
|
||||
if (parent.isValid())
|
||||
return;
|
||||
|
||||
int remaining = m_totalCount - m_loadedCount;
|
||||
if (remaining <= 0)
|
||||
return;
|
||||
|
||||
int fetchCount = qMin(m_batchSize, remaining);
|
||||
int offset = m_loadedCount;
|
||||
|
||||
QVector<MailItem> newEmails;
|
||||
if (m_folderId == -1) {
|
||||
newEmails = MailItemDao::findAllPaginated(offset, fetchCount, m_searchFilter, m_unreadOnly, m_flaggedOnly, m_hasAttachments);
|
||||
} else {
|
||||
newEmails = MailItemDao::findByFolderIdPaginated(m_folderId, offset, fetchCount, m_searchFilter, m_unreadOnly, m_flaggedOnly, m_hasAttachments);
|
||||
}
|
||||
|
||||
if (newEmails.isEmpty())
|
||||
return;
|
||||
|
||||
beginInsertRows(QModelIndex(), m_loadedCount, m_loadedCount + newEmails.size() - 1);
|
||||
m_emails.append(newEmails);
|
||||
m_loadedCount += newEmails.size();
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void EmailListModel::setFolderId(int folderId)
|
||||
{
|
||||
if (m_folderId == folderId)
|
||||
@@ -120,45 +134,31 @@ void EmailListModel::setFolderId(int folderId)
|
||||
void EmailListModel::refresh()
|
||||
{
|
||||
beginResetModel();
|
||||
QVector<MailItem> allEmails;
|
||||
if (m_folderId == -1) {
|
||||
allEmails = MailItemDao::findAll();
|
||||
} else {
|
||||
allEmails = MailItemDao::findByFolderId(m_folderId);
|
||||
}
|
||||
|
||||
// Apply filters
|
||||
m_emails.clear();
|
||||
for (const auto &item : allEmails) {
|
||||
// Search filter
|
||||
if (!m_searchFilter.isEmpty()) {
|
||||
if (!item.subject().contains(m_searchFilter, Qt::CaseInsensitive) &&
|
||||
!item.sender().contains(m_searchFilter, Qt::CaseInsensitive) &&
|
||||
!item.recipient().contains(m_searchFilter, Qt::CaseInsensitive) &&
|
||||
!item.bodyHtml().contains(m_searchFilter, Qt::CaseInsensitive)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Unread only
|
||||
if (m_unreadOnly && item.isRead()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Flagged only
|
||||
if (m_flaggedOnly && !item.isFlagged()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Has attachments
|
||||
if (m_hasAttachments && item.attachments().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_emails.append(item);
|
||||
m_loadedCount = 0;
|
||||
|
||||
// Get total count
|
||||
if (m_folderId == -1) {
|
||||
m_totalCount = MailItemDao::countAll(m_searchFilter, m_unreadOnly, m_flaggedOnly, m_hasAttachments);
|
||||
} else {
|
||||
m_totalCount = MailItemDao::countByFolderId(m_folderId, m_searchFilter, m_unreadOnly, m_flaggedOnly, m_hasAttachments);
|
||||
}
|
||||
|
||||
emit totalCountChanged(m_totalCount);
|
||||
|
||||
// Load first batch
|
||||
int fetchCount = qMin(m_batchSize, m_totalCount);
|
||||
QVector<MailItem> firstBatch;
|
||||
if (m_folderId == -1) {
|
||||
firstBatch = MailItemDao::findAllPaginated(0, fetchCount, m_searchFilter, m_unreadOnly, m_flaggedOnly, m_hasAttachments);
|
||||
} else {
|
||||
firstBatch = MailItemDao::findByFolderIdPaginated(m_folderId, 0, fetchCount, m_searchFilter, m_unreadOnly, m_flaggedOnly, m_hasAttachments);
|
||||
}
|
||||
m_emails = firstBatch;
|
||||
m_loadedCount = firstBatch.size();
|
||||
endResetModel();
|
||||
qDebug() << "EmailListModel refreshed with" << m_emails.size() << "emails for folderId" << m_folderId;
|
||||
|
||||
qDebug() << "EmailListModel refreshed: total=" << m_totalCount << "loaded=" << m_loadedCount << "for folderId" << m_folderId;
|
||||
}
|
||||
|
||||
void EmailListModel::setSearchFilter(const QString &filter)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <QByteArray>
|
||||
#include "../db/dao/mailitemdao.h"
|
||||
|
||||
class EmailListModel : public QAbstractTableModel
|
||||
class EmailListModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
@@ -25,22 +25,19 @@ public:
|
||||
FileIdRole,
|
||||
SizeRole,
|
||||
MessageIdRole,
|
||||
SenderInitialRole // computed from sender
|
||||
};
|
||||
|
||||
// Column indices (match role order for simplicity)
|
||||
enum Columns {
|
||||
ColSubject = 0,
|
||||
ColSender = 1,
|
||||
ColDate = 2,
|
||||
ColCount = 3 // number of visible columns
|
||||
SenderInitialRole, // computed from sender
|
||||
ThreadIdRole,
|
||||
InReplyToRole,
|
||||
ReferencesRole,
|
||||
IsPinnedRole
|
||||
};
|
||||
|
||||
// QAbstractListModel interface
|
||||
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;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
bool canFetchMore(const QModelIndex &parent) const override;
|
||||
void fetchMore(const QModelIndex &parent) override;
|
||||
|
||||
// Set the folderId to filter emails; -1 means all folders
|
||||
void setFolderId(int folderId);
|
||||
@@ -52,10 +49,18 @@ public:
|
||||
void setShowUnreadOnly(bool show);
|
||||
void setShowFlaggedOnly(bool show);
|
||||
void setShowHasAttachments(bool show);
|
||||
// Set batch size for pagination
|
||||
void setBatchSize(int size) { m_batchSize = size; }
|
||||
|
||||
// Get all emails currently in the model (after filtering)
|
||||
const QVector<MailItem>& emails() const { return m_emails; }
|
||||
|
||||
// Total count of emails matching filters (including unloaded)
|
||||
int totalCount() const { return m_totalCount; }
|
||||
|
||||
signals:
|
||||
void totalCountChanged(int count);
|
||||
|
||||
private:
|
||||
QVector<MailItem> m_emails;
|
||||
int m_folderId{-1}; // -1 means all folders
|
||||
@@ -63,5 +68,8 @@ private:
|
||||
bool m_unreadOnly{false};
|
||||
bool m_flaggedOnly{false};
|
||||
bool m_hasAttachments{false};
|
||||
int m_batchSize{50}; // emails per fetch
|
||||
int m_loadedCount{0}; // how many emails currently loaded
|
||||
int m_totalCount{0}; // total matching emails in DB
|
||||
};
|
||||
#endif // EMAILLISTMODEL_H
|
||||
Reference in New Issue
Block a user