aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-20 17:21:54 +0200
committerDanilo M. <danix@danix.xyz>2026-08-20 17:21:54 +0200
commitaf902e0b2268085ff2ff4c21a4430ec4a94164bd (patch)
tree80c9712390fb91a8abf2af4c3bcec87e49d4836a /src
parente81e4f09a6a40162d0041253a35777e547d4dd81 (diff)
downloadqtmaildir-af902e0b2268085ff2ff4c21a4430ec4a94164bd.tar.gz
qtmaildir-af902e0b2268085ff2ff4c21a4430ec4a94164bd.zip
refactor(ui): extract the busy indicator into a widget, item 134
The status bar's sync bar was a bare QProgressBar configured inline in MainWindow, and item 123's send popup needs the same thing again. It also needs the half MainWindow does not use: the popup drains a determinate bar through its cancellable countdown and switches THE SAME widget to indeterminate when send_command starts and the duration stops being knowable. Building that inline a second time is what this removes. BusyIndicator carries both modes. setBusy() resets the range as well as the visibility, so the switch out of the countdown cannot leave the bar drawing its last fraction, and setProgress() treats a total of zero as busy rather than passing it through: setRange(0, 0) IS the indeterminate range, so a zero total would otherwise hand the caller an animating bar while it believed it had drawn an empty one. Only the bar is extracted, not the status label the backlog row mentions beside it. m_statusLabel has 34 uses across MainWindow for transient messages, selection counts and sync phases; it belongs to the window rather than to the indicator, and the send popup owns its own phase text. The hidden-on-construction test needs a shown parent, which cost a mutation to find. Measured against a standalone Qt program: a parentless widget reports isVisible() false and isHidden() true whether or not hide() was ever called, so both obvious assertions passed against a constructor with the hide() deleted. What differs is WA_WState_ExplicitShowHide, and the behaviour it produces appears only once a parent is shown, which is how the status bar holds this widget. All five mutations checked and killed: the zero-total guard, the range reset in setBusy(), the value clamp, the show() in setProgress() and the hide() in the constructor.
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/busyindicator.cpp65
-rw-r--r--src/busyindicator.h53
-rw-r--r--src/mainwindow.cpp18
-rw-r--r--src/mainwindow.h4
5 files changed, 129 insertions, 12 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 197366d..b63ff3e 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -6,6 +6,7 @@ add_library(qtmaildir_lib STATIC
htmlbuilder.cpp
cidschemehandler.cpp
cardlayout.cpp
+ busyindicator.cpp
marks.cpp
carddelegate.cpp
notmuchworker.cpp
diff --git a/src/busyindicator.cpp b/src/busyindicator.cpp
new file mode 100644
index 0000000..0ccc719
--- /dev/null
+++ b/src/busyindicator.cpp
@@ -0,0 +1,65 @@
+/*
+ * qtmaildir - a Qt6 mail client for notmuch-indexed Maildirs
+ * Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "busyindicator.h"
+
+BusyIndicator::BusyIndicator(QWidget *parent)
+ : QProgressBar(parent)
+{
+ // Starts indeterminate and hidden, which is what both consumers want
+ // before anything is happening. A caller that wants a fraction calls
+ // setProgress() and never sees this range.
+ setRange(0, 0);
+
+ // The percentage would be meaningless in the indeterminate mode and is
+ // redundant beside the phase text in the determinate one, where the popup
+ // says what is being waited for.
+ setTextVisible(false);
+ hide();
+}
+
+void BusyIndicator::setBusy(bool busy)
+{
+ setRange(0, 0);
+ setVisible(busy);
+}
+
+void BusyIndicator::setProgress(int value, int total)
+{
+ if (total <= 0) {
+ // Not an empty determinate bar: setRange(0, 0) IS the indeterminate
+ // range, so falling through here would hand the caller an animating
+ // bar while it believed it had shown a fraction.
+ setBusy(true);
+ return;
+ }
+
+ setRange(0, total);
+ setValue(qBound(0, value, total));
+ show();
+}
+
+bool BusyIndicator::isBusy() const
+{
+ return isVisible();
+}
+
+bool BusyIndicator::isDeterminate() const
+{
+ return maximum() > minimum();
+}
diff --git a/src/busyindicator.h b/src/busyindicator.h
new file mode 100644
index 0000000..14ad6ee
--- /dev/null
+++ b/src/busyindicator.h
@@ -0,0 +1,53 @@
+/*
+ * qtmaildir - a Qt6 mail client for notmuch-indexed Maildirs
+ * Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#pragma once
+
+#include <QProgressBar>
+
+// A progress bar that carries both of the modes this application needs, so the
+// two places that show one do not each configure a bare QProgressBar. The
+// status bar's sync indicator is indeterminate, because mbsync reports no
+// percentage; the composer's send popup drains a determinate bar through its
+// undo countdown and then switches THE SAME widget to indeterminate when the
+// command starts. That switch is why one class exists rather than two.
+//
+// Deliberately no label. MainWindow's status text is m_statusLabel, which is
+// the window's and carries far more than this widget's state, and the send
+// popup's phase text belongs to the popup. A label here would be a third
+// owner of status text.
+class BusyIndicator : public QProgressBar
+{
+ Q_OBJECT
+
+public:
+ explicit BusyIndicator(QWidget *parent = nullptr);
+
+ // Indeterminate: an animating bar meaning "working, duration unknown".
+ // setRange(0, 0) is what Qt reads as indeterminate, and a bar left in that
+ // range shows no fraction however many times setValue() is called.
+ void setBusy(bool busy);
+
+ // Determinate: a real fraction. Passing a total of 0 or less would be a
+ // silent request for the indeterminate range, so it is treated as busy
+ // instead of quietly drawing an empty bar forever.
+ void setProgress(int value, int total);
+
+ bool isBusy() const;
+ bool isDeterminate() const;
+};
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 58c82ca..dc416ca 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -36,7 +36,6 @@
#include <QMenuBar>
#include <QMessageBox>
#include <QPlainTextEdit>
-#include <QProgressBar>
#include <QPushButton>
#include <QSettings>
#include <QSplitter>
@@ -54,6 +53,7 @@
#include "notmuchworker.h"
#include "querycompleter.h"
#include "carddelegate.h"
+#include "busyindicator.h"
#include "cardlayout.h"
#include "searchterm.h"
#include "tagchip.h"
@@ -504,16 +504,14 @@ void MainWindow::buildUi()
m_pendingLabel->hide();
statusBar()->addPermanentWidget(m_pendingLabel);
- // Indeterminate: setRange(0, 0). A sync has no measurable progress, since
- // mbsync reports no percentage and the script's output is unstructured, so
- // a bar filling left to right would be inventing a fraction. This one
- // animates to say "working, duration unknown".
- m_syncProgress = new QProgressBar(this);
+ // Indeterminate, which BusyIndicator starts in: a sync has no measurable
+ // progress, since mbsync reports no percentage and the script's output is
+ // unstructured, so a bar filling left to right would be inventing a
+ // fraction. This one animates to say "working, duration unknown". The
+ // determinate half of the widget is the composer's, not this one's.
+ m_syncProgress = new BusyIndicator(this);
m_syncProgress->setObjectName(QStringLiteral("syncProgress"));
- m_syncProgress->setRange(0, 0);
- m_syncProgress->setTextVisible(false);
m_syncProgress->setMaximumWidth(120);
- m_syncProgress->hide();
statusBar()->addPermanentWidget(m_syncProgress);
// Query row.
@@ -3677,7 +3675,7 @@ void MainWindow::updateSyncControls()
// local run ending would re-enable it while cron still holds the lock.
const bool busy = m_localSyncBusy || m_externalSyncBusy;
- m_syncProgress->setVisible(busy);
+ m_syncProgress->setBusy(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
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 5b1621f..8e483d2 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -52,11 +52,11 @@ class QPushButton;
class QComboBox;
class QPlainTextEdit;
class QSplitter;
-class QProgressBar;
class QTimer;
class QToolButton;
class QVBoxLayout;
+class BusyIndicator;
class ThreadListModel;
class MessageView;
class MailSync;
@@ -1074,7 +1074,7 @@ private:
QLabel *m_pendingLabel = nullptr;
/// Indeterminate, shown only while a sync runs. See setSyncBusy().
- QProgressBar *m_syncProgress = nullptr;
+ BusyIndicator *m_syncProgress = nullptr;
/// The last counts the worker answered, one per placeholderLines() entry.
/// Empty until the first reply, which renders the pane without its helper