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