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