diff options
author | Samuel Ghinet <samuel.ghinet@qt.io> | 2023-01-18 00:15:38 +0200 |
---|---|---|
committer | Thomas Hartmann <thomas.hartmann@qt.io> | 2023-02-22 10:09:25 +0000 |
commit | ed503f3db50381ddf66d6564494fca51d51086bc (patch) | |
tree | cbeb7c28ca1754f61acb85813ea1c5e8243f3c9b | |
parent | de8ea89e7584e1b4c4e05701f95c7f6d00948ccf (diff) | |
download | qt-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>
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; }; |