Skip to content

Commit 200f5ae

Browse files
jamescowensclaude
andcommitted
gui: add sync overlay and out-of-sync action guards
Add a SyncOverlay widget that covers the main window while the wallet is catching up, preventing users from attempting operations that require an up-to-date chain state. The overlay shows sync progress and can be permanently dismissed for the session via a "Hide" button. Even after dismissing the overlay, per-action guards remain in place: - Beacon wizard: blocked with a warning dialog - Voting page: blocked with a warning dialog - Send coins: warned with a Yes/No confirmation (allows proceeding) This addresses a common support issue where users attempt to set up beacons or vote before their wallet has finished synchronizing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8bdad8a commit 200f5ae

File tree

8 files changed

+314
-4
lines changed

8 files changed

+314
-4
lines changed

src/qt/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ add_library(gridcoinqt STATIC
7878
researcher/researcherwizardsummarypage.cpp
7979
rpcconsole.cpp
8080
sendcoinsdialog.cpp
81+
syncoverlay.cpp
8182
sendcoinsentry.cpp
8283
sidestaketablemodel.cpp
8384
signverifymessagedialog.cpp

src/qt/bitcoingui.cpp

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
#include "upgradeqt.h"
4646
#include "voting/votingmodel.h"
4747
#include "voting/polltablemodel.h"
48+
#include "syncoverlay.h"
4849
#include "updatedialog.h"
4950

5051
#ifdef Q_OS_MAC
@@ -110,6 +111,8 @@ BitcoinGUI::BitcoinGUI(QWidget* parent)
110111
, trayIcon(nullptr)
111112
, notificator(nullptr)
112113
, rpcConsole(nullptr)
114+
, m_sync_overlay(nullptr)
115+
, m_in_sync(false)
113116
, nWeight(0)
114117
{
115118
QSettings settings;
@@ -216,6 +219,11 @@ BitcoinGUI::BitcoinGUI(QWidget* parent)
216219

217220
setCentralWidget(centralWidget);
218221

222+
// Create sync overlay — sits on top of the central widget and blocks
223+
// interaction until the wallet is in sync (or the user dismisses it).
224+
m_sync_overlay = new SyncOverlay(centralWidget);
225+
m_sync_overlay->setVisible(true);
226+
219227
// Create status bar
220228
statusBar();
221229

@@ -1078,13 +1086,14 @@ void BitcoinGUI::setNumBlocks(int count, int nTotalBlocks)
10781086
// Note: It is a really good question why the GUI uses a different standard than the core for determining whether
10791087
// the client is in sync.
10801088
// TODO: Review this and decide whether to converge the sync standards.
1081-
bool in_sync = false;
10821089

10831090
// return if we have no connection to the network
10841091
if (!clientModel || clientModel->getNumConnections() == 0)
10851092
{
10861093
labelBlocksIcon->setPixmap(GRC::ScaleStatusIcon(this, ":/icons/status_sync_stalled_" + sSheet));
10871094
labelBlocksIcon->setToolTip(tr("Sync: no connections."));
1095+
m_in_sync = false;
1096+
m_sync_overlay->setSyncState(true, 0, 0);
10881097
return;
10891098
}
10901099

@@ -1123,7 +1132,7 @@ void BitcoinGUI::setNumBlocks(int count, int nTotalBlocks)
11231132
labelBlocksIcon->setPixmap(GRC::ScaleStatusIcon(this, ":/icons/status_sync_done_" + sSheet));
11241133

11251134
overviewPage->showOutOfSyncWarning(false);
1126-
in_sync = true;
1135+
m_in_sync = true;
11271136
statusbarAlertsLabel->setText(clientModel->getStatusBarWarnings());
11281137
}
11291138
else
@@ -1132,9 +1141,11 @@ void BitcoinGUI::setNumBlocks(int count, int nTotalBlocks)
11321141
tooltip = tr("Catching up...") + QString("<br>") + tooltip;
11331142

11341143
overviewPage->showOutOfSyncWarning(true);
1135-
in_sync = false;
1144+
m_in_sync = false;
11361145
}
11371146

