2022-11-23 11:49:45 +02:00
|
|
|
// Copyright (C) 2022 The Qt Company Ltd.
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
2022-10-07 19:49:52 +03:00
|
|
|
|
|
|
|
import QtQuick
|
|
|
|
import QtQuick.Controls
|
2022-11-23 11:49:45 +02:00
|
|
|
import HelperWidgets as HelperWidgets
|
2022-10-07 19:49:52 +03:00
|
|
|
import StudioControls as StudioControls
|
|
|
|
|
2022-11-23 11:49:45 +02:00
|
|
|
TreeView {
|
|
|
|
id: root
|
2022-10-07 19:49:52 +03:00
|
|
|
clip: true
|
2022-11-23 11:49:45 +02:00
|
|
|
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()
|
|
|
|
}
|
2022-10-07 19:49:52 +03:00
|
|
|
|
2022-11-23 11:49:45 +02:00
|
|
|
Timer {
|
|
|
|
id: updateRowsTimer
|
|
|
|
interval: 200
|
|
|
|
repeat: false
|
|
|
|
|
|
|
|
onTriggered: {
|
|
|
|
root.updateRows()
|
2022-10-07 19:49:52 +03:00
|
|
|
}
|
2022-11-23 11:49:45 +02:00
|
|
|
}
|
2022-10-07 19:49:52 +03:00
|
|
|
|
2022-11-23 11:49:45 +02:00
|
|
|
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)
|
|
|
|
})
|
2022-10-07 19:49:52 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-23 11:49:45 +02:00
|
|
|
// 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
|
2022-10-07 19:49:52 +03:00
|
|
|
}
|
2022-11-23 11:49:45 +02:00
|
|
|
|
|
|
|
// on collapsing, set expandAll flag to false.
|
|
|
|
root.requestedExpandAll = false;
|
2022-10-07 19:49:52 +03:00
|
|
|
}
|
2022-11-23 11:49:45 +02:00
|
|
|
|
|
|
|
root.lastRowCount = root.rows
|
|
|
|
}
|
|
|
|
|
|
|
|
function _doExpandAll()
|
|
|
|
{
|
|
|
|
let expandedAny = false
|
|
|
|
for (let nRow = 0; nRow < root.rows; ++nRow) {
|
|
|
|
let index = root._modelIndex(nRow, 0)
|
|
|
|
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, 0)
|
|
|
|
// 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, 0)
|
|
|
|
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, 0)
|
|
|
|
if (assetsModel.isDirectory(index))
|
|
|
|
return
|
|
|
|
|
|
|
|
let parentItem = root._getDelegateParentForIndex(index)
|
|
|
|
parentItem.hasChildWithDropHover = true
|
|
|
|
}
|
|
|
|
|
|
|
|
function endDropHover(row)
|
|
|
|
{
|
|
|
|
let index = root._modelIndex(row, 0)
|
|
|
|
if (assetsModel.isDirectory(index))
|
|
|
|
return
|
|
|
|
|
|
|
|
let parentItem = root._getDelegateParentForIndex(index)
|
|
|
|
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 _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)
|
|
|
|
}
|
|
|
|
|
|
|
|
delegate: AssetDelegate {
|
|
|
|
assetsView: root
|
|
|
|
assetsRoot: root.assetsRoot
|
|
|
|
indentation: 5
|
2022-10-07 19:49:52 +03:00
|
|
|
}
|
2022-11-23 11:49:45 +02:00
|
|
|
} // TreeView
|