2#include <score/application/GUIApplicationContext.hpp>
3#include <score/model/Skin.hpp>
4#include <score/tools/ThreadPool.hpp>
5#include <score/widgets/MessageBox.hpp>
6#include <score/widgets/Pixmap.hpp>
8#include <core/document/DefaultLibrary.hpp>
9#include <core/document/DocumentTemplates.hpp>
10#include <core/document/OnlineExamples.hpp>
11#include <core/document/ProjectInfo.hpp>
12#include <core/presenter/AboutWidget.hpp>
13#include <core/view/QRecentFilesMenu.h>
15#include <QApplication>
18#include <QDesktopServices>
21#include <QFontMetrics>
29#include <QNetworkAccessManager>
30#include <QNetworkReply>
31#include <QNetworkRequest>
33#include <QPainterPath>
36#include <QPlainTextEdit>
44#include <QStackedWidget>
47#include <QVersionNumber>
49#include <score_git_info.hpp>
64template <
typename OnSuccess,
typename OnError>
65class HTTPGet final :
public QNetworkAccessManager
68 explicit HTTPGet(QUrl url, OnSuccess on_success, OnError on_error) noexcept
69 : m_callback{std::move(on_success)}
70 , m_error{std::move(on_error)}
72 connect(
this, &QNetworkAccessManager::finished,
this, [
this](QNetworkReply* reply) {
75 qDebug() << reply->errorString();
80 m_callback(reply->readAll());
87 QNetworkRequest req{std::move(url)};
88 req.setRawHeader(
"User-Agent",
"curl/7.35.0");
89 req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute,
true);
91 QNetworkRequest::RedirectPolicyAttribute,
92 QNetworkRequest::UserVerifiedRedirectPolicy);
93 req.setAttribute(QNetworkRequest::Http2AllowedAttribute,
true);
95 auto reply = get(req);
96 connect(reply, &QNetworkReply::redirected, reply, &QNetworkReply::redirectAllowed);
100 OnSuccess m_callback;
107namespace StartScreenColors
109static const QColor Panel{
110 0x21, 0x1f, 0x1f, 205};
111static const QColor Card{
"#2b2929"};
112static const QColor CardDark{
"#161514"};
113static const QColor Scrim{0x16, 0x15, 0x14, 232};
114static const QColor Outline{
"#3d3a3a"};
115static const QColor Text{
"#f0f0f0"};
116static const QColor Muted{
"#8a8a8a"};
117static const QColor Hover{
"#03C3DD"};
118static const QColor Accent{
"#f6a019"};
119static const QColor Version{
"#0092CF"};
123QPixmap coverPixmap(
const QImage& img, QSize size)
128 = img.scaled(size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
130 (scaled.width() - size.width()) / 2, (scaled.height() - size.height()) / 2,
131 size.width(), size.height()};
132 return QPixmap::fromImage(scaled.copy(crop));
136void drawWrappedText(QPainter& p,
const QRect& rect,
const QString& text)
138 const QFontMetrics fm{p.font()};
139 const int lineSpacing = fm.lineSpacing();
142 QTextLayout layout{text, p.font()};
143 layout.beginLayout();
146 QTextLine line = layout.createLine();
149 line.setLineWidth(rect.width());
151 if(y + 2 * lineSpacing <= rect.bottom() + 1)
153 line.draw(&p, QPointF{qreal(rect.left()), qreal(y)});
159 QRect{rect.left(), y, rect.width(), lineSpacing}, Qt::AlignLeft | Qt::AlignTop,
160 fm.elidedText(text.mid(line.textStart()), Qt::ElideRight, rect.width()));
181 const QFont& font,
const QString& text,
const QString& url,
182 QWidget* parent =
nullptr);
184 void setOpenExternalLink(
bool val) { m_openExternalLink = val; }
185 void setPixmaps(
const QPixmap& pixmap,
const QPixmap& pixmapOn);
187 void disableInteractivity();
188 void setActiveColor(
const QColor& c);
189 void setInactiveColor(
const QColor& c);
190 void setElideMode(Qt::TextElideMode mode) { m_elideMode = mode; }
191 void setLeftPadding(
int px) { m_leftPadding = px; }
194 void setItemHeight(
int px);
196 void setCheckable(
bool b) { m_checkable = b; }
197 void setCheckedBackground(
const QColor& c) { m_checkedBackground = c; }
198 void setChecked(
bool b);
199 bool isChecked() const noexcept {
return m_checked; }
201 void setText(
const QString& text);
202 const QString& text() const noexcept {
return m_title; }
203 const QString& url() const noexcept {
return m_url; }
205 void labelPressed(
const QString& file) W_SIGNAL(labelPressed, file)
206 void hovered(
bool state) W_SIGNAL(hovered, state)
208 QSize sizeHint()
const override;
209 QSize minimumSizeHint()
const override;
212 void paintEvent(QPaintEvent* event)
override;
213 void enterEvent(QEnterEvent* event)
override;
214 void leaveEvent(QEvent* event)
override;
215 void mousePressEvent(QMouseEvent* event)
override;
216 void mouseReleaseEvent(QMouseEvent* event)
override;
219 bool highlighted() const noexcept {
return m_checked || (m_interactive && m_hovered); }
220 int iconWidth() const noexcept {
return m_pixmap.isNull() ? 0 : m_iconColumn; }
229 QColor m_activeColor{StartScreenColors::Hover};
230 QColor m_inactiveColor{StartScreenColors::Text};
231 QColor m_checkedBackground;
233 Qt::TextElideMode m_elideMode{Qt::ElideRight};
234 int m_leftPadding{4};
235 int m_iconColumn{34};
238 bool m_openExternalLink{};
239 bool m_interactive{
true};
246InteractiveLabel::InteractiveLabel(
247 const QFont& font,
const QString& title,
const QString& url, QWidget* parent)
253 setCursor(score::Skin::instance().CursorPointingHand);
254 setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
255 setFixedHeight(m_height);
258void InteractiveLabel::setPixmaps(
const QPixmap& pixmap,
const QPixmap& pixmapOn)
261 m_pixmapOn = pixmapOn;
263 const int h = std::max(
264 int(m_pixmap.height() / m_pixmap.devicePixelRatio()),
265 int(m_pixmapOn.height() / m_pixmapOn.devicePixelRatio()));
267 setItemHeight(h + 4);
271void InteractiveLabel::setItemHeight(
int px)
278void InteractiveLabel::disableInteractivity()
280 m_interactive =
false;
281 setCursor(score::Skin::instance().CursorPointer);
285void InteractiveLabel::setActiveColor(
const QColor& c)
291void InteractiveLabel::setInactiveColor(
const QColor& c)
297void InteractiveLabel::setChecked(
bool b)
305void InteractiveLabel::setText(
const QString& text)
312QSize InteractiveLabel::sizeHint()
const
314 int w = m_leftPadding + iconWidth();
315 if(!m_title.isEmpty())
316 w += QFontMetrics{m_font}.horizontalAdvance(m_title) + 6;
317 return {w, m_height};
320QSize InteractiveLabel::minimumSizeHint()
const
323 int w = m_leftPadding + iconWidth();
324 if(!m_title.isEmpty())
326 return {w, m_height};
330static QPixmap tintedPixmap(
const QPixmap& source,
const QColor& color)
334 static std::map<std::pair<qint64, QRgb>, QPixmap> cache;
335 const auto key = std::make_pair(source.cacheKey(), color.rgba());
336 if(
auto it = cache.find(key); it != cache.end())
339 QPixmap res{source.size()};
340 res.setDevicePixelRatio(source.devicePixelRatio());
341 res.fill(Qt::transparent);
344 p.drawPixmap(0, 0, source);
345 p.setCompositionMode(QPainter::CompositionMode_SourceIn);
346 p.fillRect(res.rect(), color);
348 return cache.emplace(key, res).first->second;
351void InteractiveLabel::paintEvent(QPaintEvent* event)
353 QPainter painter(
this);
354 painter.setRenderHint(QPainter::Antialiasing,
true);
355 painter.setRenderHint(QPainter::TextAntialiasing,
true);
356 painter.setRenderHint(QPainter::SmoothPixmapTransform,
true);
358 const bool hl = highlighted();
360 if(m_checked && m_checkedBackground.isValid())
362 painter.fillRect(rect(), m_checkedBackground);
363 painter.fillRect(QRect{0, 0, 3, height()}, m_activeColor);
366 QRectF textRect = rect().adjusted(m_leftPadding, 0, -4, 0);
369 const QColor color = hl ? m_activeColor : m_inactiveColor;
370 const QPixmap pm = tintedPixmap(m_pixmap, color);
373 const qreal w = pm.width() / pm.devicePixelRatio();
374 const qreal h = pm.height() / pm.devicePixelRatio();
377 QPointF{textRect.x() + (m_iconColumn - 8 - w) / 2., (height() - h) / 2.}, pm);
378 textRect.setX(textRect.x() + m_iconColumn);
381 if(!m_title.isEmpty())
383 painter.setPen(QPen{color});
384 painter.setFont(m_font);
386 = QFontMetrics{m_font}.elidedText(m_title, m_elideMode,
int(textRect.width()));
387 painter.drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, txt);
391void InteractiveLabel::enterEvent(QEnterEvent* event)
401void InteractiveLabel::leaveEvent(QEvent* event)
412void InteractiveLabel::mousePressEvent(QMouseEvent* event)
414 if(!m_interactive || event->button() != Qt::LeftButton)
415 return QWidget::mousePressEvent(event);
420void InteractiveLabel::mouseReleaseEvent(QMouseEvent* event)
422 if(!m_pressed || event->button() != Qt::LeftButton)
423 return QWidget::mouseReleaseEvent(event);
427 if(!rect().contains(event->pos()))
433 if(m_openExternalLink)
434 QDesktopServices::openUrl(QUrl(m_url));
445 static constexpr int Width = 264;
446 static constexpr int ThumbHeight = 158;
447 static constexpr int Margin = 6;
448 static constexpr int DescriptionLines = 2;
450 ThumbnailPopup(
const QFont& titleFont,
const QFont& subFont, QWidget* parent)
452 , m_titleFont{titleFont}
455 setAttribute(Qt::WA_TransparentForMouseEvents);
460 const QImage& thumbnail,
const QString& title,
const QString& author,
461 const QString& description,
const QString& path)
463 m_thumbnail = coverPixmap(thumbnail, {Width - 2 * Margin, ThumbHeight});
466 m_description = description.simplified();
469 const QFontMetrics tm{m_titleFont}, sm{m_subFont};
470 int h = 2 * Margin + tm.height();
471 if(!m_thumbnail.isNull())
472 h += ThumbHeight + Margin;
473 if(!m_author.isEmpty())
475 if(!m_description.isEmpty())
476 h += 4 + DescriptionLines * sm.lineSpacing();
477 if(!m_path.isEmpty())
478 h += 4 + sm.height();
484 void showFor(QWidget* anchor,
const QRect& bounds)
486 const QRect a{anchor->mapTo(parentWidget(), QPoint{0, 0}), anchor->size()};
487 int x = a.left() - width() - 12;
488 if(x < bounds.left())
490 if(x + width() > bounds.right())
491 x = bounds.right() - width();
492 int y = a.center().y() - height() / 2;
493 y = std::clamp(y, bounds.top(), std::max(bounds.top(), bounds.bottom() - height()));
500 void paintEvent(QPaintEvent*)
override
503 p.setRenderHint(QPainter::Antialiasing,
true);
504 p.setRenderHint(QPainter::SmoothPixmapTransform,
true);
507 path.addRoundedRect(QRectF{rect()}.adjusted(0.5, 0.5, -0.5, -0.5), 4, 4);
508 p.fillPath(path, StartScreenColors::CardDark);
509 p.setPen(QPen{StartScreenColors::Outline});
512 const int textX = Margin + 2;
513 const int textW = width() - 2 * Margin - 4;
514 const QFontMetrics tm{m_titleFont}, sm{m_subFont};
516 if(!m_thumbnail.isNull())
518 p.drawPixmap(Margin, y, m_thumbnail);
519 y += ThumbHeight + Margin;
522 p.setPen(StartScreenColors::Text);
523 p.setFont(m_titleFont);
525 QRect{textX, y, textW, tm.height()}, Qt::AlignLeft | Qt::AlignVCenter,
526 tm.elidedText(m_title, Qt::ElideMiddle, textW));
529 p.setFont(m_subFont);
530 if(!m_author.isEmpty())
532 p.setPen(StartScreenColors::Hover);
534 QRect{textX, y, textW, sm.height()}, Qt::AlignLeft | Qt::AlignVCenter,
535 sm.elidedText(m_author, Qt::ElideRight, textW));
538 if(!m_description.isEmpty())
541 p.setPen(StartScreenColors::Text);
542 const QRect descRect{textX, y, textW, DescriptionLines * sm.lineSpacing()};
544 p.setClipRect(descRect);
546 descRect, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, m_description);
548 y += descRect.height();
550 if(!m_path.isEmpty())
553 p.setPen(StartScreenColors::Muted);
555 QRect{textX, y, textW, sm.height()}, Qt::AlignLeft | Qt::AlignVCenter,
556 sm.elidedText(m_path, Qt::ElideMiddle, textW));
566 QString m_description;
583 const QFont& font,
const QString& title,
int count,
int depth, QWidget* parent)
590 m_font.setCapitalization(depth == 0 ? QFont::AllUppercase : QFont::MixedCase);
591 m_font.setBold(
true);
593 auto lay =
new QVBoxLayout{
this};
594 lay->setContentsMargins(depth > 0 ?
Step : 0, 0, 0, 0);
595 lay->setSpacing(
Gap);
597 m_header =
new QWidget{
this};
598 m_header->setFixedHeight(BandHeight);
599 m_header->setCursor(score::Skin::instance().CursorPointingHand);
600 m_header->installEventFilter(
this);
601 lay->addWidget(m_header);
603 m_body =
new QWidget{
this};
604 m_bodyLayout =
new QVBoxLayout{m_body};
605 m_bodyLayout->setContentsMargins(0, 0, 0, 0);
606 m_bodyLayout->setSpacing(
Gap);
607 lay->addWidget(m_body);
610 QVBoxLayout* body()
const noexcept {
return m_bodyLayout; }
611 QWidget* header()
const noexcept {
return m_header; }
613 bool expanded()
const noexcept {
return m_expanded; }
614 void setExpanded(
bool e)
617 m_body->setVisible(e);
627 static constexpr int BandHeight = 24;
629 static constexpr int Gap = 8;
631 static constexpr int Step = 16;
634 bool eventFilter(QObject* obj, QEvent* ev)
override
637 return QWidget::eventFilter(obj, ev);
641 case QEvent::Paint: {
642 QPainter p{m_header};
643 p.setRenderHint(QPainter::Antialiasing,
true);
644 const int w = m_header->width();
645 const int h = m_header->height();
648 p.setBrush(m_depth == 0 ? StartScreenColors::Outline : StartScreenColors::Card);
649 p.drawRoundedRect(QRectF{0, 0, qreal(w), qreal(h)}, 3, 3);
652 p.setBrush(StartScreenColors::Accent);
653 p.drawRoundedRect(QRectF{0, 0, 3, qreal(h)}, 1.5, 1.5);
657 const qreal cy = h / 2.;
658 constexpr qreal tx = TextX -
Step;
662 tri.moveTo(tx - 5, cy - 3);
663 tri.lineTo(tx + 5, cy - 3);
664 tri.lineTo(tx, cy + 3);
668 tri.moveTo(tx - 3, cy - 5);
669 tri.lineTo(tx + 3, cy);
670 tri.lineTo(tx - 3, cy + 5);
674 tri, m_hovered ? StartScreenColors::Hover : StartScreenColors::Muted);
677 const QFontMetrics fm{m_font};
678 const int baseline = (h + fm.capHeight()) / 2;
679 const QString count = QString::number(m_count);
680 const int countWidth = fm.horizontalAdvance(count);
681 const QString title = fm.elidedText(
682 m_title, Qt::ElideRight, std::max(0, w - TextX - CountGap - countWidth - 8));
685 p.setPen(m_hovered ? StartScreenColors::Hover : StartScreenColors::Text);
686 p.drawText(QPointF(TextX, baseline), title);
688 QFont countFont = m_font;
689 countFont.setBold(
false);
690 p.setFont(countFont);
691 p.setPen(StartScreenColors::Muted);
693 QPointF(TextX + fm.horizontalAdvance(title) + CountGap, baseline), count);
705 case QEvent::MouseButtonPress:
706 setExpanded(!m_expanded);
709 return QWidget::eventFilter(obj, ev);
715 static constexpr int TextX = 26;
716 static constexpr int CountGap = 8;
724 QVBoxLayout* m_bodyLayout{};
725 bool m_expanded{
true};
732 static constexpr int Width = 190;
733 static constexpr int ThumbHeight = 119;
734 static constexpr int Height = ThumbHeight + 66;
737 const QFont& titleFont,
const QFont& subFont,
const QString& title,
738 const QString& subtitle,
const QString& path, QWidget* parent)
740 , m_titleFont{titleFont}
743 , m_subtitle{subtitle}
746 setFixedSize(Width, Height);
747 setCursor(score::Skin::instance().CursorPointingHand);
748 setToolTip(QDir::toNativeSeparators(path));
751 const QString& path()
const noexcept {
return m_path; }
753 void setDownloadSize(qint64 bytes)
756 bytes > 0 ? QLocale{}.formattedDataSize(bytes) : QString{},
757 StartScreenColors::Muted);
761 void setBadge(
const QString& text,
const QColor& color)
764 m_badgeColor = color;
767 const QString& badge() const noexcept {
return m_badge; }
769 void setThumbnailImage(
const QImage& img)
771 m_thumbnail = coverPixmap(img, {Width, ThumbHeight});
775 void setInfo(
const ProjectInfo::Info& info)
777 if(!info.name.isEmpty())
779 if(!info.author.isEmpty())
780 m_subtitle = info.author;
781 m_description = info.description.simplified();
782 m_thumbnail = coverPixmap(info.thumbnail, {Width, ThumbHeight});
783 QString tip = m_title;
784 if(!info.author.isEmpty())
785 tip +=
"\n" + info.author;
786 if(!info.description.isEmpty())
787 tip +=
"\n\n" + info.description;
788 tip +=
"\n\n" + QDir::toNativeSeparators(m_path);
799 for(
const auto& w : words)
801 if(!m_title.contains(w, Qt::CaseInsensitive)
802 && !m_subtitle.contains(w, Qt::CaseInsensitive)
803 && !m_description.contains(w, Qt::CaseInsensitive)
804 && !m_keywords.contains(w, Qt::CaseInsensitive)
805 && !QFileInfo{m_path}.fileName().contains(w, Qt::CaseInsensitive))
812 std::function<void(
const QString& path, Qt::KeyboardModifiers mods)>
onActivated;
817 void paintEvent(QPaintEvent*)
override
820 p.setRenderHint(QPainter::Antialiasing,
true);
821 p.setRenderHint(QPainter::SmoothPixmapTransform,
true);
824 path.addRoundedRect(QRectF{rect()}.adjusted(0.5, 0.5, -0.5, -0.5), 4, 4);
825 p.fillPath(path, StartScreenColors::Card);
830 const QRect thumbRect{0, 0, Width, ThumbHeight};
831 if(!m_thumbnail.isNull())
833 p.drawPixmap(thumbRect, m_thumbnail);
837 p.fillRect(thumbRect, StartScreenColors::CardDark);
838 static const QPixmap placeholder
839 = score::get_pixmap(
":/icons/load_examples_off.png");
840 const qreal w = placeholder.width() / placeholder.devicePixelRatio();
841 const qreal h = placeholder.height() / placeholder.devicePixelRatio();
843 p.drawPixmap(QPointF{(Width - w) / 2., (ThumbHeight - h) / 2.}, placeholder);
847 if(!m_badge.isEmpty() && !m_hovered)
849 p.setFont(m_subFont);
850 const QFontMetrics fm{m_subFont};
851 const int w = fm.horizontalAdvance(m_badge) + 10;
852 const QRect box{Width - w - 6, 6, w, fm.height() + 4};
853 p.fillRect(box, StartScreenColors::Scrim);
854 p.setPen(m_badgeColor);
855 p.drawText(box, Qt::AlignCenter, m_badge);
858 if(m_hovered && !m_description.isEmpty())
860 p.fillRect(thumbRect, StartScreenColors::Scrim);
861 p.setPen(StartScreenColors::Text);
862 p.setFont(m_subFont);
863 drawWrappedText(p, thumbRect.adjusted(8, 7, -8, -7), m_description);
867 p.setPen(QPen{m_hovered ? StartScreenColors::Hover : StartScreenColors::Outline, 1});
871 const int textW = Width - 16;
872 p.setPen(m_hovered ? StartScreenColors::Hover : StartScreenColors::Text);
873 p.setFont(m_titleFont);
874 const QFontMetrics tm{m_titleFont};
876 QRect{textX, ThumbHeight + 6, textW, tm.height()},
877 Qt::AlignLeft | Qt::AlignVCenter, tm.elidedText(m_title, Qt::ElideRight, textW));
879 p.setPen(StartScreenColors::Muted);
880 p.setFont(m_subFont);
881 const QFontMetrics sm{m_subFont};
883 QRect{textX, ThumbHeight + 6 + tm.height(), textW, sm.height()},
884 Qt::AlignLeft | Qt::AlignVCenter,
885 sm.elidedText(m_subtitle, Qt::ElideRight, textW));
890 QRect{textX, ThumbHeight + 8 + tm.height() + sm.height(), textW, sm.height()},
891 Qt::AlignLeft | Qt::AlignVCenter,
892 sm.elidedText(QDir::toNativeSeparators(m_path), Qt::ElideMiddle, textW));
896 void enterEvent(QEnterEvent*)
override
901 void leaveEvent(QEvent*)
override
907 void mousePressEvent(QMouseEvent* e)
override
909 if(e->button() == Qt::LeftButton)
915 void mouseReleaseEvent(QMouseEvent* e)
override
918 = m_pressed && e->button() == Qt::LeftButton && rect().contains(e->pos());
923 void contextMenuEvent(QContextMenuEvent* e)
override
926 return QWidget::contextMenuEvent(e);
936 QString m_description;
939 QColor m_badgeColor{StartScreenColors::Muted};
951 using QLineEdit::QLineEdit;
954 QStringList
words()
const {
return text().simplified().split(
' ', Qt::SkipEmptyParts); }
957 void keyPressEvent(QKeyEvent* e)
override
959 if(e->key() == Qt::Key_Escape && !text().isEmpty())
965 QLineEdit::keyPressEvent(e);
966 if(e->key() == Qt::Key_Up || e->key() == Qt::Key_Down)
1003 , m_spacing{spacing}
1005 setContentsMargins(0, 0, 0, 0);
1009 while(
auto item = takeAt(0))
1013 void addItem(QLayoutItem* item)
override { m_items.push_back(item); }
1014 int count()
const override {
return int(m_items.size()); }
1015 QLayoutItem* itemAt(
int i)
const override
1017 return i >= 0 && i < count() ? m_items[i] :
nullptr;
1019 QLayoutItem* takeAt(
int i)
override
1021 if(i < 0 || i >= count())
1023 auto item = m_items[i];
1024 m_items.erase(m_items.begin() + i);
1028 Qt::Orientations expandingDirections()
const override {
return {}; }
1029 bool hasHeightForWidth()
const override {
return true; }
1030 int heightForWidth(
int width)
const override
1032 return arrange(QRect{0, 0, width, 0},
false);
1034 void setGeometry(
const QRect& rect)
override
1036 QLayout::setGeometry(rect);
1037 arrange(rect,
true);
1039 QSize sizeHint()
const override {
return minimumSize(); }
1040 QSize minimumSize()
const override
1043 for(
auto* item : m_items)
1044 size = size.expandedTo(item->minimumSize());
1045 const auto m = contentsMargins();
1046 return size + QSize{m.left() + m.right(), m.top() + m.bottom()};
1051 int arrange(
const QRect& rect,
bool apply)
const
1053 const auto m = contentsMargins();
1054 const QRect area = rect.adjusted(m.left(), m.top(), -m.right(), -m.bottom());
1058 for(
auto* item : m_items)
1062 const QSize size = item->sizeHint();
1063 if(x > area.x() && x + size.width() > area.x() + area.width())
1066 y += rowHeight + m_spacing;
1070 item->setGeometry(QRect{QPoint{x, y}, size});
1071 x += size.width() + m_spacing;
1072 rowHeight = std::max(rowHeight, size.height());
1074 return y + rowHeight - rect.y() + m.bottom();
1077 std::vector<QLayoutItem*> m_items;
1085 StartScreen(
const QPointer<QRecentFilesMenu>& recentFiles, QWidget* parent =
nullptr);
1088 void openNewDocument() W_SIGNAL(openNewDocument)
1089 void openFile(
const QString& file) W_SIGNAL(openFile, file)
1092 void openFileDialog() W_SIGNAL(openFileDialog)
1093 void loadCrashedSession() W_SIGNAL(loadCrashedSession)
1096 void exitApp() W_SIGNAL(exitApp)
1107 static constexpr
int Width = 880;
1108 static constexpr
int Height = 640;
1109 static constexpr
int HeaderHeight = 220;
1110 static constexpr
int NavWidth = 190;
1111 static constexpr
int MaxRecentFiles = 8;
1114 void paintEvent(QPaintEvent* event) override;
1115 bool eventFilter(QObject* watched, QEvent* event) override;
1116 void keyPressEvent(QKeyEvent* event) override;
1117 void closeEvent(QCloseEvent* event) override;
1118 void showEvent(QShowEvent* event) override;
1119 void hideEvent(QHideEvent* event) override;
1130 QWidget* createHeader();
1131 QWidget* createNavigation();
1134 int addPage(
const QString& name,
const QString& icon, std::function<QWidget*()> make);
1135 QWidget* createHomePage(
const QPointer<QRecentFilesMenu>& recentFiles);
1137 void refreshRecentFiles();
1138 QWidget* createTemplatesPage();
1139 QWidget* createAboutPage();
1141 QWidget* createExamplesPage(
bool online);
1143 QWidget* createCardsPage(
1144 const QString& title,
const QString& hint,
const QString& emptyHint,
1145 const std::vector<DocumentTemplate>& docs,
1146 std::function<
void(
const QString&)> onActivated,
const Link& more);
1147 QWidget* createLinksPage(
const QString& title,
const std::vector<Link>& links);
1157 QWidget* makeCardGrid(
1158 const std::vector<ExampleEntry>& docs, std::vector<ExampleCard*>& cards,
1160 std::vector<ExampleEntry> gatherExamples()
const;
1163 void openOnlineExample(
const OnlineExample& ex,
bool direct,
bool openPage);
1164 void showOnlineExampleMenu(
const OnlineExample& ex,
const QPoint& globalPos);
1166 void replaceLocalCopy(
const OnlineExample& ex);
1168 std::set<QString> m_replaceAsked;
1169 void refreshExamplesPage();
1170 void refreshExamplesPage(
int index,
bool online);
1171 void requestThumbnail(
const OnlineExample& ex, ExampleCard* card);
1173 int addPage(
const QString& name,
const QString& icon, QWidget* page);
1174 void setCurrentPage(
int index);
1176 QLabel* makeSectionTitle(
const QString& text, QWidget* parent);
1177 QLabel* makeHint(
const QString& text, QWidget* parent);
1179 FilterEdit* makeTitleWithFilter(
const QString& title, QVBoxLayout* lay, QWidget* page);
1180 InteractiveLabel* makeItem(
1181 const QString& text,
const QString& icon,
const QString& url, QWidget* parent);
1183 InteractiveLabel* makeAccentItem(
1184 const QString& text,
const QString& icon,
const QString& url, QWidget* parent);
1185 InteractiveLabel* makeExternalLink(
const Link& link, QWidget* parent);
1187 InteractiveLabel* makeScoreItem(
1188 const QString& text,
const QString& icon,
const QString& path,
bool asTemplate,
1193 template <
typename F>
1194 void choose(F&& emitSignal)
1198 m_actionTaken =
true;
1203 void openExample(
const QString& path,
bool direct,
bool openPage);
1204 void requestInfo(
const QString& path);
1206 void requestInfo(
const QString& path, ExampleCard& card);
1207 void onInfoLoaded(
const QString& path,
const std::optional<ProjectInfo::Info>& info);
1208 void showPreview(InteractiveLabel* label);
1210 void checkForNewVersion();
1211 void showUpdateAvailable(
const QString& version);
1217 makeLibraryNotice(QWidget* parent,
const QMargins& margins,
bool linksToTemplates);
1218 void updateLibraryNotices();
1219 void onLibraryChanged();
1220 void rebuildTemplatesPage();
1222 struct LibraryNotice
1224 QPointer<QWidget> box;
1225 QPointer<QLabel> hint;
1226 QPointer<InteractiveLabel> action;
1227 bool linksToTemplates{};
1229 std::vector<LibraryNotice> m_libraryNotices;
1232 bool m_libraryInstalled{};
1233 bool m_libraryViewRegistered{};
1236 QFont m_sectionFont;
1239 QFont m_versionFont;
1241 QPixmap m_background;
1244 QStackedWidget* m_pages{};
1245 std::vector<InteractiveLabel*> m_navItems;
1246 std::vector<std::function<QWidget*()>> m_pageFactories;
1247 QVBoxLayout* m_navLayout{};
1249 InteractiveLabel* m_updateLabel{};
1250 InteractiveLabel* m_crashLabel{};
1251 InteractiveLabel* m_joinLabel{};
1252 QPointer<QRecentFilesMenu> m_recentFiles;
1253 QWidget* m_recentPage{};
1254 QVBoxLayout* m_recentList{};
1255 int m_templatesPage{};
1256 int m_examplesPage{};
1257 int m_onlineExamplesPage{};
1258 OnlineExamples m_online;
1259 ThumbnailPopup* m_preview{};
1261 std::map<QString, std::optional<ProjectInfo::Info>> m_infos;
1262 std::vector<ExampleCard*> m_cards;
1265 bool m_actionTaken{};
1268StartScreen::StartScreen(
const QPointer<QRecentFilesMenu>& recentFiles, QWidget* parent)
1271 auto& skin = score::Skin::instance();
1273 setFocusPolicy(Qt::StrongFocus);
1274 setMinimumSize(Width, Height);
1278 parent->installEventFilter(
this);
1279 setGeometry(parent->rect());
1284 m_firstRun = !s.value(
"score/StartScreenSeen",
false).toBool();
1285 s.setValue(
"score/StartScreenSeen",
true);
1288 m_libraryState = DefaultLibrary::instance().state();
1290 &DefaultLibrary::instance(), &DefaultLibrary::changed,
this,
1291 &StartScreen::onLibraryChanged);
1294 m_navFont = QFont(
"Montserrat");
1295 m_navFont.setPixelSize(16);
1296 m_navFont.setWeight(QFont::DemiBold);
1297 m_sectionFont = QFont(
"Montserrat");
1298 m_sectionFont.setPixelSize(13);
1299 m_sectionFont.setWeight(QFont::Medium);
1300 m_sectionFont.setCapitalization(QFont::AllUppercase);
1301 m_sectionFont.setLetterSpacing(QFont::PercentageSpacing, 108);
1302 m_itemFont = QFont(
"Ubuntu");
1303 m_itemFont.setPixelSize(16);
1304 m_itemFont.setHintingPreference(QFont::HintingPreference::PreferFullHinting);
1305 m_itemFont.setStyleStrategy(QFont::PreferAntialias);
1306 m_smallFont = QFont(
"Ubuntu");
1307 m_smallFont.setPixelSize(13);
1308 m_smallFont.setHintingPreference(QFont::HintingPreference::PreferFullHinting);
1309 m_smallFont.setStyleStrategy(QFont::PreferAntialias);
1310 m_versionFont = QFont(
"Ubuntu");
1311 m_versionFont.setPixelSize(19);
1312 m_versionFont.setWeight(QFont::Light);
1313 m_versionFont.setHintingPreference(QFont::HintingPreference::PreferFullHinting);
1314 m_versionFont.setStyleStrategy(QFont::PreferAntialias);
1316 m_background = score::get_pixmap(
":/startscreen/startscreen-background.png");
1317 m_logo = score::get_pixmap(
":/startscreen/startscreen-logo.png");
1319 auto mainLayout =
new QVBoxLayout{
this};
1320 mainLayout->setContentsMargins(0, 0, 0, 0);
1321 mainLayout->setSpacing(0);
1323 mainLayout->addWidget(createHeader());
1325 auto body =
new QHBoxLayout;
1326 body->setContentsMargins(0, 0, 0, 0);
1327 body->setSpacing(0);
1329 m_pages =
new QStackedWidget{
this};
1330 body->addWidget(createNavigation());
1331 body->addWidget(m_pages, 1);
1332 mainLayout->addLayout(body, 1);
1334 addPage(tr(
"Home"),
"home", createHomePage(recentFiles));
1336 = addPage(tr(
"Templates"),
"new_file", [
this] {
return createTemplatesPage(); });
1337 m_examplesPage = addPage(
1338 tr(
"Local examples"),
"load_examples",
1339 [
this] {
return createExamplesPage(
false); });
1340 m_onlineExamplesPage = addPage(
1341 tr(
"Online examples"),
"net_session",
1342 [
this] {
return createExamplesPage(
true); });
1345 connect(&m_online, &OnlineExamples::updated,
this, [
this] {
1346 refreshExamplesPage();
1350 tr(
"Learn"),
"learn",
1352 tr(
"Learn ossia score"),
1353 {{tr(
"Quick start guide"),
"https://ossia.io/score-docs/quick-start.html",
1354 "version", tr(
"The recommended starting point if you are new to score")},
1355 {tr(
"Video tutorials"),
1356 "https://www.youtube.com/"
1357 "watch?v=R-3d8K6gQkw&list=PLIHLSiZpIa6YoY1_aW1yetDgZ7tZcxfEC",
1360 {tr(
"Documentation"),
"https://ossia.io/score-docs/",
"learn", {}},
1361 {tr(
"Process reference"),
"https://ossia.io/score-docs/processes.html",
"learn", {}},
1362 {tr(
"Device reference"),
"https://ossia.io/score-docs/devices.html",
"learn", {}},
1363 {tr(
"Web version"),
"https://ossia.io/score-web/",
"tutorials", {}},
1366 tr(
"Community"),
"community",
1369 {{tr(
"GitHub Discussions"),
"https://github.com/ossia/score/discussions",
1370 "forum", tr(
"Ask questions and share what you do with score")},
1371 {tr(
"Discord Chat"),
"https://discord.gg/8Hzm4UduaS",
"chat", {}},
1372 {tr(
"Report a bug or suggest a feature"),
1373 "https://github.com/ossia/score/issues",
1376 {tr(
"Support ossia score through the Suite SAT"),
"https://suite.sat.qc.ca/en",
1378 tr(
"ossia score is free software: donations fund its development")},
1379 {tr(
"Donate on Open Collective"),
"https://opencollective.com/ossia",
1380 "contribute", tr(
"ossia score is free software: donations fund its development")},
1381 {tr(
"Contributing"),
"https://ossia.io/score-docs/development",
"contribute",
1382 tr(
"Come implement your dream feature!")},
1384 addPage(tr(
"About"),
"about", [
this] {
return createAboutPage(); });
1386 m_navLayout->addStretch();
1388#if !defined(__EMSCRIPTEN__)
1389 auto exitLabel = makeItem(tr(
"Exit"),
"exit",
"",
this);
1390 exitLabel->setLeftPadding(20);
1391 exitLabel->setItemHeight(40);
1392 connect(exitLabel, &InteractiveLabel::labelPressed,
this, [
this] {
1393 choose([
this] { exitApp(); });
1395 m_navLayout->addWidget(exitLabel);
1399 m_preview =
new ThumbnailPopup{m_itemFont, m_smallFont,
this};
1402 updateLibraryNotices();
1404 checkForNewVersion();
1407StartScreen::~StartScreen()
1409 if(std::exchange(m_libraryViewRegistered,
false))
1410 DefaultLibrary::instance().removeView();
1413QWidget* StartScreen::createHeader()
1417 auto header =
new QWidget{
this};
1418 header->setFixedHeight(HeaderHeight);
1419 auto lay =
new QHBoxLayout{header};
1420 lay->setContentsMargins(0, 10, 10, 0);
1421 lay->setSpacing(12);
1424 m_updateLabel =
new InteractiveLabel{
1425 m_navFont, {},
"https://github.com/ossia/score/releases/latest/", header};
1426 m_updateLabel->setOpenExternalLink(
true);
1427 m_updateLabel->setPixmaps(
1428 score::get_pixmap(
":/icons/version_off.png"),
1429 score::get_pixmap(
":/icons/version_on.png"));
1430 m_updateLabel->setInactiveColor(StartScreenColors::Accent);
1431 m_updateLabel->hide();
1432 lay->addWidget(m_updateLabel, 0, Qt::AlignTop);
1434 auto closeLabel =
new InteractiveLabel{m_navFont, {},
"", header};
1435 closeLabel->setPixmaps(
1436 score::get_pixmap(
":/icons/close_window_off.png"),
1437 score::get_pixmap(
":/icons/close_window_on.png"));
1438 closeLabel->setLeftPadding(0);
1439 closeLabel->setIconColumn(32);
1440 closeLabel->setFixedWidth(32);
1441 closeLabel->setToolTip(tr(
"Close this window and start with an empty score"));
1442 connect(closeLabel, &InteractiveLabel::labelPressed,
this, [
this] {
1443 choose([
this] { openNewDocument(); });
1445 lay->addWidget(closeLabel, 0, Qt::AlignTop);
1450QWidget* StartScreen::createNavigation()
1452 auto nav =
new QWidget{
this};
1453 nav->setFixedWidth(NavWidth);
1454 m_navLayout =
new QVBoxLayout{nav};
1456 m_navLayout->setContentsMargins(0, 0, 0, 16);
1457 m_navLayout->setSpacing(4);
1461int StartScreen::addPage(
const QString& name,
const QString& icon, QWidget* page)
1463 const int index = addPage(name, icon, std::function<QWidget*()>{});
1464 m_pages->widget(index)->layout()->addWidget(page);
1468int StartScreen::addPage(
1469 const QString& name,
const QString& icon, std::function<QWidget*()> make)
1472 auto host =
new QWidget;
1473 auto hostLayout =
new QVBoxLayout{host};
1474 hostLayout->setContentsMargins(0, 0, 0, 0);
1475 const int index = m_pages->addWidget(host);
1476 m_pageFactories.resize(index + 1);
1477 m_pageFactories[index] = std::move(make);
1479 auto item = makeItem(name, icon,
"",
this);
1480 item->setCheckable(
true);
1481 item->setCheckedBackground(StartScreenColors::Panel);
1482 item->setLeftPadding(20);
1483 item->setItemHeight(44);
1484 connect(item, &InteractiveLabel::labelPressed,
this, [
this, index] {
1485 setCurrentPage(index);
1488 m_navItems.push_back(item);
1489 m_navLayout->addWidget(item);
1493void StartScreen::setCurrentPage(
int index)
1495 if(index >= 0 && index <
int(m_pageFactories.size()))
1497 if(
auto make = std::exchange(m_pageFactories[index], {}))
1498 m_pages->widget(index)->layout()->addWidget(make());
1500 m_pages->setCurrentIndex(index);
1501 for(
int i = 0; i < int(m_navItems.size()); i++)
1502 m_navItems[i]->setChecked(i == index);
1507QLabel* StartScreen::makeSectionTitle(
const QString& text, QWidget* parent)
1509 auto label =
new QLabel{text, parent};
1510 label->setFont(m_sectionFont);
1511 QPalette pal = label->palette();
1512 pal.setColor(QPalette::WindowText, StartScreenColors::Muted);
1513 label->setPalette(pal);
1517QLabel* StartScreen::makeHint(
const QString& text, QWidget* parent)
1519 auto hint =
new QLabel{text, parent};
1520 hint->setFont(m_itemFont);
1521 hint->setWordWrap(
true);
1522 QPalette pal = hint->palette();
1523 pal.setColor(QPalette::WindowText, StartScreenColors::Muted);
1524 hint->setPalette(pal);
1529StartScreen::makeTitleWithFilter(
const QString& title, QVBoxLayout* lay, QWidget* page)
1531 auto row =
new QHBoxLayout;
1532 row->setContentsMargins(0, 0, 8, 0);
1533 row->addWidget(makeSectionTitle(title, page));
1536 auto filter =
new FilterEdit{page};
1537 filter->setFont(m_smallFont);
1538 filter->setPlaceholderText(tr(
"Filter"));
1539 filter->setClearButtonEnabled(
true);
1540 filter->setFixedWidth(220);
1541 row->addWidget(filter);
1543 lay->addLayout(row);
1547InteractiveLabel* StartScreen::makeItem(
1548 const QString& text,
const QString& icon,
const QString& url, QWidget* parent)
1550 auto label =
new InteractiveLabel{m_itemFont, text, url, parent};
1554 score::get_pixmap(QString(
":/icons/%1_off.png").arg(icon)),
1555 score::get_pixmap(QString(
":/icons/%1_on.png").arg(icon)));
1560InteractiveLabel* StartScreen::makeAccentItem(
1561 const QString& text,
const QString& icon,
const QString& url, QWidget* parent)
1564 auto label = makeItem(text, icon, url, parent);
1565 label->setInactiveColor(StartScreenColors::Accent);
1569InteractiveLabel* StartScreen::makeExternalLink(
const Link& link, QWidget* parent)
1571 auto label = makeItem(link.text, link.icon, link.url, parent);
1572 label->setOpenExternalLink(
true);
1573 label->setToolTip(link.tooltip.isEmpty() ? link.url : link.tooltip);
1577InteractiveLabel* StartScreen::makeScoreItem(
1578 const QString& text,
const QString& icon,
const QString& path,
bool asTemplate,
1581 auto label = makeItem(text, icon, path, parent);
1582 label->setElideMode(Qt::ElideMiddle);
1585 connect(label, &InteractiveLabel::labelPressed,
this, [
this](
const QString& file) {
1591 connect(label, &InteractiveLabel::labelPressed,
this, [
this](
const QString& file) {
1592 choose([&] { openFile(file); });
1595 connect(label, &InteractiveLabel::hovered,
this, [
this, label](
bool on) {
1606QWidget* StartScreen::createHomePage(
const QPointer<QRecentFilesMenu>& recentFiles)
1608 auto page =
new QWidget;
1609 auto lay =
new QHBoxLayout{page};
1610 lay->setContentsMargins(28, 24, 28, 24);
1611 lay->setSpacing(32);
1617 auto col =
new QVBoxLayout;
1619 col->addWidget(makeSectionTitle(tr(
"Start"), page));
1621 auto newLabel = makeItem(tr(
"New empty score"),
"new_file",
"", page);
1622 connect(newLabel, &InteractiveLabel::labelPressed,
this, [
this] {
1623 choose([
this] { openNewDocument(); });
1625 col->addWidget(newLabel);
1627 auto templatesLabel = makeItem(tr(
"Start from a template"),
"new_file",
"", page);
1628 connect(templatesLabel, &InteractiveLabel::labelPressed,
this, [
this] {
1629 setCurrentPage(m_templatesPage);
1631 col->addWidget(templatesLabel);
1633 auto openLabel = makeItem(tr(
"Open a score file"),
"load",
"", page);
1634 connect(openLabel, &InteractiveLabel::labelPressed,
this, [
this] {
1635 choose([
this] { openFileDialog(); });
1637 col->addWidget(openLabel);
1640 = makeItem(tr(
"Join a collaborative session..."),
"net_session",
"", page);
1641 m_joinLabel->setToolTip(tr(
"Connect to a score session hosted on another computer"));
1642 m_joinLabel->hide();
1643 connect(m_joinLabel, &InteractiveLabel::labelPressed,
this, [
this] {
1646 col->addWidget(m_joinLabel);
1648 col->addWidget(makeLibraryNotice(page, {0, 18, 0, 0},
true));
1650 col->addSpacing(18);
1651 col->addWidget(makeSectionTitle(tr(
"New to ossia score?"), page));
1653 if(!firstRunScore.isEmpty())
1657 col->addWidget(makeHint(
1658 tr(
"Welcome! This guided score shows you around the interface and "
1659 "the main concepts in a few minutes."),
1662 auto guided = makeAccentItem(
1663 tr(
"Open the demo project"),
"version", firstRunScore, page);
1664 connect(guided, &InteractiveLabel::labelPressed,
this, [
this](
const QString& f) {
1667 col->addWidget(guided);
1669 col->addWidget(makeExternalLink(
1670 {tr(
"Read the quick start guide"),
1671 "https://ossia.io/score-docs/quick-start.html",
1678 col->addWidget(makeHint(
1679 tr(
"Start with the quick start guide: it walks you through the "
1680 "interface and your first score."),
1682 auto guide = makeAccentItem(
1683 tr(
"Read the quick start guide"),
"version",
1684 "https://ossia.io/score-docs/quick-start.html", page);
1685 guide->setOpenExternalLink(
true);
1686 col->addWidget(guide);
1690 lay->addLayout(col, 1);
1695 auto col =
new QVBoxLayout;
1697 col->addWidget(makeSectionTitle(tr(
"Recent"), page));
1699 m_recentFiles = recentFiles;
1700 m_recentPage = page;
1701 m_recentList =
new QVBoxLayout;
1702 m_recentList->setSpacing(6);
1703 col->addLayout(m_recentList);
1704 refreshRecentFiles();
1708 m_crashLabel = makeAccentItem(tr(
"Restore last session"),
"reload_crash",
"", page);
1709 m_crashLabel->setToolTip(
1710 tr(
"score did not exit cleanly last time: reopen the documents that were open"));
1711 m_crashLabel->hide();
1712 connect(m_crashLabel, &InteractiveLabel::labelPressed,
this, [
this] {
1713 choose([
this] { loadCrashedSession(); });
1715 col->addWidget(m_crashLabel);
1718 lay->addLayout(col, 1);
1724void StartScreen::refreshRecentFiles()
1729 while(
auto item = m_recentList->takeAt(0))
1731 if(
auto w = item->widget())
1742 for(
const auto& action : m_recentFiles->actions())
1744 if(shown++ >= MaxRecentFiles)
1748 const QString path = action->data().toString();
1749 if(
auto it = m_infos.find(path); it != m_infos.end() && it->second)
1751 m_recentList->addWidget(makeScoreItem(
1752 QFileInfo{path}.fileName(),
"load", path,
false, m_recentPage));
1758 auto none = makeItem(tr(
"No recent scores yet"),
"",
"", m_recentPage);
1759 none->disableInteractivity();
1760 none->setInactiveColor(StartScreenColors::Muted);
1761 m_recentList->addWidget(none);
1765QWidget* StartScreen::createTemplatesPage()
1767 auto page =
new QWidget;
1768 auto lay =
new QVBoxLayout{page};
1769 lay->setContentsMargins(0, 0, 0, 0);
1773 lay->addWidget(makeLibraryNotice(page, {28, 20, 28, 0},
false));
1775 lay->addWidget(createCardsPage(
1777 tr(
"A template opens as a new untitled score with devices, processes and a "
1778 "structure already in place."),
1779 tr(
"No templates are installed yet. Templates are .score files in the "
1780 "Templates folder of your user library (%1) or of an installed package.")
1782 availableDocumentTemplates(),
1783 [
this](
const QString& path) { choose([&] {
openTemplate(path); }); },
1784 {tr(
"Learn how to write your own templates"),
1785 "https://ossia.io/score-docs/",
1789 updateLibraryNotices();
1793QWidget* StartScreen::createAboutPage()
1800 style.text = StartScreenColors::Text;
1801 style.muted = StartScreenColors::Muted;
1802 style.hover = StartScreenColors::Hover;
1803 style.version = StartScreenColors::Version;
1804 style.outline = StartScreenColors::Outline;
1806 auto page =
new QWidget;
1807 auto lay =
new QVBoxLayout{page};
1808 lay->setContentsMargins(28, 20, 28, 16);
1813QWidget* StartScreen::createExamplesPage(
bool online)
1815 std::vector<ExampleEntry> docs;
1816 for(
auto& doc : gatherExamples())
1817 if((doc.online != nullptr) == online)
1818 docs.push_back(
std::move(doc));
1820 const QString title = online ? tr(
"Online examples") : tr(
"Local examples");
1823 return createCardsPage(
1826 ? tr(
"The examples of the documentation site show here once their list "
1827 "has been downloaded. Check your internet connection.")
1828 : tr(
"No example scores are installed yet. Examples are .score files in "
1829 "the Examples folder of your user library (%1) or of an installed "
1833 {tr(
"More examples online"),
1834 "https://ossia.io/score-docs/examples",
1839 auto page =
new QWidget;
1840 auto lay =
new QVBoxLayout{page};
1841 lay->setContentsMargins(28, 24, 28, 16);
1843 auto filter = makeTitleWithFilter(title, lay, page);
1844 lay->addWidget(makeHint(
1845 online ? tr(
"Each example is downloaded into your library the first time it "
1846 "is opened, then opens as a new, untitled score. Ctrl-click opens "
1847 "the example file itself, Alt-click does not open its web page.")
1848 : tr(
"Each example opens as a new, untitled score. Ctrl-click opens the "
1849 "example file itself, Alt-click does not open its web page."),
1851 auto noMatch = makeHint(tr(
"Nothing matches this filter."), page);
1853 lay->addWidget(noMatch);
1855 auto scroll =
new QScrollArea{page};
1856 scroll->setFrameShape(QFrame::NoFrame);
1857 scroll->setWidgetResizable(
true);
1858 scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1859 scroll->viewport()->setAutoFillBackground(
false);
1861 auto container =
new QWidget;
1862 container->setAutoFillBackground(
false);
1863 auto containerLay =
new QVBoxLayout{container};
1865 containerLay->setContentsMargins(
1870 std::map<QString, std::map<QString, std::vector<ExampleEntry>>> tree;
1871 for(
const auto& doc : docs)
1873 auto parts = doc.category.split(
'/', Qt::SkipEmptyParts);
1874 for(
auto& p : parts)
1876 const QString top = parts.empty() ? tr(
"Uncategorized") : parts.front();
1877 const QString sub = parts.size() > 1 ? parts[1] : QString{};
1878 tree[top][sub].push_back(doc);
1883 CategoryDrawer* drawer{};
1884 std::vector<ExampleCard*> cards;
1887 auto drawers = std::make_shared<std::vector<Drawer>>();
1888 auto cards = std::make_shared<std::vector<ExampleCard*>>();
1891 for(
const auto& [top, subs] : tree)
1894 for(
const auto& [sub, list] : subs)
1895 total += list.size();
1897 auto drawer =
new CategoryDrawer{m_sectionFont, top, total, 0, container};
1898 containerLay->addWidget(drawer);
1899 const auto topCards = cards->size();
1901 for(
const auto& [sub, list] : subs)
1905 drawer->body()->addWidget(makeCardGrid(list, *cards, drawer));
1910 =
new CategoryDrawer{m_itemFont, sub, int(list.size()), 1, drawer};
1911 const auto innerCards = cards->size();
1912 inner->body()->addWidget(makeCardGrid(list, *cards, inner));
1913 drawer->body()->addWidget(inner);
1914 drawers->push_back({inner, {cards->begin() + innerCards, cards->end()}});
1917 drawers->push_back({drawer, {cards->begin() + topCards, cards->end()}});
1923 filter, &QLineEdit::textChanged, container,
1924 [filter, noMatch, drawers, cards, filtering =
false]()
mutable {
1925 const auto words = filter->words();
1926 if(!words.empty() && !filtering)
1927 for(
auto& d : *drawers)
1928 d.expanded = d.drawer->expanded();
1930 for(
auto card : *cards)
1931 card->setVisible(card->
matches(words));
1934 for(
auto& d : *drawers)
1936 const int count = std::count_if(
1937 d.cards.begin(), d.cards.end(), [](
auto* c) { return !c->isHidden(); });
1938 d.drawer->setCount(count);
1939 d.drawer->setVisible(count > 0);
1941 d.drawer->setExpanded(count > 0);
1943 d.drawer->setExpanded(d.expanded);
1947 filtering = !words.empty();
1948 noMatch->setVisible(!any);
1951 containerLay->addStretch();
1952 scroll->setWidget(container);
1953 lay->addWidget(scroll, 1);
1955 lay->addWidget(makeExternalLink(
1956 {tr(
"More examples online"),
"https://ossia.io/score-docs/examples",
1957 "load_examples", {}},
1962std::vector<StartScreen::ExampleEntry> StartScreen::gatherExamples()
const
1964 std::vector<ExampleEntry> out;
1966 for(
const auto& doc : availableExampleDocuments())
1967 out.push_back({doc.name, doc.category, doc.path,
nullptr});
1971 for(
const auto& ex : m_online.examples())
1973 QString category = ex.section;
1974 if(!ex.group.isEmpty())
1975 category +=
" / " + ex.group;
1982void StartScreen::openOnlineExample(
const OnlineExample& ex,
bool direct,
bool openPage)
1987 if(openPage && !ex.page.isEmpty())
1989 if(
const QUrl url{ex.page}; url.isValid() && !url.scheme().isEmpty())
1990 QDesktopServices::openUrl(url);
1993 m_online.
install(ex, [
this, direct](QString path) {
1997 this, tr(
"Download failed"),
1998 tr(
"This example could not be downloaded. Check your connection and "
2002 choose([&] { direct ? openFile(path) :
openTemplate(path); });
2006void StartScreen::showOnlineExampleMenu(
const OnlineExample& ex,
const QPoint& globalPos)
2019 auto menu =
new QMenu{
this};
2020 menu->setAttribute(Qt::WA_DeleteOnClose);
2021 const auto add = [
this, menu](
const QString& text,
auto&& f) {
2022 auto action = menu->addAction(text);
2023 connect(action, &QAction::triggered,
this, std::forward<
decltype(f)>(f),
2024 Qt::QueuedConnection);
2028 add(local ? tr(
"Open your copy as a new score") : tr(
"Download and open as a new score"),
2029 [this, ex] { openOnlineExample(ex,
false,
false); });
2030 add(local ? tr(
"Open the example file") : tr(
"Download and open the example file"),
2031 [this, ex] { openOnlineExample(ex,
true,
false); });
2032 if(!ex.page.isEmpty())
2034 add(tr(
"Open the web page"), [ex] {
2035 if(
const QUrl url{ex.page}; url.isValid() && !url.scheme().isEmpty())
2036 QDesktopServices::openUrl(url);
2041 menu->addSeparator();
2042 if(m_online.isDownloading(ex))
2044 menu->addAction(tr(
"Downloading"))->setEnabled(
false);
2049 : tr(
"Download the online version again"),
2050 [this, ex] { replaceLocalCopy(ex); });
2053 menu->popup(globalPos);
2056void StartScreen::replaceLocalCopy(
const OnlineExample& ex)
2059 if(m_online.isDownloading(ex) || m_replaceAsked.contains(ex.id))
2065 const bool folder = ex.format == QLatin1String{
"zip"};
2068 m_replaceAsked.insert(ex.id);
2069 const auto answer = score::question(
2070 this, tr(
"Download again"),
2071 (folder ? tr(
"Replace your copy of %1 with the online version? Changes and "
2072 "files added to its folder are lost.\n\n%2")
2073 : tr(
"Replace your copy of %1 with the online version? Changes to "
2074 "it are lost.\n\n%2"))
2075 .arg(ex.name, QDir::toNativeSeparators(copy)));
2076 m_replaceAsked.erase(ex.id);
2077 if(answer != QMessageBox::Yes)
2081 m_online.
install(ex, [self = QPointer{
this}](
const QString& path) {
2087 self, tr(
"Download failed"),
2088 tr(
"The example could not be downloaded. Your copy is unchanged."));
2091 self->m_infos.erase(path);
2092 self->refreshExamplesPage();
2096void StartScreen::requestThumbnail(
const OnlineExample& ex, ExampleCard* card)
2100 [card = QPointer{card}](
const QByteArray& data) {
2103 if(QImage img; img.loadFromData(data))
2104 card->setThumbnailImage(img);
2109void StartScreen::refreshExamplesPage()
2112 refreshExamplesPage(m_examplesPage,
false);
2113 refreshExamplesPage(m_onlineExamplesPage,
true);
2116void StartScreen::refreshExamplesPage(
int index,
bool online)
2119 if(index <= 0 || m_pageFactories[index])
2122 auto host = m_pages->widget(index);
2125 if(
auto edit = host->findChild<QLineEdit*>())
2127 filter = edit->text();
2128 focused = edit->hasFocus();
2131 std::erase_if(m_cards, [host](
auto* card) {
return host->isAncestorOf(card); });
2132 qDeleteAll(host->findChildren<QWidget*>(Qt::FindDirectChildrenOnly));
2133 host->layout()->addWidget(createExamplesPage(online));
2135 if(
auto edit = host->findChild<QLineEdit*>())
2137 edit->setText(filter);
2143QWidget* StartScreen::makeCardGrid(
2144 const std::vector<ExampleEntry>& docs, std::vector<ExampleCard*>& cards,
2147 auto holder =
new QWidget{parent};
2148 holder->setAutoFillBackground(
false);
2149 auto grid =
new CardFlowLayout{holder, 14};
2151 for(
const auto& doc : docs)
2153 auto card =
new ExampleCard{m_itemFont, m_smallFont, doc.name, {},
2156 if(
const auto* online = doc.online)
2158 ProjectInfo::Info info;
2159 info.name = online->name;
2160 info.description = online->description;
2161 info.url = online->page;
2162 card->setInfo(info);
2164 QString tip = online->name;
2165 if(!online->description.isEmpty())
2166 tip +=
"\n\n" + online->description;
2167 if(doc.path.isEmpty())
2169 card->setDownloadSize(online->size);
2175 changed ? tr(
"Updated online") : tr(
"In your library"),
2176 changed ? StartScreenColors::Accent : StartScreenColors::Muted);
2179 tip += tr(
"Changed online. Right-click to download it again.") +
"\n";
2180 tip += QDir::toNativeSeparators(doc.path);
2182 card->setToolTip(tip);
2183 if(!online->image.isEmpty())
2184 requestThumbnail(*online, card);
2186 card->onContextMenu = [
this, ex = *online](
const QPoint& pos) {
2187 showOnlineExampleMenu(ex, pos);
2191 = [
this, ex = *online](
const QString&, Qt::KeyboardModifiers mods) {
2193 ex, mods & Qt::ControlModifier, !(mods & Qt::AltModifier));
2198 card->onActivated = [
this](
const QString& path, Qt::KeyboardModifiers mods) {
2199 openExample(path, mods & Qt::ControlModifier, !(mods & Qt::AltModifier));
2201 requestInfo(doc.path, *card);
2204 card->setKeywords(doc.category);
2205 grid->addWidget(card);
2206 m_cards.push_back(card);
2207 cards.push_back(card);
2212QWidget* StartScreen::createCardsPage(
2213 const QString& title,
const QString& hint,
const QString& emptyHint,
2214 const std::vector<DocumentTemplate>& docs,
2215 std::function<
void(
const QString&)> onActivated,
const Link& more)
2217 auto page =
new QWidget;
2218 auto lay =
new QVBoxLayout{page};
2219 lay->setContentsMargins(28, 24, 28, 16);
2224 lay->addWidget(makeSectionTitle(title, page));
2225 lay->addWidget(makeHint(emptyHint, page));
2229 auto filter = makeTitleWithFilter(title, lay, page);
2230 lay->addWidget(makeHint(hint, page));
2231 auto noMatch = makeHint(tr(
"Nothing matches this filter."), page);
2233 lay->addWidget(noMatch);
2235 auto scroll =
new QScrollArea{page};
2236 scroll->setFrameShape(QFrame::NoFrame);
2237 scroll->setWidgetResizable(
true);
2238 scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
2239 scroll->viewport()->setAutoFillBackground(
false);
2241 auto container =
new QWidget;
2242 container->setAutoFillBackground(
false);
2243 auto grid =
new CardFlowLayout{container, 14};
2244 grid->setContentsMargins(0, 4, 8, 4);
2245 std::vector<ExampleCard*> cards;
2247 for(
const auto& doc : docs)
2249 const QString subtitle = !doc.category.isEmpty() ? doc.category
2250 : doc.source ==
"library" ? tr(
"User library")
2252 auto card =
new ExampleCard{m_itemFont, m_smallFont, doc.name,
2253 subtitle, doc.path, container};
2254 card->onActivated = [onActivated](
const QString& path, Qt::KeyboardModifiers) {
2257 card->setKeywords(doc.source);
2258 grid->addWidget(card);
2259 m_cards.push_back(card);
2260 cards.push_back(card);
2261 requestInfo(doc.path, *card);
2265 filter, &QLineEdit::textChanged, container,
2266 [filter, noMatch, cards = std::move(cards)] {
2267 const auto words = filter->words();
2269 for(
auto card : cards)
2271 const bool m = card->matches(words);
2272 card->setVisible(m);
2275 noMatch->setVisible(!any);
2278 scroll->setWidget(container);
2279 lay->addWidget(scroll, 1);
2282 lay->addWidget(makeExternalLink(more, page));
2291StartScreen::createLinksPage(
const QString& title,
const std::vector<Link>& links)
2293 auto page =
new QWidget;
2294 auto lay =
new QVBoxLayout{page};
2295 lay->setContentsMargins(28, 24, 28, 24);
2298 lay->addWidget(makeSectionTitle(title, page));
2299 for(
const auto& link : links)
2300 lay->addWidget(makeExternalLink(link, page));
2306void StartScreen::openExample(
const QString& path,
bool direct,
bool openPage)
2315 std::optional<ProjectInfo::Info> info;
2316 if(
auto it = m_infos.find(path); it != m_infos.end() && it->second)
2319 info = ProjectInfo::peek(path);
2321 if(info && !info->url.isEmpty())
2323 if(
const QUrl url{info->url}; url.isValid() && !url.scheme().isEmpty())
2324 QDesktopServices::openUrl(url);
2328 choose([&] { direct ? openFile(path) :
openTemplate(path); });
2331void StartScreen::requestInfo(
const QString& path)
2333 if(m_infos.find(path) != m_infos.end())
2335 m_infos.emplace(path, std::nullopt);
2338 score::TaskPool::instance().post([path, self = QPointer{
this}] {
2339 auto info = ProjectInfo::peek(path);
2340 QMetaObject::invokeMethod(
2341 QCoreApplication::instance(), [self, path, info = std::move(info)] {
2343 self->onInfoLoaded(path, info);
2344 }, Qt::QueuedConnection);
2348void StartScreen::requestInfo(
const QString& path, ExampleCard& card)
2350 if(
auto it = m_infos.find(path); it != m_infos.end())
2353 card.setInfo(*it->second);
2359void StartScreen::onInfoLoaded(
2360 const QString& path,
const std::optional<ProjectInfo::Info>& info)
2362 m_infos[path] = info;
2366 for(
auto card : m_cards)
2367 if(card->path() == path)
2368 card->setInfo(*info);
2371void StartScreen::showPreview(InteractiveLabel* label)
2373 const QString& path = label->url();
2374 ProjectInfo::Info info;
2375 if(
auto it = m_infos.find(path); it != m_infos.end() && it->second)
2378 m_preview->setContent(
2379 info.thumbnail, info.name.isEmpty() ? label->text() : info.name, info.author,
2380 info.description, QDir::toNativeSeparators(path));
2382 m_preview->
showFor(label, m_pages->geometry().adjusted(8, 8, -8, -8));
2387 m_crashLabel->show();
2393 m_joinLabel->show();
2399 m_actionTaken =
true;
2405 m_actionTaken =
false;
2406 refreshRecentFiles();
2412void StartScreen::checkForNewVersion()
2414#if defined(__EMSCRIPTEN__)
2419 auto& tp = score::ThreadPool::instance();
2420 auto t = tp.acquireThread();
2421 QMetaObject::invokeMethod(t, [t, self = QPointer{
this}] {
2422 auto getLastVersion =
new HTTPGet{
2423 QUrl(
"https://ossia.io/score-last-version.txt"), [self](
const QByteArray& data) {
2424 const auto version = QString::fromUtf8(data.simplified());
2425 if(QVersionNumber::fromString(version)
2426 > QVersionNumber::fromString(SCORE_TAG_NO_V))
2428 QMetaObject::invokeMethod(QCoreApplication::instance(), [self, version] {
2430 self->showUpdateAvailable(version);
2434 connect(getLastVersion, &QObject::destroyed, t, [] {
2435 QMetaObject::invokeMethod(QCoreApplication::instance(), [] {
2436 auto& tp = score::ThreadPool::instance();
2444void StartScreen::showUpdateAvailable(
const QString& version)
2446 m_updateLabel->setText(
2447 tr(
"New version %1 is available, click to update").arg(version));
2448 m_updateLabel->setToolTip(tr(
"Open the download page in your browser"));
2449 m_updateLabel->show();
2452QWidget* StartScreen::makeLibraryNotice(
2453 QWidget* parent,
const QMargins& margins,
bool linksToTemplates)
2455 auto box =
new QWidget{parent};
2456 auto lay =
new QVBoxLayout{box};
2457 lay->setContentsMargins(margins);
2459 lay->addWidget(makeSectionTitle(tr(
"Default library"), box));
2461 auto hint = makeHint({}, box);
2462 lay->addWidget(hint);
2464 auto action = makeAccentItem({},
"library",
"", box);
2465 connect(action, &InteractiveLabel::labelPressed,
this, [
this] {
2466 if(m_libraryInstalled)
2467 setCurrentPage(m_templatesPage);
2469 DefaultLibrary::instance().install();
2471 lay->addWidget(action);
2474 m_libraryNotices.push_back({box, hint, action, linksToTemplates});
2478void StartScreen::onLibraryChanged()
2480 const auto previous = std::exchange(m_libraryState, DefaultLibrary::instance().state());
2484 m_libraryInstalled =
true;
2486 rebuildTemplatesPage();
2487 refreshExamplesPage();
2491 m_libraryInstalled =
false;
2493 updateLibraryNotices();
2496void StartScreen::updateLibraryNotices()
2499 const auto& lib = DefaultLibrary::instance();
2500 const auto state = m_libraryState;
2506 case State::Current:
2507 if(m_libraryInstalled)
2509 hint = tr(
"The default library is installed.");
2510 action = tr(
"Show the templates");
2513 case State::Missing:
2514 hint = tr(
"The default library is not installed. It provides the templates, "
2515 "presets and media used by the examples.");
2516 action = lib.downloadSize() > 0
2517 ? tr(
"Install the default library (%1)")
2518 .arg(QLocale{}.formattedDataSize(lib.downloadSize()))
2519 : tr(
"Install the default library");
2521 case State::UpdateAvailable:
2522 hint = tr(
"A new version of the default library is available.");
2523 action = lib.downloadSize() > 0
2524 ? tr(
"Update the default library (%1)")
2525 .arg(QLocale{}.formattedDataSize(lib.downloadSize()))
2526 : tr(
"Update the default library");
2528 case State::Installing:
2529 hint = lib.progress() >= 0.
2530 ? tr(
"Downloading the default library: %1%")
2531 .arg(
int(lib.progress() * 100.))
2532 : tr(
"Downloading the default library");
2535 hint = lib.error().isEmpty()
2536 ? tr(
"The default library could not be installed.")
2537 : tr(
"The default library could not be installed: %1").arg(lib.error());
2538 action = tr(
"Try again");
2542 const bool installAction = state == State::Missing || state == State::UpdateAvailable
2543 || state == State::Failed;
2544 if(installAction && !lib.canInstall())
2546 hint +=
" " + tr(
"Install it from the Packages settings.");
2550 std::erase_if(m_libraryNotices, [](
const LibraryNotice& n) {
return !n.box; });
2551 for(
auto& n : m_libraryNotices)
2553 const bool showAction
2554 = !action.isEmpty() && (n.linksToTemplates || !m_libraryInstalled);
2555 n.hint->setText(hint);
2556 n.action->setText(action);
2557 n.action->setVisible(showAction);
2558 n.box->setVisible(!hint.isEmpty());
2562void StartScreen::rebuildTemplatesPage()
2565 if(m_templatesPage <= 0 || m_pageFactories[m_templatesPage])
2568 auto host = m_pages->widget(m_templatesPage);
2569 std::erase_if(m_cards, [host](
auto* card) {
return host->isAncestorOf(card); });
2570 for(
auto* w : host->findChildren<QWidget*>(Qt::FindDirectChildrenOnly))
2575 host->layout()->addWidget(createTemplatesPage());
2578void StartScreen::paintEvent(QPaintEvent* event)
2580 QPainter painter(
this);
2581 painter.setRenderHint(QPainter::Antialiasing,
true);
2582 painter.setRenderHint(QPainter::TextAntialiasing,
true);
2583 painter.setRenderHint(QPainter::SmoothPixmapTransform,
true);
2587 painter.fillRect(rect(), Qt::black);
2588 if(!m_background.isNull())
2590 const QSizeF logical = m_background.deviceIndependentSize();
2592 = std::max(width() / logical.width(), height() / logical.height());
2593 const QSizeF size = logical * cover;
2595 QRectF{QPointF{0, height() - size.height()}, size}, m_background,
2596 m_background.rect());
2602 QLinearGradient fade{QPointF{0, 0}, QPointF{0, qreal(HeaderHeight)}};
2603 fade.setColorAt(0, QColor{0, 0, 0, 255});
2604 fade.setColorAt(0.92, QColor{0, 0, 0, 255});
2605 fade.setColorAt(1, QColor{0, 0, 0, 0});
2606 painter.fillRect(QRect{0, 0, width(), HeaderHeight}, fade);
2612 if(!m_logo.isNull())
2614 const QSizeF logical = m_logo.deviceIndependentSize();
2615 scale = std::max(Width / logical.width(), Height / logical.height());
2616 painter.drawPixmap(QRectF{QPointF{}, logical * scale}, m_logo, m_logo.rect());
2620 painter.setFont(m_versionFont);
2621 painter.setPen(QPen{StartScreenColors::Version});
2624 const qreal x = 217 * scale;
2625 const QFontMetrics fm{m_versionFont};
2627 QPointF(x, 188 * scale), fm.elidedText(
2628 QCoreApplication::applicationVersion(),
2629 Qt::ElideMiddle,
int(width() - x - 60)));
2634 QRect{0, HeaderHeight, NavWidth, height() - HeaderHeight}, QColor{0, 0, 0, 120});
2639 QRect{NavWidth, HeaderHeight, width() - NavWidth, height() - HeaderHeight},
2640 StartScreenColors::Panel);
2643void StartScreen::showEvent(QShowEvent* event)
2645 QWidget::showEvent(event);
2646 if(!std::exchange(m_libraryViewRegistered,
true))
2647 DefaultLibrary::instance().addView();
2648 if(
auto* p = parentWidget())
2649 setGeometry(p->rect());
2654void StartScreen::hideEvent(QHideEvent* event)
2656 QWidget::hideEvent(event);
2657 if(std::exchange(m_libraryViewRegistered,
false))
2658 DefaultLibrary::instance().removeView();
2661bool StartScreen::eventFilter(QObject* watched, QEvent* event)
2663 if(watched == parentWidget() && event->type() == QEvent::Resize)
2664 setGeometry(parentWidget()->rect());
2665 return QWidget::eventFilter(watched, event);
2668void StartScreen::keyPressEvent(QKeyEvent* event)
2670 switch(event->key())
2672 case Qt::Key_Escape:
2673 choose([
this] { openNewDocument(); });
2679 (m_pages->currentIndex() + m_pages->count() - 1) % m_pages->count());
2684 setCurrentPage((m_pages->currentIndex() + 1) % m_pages->count());
2688 QWidget::keyPressEvent(event);
2692void StartScreen::closeEvent(QCloseEvent* event)
2696 choose([
this] { openNewDocument(); });
2697 QWidget::closeEvent(event);
The window shown when score starts without a document.
Definition StartScreen.hpp:999
A score presented as a card: thumbnail, name, author.
Definition StartScreen.hpp:580
static constexpr int Gap
Vertical space between two rows of the list.
Definition StartScreen.hpp:629
static constexpr int Step
How far a subsection steps right of its section.
Definition StartScreen.hpp:631
State
Definition DefaultLibrary.hpp:33
@ Current
Installed, and no newer version is known.
@ Installing
A download or an extraction is under way.
Definition StartScreen.hpp:730
void setBadge(const QString &text, const QColor &color)
A short text in the top right corner of the thumbnail.
Definition StartScreen.hpp:761
void setKeywords(const QString &k)
Extra text the filter looks into, e.g. the category.
Definition StartScreen.hpp:794
bool matches(const QStringList &words) const
Every one of words is in the name, author, description, path or keywords.
Definition StartScreen.hpp:797
std::function< void(const QPoint &globalPos)> onContextMenu
Right click; the card has no menu without it.
Definition StartScreen.hpp:814
std::function< void(const QString &path, Qt::KeyboardModifiers mods)> onActivated
Ctrl (Cmd on macOS): the file is opened itself; Alt: no web page.
Definition StartScreen.hpp:812
Definition StartScreen.hpp:949
QStringList words() const
The words typed, each of which a card has to match.
Definition StartScreen.hpp:954
A clickable label with an optional icon and a hover state.
Definition StartScreen.hpp:176
void setIconColumn(int px)
Width reserved for the icon, so that texts align whatever the icon size.
Definition StartScreen.hpp:193
void refresh()
Emits updated() only if the manifest changed.
Definition OnlineExamples.cpp:303
void loadCache()
Synchronous, no network.
Definition OnlineExamples.cpp:281
static bool changedOnline(const OnlineExample &ex)
Definition OnlineExamples.cpp:201
static QString localPath(const OnlineExample &ex)
Empty until the example has been installed.
Definition OnlineExamples.cpp:172
static QString installFolder(const OnlineExample &ex)
Definition OnlineExamples.cpp:147
void install(const OnlineExample &ex, std::function< void(QString)> done, bool replace=false)
Definition OnlineExamples.cpp:447
Definition StartScreen.hpp:1082
void joinSession()
Join a collaborative session hosted by another instance of score.
void addLoadCrashedSession()
Shows the "Restore last session" entry.
Definition StartScreen.hpp:2385
void dismiss()
Closes the start screen without opening anything (something else took over).
Definition StartScreen.hpp:2397
void addJoinSession()
Shows the "Join a collaborative session" entry.
Definition StartScreen.hpp:2391
void reopen()
Shows the start screen again after an action that led nowhere (cancelled dialog......
Definition StartScreen.hpp:2403
void openTemplate(const QString &file)
Opens the file as a new untitled document (templates, examples).
Utilities for OSSIA data structures.
Definition DeviceInterface.hpp:35
Base toolkit upon which the software is built.
Definition Application.cpp:83
QString libraryRootPath()
Root of the user library, from the settings.
Definition DocumentTemplates.cpp:27
void setCursor(Qt::CursorShape c)
setCursor sets the cursor safely.
Definition ClearLayout.cpp:8
QString firstRunDocumentTemplate()
The guided score for first-time users, or an empty string.
Definition DocumentTemplates.cpp:163
Definition ObjectMatches.hpp:6
One entry of assets/scores/index.json.
Definition OnlineExamples.hpp:16