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 
19 #include <score_git_info.hpp>
20 
21 #include <optional>
22 #include <verdigris>
23 
24 namespace score
25 {
26 template <typename OnSuccess, typename OnError>
27 class HTTPGet final : public QNetworkAccessManager
28 {
29 public:
30  explicit HTTPGet(QUrl url, OnSuccess on_success, OnError on_error) noexcept
31  : m_callback{std::move(on_success)}
32  , m_error{std::move(on_error)}
33  {
34  connect(this, &QNetworkAccessManager::finished, this, [this](QNetworkReply* reply) {
35  if(reply->error())
36  {
37  qDebug() << reply->errorString();
38  m_error();
39  }
40  else
41  {
42  m_callback(reply->readAll());
43  }
44 
45  reply->deleteLater();
46  this->deleteLater();
47  });
48 
49  QNetworkRequest req{std::move(url)};
50  req.setRawHeader("User-Agent", "curl/7.35.0");
51 
52  req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
53  req.setAttribute(
54  QNetworkRequest::RedirectPolicyAttribute,
55  QNetworkRequest::UserVerifiedRedirectPolicy);
56  req.setAttribute(QNetworkRequest::Http2AllowedAttribute, true);
57 
58  auto reply = get(req);
59  connect(reply, &QNetworkReply::redirected, reply, &QNetworkReply::redirectAllowed);
60  }
61 
62 private:
63  OnSuccess m_callback;
64  OnError m_error;
65 };
66 
67 class InteractiveLabel : public QWidget
68 {
69  W_OBJECT(InteractiveLabel)
70 
71 public:
73  const QFont& font, const QString& text, const QString url, QWidget* parent = 0);
74 
75  void setOpenExternalLink(bool val) { m_openExternalLink = val; }
76  void setPixmaps(const QPixmap& pixmap, const QPixmap& pixmapOn);
77 
78  void disableInteractivity();
79  void setActiveColor(const QColor& c);
80 
81  void labelPressed(const QString& file) W_SIGNAL(labelPressed, file)
82 
83 protected:
84  void paintEvent(QPaintEvent* event) override;
85  void enterEvent(QEnterEvent* event) override;
86  void leaveEvent(QEvent* event) override;
87  void mousePressEvent(QMouseEvent* event) override;
88 
89 private:
90  QFont m_font;
91  QString m_title;
92 
93  QString m_url;
94  bool m_openExternalLink;
95 
96  bool m_drawPixmap;
97  QPixmap m_currentPixmap;
98  QPixmap m_pixmap;
99  QPixmap m_pixmapOn;
100 
101  bool m_interactive;
102  QColor m_currentColor;
103  QColor m_activeColor;
104 };
105 
106 InteractiveLabel::InteractiveLabel(
107  const QFont& font, const QString& title, const QString url, QWidget* parent)
108  : QWidget{parent}
109  , m_font(font)
110  , m_title(title)
111  , m_url(url)
112  , m_openExternalLink(false)
113  , m_drawPixmap(false)
114  , m_interactive(true)
115 {
116  auto& skin = score::Skin::instance();
117  m_currentColor = QColor{"#f0f0f0"};
118  m_activeColor = QColor{"#03C3DD"};
119  setCursor(skin.CursorPointingHand);
120  setFixedSize(200, 34);
121 }
122 
123 void InteractiveLabel::setPixmaps(const QPixmap& pixmap, const QPixmap& pixmapOn)
124 {
125  m_drawPixmap = true;
126  m_pixmap = pixmap;
127  m_pixmapOn = pixmapOn;
128  m_currentPixmap = m_pixmap;
129 }
130 
131 void InteractiveLabel::disableInteractivity()
132 {
133  m_interactive = false;
134  setCursor(Qt::ArrowCursor);
135 }
136 
137 void InteractiveLabel::setActiveColor(const QColor& c)
138 {
139  m_activeColor = c;
140 }
141 
142 void InteractiveLabel::paintEvent(QPaintEvent* event)
143 {
144  QPainter painter(this);
145 
146  painter.setRenderHint(QPainter::Antialiasing, true);
147  painter.setRenderHint(QPainter::TextAntialiasing, true);
148  painter.setPen(QPen{m_currentColor});
149 
150  QRectF textRect = rect();
151  if(m_drawPixmap)
152  {
153  int size = m_currentPixmap.width();
154  painter.drawPixmap(0, 0, m_currentPixmap);
155  textRect.setX(textRect.x() + size / qApp->devicePixelRatio() + 6);
156  }
157  painter.setFont(m_font);
158  painter.drawText(textRect, m_title);
159 }
160 
161 void InteractiveLabel::enterEvent(QEnterEvent* event)
162 {
163  if(!m_interactive)
164  return;
165 
166  m_currentColor = m_activeColor;
167  m_font.setUnderline(true);
168  m_currentPixmap = m_pixmapOn;
169 
170  repaint();
171 }
172 
173 void InteractiveLabel::leaveEvent(QEvent* event)
174 {
175  if(!m_interactive)
176  return;
177 
178  m_currentColor = QColor{"#f0f0f0"};
179  m_font.setUnderline(false);
180  m_currentPixmap = m_pixmap;
181 
182  repaint();
183 }
184 
185 void InteractiveLabel::mousePressEvent(QMouseEvent* event)
186 {
187  if(m_openExternalLink)
188  {
189  QDesktopServices::openUrl(QUrl(m_url));
190  }
191  else
192  {
193  labelPressed(m_url);
194  }
195 }
196 
197 class StartScreen : public QWidget
198 {
199  W_OBJECT(StartScreen)
200 public:
201  StartScreen(const QPointer<QRecentFilesMenu>& recentFiles, QWidget* parent = 0);
202 
203  void openNewDocument() W_SIGNAL(openNewDocument)
204  void openFile(const QString& file) W_SIGNAL(openFile, file)
205  void openFileDialog() W_SIGNAL(openFileDialog)
206  void loadCrashedSession() W_SIGNAL(loadCrashedSession)
207  void exitApp() W_SIGNAL(exitApp)
208 
209  void addLoadCrashedSession();
210 
211 protected:
212  void paintEvent(QPaintEvent* event) override;
213  void keyPressEvent(QKeyEvent* event) override;
214 
215 private:
216  QPixmap m_background;
217  InteractiveLabel* m_crashLabel{};
218 };
220 {
221  QString name;
222  QString url;
223  QString pixmap;
224  QString pixmapOn;
226  const QString& n, const QString& u, const QString& p, const QString& pOn)
227  : name(n)
228  , url(u)
229  , pixmap(p)
230  , pixmapOn(pOn)
231  {
232  }
233 };
234 
235 StartScreen::StartScreen(const QPointer<QRecentFilesMenu>& recentFiles, QWidget* parent)
236  : QWidget(parent)
237 {
238 // Workaround until https://bugreports.qt.io/browse/QTBUG-103225 is fixed
239 #if defined(__APPLE__)
240  static constexpr double font_factor = 96. / 72.;
241 #else
242  static constexpr double font_factor = 1.;
243 #endif
244  QFont f("Ubuntu", 14 * font_factor, QFont::Light);
245  f.setHintingPreference(QFont::HintingPreference::PreferFullHinting);
246  f.setStyleStrategy(QFont::PreferAntialias);
247 
248  QFont titleFont("Montserrat", 14 * font_factor, QFont::DemiBold);
249  titleFont.setHintingPreference(QFont::HintingPreference::PreferFullHinting);
250  titleFont.setStyleStrategy(QFont::PreferAntialias);
251 
252  this->setEnabled(true);
253  setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint); //| Qt::WindowStaysOnTopHint);
254  setWindowModality(Qt::ApplicationModal);
255 
256  m_background = score::get_pixmap(":/startscreen/startscreensplash.png");
257 
258  if(QPainter painter; painter.begin(&m_background))
259  {
260  painter.setRenderHint(QPainter::Antialiasing, true);
261  painter.setRenderHint(QPainter::TextAntialiasing, true);
262  painter.setPen(QPen(QColor("#0092CF")));
263 
264  painter.setFont(f);
265  //painter.drawText(QPointF(250, 170), QCoreApplication::applicationVersion());
266  painter.drawText(QPointF(381, 195), QCoreApplication::applicationVersion());
267  painter.end();
268  }
269 
270  // Weird code here is because the window size seems to scale only to integer ratios.
271  setFixedSize(m_background.size() / std::floor(qApp->devicePixelRatio()));
272 
273  float label_y = 285;
274  { // Create new
275  InteractiveLabel* label = new InteractiveLabel{titleFont, qApp->tr("New"), "", this};
276  label->setPixmaps(
277  score::get_pixmap(":/icons/new_file_off.png"),
278  score::get_pixmap(":/icons/new_file_on.png"));
279  connect(
280  label, &score::InteractiveLabel::labelPressed, this,
281  &score::StartScreen::openNewDocument);
282  label->move(100, label_y);
283  label_y += 35;
284  }
285  { // Load file
286  InteractiveLabel* label
287  = new InteractiveLabel{titleFont, qApp->tr("Load"), "", this};
288  label->setPixmaps(
289  score::get_pixmap(":/icons/load_off.png"),
290  score::get_pixmap(":/icons/load_on.png"));
291  connect(
292  label, &score::InteractiveLabel::labelPressed, this,
293  &score::StartScreen::openFileDialog);
294  label->move(100, label_y);
295  label_y += 35;
296  }
297  { // Load Examples
298  QSettings settings;
299  auto library_path = settings.value("Library/RootPath").toString();
300  InteractiveLabel* label = new InteractiveLabel{
301  titleFont, qApp->tr("Examples"), "https://github.com/ossia/score-examples", this};
302  label->setPixmaps(
303  score::get_pixmap(":/icons/load_examples_off.png"),
304  score::get_pixmap(":/icons/load_examples_on.png"));
305  label->setOpenExternalLink(true);
306  label->move(100, label_y);
307  label_y += 35;
308  }
309  { // Exit App
310  InteractiveLabel* label
311  = new InteractiveLabel{titleFont, qApp->tr("Exit"), "", this};
312  label->setPixmaps(
313  score::get_pixmap(":/icons/exit_off.png"),
314  score::get_pixmap(":/icons/exit_on.png"));
315  connect(
316  label, &score::InteractiveLabel::labelPressed, this,
317  &score::StartScreen::exitApp);
318  label->move(100, label_y);
319  label_y += 50;
320  }
321  label_y = 285;
322  { // recent files
323  InteractiveLabel* label
324  = new InteractiveLabel{titleFont, qApp->tr("Recent files"), "", this};
325  label->setPixmaps(
326  score::get_pixmap(":/icons/recent_files.png"),
327  score::get_pixmap(":/icons/recent_files.png"));
328  label->disableInteractivity();
329  label->move(280, label_y);
330  label_y += 30;
331  }
332  {
333  // new version
334  auto m_getLastVersion = new HTTPGet{
335  QUrl("https://ossia.io/score-last-version.txt"),
336  [=](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_off.png"),
346  score::get_pixmap(":/icons/version_on.png"));
347  label->setOpenExternalLink(true);
348  label->setFixedWidth(600);
349  label->move(140, 245);
350  label->show();
351  }
352  },
353  [] {}};
354  }
355  f.setPointSize(12);
356  for(const auto& action : recentFiles->actions())
357  {
358  InteractiveLabel* fileLabel
359  = new InteractiveLabel{f, action->text(), action->data().toString(), this};
360  connect(
361  fileLabel, &score::InteractiveLabel::labelPressed, this,
362  &score::StartScreen::openFile);
363 
364  fileLabel->move(310, label_y);
365 
366  label_y += 25;
367  }
368 
369  std::array<score::StartScreenLink, 4> menus
370  = {{{qApp->tr("Tutorials"),
371  "https://www.youtube.com/"
372  "watch?v=R-3d8K6gQkw&list=PLIHLSiZpIa6YoY1_aW1yetDgZ7tZcxfEC&index=1",
373  ":/icons/tutorials_off.png", ":/icons/tutorials_on.png"},
374  {qApp->tr("Contribute"), "https://opencollective.com/ossia/contribute",
375  ":/icons/contribute_off.png", ":/icons/contribute_on.png"},
376  {qApp->tr("Forum"), "http://forum.ossia.io/", ":/icons/forum_off.png",
377  ":/icons/forum_on.png"},
378  {qApp->tr("Chat"), "https://gitter.im/ossia/score", ":/icons/chat_off.png",
379  ":/icons/chat_on.png"}}};
380  label_y = 285;
381  for(const auto& m : menus)
382  {
383  InteractiveLabel* menu_url = new InteractiveLabel{titleFont, m.name, m.url, this};
384  menu_url->setOpenExternalLink(true);
385  menu_url->setPixmaps(score::get_pixmap(m.pixmap), score::get_pixmap(m.pixmapOn));
386  menu_url->move(530, label_y);
387  label_y += 40;
388  }
389 
390  m_crashLabel = new InteractiveLabel{
391  titleFont, qApp->tr("Reload your previously crashed work ?"), "", this};
392  m_crashLabel->setPixmaps(
393  score::get_pixmap(":/icons/reload_crash_off.png"),
394  score::get_pixmap(":/icons/reload_crash_on.png"));
395  m_crashLabel->move(150, 460);
396  m_crashLabel->setFixedWidth(600);
397  m_crashLabel->setActiveColor(QColor{"#f6a019"});
398  m_crashLabel->setDisabled(true);
399  m_crashLabel->hide();
400  connect(
401  m_crashLabel, &score::InteractiveLabel::labelPressed, this,
402  &score::StartScreen::loadCrashedSession);
403 }
404 
405 void StartScreen::addLoadCrashedSession()
406 {
407  m_crashLabel->show();
408  m_crashLabel->setEnabled(true);
409  update();
410 }
411 
412 void StartScreen::paintEvent(QPaintEvent* event)
413 {
414  QPainter painter(this);
415  painter.drawPixmap(0, 0, m_background);
416 }
417 
418 void StartScreen::keyPressEvent(QKeyEvent* event)
419 {
420  if(event->key() == Qt::Key_Escape)
421  {
422  this->openNewDocument();
423  this->close();
424  return QWidget::keyPressEvent(event);
425  }
426 }
427 }
Definition: StartScreen.hpp:28
Definition: StartScreen.hpp:68
Definition: StartScreen.hpp:198
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