modaloverlay.cpp raw
1 // Copyright (c) 2016-present The Bitcoin Core developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5 #include <bitcoin-build-config.h> // IWYU pragma: keep
6
7 #include <qt/modaloverlay.h>
8 #include <qt/forms/ui_modaloverlay.h>
9
10 #include <chainparams.h>
11 #include <qt/guiutil.h>
12
13 #include <QEasingCurve>
14 #include <QPropertyAnimation>
15 #include <QResizeEvent>
16
17 ModalOverlay::ModalOverlay(bool enable_wallet, QWidget* parent)
18 : QWidget(parent),
19 ui(new Ui::ModalOverlay),
20 bestHeaderDate(QDateTime())
21 {
22 ui->setupUi(this);
23 connect(ui->closeButton, &QPushButton::clicked, this, &ModalOverlay::closeClicked);
24 if (parent) {
25 parent->installEventFilter(this);
26 raise();
27 }
28 ui->closeButton->installEventFilter(this);
29
30 blockProcessTime.clear();
31 setVisible(false);
32 if (!enable_wallet) {
33 ui->infoText->setVisible(false);
34 ui->infoTextStrong->setText(tr("%1 is currently syncing. It will download headers and blocks from peers and validate them until reaching the tip of the block chain.").arg(CLIENT_NAME));
35 }
36
37 m_animation.setTargetObject(this);
38 m_animation.setPropertyName("pos");
39 m_animation.setDuration(300 /* ms */);
40 m_animation.setEasingCurve(QEasingCurve::OutQuad);
41 }
42
43 ModalOverlay::~ModalOverlay()
44 {
45 delete ui;
46 }
47
48 bool ModalOverlay::eventFilter(QObject * obj, QEvent * ev) {
49 if (obj == parent()) {
50 if (ev->type() == QEvent::Resize) {
51 QResizeEvent * rev = static_cast<QResizeEvent*>(ev);
52 resize(rev->size());
53 if (!layerIsVisible)
54 setGeometry(0, height(), width(), height());
55
56 if (m_animation.endValue().toPoint().y() > 0) {
57 m_animation.setEndValue(QPoint(0, height()));
58 }
59 }
60 else if (ev->type() == QEvent::ChildAdded) {
61 raise();
62 }
63 }
64
65 if (obj == ui->closeButton && ev->type() == QEvent::FocusOut && layerIsVisible) {
66 ui->closeButton->setFocus(Qt::OtherFocusReason);
67 }
68
69 return QWidget::eventFilter(obj, ev);
70 }
71
72 //! Tracks parent widget changes
73 bool ModalOverlay::event(QEvent* ev) {
74 if (ev->type() == QEvent::ParentAboutToChange) {
75 if (parent()) parent()->removeEventFilter(this);
76 }
77 else if (ev->type() == QEvent::ParentChange) {
78 if (parent()) {
79 parent()->installEventFilter(this);
80 raise();
81 }
82 }
83 return QWidget::event(ev);
84 }
85
86 void ModalOverlay::setKnownBestHeight(int count, const QDateTime& blockDate, bool presync)
87 {
88 if (!presync && count > bestHeaderHeight) {
89 bestHeaderHeight = count;
90 bestHeaderDate = blockDate;
91 UpdateHeaderSyncLabel();
92 }
93 if (presync) {
94 UpdateHeaderPresyncLabel(count, blockDate);
95 }
96 }
97
98 void ModalOverlay::tipUpdate(int count, const QDateTime& blockDate, double nVerificationProgress)
99 {
100 QDateTime currentDate = QDateTime::currentDateTime();
101
102 // keep a vector of samples of verification progress at height
103 blockProcessTime.push_front(qMakePair(currentDate.toMSecsSinceEpoch(), nVerificationProgress));
104
105 // show progress speed if we have more than one sample
106 if (blockProcessTime.size() >= 2) {
107 double progressDelta = 0;
108 double progressPerHour = 0;
109 qint64 timeDelta = 0;
110 qint64 remainingMSecs = 0;
111 double remainingProgress = 1.0 - nVerificationProgress;
112 for (int i = 1; i < blockProcessTime.size(); i++) {
113 QPair<qint64, double> sample = blockProcessTime[i];
114
115 // take first sample after 500 seconds or last available one
116 if (sample.first < (currentDate.toMSecsSinceEpoch() - 500 * 1000) || i == blockProcessTime.size() - 1) {
117 progressDelta = blockProcessTime[0].second - sample.second;
118 timeDelta = blockProcessTime[0].first - sample.first;
119 progressPerHour = (progressDelta > 0) ? progressDelta / (double)timeDelta * 1000 * 3600 : 0;
120 remainingMSecs = (progressDelta > 0) ? remainingProgress / progressDelta * timeDelta : -1;
121 break;
122 }
123 }
124 // show progress increase per hour
125 ui->progressIncreasePerH->setText(QString::number(progressPerHour * 100, 'f', 2)+"%");
126
127 // show expected remaining time
128 if(remainingMSecs >= 0) {
129 ui->expectedTimeLeft->setText(GUIUtil::formatNiceTimeOffset(remainingMSecs / 1000.0));
130 } else {
131 ui->expectedTimeLeft->setText(QObject::tr("unknown"));
132 }
133
134 static const int MAX_SAMPLES = 5000;
135 if (blockProcessTime.count() > MAX_SAMPLES) {
136 blockProcessTime.remove(MAX_SAMPLES, blockProcessTime.count() - MAX_SAMPLES);
137 }
138 }
139
140 // show the last block date
141 ui->newestBlockDate->setText(blockDate.toString());
142
143 // show the percentage done according to nVerificationProgress
144 ui->percentageProgress->setText(QString::number(nVerificationProgress*100, 'f', 2)+"%");
145
146 if (!bestHeaderDate.isValid())
147 // not syncing
148 return;
149
150 // estimate the number of headers left based on nPowTargetSpacing
151 // and check if the gui is not aware of the best header (happens rarely)
152 int estimateNumHeadersLeft = bestHeaderDate.secsTo(currentDate) / Params().GetConsensus().nPowTargetSpacing;
153 bool hasBestHeader = bestHeaderHeight >= count;
154
155 // show remaining number of blocks
156 if (estimateNumHeadersLeft < HEADER_HEIGHT_DELTA_SYNC && hasBestHeader) {
157 ui->numberOfBlocksLeft->setText(QString::number(bestHeaderHeight - count));
158 } else {
159 UpdateHeaderSyncLabel();
160 ui->expectedTimeLeft->setText(tr("Unknown…"));
161 }
162 }
163
164 void ModalOverlay::UpdateHeaderSyncLabel() {
165 int est_headers_left = bestHeaderDate.secsTo(QDateTime::currentDateTime()) / Params().GetConsensus().nPowTargetSpacing;
166 ui->numberOfBlocksLeft->setText(tr("Unknown. Syncing Headers (%1, %2%)…").arg(bestHeaderHeight).arg(QString::number(100.0 / (bestHeaderHeight + est_headers_left) * bestHeaderHeight, 'f', 1)));
167 }
168
169 void ModalOverlay::UpdateHeaderPresyncLabel(int height, const QDateTime& blockDate) {
170 int est_headers_left = blockDate.secsTo(QDateTime::currentDateTime()) / Params().GetConsensus().nPowTargetSpacing;
171 ui->numberOfBlocksLeft->setText(tr("Unknown. Pre-syncing Headers (%1, %2%)…").arg(height).arg(QString::number(100.0 / (height + est_headers_left) * height, 'f', 1)));
172 }
173
174 void ModalOverlay::toggleVisibility()
175 {
176 showHide(layerIsVisible, true);
177 if (!layerIsVisible)
178 userClosed = true;
179 }
180
181 void ModalOverlay::showHide(bool hide, bool userRequested)
182 {
183 if ( (layerIsVisible && !hide) || (!layerIsVisible && hide) || (!hide && userClosed && !userRequested))
184 return;
185
186 Q_EMIT triggered(hide);
187
188 if (!isVisible() && !hide)
189 setVisible(true);
190
191 m_animation.setStartValue(QPoint(0, hide ? 0 : height()));
192 // The eventFilter() updates the endValue if it is required for QEvent::Resize.
193 m_animation.setEndValue(QPoint(0, hide ? height() : 0));
194 m_animation.start(QAbstractAnimation::KeepWhenStopped);
195 layerIsVisible = !hide;
196
197 if (layerIsVisible) {
198 ui->closeButton->setFocus(Qt::OtherFocusReason);
199 }
200 }
201
202 void ModalOverlay::closeClicked()
203 {
204 showHide(true);
205 userClosed = true;
206 }
207