2022-08-19 15:59:36 +02:00
|
|
|
// Copyright (C) 2022 The Qt Company Ltd.
|
2022-12-21 10:12:09 +01:00
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
2022-02-03 19:49:27 +02:00
|
|
|
|
|
|
|
#include "assetslibrarywidget.h"
|
|
|
|
|
|
|
|
#include "assetslibraryiconprovider.h"
|
2022-12-15 19:26:49 +02:00
|
|
|
#include "assetslibrarymodel.h"
|
2023-02-02 21:03:17 +02:00
|
|
|
#include "assetslibraryview.h"
|
2022-02-03 19:49:27 +02:00
|
|
|
|
2024-04-20 00:25:43 +03:00
|
|
|
#include <designeractionmanager.h>
|
|
|
|
#include <designerpaths.h>
|
|
|
|
#include <hdrimage.h>
|
|
|
|
#include <import.h>
|
|
|
|
#include <modelnodeoperations.h>
|
|
|
|
#include <nodemetainfo.h>
|
|
|
|
#include <qmldesignerconstants.h>
|
|
|
|
#include <qmldesignerplugin.h>
|
2023-03-07 16:51:02 +01:00
|
|
|
#include <studioquickwidget.h>
|
2024-04-20 00:25:43 +03:00
|
|
|
#include <theme.h>
|
2024-05-15 19:38:37 +03:00
|
|
|
#include <uniquename.h>
|
2024-04-20 00:25:43 +03:00
|
|
|
#include <utils3d.h>
|
2023-03-07 16:51:02 +01:00
|
|
|
|
2023-02-07 16:44:30 +02:00
|
|
|
#include <coreplugin/fileutils.h>
|
2022-02-03 19:49:27 +02:00
|
|
|
#include <coreplugin/icore.h>
|
|
|
|
#include <coreplugin/messagebox.h>
|
|
|
|
|
2023-03-03 12:27:00 +02:00
|
|
|
#include <utils/algorithm.h>
|
2024-05-13 18:53:25 +03:00
|
|
|
#include <utils/asset.h>
|
2023-03-03 12:27:00 +02:00
|
|
|
#include <utils/environment.h>
|
|
|
|
#include <utils/filepath.h>
|
|
|
|
#include <utils/qtcassert.h>
|
|
|
|
|
2022-02-03 19:49:27 +02:00
|
|
|
#include <QFileDialog>
|
|
|
|
#include <QFileInfo>
|
|
|
|
#include <QImageReader>
|
2024-01-23 17:50:34 +02:00
|
|
|
#include <QMessageBox>
|
2022-02-03 19:49:27 +02:00
|
|
|
#include <QMimeData>
|
|
|
|
#include <QMouseEvent>
|
2023-03-03 12:27:00 +02:00
|
|
|
#include <QPointF>
|
2022-02-03 19:49:27 +02:00
|
|
|
#include <QQmlContext>
|
|
|
|
#include <QQuickItem>
|
2023-03-03 12:27:00 +02:00
|
|
|
#include <QShortcut>
|
|
|
|
#include <QToolButton>
|
|
|
|
#include <QVBoxLayout>
|
2022-02-03 19:49:27 +02:00
|
|
|
|
2024-07-26 01:32:07 +02:00
|
|
|
#include <memory>
|
|
|
|
|
2022-02-03 19:49:27 +02:00
|
|
|
namespace QmlDesigner {
|
|
|
|
|
|
|
|
static QString propertyEditorResourcesPath()
|
|
|
|
{
|
|
|
|
#ifdef SHARE_QML_PATH
|
2022-08-24 14:57:43 +02:00
|
|
|
if (Utils::qtcEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE"))
|
2022-02-03 19:49:27 +02:00
|
|
|
return QLatin1String(SHARE_QML_PATH) + "/propertyEditorQmlSources";
|
|
|
|
#endif
|
|
|
|
return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AssetsLibraryWidget::eventFilter(QObject *obj, QEvent *event)
|
|
|
|
{
|
|
|
|
if (event->type() == QEvent::FocusOut) {
|
2023-03-27 16:12:45 +03:00
|
|
|
if (obj == m_assetsWidget->quickWidget())
|
2022-02-03 19:49:27 +02:00
|
|
|
QMetaObject::invokeMethod(m_assetsWidget->rootObject(), "handleViewFocusOut");
|
|
|
|
} else if (event->type() == QMouseEvent::MouseMove) {
|
2023-03-28 01:58:46 +03:00
|
|
|
if (!m_assetsToDrag.isEmpty() && m_assetsView->model()) {
|
2022-02-03 19:49:27 +02:00
|
|
|
QMouseEvent *me = static_cast<QMouseEvent *>(event);
|
2023-06-08 08:26:57 +02:00
|
|
|
if ((me->globalPosition().toPoint() - m_dragStartPoint).manhattanLength() > 10) {
|
2024-07-26 01:32:07 +02:00
|
|
|
auto mimeData = std::make_unique<QMimeData>();
|
2022-05-10 20:31:05 +03:00
|
|
|
mimeData->setData(Constants::MIME_TYPE_ASSETS, m_assetsToDrag.join(',').toUtf8());
|
2023-02-01 12:10:34 +02:00
|
|
|
|
|
|
|
QList<QUrl> urlsToDrag = Utils::transform(m_assetsToDrag, [](const QString &path) {
|
|
|
|
return QUrl::fromLocalFile(path);
|
|
|
|
});
|
|
|
|
|
|
|
|
mimeData->setUrls(urlsToDrag);
|
|
|
|
|
2024-07-26 01:32:07 +02:00
|
|
|
m_assetsView->model()->startDrag(std::move(mimeData),
|
|
|
|
m_assetsIconProvider->requestPixmap(m_assetsToDrag[0],
|
|
|
|
nullptr,
|
|
|
|
{128, 128}),
|
|
|
|
this);
|
2023-02-01 12:10:34 +02:00
|
|
|
|
2022-02-03 19:49:27 +02:00
|
|
|
m_assetsToDrag.clear();
|
|
|
|
}
|
|
|
|
}
|
2022-10-12 20:54:24 +03:00
|
|
|
} else if (event->type() == QMouseEvent::MouseButtonRelease) {
|
|
|
|
m_assetsToDrag.clear();
|
2023-02-07 15:26:00 +02:00
|
|
|
setIsDragging(false);
|
2022-02-03 19:49:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return QObject::eventFilter(obj, event);
|
|
|
|
}
|
|
|
|
|
2022-04-11 17:20:37 +02:00
|
|
|
AssetsLibraryWidget::AssetsLibraryWidget(AsynchronousImageCache &asynchronousFontImageCache,
|
2023-02-02 21:03:17 +02:00
|
|
|
SynchronousImageCache &synchronousFontImageCache,
|
|
|
|
AssetsLibraryView *view)
|
2022-11-23 11:49:45 +02:00
|
|
|
: m_itemIconSize{24, 24}
|
|
|
|
, m_fontImageCache{synchronousFontImageCache}
|
|
|
|
, m_assetsIconProvider{new AssetsLibraryIconProvider(synchronousFontImageCache)}
|
|
|
|
, m_assetsModel{new AssetsLibraryModel(this)}
|
2023-02-02 21:03:17 +02:00
|
|
|
, m_assetsView{view}
|
2023-03-28 01:58:46 +03:00
|
|
|
, m_createTextures{view}
|
2024-05-02 19:47:34 +02:00
|
|
|
, m_assetsWidget{Utils::makeUniqueObjectPtr<StudioQuickWidget>(this)}
|
2022-02-03 19:49:27 +02:00
|
|
|
{
|
|
|
|
setWindowTitle(tr("Assets Library", "Title of assets library widget"));
|
|
|
|
setMinimumWidth(250);
|
|
|
|
|
2023-03-07 16:51:02 +01:00
|
|
|
m_assetsWidget->quickWidget()->installEventFilter(this);
|
2022-02-03 19:49:27 +02:00
|
|
|
|
|
|
|
m_fontPreviewTooltipBackend = std::make_unique<PreviewTooltipBackend>(asynchronousFontImageCache);
|
2022-03-21 14:06:39 +02:00
|
|
|
// We want font images to have custom size, so don't scale them in the tooltip
|
|
|
|
m_fontPreviewTooltipBackend->setScaleImage(false);
|
2022-02-03 19:49:27 +02:00
|
|
|
// Note: Though the text specified here appears in UI, it shouldn't be translated, as it's
|
|
|
|
// a commonly used sentence to preview the font glyphs in latin fonts.
|
|
|
|
// For fonts that do not have latin glyphs, the font family name will have to suffice for preview.
|
|
|
|
m_fontPreviewTooltipBackend->setAuxiliaryData(
|
2022-03-21 14:06:39 +02:00
|
|
|
ImageCache::FontCollectorSizeAuxiliaryData{QSize{300, 150},
|
2022-02-03 19:49:27 +02:00
|
|
|
Theme::getColor(Theme::DStextColor).name(),
|
|
|
|
QStringLiteral("The quick brown fox jumps\n"
|
|
|
|
"over the lazy dog\n"
|
|
|
|
"1234567890")});
|
|
|
|
// create assets widget
|
2023-03-17 13:12:02 +01:00
|
|
|
m_assetsWidget->quickWidget()->setObjectName(Constants::OBJECT_NAME_ASSET_LIBRARY);
|
2022-02-03 19:49:27 +02:00
|
|
|
m_assetsWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
|
|
|
|
Theme::setupTheme(m_assetsWidget->engine());
|
|
|
|
m_assetsWidget->engine()->addImportPath(propertyEditorResourcesPath() + "/imports");
|
|
|
|
m_assetsWidget->setClearColor(Theme::getColor(Theme::Color::QmlDesigner_BackgroundColorDarkAlternate));
|
|
|
|
m_assetsWidget->engine()->addImageProvider("qmldesigner_assets", m_assetsIconProvider);
|
|
|
|
|
2023-12-13 21:05:22 +01:00
|
|
|
connect(m_assetsModel, &AssetsLibraryModel::fileChanged,
|
|
|
|
QmlDesignerPlugin::instance(), &QmlDesignerPlugin::assetChanged);
|
2022-10-03 14:06:29 +03:00
|
|
|
|
2024-01-23 17:50:34 +02:00
|
|
|
connect(m_assetsModel, &AssetsLibraryModel::effectsDeleted,
|
|
|
|
this, &AssetsLibraryWidget::handleDeleteEffects);
|
|
|
|
|
2022-02-03 19:49:27 +02:00
|
|
|
auto layout = new QVBoxLayout(this);
|
|
|
|
layout->setContentsMargins({});
|
|
|
|
layout->setSpacing(0);
|
2024-05-02 19:47:34 +02:00
|
|
|
layout->addWidget(m_assetsWidget.get());
|
2022-02-03 19:49:27 +02:00
|
|
|
|
|
|
|
updateSearch();
|
|
|
|
|
|
|
|
setStyleSheet(Theme::replaceCssColors(
|
|
|
|
QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css"))));
|
|
|
|
|
2023-06-09 09:30:41 +02:00
|
|
|
m_qmlSourceUpdateShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_F6), this);
|
2022-02-03 19:49:27 +02:00
|
|
|
connect(m_qmlSourceUpdateShortcut, &QShortcut::activated, this, &AssetsLibraryWidget::reloadQmlSource);
|
2023-03-07 16:51:02 +01:00
|
|
|
connect(this,
|
|
|
|
&AssetsLibraryWidget::extFilesDrop,
|
|
|
|
this,
|
|
|
|
&AssetsLibraryWidget::handleExtFilesDrop,
|
|
|
|
Qt::QueuedConnection);
|
|
|
|
|
|
|
|
QmlDesignerPlugin::trackWidgetFocusTime(this, Constants::EVENT_ASSETSLIBRARY_TIME);
|
|
|
|
|
|
|
|
auto map = m_assetsWidget->registerPropertyMap("AssetsLibraryBackend");
|
2022-02-03 19:49:27 +02:00
|
|
|
|
2023-03-07 16:51:02 +01:00
|
|
|
map->setProperties({{"assetsModel", QVariant::fromValue(m_assetsModel)},
|
|
|
|
{"rootView", QVariant::fromValue(this)},
|
|
|
|
{"tooltipBackend", QVariant::fromValue(m_fontPreviewTooltipBackend.get())}});
|
2022-05-19 19:09:59 +02:00
|
|
|
|
2022-02-03 19:49:27 +02:00
|
|
|
// init the first load of the QML UI elements
|
|
|
|
reloadQmlSource();
|
2023-08-04 13:48:15 +03:00
|
|
|
|
|
|
|
setFocusProxy(m_assetsWidget->quickWidget());
|
2022-02-03 19:49:27 +02:00
|
|
|
}
|
|
|
|
|
2024-07-26 01:32:07 +02:00
|
|
|
AssetsLibraryWidget::~AssetsLibraryWidget() = default;
|
|
|
|
|
2023-02-02 21:03:17 +02:00
|
|
|
void AssetsLibraryWidget::contextHelp(const Core::IContext::HelpCallback &callback) const
|
|
|
|
{
|
|
|
|
if (m_assetsView)
|
|
|
|
QmlDesignerPlugin::contextHelp(callback, m_assetsView->contextHelpId());
|
|
|
|
else
|
|
|
|
callback({});
|
|
|
|
}
|
|
|
|
|
|
|
|
void AssetsLibraryWidget::deleteSelectedAssets()
|
|
|
|
{
|
|
|
|
emit deleteSelectedAssetsRequested();
|
|
|
|
}
|
|
|
|
|
2023-01-26 15:12:00 +02:00
|
|
|
QString AssetsLibraryWidget::getUniqueEffectPath(const QString &parentFolder, const QString &effectName)
|
|
|
|
{
|
2024-05-13 18:53:25 +03:00
|
|
|
QString effectsDir = ModelNodeOperations::getEffectsDefaultDirectory(parentFolder);
|
2024-05-14 00:16:50 +03:00
|
|
|
QString effectPath = QLatin1String("%1/%2.qep").arg(effectsDir, effectName);
|
2023-01-26 15:12:00 +02:00
|
|
|
|
2024-05-21 00:05:19 +03:00
|
|
|
return UniqueName::generatePath(effectPath);
|
2023-01-26 15:12:00 +02:00
|
|
|
}
|
|
|
|
|
2024-01-26 14:55:50 +02:00
|
|
|
bool AssetsLibraryWidget::createNewEffect(const QString &effectPath, bool openInEffectComposer)
|
2023-01-26 15:12:00 +02:00
|
|
|
{
|
|
|
|
bool created = QFile(effectPath).open(QIODevice::WriteOnly);
|
|
|
|
|
2024-01-26 14:55:50 +02:00
|
|
|
if (created && openInEffectComposer) {
|
|
|
|
openEffectComposer(effectPath);
|
2023-01-26 15:12:00 +02:00
|
|
|
emit directoryCreated(QFileInfo(effectPath).absolutePath());
|
|
|
|
}
|
|
|
|
|
|
|
|
return created;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AssetsLibraryWidget::canCreateEffects() const
|
|
|
|
{
|
|
|
|
#ifdef LICENSECHECKER
|
|
|
|
return checkLicense() == FoundLicense::enterprise;
|
|
|
|
#else
|
|
|
|
return true;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2023-02-07 16:44:30 +02:00
|
|
|
void AssetsLibraryWidget::showInGraphicalShell(const QString &path)
|
|
|
|
{
|
|
|
|
Core::FileUtils::showInGraphicalShell(Core::ICore::dialogParent(), Utils::FilePath::fromString(path));
|
|
|
|
}
|
|
|
|
|
|
|
|
QString AssetsLibraryWidget::showInGraphicalShellMsg() const
|
|
|
|
{
|
|
|
|
return Core::FileUtils::msgGraphicalShellAction();
|
|
|
|
}
|
|
|
|
|
2023-03-09 17:17:03 +02:00
|
|
|
int AssetsLibraryWidget::qtVersion() const
|
2022-11-23 11:49:45 +02:00
|
|
|
{
|
2023-03-09 17:17:03 +02:00
|
|
|
return QT_VERSION;
|
2022-11-23 11:49:45 +02:00
|
|
|
}
|
|
|
|
|
2022-11-24 19:23:03 +02:00
|
|
|
void AssetsLibraryWidget::addTextures(const QStringList &filePaths)
|
|
|
|
{
|
2023-03-28 01:58:46 +03:00
|
|
|
m_assetsView->executeInTransaction(__FUNCTION__, [&] {
|
2024-02-19 15:57:08 +01:00
|
|
|
m_createTextures.execute(filePaths,
|
|
|
|
AddTextureMode::Texture,
|
|
|
|
Utils3D::active3DSceneId(m_assetsView->model()));
|
2023-03-28 01:58:46 +03:00
|
|
|
});
|
2022-11-24 19:23:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void AssetsLibraryWidget::addLightProbe(const QString &filePath)
|
|
|
|
{
|
2023-03-28 01:58:46 +03:00
|
|
|
m_assetsView->executeInTransaction(__FUNCTION__, [&] {
|
2024-02-19 15:57:08 +01:00
|
|
|
m_createTextures.execute({filePath},
|
|
|
|
AddTextureMode::LightProbe,
|
|
|
|
Utils3D::active3DSceneId(m_assetsView->model()));
|
2023-03-28 01:58:46 +03:00
|
|
|
});
|
2022-11-24 19:23:03 +02:00
|
|
|
}
|
|
|
|
|
2023-03-28 01:58:46 +03:00
|
|
|
void AssetsLibraryWidget::updateContextMenuActionsEnableState()
|
2023-01-12 17:53:10 +02:00
|
|
|
{
|
2024-02-20 18:05:38 +01:00
|
|
|
setHasMaterialLibrary(Utils3D::materialLibraryNode(m_assetsView).isValid()
|
2023-03-28 01:58:46 +03:00
|
|
|
&& m_assetsView->model()->hasImport("QtQuick3D"));
|
2023-01-12 17:53:10 +02:00
|
|
|
|
2024-02-19 15:57:08 +01:00
|
|
|
ModelNode activeSceneEnv = m_createTextures.resolveSceneEnv(
|
|
|
|
Utils3D::active3DSceneId(m_assetsView->model()));
|
2023-03-28 01:58:46 +03:00
|
|
|
setHasSceneEnv(activeSceneEnv.isValid());
|
2023-01-12 17:53:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void AssetsLibraryWidget::setHasMaterialLibrary(bool enable)
|
|
|
|
{
|
|
|
|
if (m_hasMaterialLibrary == enable)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_hasMaterialLibrary = enable;
|
|
|
|
emit hasMaterialLibraryChanged();
|
|
|
|
}
|
|
|
|
|
2023-03-28 01:58:46 +03:00
|
|
|
void AssetsLibraryWidget::setHasSceneEnv(bool b)
|
|
|
|
{
|
|
|
|
if (b == m_hasSceneEnv)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_hasSceneEnv = b;
|
|
|
|
emit hasSceneEnvChanged();
|
|
|
|
}
|
|
|
|
|
2024-03-07 15:49:41 +01:00
|
|
|
void AssetsLibraryWidget::handleDeleteEffects([[maybe_unused]] const QStringList &effectNames)
|
2024-01-23 17:50:34 +02:00
|
|
|
{
|
2024-03-07 15:49:41 +01:00
|
|
|
#ifdef QDS_USE_PROJECTSTORAGE
|
|
|
|
// That code has to rewritten with modules. Seem try to find all effects nodes.
|
|
|
|
#else
|
2024-01-23 17:50:34 +02:00
|
|
|
DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument();
|
|
|
|
if (!document)
|
|
|
|
return;
|
|
|
|
|
|
|
|
bool clearStacks = false;
|
|
|
|
|
|
|
|
// Remove usages of deleted effects from the current document
|
|
|
|
m_assetsView->executeInTransaction(__FUNCTION__, [&]() {
|
|
|
|
QList<ModelNode> allNodes = m_assetsView->allModelNodes();
|
2024-04-09 15:27:42 +03:00
|
|
|
const QString typeTemplate = "%1.%2.%2";
|
|
|
|
const QString importUrlTemplate = "%1.%2";
|
2024-01-23 17:50:34 +02:00
|
|
|
const Imports imports = m_assetsView->model()->imports();
|
|
|
|
Imports removedImports;
|
2024-04-09 15:27:42 +03:00
|
|
|
const QString typePrefix = QmlDesignerPlugin::instance()->documentManager()
|
|
|
|
.generatedComponentUtils().composedEffectsTypePrefix();
|
2024-01-23 17:50:34 +02:00
|
|
|
for (const QString &effectName : effectNames) {
|
|
|
|
if (effectName.isEmpty())
|
|
|
|
continue;
|
2024-04-09 15:27:42 +03:00
|
|
|
const TypeName type = typeTemplate.arg(typePrefix, effectName).toUtf8();
|
2024-01-23 17:50:34 +02:00
|
|
|
for (ModelNode &node : allNodes) {
|
|
|
|
if (node.metaInfo().typeName() == type) {
|
|
|
|
clearStacks = true;
|
|
|
|
node.destroy();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-09 15:27:42 +03:00
|
|
|
const QString importPath = importUrlTemplate.arg(typePrefix, effectName);
|
2024-01-23 17:50:34 +02:00
|
|
|
Import removedImport = Utils::findOrDefault(imports, [&importPath](const Import &import) {
|
|
|
|
return import.url() == importPath;
|
|
|
|
});
|
|
|
|
if (!removedImport.isEmpty())
|
|
|
|
removedImports.append(removedImport);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!removedImports.isEmpty()) {
|
|
|
|
m_assetsView->model()->changeImports({}, removedImports);
|
|
|
|
clearStacks = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// The size check here is to weed out cases where project path somehow resolves
|
|
|
|
// to just slash. Shortest legal currentProjectDirPath() would be "/a/".
|
|
|
|
if (m_assetsModel->currentProjectDirPath().size() < 3)
|
|
|
|
return;
|
|
|
|
|
|
|
|
Utils::FilePath effectsDir = ModelNodeOperations::getEffectsImportDirectory();
|
|
|
|
|
|
|
|
// Delete the effect modules
|
|
|
|
for (const QString &effectName : effectNames) {
|
|
|
|
Utils::FilePath eDir = effectsDir.pathAppended(effectName);
|
|
|
|
if (eDir.exists() && eDir.toString().startsWith(m_assetsModel->currentProjectDirPath())) {
|
|
|
|
QString error;
|
|
|
|
eDir.removeRecursively(&error);
|
|
|
|
if (!error.isEmpty()) {
|
|
|
|
QMessageBox::warning(Core::ICore::dialogParent(),
|
|
|
|
tr("Failed to Delete Effect Resources"),
|
|
|
|
tr("Could not delete \"%1\".").arg(eDir.toString()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset undo stack as removing effect components cannot be undone, and thus the stack will
|
|
|
|
// contain only unworkable states.
|
|
|
|
if (clearStacks)
|
|
|
|
document->clearUndoRedoStacks();
|
2024-02-08 17:03:41 +02:00
|
|
|
|
|
|
|
m_assetsView->emitCustomNotification("effectcomposer_effects_deleted", {}, {effectNames});
|
2024-03-07 15:49:41 +01:00
|
|
|
#endif
|
2024-01-23 17:50:34 +02:00
|
|
|
}
|
|
|
|
|
2022-11-23 11:49:45 +02:00
|
|
|
void AssetsLibraryWidget::invalidateThumbnail(const QString &id)
|
|
|
|
{
|
|
|
|
m_assetsIconProvider->invalidateThumbnail(id);
|
|
|
|
}
|
2022-02-03 19:49:27 +02:00
|
|
|
|
2022-12-15 23:20:43 +02:00
|
|
|
QSize AssetsLibraryWidget::imageSize(const QString &id)
|
|
|
|
{
|
|
|
|
return m_assetsIconProvider->imageSize(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
QString AssetsLibraryWidget::assetFileSize(const QString &id)
|
|
|
|
{
|
|
|
|
qint64 fileSize = m_assetsIconProvider->fileSize(id);
|
|
|
|
return QLocale::system().formattedDataSize(fileSize, 2, QLocale::DataSizeTraditionalFormat);
|
|
|
|
}
|
|
|
|
|
2023-03-06 17:27:25 +02:00
|
|
|
bool AssetsLibraryWidget::assetIsImageOrTexture(const QString &id)
|
2022-12-15 23:20:43 +02:00
|
|
|
{
|
2023-03-06 17:27:25 +02:00
|
|
|
return Asset(id).isValidTextureSource();
|
2022-12-15 23:20:43 +02:00
|
|
|
}
|
|
|
|
|
2022-02-03 19:49:27 +02:00
|
|
|
QList<QToolButton *> AssetsLibraryWidget::createToolBarWidgets()
|
|
|
|
{
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2022-09-26 16:29:03 +03:00
|
|
|
void AssetsLibraryWidget::handleSearchFilterChanged(const QString &filterText)
|
2022-02-03 19:49:27 +02:00
|
|
|
{
|
2024-04-20 00:25:43 +03:00
|
|
|
if (filterText == m_filterText || (!m_assetsModel->hasFiles()
|
2022-11-23 11:49:45 +02:00
|
|
|
&& filterText.contains(m_filterText, Qt::CaseInsensitive)))
|
|
|
|
return;
|
2022-02-11 14:00:22 +02:00
|
|
|
|
|
|
|
m_filterText = filterText;
|
|
|
|
updateSearch();
|
2022-02-03 19:49:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void AssetsLibraryWidget::handleAddAsset()
|
|
|
|
{
|
|
|
|
addResources({});
|
|
|
|
}
|
|
|
|
|
2022-11-23 11:49:45 +02:00
|
|
|
void AssetsLibraryWidget::emitExtFilesDrop(const QList<QUrl> &simpleFilePaths,
|
|
|
|
const QList<QUrl> &complexFilePaths,
|
|
|
|
const QString &targetDirPath)
|
|
|
|
{
|
|
|
|
// workaround for but QDS-8010: we need to postpone the call to handleExtFilesDrop, otherwise
|
|
|
|
// the TreeViewDelegate might be recreated (therefore, destroyed) while we're still in a handler
|
|
|
|
// of a QML DropArea which is a child of the delegate being destroyed - this would cause a crash.
|
|
|
|
emit extFilesDrop(simpleFilePaths, complexFilePaths, targetDirPath);
|
|
|
|
}
|
|
|
|
|
2022-05-10 10:59:47 +02:00
|
|
|
void AssetsLibraryWidget::handleExtFilesDrop(const QList<QUrl> &simpleFilePaths,
|
|
|
|
const QList<QUrl> &complexFilePaths,
|
2022-03-15 13:02:15 +02:00
|
|
|
const QString &targetDirPath)
|
2022-02-03 19:49:27 +02:00
|
|
|
{
|
2022-05-10 10:59:47 +02:00
|
|
|
auto toLocalFile = [](const QUrl &url) { return url.toLocalFile(); };
|
|
|
|
|
|
|
|
QStringList simpleFilePathStrings = Utils::transform<QStringList>(simpleFilePaths, toLocalFile);
|
2023-03-03 14:46:13 +02:00
|
|
|
QStringList complexFilePathStrings = Utils::transform<QStringList>(complexFilePaths, toLocalFile);
|
2022-05-10 10:59:47 +02:00
|
|
|
|
|
|
|
if (!simpleFilePathStrings.isEmpty()) {
|
2022-03-15 15:29:27 +02:00
|
|
|
if (targetDirPath.isEmpty()) {
|
2022-05-10 10:59:47 +02:00
|
|
|
addResources(simpleFilePathStrings);
|
2022-03-15 15:29:27 +02:00
|
|
|
} else {
|
2023-03-03 14:46:13 +02:00
|
|
|
bool isDropOnRoot = m_assetsModel->rootPath() == targetDirPath;
|
2022-05-10 10:59:47 +02:00
|
|
|
AddFilesResult result = ModelNodeOperations::addFilesToProject(simpleFilePathStrings,
|
2023-03-03 14:46:13 +02:00
|
|
|
targetDirPath,
|
|
|
|
isDropOnRoot);
|
2022-11-23 11:49:45 +02:00
|
|
|
if (result.status() == AddFilesResult::Failed) {
|
2024-07-01 16:15:19 +03:00
|
|
|
QWidget *w = Core::AsynchronousMessageBox::warning(tr("Failed to Add Files"),
|
|
|
|
tr("Could not add %1 to project.")
|
|
|
|
.arg(simpleFilePathStrings.join(' ')));
|
|
|
|
// Avoid multiple modal dialogs open at the same time
|
|
|
|
auto mb = qobject_cast<QMessageBox *>(w);
|
|
|
|
if (mb && !complexFilePathStrings.empty())
|
|
|
|
mb->exec();
|
2022-03-15 15:29:27 +02:00
|
|
|
}
|
2022-03-15 13:02:15 +02:00
|
|
|
}
|
2022-03-03 12:40:40 +02:00
|
|
|
}
|
|
|
|
|
2022-05-10 10:59:47 +02:00
|
|
|
if (!complexFilePathStrings.empty())
|
|
|
|
addResources(complexFilePathStrings);
|
2023-02-23 16:31:07 +02:00
|
|
|
|
2023-03-28 01:58:46 +03:00
|
|
|
m_assetsView->model()->endDrag();
|
2022-02-03 19:49:27 +02:00
|
|
|
}
|
|
|
|
|
2022-03-15 13:02:15 +02:00
|
|
|
QSet<QString> AssetsLibraryWidget::supportedAssetSuffixes(bool complex)
|
2022-02-03 19:49:27 +02:00
|
|
|
{
|
|
|
|
const QList<AddResourceHandler> handlers = QmlDesignerPlugin::instance()->viewManager()
|
|
|
|
.designerActionManager().addResourceHandler();
|
|
|
|
|
|
|
|
QSet<QString> suffixes;
|
2022-03-15 13:02:15 +02:00
|
|
|
for (const AddResourceHandler &handler : handlers) {
|
2023-02-13 15:17:33 +02:00
|
|
|
if (Asset::isSupported(handler.filter) != complex)
|
2022-03-15 13:02:15 +02:00
|
|
|
suffixes.insert(handler.filter);
|
|
|
|
}
|
2022-02-03 19:49:27 +02:00
|
|
|
|
|
|
|
return suffixes;
|
|
|
|
}
|
|
|
|
|
2024-01-26 14:55:50 +02:00
|
|
|
void AssetsLibraryWidget::openEffectComposer(const QString &filePath)
|
2022-10-03 14:06:29 +03:00
|
|
|
{
|
2024-01-26 14:55:50 +02:00
|
|
|
ModelNodeOperations::openEffectComposer(filePath);
|
2022-10-03 14:06:29 +03:00
|
|
|
}
|
|
|
|
|
2022-02-03 19:49:27 +02:00
|
|
|
QString AssetsLibraryWidget::qmlSourcesPath()
|
|
|
|
{
|
|
|
|
#ifdef SHARE_QML_PATH
|
2022-08-24 14:57:43 +02:00
|
|
|
if (Utils::qtcEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE"))
|
2023-03-07 16:51:02 +01:00
|
|
|
return QLatin1String(SHARE_QML_PATH) + "/assetsLibraryQmlSources";
|
2022-02-03 19:49:27 +02:00
|
|
|
#endif
|
2023-03-07 16:51:02 +01:00
|
|
|
return Core::ICore::resourcePath("qmldesigner/assetsLibraryQmlSources").toString();
|
2022-02-03 19:49:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void AssetsLibraryWidget::clearSearchFilter()
|
|
|
|
{
|
|
|
|
QMetaObject::invokeMethod(m_assetsWidget->rootObject(), "clearSearchFilter");
|
|
|
|
}
|
|
|
|
|
|
|
|
void AssetsLibraryWidget::reloadQmlSource()
|
|
|
|
{
|
|
|
|
const QString assetsQmlPath = qmlSourcesPath() + "/Assets.qml";
|
|
|
|
QTC_ASSERT(QFileInfo::exists(assetsQmlPath), return);
|
|
|
|
m_assetsWidget->setSource(QUrl::fromLocalFile(assetsQmlPath));
|
|
|
|
}
|
|
|
|
|
|
|
|
void AssetsLibraryWidget::updateSearch()
|
|
|
|
{
|
|
|
|
m_assetsModel->setSearchText(m_filterText);
|
|
|
|
}
|
|
|
|
|
2023-02-07 15:26:00 +02:00
|
|
|
void AssetsLibraryWidget::setIsDragging(bool val)
|
|
|
|
{
|
|
|
|
if (m_isDragging != val) {
|
|
|
|
m_isDragging = val;
|
|
|
|
emit isDraggingChanged();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-03 19:49:27 +02:00
|
|
|
void AssetsLibraryWidget::setResourcePath(const QString &resourcePath)
|
|
|
|
{
|
|
|
|
m_assetsModel->setRootPath(resourcePath);
|
2022-11-23 11:49:45 +02:00
|
|
|
m_assetsIconProvider->clearCache();
|
2022-02-03 19:49:27 +02:00
|
|
|
updateSearch();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AssetsLibraryWidget::startDragAsset(const QStringList &assetPaths, const QPointF &mousePos)
|
|
|
|
{
|
|
|
|
// Actual drag is created after mouse has moved to avoid a QDrag bug that causes drag to stay
|
|
|
|
// active (and blocks mouse release) if mouse is released at the same spot of the drag start.
|
|
|
|
m_assetsToDrag = assetPaths;
|
|
|
|
m_dragStartPoint = mousePos.toPoint();
|
2023-02-07 15:26:00 +02:00
|
|
|
setIsDragging(true);
|
2022-02-03 19:49:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QPair<QString, QByteArray> AssetsLibraryWidget::getAssetTypeAndData(const QString &assetPath)
|
|
|
|
{
|
2022-12-15 19:26:49 +02:00
|
|
|
Asset asset(assetPath);
|
|
|
|
if (asset.hasSuffix()) {
|
|
|
|
if (asset.isImage()) {
|
2022-02-03 19:49:27 +02:00
|
|
|
// Data: Image format (suffix)
|
2022-12-15 19:26:49 +02:00
|
|
|
return {Constants::MIME_TYPE_ASSET_IMAGE, asset.suffix().toUtf8()};
|
|
|
|
} else if (asset.isFont()) {
|
2022-02-03 19:49:27 +02:00
|
|
|
// Data: Font family name
|
|
|
|
QRawFont font(assetPath, 10);
|
|
|
|
QString fontFamily = font.isValid() ? font.familyName() : "";
|
2022-05-10 20:31:05 +03:00
|
|
|
return {Constants::MIME_TYPE_ASSET_FONT, fontFamily.toUtf8()};
|
2022-12-15 19:26:49 +02:00
|
|
|
} else if (asset.isShader()) {
|
2022-02-03 19:49:27 +02:00
|
|
|
// Data: shader type, frament (f) or vertex (v)
|
2022-05-10 20:31:05 +03:00
|
|
|
return {Constants::MIME_TYPE_ASSET_SHADER,
|
2022-12-15 19:26:49 +02:00
|
|
|
asset.isFragmentShader() ? "f" : "v"};
|
|
|
|
} else if (asset.isAudio()) {
|
2022-02-03 19:49:27 +02:00
|
|
|
// No extra data for sounds
|
2022-05-10 20:31:05 +03:00
|
|
|
return {Constants::MIME_TYPE_ASSET_SOUND, {}};
|
2022-12-15 19:26:49 +02:00
|
|
|
} else if (asset.isVideo()) {
|
2022-02-03 19:49:27 +02:00
|
|
|
// No extra data for videos
|
2022-05-10 20:31:05 +03:00
|
|
|
return {Constants::MIME_TYPE_ASSET_VIDEO, {}};
|
2022-12-15 19:26:49 +02:00
|
|
|
} else if (asset.isTexture3D()) {
|
2022-02-03 19:49:27 +02:00
|
|
|
// Data: Image format (suffix)
|
2022-12-15 19:26:49 +02:00
|
|
|
return {Constants::MIME_TYPE_ASSET_TEXTURE3D, asset.suffix().toUtf8()};
|
|
|
|
} else if (asset.isEffect()) {
|
2024-01-26 11:38:16 +02:00
|
|
|
// Data: Effect Composer format (suffix)
|
2022-12-15 19:26:49 +02:00
|
|
|
return {Constants::MIME_TYPE_ASSET_EFFECT, asset.suffix().toUtf8()};
|
2022-02-03 19:49:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
static QHash<QByteArray, QStringList> allImageFormats()
|
|
|
|
{
|
|
|
|
const QList<QByteArray> mimeTypes = QImageReader::supportedMimeTypes();
|
|
|
|
auto transformer = [](const QByteArray& format) -> QString { return QString("*.") + format; };
|
|
|
|
QHash<QByteArray, QStringList> imageFormats;
|
|
|
|
for (const auto &mimeType : mimeTypes)
|
|
|
|
imageFormats.insert(mimeType, Utils::transform(QImageReader::imageFormatsForMimeType(mimeType), transformer));
|
|
|
|
imageFormats.insert("image/vnd.radiance", {"*.hdr"});
|
|
|
|
imageFormats.insert("image/ktx", {"*.ktx"});
|
|
|
|
|
|
|
|
return imageFormats;
|
|
|
|
}
|
|
|
|
|
2023-03-03 14:46:13 +02:00
|
|
|
void AssetsLibraryWidget::addResources(const QStringList &files, bool showDialog)
|
2022-02-03 19:49:27 +02:00
|
|
|
{
|
2022-02-22 21:47:29 +02:00
|
|
|
clearSearchFilter();
|
|
|
|
|
2022-02-03 19:49:27 +02:00
|
|
|
DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument();
|
|
|
|
|
|
|
|
QTC_ASSERT(document, return);
|
|
|
|
|
|
|
|
const QList<AddResourceHandler> handlers = QmlDesignerPlugin::instance()->viewManager()
|
|
|
|
.designerActionManager().addResourceHandler();
|
|
|
|
|
|
|
|
QStringList fileNames = files;
|
|
|
|
if (fileNames.isEmpty()) { // if no files, show the "add assets" dialog
|
|
|
|
QMultiMap<QString, QString> map;
|
|
|
|
QHash<QString, int> priorities;
|
|
|
|
for (const AddResourceHandler &handler : handlers) {
|
|
|
|
map.insert(handler.category, handler.filter);
|
|
|
|
priorities.insert(handler.category, handler.piority);
|
|
|
|
}
|
|
|
|
|
|
|
|
QStringList sortedKeys = map.uniqueKeys();
|
|
|
|
Utils::sort(sortedKeys, [&priorities](const QString &first, const QString &second) {
|
|
|
|
return priorities.value(first) < priorities.value(second);
|
|
|
|
});
|
|
|
|
|
|
|
|
QStringList filters { tr("All Files (%1)").arg("*.*") };
|
|
|
|
QString filterTemplate = "%1 (%2)";
|
2022-10-07 14:46:06 +02:00
|
|
|
for (const QString &key : std::as_const(sortedKeys)) {
|
2022-02-03 19:49:27 +02:00
|
|
|
const QStringList values = map.values(key);
|
|
|
|
if (values.contains("*.png")) { // Avoid long filter for images by splitting
|
|
|
|
const QHash<QByteArray, QStringList> imageFormats = allImageFormats();
|
|
|
|
QHash<QByteArray, QStringList>::const_iterator i = imageFormats.constBegin();
|
|
|
|
while (i != imageFormats.constEnd()) {
|
|
|
|
filters.append(filterTemplate.arg(key + QString::fromLatin1(i.key()), i.value().join(' ')));
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
filters.append(filterTemplate.arg(key, values.join(' ')));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static QString lastDir;
|
|
|
|
const QString currentDir = lastDir.isEmpty() ? document->fileName().parentDir().toString() : lastDir;
|
|
|
|
|
|
|
|
fileNames = QFileDialog::getOpenFileNames(Core::ICore::dialogParent(),
|
|
|
|
tr("Add Assets"),
|
|
|
|
currentDir,
|
|
|
|
filters.join(";;"));
|
|
|
|
|
|
|
|
if (!fileNames.isEmpty())
|
|
|
|
lastDir = QFileInfo(fileNames.first()).absolutePath();
|
|
|
|
}
|
|
|
|
|
|
|
|
QHash<QString, QString> filterToCategory;
|
|
|
|
QHash<QString, AddResourceOperation> categoryToOperation;
|
|
|
|
for (const AddResourceHandler &handler : handlers) {
|
|
|
|
filterToCategory.insert(handler.filter, handler.category);
|
|
|
|
categoryToOperation.insert(handler.category, handler.operation);
|
|
|
|
}
|
|
|
|
|
|
|
|
QMultiMap<QString, QString> categoryFileNames; // filenames grouped by category
|
|
|
|
|
2022-10-07 14:46:06 +02:00
|
|
|
for (const QString &fileName : std::as_const(fileNames)) {
|
2022-02-03 19:49:27 +02:00
|
|
|
const QString suffix = "*." + QFileInfo(fileName).suffix().toLower();
|
|
|
|
const QString category = filterToCategory.value(suffix);
|
|
|
|
categoryFileNames.insert(category, fileName);
|
|
|
|
}
|
|
|
|
|
2024-07-01 16:15:19 +03:00
|
|
|
QStringList unsupportedFiles;
|
|
|
|
QStringList failedOpsFiles;
|
|
|
|
|
2022-02-03 19:49:27 +02:00
|
|
|
for (const QString &category : categoryFileNames.uniqueKeys()) {
|
|
|
|
QStringList fileNames = categoryFileNames.values(category);
|
|
|
|
AddResourceOperation operation = categoryToOperation.value(category);
|
|
|
|
QmlDesignerPlugin::emitUsageStatistics(Constants::EVENT_RESOURCE_IMPORTED + category);
|
|
|
|
if (operation) {
|
|
|
|
AddFilesResult result = operation(fileNames,
|
2023-03-03 14:46:13 +02:00
|
|
|
document->fileName().parentDir().toString(), showDialog);
|
2022-11-23 11:49:45 +02:00
|
|
|
if (result.status() == AddFilesResult::Failed) {
|
2024-07-01 16:15:19 +03:00
|
|
|
failedOpsFiles.append(fileNames);
|
2022-11-23 11:49:45 +02:00
|
|
|
} else {
|
|
|
|
if (!result.directory().isEmpty()) {
|
|
|
|
emit directoryCreated(result.directory());
|
|
|
|
} else if (result.haveDelayedResult()) {
|
|
|
|
QObject *delayedResult = result.delayedResult();
|
|
|
|
QObject::connect(delayedResult, &QObject::destroyed, this, [this, delayedResult]() {
|
|
|
|
QVariant propValue = delayedResult->property(AddFilesResult::directoryPropName);
|
|
|
|
QString directory = propValue.toString();
|
|
|
|
if (!directory.isEmpty())
|
|
|
|
emit directoryCreated(directory);
|
|
|
|
});
|
|
|
|
}
|
2022-02-03 19:49:27 +02:00
|
|
|
}
|
|
|
|
} else {
|
2024-07-01 16:15:19 +03:00
|
|
|
unsupportedFiles.append(fileNames);
|
2022-02-03 19:49:27 +02:00
|
|
|
}
|
|
|
|
}
|
2024-07-01 16:15:19 +03:00
|
|
|
|
|
|
|
if (!failedOpsFiles.isEmpty()) {
|
|
|
|
QWidget *w = Core::AsynchronousMessageBox::warning(tr("Failed to Add Files"),
|
|
|
|
tr("Could not add %1 to project.")
|
|
|
|
.arg(failedOpsFiles.join(' ')));
|
|
|
|
// Avoid multiple modal dialogs open at the same time
|
|
|
|
auto mb = qobject_cast<QMessageBox *>(w);
|
|
|
|
if (mb && !unsupportedFiles.isEmpty())
|
|
|
|
mb->exec();
|
|
|
|
}
|
|
|
|
if (!unsupportedFiles.isEmpty()) {
|
|
|
|
Core::AsynchronousMessageBox::warning(tr("Failed to Add Files"),
|
|
|
|
tr("Could not add %1 to project. Unsupported file format.")
|
|
|
|
.arg(unsupportedFiles.join(' ')));
|
|
|
|
}
|
2022-02-03 19:49:27 +02:00
|
|
|
}
|
|
|
|
|
2024-04-20 00:25:43 +03:00
|
|
|
void AssetsLibraryWidget::addAssetsToContentLibrary(const QStringList &assetPaths)
|
|
|
|
{
|
|
|
|
m_assetsView->emitCustomNotification("add_assets_to_content_lib", {}, {assetPaths});
|
|
|
|
}
|
|
|
|
|
2022-02-03 19:49:27 +02:00
|
|
|
} // namespace QmlDesigner
|