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