modaloverlay.cpp raw

   1  // Copyright (c) 2016-2022 The Limenka 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 <limenka-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  #include <qt/platformstyle.h>
  13  
  14  #include <QColor>
  15  #include <QEasingCurve>
  16  #include <QPalette>
  17  #include <QPropertyAnimation>
  18  #include <QResizeEvent>
  19  
  20  ModalOverlay::ModalOverlay(bool enable_wallet, const PlatformStyle& platform_style, QWidget* parent)
  21      : QWidget(parent),
  22        ui(new Ui::ModalOverlay),
  23        bestHeaderDate(QDateTime()),
  24        m_platform_style(platform_style)
  25  {
  26      ui->setupUi(this);
  27  
  28      // Add ThemedLabel for warning icon
  29      GUIUtil::ThemedLabel* warningIcon = new GUIUtil::ThemedLabel(&m_platform_style, this);
  30      warningIcon->setThemedPixmap(QStringLiteral(":/icons/warning"), 48, 48);
  31      ui->verticalLayoutIcon->insertWidget(0, warningIcon);
  32  
  33      connect(ui->closeButton, &QPushButton::clicked, this, &ModalOverlay::closeClicked);
  34      if (parent) {
  35          parent->installEventFilter(this);
  36          raise();
  37      }
  38      ui->closeButton->installEventFilter(this);
  39  
  40      blockProcessTime.clear();
  41      setVisible(false);
  42      if (!enable_wallet) {
  43          ui->infoText->setVisible(false);
  44          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));
  45      }
  46  
  47      m_animation.setTargetObject(this);
  48      m_animation.setPropertyName("pos");
  49      m_animation.setDuration(300 /* ms */);
  50      m_animation.setEasingCurve(QEasingCurve::OutQuad);
  51  
  52      updateThemeStyles();
  53  }
  54  
  55  ModalOverlay::~ModalOverlay()
  56  {
  57      delete ui;
  58  }
  59  
  60  bool ModalOverlay::eventFilter(QObject * obj, QEvent * ev) {
  61      if (obj == parent()) {
  62          if (ev->type() == QEvent::Resize) {
  63              QResizeEvent * rev = static_cast<QResizeEvent*>(ev);
  64              resize(rev->size());
  65              if (!layerIsVisible)
  66                  setGeometry(0, height(), width(), height());
  67  
  68              if (m_animation.endValue().toPoint().y() > 0) {
  69                  m_animation.setEndValue(QPoint(0, height()));
  70              }
  71          }
  72          else if (ev->type() == QEvent::ChildAdded) {
  73              raise();
  74          }
  75      }
  76  
  77      if (obj == ui->closeButton && ev->type() == QEvent::FocusOut && layerIsVisible) {
  78          ui->closeButton->setFocus(Qt::OtherFocusReason);
  79      }
  80  
  81      return QWidget::eventFilter(obj, ev);
  82  }
  83  
  84  //! Tracks parent widget changes
  85  bool ModalOverlay::event(QEvent* ev) {
  86      if (ev->type() == QEvent::ParentAboutToChange) {
  87          if (parent()) parent()->removeEventFilter(this);
  88      }
  89      else if (ev->type() == QEvent::ParentChange) {
  90          if (parent()) {
  91              parent()->installEventFilter(this);
  92              raise();
  93          }
  94      }
  95      return QWidget::event(ev);
  96  }
  97  
  98  void ModalOverlay::setKnownBestHeight(int count, const QDateTime& blockDate, bool presync)
  99  {
 100      if (!presync && count > bestHeaderHeight) {
 101          bestHeaderHeight = count;
 102          bestHeaderDate = blockDate;
 103          UpdateHeaderSyncLabel();
 104      }
 105      if (presync) {
 106          UpdateHeaderPresyncLabel(count, blockDate);
 107      }
 108  }
 109  
 110  void ModalOverlay::tipUpdate(int count, const QDateTime& blockDate, double nVerificationProgress)
 111  {
 112      QDateTime currentDate = QDateTime::currentDateTime();
 113  
 114      // keep a vector of samples of verification progress at height
 115      blockProcessTime.push_front(qMakePair(currentDate.toMSecsSinceEpoch(), nVerificationProgress));
 116  
 117      // show progress speed if we have more than one sample
 118      if (blockProcessTime.size() >= 2) {
 119          double progressDelta = 0;
 120          double progressPerHour = 0;
 121          qint64 timeDelta = 0;
 122          qint64 remainingMSecs = 0;
 123          double remainingProgress = 1.0 - nVerificationProgress;
 124          for (int i = 1; i < blockProcessTime.size(); i++) {
 125              QPair<qint64, double> sample = blockProcessTime[i];
 126  
 127              // take first sample after 500 seconds or last available one
 128              if (sample.first < (currentDate.toMSecsSinceEpoch() - 500 * 1000) || i == blockProcessTime.size() - 1) {
 129                  progressDelta = blockProcessTime[0].second - sample.second;
 130                  timeDelta = blockProcessTime[0].first - sample.first;
 131                  progressPerHour = (progressDelta > 0) ? progressDelta / (double)timeDelta * 1000 * 3600 : 0;
 132                  remainingMSecs = (progressDelta > 0) ? remainingProgress / progressDelta * timeDelta : -1;
 133                  break;
 134              }
 135          }
 136          // show progress increase per hour
 137          ui->progressIncreasePerH->setText(QString::number(progressPerHour * 100, 'f', 2)+"%");
 138  
 139          // show expected remaining time
 140          if(remainingMSecs >= 0) {
 141              ui->expectedTimeLeft->setText(GUIUtil::formatNiceTimeOffset(remainingMSecs / 1000.0));
 142          } else {
 143              ui->expectedTimeLeft->setText(QObject::tr("unknown"));
 144          }
 145  
 146          static const int MAX_SAMPLES = 5000;
 147          if (blockProcessTime.count() > MAX_SAMPLES) {
 148              blockProcessTime.remove(MAX_SAMPLES, blockProcessTime.count() - MAX_SAMPLES);
 149          }
 150      }
 151  
 152      // show the last block date
 153      ui->newestBlockDate->setText(blockDate.toString());
 154  
 155      // show the percentage done according to nVerificationProgress
 156      ui->percentageProgress->setText(QString::number(nVerificationProgress*100, 'f', 2)+"%");
 157  
 158      if (!bestHeaderDate.isValid())
 159          // not syncing
 160          return;
 161  
 162      // estimate the number of headers left based on nPowTargetSpacing
 163      // and check if the gui is not aware of the best header (happens rarely)
 164      int estimateNumHeadersLeft = bestHeaderDate.secsTo(currentDate) / Params().GetConsensus().nPowTargetSpacing;
 165      bool hasBestHeader = bestHeaderHeight >= count;
 166  
 167      // show remaining number of blocks
 168      if (estimateNumHeadersLeft < HEADER_HEIGHT_DELTA_SYNC && hasBestHeader) {
 169          ui->numberOfBlocksLeft->setText(QString::number(bestHeaderHeight - count));
 170      } else {
 171          UpdateHeaderSyncLabel();
 172          ui->expectedTimeLeft->setText(tr("Unknown…"));
 173      }
 174  }
 175  
 176  void ModalOverlay::UpdateHeaderSyncLabel() {
 177      int est_headers_left = bestHeaderDate.secsTo(QDateTime::currentDateTime()) / Params().GetConsensus().nPowTargetSpacing;
 178      const int pct = bestHeaderHeight ? static_cast<int>(1000LL * bestHeaderHeight / (bestHeaderHeight + est_headers_left)) : 0;
 179      ui->numberOfBlocksLeft->setText(tr("Unknown. Syncing Headers (%1, %2%)…").arg(bestHeaderHeight).arg(QStringLiteral("%1.%2").arg(pct / 10).arg(pct % 10)));
 180  }
 181  
 182  void ModalOverlay::UpdateHeaderPresyncLabel(int height, const QDateTime& blockDate) {
 183      int est_headers_left = blockDate.secsTo(QDateTime::currentDateTime()) / Params().GetConsensus().nPowTargetSpacing;
 184      const int pct = height ? static_cast<int>(1000LL * height / (height + est_headers_left)) : 0;
 185      ui->numberOfBlocksLeft->setText(tr("Unknown. Pre-syncing Headers (%1, %2%)…").arg(height).arg(QStringLiteral("%1.%2").arg(pct / 10).arg(pct % 10)));
 186  }
 187  
 188  void ModalOverlay::toggleVisibility()
 189  {
 190      showHide(layerIsVisible, true);
 191      if (!layerIsVisible)
 192          userClosed = true;
 193  }
 194  
 195  void ModalOverlay::showHide(bool hide, bool userRequested)
 196  {
 197      if ( (layerIsVisible && !hide) || (!layerIsVisible && hide) || (!hide && userClosed && !userRequested))
 198          return;
 199  
 200      Q_EMIT triggered(hide);
 201  
 202      if (!isVisible() && !hide)
 203          setVisible(true);
 204  
 205      m_animation.setStartValue(QPoint(0, hide ? 0 : height()));
 206      // The eventFilter() updates the endValue if it is required for QEvent::Resize.
 207      m_animation.setEndValue(QPoint(0, hide ? height() : 0));
 208      m_animation.start(QAbstractAnimation::KeepWhenStopped);
 209      layerIsVisible = !hide;
 210  
 211      if (layerIsVisible) {
 212          ui->closeButton->setFocus(Qt::OtherFocusReason);
 213      }
 214  }
 215  
 216  void ModalOverlay::closeClicked()
 217  {
 218      showHide(true);
 219      userClosed = true;
 220  }
 221  
 222  void ModalOverlay::changeEvent(QEvent* e)
 223  {
 224      if (e->type() == QEvent::PaletteChange) {
 225          updateThemeStyles();
 226      }
 227      QWidget::changeEvent(e);
 228  }
 229  
 230  void ModalOverlay::updateThemeStyles()
 231  {
 232      const QColor bg_colour = palette().color(backgroundRole());
 233      const bool dark_mode = GUIUtil::isDarkMode(bg_colour);
 234  
 235      // Overlay background - both light and dark mode use black but with different alpha
 236      QColor overlayBg = dark_mode ? QColor(0, 0, 0, 240) : QColor(0, 0, 0, 220);
 237      const QString bgStyle = QStringLiteral("#bgWidget { background: ") + overlayBg.name(QColor::HexArgb) + QStringLiteral("; }");
 238      ui->bgWidget->setStyleSheet(bgStyle);
 239  
 240      // Content widget with system window background, slightly transparent
 241      QColor contentBg = QColor(bg_colour.red(), bg_colour.green(), bg_colour.blue(), 240);
 242      const QString contentStyle = QStringLiteral("#contentWidget { background: ") + contentBg.name(QColor::HexArgb) + QStringLiteral("; border-radius: 6px; }");
 243      ui->contentWidget->setStyleSheet(contentStyle);
 244  }
 245