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