diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml index 6dedaf79012..d3b28d77bbe 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml @@ -183,4 +183,16 @@ StudioControls.Menu { } } } + + StudioControls.MenuItem { + text: qsTr("New Effect") + + NewEffectDialog { + id: newEffectDialog + parent: root.assetsView + dirPath: root.__dirPath + } + + onTriggered: newEffectDialog.open() + } } diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/NewEffectDialog.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/NewEffectDialog.qml new file mode 100644 index 00000000000..f57284e6782 --- /dev/null +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/NewEffectDialog.qml @@ -0,0 +1,105 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Controls +import HelperWidgets as HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme + +Dialog { + id: root + + title: qsTr("Create New Effect") + anchors.centerIn: parent + closePolicy: Popup.CloseOnEscape + modal: true + + required property string dirPath + readonly property int __maxPath: 32 + + HelperWidgets.RegExpValidator { + id: effectNameValidator + regExp: /^[A-Z]\w[A-Za-z0-9_]*$/ + } + + ErrorDialog { + id: creationFailedDialog + title: qsTr("Could not create effect") + message: qsTr("An error occurred while trying to create the effect.") + } + + contentItem: Column { + spacing: 2 + + Row { + Text { + text: qsTr("Effect name: ") + anchors.verticalCenter: parent.verticalCenter + color: StudioTheme.Values.themeTextColor + } + + StudioControls.TextField { + id: effectName + + actionIndicator.visible: false + translationIndicator.visible: false + validator: effectNameValidator + + Keys.onEnterPressed: btnCreate.onClicked() + Keys.onReturnPressed: btnCreate.onClicked() + } + } + + Text { + text: qsTr("Effect name cannot be empty.") + color: "#ff0000" + anchors.right: parent.right + visible: effectName.text === "" + } + + Text { + text: qsTr("Effect path is too long.") + color: "#ff0000" + anchors.right: parent.right + visible: effectName.text.length > root.__maxPath + } + + Item { // spacer + width: 1 + height: 20 + } + + Row { + anchors.right: parent.right + + HelperWidgets.Button { + id: btnCreate + + text: qsTr("Create") + enabled: effectName.text !== "" + && effectName.length >=3 + && effectName.text.length <= root.__maxPath + onClicked: { + const path = assetsModel.getUniqueEffectPath(root.dirPath, effectName.text) + if (assetsModel.createNewEffect(path)) + root.accept() + else + creationFailedDialog.open() + } + } + + HelperWidgets.Button { + text: qsTr("Cancel") + onClicked: root.reject() + } + } + } + + onOpened: { + const path = assetsModel.getUniqueEffectPath(root.dirPath, "Effect01") + effectName.text = path.split('/').pop().replace(".qep", '') + effectName.selectAll() + effectName.forceActiveFocus() + } +} diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp index 6587c89cf32..2d51bc4734a 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp @@ -134,32 +134,10 @@ bool AssetsLibraryModel::renameFolder(const QString &folderPath, const QString & bool AssetsLibraryModel::addNewFolder(const QString &folderPath) { QString iterPath = folderPath; - static QRegularExpression rgx("\\d+$"); // matches a number at the end of a string QDir dir{folderPath}; while (dir.exists()) { - // if the folder name ends with a number, increment it - QRegularExpressionMatch match = rgx.match(iterPath); - if (match.hasMatch()) { // ends with a number - QString numStr = match.captured(0); - int num = match.captured(0).toInt(); - - // get number of padding zeros, ex: for "005" = 2 - int nPaddingZeros = 0; - for (; nPaddingZeros < numStr.size() && numStr[nPaddingZeros] == '0'; ++nPaddingZeros); - - ++num; - - // if the incremented number's digits increased, decrease the padding zeros - if (std::fmod(std::log10(num), 1.0) == 0) - --nPaddingZeros; - - iterPath = folderPath.mid(0, match.capturedStart()) - + QString('0').repeated(nPaddingZeros) - + QString::number(num); - } else { - iterPath = folderPath + '1'; - } + iterPath = getUniqueName(iterPath); dir.setPath(iterPath); } @@ -186,6 +164,36 @@ bool AssetsLibraryModel::allFilePathsAreImages(const QStringList &filePaths) con }); } +QString AssetsLibraryModel::getUniqueEffectPath(const QString &parentFolder, const QString &effectName) +{ + auto genEffectPath = [=](const QString &name) { + return QString(parentFolder + "/" + name + ".qep"); + }; + + QString uniqueName = effectName; + QString path = genEffectPath(uniqueName); + QFileInfo file{path}; + + while (file.exists()) { + uniqueName = getUniqueName(uniqueName); + + path = genEffectPath(uniqueName); + file.setFile(path); + } + + return path; +} + +bool AssetsLibraryModel::createNewEffect(const QString &effectPath, bool openEffectMaker) +{ + bool created = QFile(effectPath).open(QIODevice::WriteOnly); + + if (created && openEffectMaker) + ModelNodeOperations::openEffectMaker(effectPath); + + return created; +} + bool AssetsLibraryModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { QString path = m_sourceFsModel->filePath(sourceParent); @@ -242,6 +250,36 @@ void AssetsLibraryModel::syncHaveFiles() setHaveFiles(checkHaveFiles()); } +QString AssetsLibraryModel::getUniqueName(const QString &oldName) { + static QRegularExpression rgx("\\d+$"); // matches a number at the end of a string + + QString uniqueName = oldName; + // if the folder name ends with a number, increment it + QRegularExpressionMatch match = rgx.match(uniqueName); + if (match.hasMatch()) { // ends with a number + QString numStr = match.captured(0); + int num = match.captured(0).toInt(); + + // get number of padding zeros, ex: for "005" = 2 + int nPaddingZeros = 0; + for (; nPaddingZeros < numStr.size() && numStr[nPaddingZeros] == '0'; ++nPaddingZeros); + + ++num; + + // if the incremented number's digits increased, decrease the padding zeros + if (std::fmod(std::log10(num), 1.0) == 0) + --nPaddingZeros; + + uniqueName = oldName.mid(0, match.capturedStart()) + + QString('0').repeated(nPaddingZeros) + + QString::number(num); + } else { + uniqueName = oldName + '1'; + } + + return uniqueName; +} + void AssetsLibraryModel::setRootPath(const QString &newPath) { beginResetModel(); diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h index 99c96017084..6567ca8338b 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h @@ -47,6 +47,9 @@ public: Q_INVOKABLE bool deleteFolderRecursively(const QModelIndex &folderIndex); Q_INVOKABLE bool allFilePathsAreImages(const QStringList &filePaths) const; + Q_INVOKABLE QString getUniqueEffectPath(const QString &parentFolder, const QString &effectName); + Q_INVOKABLE bool createNewEffect(const QString &effectPath, bool openEffectMaker = true); + int columnCount(const QModelIndex &parent = QModelIndex()) const override { int result = QSortFilterProxyModel::columnCount(parent); @@ -79,6 +82,7 @@ private: void destroyBackendModel(); bool checkHaveFiles(const QModelIndex &parentIdx) const; bool checkHaveFiles() const; + QString getUniqueName(const QString &oldName); QString m_searchText; QString m_rootPath;