diff options
author | Samuel Ghinet <samuel.ghinet@qt.io> | 2022-10-07 19:49:52 +0300 |
---|---|---|
committer | Samuel Ghinet <samuel.ghinet@qt.io> | 2022-10-10 12:00:23 +0000 |
commit | 7319eac2835b21d9a8c3be72016071de07d608d2 (patch) | |
tree | e0460993a620166f7f87730c87e945f811a3ce9f | |
parent | fcc7edc135012ed5853e7afc7c56c811d95d2464 (diff) |
Refactor Assets Library
Extracted the dialogs, the context menu, and the assets view into
separate qml files. Also, reordered some functions in the assets
library model
Task-number: QDS-7344
Change-Id: Ida21b60d30f34723c07b2659a138e14b95598421
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
7 files changed, 763 insertions, 581 deletions
diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml index 6578f96b7d..1a91e2f492 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml @@ -46,6 +46,10 @@ Item { // Array of supported externally dropped files that trigger custom import process property var dropComplexExtFiles: [] + AssetsContextMenu { + id: contextMenu + } + function clearSearchFilter() { searchBox.clear(); @@ -125,307 +129,11 @@ Item { root.selectedAssetsChanged() } - StudioControls.Menu { - id: contextMenu - - closePolicy: Popup.CloseOnPressOutside | Popup.CloseOnEscape - - onOpened: { - var numSelected = Object.values(root.selectedAssets).filter(p => p).length - deleteFileItem.text = numSelected > 1 ? qsTr("Delete Files") : qsTr("Delete File") - } - - StudioControls.MenuItem { - text: qsTr("Expand All") - enabled: root.allExpandedState !== 1 - visible: root.isDirContextMenu - height: visible ? implicitHeight : 0 - onTriggered: assetsModel.toggleExpandAll(true) - } - - StudioControls.MenuItem { - text: qsTr("Collapse All") - enabled: root.allExpandedState !== 2 - visible: root.isDirContextMenu - height: visible ? implicitHeight : 0 - onTriggered: assetsModel.toggleExpandAll(false) - } - - StudioControls.MenuSeparator { - visible: root.isDirContextMenu - height: visible ? StudioTheme.Values.border : 0 - } - - StudioControls.MenuItem { - id: deleteFileItem - text: qsTr("Delete File") - visible: root.contextFilePath - height: deleteFileItem.visible ? deleteFileItem.implicitHeight : 0 - onTriggered: { - assetsModel.deleteFiles(Object.keys(root.selectedAssets).filter(p => root.selectedAssets[p])) - } - } - - StudioControls.MenuSeparator { - visible: root.contextFilePath - height: visible ? StudioTheme.Values.border : 0 - } - - StudioControls.MenuItem { - text: qsTr("Rename Folder") - visible: root.isDirContextMenu - height: visible ? implicitHeight : 0 - onTriggered: renameFolderDialog.open() - } - - StudioControls.MenuItem { - text: qsTr("New Folder") - onTriggered: newFolderDialog.open() - } - - StudioControls.MenuItem { - text: qsTr("Delete Folder") - visible: root.isDirContextMenu - height: visible ? implicitHeight : 0 - onTriggered: { - var dirEmpty = !(root.contextDir.dirsModel && root.contextDir.dirsModel.rowCount() > 0) - && !(root.contextDir.filesModel && root.contextDir.filesModel.rowCount() > 0); - - if (dirEmpty) - assetsModel.deleteFolder(root.contextDir.dirPath) - else - confirmDeleteFolderDialog.open() - } - } - } - RegExpValidator { id: folderNameValidator regExp: /^(\w[^*/><?\\|:]*)$/ } - Dialog { - id: renameFolderDialog - - title: qsTr("Rename Folder") - anchors.centerIn: parent - closePolicy: Popup.CloseOnEscape - implicitWidth: 280 - modal: true - - property bool renameError: false - - contentItem: Column { - spacing: 2 - - StudioControls.TextField { - id: folderRename - - actionIndicator.visible: false - translationIndicator.visible: false - width: renameFolderDialog.width - 12 - validator: folderNameValidator - - onEditChanged: renameFolderDialog.renameError = false - Keys.onEnterPressed: btnRename.onClicked() - Keys.onReturnPressed: btnRename.onClicked() - } - - Text { - text: qsTr("Folder name cannot be empty.") - color: "#ff0000" - visible: folderRename.text === "" && !renameFolderDialog.renameError - } - - Text { - text: qsTr("Could not rename folder. Make sure no folder with the same name exists.") - wrapMode: Text.WordWrap - width: renameFolderDialog.width - 12 - color: "#ff0000" - visible: renameFolderDialog.renameError - } - - Item { // spacer - width: 1 - height: 10 - } - - Text { - text: qsTr("If the folder has assets in use, renaming it might cause the project to not work correctly.") - color: StudioTheme.Values.themeTextColor - wrapMode: Text.WordWrap - width: renameFolderDialog.width - leftPadding: 10 - rightPadding: 10 - } - - Item { // spacer - width: 1 - height: 20 - } - - Row { - anchors.right: parent.right - - Button { - id: btnRename - - text: qsTr("Rename") - enabled: folderRename.text !== "" - onClicked: { - var success = assetsModel.renameFolder(root.contextDir.dirPath, folderRename.text) - if (success) - renameFolderDialog.accept() - - renameFolderDialog.renameError = !success - } - } - - Button { - text: qsTr("Cancel") - onClicked: renameFolderDialog.reject() - } - } - } - - onOpened: { - folderRename.text = root.contextDir.dirName - folderRename.selectAll() - folderRename.forceActiveFocus() - renameFolderDialog.renameError = false - } - } - - 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 - validator: folderNameValidator - - 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(root.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. Delete it anyway?") - .arg(root.contextDir ? root.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(root.contextDir.dirPath) - confirmDeleteFolderDialog.accept() - } - } - - Button { - text: qsTr("Cancel") - onClicked: confirmDeleteFolderDialog.reject() - } - } - } - - onOpened: folderNotEmpty.forceActiveFocus() - } - Column { anchors.fill: parent anchors.topMargin: 5 @@ -529,220 +237,10 @@ Item { } } - ScrollView { // TODO: experiment using ListView instead of ScrollView + Column + AssetsView { id: assetsView width: parent.width height: parent.height - y - clip: true - interactive: assetsView.verticalScrollBarVisible && !contextMenu.opened - - Column { - Repeater { - model: assetsModel // context property - delegate: dirSection - } - - Component { - id: dirSection - - Section { - id: section - - width: assetsView.width - - (assetsView.verticalScrollBarVisible ? assetsView.verticalThickness : 0) - 5 - caption: dirName - sectionHeight: 30 - sectionFontSize: 15 - leftPadding: 0 - topPadding: dirDepth > 0 ? 5 : 0 - bottomPadding: 0 - hideHeader: dirDepth === 0 - showLeftBorder: dirDepth > 0 - expanded: dirExpanded - visible: dirVisible - expandOnClick: false - useDefaulContextMenu: false - dropEnabled: true - - onToggleExpand: { - dirExpanded = !dirExpanded - } - - onDropEnter: (drag)=> { - root.updateDropExtFiles(drag) - section.highlight = drag.accepted && root.dropSimpleExtFiles.length > 0 - } - - onDropExit: { - section.highlight = false - } - - onDrop: { - section.highlight = false - rootView.handleExtFilesDrop(root.dropSimpleExtFiles, - root.dropComplexExtFiles, - dirPath) - } - - onShowContextMenu: { - root.contextFilePath = "" - root.contextDir = model - root.isDirContextMenu = true - root.allExpandedState = assetsModel.getAllExpandedState() - contextMenu.popup() - } - - Column { - spacing: 5 - leftPadding: 5 - - Repeater { - model: dirsModel - delegate: dirSection - } - - Repeater { - 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: { - root.contextFilePath = "" - root.contextDir = model - root.isDirContextMenu = true - contextMenu.popup() - } - } - } - } - } - } - - Component { - id: fileSection - - Rectangle { - width: assetsView.width - - (assetsView.verticalScrollBarVisible ? assetsView.verticalThickness : 0) - height: img.height - color: root.selectedAssets[filePath] - ? StudioTheme.Values.themeInteraction - : (mouseArea.containsMouse ? StudioTheme.Values.themeSectionHeadBackground - : "transparent") - - Row { - spacing: 5 - - Image { - id: img - asynchronous: true - fillMode: Image.PreserveAspectFit - width: 48 - height: 48 - source: "image://qmldesigner_assets/" + filePath - } - - Text { - text: fileName - color: StudioTheme.Values.themeTextColor - font.pixelSize: 14 - anchors.verticalCenter: parent.verticalCenter - } - } - - readonly property string suffix: fileName.substr(-4) - readonly property bool isFont: suffix === ".ttf" || suffix === ".otf" - property bool currFileSelected: false - - MouseArea { - id: mouseArea - - property bool allowTooltip: true - - anchors.fill: parent - hoverEnabled: true - acceptedButtons: Qt.LeftButton | Qt.RightButton - - onExited: tooltipBackend.hideTooltip() - onEntered: allowTooltip = true - onCanceled: { - tooltipBackend.hideTooltip() - allowTooltip = true - } - onPositionChanged: tooltipBackend.reposition() - onPressed: (mouse)=> { - forceActiveFocus() - allowTooltip = false - tooltipBackend.hideTooltip() - var ctrlDown = mouse.modifiers & Qt.ControlModifier - if (mouse.button === Qt.LeftButton) { - if (!root.selectedAssets[filePath] && !ctrlDown) - root.selectedAssets = {} - currFileSelected = ctrlDown ? !root.selectedAssets[filePath] : true - root.selectedAssets[filePath] = currFileSelected - root.selectedAssetsChanged() - - if (currFileSelected) { - rootView.startDragAsset( - Object.keys(root.selectedAssets).filter(p => root.selectedAssets[p]), - mapToGlobal(mouse.x, mouse.y)) - } - } else { - if (!root.selectedAssets[filePath] && !ctrlDown) - root.selectedAssets = {} - currFileSelected = root.selectedAssets[filePath] || !ctrlDown - root.selectedAssets[filePath] = currFileSelected - root.selectedAssetsChanged() - - root.contextFilePath = filePath - root.contextDir = model.fileDir - root.isDirContextMenu = false - - contextMenu.popup() - } - } - - onReleased: (mouse)=> { - allowTooltip = true - if (mouse.button === Qt.LeftButton) { - if (!(mouse.modifiers & Qt.ControlModifier)) - root.selectedAssets = {} - root.selectedAssets[filePath] = currFileSelected - root.selectedAssetsChanged() - } - } - - ToolTip { - visible: !isFont && mouseArea.containsMouse && !contextMenu.visible - text: filePath - delay: 1000 - } - - Timer { - interval: 1000 - running: mouseArea.containsMouse && mouseArea.allowTooltip - onTriggered: { - if (suffix === ".ttf" || suffix === ".otf") { - tooltipBackend.name = fileName - tooltipBackend.path = filePath - tooltipBackend.showTooltip() - } - } - } - } - } - } - } } } } diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml new file mode 100644 index 0000000000..5caa139651 --- /dev/null +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuickDesignerTheme +import HelperWidgets as HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme + +StudioControls.Menu { + id: contextMenu + + closePolicy: Popup.CloseOnPressOutside | Popup.CloseOnEscape + + onOpened: { + var numSelected = Object.values(root.selectedAssets).filter(p => p).length + deleteFileItem.text = numSelected > 1 ? qsTr("Delete Files") : qsTr("Delete File") + } + + StudioControls.MenuItem { + text: qsTr("Expand All") + enabled: root.allExpandedState !== 1 + visible: root.isDirContextMenu + height: visible ? implicitHeight : 0 + onTriggered: assetsModel.toggleExpandAll(true) + } + + StudioControls.MenuItem { + text: qsTr("Collapse All") + enabled: root.allExpandedState !== 2 + visible: root.isDirContextMenu + height: visible ? implicitHeight : 0 + onTriggered: assetsModel.toggleExpandAll(false) + } + + StudioControls.MenuSeparator { + visible: root.isDirContextMenu + height: visible ? StudioTheme.Values.border : 0 + } + + StudioControls.MenuItem { + id: deleteFileItem + text: qsTr("Delete File") + visible: root.contextFilePath + height: deleteFileItem.visible ? deleteFileItem.implicitHeight : 0 + onTriggered: { + assetsModel.deleteFiles(Object.keys(root.selectedAssets).filter(p => root.selectedAssets[p])) + } + } + + StudioControls.MenuSeparator { + visible: root.contextFilePath + height: visible ? StudioTheme.Values.border : 0 + } + + StudioControls.MenuItem { + text: qsTr("Rename Folder") + visible: root.isDirContextMenu + height: visible ? implicitHeight : 0 + onTriggered: renameFolderDialog.open() + + RenameFolderDialog { + id: renameFolderDialog + } + } + + StudioControls.MenuItem { + text: qsTr("New Folder") + + NewFolderDialog { + id: newFolderDialog + } + + onTriggered: newFolderDialog.open() + } + + StudioControls.MenuItem { + text: qsTr("Delete Folder") + visible: root.isDirContextMenu + height: visible ? implicitHeight : 0 + + ConfirmDeleteFolderDialog { + id: confirmDeleteFolderDialog + } + + onTriggered: { + var dirEmpty = !(root.contextDir.dirsModel && root.contextDir.dirsModel.rowCount() > 0) + && !(root.contextDir.filesModel && root.contextDir.filesModel.rowCount() > 0); + + if (dirEmpty) + assetsModel.deleteFolder(root.contextDir.dirPath) + else + confirmDeleteFolderDialog.open() + } + } +} diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsView.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsView.qml new file mode 100644 index 0000000000..e1c017814d --- /dev/null +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsView.qml @@ -0,0 +1,246 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuickDesignerTheme +import HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme + +ScrollView { // TODO: experiment using ListView instead of ScrollView + Column + id: assetsView + clip: true + interactive: assetsView.verticalScrollBarVisible && !contextMenu.opened + + Column { + Repeater { + model: assetsModel // context property + delegate: dirSection + } + + Component { + id: dirSection + + Section { + id: section + + width: assetsView.width - + (assetsView.verticalScrollBarVisible ? assetsView.verticalThickness : 0) - 5 + caption: dirName + sectionHeight: 30 + sectionFontSize: 15 + leftPadding: 0 + topPadding: dirDepth > 0 ? 5 : 0 + bottomPadding: 0 + hideHeader: dirDepth === 0 + showLeftBorder: dirDepth > 0 + expanded: dirExpanded + visible: dirVisible + expandOnClick: false + useDefaulContextMenu: false + dropEnabled: true + + onToggleExpand: { + dirExpanded = !dirExpanded + } + + onDropEnter: (drag)=> { + root.updateDropExtFiles(drag) + section.highlight = drag.accepted && root.dropSimpleExtFiles.length > 0 + } + + onDropExit: { + section.highlight = false + } + + onDrop: { + section.highlight = false + rootView.handleExtFilesDrop(root.dropSimpleExtFiles, + root.dropComplexExtFiles, + dirPath) + } + + onShowContextMenu: { + root.contextFilePath = "" + root.contextDir = model + root.isDirContextMenu = true + root.allExpandedState = assetsModel.getAllExpandedState() + contextMenu.popup() + } + + Column { + spacing: 5 + leftPadding: 5 + + Repeater { + model: dirsModel + delegate: dirSection + } + + Repeater { + 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: { + root.contextFilePath = "" + root.contextDir = model + root.isDirContextMenu = true + contextMenu.popup() + } + } + } + } + } + } + + Component { + id: fileSection + + Rectangle { + width: assetsView.width - + (assetsView.verticalScrollBarVisible ? assetsView.verticalThickness : 0) + height: img.height + color: root.selectedAssets[filePath] + ? StudioTheme.Values.themeInteraction + : (mouseArea.containsMouse ? StudioTheme.Values.themeSectionHeadBackground + : "transparent") + + Row { + spacing: 5 + + Image { + id: img + asynchronous: true + fillMode: Image.PreserveAspectFit + width: 48 + height: 48 + source: "image://qmldesigner_assets/" + filePath + } + + Text { + text: fileName + color: StudioTheme.Values.themeTextColor + font.pixelSize: 14 + anchors.verticalCenter: parent.verticalCenter + } + } + + readonly property string suffix: fileName.substr(-4) + readonly property bool isFont: suffix === ".ttf" || suffix === ".otf" + property bool currFileSelected: false + + MouseArea { + id: mouseArea + + property bool allowTooltip: true + + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton + + onExited: tooltipBackend.hideTooltip() + onEntered: allowTooltip = true + onCanceled: { + tooltipBackend.hideTooltip() + allowTooltip = true + } + onPositionChanged: tooltipBackend.reposition() + onPressed: (mouse)=> { + forceActiveFocus() + allowTooltip = false + tooltipBackend.hideTooltip() + var ctrlDown = mouse.modifiers & Qt.ControlModifier + if (mouse.button === Qt.LeftButton) { + if (!root.selectedAssets[filePath] && !ctrlDown) + root.selectedAssets = {} + currFileSelected = ctrlDown ? !root.selectedAssets[filePath] : true + root.selectedAssets[filePath] = currFileSelected + root.selectedAssetsChanged() + + if (currFileSelected) { + rootView.startDragAsset( + Object.keys(root.selectedAssets).filter(p => root.selectedAssets[p]), + mapToGlobal(mouse.x, mouse.y)) + } + } else { + if (!root.selectedAssets[filePath] && !ctrlDown) + root.selectedAssets = {} + currFileSelected = root.selectedAssets[filePath] || !ctrlDown + root.selectedAssets[filePath] = currFileSelected + root.selectedAssetsChanged() + + root.contextFilePath = filePath + root.contextDir = model.fileDir + root.isDirContextMenu = false + + contextMenu.popup() + } + } + + onReleased: (mouse)=> { + allowTooltip = true + if (mouse.button === Qt.LeftButton) { + if (!(mouse.modifiers & Qt.ControlModifier)) + root.selectedAssets = {} + root.selectedAssets[filePath] = currFileSelected + root.selectedAssetsChanged() + } + } + + ToolTip { + visible: !isFont && mouseArea.containsMouse && !contextMenu.visible + text: filePath + delay: 1000 + } + + Timer { + interval: 1000 + running: mouseArea.containsMouse && mouseArea.allowTooltip + onTriggered: { + if (suffix === ".ttf" || suffix === ".otf") { + tooltipBackend.name = fileName + tooltipBackend.path = filePath + tooltipBackend.showTooltip() + } + } + } + } + } + } + } +} diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/ConfirmDeleteFolderDialog.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ConfirmDeleteFolderDialog.qml new file mode 100644 index 0000000000..a4fd300975 --- /dev/null +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ConfirmDeleteFolderDialog.qml @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuickDesignerTheme +import HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme + +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. Delete it anyway?") + .arg(root.contextDir ? root.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(root.contextDir.dirPath) + confirmDeleteFolderDialog.accept() + } + } + + Button { + text: qsTr("Cancel") + onClicked: confirmDeleteFolderDialog.reject() + } + } + } + + onOpened: folderNotEmpty.forceActiveFocus() +} diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/NewFolderDialog.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/NewFolderDialog.qml new file mode 100644 index 0000000000..130026ddce --- /dev/null +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/NewFolderDialog.qml @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuickDesignerTheme +import HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme + +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 + validator: folderNameValidator + + 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(root.contextDir.dirPath + '/' + folderName.text) + newFolderDialog.accept() + } + } + + Button { + text: qsTr("Cancel") + onClicked: newFolderDialog.reject() + } + } + } + + onOpened: { + folderName.text = qsTr("New folder") + folderName.selectAll() + folderName.forceActiveFocus() + } +} diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/RenameFolderDialog.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/RenameFolderDialog.qml new file mode 100644 index 0000000000..351c0a35fc --- /dev/null +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/RenameFolderDialog.qml @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuickDesignerTheme +import HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme + +Dialog { + id: renameFolderDialog + + title: qsTr("Rename Folder") + anchors.centerIn: parent + closePolicy: Popup.CloseOnEscape + implicitWidth: 280 + modal: true + + property bool renameError: false + + contentItem: Column { + spacing: 2 + + StudioControls.TextField { + id: folderRename + + actionIndicator.visible: false + translationIndicator.visible: false + width: renameFolderDialog.width - 12 + validator: folderNameValidator + + onEditChanged: renameFolderDialog.renameError = false + Keys.onEnterPressed: btnRename.onClicked() + Keys.onReturnPressed: btnRename.onClicked() + } + + Text { + text: qsTr("Folder name cannot be empty.") + color: "#ff0000" + visible: folderRename.text === "" && !renameFolderDialog.renameError + } + + Text { + text: qsTr("Could not rename folder. Make sure no folder with the same name exists.") + wrapMode: Text.WordWrap + width: renameFolderDialog.width - 12 + color: "#ff0000" + visible: renameFolderDialog.renameError + } + + Item { // spacer + width: 1 + height: 10 + } + + Text { + text: qsTr("If the folder has assets in use, renaming it might cause the project to not work correctly.") + color: StudioTheme.Values.themeTextColor + wrapMode: Text.WordWrap + width: renameFolderDialog.width + leftPadding: 10 + rightPadding: 10 + } + + Item { // spacer + width: 1 + height: 20 + } + + Row { + anchors.right: parent.right + + Button { + id: btnRename + + text: qsTr("Rename") + enabled: folderRename.text !== "" + onClicked: { + var success = assetsModel.renameFolder(root.contextDir.dirPath, folderRename.text) + if (success) + renameFolderDialog.accept() + + renameFolderDialog.renameError = !success + } + } + + Button { + text: qsTr("Cancel") + onClicked: renameFolderDialog.reject() + } + } + } + + onOpened: { + folderRename.text = root.contextDir.dirName + folderRename.selectAll() + folderRename.forceActiveFocus() + renameFolderDialog.renameError = false + } +} diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp index 18967a5fe1..c538b45272 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp @@ -57,6 +57,25 @@ static Q_LOGGING_CATEGORY(assetsLibraryBenchmark, "qtc.assetsLibrary.setRoot", Q namespace QmlDesigner { +AssetsLibraryModel::AssetsLibraryModel(Utils::FileSystemWatcher *fileSystemWatcher, QObject *parent) + : QAbstractListModel(parent) + , m_fileSystemWatcher(fileSystemWatcher) +{ + // add role names + int role = 0; + const QMetaObject meta = AssetsLibraryDir::staticMetaObject; + for (int i = meta.propertyOffset(); i < meta.propertyCount(); ++i) + m_roleNames.insert(role++, meta.property(i).name()); +} + +void AssetsLibraryModel::setSearchText(const QString &searchText) +{ + if (m_searchText != searchText) { + m_searchText = searchText; + refresh(); + } +} + void AssetsLibraryModel::saveExpandedState(bool expanded, const QString &assetPath) { m_expandedStateHash.insert(assetPath, expanded); @@ -208,66 +227,18 @@ QObject *AssetsLibraryModel::rootDir() const return m_assetsDir; } -const QStringList &AssetsLibraryModel::supportedImageSuffixes() -{ - static QStringList retList; - if (retList.isEmpty()) { - const QList<QByteArray> suffixes = QImageReader::supportedImageFormats(); - for (const QByteArray &suffix : suffixes) - retList.append("*." + QString::fromUtf8(suffix)); - } - return retList; -} - -const QStringList &AssetsLibraryModel::supportedFragmentShaderSuffixes() -{ - static const QStringList retList {"*.frag", "*.glsl", "*.glslf", "*.fsh"}; - return retList; -} - -const QStringList &AssetsLibraryModel::supportedShaderSuffixes() -{ - static const QStringList retList {"*.frag", "*.vert", - "*.glsl", "*.glslv", "*.glslf", - "*.vsh", "*.fsh"}; - return retList; -} - -const QStringList &AssetsLibraryModel::supportedFontSuffixes() -{ - static const QStringList retList {"*.ttf", "*.otf"}; - return retList; -} - -const QStringList &AssetsLibraryModel::supportedAudioSuffixes() -{ - static const QStringList retList {"*.wav", "*.mp3"}; - return retList; -} - -const QStringList &AssetsLibraryModel::supportedVideoSuffixes() -{ - static const QStringList retList {"*.mp4"}; - return retList; -} - -const QStringList &AssetsLibraryModel::supportedTexture3DSuffixes() +bool AssetsLibraryModel::isEmpty() const { - // These are file types only supported by 3D textures - static QStringList retList {"*.hdr", "*.ktx"}; - return retList; -} + return m_isEmpty; +}; -AssetsLibraryModel::AssetsLibraryModel(Utils::FileSystemWatcher *fileSystemWatcher, QObject *parent) - : QAbstractListModel(parent) - , m_fileSystemWatcher(fileSystemWatcher) +void AssetsLibraryModel::setIsEmpty(bool empty) { - // add role names - int role = 0; - const QMetaObject meta = AssetsLibraryDir::staticMetaObject; - for (int i = meta.propertyOffset(); i < meta.propertyCount(); ++i) - m_roleNames.insert(role++, meta.property(i).name()); -} + if (m_isEmpty != empty) { + m_isEmpty = empty; + emit isEmptyChanged(); + } +}; QVariant AssetsLibraryModel::data(const QModelIndex &index, int role) const { @@ -377,12 +348,54 @@ void AssetsLibraryModel::setRootPath(const QString &path) qCInfo(assetsLibraryBenchmark) << "model reset:" << time.elapsed(); } -void AssetsLibraryModel::setSearchText(const QString &searchText) +const QStringList &AssetsLibraryModel::supportedImageSuffixes() { - if (m_searchText != searchText) { - m_searchText = searchText; - refresh(); + static QStringList retList; + if (retList.isEmpty()) { + const QList<QByteArray> suffixes = QImageReader::supportedImageFormats(); + for (const QByteArray &suffix : suffixes) + retList.append("*." + QString::fromUtf8(suffix)); } + return retList; +} + +const QStringList &AssetsLibraryModel::supportedFragmentShaderSuffixes() +{ + static const QStringList retList {"*.frag", "*.glsl", "*.glslf", "*.fsh"}; + return retList; +} + +const QStringList &AssetsLibraryModel::supportedShaderSuffixes() +{ + static const QStringList retList {"*.frag", "*.vert", + "*.glsl", "*.glslv", "*.glslf", + "*.vsh", "*.fsh"}; + return retList; +} + +const QStringList &AssetsLibraryModel::supportedFontSuffixes() +{ + static const QStringList retList {"*.ttf", "*.otf"}; + return retList; +} + +const QStringList &AssetsLibraryModel::supportedAudioSuffixes() +{ + static const QStringList retList {"*.wav", "*.mp3"}; + return retList; +} + +const QStringList &AssetsLibraryModel::supportedVideoSuffixes() +{ + static const QStringList retList {"*.mp4"}; + return retList; +} + +const QStringList &AssetsLibraryModel::supportedTexture3DSuffixes() +{ + // These are file types only supported by 3D textures + static QStringList retList {"*.hdr", "*.ktx"}; + return retList; } const QSet<QString> &AssetsLibraryModel::supportedSuffixes() @@ -403,19 +416,6 @@ const QSet<QString> &AssetsLibraryModel::supportedSuffixes() return allSuffixes; } -bool AssetsLibraryModel::isEmpty() const -{ - return m_isEmpty; -}; - -void AssetsLibraryModel::setIsEmpty(bool empty) -{ - if (m_isEmpty != empty) { - m_isEmpty = empty; - emit isEmptyChanged(); - } -}; - const QSet<QString> &AssetsLibraryModel::previewableSuffixes() const { static QSet<QString> previewableSuffixes; |