feat: Pinned emails support (flag/follow-up)

- EmailListModel: new m_pinnedOnly filter + setShowPinnedOnly()
- EmailListModel/EmailListModel.cpp: passes pinnedOnly to paginated queries
- MailItemDao: countAll, countByFolderId, findAllPaginated, findByFolderIdPaginated accept pinnedOnly
- MailListView: new 'Solo fijados' checkbox in filter bar
- CompactMailDelegate: orange pushpin indicator (top-right) for pinned emails
- Works in both tree view and compact card view
- Pinned indicator uses QPainterPath for pushpin shape (orange)
This commit is contained in:
2026-08-26 00:04:20 +02:00
parent 68d237c873
commit 3846bd4e9d
16 changed files with 11939 additions and 11893 deletions
@@ -555,4 +555,6 @@ CMakeFiles/wino-mail-qt.dir/src/ui/delegates/CompactMailDelegate.cpp.o: \
/usr/include/x86_64-linux-gnu/qt6/QtCore/QDebug \
/usr/include/x86_64-linux-gnu/qt6/QtCore/qdebug.h \
/usr/include/x86_64-linux-gnu/qt6/QtGui/QCursor \
/usr/include/x86_64-linux-gnu/qt6/QtGui/qcursor.h
/usr/include/x86_64-linux-gnu/qt6/QtGui/qcursor.h \
/usr/include/x86_64-linux-gnu/qt6/QtGui/QPainterPath \
/usr/include/x86_64-linux-gnu/qt6/QtGui/qpainterpath.h
File diff suppressed because it is too large Load Diff
Binary file not shown.
+16 -4
View File
@@ -438,7 +438,7 @@ int MailItemDao::countUnreadByFolderId(int folderId)
return 0;
}
int MailItemDao::countAll(const QString& searchFilter, bool unreadOnly, bool flaggedOnly, bool hasAttachments)
int MailItemDao::countAll(const QString& searchFilter, bool unreadOnly, bool flaggedOnly, bool hasAttachments, bool pinnedOnly)
{
QSqlDatabase db = DatabaseManager::instance().database();
QString sql = "SELECT COUNT(*) FROM MailCopy WHERE 1=1";
@@ -457,6 +457,9 @@ int MailItemDao::countAll(const QString& searchFilter, bool unreadOnly, bool fla
if (hasAttachments) {
sql += " AND hasAttachment = 1";
}
if (pinnedOnly) {
sql += " AND isPinned = 1";
}
query.prepare(sql);
if (!query.exec()) {
@@ -469,7 +472,7 @@ int MailItemDao::countAll(const QString& searchFilter, bool unreadOnly, bool fla
return 0;
}
int MailItemDao::countByFolderId(int folderId, const QString& searchFilter, bool unreadOnly, bool flaggedOnly, bool hasAttachments)
int MailItemDao::countByFolderId(int folderId, const QString& searchFilter, bool unreadOnly, bool flaggedOnly, bool hasAttachments, bool pinnedOnly)
{
QSqlDatabase db = DatabaseManager::instance().database();
QString sql = "SELECT COUNT(*) FROM MailCopy WHERE folderId = :folderId";
@@ -489,6 +492,9 @@ int MailItemDao::countByFolderId(int folderId, const QString& searchFilter, bool
if (hasAttachments) {
sql += " AND hasAttachment = 1";
}
if (pinnedOnly) {
sql += " AND isPinned = 1";
}
query.prepare(sql);
if (!query.exec()) {
@@ -501,7 +507,7 @@ int MailItemDao::countByFolderId(int folderId, const QString& searchFilter, bool
return 0;
}
QVector<MailItem> MailItemDao::findAllPaginated(int offset, int limit, const QString& searchFilter, bool unreadOnly, bool flaggedOnly, bool hasAttachments)
QVector<MailItem> MailItemDao::findAllPaginated(int offset, int limit, const QString& searchFilter, bool unreadOnly, bool flaggedOnly, bool hasAttachments, bool pinnedOnly)
{
QVector<MailItem> items;
QSqlDatabase db = DatabaseManager::instance().database();
@@ -519,6 +525,9 @@ QVector<MailItem> MailItemDao::findAllPaginated(int offset, int limit, const QSt
if (hasAttachments) {
sql += " AND hasAttachment = 1";
}
if (pinnedOnly) {
sql += " AND isPinned = 1";
}
sql += " ORDER BY date DESC LIMIT :limit OFFSET :offset";
@@ -567,7 +576,7 @@ QVector<MailItem> MailItemDao::findAllPaginated(int offset, int limit, const QSt
return items;
}
QVector<MailItem> MailItemDao::findByFolderIdPaginated(int folderId, int offset, int limit, const QString& searchFilter, bool unreadOnly, bool flaggedOnly, bool hasAttachments)
QVector<MailItem> MailItemDao::findByFolderIdPaginated(int folderId, int offset, int limit, const QString& searchFilter, bool unreadOnly, bool flaggedOnly, bool hasAttachments, bool pinnedOnly)
{
QVector<MailItem> items;
QSqlDatabase db = DatabaseManager::instance().database();
@@ -588,6 +597,9 @@ QVector<MailItem> MailItemDao::findByFolderIdPaginated(int folderId, int offset,
if (hasAttachments) {
sql += " AND hasAttachment = 1";
}
if (pinnedOnly) {
sql += " AND isPinned = 1";
}
sql += " ORDER BY date DESC LIMIT :limit OFFSET :offset";
+4 -4
View File
@@ -33,8 +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);
static int countAll(const QString& searchFilter, bool unreadOnly, bool flaggedOnly, bool hasAttachments, bool pinnedOnly);
static int countByFolderId(int folderId, const QString& searchFilter, bool unreadOnly, bool flaggedOnly, bool hasAttachments, bool pinnedOnly);
static QVector<MailItem> findAllPaginated(int offset, int limit, const QString& searchFilter, bool unreadOnly, bool flaggedOnly, bool hasAttachments, bool pinnedOnly);
static QVector<MailItem> findByFolderIdPaginated(int folderId, int offset, int limit, const QString& searchFilter, bool unreadOnly, bool flaggedOnly, bool hasAttachments, bool pinnedOnly);
};
+16
View File
@@ -2,6 +2,7 @@
#include <QDebug>
#include <QDateTime>
#include <QCursor>
#include <QPainterPath>
CompactMailDelegate::CompactMailDelegate(QObject *parent)
: QStyledItemDelegate(parent)
@@ -220,6 +221,21 @@ void CompactMailDelegate::drawCard(QPainter *painter, const QStyleOptionViewItem
painter->setPen(dateColor);
painter->drawText(QRect(x + senderMaxWidth + 8, line1Y, dateWidth, dateFM.height()), Qt::AlignRight | Qt::AlignVCenter, dateStr);
// Pinned indicator (top-right, next to unread dot)
bool isPinned = index.data(EmailListModel::IsPinnedRole).toBool();
if (isPinned) {
int pinSize = 10;
QRect pinRect(cardRect.right() - 12 - pinSize, cardRect.top() + 12, pinSize, pinSize);
painter->setBrush(QColor(0xff, 0x95, 0x00)); // orange
painter->setPen(Qt::NoPen);
// Draw pushpin shape
QPainterPath pinPath;
pinPath.addEllipse(pinRect.center().x() - 2, pinRect.top() + 2, 4, 4); // head
pinPath.moveTo(pinRect.center().x(), pinRect.top() + 6);
pinPath.lineTo(pinRect.center().x(), pinRect.bottom() - 2); // shaft
painter->drawPath(pinPath);
}
// Line 2: Subject + Attachment icon
int line2Y = line1Y + senderFM.height() + 2;
QString subjectPrefix = "";
+6
View File
@@ -69,6 +69,10 @@ void MailListView::setupUI()
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);
filterLayout->addStretch();
// Display mode combo (Compact/Medium/Spacious)
@@ -209,6 +213,7 @@ void MailListView::setupUI()
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)
@@ -287,6 +292,7 @@ void MailListView::onFilterChanged()
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)
+1
View File
@@ -80,6 +80,7 @@ private:
QCheckBox *m_unreadOnlyCheck;
QCheckBox *m_flaggedOnlyCheck;
QCheckBox *m_hasAttachmentsCheck;
QCheckBox *m_pinnedOnlyCheck;
QComboBox *m_groupByCombo;
QComboBox *m_displayModeCombo;
CompactMailDelegate *m_compactDelegate = nullptr;
+14 -6
View File
@@ -109,9 +109,9 @@ void EmailListModel::fetchMore(const QModelIndex &parent)
QVector<MailItem> newEmails;
if (m_folderId == -1) {
newEmails = MailItemDao::findAllPaginated(offset, fetchCount, m_searchFilter, m_unreadOnly, m_flaggedOnly, m_hasAttachments);
newEmails = MailItemDao::findAllPaginated(offset, fetchCount, m_searchFilter, m_unreadOnly, m_flaggedOnly, m_hasAttachments, m_pinnedOnly);
} else {
newEmails = MailItemDao::findByFolderIdPaginated(m_folderId, offset, fetchCount, m_searchFilter, m_unreadOnly, m_flaggedOnly, m_hasAttachments);
newEmails = MailItemDao::findByFolderIdPaginated(m_folderId, offset, fetchCount, m_searchFilter, m_unreadOnly, m_flaggedOnly, m_hasAttachments, m_pinnedOnly);
}
if (newEmails.isEmpty())
@@ -139,9 +139,9 @@ void EmailListModel::refresh()
// Get total count
if (m_folderId == -1) {
m_totalCount = MailItemDao::countAll(m_searchFilter, m_unreadOnly, m_flaggedOnly, m_hasAttachments);
m_totalCount = MailItemDao::countAll(m_searchFilter, m_unreadOnly, m_flaggedOnly, m_hasAttachments, m_pinnedOnly);
} else {
m_totalCount = MailItemDao::countByFolderId(m_folderId, m_searchFilter, m_unreadOnly, m_flaggedOnly, m_hasAttachments);
m_totalCount = MailItemDao::countByFolderId(m_folderId, m_searchFilter, m_unreadOnly, m_flaggedOnly, m_hasAttachments, m_pinnedOnly);
}
emit totalCountChanged(m_totalCount);
@@ -150,9 +150,9 @@ void EmailListModel::refresh()
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);
firstBatch = MailItemDao::findAllPaginated(0, fetchCount, m_searchFilter, m_unreadOnly, m_flaggedOnly, m_hasAttachments, m_pinnedOnly);
} else {
firstBatch = MailItemDao::findByFolderIdPaginated(m_folderId, 0, fetchCount, m_searchFilter, m_unreadOnly, m_flaggedOnly, m_hasAttachments);
firstBatch = MailItemDao::findByFolderIdPaginated(m_folderId, 0, fetchCount, m_searchFilter, m_unreadOnly, m_flaggedOnly, m_hasAttachments, m_pinnedOnly);
}
m_emails = firstBatch;
m_loadedCount = firstBatch.size();
@@ -192,3 +192,11 @@ void EmailListModel::setShowHasAttachments(bool show)
m_hasAttachments = show;
refresh();
}
void EmailListModel::setShowPinnedOnly(bool show)
{
if (m_pinnedOnly == show)
return;
m_pinnedOnly = show;
refresh();
}
+3 -2
View File
@@ -49,8 +49,8 @@ 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; }
// Set filter for pinned emails
void setShowPinnedOnly(bool show);
// Get all emails currently in the model (after filtering)
const QVector<MailItem>& emails() const { return m_emails; }
@@ -68,6 +68,7 @@ private:
bool m_unreadOnly{false};
bool m_flaggedOnly{false};
bool m_hasAttachments{false};
bool m_pinnedOnly{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