aboutsummaryrefslogtreecommitdiffstats
path: root/src/mainwindow.cpp
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-04 19:16:07 +0200
committerDanilo M. <danix@danix.xyz>2026-08-04 19:16:07 +0200
commit69e777cc72bd846c250d68656a8d558c9401fcd5 (patch)
tree52eb0b736167c035c9c38b1d8aac88aa008e34e1 /src/mainwindow.cpp
parentb34b18c3a6288031c4eed1687f8e53487c3af56f (diff)
downloadqtmaildir-69e777cc72bd846c250d68656a8d558c9401fcd5.tar.gz
qtmaildir-69e777cc72bd846c250d68656a8d558c9401fcd5.zip
feat(ui): disable Sync during a background sync, and blank the pane on Esc
Items 29 and 32. 29 was a constraint item 27 specified and that shipped unbuilt: while a cron sync held the lock the Sync button stayed clickable, and pressing it could only produce the EX_TEMPFAIL skip. The progress bar and the button are now written by one updateSyncControls() taking both sync sources, which the item asked for by name: two independent assignments, one per path, means whichever finishes second wins, so a background sync ending would re-enable the button in the middle of a local run. Unknown re-enables the button, deliberately. It means /proc/locks could not be read and nothing was observed, so leaving the button disabled would strand it permanently wherever the lock cannot be seen. 32 adds a clear_pane action on Esc. It clears m_currentThreadId with the pane, not merely alongside it, or a threadLoaded still in flight would paint the thread straight back; and it cancels any pending mark-read, since a thread blanked from view must not be marked read two seconds later. The selection, the query and the undo stack are untouched. The one real risk in 32 was Escape being stolen from the query completer, the way Return was once lost to a window shortcut. Probed rather than reasoned about: a popup consumes the key before a window-level shortcut sees it, so the completer still dismisses. Every test here was verified by reverting the code it covers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'src/mainwindow.cpp')
-rw-r--r--src/mainwindow.cpp56
1 files changed, 49 insertions, 7 deletions
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) {