aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/keymap.cpp5
-rw-r--r--src/mainwindow.cpp56
-rw-r--r--src/mainwindow.h17
3 files changed, 71 insertions, 7 deletions
diff --git a/src/keymap.cpp b/src/keymap.cpp
index dcb63a8..f27f9a9 100644
--- a/src/keymap.cpp
+++ b/src/keymap.cpp
@@ -36,6 +36,7 @@ QStringList KeyMap::knownActions()
QStringLiteral("focus_query"),
QStringLiteral("complete_query"),
QStringLiteral("select_all"),
+ QStringLiteral("clear_pane"),
QStringLiteral("toggle_html"),
QStringLiteral("load_remote"),
QStringLiteral("message_details"),
@@ -76,6 +77,10 @@ QList<QPair<QString, QString>> KeyMap::defaultBindings()
// The conventional select-all key, and free here: the thread list is a
// read-only view, so nothing else in the window wants it.
{ QStringLiteral("Ctrl+A"), QStringLiteral("select_all") },
+ // Escape is not claimed by anything else at window level. The query
+ // completer handles its own Escape while its popup is up, and a popup
+ // consumes the key before a window shortcut sees it.
+ { QStringLiteral("Esc"), QStringLiteral("clear_pane") },
{ QStringLiteral("Ctrl+H"), QStringLiteral("toggle_html") },
{ QStringLiteral("Ctrl+M"), QStringLiteral("load_remote") },
// Shifted because Ctrl+D is delete. Both are "D for details/delete"
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 792ba3f..e546af5 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -410,6 +410,7 @@ void MainWindow::buildUi()
m_syncLogPane->hide();
m_syncButton = new QPushButton(tr("Sync"), central);
+ m_syncButton->setObjectName(QStringLiteral("syncButton"));
m_sync = new MailSync(m_config.syncCommand(), this);
m_syncButton->setEnabled(m_sync->isAvailable());
if (!m_sync->isAvailable()) {
@@ -679,6 +680,21 @@ void MainWindow::registerActions()
m_queryEdit->setFocus();
m_queryCompleter->triggerCompletion();
});
+ addAction(QStringLiteral("clear_pane"), tr("Clear &message pane"),
+ tr("Blank the message pane without changing the selection"),
+ [this]() {
+ // A view change, not a mail change: the selection, the query and the
+ // undo stack are all left alone.
+ //
+ // m_currentThreadId is cleared with the pane, not merely alongside it.
+ // A threadLoaded still in flight for that id would otherwise paint the
+ // thread straight back, which is the queued-reply race documented in
+ // CLAUDE.md.
+ m_currentThreadId.clear();
+ m_messageView->clear();
+ m_markReadTimer->stop();
+ m_markReadThreadId.clear();
+ });
addAction(QStringLiteral("select_all"), tr("Select &all threads"),
tr("Select every thread in the current result list"), [this]() {
// A registered action rather than the view's built-in SelectAll key, so
@@ -1318,7 +1334,8 @@ void MainWindow::onExternalSyncStateChanged(SyncMonitor::State state)
if (m_localSyncHoldsLock)
return;
- m_syncProgress->setVisible(true);
+ m_externalSyncBusy = true;
+ updateSyncControls();
m_statusLabel->setText(tr("Background sync running..."));
return;
}
@@ -1327,10 +1344,16 @@ void MainWindow::onExternalSyncStateChanged(SyncMonitor::State state)
// said what happened, including for a failure, so there is nothing to add.
if (m_localSyncHoldsLock) {
m_localSyncHoldsLock = false;
+ m_externalSyncBusy = false;
+ updateSyncControls();
return;
}
- m_syncProgress->setVisible(false);
+ // Cleared for Idle AND for Unknown. Unknown means /proc/locks could not be
+ // read, so nothing is observed; leaving the button disabled there would
+ // strand it permanently on a platform that cannot see the lock at all.
+ m_externalSyncBusy = false;
+ updateSyncControls();
// Deliberately reports rather than refreshes. runCurrentQuery() clears the
// undo stack, the selection and the message pane, which is right for a
@@ -1350,16 +1373,35 @@ void MainWindow::onExternalSyncStateChanged(SyncMonitor::State state)
void MainWindow::setSyncBusy(bool busy)
{
- m_syncProgress->setVisible(busy);
- // Disabled rather than left clickable: MailSync::start() already refuses a
- // second run, but a button that looks live and does nothing is worse than
- // one that shows it is unavailable.
- m_syncButton->setEnabled(!busy && m_sync && m_sync->isAvailable());
+ m_localSyncBusy = busy;
+ updateSyncControls();
if (busy)
m_statusLabel->setText(tr("Syncing..."));
}
+void MainWindow::updateSyncControls()
+{
+ // ONE function of both states, deliberately. Two independent assignments,
+ // one per sync path, means whichever fires second wins: a background sync
+ // ending would re-enable the button in the middle of a local run, and a
+ // local run ending would re-enable it while cron still holds the lock.
+ const bool busy = m_localSyncBusy || m_externalSyncBusy;
+
+ m_syncProgress->setVisible(busy);
+
+ // Disabled rather than left clickable: MailSync::start() already refuses a
+ // second run and the script exits 75 when another holds the lock, but a
+ // button that looks live and does nothing is worse than one that shows it
+ // is unavailable.
+ //
+ // Note this reads Running specifically, not "not Idle". Unknown means
+ // /proc/locks could not be read and nothing was observed, so the button
+ // stays usable: permanently disabling it where the lock cannot be seen is
+ // worse than occasionally offering a run that gets skipped.
+ m_syncButton->setEnabled(!busy && m_sync && m_sync->isAvailable());
+}
+
void MainWindow::updatePendingIndicator()
{
if (m_pendingEdits <= 0) {
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 66044dc..61722aa 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -175,6 +175,14 @@ private:
/// says "working, duration unknown", which is the truth.
void setSyncBusy(bool busy);
+ /// Applies the sync progress bar and button state from BOTH sync sources.
+ ///
+ /// One function of both, never two assignments: with a local and a
+ /// background sync each writing the widgets independently, whichever
+ /// finished second would win and re-enable the button while the other was
+ /// still running.
+ void updateSyncControls();
+
/// Opens the tag dialog on the current selection and applies its result.
///
@@ -223,6 +231,15 @@ private:
/// MailSync::isRunning() is already false and can no longer answer "was
/// that ours?".
bool m_localSyncHoldsLock = false;
+
+ /// True while a sync this window started is running. Half of the input to
+ /// updateSyncControls().
+ bool m_localSyncBusy = false;
+
+ /// True while a sync this window did NOT start holds the lock. The other
+ /// half. Tracked here rather than read back from SyncMonitor so the state
+ /// the UI acted on is the state it was told about.
+ bool m_externalSyncBusy = false;
QUndoStack m_undoStack;
QLineEdit *m_queryEdit = nullptr;