diff options
author | Mahmoud Badri <mahmoud.badri@qt.io> | 2022-01-28 16:12:23 +0200 |
---|---|---|
committer | Mahmoud Badri <mahmoud.badri@qt.io> | 2022-01-31 14:57:22 +0000 |
commit | 25fa5540ea8601cdf5fe1e841d940aff8483ad3f (patch) | |
tree | 442b4ea667bcf2be93ab3ef3876fdade6eda76cd | |
parent | 01f33fe4f1b83797845cbacacf2762fc4b63ce71 (diff) | |
download | qt-creator-25fa5540ea8601cdf5fe1e841d940aff8483ad3f.tar.gz |
QmlDesigner: Implement adding and removing asset folders
Also corrected assets view margins and few relevants tweaks.
Task-number: QDS-5795
Change-Id: Ieeb68584bcb261422f48ec1a865f510a00c251f5
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
7 files changed, 288 insertions, 19 deletions
diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml index 8444a2c88f..ab403d203b 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml @@ -36,7 +36,9 @@ Item { property var selectedAssets: ({}) property int allExpandedState: 0 - property string delFilePath: "" + property string contextFilePath: "" + property var contextDir: undefined + property bool isDirContextMenu: false DropArea { id: dropArea @@ -67,6 +69,19 @@ Item { } } + MouseArea { // right clicking the empty area of the view + anchors.fill: parent + acceptedButtons: Qt.RightButton + onClicked: { + if (!assetsModel.isEmpty) { + contextFilePath = "" + contextDir = assetsModel.rootDir() + isDirContextMenu = false + contextMenu.popup() + } + } + } + // called from C++ to close context menu on focus out function handleViewFocusOut() { @@ -75,9 +90,139 @@ Item { selectedAssetsChanged() } + Dialog { + id: newFolderDialog + + title: qsTr("Create new folder") + anchors.centerIn: parent + closePolicy: Popup.CloseOnEscape + modal: true + + contentItem: Column { + spacing: 2 + + Row { + Text { + text: qsTr("Folder Name: ") + anchors.verticalCenter: parent.verticalCenter + color: StudioTheme.Values.themeTextColor + } + + StudioControls.TextField { + id: folderName + + actionIndicator.visible: false + translationIndicator.visible: false + + Keys.onEnterPressed: btnCreate.onClicked() + Keys.onReturnPressed: btnCreate.onClicked() + } + } + + Text { + text: qsTr("Folder Name cannot be empty.") + color: "#ff0000" + anchors.right: parent.right + visible: folderName.text === "" + } + + Item { // spacer + width: 1 + height: 20 + } + + Row { + anchors.right: parent.right + + Button { + id: btnCreate + + text: qsTr("Create") + enabled: folderName.text !== "" + onClicked: { + assetsModel.addNewFolder(contextDir.dirPath + '/' + folderName.text) + newFolderDialog.accept() + } + } + + Button { + text: qsTr("Cancel") + onClicked: newFolderDialog.reject() + } + } + } + + onOpened: { + folderName.text = "New folder" + folderName.selectAll() + folderName.forceActiveFocus() + } + } + + Dialog { + id: confirmDeleteFolderDialog + + title: qsTr("Folder not empty") + anchors.centerIn: parent + closePolicy: Popup.CloseOnEscape + implicitWidth: 300 + modal: true + + contentItem: Column { + spacing: 20 + width: parent.width + + Text { + id: folderNotEmpty + + text: qsTr("Folder '%1' is not empty. Are you sure you want to delete it?") + .arg(contextDir ? contextDir.dirName : "") + color: StudioTheme.Values.themeTextColor + wrapMode: Text.WordWrap + width: confirmDeleteFolderDialog.width + leftPadding: 10 + rightPadding: 10 + + Keys.onEnterPressed: btnDelete.onClicked() + Keys.onReturnPressed: btnDelete.onClicked() + } + + Text { + text: qsTr("If the folder has assets in use, deleting it might cause the project to not work correctly.") + color: StudioTheme.Values.themeTextColor + wrapMode: Text.WordWrap + width: confirmDeleteFolderDialog.width + leftPadding: 10 + rightPadding: 10 + } + + Row { + anchors.right: parent.right + Button { + id: btnDelete + + text: qsTr("Delete") + + onClicked: { + assetsModel.deleteFolder(contextDir.dirPath) + confirmDeleteFolderDialog.accept() + } + } + + Button { + text: qsTr("Cancel") + onClicked: confirmDeleteFolderDialog.reject() + } + } + } + + onOpened: folderNotEmpty.forceActiveFocus() + } + ScrollView { // TODO: experiment using ListView instead of ScrollView + Column id: assetsView anchors.fill: parent + interactive: assetsView.verticalScrollBarVisible Item { StudioControls.Menu { @@ -86,7 +231,7 @@ Item { StudioControls.MenuItem { text: qsTr("Expand All") enabled: allExpandedState !== 1 - visible: !delFilePath + visible: isDirContextMenu height: visible ? implicitHeight : 0 onTriggered: assetsModel.toggleExpandAll(true) } @@ -94,22 +239,51 @@ Item { StudioControls.MenuItem { text: qsTr("Collapse All") enabled: allExpandedState !== 2 - visible: !delFilePath + visible: isDirContextMenu height: visible ? implicitHeight : 0 onTriggered: assetsModel.toggleExpandAll(false) } + StudioControls.MenuSeparator { + visible: isDirContextMenu + height: visible ? StudioTheme.Values.border : 0 + } + StudioControls.MenuItem { text: qsTr("Delete File") - visible: delFilePath + visible: contextFilePath + height: visible ? implicitHeight : 0 + onTriggered: assetsModel.deleteFile(contextFilePath) + } + + StudioControls.MenuSeparator { + visible: contextFilePath + height: visible ? StudioTheme.Values.border : 0 + } + + StudioControls.MenuItem { + text: qsTr("New Folder") + onTriggered: newFolderDialog.open() + } + + StudioControls.MenuItem { + text: qsTr("Delete Folder") + visible: isDirContextMenu height: visible ? implicitHeight : 0 - onTriggered: assetsModel.removeFile(delFilePath) + onTriggered: { + var dirEmpty = !(contextDir.dirsModel && contextDir.dirsModel.rowCount() > 0) + && !(contextDir.filesModel && contextDir.filesModel.rowCount() > 0); + + if (dirEmpty) + assetsModel.deleteFolder(contextDir.dirPath) + else + confirmDeleteFolderDialog.open() + } } } } Column { - spacing: 2 Repeater { model: assetsModel // context property delegate: dirSection @@ -120,31 +294,35 @@ Item { Section { width: assetsView.width - - (assetsView.verticalScrollBarVisible ? assetsView.verticalThickness : 0) + (assetsView.verticalScrollBarVisible ? assetsView.verticalThickness : 0) - 5 caption: dirName sectionHeight: 30 sectionFontSize: 15 - levelShift: 20 leftPadding: 0 + topPadding: dirDepth > 0 ? 5 : 0 + bottomPadding: 0 hideHeader: dirDepth === 0 - showLeftBorder: true + showLeftBorder: dirDepth > 0 expanded: dirExpanded - visible: dirVisible + visible: !assetsModel.isEmpty && dirVisible expandOnClick: false useDefaulContextMenu: false onToggleExpand: { dirExpanded = !dirExpanded } + onShowContextMenu: { - delFilePath = "" + contextFilePath = "" + contextDir = model + isDirContextMenu = true allExpandedState = assetsModel.getAllExpandedState() contextMenu.popup() } Column { spacing: 5 - leftPadding: 15 + leftPadding: 5 Repeater { model: dirsModel @@ -155,6 +333,25 @@ Item { model: filesModel delegate: fileSection } + + Text { + text: qsTr("Empty folder") + color: StudioTheme.Values.themeTextColorDisabled + font.pixelSize: 12 + visible: !(dirsModel && dirsModel.rowCount() > 0) + && !(filesModel && filesModel.rowCount() > 0) + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + onClicked: { + contextFilePath = "" + contextDir = model + isDirContextMenu = true + contextMenu.popup() + } + } + } } } } @@ -222,8 +419,11 @@ Item { if (currFileSelected) rootView.startDragAsset(selectedAssetsArr, mapToGlobal(mouse.x, mouse.y)) } else { - delFilePath = filePath + contextFilePath = filePath + contextDir = model.fileDir + tooltipBackend.hideTooltip() + isDirContextMenu = false contextMenu.popup() } } @@ -263,7 +463,7 @@ Item { // Placeholder when the assets panel is empty Column { id: colNoAssets - visible: assetsModel.isEmpty + visible: assetsModel.isEmpty && !rootView.searchActive spacing: 20 x: 20 @@ -307,4 +507,13 @@ Item { wrapMode: Text.WordWrap } } + + Text { + text: qsTr("No match found.") + x: 20 + y: 10 + color: StudioTheme.Values.themeTextColor + font.pixelSize: 12 + visible: assetsModel.isEmpty && rootView.searchActive + } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/Section.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/Section.qml index 66f05462c3..3f8a331189 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/Section.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/Section.qml @@ -173,7 +173,7 @@ Item { id: leftBorder visible: false width: 1 - height: parent.height - 15 + height: parent.height - bottomPadding color: header.color } diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsfilesmodel.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsfilesmodel.cpp index 1140ba43d7..271f330fd2 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsfilesmodel.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsfilesmodel.cpp @@ -34,6 +34,7 @@ ItemLibraryAssetsFilesModel::ItemLibraryAssetsFilesModel(QObject *parent) // add roles m_roleNames.insert(FileNameRole, "fileName"); m_roleNames.insert(FilePathRole, "filePath"); + m_roleNames.insert(FileDirRole, "fileDir"); } QVariant ItemLibraryAssetsFilesModel::data(const QModelIndex &index, int role) const @@ -49,6 +50,9 @@ QVariant ItemLibraryAssetsFilesModel::data(const QModelIndex &index, int role) c if (role == FilePathRole) return m_files[index.row()]; + if (role == FileDirRole) + return QVariant::fromValue(parent()); + qWarning() << Q_FUNC_INFO << "Invalid role requested: " << QString::number(role); return {}; } diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsfilesmodel.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsfilesmodel.h index 25577dce51..5b44878eef 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsfilesmodel.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsfilesmodel.h @@ -44,7 +44,8 @@ public: private: enum Roles {FileNameRole = Qt::UserRole + 1, - FilePathRole}; + FilePathRole, + FileDirRole}; QStringList m_files; QHash<int, QByteArray> m_roleNames; diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.cpp index a09391570d..be9b3b9f25 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.cpp @@ -99,7 +99,7 @@ void ItemLibraryAssetsModel::toggleExpandAll(bool expand) endResetModel(); } -void ItemLibraryAssetsModel::removeFile(const QString &filePath) +void ItemLibraryAssetsModel::deleteFile(const QString &filePath) { bool askBeforeDelete = DesignerSettings::getValue( DesignerSettingsKey::ASK_BEFORE_DELETING_ASSET).toBool(); @@ -134,6 +134,52 @@ void ItemLibraryAssetsModel::removeFile(const QString &filePath) } } +void ItemLibraryAssetsModel::addNewFolder(const QString &folderPath) +{ + QString iterPath = folderPath; + 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'; + } + + dir.setPath(iterPath); + } + + dir.mkpath(iterPath); +} + +void ItemLibraryAssetsModel::deleteFolder(const QString &folderPath) +{ + QDir{folderPath}.removeRecursively(); +} + +QObject *ItemLibraryAssetsModel::rootDir() const +{ + return m_assetsDir; +} + const QStringList &ItemLibraryAssetsModel::supportedImageSuffixes() { static QStringList retList; @@ -270,7 +316,7 @@ void ItemLibraryAssetsModel::setRootPath(const QString &path) isEmpty &= parseDirRecursive(assetsDir, currDepth + 1); } - if (isEmpty) + if (!m_searchText.isEmpty() && isEmpty) currAssetsDir->setDirVisible(false); return isEmpty; diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.h index f762156349..e429605b9a 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.h @@ -83,7 +83,10 @@ public: Q_INVOKABLE void toggleExpandAll(bool expand); Q_INVOKABLE DirExpandState getAllExpandedState() const; - Q_INVOKABLE void removeFile(const QString &filePath); + Q_INVOKABLE void deleteFile(const QString &filePath); + Q_INVOKABLE void addNewFolder(const QString &folderPath); + Q_INVOKABLE void deleteFolder(const QString &folderPath); + Q_INVOKABLE QObject *rootDir() const; signals: void isEmptyChanged(); diff --git a/src/plugins/qmldesigner/documentmanager.cpp b/src/plugins/qmldesigner/documentmanager.cpp index 84e1416eb0..d510193d0a 100644 --- a/src/plugins/qmldesigner/documentmanager.cpp +++ b/src/plugins/qmldesigner/documentmanager.cpp @@ -504,8 +504,14 @@ bool DocumentManager::belongsToQmakeProject() Utils::FilePath DocumentManager::currentResourcePath() { Utils::FilePath resourcePath = currentProjectDirPath(); + if (resourcePath.isEmpty()) return currentFilePath().absolutePath(); + + FilePath contentFilePath = resourcePath.pathAppended("content"); + if (contentFilePath.exists()) + return contentFilePath; + return resourcePath; } |