forked from qt-creator/qt-creator
QmlDesigner: Enable dropping external assets to a specific folder
Fixes: QDS-6311 Change-Id: I25bccfa6788e3b5d47fc598decbf7e03a00399b4 Reviewed-by: Henning Gründl <henning.gruendl@qt.io> Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
This commit is contained in:
@@ -32,45 +32,67 @@ import StudioControls 1.0 as StudioControls
|
||||
import StudioTheme 1.0 as StudioTheme
|
||||
|
||||
Item {
|
||||
id: rootItem
|
||||
id: root
|
||||
|
||||
property var selectedAssets: ({})
|
||||
property int allExpandedState: 0
|
||||
property string contextFilePath: ""
|
||||
property var contextDir: undefined
|
||||
property bool isDirContextMenu: false
|
||||
property var dropExtFiles: [] // array of supported externally dropped files
|
||||
|
||||
function clearSearchFilter()
|
||||
{
|
||||
searchBox.text = "";
|
||||
}
|
||||
|
||||
DropArea {
|
||||
id: dropArea
|
||||
|
||||
property var files // list of supported dropped files
|
||||
|
||||
enabled: true
|
||||
anchors.fill: parent
|
||||
|
||||
onEntered: (drag)=> {
|
||||
files = []
|
||||
for (var i = 0; i < drag.urls.length; ++i) {
|
||||
var url = drag.urls[i].toString();
|
||||
function updateDropExtFiles(drag)
|
||||
{
|
||||
root.dropExtFiles = []
|
||||
for (const u of drag.urls) {
|
||||
var url = u.toString();
|
||||
if (url.startsWith("file:///")) // remove file scheme (happens on Windows)
|
||||
url = url.substr(8)
|
||||
|
||||
var ext = url.slice(url.lastIndexOf('.') + 1).toLowerCase()
|
||||
if (rootView.supportedDropSuffixes().includes('*.' + ext))
|
||||
files.push(url)
|
||||
root.dropExtFiles.push(url)
|
||||
}
|
||||
|
||||
if (files.length === 0)
|
||||
drag.accepted = false;
|
||||
drag.accepted = root.dropExtFiles.length > 0
|
||||
}
|
||||
|
||||
DropArea { // handles external drop on empty area of the view (goes to root folder)
|
||||
id: dropArea
|
||||
y: assetsView.y + assetsView.contentHeight + 5
|
||||
width: parent.width
|
||||
height: parent.height - y
|
||||
|
||||
onEntered: (drag)=> {
|
||||
root.updateDropExtFiles(drag)
|
||||
}
|
||||
|
||||
onDropped: {
|
||||
if (files.length > 0)
|
||||
rootView.handleFilesDrop(files)
|
||||
rootView.handleExtFilesDrop(root.dropExtFiles, assetsModel.rootDir().dirPath)
|
||||
}
|
||||
|
||||
Canvas { // marker for the drop area
|
||||
id: dropCanvas
|
||||
anchors.fill: parent
|
||||
visible: dropArea.containsDrag
|
||||
|
||||
onWidthChanged: dropCanvas.requestPaint()
|
||||
onHeightChanged: dropCanvas.requestPaint()
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d")
|
||||
ctx.reset()
|
||||
ctx.strokeStyle = StudioTheme.Values.themeInteraction
|
||||
ctx.lineWidth = 2
|
||||
ctx.setLineDash([4, 4])
|
||||
ctx.rect(5, 5, dropCanvas.width - 10, dropCanvas.height - 10)
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,9 +101,9 @@ Item {
|
||||
acceptedButtons: Qt.RightButton
|
||||
onClicked: {
|
||||
if (!assetsModel.isEmpty) {
|
||||
contextFilePath = ""
|
||||
contextDir = assetsModel.rootDir()
|
||||
isDirContextMenu = false
|
||||
root.contextFilePath = ""
|
||||
root.contextDir = assetsModel.rootDir()
|
||||
root.isDirContextMenu = false
|
||||
contextMenu.popup()
|
||||
}
|
||||
}
|
||||
@@ -91,8 +113,8 @@ Item {
|
||||
function handleViewFocusOut()
|
||||
{
|
||||
contextMenu.close()
|
||||
selectedAssets = {}
|
||||
selectedAssetsChanged()
|
||||
root.selectedAssets = {}
|
||||
root.selectedAssetsChanged()
|
||||
}
|
||||
|
||||
StudioControls.Menu {
|
||||
@@ -101,49 +123,49 @@ Item {
|
||||
closePolicy: Popup.CloseOnPressOutside | Popup.CloseOnEscape
|
||||
|
||||
onOpened: {
|
||||
var numSelected = Object.values(selectedAssets).filter(p => p).length
|
||||
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: allExpandedState !== 1
|
||||
visible: isDirContextMenu
|
||||
enabled: root.allExpandedState !== 1
|
||||
visible: root.isDirContextMenu
|
||||
height: visible ? implicitHeight : 0
|
||||
onTriggered: assetsModel.toggleExpandAll(true)
|
||||
}
|
||||
|
||||
StudioControls.MenuItem {
|
||||
text: qsTr("Collapse All")
|
||||
enabled: allExpandedState !== 2
|
||||
visible: isDirContextMenu
|
||||
enabled: root.allExpandedState !== 2
|
||||
visible: root.isDirContextMenu
|
||||
height: visible ? implicitHeight : 0
|
||||
onTriggered: assetsModel.toggleExpandAll(false)
|
||||
}
|
||||
|
||||
StudioControls.MenuSeparator {
|
||||
visible: isDirContextMenu
|
||||
visible: root.isDirContextMenu
|
||||
height: visible ? StudioTheme.Values.border : 0
|
||||
}
|
||||
|
||||
StudioControls.MenuItem {
|
||||
id: deleteFileItem
|
||||
text: qsTr("Delete File")
|
||||
visible: contextFilePath
|
||||
height: visible ? implicitHeight : 0
|
||||
visible: root.contextFilePath
|
||||
height: deleteFileItem.visible ? deleteFileItem.implicitHeight : 0
|
||||
onTriggered: {
|
||||
assetsModel.deleteFiles(Object.keys(selectedAssets).filter(p => selectedAssets[p]))
|
||||
assetsModel.deleteFiles(Object.keys(root.selectedAssets).filter(p => root.selectedAssets[p]))
|
||||
}
|
||||
}
|
||||
|
||||
StudioControls.MenuSeparator {
|
||||
visible: contextFilePath
|
||||
visible: root.contextFilePath
|
||||
height: visible ? StudioTheme.Values.border : 0
|
||||
}
|
||||
|
||||
StudioControls.MenuItem {
|
||||
text: qsTr("Rename Folder")
|
||||
visible: isDirContextMenu
|
||||
visible: root.isDirContextMenu
|
||||
height: visible ? implicitHeight : 0
|
||||
onTriggered: renameFolderDialog.open()
|
||||
}
|
||||
@@ -155,14 +177,14 @@ Item {
|
||||
|
||||
StudioControls.MenuItem {
|
||||
text: qsTr("Delete Folder")
|
||||
visible: isDirContextMenu
|
||||
visible: root.isDirContextMenu
|
||||
height: visible ? implicitHeight : 0
|
||||
onTriggered: {
|
||||
var dirEmpty = !(contextDir.dirsModel && contextDir.dirsModel.rowCount() > 0)
|
||||
&& !(contextDir.filesModel && contextDir.filesModel.rowCount() > 0);
|
||||
var dirEmpty = !(root.contextDir.dirsModel && root.contextDir.dirsModel.rowCount() > 0)
|
||||
&& !(root.contextDir.filesModel && root.contextDir.filesModel.rowCount() > 0);
|
||||
|
||||
if (dirEmpty)
|
||||
assetsModel.deleteFolder(contextDir.dirPath)
|
||||
assetsModel.deleteFolder(root.contextDir.dirPath)
|
||||
else
|
||||
confirmDeleteFolderDialog.open()
|
||||
}
|
||||
@@ -243,7 +265,7 @@ Item {
|
||||
text: qsTr("Rename")
|
||||
enabled: folderRename.text !== ""
|
||||
onClicked: {
|
||||
var success = assetsModel.renameFolder(contextDir.dirPath, folderRename.text)
|
||||
var success = assetsModel.renameFolder(root.contextDir.dirPath, folderRename.text)
|
||||
if (success)
|
||||
renameFolderDialog.accept()
|
||||
|
||||
@@ -259,7 +281,7 @@ Item {
|
||||
}
|
||||
|
||||
onOpened: {
|
||||
folderRename.text = contextDir.dirName
|
||||
folderRename.text = root.contextDir.dirName
|
||||
folderRename.selectAll()
|
||||
folderRename.forceActiveFocus()
|
||||
renameFolderDialog.renameError = false
|
||||
@@ -317,7 +339,7 @@ Item {
|
||||
text: qsTr("Create")
|
||||
enabled: folderName.text !== ""
|
||||
onClicked: {
|
||||
assetsModel.addNewFolder(contextDir.dirPath + '/' + folderName.text)
|
||||
assetsModel.addNewFolder(root.contextDir.dirPath + '/' + folderName.text)
|
||||
newFolderDialog.accept()
|
||||
}
|
||||
}
|
||||
@@ -353,7 +375,7 @@ Item {
|
||||
id: folderNotEmpty
|
||||
|
||||
text: qsTr("Folder \"%1\" is not empty. Delete it anyway?")
|
||||
.arg(contextDir ? contextDir.dirName : "")
|
||||
.arg(root.contextDir ? root.contextDir.dirName : "")
|
||||
color: StudioTheme.Values.themeTextColor
|
||||
wrapMode: Text.WordWrap
|
||||
width: confirmDeleteFolderDialog.width
|
||||
@@ -381,7 +403,7 @@ Item {
|
||||
text: qsTr("Delete")
|
||||
|
||||
onClicked: {
|
||||
assetsModel.deleteFolder(contextDir.dirPath)
|
||||
assetsModel.deleteFolder(root.contextDir.dirPath)
|
||||
confirmDeleteFolderDialog.accept()
|
||||
}
|
||||
}
|
||||
@@ -441,7 +463,7 @@ Item {
|
||||
|
||||
spacing: 20
|
||||
x: 20
|
||||
width: rootItem.width - 2 * x
|
||||
width: root.width - 2 * x
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text {
|
||||
@@ -500,6 +522,8 @@ Item {
|
||||
id: dirSection
|
||||
|
||||
Section {
|
||||
id: section
|
||||
|
||||
width: assetsView.width -
|
||||
(assetsView.verticalScrollBarVisible ? assetsView.verticalThickness : 0) - 5
|
||||
caption: dirName
|
||||
@@ -514,16 +538,31 @@ Item {
|
||||
visible: dirVisible
|
||||
expandOnClick: false
|
||||
useDefaulContextMenu: false
|
||||
dropEnabled: true
|
||||
|
||||
onToggleExpand: {
|
||||
dirExpanded = !dirExpanded
|
||||
}
|
||||
|
||||
onDropEnter: (drag)=> {
|
||||
root.updateDropExtFiles(drag)
|
||||
section.highlight = drag.accepted
|
||||
}
|
||||
|
||||
onDropExit: {
|
||||
section.highlight = false
|
||||
}
|
||||
|
||||
onDrop: {
|
||||
section.highlight = false
|
||||
rootView.handleExtFilesDrop(root.dropExtFiles, dirPath)
|
||||
}
|
||||
|
||||
onShowContextMenu: {
|
||||
contextFilePath = ""
|
||||
contextDir = model
|
||||
isDirContextMenu = true
|
||||
allExpandedState = assetsModel.getAllExpandedState()
|
||||
root.contextFilePath = ""
|
||||
root.contextDir = model
|
||||
root.isDirContextMenu = true
|
||||
root.allExpandedState = assetsModel.getAllExpandedState()
|
||||
contextMenu.popup()
|
||||
}
|
||||
|
||||
@@ -552,9 +591,9 @@ Item {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
onClicked: {
|
||||
contextFilePath = ""
|
||||
contextDir = model
|
||||
isDirContextMenu = true
|
||||
root.contextFilePath = ""
|
||||
root.contextDir = model
|
||||
root.isDirContextMenu = true
|
||||
contextMenu.popup()
|
||||
}
|
||||
}
|
||||
@@ -570,7 +609,8 @@ Item {
|
||||
width: assetsView.width -
|
||||
(assetsView.verticalScrollBarVisible ? assetsView.verticalThickness : 0)
|
||||
height: img.height
|
||||
color: selectedAssets[filePath] ? StudioTheme.Values.themeInteraction
|
||||
color: root.selectedAssets[filePath]
|
||||
? StudioTheme.Values.themeInteraction
|
||||
: (mouseArea.containsMouse ? StudioTheme.Values.themeSectionHeadBackground
|
||||
: "transparent")
|
||||
|
||||
@@ -611,29 +651,29 @@ Item {
|
||||
forceActiveFocus()
|
||||
var ctrlDown = mouse.modifiers & Qt.ControlModifier
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
if (!selectedAssets[filePath] && !ctrlDown)
|
||||
selectedAssets = {}
|
||||
currFileSelected = ctrlDown ? !selectedAssets[filePath] : true
|
||||
selectedAssets[filePath] = currFileSelected
|
||||
selectedAssetsChanged()
|
||||
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(selectedAssets).filter(p => selectedAssets[p]),
|
||||
Object.keys(root.selectedAssets).filter(p => root.selectedAssets[p]),
|
||||
mapToGlobal(mouse.x, mouse.y))
|
||||
}
|
||||
} else {
|
||||
if (!selectedAssets[filePath] && !ctrlDown)
|
||||
selectedAssets = {}
|
||||
currFileSelected = selectedAssets[filePath] || !ctrlDown
|
||||
selectedAssets[filePath] = currFileSelected
|
||||
selectedAssetsChanged()
|
||||
if (!root.selectedAssets[filePath] && !ctrlDown)
|
||||
root.selectedAssets = {}
|
||||
currFileSelected = root.selectedAssets[filePath] || !ctrlDown
|
||||
root.selectedAssets[filePath] = currFileSelected
|
||||
root.selectedAssetsChanged()
|
||||
|
||||
contextFilePath = filePath
|
||||
contextDir = model.fileDir
|
||||
root.contextFilePath = filePath
|
||||
root.contextDir = model.fileDir
|
||||
root.isDirContextMenu = false
|
||||
|
||||
tooltipBackend.hideTooltip()
|
||||
isDirContextMenu = false
|
||||
contextMenu.popup()
|
||||
}
|
||||
}
|
||||
@@ -641,9 +681,9 @@ Item {
|
||||
onReleased: (mouse)=> {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
if (!(mouse.modifiers & Qt.ControlModifier))
|
||||
selectedAssets = {}
|
||||
selectedAssets[filePath] = currFileSelected
|
||||
selectedAssetsChanged()
|
||||
root.selectedAssets = {}
|
||||
root.selectedAssets[filePath] = currFileSelected
|
||||
root.selectedAssetsChanged()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -54,6 +54,8 @@ Item {
|
||||
property bool expandOnClick: true // if false, toggleExpand signal will be emitted instead
|
||||
property bool addTopPadding: true
|
||||
property bool addBottomPadding: true
|
||||
property bool dropEnabled: false
|
||||
property bool highlight: false
|
||||
|
||||
property bool useDefaulContextMenu: true
|
||||
|
||||
@@ -72,9 +74,22 @@ Item {
|
||||
function onExpandAll() { section.expanded = true }
|
||||
}
|
||||
|
||||
signal drop(var drag)
|
||||
signal dropEnter(var drag)
|
||||
signal dropExit()
|
||||
signal showContextMenu()
|
||||
signal toggleExpand()
|
||||
|
||||
DropArea {
|
||||
id: dropArea
|
||||
|
||||
enabled: section.dropEnabled
|
||||
anchors.fill: parent
|
||||
|
||||
onEntered: (drag)=> section.dropEnter(drag)
|
||||
onDropped: (drag)=> section.drop(drag)
|
||||
onExited: section.dropExit()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: header
|
||||
@@ -82,7 +97,9 @@ Item {
|
||||
visible: !section.hideHeader
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
color: Qt.lighter(StudioTheme.Values.themeSectionHeadBackground, 1.0 + (0.2 * section.level))
|
||||
color: section.highlight ? StudioTheme.Values.themeInteraction
|
||||
: Qt.lighter(StudioTheme.Values.themeSectionHeadBackground, 1.0
|
||||
+ (0.2 * section.level))
|
||||
|
||||
Item {
|
||||
StudioControls.Menu {
|
||||
|
@@ -373,7 +373,7 @@ void AssetsLibraryModel::setSearchText(const QString &searchText)
|
||||
}
|
||||
}
|
||||
|
||||
const QSet<QString> &AssetsLibraryModel::supportedSuffixes() const
|
||||
const QSet<QString> &AssetsLibraryModel::supportedSuffixes()
|
||||
{
|
||||
static QSet<QString> allSuffixes;
|
||||
if (allSuffixes.isEmpty()) {
|
||||
|
@@ -68,6 +68,7 @@ public:
|
||||
static const QStringList &supportedAudioSuffixes();
|
||||
static const QStringList &supportedVideoSuffixes();
|
||||
static const QStringList &supportedTexture3DSuffixes();
|
||||
static const QSet<QString> &supportedSuffixes();
|
||||
|
||||
const QSet<QString> &previewableSuffixes() const;
|
||||
|
||||
@@ -93,7 +94,6 @@ signals:
|
||||
void isEmptyChanged();
|
||||
|
||||
private:
|
||||
const QSet<QString> &supportedSuffixes() const;
|
||||
|
||||
void setIsEmpty(bool empty);
|
||||
|
||||
|
@@ -213,9 +213,24 @@ void AssetsLibraryWidget::handleAddAsset()
|
||||
addResources({});
|
||||
}
|
||||
|
||||
void AssetsLibraryWidget::handleFilesDrop(const QStringList &filesPaths)
|
||||
void AssetsLibraryWidget::handleExtFilesDrop(const QStringList &filesPaths, const QString &targetDirPath)
|
||||
{
|
||||
addResources(filesPaths);
|
||||
QStringList assetPaths;
|
||||
QStringList otherPaths; // as of now 3D models, and 3D Studio presentations
|
||||
std::tie(assetPaths, otherPaths) = Utils::partition(filesPaths, [](const QString &path) {
|
||||
QString suffix = "*." + path.split('.').last().toLower();
|
||||
return AssetsLibraryModel::supportedSuffixes().contains(suffix);
|
||||
});
|
||||
|
||||
AddFilesResult result = ModelNodeOperations::addFilesToProject(assetPaths, targetDirPath);
|
||||
if (result == AddFilesResult::Failed) {
|
||||
Core::AsynchronousMessageBox::warning(tr("Failed to Add Files"),
|
||||
tr("Could not add %1 to project.")
|
||||
.arg(filesPaths.join(' ')));
|
||||
}
|
||||
|
||||
if (!otherPaths.empty())
|
||||
addResources(otherPaths);
|
||||
}
|
||||
|
||||
QSet<QString> AssetsLibraryWidget::supportedDropSuffixes()
|
||||
|
@@ -80,7 +80,7 @@ public:
|
||||
Q_INVOKABLE void startDragAsset(const QStringList &assetPaths, const QPointF &mousePos);
|
||||
Q_INVOKABLE void handleAddAsset();
|
||||
Q_INVOKABLE void handleSearchfilterChanged(const QString &filterText);
|
||||
Q_INVOKABLE void handleFilesDrop(const QStringList &filesPaths);
|
||||
Q_INVOKABLE void handleExtFilesDrop(const QStringList &filesPaths, const QString &targetDirPath);
|
||||
Q_INVOKABLE QSet<QString> supportedDropSuffixes();
|
||||
|
||||
signals:
|
||||
|
@@ -989,7 +989,7 @@ void addTabBarToStackedContainer(const SelectionContext &selectionContext)
|
||||
|
||||
}
|
||||
|
||||
static AddFilesResult addFilesToProject(const QStringList &fileNames, const QString &defaultDirectory)
|
||||
AddFilesResult addFilesToProject(const QStringList &fileNames, const QString &defaultDirectory)
|
||||
{
|
||||
QString directory = AddImagesDialog::getDirectory(fileNames, defaultDirectory);
|
||||
if (directory.isEmpty())
|
||||
|
@@ -76,6 +76,7 @@ void addItemToStackedContainer(const SelectionContext &selectionContext);
|
||||
void increaseIndexOfStackedContainer(const SelectionContext &selectionContext);
|
||||
void decreaseIndexOfStackedContainer(const SelectionContext &selectionContext);
|
||||
void addTabBarToStackedContainer(const SelectionContext &selectionContext);
|
||||
AddFilesResult addFilesToProject(const QStringList &fileNames, const QString &defaultDirectory);
|
||||
AddFilesResult addImageToProject(const QStringList &fileNames, const QString &directory);
|
||||
AddFilesResult addFontToProject(const QStringList &fileNames, const QString &directory);
|
||||
AddFilesResult addSoundToProject(const QStringList &fileNames, const QString &directory);
|
||||
|
Reference in New Issue
Block a user