modaloverlay.cpp raw

   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  }
 207