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.setPen(QPen{m_currentColor});
191
192 painter.save();
193 QRectF textRect = rect();
194 if(m_drawPixmap)
195 {
196 int size = m_currentPixmap.width() / qApp->devicePixelRatio();
197 int pixmapX = 0;
198
199 if(m_textOption.alignment() == Qt::AlignRight)
200 {
201 QTextLayout lay;
202 lay.setText(m_title);
203 lay.setFont(m_font);
204 lay.setTextOption(m_textOption);
205 lay.beginLayout();
206 lay.createLine();
207 lay.endLayout();
208 pixmapX = lay.boundingRect().width();
209 }
210 painter.drawPixmap(
211 width() - pixmapX - m_currentPixmap.width() - 6,
212 (textRect.height() - size - 10) / 2, m_currentPixmap);
213 textRect.setX(textRect.x() + size + 6);
214 }
215 painter.setFont(m_font);
216 painter.drawText(textRect, m_title, m_textOption);
217 painter.restore();
218}
219
220void InteractiveLabel::enterEvent(QEnterEvent* event)
221{
222 if(!m_interactive)
223 return;
224
225 m_currentColor = m_activeColor;
226 m_font.setUnderline(true);
227 m_currentPixmap = m_pixmapOn;
228
229 repaint();
230}
231
232void InteractiveLabel::leaveEvent(QEvent* event)
233{
234 if(!m_interactive)
235 return;
236
237 m_currentColor = m_inactiveColor;
238 m_font.setUnderline(false);
239 m_currentPixmap = m_pixmap;
240
241 repaint();
242}
243
244void InteractiveLabel::mousePressEvent(QMouseEvent* event)
245{
246 if(m_openExternalLink)
247 {
248 QDesktopServices::openUrl(QUrl(m_url));
249 }
250 else
251 {
252 labelPressed(m_url);
253 }
254}
255class StartScreen : public QWidget
256{
257 W_OBJECT(StartScreen)
258public:
259 StartScreen(const QPointer<QRecentFilesMenu>& recentFiles, QWidget* parent = 0);
260
261 void openNewDocument() W_SIGNAL(openNewDocument)
262 void openFile(const QString& file) W_SIGNAL(openFile, file)
263 void openFileDialog() W_SIGNAL(openFileDialog)
264 void loadCrashedSession() W_SIGNAL(loadCrashedSession)
265 void exitApp() W_SIGNAL(exitApp)
266
267 void addLoadCrashedSession();
268
269protected:
270 void paintEvent(QPaintEvent* event) override;
271 void keyPressEvent(QKeyEvent* event) override;
272
273private:
274 QPixmap m_background;
275 InteractiveLabel* m_crashLabel{};
276};
278{
279 QString name;
280 QString url;
281 QString pixmap;
282 QString pixmapOn;
284 const QString& n, const QString& u, const QString& p, const QString& pOn)
285 : name(n)
286 , url(u)
287 , pixmap(p)
288 , pixmapOn(pOn)
289 {
290 }
291};
292
293StartScreen::StartScreen(const QPointer<QRecentFilesMenu>& recentFiles, QWidget* parent)
294 : QWidget(parent)
295{
296// Workaround until https://bugreports.qt.io/browse/QTBUG-103225 is fixed
297#if defined(__APPLE__)
298 static constexpr double font_factor = 96. / 72.;
299#else
300 static constexpr double font_factor = 1.;
301#endif
302 QFont f("Ubuntu", 14 * font_factor, QFont::Light);
303 f.setHintingPreference(QFont::HintingPreference::PreferFullHinting);
304 f.setStyleStrategy(QFont::PreferAntialias);
305
306 QFont titleFont("Montserrat", 14 * font_factor, QFont::DemiBold);
307 titleFont.setHintingPreference(QFont::HintingPreference::PreferFullHinting);
308 titleFont.setStyleStrategy(QFont::PreferAntialias);
309
310 this->setEnabled(true);
311 setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint); //| Qt::WindowStaysOnTopHint);
312 setWindowModality(Qt::ApplicationModal);
313
314 m_background = score::get_pixmap(":/startscreen/startscreensplash.png");
315
316 if(QPainter painter; painter.begin(&m_background))
317 {
318 painter.setRenderHint(QPainter::Antialiasing, true);
319 painter.setRenderHint(QPainter::TextAntialiasing, true);
320 painter.setPen(QPen(QColor("#0092CF")));
321
322 painter.setFont(f);
323 //painter.drawText(QPointF(250, 170), QCoreApplication::applicationVersion());
324 painter.drawText(QPointF(217, 188), QCoreApplication::applicationVersion());
325 painter.end();
326 }
327
328 // Weird code here is because the window size seems to scale only to integer ratios.
329 setFixedSize(m_background.size() / std::floor(qApp->devicePixelRatio()));
330
331 {
332 // new version
333 auto m_getLastVersion = new HTTPGet{
334 QUrl("https://ossia.io/score-last-version.txt"),
335 [this, titleFont](const QByteArray& data) {
336 auto version = QString::fromUtf8(data.simplified());
337 if(SCORE_TAG_NO_V < version)
338 {
339 QString text
340 = qApp->tr("New version %1 is available, click to update !").arg(version);
341 QString url = "https://github.com/ossia/score/releases/latest/";
342 InteractiveLabel* label = new InteractiveLabel{titleFont, text, url, this};
343 label->setPixmaps(
344 score::get_pixmap(":/icons/version_on.png"),
345 score::get_pixmap(":/icons/version_off.png"));
346 label->setOpenExternalLink(true);
347 label->setInactiveColor(QColor{"#f6a019"});
348 label->setActiveColor(QColor{"#f0f0f0"});
349 label->setFixedWidth(600);
350 label->move(280, 170);
351 label->show();
352 }
353 }, [] {}};
354 }
355
356 float label_x = 300;
357 float label_y = 215;
358
359 { // recent files
360 auto label = new InteractiveLabel{titleFont, qApp->tr("Recent files"), "", this};
361 label->setTextOption(QTextOption(Qt::AlignRight));
362 label->setPixmaps(
363 score::get_pixmap(":/icons/recent_files.png"),
364 score::get_pixmap(":/icons/recent_files.png"));
365 label->setInactiveColor(QColor{"#f0f0f0"});
366 label->setActiveColor(QColor{"#03C3DD"});
367 label->disableInteractivity();
368 label->move(label_x, label_y);
369 label_y += 35;
370 }
371 f.setPointSize(12);
372
373 // label_x += 40;
374 for(const auto& action : recentFiles->actions())
375 {
376 auto fileLabel
377 = new InteractiveLabel{f, action->text(), action->data().toString(), this};
378 fileLabel->setTextOption(QTextOption(Qt::AlignRight));
379 connect(
380 fileLabel, &score::InteractiveLabel::labelPressed, this,
381 &score::StartScreen::openFile);
382
383 auto textHeight = std::max(25, (int)fileLabel->textBoundingBox(200).height() + 7);
384 fileLabel->setFixedSize(200, textHeight);
385 fileLabel->move(label_x, label_y);
386 label_y += textHeight + 1;
387 }
388 // label_x = 160;
389 label_y += 10;
390
391 m_crashLabel
392 = new InteractiveLabel{titleFont, qApp->tr("Restore last session"), "", this};
393 m_crashLabel->setTextOption(QTextOption(Qt::AlignRight));
394 m_crashLabel->setPixmaps(
395 score::get_pixmap(":/icons/reload_crash_off.png"),
396 score::get_pixmap(":/icons/reload_crash_on.png"));
397 m_crashLabel->move(label_x - 100, label_y);
398 m_crashLabel->setFixedWidth(300);
399 m_crashLabel->setInactiveColor(QColor{"#f0f0f0"});
400 m_crashLabel->setActiveColor(QColor{"#f6a019"});
401 m_crashLabel->setDisabled(true);
402 m_crashLabel->hide();
403 connect(
404 m_crashLabel, &score::InteractiveLabel::labelPressed, this,
405 &score::StartScreen::loadCrashedSession);
406
407 label_x = 510;
408 label_y = 215;
409 { // Create new
410 InteractiveLabel* label = new InteractiveLabel{titleFont, qApp->tr("New"), "", this};
411 label->setPixmaps(
412 score::get_pixmap(":/icons/new_file_off.png"),
413 score::get_pixmap(":/icons/new_file_on.png"));
414 connect(
415 label, &score::InteractiveLabel::labelPressed, this,
416 &score::StartScreen::openNewDocument);
417 label->move(label_x, label_y);
418 label_y += 35;
419 }
420 { // Load file
421 InteractiveLabel* label
422 = new InteractiveLabel{titleFont, qApp->tr("Load"), "", this};
423 label->setPixmaps(
424 score::get_pixmap(":/icons/load_off.png"),
425 score::get_pixmap(":/icons/load_on.png"));
426 connect(
427 label, &score::InteractiveLabel::labelPressed, this,
428 &score::StartScreen::openFileDialog);
429 label->move(label_x, label_y);
430 label_y += 35;
431 }
432 { // Load Examples
433 QSettings settings;
434 auto library_path = settings.value("Library/RootPath").toString();
435 InteractiveLabel* label = new InteractiveLabel{
436 titleFont, qApp->tr("Examples"), "https://github.com/ossia/score-examples", this};
437 label->setPixmaps(
438 score::get_pixmap(":/icons/load_examples_off.png"),
439 score::get_pixmap(":/icons/load_examples_on.png"));
440 label->setOpenExternalLink(true);
441 label->move(label_x, label_y);
442 label_y += 35;
443 }
444
445 label_y += 20;
446
447 std::array<score::StartScreenLink, 4> menus
448 = {{{qApp->tr("Tutorials"),
449 "https://www.youtube.com/"
450 "watch?v=R-3d8K6gQkw&list=PLIHLSiZpIa6YoY1_aW1yetDgZ7tZcxfEC&index=1",
451 ":/icons/tutorials_off.png", ":/icons/tutorials_on.png"},
452 {qApp->tr("Contribute"), "https://opencollective.com/ossia/contribute",
453 ":/icons/contribute_off.png", ":/icons/contribute_on.png"},
454 {qApp->tr("Forum"), "http://forum.ossia.io/", ":/icons/forum_off.png",
455 ":/icons/forum_on.png"},
456 {qApp->tr("Chat"), "https://gitter.im/ossia/score", ":/icons/chat_off.png",
457 ":/icons/chat_on.png"}}};
458 for(const auto& m : menus)
459 {
460 InteractiveLabel* menu_url = new InteractiveLabel{titleFont, m.name, m.url, this};
461 menu_url->setOpenExternalLink(true);
462 menu_url->setPixmaps(score::get_pixmap(m.pixmap), score::get_pixmap(m.pixmapOn));
463 menu_url->move(label_x, label_y);
464 label_y += 35;
465 }
466
467 label_y += 10;
468
469 { // Exit App
470 InteractiveLabel* label
471 = new InteractiveLabel{titleFont, qApp->tr("Exit"), "", this};
472 label->setPixmaps(
473 score::get_pixmap(":/icons/exit_off.png"),
474 score::get_pixmap(":/icons/exit_on.png"));
475 connect(
476 label, &score::InteractiveLabel::labelPressed, this,
477 &score::StartScreen::exitApp);
478 label->move(label_x, label_y);
479 }
480}
481
482void StartScreen::addLoadCrashedSession()
483{
484 m_crashLabel->show();
485 m_crashLabel->setEnabled(true);
486 update();
487}
488
489void StartScreen::paintEvent(QPaintEvent* event)
490{
491 QPainter painter(this);
492 painter.drawPixmap(0, 0, m_background);
493}
494
495void StartScreen::keyPressEvent(QKeyEvent* event)
496{
497 if(event->key() == Qt::Key_Escape)
498 {
499 this->openNewDocument();
500 this->close();
501 return QWidget::keyPressEvent(event);
502 }
503}
504}
Definition StartScreen.hpp:72
Definition StartScreen.hpp:256
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