diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-26 19:31:45 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-26 19:31:45 +0200 |
| commit | 59a196ea849c64c51311f56bf62130e74d90b784 (patch) | |
| tree | f322f955b0b5981dfafc85db72f224795941a4ad /src/mainwindow.cpp | |
| parent | 1c034f6358f17c5c1d0eeaa04c42c33fac125d93 (diff) | |
| download | qtmaildir-59a196ea849c64c51311f56bf62130e74d90b784.tar.gz qtmaildir-59a196ea849c64c51311f56bf62130e74d90b784.zip | |
feat: open the unsynced-changes count to see what it counts
Item 119, and item 146 which is the same request recorded again. The status
bar's count answers "is my work safe to quit on" and could not say what the
work was.
The label opens a read-only list on a click. A QLabel has no clicked signal,
so the press is taken by MainWindow's existing event filter rather than by
replacing the label with a flat QToolButton, which would have brought the
style's button metrics into a status bar the label already sits correctly
in. The pointing-hand cursor is the affordance, since a status-bar label has
room for nothing else.
The layout is the user's own: a message appears once with its actions
beneath it. PendingChangesDialog::rowsFor() does the grouping over a run of
rows sharing an id, which the snapshot has already ordered, so the actions
under one message keep the order they were made in.
Read-only, deliberately. Retrying or discarding a change from here would be
a new mutation path with its own undo question, and the count exists to be
understood rather than edited.
Three rules the tests pin, each of which is a way the list could disagree
with the count it was opened from:
- Grouping must not collapse: two actions on one message are two rows.
- A thread row stays thread-scoped and reports how many messages it covered.
- An id the index no longer holds still opens a run of its own, showing that
its subject is unknown rather than folding its actions under the message
above it. This is why the row carries startsMessage rather than inferring
it from a non-empty subject.
The queued call carrying QStringList, QList<bool> and QList<int> is covered
by a test that drives it across a real thread, since a container whose
metatype does not resolve is dropped at runtime and the slot runs with a
default. Both survive on Qt 6.11; the test is what says so, and what would
fail if that changed.
Italian ships with it: five new strings, lupdate clean, lrelease 522
finished and 0 unfinished.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P88Q3MCSCSQxKDy7pmXh9F
Diffstat (limited to 'src/mainwindow.cpp')
| -rw-r--r-- | src/mainwindow.cpp | 76 |
1 files changed, 76 insertions, 0 deletions
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0c6092e..231a9a5 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -26,6 +26,7 @@ #include <QApplication> #include <QCloseEvent> #include <QKeyEvent> +#include <QMouseEvent> #include <QComboBox> #include <QDialog> #include <QDialogButtonBox> @@ -65,6 +66,7 @@ #include "searchterm.h" #include "tagchip.h" #include "tagdialog.h" +#include "pendingchangesdialog.h" #include "savequerydialog.h" #include "tagrulesdialog.h" #include "threadlistmodel.h" @@ -490,6 +492,20 @@ bool MainWindow::eventFilter(QObject *watched, QEvent *event) // cannot fail. } + // The unsynced-changes indicator opens its list on a click (item 119). A + // QLabel has no clicked signal, so the press is taken here rather than + // replacing the label with a flat QToolButton: a button would inherit the + // style's button metrics inside a status bar, and the label already sits + // correctly. + if (watched == m_pendingLabel + && event->type() == QEvent::MouseButtonRelease) { + auto *mouse = static_cast<QMouseEvent *>(event); + if (mouse->button() == Qt::LeftButton) { + showPendingChanges(); + return true; + } + } + return QMainWindow::eventFilter(watched, event); } @@ -682,6 +698,11 @@ void MainWindow::buildUi() // them together. m_pendingLabel = new QLabel(this); m_pendingLabel->setObjectName(QStringLiteral("pendingEdits")); + // Clickable, opening the list of what it counts (item 119). The cursor is + // the only affordance a status-bar label can carry, so it is what says + // this one can be opened. + m_pendingLabel->setCursor(Qt::PointingHandCursor); + m_pendingLabel->installEventFilter(this); m_pendingLabel->hide(); statusBar()->addPermanentWidget(m_pendingLabel); @@ -2618,6 +2639,8 @@ void MainWindow::wireWorker() // unrelated error would roll back a change that actually succeeded. connect(m_worker, &NotmuchWorker::tagsApplied, this, &MainWindow::onTagsApplied); + connect(m_worker, &NotmuchWorker::pendingSubjectsResolved, + this, &MainWindow::onPendingSubjectsResolved); // messagesMovedFrom rather than messagesMoved: the tags a move carries can // only be resolved once the origins are known, and that signal is the one @@ -5108,6 +5131,59 @@ QVector<PendingChange> MainWindow::pendingChangeSnapshot() const return rows; } +void MainWindow::showPendingChanges() +{ + // The snapshot is taken HERE, at the click, and is what the dialog shows + // however long it stays open. Nothing refreshes it: the count the user + // clicked is the list they get. + m_pendingChangeRequest = pendingChangeSnapshot(); + + if (m_pendingChangeRequest.isEmpty() || !m_worker) { + // Nothing to resolve. Shown anyway rather than silently ignoring the + // click, since a window saying "nothing is waiting" is an answer and a + // dead click is not. + PendingChangesDialog(m_pendingChangeRequest, this).exec(); + m_pendingChangeRequest.clear(); + return; + } + + QStringList ids; + QList<bool> areThreads; + ids.reserve(m_pendingChangeRequest.size()); + areThreads.reserve(m_pendingChangeRequest.size()); + for (const PendingChange &change : m_pendingChangeRequest) { + ids.append(change.id); + areThreads.append(change.isThread); + } + + QMetaObject::invokeMethod(m_worker, "resolvePendingSubjects", + Qt::QueuedConnection, + Q_ARG(QStringList, ids), + Q_ARG(QList<bool>, areThreads)); +} + +void MainWindow::onPendingSubjectsResolved(const QStringList &subjects, + const QList<int> &messageCounts) +{ + // Positional, so the two must line up. A mismatch means the answer is not + // this request's, which is not something to render half of. + if (m_pendingChangeRequest.isEmpty() + || subjects.size() != m_pendingChangeRequest.size() + || messageCounts.size() != m_pendingChangeRequest.size()) { + m_pendingChangeRequest.clear(); + return; + } + + QVector<PendingChange> changes = m_pendingChangeRequest; + m_pendingChangeRequest.clear(); + for (int i = 0; i < changes.size(); ++i) { + changes[i].subject = subjects.at(i); + changes[i].messageCount = messageCounts.at(i); + } + + PendingChangesDialog(changes, this).exec(); +} + void MainWindow::updatePendingIndicator() { const int pending = pendingEditCount(); |
