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