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 
25 namespace score
26 {
27 namespace
28 {
29 template <typename OnSuccess, typename OnError>
30 class HTTPGet final : public QNetworkAccessManager
31 {
32 public:
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 
65 private:
66  OnSuccess m_callback;
67  OnError m_error;
68 };
69 
70 }
71 class InteractiveLabel : public QWidget
72 {
73  W_OBJECT(InteractiveLabel)
74 
75 public:
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 
91 protected:
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 
97 private:
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 
117 InteractiveLabel::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 
135 void 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 
143 void InteractiveLabel::disableInteractivity()
144 {
145  m_interactive = false;
146  setCursor(Qt::ArrowCursor);
147 }
148 
149 void InteractiveLabel::setActiveColor(const QColor& c)
150 {
151  m_activeColor = c;
152 }
153 
154 void InteractiveLabel::setInactiveColor(const QColor& c)
155 {
156  m_inactiveColor = c;
157  m_currentColor = m_inactiveColor;
158 }
159 
160 QRectF 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 
184 void 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 
220 void 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 
232 void 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 
244 void 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 }
255 class StartScreen : public QWidget
256 {
257  W_OBJECT(StartScreen)
258 public:
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 
269 protected:
270  void paintEvent(QPaintEvent* event) override;
271  void keyPressEvent(QKeyEvent* event) override;
272 
273 private:
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 
293 StartScreen::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 
482 void StartScreen::addLoadCrashedSession()
483 {
484  m_crashLabel->show();
485  m_crashLabel->setEnabled(true);
486  update();
487 }
488 
489 void StartScreen::paintEvent(QPaintEvent* event)
490 {
491  QPainter painter(this);
492  painter.drawPixmap(0, 0, m_background);
493 }
494 
495 void 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