splashscreen.cpp raw

   1  // Copyright (c) 2011-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/splashscreen.h>
   8  
   9  #include <clientversion.h>
  10  #include <common/system.h>
  11  #include <interfaces/handler.h>
  12  #include <interfaces/node.h>
  13  #include <interfaces/wallet.h>
  14  #include <qt/guiutil.h>
  15  #include <qt/networkstyle.h>
  16  #include <qt/walletmodel.h>
  17  #include <util/translation.h>
  18  
  19  #include <functional>
  20  
  21  #include <Qt>
  22  #include <QtGlobal>
  23  #include <QApplication>
  24  #include <QCloseEvent>
  25  #include <QPainter>
  26  #include <QRadialGradient>
  27  #include <QScreen>
  28  
  29  
  30  SplashScreen::SplashScreen(const NetworkStyle* networkStyle)
  31      : QWidget()
  32  {
  33      // set reference point, paddings
  34      int paddingRight            = 50;
  35      int titleVersionVSpace      = 17;
  36      int titleCopyrightVSpace    = 40;
  37  
  38      float fontFactor            = 1.0;
  39      float devicePixelRatio      = 1.0;
  40      devicePixelRatio = static_cast<QGuiApplication*>(QCoreApplication::instance())->devicePixelRatio();
  41  
  42      // define text to place
  43      QString titleText       = CLIENT_NAME;
  44      QString versionText     = QString("Version %1").arg(QString::fromStdString(FormatFullVersion()));
  45      QString copyrightText   = QString::fromUtf8(CopyrightHolders(strprintf("\xc2\xA9 %u-%u ", 2009, COPYRIGHT_YEAR)).c_str());
  46      const QString& titleAddText    = networkStyle->getTitleAddText();
  47  
  48      QString font            = GUIUtil::fixedPitchFont(/*use_embedded_font=*/ true).toString();
  49  
  50      // create a bitmap according to device pixelratio
  51      QSize splashSize(480*devicePixelRatio,320*devicePixelRatio);
  52      pixmap = QPixmap(splashSize);
  53  
  54      // change to HiDPI if it makes sense
  55      pixmap.setDevicePixelRatio(devicePixelRatio);
  56  
  57      QPainter pixPaint(&pixmap);
  58      pixPaint.setPen(QColor(0x17, 0x17, 0x17));
  59  
  60      // draw a slightly radial gradient
  61      QRadialGradient gradient(QPoint(0,0), splashSize.width()/devicePixelRatio);
  62      gradient.setColorAt(0, networkStyle->AdjustColour(QColor(0xde, 0x7e, 0x11)));
  63      gradient.setColorAt(1, networkStyle->AdjustColour(QColor(0xfe, 0xaa, 0x35)));
  64      QRect rGradient(QPoint(0,0), splashSize);
  65      pixPaint.fillRect(rGradient, gradient);
  66  
  67      // draw the limenka icon, expected size of PNG: 1024x1024
  68      const QPoint nonstatus_centre(splashSize.width() / 2 / devicePixelRatio, (splashSize.height() - (3 * QFontMetrics(font).lineSpacing() / 2)) / 2 / devicePixelRatio);
  69      const int icon_top{nonstatus_centre.y() - (nonstatus_centre.x() / 2)};
  70      QRect rectIcon(QPoint(0, icon_top), QSize(nonstatus_centre.x(), nonstatus_centre.x()));
  71  
  72      QPixmap icon(":/icons/splash");
  73      icon = icon.scaledToWidth(nonstatus_centre.x() * devicePixelRatio, Qt::SmoothTransformation);
  74  
  75      pixPaint.drawPixmap(rectIcon, icon);
  76  
  77      // check font size and drawing with
  78      QStringList titleParts = titleText.split(' ');
  79      assert(titleParts.size() == 2);
  80      pixPaint.setFont(QFont(font, 33*fontFactor));
  81      QFontMetrics fm = pixPaint.fontMetrics();
  82      int titleTextWidth = GUIUtil::TextWidth(fm, titleParts[0]);
  83      pixPaint.setFont(QFont(font, 50*fontFactor));
  84      fm = pixPaint.fontMetrics();
  85      titleTextWidth = qMax(titleTextWidth, GUIUtil::TextWidth(fm, titleParts[1]));
  86      const int titleTextMaxWidth{nonstatus_centre.x() - 10 - paddingRight};
  87      if (titleTextWidth > titleTextMaxWidth) {
  88          fontFactor = fontFactor * titleTextMaxWidth / titleTextWidth;
  89          titleTextWidth = titleTextMaxWidth;
  90      }
  91  
  92      // pixPaint.setBackgroundMode(Qt::OpaqueMode);  // TODO
  93      // pixPaint.setBackground(QBrush(QColor(255, 0, 0)));  // TODO
  94      pixPaint.setFont(QFont(font, 50*fontFactor));
  95      fm = pixPaint.fontMetrics();
  96      int titleTextWidth2{GUIUtil::TextWidth(fm, titleParts[1]) - 5};
  97      if (titleTextWidth != titleTextWidth2) {
  98          QFont tweaked_font(font, 50*fontFactor);
  99          tweaked_font.setPointSizeF(50.*fontFactor * titleTextWidth / titleTextWidth2);
 100          pixPaint.setFont(tweaked_font);
 101          fm = pixPaint.fontMetrics();
 102      }
 103      pixPaint.drawText(nonstatus_centre.x() + 3, nonstatus_centre.y(), titleParts[1]);
 104      const int titleTextHeight2{fm.ascent()};
 105  
 106      pixPaint.setFont(QFont(font, 33*fontFactor));
 107      fm = pixPaint.fontMetrics();
 108      titleTextWidth2 = GUIUtil::TextWidth(fm, titleParts[0]);
 109      if (titleTextWidth != titleTextWidth2) {
 110          QFont tweaked_font(font, 33*fontFactor);
 111          tweaked_font.setPointSizeF(33.*fontFactor * titleTextWidth / titleTextWidth2);
 112          pixPaint.setFont(tweaked_font);
 113      }
 114      pixPaint.drawText(nonstatus_centre.x() + 5, nonstatus_centre.y() - titleTextHeight2, titleParts[0]);
 115      // pixPaint.drawLine(nonstatus_centre.x()+5, nonstatus_centre.y(), nonstatus_centre.x()+5+titleTextWidth, nonstatus_centre.y()); // TODO
 116      // pixPaint.drawLine(nonstatus_centre.x()+5, nonstatus_centre.y() - titleTextHeight2, nonstatus_centre.x()+5+titleTextWidth, nonstatus_centre.y() - titleTextHeight2); // TODO
 117  
 118      pixPaint.setFont(QFont(font, 15*fontFactor));
 119  
 120      // if the version string is too long, reduce size
 121      fm = pixPaint.fontMetrics();
 122      int versionTextWidth  = GUIUtil::TextWidth(fm, versionText);
 123      int versionTextMaxWidth{titleTextWidth + paddingRight - 20};
 124      if(versionTextWidth > versionTextMaxWidth) {
 125          pixPaint.setFont(QFont(font, 15 * fontFactor * versionTextMaxWidth / versionTextWidth));
 126          fm = pixPaint.fontMetrics();
 127      }
 128      titleVersionVSpace = fm.lineSpacing() * 2;
 129      pixPaint.drawText(nonstatus_centre.x() + 10, nonstatus_centre.y() + titleVersionVSpace, versionText);
 130  
 131      // draw copyright stuff
 132      {
 133          pixPaint.setFont(QFont(QApplication::font().toString(), 10*fontFactor));
 134          fm = pixPaint.fontMetrics();
 135          titleCopyrightVSpace = titleVersionVSpace + (fm.lineSpacing() * (2 + copyrightText.count('\n')));
 136          const int x = nonstatus_centre.x() + 10;
 137          const int y = nonstatus_centre.y() + titleCopyrightVSpace;
 138          QRect copyrightRect(x, y, pixmap.width() - x, pixmap.height() - y);
 139          pixPaint.drawText(copyrightRect, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, copyrightText);
 140      }
 141  
 142      // draw additional text if special network
 143      if(!titleAddText.isEmpty()) {
 144          QFont boldFont = QFont(font, 10*fontFactor);
 145          boldFont.setWeight(QFont::Bold);
 146          pixPaint.setFont(boldFont);
 147          fm = pixPaint.fontMetrics();
 148          int titleAddTextWidth  = GUIUtil::TextWidth(fm, titleAddText);
 149          pixPaint.drawText(pixmap.width()/devicePixelRatio-titleAddTextWidth-10,15,titleAddText);
 150      }
 151  
 152      pixPaint.end();
 153  
 154      // Set window title
 155      setWindowTitle(titleText + " " + titleAddText);
 156  
 157      // Resize window and move to center of desktop, disallow resizing
 158      QRect r(QPoint(), QSize(pixmap.size().width()/devicePixelRatio,pixmap.size().height()/devicePixelRatio));
 159      resize(r.size());
 160      setFixedSize(r.size());
 161      move(QGuiApplication::primaryScreen()->geometry().center() - r.center());
 162  
 163      installEventFilter(this);
 164  
 165      GUIUtil::handleCloseWindowShortcut(this);
 166  }
 167  
 168  SplashScreen::~SplashScreen()
 169  {
 170      if (m_node) unsubscribeFromCoreSignals();
 171  }
 172  
 173  void SplashScreen::setNode(interfaces::Node& node)
 174  {
 175      assert(!m_node);
 176      m_node = &node;
 177      subscribeToCoreSignals();
 178      if (m_shutdown) m_node->startShutdown();
 179  }
 180  
 181  void SplashScreen::shutdown()
 182  {
 183      m_shutdown = true;
 184      if (m_node) m_node->startShutdown();
 185  }
 186  
 187  bool SplashScreen::eventFilter(QObject * obj, QEvent * ev) {
 188      if (ev->type() == QEvent::KeyPress) {
 189          QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev);
 190          if (keyEvent->key() == Qt::Key_Q) {
 191              shutdown();
 192          }
 193      }
 194      return QObject::eventFilter(obj, ev);
 195  }
 196  
 197  static void InitMessage(SplashScreen *splash, const std::string &message)
 198  {
 199      bool invoked = QMetaObject::invokeMethod(splash, "showMessage",
 200          Qt::QueuedConnection,
 201          Q_ARG(QString, QString::fromStdString(message)),
 202          Q_ARG(int, Qt::AlignBottom|Qt::AlignHCenter),
 203          Q_ARG(QColor, QColor(55,55,55)));
 204      assert(invoked);
 205  }
 206  
 207  static void ShowProgress(SplashScreen *splash, const std::string &title, int nProgress, bool resume_possible)
 208  {
 209      InitMessage(splash, title + std::string("\n") +
 210              (resume_possible ? SplashScreen::tr("(press q to shutdown and continue later)").toStdString()
 211                                  : SplashScreen::tr("press q to shutdown").toStdString()) +
 212              strprintf("\n%d", nProgress) + "%");
 213  }
 214  
 215  void SplashScreen::subscribeToCoreSignals()
 216  {
 217      // Connect signals to client
 218      m_handler_init_message = m_node->handleInitMessage(std::bind(InitMessage, this, std::placeholders::_1));
 219      m_handler_show_progress = m_node->handleShowProgress(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
 220      m_handler_init_wallet = m_node->handleInitWallet([this]() { handleLoadWallet(); });
 221  }
 222  
 223  void SplashScreen::handleLoadWallet()
 224  {
 225  #ifdef ENABLE_WALLET
 226      if (!WalletModel::isWalletEnabled()) return;
 227      m_handler_load_wallet = m_node->walletLoader().handleLoadWallet([this](std::unique_ptr<interfaces::Wallet> wallet) {
 228          m_connected_wallet_handlers.emplace_back(wallet->handleShowProgress(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2, false)));
 229          m_connected_wallets.emplace_back(std::move(wallet));
 230      });
 231  #endif
 232  }
 233  
 234  void SplashScreen::unsubscribeFromCoreSignals()
 235  {
 236      // Disconnect signals from client
 237      m_handler_init_message->disconnect();
 238      m_handler_show_progress->disconnect();
 239      for (const auto& handler : m_connected_wallet_handlers) {
 240          handler->disconnect();
 241      }
 242      m_connected_wallet_handlers.clear();
 243      m_connected_wallets.clear();
 244  }
 245  
 246  void SplashScreen::showMessage(const QString &message, int alignment, const QColor &color)
 247  {
 248      curMessage = message;
 249      curAlignment = alignment;
 250      curColor = color;
 251      update();
 252  }
 253  
 254  void SplashScreen::paintEvent(QPaintEvent *event)
 255  {
 256      QPainter painter(this);
 257      painter.drawPixmap(0, 0, pixmap);
 258      QFont font = GUIUtil::fixedPitchFont(/*use_embedded_font=*/ true);
 259      painter.setFont(font);
 260      QRect r = rect().adjusted(5, 5, -5, -5 - QFontMetrics(font).height() / 4);
 261      painter.setPen(curColor);
 262      painter.drawText(r, curAlignment, curMessage);
 263  }
 264  
 265  void SplashScreen::closeEvent(QCloseEvent *event)
 266  {
 267      shutdown(); // allows an "emergency" shutdown during startup
 268      event->ignore();
 269  }
 270