1147+
m_sync_overlay->setSyncState(!m_in_sync, count, nTotalBlocks);
1148+
11381149
if(!text.isEmpty())
11391150
{
11401151
tooltip += QString("<br>");
@@ -1145,7 +1156,7 @@ void BitcoinGUI::setNumBlocks(int count, int nTotalBlocks)
11451156
tooltip = QString("<nobr>") + tooltip + QString("</nobr>");
11461157

11471158
labelBlocksIcon->setToolTip(tooltip);
1148-
overviewPage->setHeight(count, nTotalBlocks, in_sync);
1159+
overviewPage->setHeight(count, nTotalBlocks, m_in_sync);
11491160
}
11501161

11511162
void BitcoinGUI::setDifficulty(double difficulty)
@@ -1471,6 +1482,18 @@ void BitcoinGUI::peersClicked()
14711482
rpcConsole->showPeersTab();
14721483
}
14731484

1485+
void BitcoinGUI::recheckCurrentTabAction()
1486+
{
1487+
QWidget* current = centralWidget->currentWidget();
1488+
1489+
if (current == overviewPage) overviewAction->setChecked(true);
1490+
else if (current == sendCoinsPage) sendCoinsAction->setChecked(true);
1491+
else if (current == receiveCoinsPage) receiveCoinsAction->setChecked(true);
1492+
else if (current == transactionView) historyAction->setChecked(true);
1493+
else if (current == addressBookPage) addressBookAction->setChecked(true);
1494+
else if (current == votingPage) votingAction->setChecked(true);
1495+
}
1496+
14741497
void BitcoinGUI::gotoOverviewPage()
14751498
{
14761499
overviewAction->setChecked(true);
@@ -1519,6 +1542,22 @@ void BitcoinGUI::gotoReceiveCoinsPage()
15191542

15201543
void BitcoinGUI::gotoSendCoinsPage()
15211544
{
1545+
if (!m_in_sync) {
1546+
QMessageBox::StandardButton reply = QMessageBox::warning(this,
1547+
tr("Wallet Not In Sync"),
1548+
tr("The wallet is not yet in sync with the network. Your balance "
1549+
"may be inaccurate, and transactions created while out of sync "
1550+
"may not confirm properly.\n\n"
1551+
"Are you sure you want to proceed?"),
1552+
QMessageBox::Yes | QMessageBox::No,
1553+
QMessageBox::No);
1554+
1555+
if (reply != QMessageBox::Yes) {
1556+
recheckCurrentTabAction();
1557+
return;
1558+
}
1559+
}
1560+
15221561
sendCoinsAction->setChecked(true);
15231562
centralWidget->setCurrentWidget(sendCoinsPage);
15241563

@@ -1528,6 +1567,15 @@ void BitcoinGUI::gotoSendCoinsPage()
15281567

15291568
void BitcoinGUI::gotoVotingPage()
15301569
{
1570+
if (!m_in_sync) {
1571+
QMessageBox::warning(this,
1572+
tr("Wallet Not In Sync"),
1573+
tr("The wallet must be in sync to access the voting system. "
1574+
"Please wait for synchronization to complete."));
1575+
recheckCurrentTabAction();
1576+
return;
1577+
}
1578+
15311579
votingAction->setChecked(true);
15321580
centralWidget->setCurrentWidget(votingPage);
15331581

src/qt/bitcoingui.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class Notificator;
3232
class RPCConsole;
3333
class DiagnosticsDialog;
3434
class ClickLabel;
35+
class SyncOverlay;
3536
class UpdateDialog;
3637

3738
QT_BEGIN_NAMESPACE
@@ -113,6 +114,9 @@ class BitcoinGUI : public QMainWindow
113114
SignVerifyMessageDialog *signVerifyMessageDialog;
114115
std::unique_ptr<UpdateDialog> updateMessageDialog;
115116

117+
SyncOverlay *m_sync_overlay;
118+
bool m_in_sync;
119+
116120
QLabel *statusbarAlertsLabel;
117121
QLabel *labelEncryptionIcon;
118122
QLabel *labelStakingIcon;
@@ -191,6 +195,8 @@ class BitcoinGUI : public QMainWindow
191195
void createTrayIconMenu();
192196
/** Set Icons */
193197
void setIcons();
198+
/** Re-check the toolbar action that matches the currently displayed page. */
199+
void recheckCurrentTabAction();
194200

195201

196202
public slots:

src/qt/researcher/researchermodel.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "qt/researcher/researchermodel.h"
2424
#include "qt/researcher/researcherwizard.h"
2525

26+
#include <QApplication>
2627
#include <QIcon>
2728
#include <QMessageBox>
2829
#include <QTimer>
@@ -205,6 +206,15 @@ void ResearcherModel::showWizard(WalletModel* wallet_model)
205206
return;
206207
}
207208

209+
if (outOfSync()) {
210+
QMessageBox::warning(QApplication::activeWindow(),
211+
QObject::tr("Wallet Not In Sync"),
212+
QObject::tr("The wallet must be in sync to manage beacons. Please wait "
213+
"for synchronization to complete before using the researcher "
214+
"and beacon configuration wizard."));
215+
return;
216+
}
217+
208218
m_wizard_open = true;
209219

210220
ResearcherWizard *wizard = new ResearcherWizard(nullptr, this, wallet_model);

src/qt/syncoverlay.cpp

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
// Copyright (c) 2025 The Gridcoin developers
2+
// Distributed under the MIT/X11 software license, see the accompanying
3+
// file COPYING or https://opensource.org/licenses/mit-license.php.
4+
5+
#include "qt/syncoverlay.h"
6+
#include "qt/decoration.h"
7+
8+
#include <QEvent>
9+
#include <QHBoxLayout>
10+
#include <QLabel>
11+
#include <QPainter>
12+
#include <QPushButton>
13+
#include <QVBoxLayout>
14+
15+
SyncOverlay::SyncOverlay(QWidget* parent)
16+
: QWidget(parent)
17+
, m_dismissed(false)
18+
, m_out_of_sync(true)
19+
{
20+
// Fill the entire parent widget area and sit on top of everything.
21+
setAttribute(Qt::WA_TranslucentBackground, false);
22+
23+
if (parent) {
24+
parent->installEventFilter(this);
25+
resize(parent->size());
26+
}
27+
28+
// --- Central panel ---
29+
30+
QWidget* panel = new QWidget(this);
31+
panel->setObjectName("syncOverlayPanel");
32+
panel->setStyleSheet(
33+
"#syncOverlayPanel {"
34+
" background-color: palette(window);"
35+
" border: 1px solid palette(mid);"
36+
" border-radius: 8px;"
37+
"}"
38+
);
39+
panel->setFixedWidth(480);
40+
41+
// Icon
42+
m_icon_label = new QLabel(panel);
43+
m_icon_label->setPixmap(GRC::ScaleIcon(this, ":/icons/no_result", 48));
44+
m_icon_label->setAlignment(Qt::AlignCenter);
45+
46+
// Title
47+
m_title_label = new QLabel(panel);
48+
m_title_label->setText(tr("Wallet is syncing"));
49+
m_title_label->setAlignment(Qt::AlignCenter);
50+
QFont title_font = m_title_label->font();
51+
title_font.setPointSize(title_font.pointSize() + 4);
52+
title_font.setBold(true);
53+
m_title_label->setFont(title_font);
54+
55+
// Detail
56+
m_detail_label = new QLabel(panel);
57+
m_detail_label->setText(
58+
tr("The displayed information may be out of date. Your wallet "
59+
"automatically synchronizes with the Gridcoin network after a "
60+
"connection is established, but this process has not completed yet."
61+
"\n\n"
62+
"Operations such as beacon management, voting, and sending "
63+
"transactions should not be performed until synchronization "
64+
"is complete.")
65+
);
66+
m_detail_label->setAlignment(Qt::AlignCenter);
67+
m_detail_label->setWordWrap(true);
68+
69+
// Hide button
70+
m_hide_button = new QPushButton(tr("Hide"), panel);
71+
m_hide_button->setFixedWidth(120);
72+
connect(m_hide_button, &QPushButton::clicked, this, &SyncOverlay::dismiss);
73+
74+
// Panel layout
75+
QVBoxLayout* panel_layout = new QVBoxLayout(panel);
76+
panel_layout->setContentsMargins(32, 24, 32, 24);
77+
panel_layout->setSpacing(12);
78+
panel_layout->addWidget(m_icon_label);
79+
panel_layout->addWidget(m_title_label);
80+
panel_layout->addWidget(m_detail_label);
81+
82+
QHBoxLayout* button_layout = new QHBoxLayout();
83+
button_layout->addStretch();
84+
button_layout->addWidget(m_hide_button);
85+
button_layout->addStretch();
86+
panel_layout->addSpacing(8);
87+
panel_layout->addLayout(button_layout);
88+
89+
// Overlay layout — center the panel
90+
QVBoxLayout* outer_v = new QVBoxLayout(this);
91+
outer_v->setContentsMargins(0, 0, 0, 0);
92+
93+
QHBoxLayout* outer_h = new QHBoxLayout();
94+
outer_h->addStretch();
95+
outer_h->addWidget(panel);
96+
outer_h->addStretch();
97+
98+
outer_v->addStretch();
99+
outer_v->addLayout(outer_h);
100+
outer_v->addStretch();
101+
}
102+
103+
bool SyncOverlay::eventFilter(QObject* watched, QEvent* event)
104+
{
105+
if (watched == parent() && event->type() == QEvent::Resize) {
106+
resize(parentWidget()->size());
107+
raise();
108+
}
109+
110+
return QWidget::eventFilter(watched, event);
111+
}
112+
113+
void SyncOverlay::setSyncState(bool out_of_sync, int blocks, int total_blocks)
114+
{
115+
m_out_of_sync = out_of_sync;
116+
117+
if (!out_of_sync) {
118+
// In sync — hide unconditionally and reset dismissed state so the
119+
// overlay will reappear if the wallet falls out of sync again later.
120+
m_dismissed = false;
121+
QWidget::hide();
122+
return;
123+
}
124+
125+
if (m_dismissed) {
126+
return;
127+
}
128+
129+
// Update progress detail
130+
if (total_blocks > 0 && blocks < total_blocks) {
131+
int pct = static_cast<int>(100.0 * blocks / total_blocks);
132+
m_detail_label->setText(
133+
tr("The wallet is currently synchronizing with the Gridcoin "
134+
"network. Progress: %1 of %2 blocks (%3%)."
135+
"\n\n"
136+
"Operations such as beacon management, voting, and sending "
137+
"transactions should not be performed until synchronization "
138+
"is complete.")
139+
.arg(blocks)
140+
.arg(total_blocks)
141+
.arg(pct)
142+
);
143+
}
144+
145+
raise();
146+
show();
147+
}
148+
149+
bool SyncOverlay::isActive() const
150+
{
151+
return m_out_of_sync && !m_dismissed;
152+
}
153+
154+
void SyncOverlay::paintEvent(QPaintEvent* event)
155+
{
156+
Q_UNUSED(event);
157+
158+
QPainter painter(this);
159+
painter.fillRect(rect(), QColor(0, 0, 0, 160));
160+
}
161+
162+
void SyncOverlay::dismiss()
163+
{
164+
m_dismissed = true;
165+
QWidget::hide();
166+
}

0 commit comments

Comments
 (0)