Files
o3de/Code/Tools/AssetProcessor/native/ui/AssetTreeModel.cpp
T
Dennis Brakhane 7f75dc6dee Fix "index out of range" error in AssetProcessor (#4324)
* Fix "index out of range" error

When the parent is the tree root element, beginInsertRows must be
called with an invalid (but legal) index. A QModelIndex with a row
index of zero when the parent has no children is an illegal index
and will result in "undefined behavior", like the "index out of
range" one.

Therefore, if our parent is the tree root element, we use QModelIndex()
instead.

Fixes #2343

Signed-off-by: Dennis Brakhane <brakhane@gmail.com>

* Use QModelIndex() instead of createIndex(-1, -1)

Both do the same, but the former is Qt best practise.

Signed-off-by: Dennis Brakhane <brakhane@gmail.com>

* add some sanity checks in debug mode

Using illegal ModelIndices can result in hard to debug problems later
on, so add a few checks to help spotting them sooner.

Signed-off-by: Dennis Brakhane <brakhane@gmail.com>
2021-10-04 17:41:35 -07:00

234 lines
6.6 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "AssetTreeModel.h"
#include "AssetTreeItem.h"
#include <AzCore/Component/TickBus.h>
#include <AzCore/std/smart_ptr/make_shared.h>
namespace AssetProcessor
{
AssetTreeModel::AssetTreeModel(AZStd::shared_ptr<AzToolsFramework::AssetDatabase::AssetDatabaseConnection> sharedDbConnection, QObject *parent) :
QAbstractItemModel(parent),
m_sharedDbConnection(sharedDbConnection),
m_errorIcon(QStringLiteral(":/stylesheet/img/logging/error.svg"))
{
ApplicationManagerNotifications::Bus::Handler::BusConnect();
AzToolsFramework::AssetDatabase::AssetDatabaseNotificationBus::Handler::BusConnect();
}
AssetTreeModel::~AssetTreeModel()
{
AzToolsFramework::AssetDatabase::AssetDatabaseNotificationBus::Handler::BusDisconnect();
ApplicationManagerNotifications::Bus::Handler::BusDisconnect();
}
void AssetTreeModel::ApplicationShutdownRequested()
{
AzToolsFramework::AssetDatabase::AssetDatabaseNotificationBus::Handler::BusDisconnect();
// AssetTreeModels can queue functions on the systemTickBus for processing on the main thread in response to asset changes
// We need to clear out any left pending before we go away
AZ::SystemTickBus::ExecuteQueuedEvents();
}
void AssetTreeModel::Reset()
{
beginResetModel();
m_root.reset(new AssetTreeItem(AZStd::make_shared<AssetTreeItemData>("", "", true, AZ::Uuid::CreateNull()), m_errorIcon));
ResetModel();
endResetModel();
}
int AssetTreeModel::rowCount(const QModelIndex &parent) const
{
if (parent.column() > 0)
{
return 0;
}
AssetTreeItem* parentItem = nullptr;
if (!parent.isValid())
{
parentItem = m_root.get();
}
else
{
parentItem = static_cast<AssetTreeItem*>(parent.internalPointer());
}
if (!parentItem)
{
return 0;
}
return parentItem->getChildCount();
}
int AssetTreeModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid())
{
return static_cast<AssetTreeItem*>(parent.internalPointer())->GetColumnCount();
}
if (m_root)
{
return m_root->GetColumnCount();
}
return 0;
}
QVariant AssetTreeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
{
return QVariant();
}
AssetTreeItem* item = static_cast<AssetTreeItem*>(index.internalPointer());
switch (role)
{
case Qt::DisplayRole:
return item->GetDataForColumn(index.column());
case Qt::DecorationRole:
// Only show the icon in the name column
if (index.column() == static_cast<int>(AssetTreeColumns::Name))
{
return item->GetIcon();
}
break;
case Qt::ToolTipRole:
{
QString toolTip = item->GetData()->m_unresolvedIssuesTooltip;
if (!toolTip.isEmpty())
{
return toolTip;
}
// Purposely return an empty string, so mousing over rows clear out.
return QString("");
}
}
return QVariant();
}
QVariant AssetTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
{
return QVariant();
}
if (section < 0 || section >= static_cast<int>(AssetTreeColumns::Max))
{
return QVariant();
}
switch (section)
{
case static_cast<int>(AssetTreeColumns::Name):
return tr("Name");
case static_cast<int>(AssetTreeColumns::Extension):
return tr("Extension");
default:
AZ_Warning("AssetProcessor", false, "Unhandled AssetTree section %d", section);
break;
}
return QVariant();
}
QModelIndex AssetTreeModel::index(int row, int column, const QModelIndex &parent) const
{
if (!hasIndex(row, column, parent))
{
return QModelIndex();
}
AssetTreeItem* parentItem = nullptr;
if (!parent.isValid())
{
parentItem = m_root.get();
}
else
{
parentItem = static_cast<AssetTreeItem*>(parent.internalPointer());
}
if (!parentItem)
{
return QModelIndex();
}
AssetTreeItem* childItem = parentItem->GetChild(row);
if (childItem)
{
QModelIndex index = createIndex(row, column, childItem);
Q_ASSERT(checkIndex(index));
return index;
}
return QModelIndex();
}
bool AssetTreeModel::setData(const QModelIndex &/*index*/, const QVariant &/*value*/, int /*role*/)
{
return false;
}
Qt::ItemFlags AssetTreeModel::flags(const QModelIndex &index) const
{
return Qt::ItemIsSelectable | QAbstractItemModel::flags(index);
}
QModelIndex AssetTreeModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
{
return QModelIndex();
}
AssetTreeItem* childItem = static_cast<AssetTreeItem*>(index.internalPointer());
AssetTreeItem* parentItem = childItem->GetParent();
if (parentItem == m_root.get() || parentItem == nullptr)
{
return QModelIndex();
}
QModelIndex parentIndex = createIndex(parentItem->GetRow(), 0, parentItem);
Q_ASSERT(checkIndex(parentIndex));
return parentIndex;
}
bool AssetTreeModel::hasChildren(const QModelIndex &parent) const
{
AssetTreeItem* parentItem = nullptr;
if (!parent.isValid())
{
parentItem = m_root.get();
}
else
{
parentItem = static_cast<AssetTreeItem*>(parent.internalPointer());
}
if (!parentItem)
{
return false;
}
return parentItem->getChildCount() > 0;
}
QFlags<QItemSelectionModel::SelectionFlag> AssetTreeModel::GetAssetTreeSelectionFlags()
{
return QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows | QItemSelectionModel::QItemSelectionModel::Current;
}
}