summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSamuel Ghinet <samuel.ghinet@qt.io>2023-01-18 00:15:38 +0200
committerThomas Hartmann <thomas.hartmann@qt.io>2023-02-22 10:09:25 +0000
commited503f3db50381ddf66d6564494fca51d51086bc (patch)
treecbeb7c28ca1754f61acb85813ea1c5e8243f3c9b
parentde8ea89e7584e1b4c4e05701f95c7f6d00948ccf (diff)
downloadqt-creator-ed503f3db50381ddf66d6564494fca51d51086bc.tar.gz
QmlDesigner: Make ContentLibrary textures downloadable
At this point the textures_bundle is still required, but only because of the icons of the textures. Also, some changes should be done for the visuals of the downloading. Also, did a fix in FileDownloader: In case the URL given does not look to be an image file, we should cancel the download instead of treating it as a zip archive--it can be that eg we were redirected to a sign-in page and we don't want to download the content of the page and save it as a zip file. (cherry picked from commit 1a6cc6fa5eeb79f6cbc2d58b5205dbcddc9a3581) Task-number: QDS-8664 Change-Id: Iec40e540c116030288df76e1922eab56ba323d1e Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io> Reviewed-by: Samuel Ghinet <samuel.ghinet@qt.io>
-rw-r--r--share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexture.qml227
-rw-r--r--share/qtcreator/qmldesigner/contentLibraryQmlSource/TextureProgressBar.qml62
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.cpp70
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.h21
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturescategory.cpp8
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturescategory.h2
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.cpp26
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.h5
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp28
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h3
-rw-r--r--src/plugins/qmldesigner/utils/filedownloader.cpp63
-rw-r--r--src/plugins/qmldesigner/utils/filedownloader.h14
-rw-r--r--src/plugins/qmldesigner/utils/fileextractor.cpp107
-rw-r--r--src/plugins/qmldesigner/utils/fileextractor.h16
-rw-r--r--src/plugins/qmldesigner/utils/imageutils.cpp5
-rw-r--r--src/plugins/qmldesigner/utils/imageutils.h2
-rw-r--r--src/plugins/qmldesignerbase/utils/designersettings.cpp2
-rw-r--r--src/plugins/qmldesignerbase/utils/designersettings.h1
-rw-r--r--src/plugins/studiowelcome/examplecheckout.cpp75
-rw-r--r--src/plugins/studiowelcome/examplecheckout.h2
20 files changed, 608 insertions, 131 deletions
diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexture.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexture.qml
index 494bebd07c..a749d8d52d 100644
--- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexture.qml
+++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTexture.qml
@@ -9,15 +9,141 @@ import QtQuick.Controls
import StudioTheme 1.0 as StudioTheme
-Image {
+import WebFetcher 1.0
+
+Item {
id: root
- source: modelData.textureIcon
- visible: modelData.textureVisible
- cache: false
+ // Download states: "" (ie default, not downloaded), "unavailable", "downloading", "downloaded",
+ // "failed"
+ property string downloadState: modelData.isDownloaded() ? "downloaded" : ""
+ property bool delegateVisible: modelData.textureVisible
+
+ property alias allowCancel: progressBar.closeButtonVisible
+ property alias progressValue: progressBar.value
+ property alias progressText: progressLabel.text
signal showContextMenu()
+ function statusText()
+ {
+ if (root.downloadState === "downloaded")
+ return qsTr("Texture was already downloaded.")
+ if (root.downloadState === "unavailable")
+ return qsTr("Network/Texture unavailable or broken Link.")
+ if (root.downloadState === "failed")
+ return qsTr("Could not download texture.")
+
+ return qsTr("Click to download the texture.")
+ }
+
+ Rectangle {
+ id: downloadPane
+ anchors.fill: parent
+ color: StudioTheme.Values.themeThumbnailBackground
+ border.color: "#00000000"
+
+ visible: root.downloadState === "downloading"
+
+ TextureProgressBar {
+ id: progressBar
+ anchors.rightMargin: 10
+ anchors.leftMargin: 10
+
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+
+ visible: false
+
+ onCancelRequested: {
+ downloader.cancel()
+ }
+
+ Text {
+ id: progressLabel
+ color: StudioTheme.Values.themeTextColor
+ text: qsTr("Progress:")
+ anchors.bottom: parent.top
+ anchors.bottomMargin: 5
+ anchors.left: parent.left
+ font.pixelSize: 12
+ }
+
+ Row {
+ anchors.top: parent.bottom
+ anchors.topMargin: 5
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ Text {
+ id: progressAmount
+ color: StudioTheme.Values.themeTextColor
+ text: progressBar.value.toFixed(1)
+
+ font.pixelSize: 12
+ }
+
+ Text {
+ id: percentSign
+ color: StudioTheme.Values.themeTextColor
+ text: qsTr("%")
+ font.pixelSize: 12
+ }
+ }
+ } // TextureProgressBar
+ } // Rectangle
+
+ Image {
+ id: image
+ anchors.fill: parent
+
+ source: modelData.textureIcon
+ visible: root.delegateVisible && root.downloadState != "downloading"
+ cache: false
+
+ property string webUrl: modelData.textureWebUrl
+
+ Text {
+ id: downloadIcon
+ color: root.downloadState === "unavailable" || root.downloadState === "failed"
+ ? StudioTheme.Values.themeRedLight
+ : StudioTheme.Values.themeTextColor
+
+ font.family: StudioTheme.Constants.iconFont.family
+ text: root.downloadState === "unavailable"
+ ? StudioTheme.Constants.downloadUnavailable
+ : StudioTheme.Constants.download
+
+ font.pixelSize: 22
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ anchors.bottomMargin: 0
+
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+
+ visible: root.downloadState !== "downloaded"
+ }
+
+ ToolTip {
+ id: tooltip
+ // contentWidth is not calculated correctly by the toolTip (resulting in a wider tooltip than
+ // needed). Using a helper Text to calculate the correct width
+ contentWidth: helperText.width
+ bottomInset: -2
+ text: modelData.textureToolTip + (downloadIcon.visible
+ ? "\n\n" + root.statusText()
+ : "")
+ delay: 1000
+
+ Text {
+ id: helperText
+ text: modelData.textureToolTip
+ visible: false
+ }
+ }
+ } // Image
+
MouseArea {
id: mouseArea
@@ -25,27 +151,90 @@ Image {
acceptedButtons: Qt.LeftButton | Qt.RightButton
hoverEnabled: true
+ onEntered: tooltip.visible = image.visible
+ onExited: tooltip.visible = false
+
onPressed: (mouse) => {
- if (mouse.button === Qt.LeftButton)
- rootView.startDragTexture(modelData, mapToGlobal(mouse.x, mouse.y))
- else if (mouse.button === Qt.RightButton)
+ if (mouse.button === Qt.LeftButton) {
+ if (root.downloadState === "downloaded")
+ rootView.startDragTexture(modelData, mapToGlobal(mouse.x, mouse.y))
+ } else if (mouse.button === Qt.RightButton) {
root.showContextMenu()
+ }
+ }
+
+ onClicked: {
+ if (!rootView.markTextureDownloading())
+ return
+
+ if (root.downloadState !== "" && root.downloadState !== "failed")
+ return
+
+ progressBar.visible = true
+ tooltip.visible = false
+ root.progressText = qsTr("Downloading...")
+ root.allowCancel = true
+ root.progressValue = Qt.binding(function() { return downloader.progress })
+
+ mouseArea.enabled = false
+ root.downloadState = ""
+ root.downloadStateChanged()
+ downloader.start()
}
}
- ToolTip {
- visible: mouseArea.containsMouse
- // contentWidth is not calculated correctly by the toolTip (resulting in a wider tooltip than
- // needed). Using a helper Text to calculate the correct width
- contentWidth: helperText.width
- bottomInset: -2
- text: modelData.textureToolTip
- delay: 1000
+ FileDownloader {
+ id: downloader
+ url: image.webUrl
+ probeUrl: false
+ downloadEnabled: true
+ onDownloadStarting: {
+ root.downloadState = "downloading"
+ root.downloadStateChanged()
+ }
- Text {
- id: helperText
- text: modelData.textureToolTip
- visible: false
+ onFinishedChanged: {
+ root.progressText = qsTr("Extracting...")
+ root.allowCancel = false
+ root.progressValue = Qt.binding(function() { return extractor.progress })
+
+ extractor.extract()
+ }
+
+ onDownloadCanceled: {
+ root.progressText = ""
+ root.progressValue = 0
+
+ root.downloadState = "failed"
+ root.downloadStateChanged()
+ mouseArea.enabled = true
+
+ rootView.markNoTextureDownloading()
+ }
+
+ onDownloadFailed: {
+ root.downloadState = "failed"
+ root.downloadStateChanged()
+ mouseArea.enabled = true
+
+ rootView.markNoTextureDownloading()
+ }
+ }
+
+ FileExtractor {
+ id: extractor
+ archiveName: downloader.completeBaseName
+ sourceFile: downloader.tempFile
+ targetPath: modelData.textureParentPath
+ alwaysCreateDir: false
+ clearTargetPathContents: false
+ onFinishedChanged: {
+ mouseArea.enabled = true
+ modelData.setDownloaded()
+ root.downloadState = "downloaded"
+ root.downloadStateChanged()
+
+ rootView.markNoTextureDownloading()
}
}
}
diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/TextureProgressBar.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/TextureProgressBar.qml
new file mode 100644
index 0000000000..fcd7437961
--- /dev/null
+++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/TextureProgressBar.qml
@@ -0,0 +1,62 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+import QtQuick
+import QtQuick.Controls
+
+import StudioTheme as StudioTheme
+
+Item {
+ id: root
+ width: 272
+ height: 25
+ property int value: 0
+ property bool closeButtonVisible
+
+ readonly property int margin: 4
+
+ readonly property string qdsBrand: "#57B9FC"
+
+ signal cancelRequested
+
+ Rectangle {
+ id: progressBarGroove
+ color: StudioTheme.Values.themeThumbnailLabelBackground
+ anchors.fill: parent
+ }
+
+ Rectangle {
+ id: progressBarTrack
+ width: root.value * ((root.width - closeButton.width) - 2 * root.margin) / 100
+ color: root.qdsBrand
+ border.color: "#002e769e"
+ anchors.left: parent.left
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.margins: root.margin
+ }
+
+ Text {
+ id: closeButton
+ visible: root.closeButtonVisible
+ width: 20
+ text: StudioTheme.Constants.closeCross
+ color: root.qdsBrand
+ horizontalAlignment: Qt.AlignHCenter
+ verticalAlignment: Qt.AlignVCenter
+ font.family: StudioTheme.Constants.iconFont.family
+ font.pixelSize: StudioTheme.Values.myIconFontSize
+
+ anchors.right: parent.right
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.margins: root.margin
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ root.cancelRequested()
+ }
+ }
+ }
+}
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.cpp
index 84cd0f9836..600d84368e 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.cpp
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.cpp
@@ -4,20 +4,41 @@
#include "contentlibrarytexture.h"
#include "imageutils.h"
+#include <utils/algorithm.h>
+
+#include <QDir>
+#include <QFileInfo>
namespace QmlDesigner {
-ContentLibraryTexture::ContentLibraryTexture(QObject *parent, const QString &path, const QUrl &icon)
+ContentLibraryTexture::ContentLibraryTexture(QObject *parent, const QFileInfo &iconFileInfo,
+ const QString &downloadPath, const QUrl &icon, const QString &webUrl)
: QObject(parent)
- , m_path(path)
+ , m_iconPath(iconFileInfo.filePath())
+ , m_downloadPath(downloadPath)
+ , m_webUrl(webUrl)
+ , m_baseName{iconFileInfo.baseName()}
, m_icon(icon)
{
- m_toolTip = QLatin1String("%1\n%2").arg(path.split('/').last(), ImageUtils::imageInfo(path));
+ m_fileExt = computeFileExt();
+
+ QString fileName;
+ QString imageInfo;
+ if (m_fileExt.isEmpty()) {
+ imageInfo = ImageUtils::imageInfo(m_iconPath, false);
+ fileName = m_baseName + m_defaultExt;
+ } else {
+ fileName = m_baseName + m_fileExt;
+ QString fullDownloadPath = m_downloadPath + "/" + fileName;
+ imageInfo = ImageUtils::imageInfo(fullDownloadPath, false);
+ }
+
+ m_toolTip = QLatin1String("%1\n%2").arg(fileName, imageInfo);
}
bool ContentLibraryTexture::filter(const QString &searchText)
{
- if (m_visible != m_path.contains(searchText, Qt::CaseInsensitive)) {
+ if (m_visible != m_iconPath.contains(searchText, Qt::CaseInsensitive)) {
m_visible = !m_visible;
emit textureVisibleChanged();
}
@@ -32,7 +53,46 @@ QUrl ContentLibraryTexture::icon() const
QString ContentLibraryTexture::path() const
{
- return m_path;
+ return m_iconPath;
+}
+
+QString ContentLibraryTexture::computeFileExt()
+{
+ const QFileInfoList files = QDir(m_downloadPath).entryInfoList(QDir::Files);
+ const QFileInfoList textureFiles = Utils::filtered(files, [this](const QFileInfo &fi) {
+ return fi.baseName() == m_baseName;
+ });
+
+ if (textureFiles.isEmpty())
+ return {};
+
+ if (textureFiles.count() > 1) {
+ qWarning() << "Found multiple textures with the same name in the same directories: "
+ << Utils::transform(textureFiles, [](const QFileInfo &fi) {
+ return fi.fileName();
+ });
+ }
+
+ return QString{"."} + textureFiles.at(0).completeSuffix();
+}
+
+bool ContentLibraryTexture::isDownloaded() const
+{
+ if (m_fileExt.isEmpty())
+ return false;
+
+ QString fullPath = m_downloadPath + "/" + m_baseName + m_fileExt;
+ return QFileInfo(fullPath).isFile();
+}
+
+void ContentLibraryTexture::setDownloaded()
+{
+ m_fileExt = computeFileExt();
+}
+
+QString ContentLibraryTexture::parentDirPath() const
+{
+ return m_downloadPath;
}
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.h
index 468dfea09e..5921bb2796 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.h
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.h
@@ -3,6 +3,7 @@
#pragma once
+#include <QFileInfo>
#include <QObject>
#include <QUrl>
@@ -12,25 +13,39 @@ class ContentLibraryTexture : public QObject
{
Q_OBJECT
- Q_PROPERTY(QString texturePath MEMBER m_path CONSTANT)
+ Q_PROPERTY(QString textureIconPath MEMBER m_iconPath CONSTANT)
+ Q_PROPERTY(QString textureParentPath READ parentDirPath CONSTANT)
Q_PROPERTY(QString textureToolTip MEMBER m_toolTip CONSTANT)
Q_PROPERTY(QUrl textureIcon MEMBER m_icon CONSTANT)
Q_PROPERTY(bool textureVisible MEMBER m_visible NOTIFY textureVisibleChanged)
+ Q_PROPERTY(QString textureWebUrl MEMBER m_webUrl CONSTANT)
public:
- ContentLibraryTexture(QObject *parent, const QString &path, const QUrl &icon);
+ ContentLibraryTexture(QObject *parent, const QFileInfo &iconFileInfo,
+ const QString &downloadPath, const QUrl &icon, const QString &webUrl);
+
+ Q_INVOKABLE bool isDownloaded() const;
+ Q_INVOKABLE void setDownloaded();
bool filter(const QString &searchText);
QUrl icon() const;
QString path() const;
+ QString parentDirPath() const;
signals:
void textureVisibleChanged();
private:
- QString m_path;
+ inline static const QString m_defaultExt = ".png";
+ QString computeFileExt();
+
+ QString m_iconPath;
+ QString m_downloadPath;
+ QString m_webUrl;
QString m_toolTip;
+ QString m_baseName;
+ QString m_fileExt;
QUrl m_icon;
bool m_visible = true;
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturescategory.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturescategory.cpp
index 56468364e6..62553b753b 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturescategory.cpp
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturescategory.cpp
@@ -12,10 +12,12 @@ namespace QmlDesigner {
ContentLibraryTexturesCategory::ContentLibraryTexturesCategory(QObject *parent, const QString &name)
: QObject(parent), m_name(name) {}
-void ContentLibraryTexturesCategory::addTexture(const QFileInfo &tex)
+void ContentLibraryTexturesCategory::addTexture(const QFileInfo &tex, const QString &downloadPath,
+ const QString &webUrl)
{
- QUrl icon = QUrl::fromLocalFile(tex.path() + "/icon/" + tex.baseName() + ".png");
- m_categoryTextures.append(new ContentLibraryTexture(this, tex.filePath(), icon));
+ QUrl icon = QUrl::fromLocalFile(tex.absoluteFilePath());
+
+ m_categoryTextures.append(new ContentLibraryTexture(this, tex, downloadPath, icon, webUrl));
}
bool ContentLibraryTexturesCategory::filter(const QString &searchText)
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturescategory.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturescategory.h
index 1e0055d28c..0c4e9abb16 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturescategory.h
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturescategory.h
@@ -26,7 +26,7 @@ class ContentLibraryTexturesCategory : public QObject
public:
ContentLibraryTexturesCategory(QObject *parent, const QString &name);
- void addTexture(const QFileInfo &tex);
+ void addTexture(const QFileInfo &tex, const QString &subPath, const QString &webUrl);
bool filter(const QString &searchText);
QString name() const;
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.cpp
index b12473da61..ba4e34ce89 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.cpp
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.cpp
@@ -8,16 +8,29 @@
#include "utils/algorithm.h"
#include "utils/qtcassert.h"
+#include <qmldesigner/utils/fileextractor.h>
+#include <qmldesigner/utils/filedownloader.h>
+
#include <QCoreApplication>
#include <QDir>
#include <QFileInfo>
#include <QUrl>
+#include <QQmlEngine>
+#include <QStandardPaths>
namespace QmlDesigner {
-ContentLibraryTexturesModel::ContentLibraryTexturesModel(QObject *parent)
+ContentLibraryTexturesModel::ContentLibraryTexturesModel(const QString &bundleSubpath, QObject *parent)
: QAbstractListModel(parent)
{
+ qmlRegisterType<QmlDesigner::FileDownloader>("WebFetcher", 1, 0, "FileDownloader");
+ qmlRegisterType<QmlDesigner::FileExtractor>("WebFetcher", 1, 0, "FileExtractor");
+
+ static const QString baseDownloadPath =
+ QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)
+ + "/QtDesignStudio/Downloaded";
+
+ m_downloadPath = baseDownloadPath + "/" + bundleSubpath;
}
int ContentLibraryTexturesModel::rowCount(const QModelIndex &) const
@@ -83,7 +96,7 @@ QHash<int, QByteArray> ContentLibraryTexturesModel::roleNames() const
return roles;
}
-void ContentLibraryTexturesModel::loadTextureBundle(const QString &bundlePath)
+void ContentLibraryTexturesModel::loadTextureBundle(const QString &bundlePath, const QString &baseUrl)
{
QDir bundleDir = QDir(bundlePath);
if (!bundleDir.exists()) {
@@ -97,9 +110,12 @@ void ContentLibraryTexturesModel::loadTextureBundle(const QString &bundlePath)
const QFileInfoList dirs = bundleDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
for (const QFileInfo &dir : dirs) {
auto category = new ContentLibraryTexturesCategory(this, dir.fileName());
- const QFileInfoList texFiles = QDir(dir.filePath()).entryInfoList(QDir::Files);
- for (const QFileInfo &tex : texFiles)
- category->addTexture(tex);
+ const QFileInfoList texFiles = QDir(dir.filePath() + "/icon").entryInfoList(QDir::Files);
+ for (const QFileInfo &tex : texFiles) {
+ QString urlPath = baseUrl + "/" + dir.fileName() + "/" + tex.baseName() + ".zip";
+ QString downloadPath = m_downloadPath + "/" + dir.fileName();
+ category->addTexture(tex, downloadPath, urlPath);
+ }
m_bundleCategories.append(category);
}
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.h
index cbbd2a364c..7c0691cff5 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.h
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.h
@@ -18,7 +18,7 @@ class ContentLibraryTexturesModel : public QAbstractListModel
Q_PROPERTY(bool hasSceneEnv READ hasSceneEnv NOTIFY hasSceneEnvChanged)
public:
- ContentLibraryTexturesModel(QObject *parent = nullptr);
+ ContentLibraryTexturesModel(const QString &bundleSubpath, QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
@@ -33,7 +33,7 @@ public:
void setHasSceneEnv(bool b);
void resetModel();
- void loadTextureBundle(const QString &bundlePath);
+ void loadTextureBundle(const QString &bundlePath, const QString &baseUrl);
signals:
void isEmptyChanged();
@@ -45,6 +45,7 @@ private:
void updateIsEmpty();
QString m_searchText;
+ QString m_downloadPath;
QList<ContentLibraryTexturesCategory *> m_bundleCategories;
bool m_isEmpty = true;
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp
index 75f816002d..c85194ffcf 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp
@@ -20,10 +20,11 @@
#include <QMimeData>
#include <QMouseEvent>
#include <QQmlContext>
-#include <QQuickWidget>
#include <QQmlEngine>
#include <QQuickItem>
+#include <QQuickWidget>
#include <QShortcut>
+#include <QStandardPaths>
#include <QVBoxLayout>
namespace QmlDesigner {
@@ -88,8 +89,8 @@ bool ContentLibraryWidget::eventFilter(QObject *obj, QEvent *event)
ContentLibraryWidget::ContentLibraryWidget()
: m_quickWidget(new QQuickWidget(this))
, m_materialsModel(new ContentLibraryMaterialsModel(this))
- , m_texturesModel(new ContentLibraryTexturesModel(this))
- , m_environmentsModel(new ContentLibraryTexturesModel(this))
+ , m_texturesModel(new ContentLibraryTexturesModel("Textures", this))
+ , m_environmentsModel(new ContentLibraryTexturesModel("Environments", this))
{
setWindowTitle(tr("Content Library", "Title of content library widget"));
setMinimumWidth(120);
@@ -100,8 +101,11 @@ ContentLibraryWidget::ContentLibraryWidget()
m_quickWidget->setClearColor(Theme::getColor(Theme::Color::DSpanelBackground));
QString textureBundlePath = findTextureBundlePath();
- m_texturesModel->loadTextureBundle(textureBundlePath + "/Textures");
- m_environmentsModel->loadTextureBundle(textureBundlePath + "/Environments");
+ QString baseUrl = QmlDesignerPlugin::settings()
+ .value(DesignerSettingsKey::DOWNLOADABLE_BUNDLES_URL)
+ .toString();
+ m_texturesModel->loadTextureBundle(textureBundlePath + "/Textures", baseUrl + "/Textures");
+ m_environmentsModel->loadTextureBundle(textureBundlePath + "/Environments", baseUrl + "/Environments");
m_quickWidget->rootContext()->setContextProperties({
{"rootView", QVariant::fromValue(this)},
@@ -290,4 +294,18 @@ QPointer<ContentLibraryTexturesModel> ContentLibraryWidget::environmentsModel()
return m_environmentsModel;
}
+bool ContentLibraryWidget::markTextureDownloading()
+{
+ if (m_anyTextureBeingDownloaded)
+ return false;
+
+ m_anyTextureBeingDownloaded = true;
+ return true; // let the caller know it can begin download
+}
+
+void ContentLibraryWidget::markNoTextureDownloading()
+{
+ m_anyTextureBeingDownloaded = false; // allow other textures to be downloaded
+}
+
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h
index 6fadff2de6..28ac67a330 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h
@@ -59,6 +59,8 @@ public:
Q_INVOKABLE void addTexture(QmlDesigner::ContentLibraryTexture *tex);
Q_INVOKABLE void addLightProbe(QmlDesigner::ContentLibraryTexture *tex);
Q_INVOKABLE void updateSceneEnvState();
+ Q_INVOKABLE bool markTextureDownloading();
+ Q_INVOKABLE void markNoTextureDownloading();
signals:
void bundleMaterialDragStarted(QmlDesigner::ContentLibraryMaterial *bundleMat);
@@ -94,6 +96,7 @@ private:
bool m_hasMaterialLibrary = false;
bool m_hasQuick3DImport = false;
bool m_isDragging = false;
+ bool m_anyTextureBeingDownloaded = false;
};
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/utils/filedownloader.cpp b/src/plugins/qmldesigner/utils/filedownloader.cpp
index da9d023caa..e079f47ca3 100644
--- a/src/plugins/qmldesigner/utils/filedownloader.cpp
+++ b/src/plugins/qmldesigner/utils/filedownloader.cpp
@@ -33,17 +33,40 @@ void FileDownloader::start()
m_reply = Utils::NetworkAccessManager::instance()->get(request);
QNetworkReply::connect(m_reply, &QNetworkReply::readyRead, this, [this]() {
- m_tempFile.write(m_reply->readAll());
+ bool isDownloadingFile = false;
+ QString contentType;
+ if (!m_reply->hasRawHeader("Content-Type")) {
+ isDownloadingFile = true;
+ } else {
+ contentType = QString::fromUtf8(m_reply->rawHeader("Content-Type"));
+
+ if (contentType.startsWith("application/")
+ || contentType.startsWith("image/")
+ || contentType.startsWith("binary/")) {
+ isDownloadingFile = true;
+ } else {
+ qWarning() << "FileDownloader: Content type '" << contentType << "' is not supported";
+ }
+ }
+
+ if (isDownloadingFile)
+ m_tempFile.write(m_reply->readAll());
+ else
+ m_reply->close();
});
QNetworkReply::connect(m_reply,
&QNetworkReply::downloadProgress,
this,
[this](qint64 current, qint64 max) {
- if (max == 0)
+ if (max <= 0) {
+ // NOTE: according to doc, we might have the second arg
+ // of QNetworkReply::downloadProgress less than 0.
return;
+ }
m_progress = current * 100 / max;
+
emit progressChanged();
});
@@ -74,6 +97,20 @@ void FileDownloader::start()
});
}
+void FileDownloader::setProbeUrl(bool value)
+{
+ if (m_probeUrl == value)
+ return;
+
+ m_probeUrl = value;
+ emit probeUrlChanged();
+}
+
+bool FileDownloader::probeUrl() const
+{
+ return m_probeUrl;
+}
+
void FileDownloader::cancel()
{
if (m_reply)
@@ -82,10 +119,13 @@ void FileDownloader::cancel()
void FileDownloader::setUrl(const QUrl &url)
{
- m_url = url;
- emit nameChanged();
+ if (m_url != url) {
+ m_url = url;
+ emit urlChanged();
+ }
- probeUrl();
+ if (m_probeUrl)
+ doProbeUrl();
}
QUrl FileDownloader::url() const
@@ -99,9 +139,10 @@ void FileDownloader::setDownloadEnabled(bool value)
return;
m_downloadEnabled = value;
+ emit downloadEnabledChanged();
- if (!m_url.isEmpty())
- probeUrl();
+ if (!m_url.isEmpty() && m_probeUrl)
+ doProbeUrl();
}
bool FileDownloader::downloadEnabled() const
@@ -127,8 +168,7 @@ QString FileDownloader::name() const
QString FileDownloader::completeBaseName() const
{
- const QFileInfo fileInfo(m_url.path());
- return fileInfo.completeBaseName();
+ return QFileInfo(m_url.path()).completeBaseName();
}
int FileDownloader::progress() const
@@ -151,8 +191,11 @@ bool FileDownloader::available() const
return m_available;
}
-void FileDownloader::probeUrl()
+void FileDownloader::doProbeUrl()
{
+ if (!m_probeUrl)
+ return;
+
if (!m_downloadEnabled) {
m_available = false;
emit availableChanged();
diff --git a/src/plugins/qmldesigner/utils/filedownloader.h b/src/plugins/qmldesigner/utils/filedownloader.h
index d55064079f..32c0fb5c87 100644
--- a/src/plugins/qmldesigner/utils/filedownloader.h
+++ b/src/plugins/qmldesigner/utils/filedownloader.h
@@ -13,8 +13,9 @@ class FileDownloader : public QObject
{
Q_OBJECT
- Q_PROPERTY(bool downloadEnabled WRITE setDownloadEnabled READ downloadEnabled)
- Q_PROPERTY(QUrl url READ url WRITE setUrl)
+ Q_PROPERTY(bool downloadEnabled WRITE setDownloadEnabled READ downloadEnabled NOTIFY downloadEnabledChanged)
+ Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
+ Q_PROPERTY(bool probeUrl READ probeUrl WRITE setProbeUrl NOTIFY probeUrlChanged)
Q_PROPERTY(bool finished READ finished NOTIFY finishedChanged)
Q_PROPERTY(bool error READ error NOTIFY errorChanged)
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
@@ -42,6 +43,9 @@ public:
void setDownloadEnabled(bool value);
bool downloadEnabled() const;
+ void setProbeUrl(bool value);
+ bool probeUrl() const;
+
Q_INVOKABLE void start();
Q_INVOKABLE void cancel();
@@ -49,19 +53,23 @@ signals:
void finishedChanged();
void errorChanged();
void nameChanged();
+ void urlChanged();
void progressChanged();
void tempFileChanged();
void downloadFailed();
void lastModifiedChanged();
void availableChanged();
+ void downloadEnabledChanged();
void downloadStarting();
void downloadCanceled();
+ void probeUrlChanged();
private:
- void probeUrl();
+ void doProbeUrl();
QUrl m_url;
+ bool m_probeUrl = false;
bool m_finished = false;
bool m_error = false;
int m_progress = 0;
diff --git a/src/plugins/qmldesigner/utils/fileextractor.cpp b/src/plugins/qmldesigner/utils/fileextractor.cpp
index 1c409bc086..79aa4974e5 100644
--- a/src/plugins/qmldesigner/utils/fileextractor.cpp
+++ b/src/plugins/qmldesigner/utils/fileextractor.cpp
@@ -29,6 +29,40 @@ FileExtractor::FileExtractor(QObject *parent)
emit birthTimeChanged();
});
+
+ QObject::connect(
+ &m_timer, &QTimer::timeout, this, [this]() {
+ static QHash<QString, int> hash;
+ QDirIterator it(m_targetFolder, {"*.*"}, QDir::Files, QDirIterator::Subdirectories);
+
+ int count = 0;
+ while (it.hasNext()) {
+ if (!hash.contains(it.fileName())) {
+ m_currentFile = it.fileName();
+ hash.insert(m_currentFile, 0);
+ emit currentFileChanged();
+ }
+ it.next();
+ count++;
+ }
+
+ qint64 currentSize = m_bytesBefore
+ - QStorageInfo(m_targetPath.toFileInfo().dir()).bytesAvailable();
+
+ // We can not get the uncompressed size of the archive yet, that is why we use an
+ // approximation. We assume a 50% compression rate.
+ int progress = std::min(100ll, currentSize * 100 / m_compressedSize * 2);
+ if (progress >= 0) {
+ m_progress = progress;
+ emit progressChanged();
+ } else {
+ qWarning() << "FileExtractor has got negative progress. Likely due to QStorageInfo.";
+ }
+
+ m_size = QString::number(currentSize);
+ m_count = QString::number(count);
+ emit sizeChanged();
+ });
}
FileExtractor::~FileExtractor() {}
@@ -74,11 +108,37 @@ void FileExtractor::setArchiveName(QString &filePath)
emit targetFolderExistsChanged();
}
-const QString FileExtractor::detailedText()
+const QString FileExtractor::detailedText() const
{
return m_detailedText;
}
+void FileExtractor::setClearTargetPathContents(bool value)
+{
+ if (m_clearTargetPathContents != value) {
+ m_clearTargetPathContents = value;
+ emit clearTargetPathContentsChanged();
+ }
+}
+
+bool FileExtractor::clearTargetPathContents() const
+{
+ return m_clearTargetPathContents;
+}
+
+void FileExtractor::setAlwaysCreateDir(bool value)
+{
+ if (m_alwaysCreateDir != value) {
+ m_alwaysCreateDir = value;
+ emit alwaysCreateDirChanged();
+ }
+}
+
+bool FileExtractor::alwaysCreateDir() const
+{
+ return m_alwaysCreateDir;
+}
+
bool FileExtractor::finished() const
{
return m_finished;
@@ -126,51 +186,24 @@ QString FileExtractor::sourceFile() const
void FileExtractor::extract()
{
- const QString targetFolder = m_targetPath.toString() + "/" + m_archiveName;
+ m_targetFolder = m_targetPath.toString() + "/" + m_archiveName;
// If the target directory already exists, remove it and its content
- QDir targetDir(targetFolder);
- if (targetDir.exists())
+ QDir targetDir(m_targetFolder);
+ if (targetDir.exists() && m_clearTargetPathContents)
targetDir.removeRecursively();
- // Create a new directory to generate a proper creation date
- targetDir.mkdir(targetFolder);
+ if (m_alwaysCreateDir) {
+ // Create a new directory to generate a proper creation date
+ targetDir.mkdir(m_targetFolder);
+ }
Utils::Archive *archive = new Utils::Archive(m_sourceFile, m_targetPath);
QTC_ASSERT(archive->isValid(), delete archive; return);
m_timer.start();
- qint64 bytesBefore = QStorageInfo(m_targetPath.toFileInfo().dir()).bytesAvailable();
- qint64 compressedSize = QFileInfo(m_sourceFile.toString()).size();
-
- QTimer::connect(
- &m_timer, &QTimer::timeout, this, [this, bytesBefore, targetFolder, compressedSize]() {
- static QHash<QString, int> hash;
- QDirIterator it(targetFolder, {"*.*"}, QDir::Files, QDirIterator::Subdirectories);
-
- int count = 0;
- while (it.hasNext()) {
- if (!hash.contains(it.fileName())) {
- m_currentFile = it.fileName();
- hash.insert(m_currentFile, 0);
- emit currentFileChanged();
- }
- it.next();
- count++;
- }
-
- qint64 currentSize = bytesBefore
- - QStorageInfo(m_targetPath.toFileInfo().dir()).bytesAvailable();
-
- // We can not get the uncompressed size of the archive yet, that is why we use an
- // approximation. We assume a 50% compression rate.
- m_progress = std::min(100ll, currentSize * 100 / compressedSize * 2);
- emit progressChanged();
-
- m_size = QString::number(currentSize);
- m_count = QString::number(count);
- emit sizeChanged();
- });
+ m_bytesBefore = QStorageInfo(m_targetPath.toFileInfo().dir()).bytesAvailable();
+ m_compressedSize = QFileInfo(m_sourceFile.toString()).size();
QObject::connect(archive, &Utils::Archive::outputReceived, this, [this](const QString &output) {
m_detailedText += output;
diff --git a/src/plugins/qmldesigner/utils/fileextractor.h b/src/plugins/qmldesigner/utils/fileextractor.h
index ddbcfd0929..2579ddcda8 100644
--- a/src/plugins/qmldesigner/utils/fileextractor.h
+++ b/src/plugins/qmldesigner/utils/fileextractor.h
@@ -25,6 +25,8 @@ class FileExtractor : public QObject
Q_PROPERTY(bool targetFolderExists READ targetFolderExists NOTIFY targetFolderExistsChanged)
Q_PROPERTY(int progress READ progress NOTIFY progressChanged)
Q_PROPERTY(QDateTime birthTime READ birthTime NOTIFY birthTimeChanged)
+ Q_PROPERTY(bool clearTargetPathContents READ clearTargetPathContents WRITE setClearTargetPathContents NOTIFY clearTargetPathContentsChanged)
+ Q_PROPERTY(bool alwaysCreateDir READ alwaysCreateDir WRITE setAlwaysCreateDir NOTIFY alwaysCreateDirChanged)
public:
explicit FileExtractor(QObject *parent = nullptr);
@@ -36,7 +38,7 @@ public:
void setTargetPath(const QString &path);
void setSourceFile(QString &sourceFilePath);
void setArchiveName(QString &filePath);
- const QString detailedText();
+ const QString detailedText() const;
bool finished() const;
QString currentFile() const;
QString size() const;
@@ -44,6 +46,10 @@ public:
bool targetFolderExists() const;
int progress() const;
QDateTime birthTime() const;
+ void setClearTargetPathContents(bool value);
+ bool clearTargetPathContents() const;
+ void setAlwaysCreateDir(bool value);
+ bool alwaysCreateDir() const;
QString sourceFile() const;
QString archiveName() const;
@@ -60,9 +66,12 @@ signals:
void targetFolderExistsChanged();
void progressChanged();
void birthTimeChanged();
+ void clearTargetPathContentsChanged();
+ void alwaysCreateDirChanged();
private:
Utils::FilePath m_targetPath;
+ QString m_targetFolder; // The same as m_targetPath, but with the archive name also.
Utils::FilePath m_sourceFile;
QString m_detailedText;
bool m_finished = false;
@@ -73,6 +82,11 @@ private:
QString m_archiveName;
int m_progress = 0;
QDateTime m_birthTime;
+ bool m_clearTargetPathContents = false;
+ bool m_alwaysCreateDir = false;
+
+ qint64 m_bytesBefore = 0;
+ qint64 m_compressedSize = 0;
};
} // QmlDesigner
diff --git a/src/plugins/qmldesigner/utils/imageutils.cpp b/src/plugins/qmldesigner/utils/imageutils.cpp
index ccfa5bfe42..63f57d8a94 100644
--- a/src/plugins/qmldesigner/utils/imageutils.cpp
+++ b/src/plugins/qmldesigner/utils/imageutils.cpp
@@ -11,12 +11,15 @@
namespace QmlDesigner {
-QString QmlDesigner::ImageUtils::imageInfo(const QString &path)
+QString QmlDesigner::ImageUtils::imageInfo(const QString &path, bool fetchSizeInfo)
{
QFileInfo info(path);
if (!info.exists())
return {};
+ if (!fetchSizeInfo)
+ return QLatin1String("(%1)").arg(info.suffix());
+
int width = 0;
int height = 0;
const QString suffix = info.suffix();
diff --git a/src/plugins/qmldesigner/utils/imageutils.h b/src/plugins/qmldesigner/utils/imageutils.h
index 3b740b76b1..2cc58e8817 100644
--- a/src/plugins/qmldesigner/utils/imageutils.h
+++ b/src/plugins/qmldesigner/utils/imageutils.h
@@ -11,7 +11,7 @@ class ImageUtils
public:
ImageUtils();
- static QString imageInfo(const QString &path);
+ static QString imageInfo(const QString &path, bool sizeInfo = true);
};
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesignerbase/utils/designersettings.cpp b/src/plugins/qmldesignerbase/utils/designersettings.cpp
index 845e1d588e..c5db098cd4 100644
--- a/src/plugins/qmldesignerbase/utils/designersettings.cpp
+++ b/src/plugins/qmldesignerbase/utils/designersettings.cpp
@@ -88,6 +88,8 @@ void DesignerSettings::fromSettings(QSettings *settings)
restoreValue(settings, DesignerSettingsKey::OLD_STATES_EDITOR, false);
restoreValue(settings, DesignerSettingsKey::EDITOR_ZOOM_FACTOR, 1.0);
restoreValue(settings, DesignerSettingsKey::ACTIONS_MERGE_TEMPLATE_ENABLED, false);
+ restoreValue(settings, DesignerSettingsKey::DOWNLOADABLE_BUNDLES_URL,
+ "https://cdn.qt.io/designstudio/bundles/textures");
settings->endGroup();
settings->endGroup();
diff --git a/src/plugins/qmldesignerbase/utils/designersettings.h b/src/plugins/qmldesignerbase/utils/designersettings.h
index 267195d614..b189b82501 100644
--- a/src/plugins/qmldesignerbase/utils/designersettings.h
+++ b/src/plugins/qmldesignerbase/utils/designersettings.h
@@ -60,6 +60,7 @@ inline constexpr char SMOOTH_RENDERING[] = "SmoothRendering";
inline constexpr char OLD_STATES_EDITOR[] = "ForceOldStatesEditor";
inline constexpr char EDITOR_ZOOM_FACTOR[] = "EditorZoomFactor";
inline constexpr char ACTIONS_MERGE_TEMPLATE_ENABLED[] = "ActionsMergeTemplateEnabled";
+inline constexpr char DOWNLOADABLE_BUNDLES_URL[] = "DownloadableBundlesUrl";
}
class QMLDESIGNERBASE_EXPORT DesignerSettings
diff --git a/src/plugins/studiowelcome/examplecheckout.cpp b/src/plugins/studiowelcome/examplecheckout.cpp
index cde0451b0b..54194b27d7 100644
--- a/src/plugins/studiowelcome/examplecheckout.cpp
+++ b/src/plugins/studiowelcome/examplecheckout.cpp
@@ -108,6 +108,43 @@ DataModelDownloader::DataModelDownloader(QObject * /* parent */)
this,
&DataModelDownloader::targetPathMustChange);
}
+
+ connect(&m_fileDownloader, &QmlDesigner::FileDownloader::finishedChanged, this, [this]() {
+ m_started = false;
+
+ if (m_fileDownloader.finished()) {
+ const Utils::FilePath archiveFile = Utils::FilePath::fromString(
+ m_fileDownloader.tempFile());
+ QTC_ASSERT(Utils::Archive::supportsFile(archiveFile), return );
+ auto archive = new Utils::Archive(archiveFile, tempFilePath());
+ QTC_ASSERT(archive->isValid(), delete archive; return );
+ QObject::connect(archive, &Utils::Archive::finished, this, [this, archive](bool ret) {
+ QTC_CHECK(ret);
+ archive->deleteLater();
+ emit finished();
+ });
+ archive->unarchive();
+ }
+ });
+}
+
+void DataModelDownloader::onAvailableChanged()
+{
+ m_available = m_fileDownloader.available();
+
+ emit availableChanged();
+
+ if (!m_available) {
+ qWarning() << m_fileDownloader.url() << "failed to download";
+ return;
+ }
+
+ if (!m_forceDownload && (m_fileDownloader.lastModified() <= m_birthTime))
+ return;
+
+ m_started = true;
+
+ m_fileDownloader.start();
}
bool DataModelDownloader::start()
@@ -122,42 +159,10 @@ bool DataModelDownloader::start()
m_fileDownloader.setUrl(QUrl::fromUserInput(
"https://download.qt.io/learning/examples/qtdesignstudio/dataImports.zip"));
- bool started = false;
-
- connect(&m_fileDownloader, &QmlDesigner::FileDownloader::availableChanged, this, [this, &started]() {
-
- m_available = m_fileDownloader.available();
+ m_started = false;
- emit availableChanged();
-
- if (!m_available) {
- qWarning() << m_fileDownloader.url() << "failed to download";
- return;
- }
-
- if (!m_forceDownload && (m_fileDownloader.lastModified() <= m_birthTime))
- return;
-
- started = true;
-
- m_fileDownloader.start();
- connect(&m_fileDownloader, &QmlDesigner::FileDownloader::finishedChanged, this, [this]() {
- if (m_fileDownloader.finished()) {
- const Utils::FilePath archiveFile = Utils::FilePath::fromString(
- m_fileDownloader.tempFile());
- QTC_ASSERT(Utils::Archive::supportsFile(archiveFile), return );
- auto archive = new Utils::Archive(archiveFile, tempFilePath());
- QTC_ASSERT(archive->isValid(), delete archive; return );
- QObject::connect(archive, &Utils::Archive::finished, this, [this, archive](bool ret) {
- QTC_CHECK(ret);
- archive->deleteLater();
- emit finished();
- });
- archive->unarchive();
- }
- });
- });
- return started;
+ connect(&m_fileDownloader, &QmlDesigner::FileDownloader::availableChanged, this, &DataModelDownloader::onAvailableChanged);
+ return m_started;
}
bool DataModelDownloader::exists() const
diff --git a/src/plugins/studiowelcome/examplecheckout.h b/src/plugins/studiowelcome/examplecheckout.h
index 9ad4a2a436..a59f8306c3 100644
--- a/src/plugins/studiowelcome/examplecheckout.h
+++ b/src/plugins/studiowelcome/examplecheckout.h
@@ -42,9 +42,11 @@ signals:
void targetPathMustChange(const QString &newPath);
private:
+ void onAvailableChanged();
QmlDesigner::FileDownloader m_fileDownloader;
QDateTime m_birthTime;
bool m_exists = false;
bool m_available = false;
bool m_forceDownload = false;
+ bool m_started = false;
};