summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mainwindow.cpp80
-rw-r--r--src/mainwindow.h22
2 files changed, 91 insertions, 11 deletions
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index b62d8d7..a7f9b40 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -337,6 +337,26 @@ void MainWindow::buildUi()
m_statusLabel->setObjectName(QStringLiteral("statusMessage"));
statusBar()->addWidget(m_statusLabel);
+ // Transient messages describe an EVENT and go stale: "Sync complete" reads
+ // as the present tense until something else overwrites it. State messages,
+ // the selection count above all, describe what is true right now and must
+ // not expire while it stays true, so only showTransientStatus() arms this.
+ //
+ // ponytail: one timer beside the label, not QStatusBar::showMessage().
+ // That would mean moving off addWidget() and reworking the permanent
+ // widgets beside it, for the same behaviour.
+ m_statusTimer = new QTimer(this);
+ m_statusTimer->setObjectName(QStringLiteral("statusTimer"));
+ m_statusTimer->setSingleShot(true);
+ m_statusTimer->setInterval(kStatusMessageMs);
+ connect(m_statusTimer, &QTimer::timeout, this, [this]() {
+ // Only take back a message this timer armed. Anything written since is
+ // newer and more relevant than the default.
+ if (m_statusLabel->text() == m_transientMessage)
+ m_statusLabel->setText(m_defaultStatus);
+ m_transientMessage.clear();
+ });
+
// Beside the sync status rather than as a widget competing with it: the two
// say related things and reading them apart would be worse than reading
// them together.
@@ -428,7 +448,7 @@ void MainWindow::buildUi()
}
connect(m_syncButton, &QPushButton::clicked, this, [this]() {
if (!m_sync->start()) {
- m_statusLabel->setText(tr("Sync already running"));
+ showTransientStatus(tr("Sync already running"));
return;
}
// Fresh run, fresh output: leaving the previous run's lines in place
@@ -594,8 +614,29 @@ void MainWindow::registerActions()
tagSelected({}, { QStringLiteral("inbox") }, tr("Archive"));
});
addAction(QStringLiteral("delete"), tr("&Delete"),
- tr("Add the deleted tag"), [this]() {
- tagSelected({ QStringLiteral("deleted") }, {}, tr("Delete"));
+ tr("Add or remove the deleted tag"), [this]() {
+ // A toggle, like toggle_unread: pressing Delete twice is the natural
+ // way to say "no, put it back", and adding a tag that is already there
+ // is a no-op the user cannot see.
+ //
+ // One direction for the WHOLE selection. Toggling each thread
+ // independently would leave one keystroke with the selection in two
+ // states, which is worse than either outcome, so undelete only when
+ // every selected thread is already deleted.
+ const QModelIndexList rows =
+ m_threadView->selectionModel()->selectedRows();
+ bool allDeleted = !rows.isEmpty();
+ for (const QModelIndex &index : rows) {
+ if (!m_model->threadAt(index.row()).isDeleted()) {
+ allDeleted = false;
+ break;
+ }
+ }
+
+ if (allDeleted)
+ tagSelected({}, { QStringLiteral("deleted") }, tr("Undelete"));
+ else
+ tagSelected({ QStringLiteral("deleted") }, {}, tr("Delete"));
});
addAction(QStringLiteral("spam"), tr("Mark &spam"),
tr("Add spam and remove inbox"), [this]() {
@@ -675,7 +716,7 @@ void MainWindow::registerActions()
if (m_undoStack.canUndo())
m_undoStack.undo();
else
- m_statusLabel->setText(tr("Nothing to undo"));
+ showTransientStatus(tr("Nothing to undo"));
});
addAction(QStringLiteral("sync"), tr("&Sync"),
tr("Run the configured sync command"), [this]() {
@@ -1061,7 +1102,11 @@ void MainWindow::onQueryFinished(int total, quint64 generation)
{
if (generation != m_generation)
return;
- m_statusLabel->setText(tr("%n thread(s)", "", total));
+ // The query's own result is what the bar says when nothing more pressing
+ // is happening, so a transient message falls back to it rather than to
+ // nothing.
+ m_defaultStatus = tr("%n thread(s)", "", total);
+ m_statusLabel->setText(m_defaultStatus);
}
void MainWindow::showThreadContextMenu(const QPoint &pos)
@@ -1112,6 +1157,12 @@ void MainWindow::onSelectionChanged()
m_selectionMessage = tr("%n thread(s) selected", "", selected);
m_statusLabel->setText(m_selectionMessage);
+ // State, not an event: it must persist while the selection does. Cancel any
+ // transient message still counting down, or that timer fires and replaces a
+ // count that is still true.
+ m_statusTimer->stop();
+ m_transientMessage.clear();
+
// Ctrl+click and selectAll() reach a multi-row selection without moving
// current, so onThreadSelected never runs and its guard never fires. The
// pane and the pending timer have to be dealt with here as well.
@@ -1251,7 +1302,7 @@ void MainWindow::onSyncFinished(bool success, int exitCode)
m_pendingEdits = 0;
updatePendingIndicator();
- m_statusLabel->setText(tr("Sync complete"));
+ showTransientStatus(tr("Sync complete"));
if (m_syncingForExit) {
// The work is safely across, so finish the quit the user asked for.
@@ -1274,8 +1325,8 @@ void MainWindow::onSyncFinished(bool success, int exitCode)
// Not a failure: another run holds the lock and is doing the work.
// The user's cron fires every ten minutes, so a click landing inside
// one is routine and must not raise an error or the log pane.
- m_statusLabel->setText(tr("A sync is already running (started "
- "elsewhere); this one was skipped"));
+ showTransientStatus(tr("A sync is already running (started "
+ "elsewhere); this one was skipped"));
if (m_syncingForExit) {
// The other run is syncing, but this application cannot see when
@@ -1374,12 +1425,19 @@ void MainWindow::onExternalSyncStateChanged(SyncMonitor::State state)
// be read, so nothing was observed, and "sync finished" would be a claim
// this cannot support.
if (state == SyncMonitor::State::Idle) {
- m_statusLabel->setText(
+ showTransientStatus(
tr("Background sync completed. Press Enter in the query bar to "
"refresh."));
}
}
+void MainWindow::showTransientStatus(const QString &text)
+{
+ m_transientMessage = text;
+ m_statusLabel->setText(text);
+ m_statusTimer->start();
+}
+
void MainWindow::setSyncBusy(bool busy)
{
m_localSyncBusy = busy;
@@ -1501,7 +1559,7 @@ void MainWindow::editTagsOnSelection()
const QModelIndexList rows =
m_threadView->selectionModel()->selectedRows();
if (rows.isEmpty()) {
- m_statusLabel->setText(tr("Select a thread first"));
+ showTransientStatus(tr("Select a thread first"));
return;
}
@@ -1551,7 +1609,7 @@ void MainWindow::tagSelected(const QStringList &add, const QStringList &remove,
m_undoStack.push(new ThreadTagCommand(this, threadIds, add, remove,
description));
- m_statusLabel->setText(
+ showTransientStatus(
tr("%1: %n thread(s)", "", threadIds.size()).arg(description));
}
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 61722aa..2edeb79 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -84,6 +84,11 @@ public:
/// `assets/mailsync.sh` is the reference implementation of this contract.
static constexpr int kSyncSkippedExitCode = 75;
+ /// How long a transient status message stays before the bar falls back to
+ /// the thread count. Long enough to read a sentence, short enough that a
+ /// stale "Sync complete" does not sit there describing the present.
+ static constexpr int kStatusMessageMs = 6000;
+
/// Path of the machine-written UI state file. Deliberately not
/// Config::defaultPath(): the config is hand-edited and must never gain a
/// base64 geometry blob, nor be rewritten on exit (QSettings does not
@@ -115,6 +120,14 @@ private slots:
void onWorkerError(const QString &message);
void onSyncFinished(bool success, int exitCode);
+ /// Shows a message that describes an event and takes it back after a few
+ /// seconds, restoring the last query's thread count.
+ ///
+ /// Use this for events ("Sync complete"), never for state: the selection
+ /// count must persist while the selection does. A private slot so tests can
+ /// drive it through the meta-object.
+ void showTransientStatus(const QString &text);
+
/// Reacts to a sync started outside this window, by cron or by hand.
///
/// A private slot rather than a plain method so tests can drive it through
@@ -254,6 +267,15 @@ private:
QPushButton *m_syncButton = nullptr;
QLabel *m_statusLabel = nullptr;
+ /// Expires a transient status message. See showTransientStatus().
+ QTimer *m_statusTimer = nullptr;
+
+ /// The message m_statusTimer armed for, so it takes back only its own.
+ QString m_transientMessage;
+
+ /// What the status bar falls back to: the last query's thread count.
+ QString m_defaultStatus;
+
/// Says how many tag changes have not been seen to reach the mail store.
/// Hidden entirely at zero rather than reading "0 unsynced", which is noise.
QLabel *m_pendingLabel = nullptr;