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