Loading...
Searching...
No Matches
StartScreen.hpp
1#pragma once
2#include <score/model/Skin.hpp>
3#include <score/widgets/Pixmap.hpp>
4
5#include <core/view/QRecentFilesMenu.h>
6
7#include <QApplication>
8#include <QDesktopServices>
9#include <QKeyEvent>
10#include <QLabel>
11#include <QNetworkAccessManager>
12#include <QNetworkReply>
13#include <QNetworkRequest>
14#include <QPainter>
15#include <QPixmap>
16#include <QPointer>
17#include <QSettings>
18#include <QTextLayout>
19
20#include <score_git_info.hpp>
21
22#include <optional>
23#include <verdigris>
24
25namespace score
26{
27namespace
28{
29template <typename OnSuccess, typename OnError>
30class HTTPGet final : public QNetworkAccessManager
31{
32public:
33 explicit HTTPGet(QUrl url, OnSuccess on_success, OnError on_error) noexcept
34 : m_callback{std::move(on_success)}
35 , m_error{std::move(on_error)}
36 {
37 connect(this, &QNetworkAccessManager::finished, this, [this](QNetworkReply* reply) {
38 if(reply->error())
39 {
40 qDebug() << reply->errorString();
41 m_error();
42 }
43 else
44 {
45 m_callback(reply->readAll());
46 }
47
48 reply->deleteLater();
49 this->deleteLater();
50 });
51
52 QNetworkRequest req{std::move(url)};
53 req.setRawHeader("User-Agent", "curl/7.35.0");
54
55 req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
56 req.setAttribute(
57 QNetworkRequest::RedirectPolicyAttribute,
58 QNetworkRequest::UserVerifiedRedirectPolicy);
59 req.setAttribute(QNetworkRequest::Http2AllowedAttribute, true);
60
61 auto reply = get(req);
62 connect(reply, &QNetworkReply::redirected, reply, &QNetworkReply::redirectAllowed);
63 }
64
65private:
66 OnSuccess m_callback;
67 OnError m_error;
68};
69
70}
71class InteractiveLabel : public QWidget
72{
73 W_OBJECT(InteractiveLabel)
74
75public:
77 const QFont& font, const QString& text, const QString url, QWidget* parent = 0);
78
79 void setOpenExternalLink(bool val) { m_openExternalLink = val; }
80 void setPixmaps(const QPixmap& pixmap, const QPixmap& pixmapOn);
81
82 void disableInteractivity();
83 void setActiveColor(const QColor& c);
84 void setInactiveColor(const QColor& c);
85 void setTextOption(QTextOption opt) { m_textOption = opt; }
86
87 void labelPressed(const QString& file) W_SIGNAL(labelPressed, file)
88
89 QRectF textBoundingBox(double width) const noexcept;
90
91protected:
92 void paintEvent(QPaintEvent* event) override;
93 void enterEvent(QEnterEvent* event) override;
94 void leaveEvent(QEvent* event) override;
95 void mousePressEvent(QMouseEvent* event) override;
96
97private:
98 QFont m_font;
99 QString m_title;
100
101 QString m_url;
102 bool m_openExternalLink{};
103
104 bool m_drawPixmap{};
105 QPixmap m_currentPixmap;
106 QPixmap m_pixmap;
107 QPixmap m_pixmapOn;
108
109 bool m_interactive{};
110 QColor m_currentColor;
111 QColor m_activeColor;
112 QColor m_inactiveColor;
113
114 QTextOption m_textOption;
115};
116
117InteractiveLabel::InteractiveLabel(
118 const QFont& font, const QString& title, const QString url, QWidget* parent)
119 : QWidget{parent}
120 , m_font(font)
121 , m_title(title)
122 , m_url(url)
123 , m_openExternalLink(false)
124 , m_drawPixmap(false)
125 , m_interactive(true)
126{
127 auto& skin = score::Skin::instance();
128 m_inactiveColor = QColor{"#d3d3d3"}; //"#f0f0f0"};
129 m_activeColor = QColor{"#03C3DD"};
130 m_currentColor = m_inactiveColor;
131 setCursor(skin.CursorPointingHand);
132 setFixedSize(200, 34);
133}
134
135void InteractiveLabel::setPixmaps(const QPixmap& pixmap, const QPixmap& pixmapOn)
136{
137 m_drawPixmap = true;
138 m_pixmap = pixmap;
139 m_pixmapOn = pixmapOn;
140 m_currentPixmap = m_pixmap;
141}
142
143void InteractiveLabel::disableInteractivity()
144{
145 m_interactive = false;
146 setCursor(Qt::ArrowCursor);
147}
148
149void InteractiveLabel::setActiveColor(const QColor& c)
150{
151 m_activeColor = c;
152}
153
154void InteractiveLabel::setInactiveColor(const QColor& c)
155{
156 m_inactiveColor = c;
157 m_currentColor = m_inactiveColor;
158}
159
160QRectF InteractiveLabel::textBoundingBox(double width) const noexcept
161{
162 int leading = QFontMetrics{m_font}.leading();
163 QTextLayout lay;
164 lay.setText(m_title);
165 lay.setFont(m_font);
166 lay.setTextOption(m_textOption);
167 lay.beginLayout();
168 double height{};
169 while(true)
170 {
171 QTextLine line = lay.createLine();
172 if(!line.isValid())
173 break;
174
175 line.setLineWidth(width);
176 height += leading;
177 line.setPosition(QPointF(0, height));
178 height += line.height();
179 }
180 lay.endLayout();
181 return lay.boundingRect();
182}
183
184void InteractiveLabel::paintEvent(QPaintEvent* event)
185{
186 QPainter painter(this);
187
188 painter.setRenderHint(QPainter::Antialiasing, true);
189 painter.setRenderHint(QPainter::TextAntialiasing, true);
190 painter.setRenderHint(QPainter::RenderHint::SmoothPixmapTransform);
191 painter.setPen(QPen{m_currentColor});
192
193 painter.save();
194 QRectF textRect = rect();
195 if(m_drawPixmap)
196 {
197 int size = m_currentPixmap.width() / qApp->devicePixelRatio();
198 int pixmapX = 0;
199
200 if(m_textOption.alignment() == Qt::AlignRight)
201 {
202 QTextLayout lay;
203 lay.setText(m_title);
204 lay.setFont(m_font);
205 lay.setTextOption(m_textOption);
206 lay.beginLayout();
207 lay.createLine();
208 lay.endLayout();
209 pixmapX = lay.boundingRect().width();
210 }
211 painter.drawPixmap(
212 width() - pixmapX - m_currentPixmap.width() - 6,
213 (textRect.height() - size - 10) / 2, m_currentPixmap);
214 textRect.setX(textRect.x() + size + 6);
215 }
216 painter.setFont(m_font);
217 painter.drawText(textRect, m_title, m_textOption);
218 painter.restore();
219}
220
221void InteractiveLabel::enterEvent(QEnterEvent* event)
222{
223 if(!m_interactive)
224 return;
225
226 m_currentColor = m_activeColor;
227 m_font.setUnderline(true);
228 m_currentPixmap = m_pixmapOn;
229
230 repaint();
231}
232
233void InteractiveLabel::leaveEvent(QEvent* event)
234{
235 if(!m_interactive)
236 return;
237
238 m_currentColor = m_inactiveColor;
239 m_font.setUnderline(false);
240 m_currentPixmap = m_pixmap;
241
242 repaint();
243}
244
245void InteractiveLabel::mousePressEvent(QMouseEvent* event)
246{
247 if(m_openExternalLink)
248 {
249 QDesktopServices::openUrl(QUrl(m_url));
250 }
251 else
252 {
253 labelPressed(m_url);
254 }
255}
256class StartScreen : public QWidget
257{
258 W_OBJECT(StartScreen)
259public:
260 StartScreen(const QPointer<QRecentFilesMenu>& recentFiles, QWidget* parent = 0);
261
262 void openNewDocument() W_SIGNAL(openNewDocument)
263 void openFile(const QString& file) W_SIGNAL(openFile, file)
264 void openFileDialog() W_SIGNAL(openFileDialog)
265 void loadCrashedSession() W_SIGNAL(loadCrashedSession)
266 void exitApp() W_SIGNAL(exitApp)
267
268 void addLoadCrashedSession();
269
270protected:
271 void paintEvent(QPaintEvent* event) override;
272 void keyPressEvent(QKeyEvent* event) override;
273
274private:
275 QPixmap m_background;
276 InteractiveLabel* m_crashLabel{};
277};
279{
280 QString name;
281 QString url;
282 QString pixmap;
283 QString pixmapOn;
285 const QString& n, const QString& u, const QString& p, const QString& pOn)
286 : name(n)
287 , url(u)
288 , pixmap(p)
289 , pixmapOn(pOn)
290 {
291 }
292};
293
294StartScreen::StartScreen(const QPointer<QRecentFilesMenu>& recentFiles, QWidget* parent)
295 : QWidget(parent)
296{
297// Workaround until https://bugreports.qt.io/browse/QTBUG-103225 is fixed
298#if defined(__APPLE__)
299 static constexpr double font_factor = 96. / 72.;
300#else
301 static constexpr double font_factor = 1.;
302#endif
303 QFont f("Ubuntu", 14 * font_factor, QFont::Light);
304 f.setHintingPreference(QFont::HintingPreference::PreferFullHinting);
305 f.setStyleStrategy(QFont::PreferAntialias);
306
307 QFont titleFont("Montserrat", 14 * font_factor, QFont::DemiBold);
308 titleFont.setHintingPreference(QFont::HintingPreference::PreferFullHinting);
309 titleFont.setStyleStrategy(QFont::PreferAntialias);
310
311 this->setEnabled(true);
312 setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint); //| Qt::WindowStaysOnTopHint);
313 setWindowModality(Qt::ApplicationModal);
314
315 m_background = score::get_pixmap(":/startscreen/startscreensplash.png");
316
317 if(QPainter painter; painter.begin(&m_background))
318 {
319 painter.setRenderHint(QPainter::Antialiasing, true);
320 painter.setRenderHint(QPainter::TextAntialiasing, true);
321 painter.setPen(QPen(QColor("#0092CF")));
322
323 painter.setFont(f);
324 //painter.drawText(QPointF(250, 170), QCoreApplication::applicationVersion());
325 painter.drawText(QPointF(217, 188), QCoreApplication::applicationVersion());
326 painter.end();
327 }
328
329 // Weird code here is because the window size seems to scale only to integer ratios.
330 setFixedSize(m_background.size() / m_background.devicePixelRatioF());
331
332 {
333 // new version
334 auto m_getLastVersion = new HTTPGet{
335 QUrl("https://ossia.io/score-last-version.txt"),
336 [this, titleFont](const QByteArray& data) {
337 auto version = QString::fromUtf8(data.simplified());
338 if(SCORE_TAG_NO_V < version)
339 {
340 QString text
341 = qApp->tr("New version %1 is available, click to update !").arg(version);
342 QString url = "https://github.com/ossia/score/releases/latest/";
343 InteractiveLabel* label = new InteractiveLabel{titleFont, text, url, this};
344 label->setPixmaps(
345 score::get_pixmap(":/icons/version_on.png"),
346 score::get_pixmap(":/icons/version_off.png"));
347 label->setOpenExternalLink(true);
348 label->setInactiveColor(QColor{"#f6a019"});
349 label->setActiveColor(QColor{"#f0f0f0"});
350 label->setFixedWidth(600);
351 label->move(280, 170);
352 label->show();
353 }
354 }, [] {}};
355 }
356
357 float label_x = 300;
358 float label_y = 215;
359
360 { // recent files
361 auto label = new InteractiveLabel{titleFont, qApp->tr("Recent files"), "", this};
362 label->setTextOption(QTextOption(Qt::AlignRight));
363 label->setPixmaps(
364 score::get_pixmap(":/icons/recent_files.png"),
365 score::get_pixmap(":/icons/recent_files.png"));
366 label->setInactiveColor(QColor{"#f0f0f0"});
367 label->setActiveColor(QColor{"#03C3DD"});
368 label->disableInteractivity();
369 label->move(label_x, label_y);
370 label_y += 35;
371 }
372 f.setPointSize(12);
373
374 // label_x += 40;
375 for(const auto& action : recentFiles->actions())
376 {
377 auto fileLabel
378 = new InteractiveLabel{f, action->text(), action->data().toString(), this};
379 fileLabel->setTextOption(QTextOption(Qt::AlignRight));
380 connect(
381 fileLabel, &score::InteractiveLabel::labelPressed, this,
382 &score::StartScreen::openFile);
383
384 auto textHeight = std::max(25, (int)fileLabel->textBoundingBox(200).height() + 7);
385 fileLabel->setFixedSize(200, textHeight);
386 fileLabel->move(label_x, label_y);
387 label_y += textHeight + 1;
388 }
389 // label_x = 160;
390 label_y += 10;
391
392 m_crashLabel
393 = new InteractiveLabel{titleFont, qApp->tr("Restore last session"), "", this};
394 m_crashLabel->setTextOption(QTextOption(Qt::AlignRight));
395 m_crashLabel->setPixmaps(
396 score::get_pixmap(":/icons/reload_crash_off.png"),
397 score::get_pixmap(":/icons/reload_crash_on.png"));
398 m_crashLabel->move(label_x - 100, label_y);
399 m_crashLabel->setFixedWidth(300);
400 m_crashLabel->setInactiveColor(QColor{"#f0f0f0"});
401 m_crashLabel->setActiveColor(QColor{"#f6a019"});
402 m_crashLabel->setDisabled(true);
403 m_crashLabel->hide();
404 connect(
405 m_crashLabel, &score::InteractiveLabel::labelPressed, this,
406 &score::StartScreen::loadCrashedSession);
407
408 label_x = 510;
409 label_y = 215;
410 { // Create new
411 InteractiveLabel* label = new InteractiveLabel{titleFont, qApp->tr("New"), "", this};
412 label->setPixmaps(
413 score::get_pixmap(":/icons/new_file_off.png"),
414 score::get_pixmap(":/icons/new_file_on.png"));
415 connect(
416 label, &score::InteractiveLabel::labelPressed, this,
417 &score::StartScreen::openNewDocument);
418 label->move(label_x, label_y);
419 label_y += 35;
420 }
421 { // Load file
422 InteractiveLabel* label
423 = new InteractiveLabel{titleFont, qApp->tr("Load"), "", this};
424 label->setPixmaps(
425 score::get_pixmap(":/icons/load_off.png"),
426 score::get_pixmap(":/icons/load_on.png"));
427 connect(
428 label, &score::InteractiveLabel::labelPressed, this,
429 &score::StartScreen::openFileDialog);
430 label->move(label_x, label_y);
431 label_y += 35;
432 }
433 { // Load Examples
434 QSettings settings;
435 auto library_path = settings.value("Library/RootPath").toString();
436 InteractiveLabel* label = new InteractiveLabel{
437 titleFont, qApp->tr("Examples"), "https://github.com/ossia/score-examples", this};
438 label->setPixmaps(
439 score::get_pixmap(":/icons/load_examples_off.png"),
440 score::get_pixmap(":/icons/load_examples_on.png"));
441 label->setOpenExternalLink(true);
442 label->move(label_x, label_y);
443 label_y += 35;
444 }
445
446 label_y += 20;
447
448 std::array<score::StartScreenLink, 4> menus
449 = {{{qApp->tr("Tutorials"),
450 "https://www.youtube.com/"
451 "watch?v=R-3d8K6gQkw&list=PLIHLSiZpIa6YoY1_aW1yetDgZ7tZcxfEC&index=1",
452 ":/icons/tutorials_off.png", ":/icons/tutorials_on.png"},
453 {qApp->tr("Contribute"), "https://opencollective.com/ossia/contribute",
454 ":/icons/contribute_off.png", ":/icons/contribute_on.png"},
455 {qApp->tr("Forum"), "http://forum.ossia.io/", ":/icons/forum_off.png",
456 ":/icons/forum_on.png"},
457 {qApp->tr("Chat"), "https://gitter.im/ossia/score", ":/icons/chat_off.png",
458 ":/icons/chat_on.png"}}};
459 for(const auto& m : menus)
460 {
461 InteractiveLabel* menu_url = new InteractiveLabel{titleFont, m.name, m.url, this};
462 menu_url->setOpenExternalLink(true);
463 menu_url->setPixmaps(score::get_pixmap(m.pixmap), score::get_pixmap(m.pixmapOn));
464 menu_url->move(label_x, label_y);
465 label_y += 35;
466 }
467
468 label_y += 10;
469
470 { // Exit App
471 InteractiveLabel* label
472 = new InteractiveLabel{titleFont, qApp->tr("Exit"), "", this};
473 label->setPixmaps(
474 score::get_pixmap(":/icons/exit_off.png"),
475 score::get_pixmap(":/icons/exit_on.png"));
476 connect(
477 label, &score::InteractiveLabel::labelPressed, this,
478 &score::StartScreen::exitApp);
479 label->move(label_x, label_y);
480 }
481}
482
483void StartScreen::addLoadCrashedSession()
484{
485 m_crashLabel->show();
486 m_crashLabel->setEnabled(true);
487 update();
488}
489
490void StartScreen::paintEvent(QPaintEvent* event)
491{
492 QPainter painter(this);
493 painter.setRenderHint(QPainter::RenderHint::SmoothPixmapTransform);
494 painter.drawPixmap(0, 0, m_background);
495}
496
497void StartScreen::keyPressEvent(QKeyEvent* event)
498{
499 if(event->key() == Qt::Key_Escape)
500 {
501 this->openNewDocument();
502 this->close();
503 return QWidget::keyPressEvent(event);
504 }
505}
506}
Definition StartScreen.hpp:72
Definition StartScreen.hpp:257
Base toolkit upon which the software is built.
Definition Application.cpp:90
void setCursor(Qt::CursorShape c)
setCursor sets the cursor safely.
Definition ClearLayout.cpp:8