modaloverlay.cpp
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 }