diff --git a/src/qt/modaloverlay.cpp b/src/qt/modaloverlay.cpp index e5f32fe12..ddd56a398 100644 --- a/src/qt/modaloverlay.cpp +++ b/src/qt/modaloverlay.cpp @@ -1,202 +1,208 @@ // Copyright (c) 2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include +#include #include #include ModalOverlay::ModalOverlay(bool enable_wallet, QWidget *parent) : QWidget(parent), ui(new Ui::ModalOverlay), bestHeaderHeight(0), bestHeaderDate(QDateTime()), layerIsVisible(false), userClosed(false) { ui->setupUi(this); connect(ui->closeButton, &QPushButton::clicked, this, &ModalOverlay::closeClicked); if (parent) { parent->installEventFilter(this); raise(); } blockProcessTime.clear(); setVisible(false); if (!enable_wallet) { ui->infoText->setVisible(false); 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)); } + + m_animation.setTargetObject(this); + m_animation.setPropertyName("pos"); + m_animation.setDuration(300 /* ms */); + m_animation.setEasingCurve(QEasingCurve::OutQuad); } ModalOverlay::~ModalOverlay() { delete ui; } bool ModalOverlay::eventFilter(QObject *obj, QEvent *ev) { if (obj == parent()) { if (ev->type() == QEvent::Resize) { QResizeEvent *rev = static_cast(ev); resize(rev->size()); if (!layerIsVisible) { setGeometry(0, height(), width(), height()); } + if (m_animation.endValue().toPoint().y() > 0) { + m_animation.setEndValue(QPoint(0, height())); + } } else if (ev->type() == QEvent::ChildAdded) { raise(); } } return QWidget::eventFilter(obj, ev); } //! Tracks parent widget changes bool ModalOverlay::event(QEvent *ev) { if (ev->type() == QEvent::ParentAboutToChange) { if (parent()) { parent()->removeEventFilter(this); } } else if (ev->type() == QEvent::ParentChange) { if (parent()) { parent()->installEventFilter(this); raise(); } } return QWidget::event(ev); } void ModalOverlay::setKnownBestHeight(int count, const QDateTime &blockDate) { if (count > bestHeaderHeight) { bestHeaderHeight = count; bestHeaderDate = blockDate; UpdateHeaderSyncLabel(); } } void ModalOverlay::tipUpdate(int count, const QDateTime &blockDate, double nVerificationProgress) { QDateTime currentDate = QDateTime::currentDateTime(); // keep a vector of samples of verification progress at height blockProcessTime.push_front( qMakePair(currentDate.toMSecsSinceEpoch(), nVerificationProgress)); // show progress speed if we have more than one sample if (blockProcessTime.size() >= 2) { double progressDelta = 0; double progressPerHour = 0; qint64 timeDelta = 0; qint64 remainingMSecs = 0; double remainingProgress = 1.0 - nVerificationProgress; for (int i = 1; i < blockProcessTime.size(); i++) { QPair sample = blockProcessTime[i]; // take first sample after 500 seconds or last available one if (sample.first < (currentDate.toMSecsSinceEpoch() - 500 * 1000) || i == blockProcessTime.size() - 1) { progressDelta = blockProcessTime[0].second - sample.second; timeDelta = blockProcessTime[0].first - sample.first; progressPerHour = progressDelta / (double)timeDelta * 1000 * 3600; remainingMSecs = (progressDelta > 0) ? remainingProgress / progressDelta * timeDelta : -1; break; } } // show progress increase per hour ui->progressIncreasePerH->setText( QString::number(progressPerHour * 100, 'f', 2) + "%"); // show expected remaining time if (remainingMSecs >= 0) { ui->expectedTimeLeft->setText( GUIUtil::formatNiceTimeOffset(remainingMSecs / 1000.0)); } else { ui->expectedTimeLeft->setText(QObject::tr("unknown")); } static const int MAX_SAMPLES = 5000; if (blockProcessTime.count() > MAX_SAMPLES) { blockProcessTime.remove(MAX_SAMPLES, blockProcessTime.count() - MAX_SAMPLES); } } // show the last block date ui->newestBlockDate->setText(blockDate.toString()); // show the percentage done according to nVerificationProgress ui->percentageProgress->setText( QString::number(nVerificationProgress * 100, 'f', 2) + "%"); ui->progressBar->setValue(nVerificationProgress * 100); if (!bestHeaderDate.isValid()) { // not syncing return; } // estimate the number of headers left based on nPowTargetSpacing // and check if the gui is not aware of the best header (happens rarely) int estimateNumHeadersLeft = bestHeaderDate.secsTo(currentDate) / Params().GetConsensus().nPowTargetSpacing; bool hasBestHeader = bestHeaderHeight >= count; // show remaining number of blocks if (estimateNumHeadersLeft < HEADER_HEIGHT_DELTA_SYNC && hasBestHeader) { ui->numberOfBlocksLeft->setText( QString::number(bestHeaderHeight - count)); } else { UpdateHeaderSyncLabel(); ui->expectedTimeLeft->setText(tr("Unknown...")); } } void ModalOverlay::UpdateHeaderSyncLabel() { int est_headers_left = bestHeaderDate.secsTo(QDateTime::currentDateTime()) / Params().GetConsensus().nPowTargetSpacing; ui->numberOfBlocksLeft->setText( tr("Unknown. Syncing Headers (%1, %2%)...") .arg(bestHeaderHeight) .arg(QString::number(100.0 / (bestHeaderHeight + est_headers_left) * bestHeaderHeight, 'f', 1))); } void ModalOverlay::toggleVisibility() { showHide(layerIsVisible, true); if (!layerIsVisible) { userClosed = true; } } void ModalOverlay::showHide(bool hide, bool userRequested) { if ((layerIsVisible && !hide) || (!layerIsVisible && hide) || (!hide && userClosed && !userRequested)) { return; } if (!isVisible() && !hide) { setVisible(true); } - setGeometry(0, hide ? 0 : height(), width(), height()); - - QPropertyAnimation *animation = new QPropertyAnimation(this, "pos"); - animation->setDuration(300); - animation->setStartValue(QPoint(0, hide ? 0 : this->height())); - animation->setEndValue(QPoint(0, hide ? this->height() : 0)); - animation->setEasingCurve(QEasingCurve::OutQuad); - animation->start(QAbstractAnimation::DeleteWhenStopped); + m_animation.setStartValue(QPoint(0, hide ? 0 : height())); + // The eventFilter() updates the endValue if it is required for + // QEvent::Resize. + m_animation.setEndValue(QPoint(0, hide ? height() : 0)); + m_animation.start(QAbstractAnimation::KeepWhenStopped); layerIsVisible = !hide; } void ModalOverlay::closeClicked() { showHide(true); userClosed = true; } diff --git a/src/qt/modaloverlay.h b/src/qt/modaloverlay.h index f76f66431..9bffc0397 100644 --- a/src/qt/modaloverlay.h +++ b/src/qt/modaloverlay.h @@ -1,52 +1,54 @@ // Copyright (c) 2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_QT_MODALOVERLAY_H #define BITCOIN_QT_MODALOVERLAY_H #include +#include #include //! The required delta of headers to the estimated number of available headers //! until we show the IBD progress static constexpr int HEADER_HEIGHT_DELTA_SYNC = 24; namespace Ui { class ModalOverlay; } /** Modal overlay to display information about the chain-sync state */ class ModalOverlay : public QWidget { Q_OBJECT public: explicit ModalOverlay(bool enable_wallet, QWidget *parent); ~ModalOverlay(); public Q_SLOTS: void tipUpdate(int count, const QDateTime &blockDate, double nVerificationProgress); void setKnownBestHeight(int count, const QDateTime &blockDate); void toggleVisibility(); // will show or hide the modal layer void showHide(bool hide = false, bool userRequested = false); void closeClicked(); bool isLayerVisible() const { return layerIsVisible; } protected: bool eventFilter(QObject *obj, QEvent *ev) override; bool event(QEvent *ev) override; private: Ui::ModalOverlay *ui; int bestHeaderHeight; // best known height (based on the headers) QDateTime bestHeaderDate; QVector> blockProcessTime; bool layerIsVisible; bool userClosed; + QPropertyAnimation m_animation; void UpdateHeaderSyncLabel(); }; #endif // BITCOIN_QT_MODALOVERLAY_H