forked from qt-creator/qt-creator
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. Task-number: QDS-8664 Change-Id: Iec40e540c116030288df76e1922eab56ba323d1e Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io> Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
This commit is contained in:
@@ -9,15 +9,141 @@ import QtQuick.Controls
|
|||||||
|
|
||||||
import StudioTheme 1.0 as StudioTheme
|
import StudioTheme 1.0 as StudioTheme
|
||||||
|
|
||||||
Image {
|
import WebFetcher 1.0
|
||||||
|
|
||||||
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
source: modelData.textureIcon
|
// Download states: "" (ie default, not downloaded), "unavailable", "downloading", "downloaded",
|
||||||
visible: modelData.textureVisible
|
// "failed"
|
||||||
cache: false
|
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()
|
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 {
|
MouseArea {
|
||||||
id: mouseArea
|
id: mouseArea
|
||||||
|
|
||||||
@@ -25,27 +151,90 @@ Image {
|
|||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
|
|
||||||
|
onEntered: tooltip.visible = image.visible
|
||||||
|
onExited: tooltip.visible = false
|
||||||
|
|
||||||
onPressed: (mouse) => {
|
onPressed: (mouse) => {
|
||||||
if (mouse.button === Qt.LeftButton)
|
if (mouse.button === Qt.LeftButton) {
|
||||||
rootView.startDragTexture(modelData, mapToGlobal(mouse.x, mouse.y))
|
if (root.downloadState === "downloaded")
|
||||||
else if (mouse.button === Qt.RightButton)
|
rootView.startDragTexture(modelData, mapToGlobal(mouse.x, mouse.y))
|
||||||
|
} else if (mouse.button === Qt.RightButton) {
|
||||||
root.showContextMenu()
|
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 {
|
FileDownloader {
|
||||||
visible: mouseArea.containsMouse
|
id: downloader
|
||||||
// contentWidth is not calculated correctly by the toolTip (resulting in a wider tooltip than
|
url: image.webUrl
|
||||||
// needed). Using a helper Text to calculate the correct width
|
probeUrl: false
|
||||||
contentWidth: helperText.width
|
downloadEnabled: true
|
||||||
bottomInset: -2
|
onDownloadStarting: {
|
||||||
text: modelData.textureToolTip
|
root.downloadState = "downloading"
|
||||||
delay: 1000
|
root.downloadStateChanged()
|
||||||
|
}
|
||||||
|
|
||||||
Text {
|
onFinishedChanged: {
|
||||||
id: helperText
|
root.progressText = qsTr("Extracting...")
|
||||||
text: modelData.textureToolTip
|
root.allowCancel = false
|
||||||
visible: 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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -4,20 +4,41 @@
|
|||||||
#include "contentlibrarytexture.h"
|
#include "contentlibrarytexture.h"
|
||||||
|
|
||||||
#include "imageutils.h"
|
#include "imageutils.h"
|
||||||
|
#include <utils/algorithm.h>
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFileInfo>
|
||||||
|
|
||||||
namespace QmlDesigner {
|
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)
|
: QObject(parent)
|
||||||
, m_path(path)
|
, m_iconPath(iconFileInfo.filePath())
|
||||||
|
, m_downloadPath(downloadPath)
|
||||||
|
, m_webUrl(webUrl)
|
||||||
|
, m_baseName{iconFileInfo.baseName()}
|
||||||
, m_icon(icon)
|
, 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)
|
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;
|
m_visible = !m_visible;
|
||||||
emit textureVisibleChanged();
|
emit textureVisibleChanged();
|
||||||
}
|
}
|
||||||
@@ -32,7 +53,46 @@ QUrl ContentLibraryTexture::icon() const
|
|||||||
|
|
||||||
QString ContentLibraryTexture::path() 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
|
} // namespace QmlDesigner
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QFileInfo>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
@@ -12,25 +13,39 @@ class ContentLibraryTexture : public QObject
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
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(QString textureToolTip MEMBER m_toolTip CONSTANT)
|
||||||
Q_PROPERTY(QUrl textureIcon MEMBER m_icon CONSTANT)
|
Q_PROPERTY(QUrl textureIcon MEMBER m_icon CONSTANT)
|
||||||
Q_PROPERTY(bool textureVisible MEMBER m_visible NOTIFY textureVisibleChanged)
|
Q_PROPERTY(bool textureVisible MEMBER m_visible NOTIFY textureVisibleChanged)
|
||||||
|
Q_PROPERTY(QString textureWebUrl MEMBER m_webUrl CONSTANT)
|
||||||
|
|
||||||
public:
|
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);
|
bool filter(const QString &searchText);
|
||||||
|
|
||||||
QUrl icon() const;
|
QUrl icon() const;
|
||||||
QString path() const;
|
QString path() const;
|
||||||
|
QString parentDirPath() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void textureVisibleChanged();
|
void textureVisibleChanged();
|
||||||
|
|
||||||
private:
|
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_toolTip;
|
||||||
|
QString m_baseName;
|
||||||
|
QString m_fileExt;
|
||||||
QUrl m_icon;
|
QUrl m_icon;
|
||||||
|
|
||||||
bool m_visible = true;
|
bool m_visible = true;
|
||||||
|
@@ -12,10 +12,12 @@ namespace QmlDesigner {
|
|||||||
ContentLibraryTexturesCategory::ContentLibraryTexturesCategory(QObject *parent, const QString &name)
|
ContentLibraryTexturesCategory::ContentLibraryTexturesCategory(QObject *parent, const QString &name)
|
||||||
: QObject(parent), m_name(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");
|
QUrl icon = QUrl::fromLocalFile(tex.absoluteFilePath());
|
||||||
m_categoryTextures.append(new ContentLibraryTexture(this, tex.filePath(), icon));
|
|
||||||
|
m_categoryTextures.append(new ContentLibraryTexture(this, tex, downloadPath, icon, webUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ContentLibraryTexturesCategory::filter(const QString &searchText)
|
bool ContentLibraryTexturesCategory::filter(const QString &searchText)
|
||||||
|
@@ -26,7 +26,7 @@ class ContentLibraryTexturesCategory : public QObject
|
|||||||
public:
|
public:
|
||||||
ContentLibraryTexturesCategory(QObject *parent, const QString &name);
|
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);
|
bool filter(const QString &searchText);
|
||||||
|
|
||||||
QString name() const;
|
QString name() const;
|
||||||
|
@@ -8,16 +8,29 @@
|
|||||||
#include "utils/algorithm.h"
|
#include "utils/algorithm.h"
|
||||||
#include "utils/qtcassert.h"
|
#include "utils/qtcassert.h"
|
||||||
|
|
||||||
|
#include <qmldesigner/utils/fileextractor.h>
|
||||||
|
#include <qmldesigner/utils/filedownloader.h>
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
#include <QQmlEngine>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
|
||||||
namespace QmlDesigner {
|
namespace QmlDesigner {
|
||||||
|
|
||||||
ContentLibraryTexturesModel::ContentLibraryTexturesModel(QObject *parent)
|
ContentLibraryTexturesModel::ContentLibraryTexturesModel(const QString &bundleSubpath, QObject *parent)
|
||||||
: QAbstractListModel(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
|
int ContentLibraryTexturesModel::rowCount(const QModelIndex &) const
|
||||||
@@ -83,7 +96,7 @@ QHash<int, QByteArray> ContentLibraryTexturesModel::roleNames() const
|
|||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContentLibraryTexturesModel::loadTextureBundle(const QString &bundlePath)
|
void ContentLibraryTexturesModel::loadTextureBundle(const QString &bundlePath, const QString &baseUrl)
|
||||||
{
|
{
|
||||||
QDir bundleDir = QDir(bundlePath);
|
QDir bundleDir = QDir(bundlePath);
|
||||||
if (!bundleDir.exists()) {
|
if (!bundleDir.exists()) {
|
||||||
@@ -97,9 +110,12 @@ void ContentLibraryTexturesModel::loadTextureBundle(const QString &bundlePath)
|
|||||||
const QFileInfoList dirs = bundleDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
|
const QFileInfoList dirs = bundleDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
for (const QFileInfo &dir : dirs) {
|
for (const QFileInfo &dir : dirs) {
|
||||||
auto category = new ContentLibraryTexturesCategory(this, dir.fileName());
|
auto category = new ContentLibraryTexturesCategory(this, dir.fileName());
|
||||||
const QFileInfoList texFiles = QDir(dir.filePath()).entryInfoList(QDir::Files);
|
const QFileInfoList texFiles = QDir(dir.filePath() + "/icon").entryInfoList(QDir::Files);
|
||||||
for (const QFileInfo &tex : texFiles)
|
for (const QFileInfo &tex : texFiles) {
|
||||||
category->addTexture(tex);
|
QString urlPath = baseUrl + "/" + dir.fileName() + "/" + tex.baseName() + ".zip";
|
||||||
|
QString downloadPath = m_downloadPath + "/" + dir.fileName();
|
||||||
|
category->addTexture(tex, downloadPath, urlPath);
|
||||||
|
}
|
||||||
m_bundleCategories.append(category);
|
m_bundleCategories.append(category);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -18,7 +18,7 @@ class ContentLibraryTexturesModel : public QAbstractListModel
|
|||||||
Q_PROPERTY(bool hasSceneEnv READ hasSceneEnv NOTIFY hasSceneEnvChanged)
|
Q_PROPERTY(bool hasSceneEnv READ hasSceneEnv NOTIFY hasSceneEnvChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ContentLibraryTexturesModel(QObject *parent = nullptr);
|
ContentLibraryTexturesModel(const QString &bundleSubpath, QObject *parent = nullptr);
|
||||||
|
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
@@ -33,7 +33,7 @@ public:
|
|||||||
void setHasSceneEnv(bool b);
|
void setHasSceneEnv(bool b);
|
||||||
|
|
||||||
void resetModel();
|
void resetModel();
|
||||||
void loadTextureBundle(const QString &bundlePath);
|
void loadTextureBundle(const QString &bundlePath, const QString &baseUrl);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void isEmptyChanged();
|
void isEmptyChanged();
|
||||||
@@ -45,6 +45,7 @@ private:
|
|||||||
void updateIsEmpty();
|
void updateIsEmpty();
|
||||||
|
|
||||||
QString m_searchText;
|
QString m_searchText;
|
||||||
|
QString m_downloadPath;
|
||||||
QList<ContentLibraryTexturesCategory *> m_bundleCategories;
|
QList<ContentLibraryTexturesCategory *> m_bundleCategories;
|
||||||
|
|
||||||
bool m_isEmpty = true;
|
bool m_isEmpty = true;
|
||||||
|
@@ -20,10 +20,11 @@
|
|||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
#include <QQuickWidget>
|
|
||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
#include <QQuickItem>
|
#include <QQuickItem>
|
||||||
|
#include <QQuickWidget>
|
||||||
#include <QShortcut>
|
#include <QShortcut>
|
||||||
|
#include <QStandardPaths>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
namespace QmlDesigner {
|
namespace QmlDesigner {
|
||||||
@@ -88,8 +89,8 @@ bool ContentLibraryWidget::eventFilter(QObject *obj, QEvent *event)
|
|||||||
ContentLibraryWidget::ContentLibraryWidget()
|
ContentLibraryWidget::ContentLibraryWidget()
|
||||||
: m_quickWidget(new QQuickWidget(this))
|
: m_quickWidget(new QQuickWidget(this))
|
||||||
, m_materialsModel(new ContentLibraryMaterialsModel(this))
|
, m_materialsModel(new ContentLibraryMaterialsModel(this))
|
||||||
, m_texturesModel(new ContentLibraryTexturesModel(this))
|
, m_texturesModel(new ContentLibraryTexturesModel("Textures", this))
|
||||||
, m_environmentsModel(new ContentLibraryTexturesModel(this))
|
, m_environmentsModel(new ContentLibraryTexturesModel("Environments", this))
|
||||||
{
|
{
|
||||||
setWindowTitle(tr("Content Library", "Title of content library widget"));
|
setWindowTitle(tr("Content Library", "Title of content library widget"));
|
||||||
setMinimumWidth(120);
|
setMinimumWidth(120);
|
||||||
@@ -100,8 +101,11 @@ ContentLibraryWidget::ContentLibraryWidget()
|
|||||||
m_quickWidget->setClearColor(Theme::getColor(Theme::Color::DSpanelBackground));
|
m_quickWidget->setClearColor(Theme::getColor(Theme::Color::DSpanelBackground));
|
||||||
|
|
||||||
QString textureBundlePath = findTextureBundlePath();
|
QString textureBundlePath = findTextureBundlePath();
|
||||||
m_texturesModel->loadTextureBundle(textureBundlePath + "/Textures");
|
QString baseUrl = QmlDesignerPlugin::settings()
|
||||||
m_environmentsModel->loadTextureBundle(textureBundlePath + "/Environments");
|
.value(DesignerSettingsKey::DOWNLOADABLE_BUNDLES_URL)
|
||||||
|
.toString();
|
||||||
|
m_texturesModel->loadTextureBundle(textureBundlePath + "/Textures", baseUrl + "/Textures");
|
||||||
|
m_environmentsModel->loadTextureBundle(textureBundlePath + "/Environments", baseUrl + "/Environments");
|
||||||
|
|
||||||
m_quickWidget->rootContext()->setContextProperties({
|
m_quickWidget->rootContext()->setContextProperties({
|
||||||
{"rootView", QVariant::fromValue(this)},
|
{"rootView", QVariant::fromValue(this)},
|
||||||
@@ -290,4 +294,18 @@ QPointer<ContentLibraryTexturesModel> ContentLibraryWidget::environmentsModel()
|
|||||||
return m_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
|
} // namespace QmlDesigner
|
||||||
|
@@ -59,6 +59,8 @@ public:
|
|||||||
Q_INVOKABLE void addTexture(QmlDesigner::ContentLibraryTexture *tex);
|
Q_INVOKABLE void addTexture(QmlDesigner::ContentLibraryTexture *tex);
|
||||||
Q_INVOKABLE void addLightProbe(QmlDesigner::ContentLibraryTexture *tex);
|
Q_INVOKABLE void addLightProbe(QmlDesigner::ContentLibraryTexture *tex);
|
||||||
Q_INVOKABLE void updateSceneEnvState();
|
Q_INVOKABLE void updateSceneEnvState();
|
||||||
|
Q_INVOKABLE bool markTextureDownloading();
|
||||||
|
Q_INVOKABLE void markNoTextureDownloading();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void bundleMaterialDragStarted(QmlDesigner::ContentLibraryMaterial *bundleMat);
|
void bundleMaterialDragStarted(QmlDesigner::ContentLibraryMaterial *bundleMat);
|
||||||
@@ -94,6 +96,7 @@ private:
|
|||||||
bool m_hasMaterialLibrary = false;
|
bool m_hasMaterialLibrary = false;
|
||||||
bool m_hasQuick3DImport = false;
|
bool m_hasQuick3DImport = false;
|
||||||
bool m_isDragging = false;
|
bool m_isDragging = false;
|
||||||
|
bool m_anyTextureBeingDownloaded = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QmlDesigner
|
} // namespace QmlDesigner
|
||||||
|
@@ -33,17 +33,40 @@ void FileDownloader::start()
|
|||||||
m_reply = Utils::NetworkAccessManager::instance()->get(request);
|
m_reply = Utils::NetworkAccessManager::instance()->get(request);
|
||||||
|
|
||||||
QNetworkReply::connect(m_reply, &QNetworkReply::readyRead, this, [this]() {
|
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::connect(m_reply,
|
||||||
&QNetworkReply::downloadProgress,
|
&QNetworkReply::downloadProgress,
|
||||||
this,
|
this,
|
||||||
[this](qint64 current, qint64 max) {
|
[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;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
m_progress = current * 100 / max;
|
m_progress = current * 100 / max;
|
||||||
|
|
||||||
emit progressChanged();
|
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()
|
void FileDownloader::cancel()
|
||||||
{
|
{
|
||||||
if (m_reply)
|
if (m_reply)
|
||||||
@@ -82,10 +119,13 @@ void FileDownloader::cancel()
|
|||||||
|
|
||||||
void FileDownloader::setUrl(const QUrl &url)
|
void FileDownloader::setUrl(const QUrl &url)
|
||||||
{
|
{
|
||||||
m_url = url;
|
if (m_url != url) {
|
||||||
emit nameChanged();
|
m_url = url;
|
||||||
|
emit urlChanged();
|
||||||
|
}
|
||||||
|
|
||||||
probeUrl();
|
if (m_probeUrl)
|
||||||
|
doProbeUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl FileDownloader::url() const
|
QUrl FileDownloader::url() const
|
||||||
@@ -99,9 +139,10 @@ void FileDownloader::setDownloadEnabled(bool value)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
m_downloadEnabled = value;
|
m_downloadEnabled = value;
|
||||||
|
emit downloadEnabledChanged();
|
||||||
|
|
||||||
if (!m_url.isEmpty())
|
if (!m_url.isEmpty() && m_probeUrl)
|
||||||
probeUrl();
|
doProbeUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FileDownloader::downloadEnabled() const
|
bool FileDownloader::downloadEnabled() const
|
||||||
@@ -127,8 +168,7 @@ QString FileDownloader::name() const
|
|||||||
|
|
||||||
QString FileDownloader::completeBaseName() const
|
QString FileDownloader::completeBaseName() const
|
||||||
{
|
{
|
||||||
const QFileInfo fileInfo(m_url.path());
|
return QFileInfo(m_url.path()).completeBaseName();
|
||||||
return fileInfo.completeBaseName();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int FileDownloader::progress() const
|
int FileDownloader::progress() const
|
||||||
@@ -151,8 +191,11 @@ bool FileDownloader::available() const
|
|||||||
return m_available;
|
return m_available;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileDownloader::probeUrl()
|
void FileDownloader::doProbeUrl()
|
||||||
{
|
{
|
||||||
|
if (!m_probeUrl)
|
||||||
|
return;
|
||||||
|
|
||||||
if (!m_downloadEnabled) {
|
if (!m_downloadEnabled) {
|
||||||
m_available = false;
|
m_available = false;
|
||||||
emit availableChanged();
|
emit availableChanged();
|
||||||
|
@@ -13,8 +13,9 @@ class FileDownloader : public QObject
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
Q_PROPERTY(bool downloadEnabled WRITE setDownloadEnabled READ downloadEnabled)
|
Q_PROPERTY(bool downloadEnabled WRITE setDownloadEnabled READ downloadEnabled NOTIFY downloadEnabledChanged)
|
||||||
Q_PROPERTY(QUrl url READ url WRITE setUrl)
|
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 finished READ finished NOTIFY finishedChanged)
|
||||||
Q_PROPERTY(bool error READ error NOTIFY errorChanged)
|
Q_PROPERTY(bool error READ error NOTIFY errorChanged)
|
||||||
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
||||||
@@ -42,6 +43,9 @@ public:
|
|||||||
void setDownloadEnabled(bool value);
|
void setDownloadEnabled(bool value);
|
||||||
bool downloadEnabled() const;
|
bool downloadEnabled() const;
|
||||||
|
|
||||||
|
void setProbeUrl(bool value);
|
||||||
|
bool probeUrl() const;
|
||||||
|
|
||||||
Q_INVOKABLE void start();
|
Q_INVOKABLE void start();
|
||||||
Q_INVOKABLE void cancel();
|
Q_INVOKABLE void cancel();
|
||||||
|
|
||||||
@@ -49,19 +53,23 @@ signals:
|
|||||||
void finishedChanged();
|
void finishedChanged();
|
||||||
void errorChanged();
|
void errorChanged();
|
||||||
void nameChanged();
|
void nameChanged();
|
||||||
|
void urlChanged();
|
||||||
void progressChanged();
|
void progressChanged();
|
||||||
void tempFileChanged();
|
void tempFileChanged();
|
||||||
void downloadFailed();
|
void downloadFailed();
|
||||||
void lastModifiedChanged();
|
void lastModifiedChanged();
|
||||||
void availableChanged();
|
void availableChanged();
|
||||||
|
void downloadEnabledChanged();
|
||||||
|
|
||||||
void downloadStarting();
|
void downloadStarting();
|
||||||
void downloadCanceled();
|
void downloadCanceled();
|
||||||
|
void probeUrlChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void probeUrl();
|
void doProbeUrl();
|
||||||
|
|
||||||
QUrl m_url;
|
QUrl m_url;
|
||||||
|
bool m_probeUrl = false;
|
||||||
bool m_finished = false;
|
bool m_finished = false;
|
||||||
bool m_error = false;
|
bool m_error = false;
|
||||||
int m_progress = 0;
|
int m_progress = 0;
|
||||||
|
@@ -29,6 +29,40 @@ FileExtractor::FileExtractor(QObject *parent)
|
|||||||
|
|
||||||
emit birthTimeChanged();
|
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() {}
|
FileExtractor::~FileExtractor() {}
|
||||||
@@ -74,11 +108,37 @@ void FileExtractor::setArchiveName(QString &filePath)
|
|||||||
emit targetFolderExistsChanged();
|
emit targetFolderExistsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString FileExtractor::detailedText()
|
const QString FileExtractor::detailedText() const
|
||||||
{
|
{
|
||||||
return m_detailedText;
|
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
|
bool FileExtractor::finished() const
|
||||||
{
|
{
|
||||||
return m_finished;
|
return m_finished;
|
||||||
@@ -126,51 +186,24 @@ QString FileExtractor::sourceFile() const
|
|||||||
|
|
||||||
void FileExtractor::extract()
|
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
|
// If the target directory already exists, remove it and its content
|
||||||
QDir targetDir(targetFolder);
|
QDir targetDir(m_targetFolder);
|
||||||
if (targetDir.exists())
|
if (targetDir.exists() && m_clearTargetPathContents)
|
||||||
targetDir.removeRecursively();
|
targetDir.removeRecursively();
|
||||||
|
|
||||||
// Create a new directory to generate a proper creation date
|
if (m_alwaysCreateDir) {
|
||||||
targetDir.mkdir(targetFolder);
|
// Create a new directory to generate a proper creation date
|
||||||
|
targetDir.mkdir(m_targetFolder);
|
||||||
|
}
|
||||||
|
|
||||||
Utils::Archive *archive = new Utils::Archive(m_sourceFile, m_targetPath);
|
Utils::Archive *archive = new Utils::Archive(m_sourceFile, m_targetPath);
|
||||||
QTC_ASSERT(archive->isValid(), delete archive; return);
|
QTC_ASSERT(archive->isValid(), delete archive; return);
|
||||||
|
|
||||||
m_timer.start();
|
m_timer.start();
|
||||||
qint64 bytesBefore = QStorageInfo(m_targetPath.toFileInfo().dir()).bytesAvailable();
|
m_bytesBefore = QStorageInfo(m_targetPath.toFileInfo().dir()).bytesAvailable();
|
||||||
qint64 compressedSize = QFileInfo(m_sourceFile.toString()).size();
|
m_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();
|
|
||||||
});
|
|
||||||
|
|
||||||
QObject::connect(archive, &Utils::Archive::outputReceived, this, [this](const QString &output) {
|
QObject::connect(archive, &Utils::Archive::outputReceived, this, [this](const QString &output) {
|
||||||
m_detailedText += output;
|
m_detailedText += output;
|
||||||
|
@@ -25,6 +25,8 @@ class FileExtractor : public QObject
|
|||||||
Q_PROPERTY(bool targetFolderExists READ targetFolderExists NOTIFY targetFolderExistsChanged)
|
Q_PROPERTY(bool targetFolderExists READ targetFolderExists NOTIFY targetFolderExistsChanged)
|
||||||
Q_PROPERTY(int progress READ progress NOTIFY progressChanged)
|
Q_PROPERTY(int progress READ progress NOTIFY progressChanged)
|
||||||
Q_PROPERTY(QDateTime birthTime READ birthTime NOTIFY birthTimeChanged)
|
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:
|
public:
|
||||||
explicit FileExtractor(QObject *parent = nullptr);
|
explicit FileExtractor(QObject *parent = nullptr);
|
||||||
@@ -36,7 +38,7 @@ public:
|
|||||||
void setTargetPath(const QString &path);
|
void setTargetPath(const QString &path);
|
||||||
void setSourceFile(QString &sourceFilePath);
|
void setSourceFile(QString &sourceFilePath);
|
||||||
void setArchiveName(QString &filePath);
|
void setArchiveName(QString &filePath);
|
||||||
const QString detailedText();
|
const QString detailedText() const;
|
||||||
bool finished() const;
|
bool finished() const;
|
||||||
QString currentFile() const;
|
QString currentFile() const;
|
||||||
QString size() const;
|
QString size() const;
|
||||||
@@ -44,6 +46,10 @@ public:
|
|||||||
bool targetFolderExists() const;
|
bool targetFolderExists() const;
|
||||||
int progress() const;
|
int progress() const;
|
||||||
QDateTime birthTime() const;
|
QDateTime birthTime() const;
|
||||||
|
void setClearTargetPathContents(bool value);
|
||||||
|
bool clearTargetPathContents() const;
|
||||||
|
void setAlwaysCreateDir(bool value);
|
||||||
|
bool alwaysCreateDir() const;
|
||||||
|
|
||||||
QString sourceFile() const;
|
QString sourceFile() const;
|
||||||
QString archiveName() const;
|
QString archiveName() const;
|
||||||
@@ -60,9 +66,12 @@ signals:
|
|||||||
void targetFolderExistsChanged();
|
void targetFolderExistsChanged();
|
||||||
void progressChanged();
|
void progressChanged();
|
||||||
void birthTimeChanged();
|
void birthTimeChanged();
|
||||||
|
void clearTargetPathContentsChanged();
|
||||||
|
void alwaysCreateDirChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Utils::FilePath m_targetPath;
|
Utils::FilePath m_targetPath;
|
||||||
|
QString m_targetFolder; // The same as m_targetPath, but with the archive name also.
|
||||||
Utils::FilePath m_sourceFile;
|
Utils::FilePath m_sourceFile;
|
||||||
QString m_detailedText;
|
QString m_detailedText;
|
||||||
bool m_finished = false;
|
bool m_finished = false;
|
||||||
@@ -73,6 +82,11 @@ private:
|
|||||||
QString m_archiveName;
|
QString m_archiveName;
|
||||||
int m_progress = 0;
|
int m_progress = 0;
|
||||||
QDateTime m_birthTime;
|
QDateTime m_birthTime;
|
||||||
|
bool m_clearTargetPathContents = false;
|
||||||
|
bool m_alwaysCreateDir = false;
|
||||||
|
|
||||||
|
qint64 m_bytesBefore = 0;
|
||||||
|
qint64 m_compressedSize = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // QmlDesigner
|
} // QmlDesigner
|
||||||
|
@@ -11,12 +11,15 @@
|
|||||||
|
|
||||||
namespace QmlDesigner {
|
namespace QmlDesigner {
|
||||||
|
|
||||||
QString QmlDesigner::ImageUtils::imageInfo(const QString &path)
|
QString QmlDesigner::ImageUtils::imageInfo(const QString &path, bool fetchSizeInfo)
|
||||||
{
|
{
|
||||||
QFileInfo info(path);
|
QFileInfo info(path);
|
||||||
if (!info.exists())
|
if (!info.exists())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
|
if (!fetchSizeInfo)
|
||||||
|
return QLatin1String("(%1)").arg(info.suffix());
|
||||||
|
|
||||||
int width = 0;
|
int width = 0;
|
||||||
int height = 0;
|
int height = 0;
|
||||||
const QString suffix = info.suffix();
|
const QString suffix = info.suffix();
|
||||||
|
@@ -11,7 +11,7 @@ class ImageUtils
|
|||||||
public:
|
public:
|
||||||
ImageUtils();
|
ImageUtils();
|
||||||
|
|
||||||
static QString imageInfo(const QString &path);
|
static QString imageInfo(const QString &path, bool sizeInfo = true);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QmlDesigner
|
} // namespace QmlDesigner
|
||||||
|
@@ -88,6 +88,8 @@ void DesignerSettings::fromSettings(QSettings *settings)
|
|||||||
restoreValue(settings, DesignerSettingsKey::OLD_STATES_EDITOR, false);
|
restoreValue(settings, DesignerSettingsKey::OLD_STATES_EDITOR, false);
|
||||||
restoreValue(settings, DesignerSettingsKey::EDITOR_ZOOM_FACTOR, 1.0);
|
restoreValue(settings, DesignerSettingsKey::EDITOR_ZOOM_FACTOR, 1.0);
|
||||||
restoreValue(settings, DesignerSettingsKey::ACTIONS_MERGE_TEMPLATE_ENABLED, false);
|
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();
|
||||||
settings->endGroup();
|
settings->endGroup();
|
||||||
|
@@ -60,6 +60,7 @@ inline constexpr char SMOOTH_RENDERING[] = "SmoothRendering";
|
|||||||
inline constexpr char OLD_STATES_EDITOR[] = "ForceOldStatesEditor";
|
inline constexpr char OLD_STATES_EDITOR[] = "ForceOldStatesEditor";
|
||||||
inline constexpr char EDITOR_ZOOM_FACTOR[] = "EditorZoomFactor";
|
inline constexpr char EDITOR_ZOOM_FACTOR[] = "EditorZoomFactor";
|
||||||
inline constexpr char ACTIONS_MERGE_TEMPLATE_ENABLED[] = "ActionsMergeTemplateEnabled";
|
inline constexpr char ACTIONS_MERGE_TEMPLATE_ENABLED[] = "ActionsMergeTemplateEnabled";
|
||||||
|
inline constexpr char DOWNLOADABLE_BUNDLES_URL[] = "DownloadableBundlesUrl";
|
||||||
}
|
}
|
||||||
|
|
||||||
class QMLDESIGNERBASE_EXPORT DesignerSettings
|
class QMLDESIGNERBASE_EXPORT DesignerSettings
|
||||||
|
@@ -108,6 +108,43 @@ DataModelDownloader::DataModelDownloader(QObject * /* parent */)
|
|||||||
this,
|
this,
|
||||||
&DataModelDownloader::targetPathMustChange);
|
&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()
|
bool DataModelDownloader::start()
|
||||||
@@ -122,42 +159,10 @@ bool DataModelDownloader::start()
|
|||||||
m_fileDownloader.setUrl(QUrl::fromUserInput(
|
m_fileDownloader.setUrl(QUrl::fromUserInput(
|
||||||
"https://download.qt.io/learning/examples/qtdesignstudio/dataImports.zip"));
|
"https://download.qt.io/learning/examples/qtdesignstudio/dataImports.zip"));
|
||||||
|
|
||||||
bool started = false;
|
m_started = false;
|
||||||
|
|
||||||
connect(&m_fileDownloader, &QmlDesigner::FileDownloader::availableChanged, this, [this, &started]() {
|
connect(&m_fileDownloader, &QmlDesigner::FileDownloader::availableChanged, this, &DataModelDownloader::onAvailableChanged);
|
||||||
|
return m_started;
|
||||||
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;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DataModelDownloader::exists() const
|
bool DataModelDownloader::exists() const
|
||||||
|
@@ -42,9 +42,11 @@ signals:
|
|||||||
void targetPathMustChange(const QString &newPath);
|
void targetPathMustChange(const QString &newPath);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void onAvailableChanged();
|
||||||
QmlDesigner::FileDownloader m_fileDownloader;
|
QmlDesigner::FileDownloader m_fileDownloader;
|
||||||
QDateTime m_birthTime;
|
QDateTime m_birthTime;
|
||||||
bool m_exists = false;
|
bool m_exists = false;
|
||||||
bool m_available = false;
|
bool m_available = false;
|
||||||
bool m_forceDownload = false;
|
bool m_forceDownload = false;
|
||||||
|
bool m_started = false;
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user