forked from qt-creator/qt-creator
Moved the DropArea from the AssetDelegate into the AssetsView, so that moving the cursor through the delegates no longer denies and then permits dragging. This glitch happened because the DropArea was inside the delegate, while the TreeView also has rowSpacing, which are areas that do not belong to the delegates, and for which you don't normally have drag & drop support. Task-number: QDS-8232 Change-Id: If49a384f25bb870105448156f436e048479e880c Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io> Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
396 lines
12 KiB
QML
396 lines
12 KiB
QML
// 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
|
|
|
|
TreeView {
|
|
id: root
|
|
clip: true
|
|
interactive: verticalScrollBar.visible && !root.contextMenu.opened
|
|
reuseItems: false
|
|
boundsBehavior: Flickable.StopAtBounds
|
|
rowSpacing: 5
|
|
|
|
required property Item assetsRoot
|
|
required property StudioControls.Menu contextMenu
|
|
property alias verticalScrollBar: verticalScrollBar
|
|
|
|
property var selectedAssets: ({})
|
|
|
|
// used to see if the op requested is to expand or to collapse.
|
|
property int lastRowCount: -1
|
|
// we need this to know if we need to expand further, while we're in onRowsChanged()
|
|
property bool requestedExpandAll: true
|
|
// used to compute the visual depth of the items we show to the user.
|
|
property int rootPathDepth: 0
|
|
property int rootPathRow: 0
|
|
// i.e. first child of the root path
|
|
readonly property int firstRow: root.rootPathRow + 1
|
|
property int rowToExpand: -1
|
|
property var __createdDirectories: []
|
|
|
|
rowHeightProvider: (row) => {
|
|
if (row <= root.rootPathRow)
|
|
return 0
|
|
|
|
return -1
|
|
}
|
|
|
|
ScrollBar.vertical: HelperWidgets.VerticalScrollBar {
|
|
id: verticalScrollBar
|
|
scrollBarVisible: root.contentHeight > root.height
|
|
}
|
|
|
|
model: assetsModel
|
|
|
|
onRowsChanged: {
|
|
if (root.rows > root.rootPathRow + 1 && !assetsModel.haveFiles ||
|
|
root.rows <= root.rootPathRow + 1 && assetsModel.haveFiles) {
|
|
assetsModel.syncHaveFiles()
|
|
}
|
|
|
|
updateRows()
|
|
}
|
|
|
|
Timer {
|
|
id: updateRowsTimer
|
|
interval: 200
|
|
repeat: false
|
|
|
|
onTriggered: {
|
|
root.updateRows()
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: rootView
|
|
|
|
function onDirectoryCreated(path)
|
|
{
|
|
root.__createdDirectories.push(path)
|
|
|
|
updateRowsTimer.restart()
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: assetsModel
|
|
function onDirectoryLoaded(path)
|
|
{
|
|
// updating rows for safety: the rows might have been created before the
|
|
// directory (esp. the root path) has been loaded, so we must make sure all rows are
|
|
// expanded -- otherwise, the tree may not become visible.
|
|
|
|
updateRowsTimer.restart()
|
|
|
|
let idx = assetsModel.indexForPath(path)
|
|
let row = root.rowAtIndex(idx)
|
|
let column = root.columnAtIndex(idx)
|
|
|
|
if (row >= root.rootPathRow && !root.isExpanded(row))
|
|
root.expand(row)
|
|
}
|
|
|
|
function onRootPathChanged()
|
|
{
|
|
// when we switch from one project to another, we need to reset the state of the
|
|
// view: make sure we will do an "expand all" (otherwise, the whole tree might
|
|
// be collapsed, and with our visible root not being the actual root of the tree,
|
|
// the entire tree would be invisible)
|
|
root.lastRowCount = -1
|
|
root.requestedExpandAll = true
|
|
}
|
|
|
|
function onFileChanged(filePath)
|
|
{
|
|
rootView.invalidateThumbnail(filePath)
|
|
|
|
let index = assetsModel.indexForPath(filePath)
|
|
let cell = root.cellAtIndex(index)
|
|
let fileItem = root.itemAtCell(cell)
|
|
|
|
if (fileItem)
|
|
fileItem.reloadImage()
|
|
}
|
|
|
|
} // Connections
|
|
|
|
function addCreatedFolder(path)
|
|
{
|
|
root.__createdDirectories.push(path)
|
|
}
|
|
|
|
function selectedPathsAsList()
|
|
{
|
|
return Object.keys(root.selectedAssets)
|
|
.filter(itemPath => root.selectedAssets[itemPath])
|
|
}
|
|
|
|
// workaround for a bug -- might be fixed by https://codereview.qt-project.org/c/qt/qtdeclarative/+/442721
|
|
function resetVerticalScrollPosition()
|
|
{
|
|
root.contentY = 0
|
|
}
|
|
|
|
function updateRows()
|
|
{
|
|
if (root.rows <= 0)
|
|
return
|
|
|
|
while (root.__createdDirectories.length > 0) {
|
|
let dirPath = root.__createdDirectories.pop()
|
|
let index = assetsModel.indexForPath(dirPath)
|
|
let row = root.rowAtIndex(index)
|
|
|
|
if (row > 0)
|
|
root.expand(row)
|
|
else if (row === -1 && assetsModel.indexIsValid(index)) {
|
|
// It is possible that this directory, dirPath, was created inside of a parent
|
|
// directory that was not yet expanded in the TreeView. This can happen with the
|
|
// bridge plugin. In such a situation, we don't have a "row" for it yet, so we have
|
|
// to expand its parents, from root to our `index`
|
|
let parents = assetsModel.parentIndices(index);
|
|
parents.reverse().forEach(idx => {
|
|
let row = root.rowAtIndex(idx)
|
|
if (row > 0)
|
|
root.expand(row)
|
|
})
|
|
}
|
|
}
|
|
|
|
// we have no way to know beyond doubt here if updateRows() was called due
|
|
// to a request to expand or to collapse rows - but it should be safe to
|
|
// assume that, if we have more rows now than the last time, then it's an expand
|
|
var expanding = (root.rows >= root.lastRowCount)
|
|
|
|
if (expanding) {
|
|
if (root.requestedExpandAll)
|
|
root.__doExpandAll()
|
|
} else {
|
|
if (root.rowToExpand > 0) {
|
|
root.expand(root.rowToExpand)
|
|
root.rowToExpand = -1
|
|
}
|
|
|
|
// on collapsing, set expandAll flag to false.
|
|
root.requestedExpandAll = false;
|
|
}
|
|
|
|
root.lastRowCount = root.rows
|
|
}
|
|
|
|
function __doExpandAll()
|
|
{
|
|
let expandedAny = false
|
|
for (let nRow = 0; nRow < root.rows; ++nRow) {
|
|
let index = root.__modelIndex(nRow)
|
|
if (assetsModel.isDirectory(index) && !root.isExpanded(nRow)) {
|
|
root.expand(nRow);
|
|
expandedAny = true
|
|
}
|
|
}
|
|
|
|
if (!expandedAny)
|
|
Qt.callLater(root.forceLayout)
|
|
}
|
|
|
|
function expandAll()
|
|
{
|
|
// In order for __doExpandAll() to be called repeatedly (every time a new node is
|
|
// loaded, and then, expanded), we need to set requestedExpandAll to true.
|
|
root.requestedExpandAll = true
|
|
root.__doExpandAll()
|
|
}
|
|
|
|
function collapseAll()
|
|
{
|
|
root.resetVerticalScrollPosition()
|
|
|
|
// collapse all, except for the root path - from the last item (leaves) up to the root
|
|
for (let nRow = root.rows - 1; nRow >= 0; --nRow) {
|
|
let index = root.__modelIndex(nRow)
|
|
// we don't want to collapse the root path, because doing so will hide the contents
|
|
// of the tree.
|
|
if (assetsModel.filePath(index) === assetsModel.rootPath())
|
|
break
|
|
|
|
root.collapse(nRow)
|
|
}
|
|
}
|
|
|
|
// workaround for a bug -- might be fixed by https://codereview.qt-project.org/c/qt/qtdeclarative/+/442721
|
|
onContentHeightChanged: {
|
|
if (root.contentHeight <= root.height) {
|
|
let first = root.itemAtCell(0, root.firstRow)
|
|
if (!first)
|
|
root.contentY = 0
|
|
}
|
|
}
|
|
|
|
function computeAllExpandedState()
|
|
{
|
|
var dirsWithChildren = [...Array(root.rows).keys()].filter(row => {
|
|
let index = root.__modelIndex(row)
|
|
return assetsModel.isDirectory(index) && assetsModel.hasChildren(index)
|
|
})
|
|
|
|
var countExpanded = dirsWithChildren.filter(row => root.isExpanded(row)).length
|
|
|
|
if (countExpanded === dirsWithChildren.length)
|
|
return "all_expanded"
|
|
|
|
if (countExpanded === 0)
|
|
return "all_collapsed"
|
|
return ""
|
|
}
|
|
|
|
function startDropHoverOver(row)
|
|
{
|
|
let index = root.__modelIndex(row)
|
|
if (assetsModel.isDirectory(index)) {
|
|
let item = root.__getDelegateItemForIndex(index)
|
|
if (item)
|
|
item.isHighlighted = true
|
|
return
|
|
}
|
|
|
|
let parentItem = root.__getDelegateParentForIndex(index)
|
|
if (parentItem)
|
|
parentItem.hasChildWithDropHover = true
|
|
}
|
|
|
|
function endDropHover(row)
|
|
{
|
|
let index = root.__modelIndex(row)
|
|
if (assetsModel.isDirectory(index)) {
|
|
let item = root.__getDelegateItemForIndex(index)
|
|
if (item)
|
|
item.isHighlighted = false
|
|
return
|
|
}
|
|
|
|
let parentItem = root.__getDelegateParentForIndex(index)
|
|
if (parentItem)
|
|
parentItem.hasChildWithDropHover = false
|
|
}
|
|
|
|
function isAssetSelected(itemPath)
|
|
{
|
|
return root.selectedAssets[itemPath] ? true : false
|
|
}
|
|
|
|
function clearSelectedAssets()
|
|
{
|
|
root.selectedAssets = {}
|
|
}
|
|
|
|
function setAssetSelected(itemPath, selected)
|
|
{
|
|
root.selectedAssets[itemPath] = selected
|
|
root.selectedAssetsChanged()
|
|
}
|
|
|
|
function __getDelegateParentForIndex(index)
|
|
{
|
|
let parentIndex = assetsModel.parentDirIndex(index)
|
|
let parentCell = root.cellAtIndex(parentIndex)
|
|
return root.itemAtCell(parentCell)
|
|
}
|
|
|
|
function __getDelegateItemForIndex(index)
|
|
{
|
|
let cell = root.cellAtIndex(index)
|
|
return root.itemAtCell(cell)
|
|
}
|
|
|
|
function __modelIndex(row)
|
|
{
|
|
// The modelIndex() function exists since 6.3. In Qt 6.3, this modelIndex() function was a
|
|
// member of the TreeView, while in Qt6.4 it was moved to TableView. In Qt6.4, the order of
|
|
// the arguments was changed.
|
|
if (assetsRoot.qtVersionAtLeast6_4)
|
|
return root.modelIndex(0, row)
|
|
else
|
|
return root.modelIndex(row, 0)
|
|
}
|
|
|
|
DropArea {
|
|
id: dropArea
|
|
enabled: true
|
|
anchors.fill: parent
|
|
|
|
property bool __isHoveringDrop: false
|
|
property int __rowHoveringOver: -1
|
|
|
|
function __rowAndItem(drag)
|
|
{
|
|
let pos = dropArea.mapToItem(root, drag.x, drag.y)
|
|
let cell = root.cellAtPos(pos.x, pos.y, true)
|
|
let item = root.itemAtCell(cell)
|
|
|
|
return [cell.y, item]
|
|
}
|
|
|
|
onEntered: (drag) => {
|
|
root.assetsRoot.updateDropExtFiles(drag)
|
|
|
|
let [row, item] = dropArea.__rowAndItem(drag)
|
|
dropArea.__isHoveringDrop = drag.accepted && root.assetsRoot.dropSimpleExtFiles.length > 0
|
|
|
|
if (item && dropArea.__isHoveringDrop)
|
|
root.startDropHoverOver(row)
|
|
|
|
dropArea.__rowHoveringOver = row
|
|
}
|
|
|
|
onDropped: (drag) => {
|
|
let [row, item] = dropArea.__rowAndItem(drag)
|
|
|
|
if (item) {
|
|
root.endDropHover(row)
|
|
|
|
let dirPath = item.getDirPath()
|
|
|
|
rootView.emitExtFilesDrop(root.assetsRoot.dropSimpleExtFiles,
|
|
root.assetsRoot.dropComplexExtFiles,
|
|
dirPath)
|
|
}
|
|
|
|
dropArea.__isHoveringDrop = false
|
|
dropArea.__rowHoveringOver = -1
|
|
}
|
|
|
|
onPositionChanged: (drag) => {
|
|
let [row, item] = dropArea.__rowAndItem(drag)
|
|
|
|
if (dropArea.__rowHoveringOver !== row && dropArea.__rowHoveringOver > -1) {
|
|
root.endDropHover(dropArea.__rowHoveringOver)
|
|
|
|
if (item)
|
|
root.startDropHoverOver(row)
|
|
}
|
|
|
|
dropArea.__rowHoveringOver = row
|
|
}
|
|
|
|
onExited: {
|
|
if (!dropArea.__isHoveringDrop || dropArea.__rowHoveringOver === -1)
|
|
return
|
|
|
|
root.endDropHover(dropArea.__rowHoveringOver)
|
|
|
|
dropArea.__isHoveringDrop = false
|
|
dropArea.__rowHoveringOver = -1
|
|
}
|
|
}
|
|
|
|
delegate: AssetDelegate {
|
|
assetsView: root
|
|
assetsRoot: root.assetsRoot
|
|
indentation: 5
|
|
}
|
|
} // TreeView
|