Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,90 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "AssetDetailsPanel.h"
#include "AssetTreeFilterModel.h"
#include "MainWindow.h"
#include "ProductAssetTreeModel.h"
#include "SourceAssetTreeModel.h"
namespace AssetProcessor
{
AssetDetailsPanel::AssetDetailsPanel(QWidget* parent) : QFrame(parent)
{
}
AssetDetailsPanel::~AssetDetailsPanel()
{
}
void AssetDetailsPanel::RegisterAssociatedWidgets(
QTreeView* sourceTreeView,
SourceAssetTreeModel* sourceAssetTreeModel,
AssetTreeFilterModel* sourceFilterModel,
QTreeView* productTreeView,
ProductAssetTreeModel* productAssetTreeModel,
AssetTreeFilterModel* productFilterModel,
QTabWidget* assetTab)
{
m_sourceTreeView = sourceTreeView;
m_sourceTreeModel = sourceAssetTreeModel;
m_sourceFilterModel = sourceFilterModel;
m_productTreeView = productTreeView;
m_productTreeModel = productAssetTreeModel;
m_productFilterModel = productFilterModel;
m_assetsTab = assetTab;
}
void AssetDetailsPanel::GoToSource(const AZStd::string& source)
{
if (!m_sourceTreeModel || !m_sourceTreeView || !m_assetsTab || !m_sourceFilterModel)
{
return;
}
m_assetsTab->setCurrentIndex(static_cast<int>(MainWindow::AssetTabIndex::Source));
QModelIndex goToIndex = m_sourceTreeModel->GetIndexForSource(source);
// Make sure this index is visible, even if a search is active.
m_sourceFilterModel->ForceModelIndexVisible(goToIndex);
QModelIndex filterIndex = m_sourceFilterModel->mapFromSource(goToIndex);
// Some tables, like the source dependencies table, may have wildcards or links to files that don't exist.
if (!filterIndex.isValid())
{
return;
}
m_sourceTreeView->scrollTo(filterIndex, QAbstractItemView::ScrollHint::EnsureVisible);
m_sourceTreeView->selectionModel()->setCurrentIndex(filterIndex, AssetTreeModel::GetAssetTreeSelectionFlags());
}
void AssetDetailsPanel::GoToProduct(const AZStd::string& product)
{
if (!m_productTreeModel || !m_productTreeView || !m_assetsTab || !m_productFilterModel)
{
return;
}
m_assetsTab->setCurrentIndex(static_cast<int>(MainWindow::AssetTabIndex::Product));
QModelIndex goToIndex = m_productTreeModel->GetIndexForProduct(product);
// Make sure this index is visible, even if a search is active.
m_productFilterModel->ForceModelIndexVisible(goToIndex);
QModelIndex filterIndex = m_productFilterModel->mapFromSource(goToIndex);
// Some tables may have wildcards or links to files that don't exist.
if (!filterIndex.isValid())
{
return;
}
m_productTreeView->scrollTo(filterIndex, QAbstractItemView::ScrollHint::EnsureVisible);
m_productTreeView->selectionModel()->setCurrentIndex(filterIndex, AssetTreeModel::GetAssetTreeSelectionFlags());
}
}
@@ -0,0 +1,62 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <QFrame>
#include <AzCore/std/string/string.h>
class QTreeView;
class QTabWidget;
namespace AzQtComponents
{
class FilteredSearchWidget;
}
namespace AssetProcessor
{
class AssetTreeFilterModel;
class ProductAssetTreeModel;
class SourceAssetTreeModel;
class AssetDetailsPanel
: public QFrame
{
public:
explicit AssetDetailsPanel(QWidget* parent = nullptr);
~AssetDetailsPanel() override;
void RegisterAssociatedWidgets(
QTreeView* sourceTreeView,
SourceAssetTreeModel* sourceAssetTreeModel,
AssetTreeFilterModel* sourceFilterModel,
QTreeView* productTreeView,
ProductAssetTreeModel* productAssetTreeModel,
AssetTreeFilterModel* productFilterModel,
QTabWidget* assetTab);
void GoToSource(const AZStd::string& source);
void GoToProduct(const AZStd::string& product);
protected:
QTreeView* m_sourceTreeView = nullptr;
SourceAssetTreeModel* m_sourceTreeModel = nullptr;
AssetTreeFilterModel* m_sourceFilterModel = nullptr;
QTreeView* m_productTreeView = nullptr;
ProductAssetTreeModel* m_productTreeModel = nullptr;
AssetTreeFilterModel* m_productFilterModel = nullptr;
QTabWidget* m_assetsTab = nullptr;
};
} // namespace AssetProcessor
@@ -0,0 +1,160 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "AssetTreeFilterModel.h"
#include "AssetTreeItem.h"
namespace AssetProcessor
{
AssetTreeFilterModel::AssetTreeFilterModel(QObject* parent) : QSortFilterProxyModel(parent)
{
}
void AssetTreeFilterModel::FilterChanged(const QString& newFilter)
{
// If the search was changed, clear the asset that had visibility forced.
m_pathToForceVisibleAsset.clear();
setFilterRegExp(newFilter);
setFilterCaseSensitivity(Qt::CaseInsensitive);
invalidateFilter();
}
bool AssetTreeFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
{
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
AssetTreeItem* assetTreeItem = static_cast<AssetTreeItem*>(index.internalPointer());
if (!assetTreeItem)
{
return false;
}
for (const auto& forceVisible : m_pathToForceVisibleAsset)
{
if (forceVisible.get() == assetTreeItem->GetData().get())
{
return true;
}
}
QRegExp filter(filterRegExp());
if (filter.isEmpty())
{
return true;
}
// It's common to find assets referenced by UUID and not by name or path.
// For example, asset references in AZ data files (like slices) are stored on disk as UUIDs.
// It's useful to be able to search for an asset by UUID.
QString searchStr = filter.pattern();
// If a subId is provided just ignore that bit and pass in the UUID string since that's what we're
// going to search against
auto subidPos = searchStr.indexOf(':');
if (subidPos != -1)
{
searchStr = searchStr.mid(0, subidPos);
}
AZ::Uuid filterAsUuid = AZ::Uuid::CreateStringPermissive(searchStr.toUtf8(), 0);
return DescendantMatchesFilter(*assetTreeItem, filter, filterAsUuid);
}
bool AssetTreeFilterModel::DescendantMatchesFilter(const AssetTreeItem& assetTreeItem, const QRegExp& filter, const AZ::Uuid& filterAsUuid) const
{
if (filter.isEmpty())
{
// Match everything if there is no filter.
return true;
}
if (!filterAsUuid.IsNull())
{
if (assetTreeItem.GetData()->m_uuid == filterAsUuid)
{
return true;
}
}
if (assetTreeItem.GetData()->m_name.contains(filter))
{
return true;
}
if (!assetTreeItem.GetData()->m_isFolder)
{
return false;
}
for (int childIndex = 0; childIndex < assetTreeItem.getChildCount(); ++childIndex)
{
const AssetTreeItem* childItem = assetTreeItem.GetChild(childIndex);
if (!childItem)
{
continue;
}
if (DescendantMatchesFilter(*childItem, filter, filterAsUuid))
{
return true;
}
}
return false;
}
bool AssetTreeFilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) const
{
if (!left.isValid())
{
return false;
}
else if (!right.isValid())
{
return true;
}
AssetTreeItem* leftItem = static_cast<AssetTreeItem*>(left.internalPointer());
AssetTreeItem* rightItem = static_cast<AssetTreeItem*>(right.internalPointer());
// Always sort folders separately.
if (leftItem && rightItem && leftItem->GetData()->m_isFolder != rightItem->GetData()->m_isFolder)
{
// Sort folders before files.
return rightItem->GetData()->m_isFolder;
}
QVariant leftData = sourceModel()->data(left);
QVariant rightData = sourceModel()->data(right);
return rightData.toString() < leftData.toString();
}
void AssetTreeFilterModel::ForceModelIndexVisible(const QModelIndex& sourceIndex)
{
if (!sourceIndex.isValid())
{
return;
}
m_pathToForceVisibleAsset.clear();
for (AssetTreeItem* item = static_cast<AssetTreeItem*>(sourceIndex.internalPointer());
item != nullptr;
item = item->GetParent())
{
m_pathToForceVisibleAsset.push_front(item->GetData());
}
invalidateFilter();
}
}
@@ -0,0 +1,52 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/std/containers/list.h>
#include <QSortFilterProxyModel>
#endif
namespace AZ
{
struct Uuid;
}
namespace AssetProcessor
{
class AssetTreeItem;
class AssetTreeItemData;
class AssetTreeFilterModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
AssetTreeFilterModel(QObject* parent = nullptr);
void FilterChanged(const QString& newFilter);
// The asset trees have buttons to jump to related assets.
// If a search is active and one is clicked, force that asset to be visible.
// This index is to the source model, and not the proxy model.
void ForceModelIndexVisible(const QModelIndex& sourceIndex);
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override;
bool lessThan(const QModelIndex& left, const QModelIndex& right) const override;
bool DescendantMatchesFilter(const AssetTreeItem& assetTreeItem, const QRegExp& filter, const AZ::Uuid& filterAsUuid) const;
AZStd::list<AZStd::shared_ptr<AssetTreeItemData>> m_pathToForceVisibleAsset;
};
}
@@ -0,0 +1,169 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "AssetTreeItem.h"
#include <QApplication>
#include <QFileIconProvider>
#include <QStyle>
#include <QVariant>
namespace AssetProcessor
{
AssetTreeItemData::AssetTreeItemData(const AZStd::string& assetDbName, QString name, bool isFolder, const AZ::Uuid& uuid) :
m_assetDbName(assetDbName),
m_name(name),
m_isFolder(isFolder),
m_uuid(uuid)
{
QFileInfo fileInfo(name);
m_extension = fileInfo.completeSuffix();
}
AssetTreeItem::AssetTreeItem(
AZStd::shared_ptr<AssetTreeItemData> data,
QIcon errorIcon,
AssetTreeItem* parentItem) :
m_data(data),
m_parent(parentItem),
m_errorIcon(errorIcon), // QIcon is implicitily shared.
m_folderIcon(QIcon(QStringLiteral(":/Gallery/Asset_Folder.svg"))),
m_fileIcon(QIcon(QStringLiteral(":/Gallery/Asset_File.svg")))
{
m_folderIcon.addFile(QStringLiteral(":/Gallery/Asset_Folder.svg"), QSize(), QIcon::Selected);
}
AssetTreeItem::~AssetTreeItem()
{
}
AssetTreeItem* AssetTreeItem::CreateChild(AZStd::shared_ptr<AssetTreeItemData> data)
{
m_childItems.emplace_back(new AssetTreeItem(data, m_errorIcon, this));
return m_childItems.back().get();
}
AssetTreeItem* AssetTreeItem::GetChild(int row) const
{
if (row < 0 || row >= getChildCount())
{
return nullptr;
}
return m_childItems.at(row).get();
}
void AssetTreeItem::EraseChild(AssetTreeItem* child)
{
for (auto& item : m_childItems)
{
if (item.get() == child)
{
m_childItems.erase(&item);
break;
}
}
}
int AssetTreeItem::getChildCount() const
{
return static_cast<int>(m_childItems.size());
}
int AssetTreeItem::GetRow() const
{
if (m_parent)
{
int index = 0;
for (const auto& item : m_parent->m_childItems)
{
if (item.get() == this)
{
return index;
}
++index;
}
}
return 0;
}
int AssetTreeItem::GetColumnCount() const
{
return static_cast<int>(AssetTreeColumns::Max);
}
QVariant AssetTreeItem::GetDataForColumn(int column) const
{
if (column < 0 || column >= GetColumnCount() || !m_data)
{
return QVariant();
}
switch (column)
{
case static_cast<int>(AssetTreeColumns::Name):
return m_data->m_name;
case static_cast<int>(AssetTreeColumns::Extension):
if (m_data->m_isFolder)
{
return QVariant();
}
return m_data->m_extension;
default:
AZ_Warning("AssetProcessor", false, "Unhandled AssetTree column %d", column);
break;
}
return QVariant();
}
QIcon AssetTreeItem::GetIcon() const
{
if (!m_data)
{
return QIcon();
}
if (m_data->m_assetHasUnresolvedIssue)
{
return m_errorIcon;
}
if (m_data->m_isFolder)
{
return m_folderIcon;
}
else
{
return m_fileIcon;
}
}
AssetTreeItem* AssetTreeItem::GetParent() const
{
return m_parent;
}
AssetTreeItem* AssetTreeItem::GetChildFolder(QString folder) const
{
for (const auto& item : m_childItems)
{
if (!item->m_data ||
!item->m_data->m_isFolder)
{
continue;
}
if (item->m_data->m_name == folder)
{
return item.get();
}
}
return nullptr;
}
}
@@ -0,0 +1,83 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/string/string.h>
#include <QIcon>
#include <QString>
class QFileIconProvider;
class QVariant;
namespace AssetProcessor
{
class AssetTreeItemData
{
public:
AZ_RTTI(AssetTreeItemData, "{5660BA97-C4B0-4E3B-A03B-9ACD9C67841B}");
AssetTreeItemData(const AZStd::string& assetDbName, QString name, bool isFolder, const AZ::Uuid& uuid);
virtual ~AssetTreeItemData() {}
AZStd::string m_assetDbName;
QString m_name;
QString m_extension;
AZ::Uuid m_uuid;
bool m_isFolder = false;
bool m_assetHasUnresolvedIssue = false;
QString m_unresolvedIssuesTooltip;
};
enum class AssetTreeColumns
{
Name,
Extension,
Max
};
class AssetTreeItem
{
public:
explicit AssetTreeItem(
AZStd::shared_ptr<AssetTreeItemData> data,
QIcon errorIcon,
AssetTreeItem* parentItem = nullptr);
virtual ~AssetTreeItem();
AssetTreeItem* CreateChild(AZStd::shared_ptr<AssetTreeItemData> data);
AssetTreeItem* GetChild(int row) const;
void EraseChild(AssetTreeItem* child);
int getChildCount() const;
int GetColumnCount() const;
int GetRow() const;
QVariant GetDataForColumn(int column) const;
QIcon GetIcon() const;
AssetTreeItem* GetParent() const;
AssetTreeItem* GetChildFolder(QString folder) const;
AZStd::shared_ptr<AssetTreeItemData> GetData() const { return m_data; }
private:
AZStd::vector<AZStd::unique_ptr<AssetTreeItem>> m_childItems;
AZStd::shared_ptr<AssetTreeItemData> m_data;
AssetTreeItem* m_parent = nullptr;
QIcon m_errorIcon;
QIcon m_folderIcon;
QIcon m_fileIcon;
};
}
@@ -0,0 +1,233 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#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)
{
return createIndex(row, column, childItem);
}
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();
}
return createIndex(parentItem->GetRow(), 0, parentItem);
}
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;
}
}
@@ -0,0 +1,62 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AssetDatabase/AssetDatabase.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzToolsFramework/API/AssetDatabaseBus.h>
#include <native/utilities/ApplicationManagerAPI.h>
#include <QAbstractItemModel>
#include <QFileIconProvider>
#include <QItemSelectionModel>
namespace AssetProcessor
{
class AssetTreeItem;
class AssetTreeModel :
public QAbstractItemModel,
AzToolsFramework::AssetDatabase::AssetDatabaseNotificationBus::Handler,
AssetProcessor::ApplicationManagerNotifications::Bus::Handler
{
public:
AssetTreeModel(AZStd::shared_ptr<AzToolsFramework::AssetDatabase::AssetDatabaseConnection> sharedDbConnection, QObject *parent = nullptr);
virtual ~AssetTreeModel();
// ApplicationManagerNotifications::Bus::Handler
void ApplicationShutdownRequested() override;
// QAbstractItemModel
QModelIndex index(int row, int column, const QModelIndex &parent) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
QModelIndex parent(const QModelIndex &index) const override;
bool hasChildren(const QModelIndex &parent) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
void Reset();
static QFlags<QItemSelectionModel::SelectionFlag> GetAssetTreeSelectionFlags();
protected:
// Called by reset, while a Qt model reset is active.
virtual void ResetModel() = 0;
AZStd::unique_ptr<AssetTreeItem> m_root;
AZStd::shared_ptr<AzToolsFramework::AssetDatabase::AssetDatabaseConnection> m_sharedDbConnection;
QIcon m_errorIcon;
};
}
@@ -0,0 +1,103 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "ConnectionEditDialog.h"
#include <AzQtComponents/Components/Widgets/SpinBox.h>
#include "../connection/connectionManager.h"
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QLabel>
static QVariant dataAtColumn(const QModelIndex& index, int column)
{
QModelIndex columnIndex = index.sibling(index.row(), column);
return columnIndex.data(Qt::DisplayRole);
}
void setDataAtColumn(ConnectionManager* connectionManager, const QModelIndex& index, int column, const QVariant& data)
{
QModelIndex columnIndex = index.sibling(index.row(), column);
connectionManager->setData(columnIndex, data, Qt::DisplayRole);
}
template <typename WidgetType>
WidgetType* createGridRowWidget(QGridLayout* gridLayout, int gridRow, QDialog* parent, const QString& label)
{
QLabel* labelWidget = new QLabel(label, parent);
gridLayout->addWidget(labelWidget, gridRow, 0, Qt::AlignRight);
WidgetType* widget = new WidgetType(parent);
gridLayout->addWidget(widget, gridRow, 1, Qt::AlignLeft);
return widget;
}
ConnectionEditDialog::ConnectionEditDialog(ConnectionManager* connectionManager, const QModelIndex& connectionIndex, QWidget* parent)
: AzQtComponents::StyledDialog(parent)
, m_connectionManager(connectionManager)
, m_index(connectionIndex)
{
setWindowTitle("Edit Connection");
QVBoxLayout* layout = new QVBoxLayout(this);
layout->addSpacing(16);
QGridLayout* gridLayout = new QGridLayout(this);
int row = 0;
m_id = createGridRowWidget<QLineEdit>(gridLayout, row++, this, tr("ID"));
m_id->setPlaceholderText("Enter a name");
m_id->setText(dataAtColumn(connectionIndex, ConnectionManager::IdColumn).toString());
m_ipAddress = createGridRowWidget<QLineEdit>(gridLayout, row++, this, tr("IP Address"));
m_ipAddress->setPlaceholderText("Enter an IP address");
m_ipAddress->setText(dataAtColumn(connectionIndex, ConnectionManager::IpColumn).toString());
using namespace AzQtComponents;
m_port = createGridRowWidget<SpinBox>(gridLayout, row++, this, tr("Port"));
m_port->setMinimum(0);
m_port->setMaximum(std::numeric_limits<unsigned short>::max());
m_port->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
m_port->setValue(dataAtColumn(connectionIndex, ConnectionManager::PortColumn).toInt());
layout->addLayout(gridLayout);
layout->addSpacing(16);
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::StandardButtons(QDialogButtonBox::Ok) | QDialogButtonBox::Cancel, this);
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
layout->addWidget(buttons);
adjustSize();
}
void ConnectionEditDialog::accept()
{
// since this is a modal dialog, and since the user created indices should only
// be edited by the user, this should always work
Q_ASSERT(m_index.isValid());
if (m_index.isValid())
{
setDataAtColumn(m_connectionManager, m_index, ConnectionManager::IdColumn, m_id->text());
setDataAtColumn(m_connectionManager, m_index, ConnectionManager::IpColumn, m_ipAddress->text());
setDataAtColumn(m_connectionManager, m_index, ConnectionManager::PortColumn, m_port->value());
}
AzQtComponents::StyledDialog::accept();
}
@@ -0,0 +1,45 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QModelIndex>
#include <QPersistentModelIndex>
#include <AzQtComponents/Components/StyledDialog.h>
#endif
class ConnectionManager;
class QLineEdit;
namespace AzQtComponents
{
class SpinBox;
}
class ConnectionEditDialog : public AzQtComponents::StyledDialog
{
Q_OBJECT // AUTOMOC
public:
ConnectionEditDialog(ConnectionManager* connectionManager, const QModelIndex& connectionIndex, QWidget* parent = nullptr);
void accept() override;
private:
ConnectionManager* m_connectionManager;
QPersistentModelIndex m_index;
QLineEdit* m_id;
QLineEdit* m_ipAddress;
AzQtComponents::SpinBox* m_port;
};
@@ -0,0 +1,51 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "GoToButton.h"
#include <native/ui/ui_GoToButton.h>
namespace AssetProcessor
{
GoToButton::GoToButton(QWidget* parent) : QWidget(parent), m_ui(new Ui::GoToButton)
{
m_ui->setupUi(this);
m_ui->goToPushButton->installEventFilter(this);
}
GoToButton::~GoToButton()
{
}
bool GoToButton::eventFilter(QObject* watched, QEvent* event)
{
QPushButton* button = qobject_cast<QPushButton*>(watched);
if (!button)
{
return false;
}
if (event->type() == QEvent::Enter)
{
button->setIcon(QIcon(":/AssetProcessor_goto_hover.svg"));
return true;
}
else if (event->type() == QEvent::Leave)
{
button->setIcon(QIcon(":/AssetProcessor_goto.svg"));
return true;
}
return false;
}
}
@@ -0,0 +1,38 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QWidget>
#include <QScopedPointer>
#endif
namespace Ui
{
class GoToButton;
}
namespace AssetProcessor
{
class GoToButton
: public QWidget
{
Q_OBJECT
public:
explicit GoToButton(QWidget* parent = nullptr);
~GoToButton() override;
bool eventFilter(QObject* watched, QEvent* event) Q_DECL_OVERRIDE;
QScopedPointer<Ui::GoToButton> m_ui;
};
} // namespace AssetProcessor
@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>GoToButton</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>18</width>
<height>18</height>
</rect>
</property>
<widget class="QWidget" name="verticalLayoutWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>24</width>
<height>24</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="goToPushButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>18</width>
<height>18</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>18</width>
<height>18</height>
</size>
</property>
<property name="toolTip">
<string>View this asset</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>:/AssetProcessor_goto.svg</normaloff>
<activeon>:/AssetProcessor_goto_hover.svg</activeon>:/AssetProcessor_goto.svg
</iconset>
</property>
<property name="iconSize">
<size>
<width>18</width>
<height>18</height>
</size>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,225 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QMainWindow>
#include <QStringList>
#include <QStringListModel>
#include "native/utilities/LogPanel.h"
#include <QPointer>
#include "native/assetprocessor.h"
#include <AzQtComponents/Components/FilteredSearchWidget.h>
#include <QElapsedTimer>
#endif
namespace AzToolsFramework
{
namespace AssetDatabase
{
class AssetDatabaseConnection;
}
}
namespace Ui {
class MainWindow;
}
class GUIApplicationManager;
class QListWidgetItem;
class QFileSystemWatcher;
class QSettings;
namespace AssetProcessor
{
class AssetTreeFilterModel;
class JobSortFilterProxyModel;
class JobsModel;
class ProductAssetTreeModel;
class SourceAssetTreeModel;
class JobEntry;
}
class MainWindow
: public QMainWindow
{
Q_OBJECT
public:
// Tracks which asset tab the asset page is on.
enum class AssetTabIndex
{
Source = 0,
Product = 1
};
// This order is actually driven by the layout in the UI file.
// If the order is changed in the UI file, it should be changed here, too.
enum class DialogStackIndex
{
Jobs,
Assets,
Logs,
Shaders,
Connections,
Tools
};
struct Config
{
// These default values are used if the values can't be read from AssetProcessorConfig.ini,
// and the call to defaultConfig fails.
// Asset Status
int jobStatusColumnWidth = -1;
int jobSourceColumnWidth = -1;
int jobPlatformColumnWidth = -1;
int jobKeyColumnWidth = -1;
int jobCompletedColumnWidth = -1;
// Event Log Details
int logTypeColumnWidth = -1;
};
/*!
* Loads the button config data from a settings object.
*/
static Config loadConfig(QSettings& settings);
/*!
* Returns default button config data.
*/
static Config defaultConfig();
explicit MainWindow(GUIApplicationManager* guiApplicationManager, QWidget* parent = 0);
void Activate();
~MainWindow();
public Q_SLOTS:
void ShowWindow();
void SyncWhiteListAndRejectedList(QStringList whiteList, QStringList rejectedList);
void FirstTimeAddedToRejctedList(QString ipAddress);
void SaveLogPanelState();
void OnAssetProcessorStatusChanged(const AssetProcessor::AssetProcessorStatusEntry entry);
void OnRescanButtonClicked();
void HighlightAsset(QString assetPath);
void OnAssetTabChange(int index);
protected Q_SLOTS:
void ApplyConfig();
protected:
bool eventFilter(QObject* obj, QEvent* event) override;
private:
class LogSortFilterProxy : public QSortFilterProxyModel
{
public:
LogSortFilterProxy(QObject* parentOjbect);
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
void onTypeFilterChanged(const AzQtComponents::SearchTypeFilterList& activeTypeFilters);
private:
QSet<AzToolsFramework::Logging::LogLine::LogType> m_logTypes;
};
Ui::MainWindow* ui;
GUIApplicationManager* m_guiApplicationManager;
AzToolsFramework::Logging::LogTableModel* m_logsModel;
AssetProcessor::JobSortFilterProxyModel* m_jobSortFilterProxy;
LogSortFilterProxy* m_logSortFilterProxy;
AssetProcessor::JobsModel* m_jobsModel;
AssetProcessor::SourceAssetTreeModel* m_sourceModel = nullptr;
AssetProcessor::ProductAssetTreeModel* m_productModel = nullptr;
AssetProcessor::AssetTreeFilterModel* m_sourceAssetTreeFilterModel = nullptr;
AssetProcessor::AssetTreeFilterModel* m_productAssetTreeFilterModel = nullptr;
QPointer<AssetProcessor::LogPanel> m_loggingPanel;
int m_processJobsCount = 0;
int m_createJobCount = 0;
QFileSystemWatcher* m_fileSystemWatcher;
Config m_config;
void SetContextLogDetailsVisible(bool visible);
void SetContextLogDetails(const QMap<QString, QString>& details);
void ClearContextLogDetails();
void EditConnection(const QModelIndex& index);
void OnConnectionContextMenu(const QPoint& point);
void OnEditConnection(bool checked);
void OnAddConnection(bool checked);
void OnRemoveConnection(bool checked);
void OnSupportClicked(bool checked);
void OnConnectionSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
QStringListModel m_rejectedAddresses;
QStringListModel m_whitelistedAddresses;
void OnWhiteListedConnectionsListViewClicked();
void OnRejectedConnectionsListViewClicked();
void OnWhiteListCheckBoxToggled();
void OnAddHostNameWhiteListButtonClicked();
void OnAddIPWhiteListButtonClicked();
void OnToWhiteListButtonClicked();
void OnToRejectedListButtonClicked();
void UpdateJobLogView(QModelIndex selectedIndex);
void JobSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
void JobStatusChanged(AssetProcessor::JobEntry entry, AzToolsFramework::AssetSystem::JobStatus status);
void JobLogSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
void DesktopOpenJobLogs();
// Switches to the Job tab of the Asset Processor, clears any current searches, scroll to, and select the job at the given index.
void SelectJobAndMakeVisible(const QModelIndex& index);
void ResetLoggingPanel();
void ShowJobViewContextMenu(const QPoint& pos);
void ShowLogLineContextMenu(const QPoint& pos);
void ShowJobLogContextMenu(const QPoint& pos);
void ShowProductAssetContextMenu(const QPoint& pos);
void ShowSourceAssetContextMenu(const QPoint& pos);
void ResetTimers();
void CheckStartAnalysisTimers();
void CheckEndAnalysisTimer();
void CheckStartProcessTimers();
void CheckEndProcessTimer();
QString FormatStringTime(qint64 timeMs) const;
/// Refreshes the filter in the Asset Tab at a set time interval.
/// TreeView filters can be expensive to refresh every time an item is added, so refreshing on a set schedule
/// keeps the view up-to-date without causing a performance bottleneck.
void IntervalAssetTabFilterRefresh();
/// Fires off one final refresh before invalidating the filter refresh timer.
void ShutdownAssetTabFilterRefresh();
void SetupAssetSelectionCaching();
QElapsedTimer m_scanTimer;
QElapsedTimer m_analysisTimer;
QElapsedTimer m_processTimer;
QElapsedTimer m_filterRefreshTimer;
qint64 m_scanTime{ 0 };
qint64 m_analysisTime{ 0 };
qint64 m_processTime{ 0 };
AZStd::shared_ptr<AzToolsFramework::AssetDatabase::AssetDatabaseConnection> m_sharedDbConnection;
AZStd::string m_cachedSourceAssetSelection;
AZStd::string m_cachedProductAssetSelection;
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,643 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "ProductAssetDetailsPanel.h"
#include "AssetTreeFilterModel.h"
#include "ProductAssetTreeItemData.h"
#include "native/utilities/assetUtils.h"
#include "native/utilities/MissingDependencyScanner.h"
#include <AssetDatabase/AssetDatabase.h>
#include <AzToolsFramework/API/AssetDatabaseBus.h>
#include <native/ui/ui_GoToButton.h>
#include <native/ui/ui_ProductAssetDetailsPanel.h>
#include <QDateTime>
#include <QDesktopServices>
#include <QDir>
#include <QStringLiteral>
#include <QUrl>
namespace AssetProcessor
{
ProductAssetDetailsPanel::ProductAssetDetailsPanel(QWidget* parent) : AssetDetailsPanel(parent), m_ui(new Ui::ProductAssetDetailsPanel)
{
m_ui->setupUi(this);
m_ui->scrollAreaWidgetContents->setLayout(m_ui->scrollableVerticalLayout);
m_ui->MissingProductDependenciesTable->setColumnWidth(1, 160);
ResetText();
connect(m_ui->MissingProductDependenciesSupport, &QPushButton::clicked, this, &ProductAssetDetailsPanel::OnSupportClicked);
connect(m_ui->ScanMissingDependenciesButton, &QPushButton::clicked, this, &ProductAssetDetailsPanel::OnScanFileClicked);
connect(m_ui->ScanFolderButton, &QPushButton::clicked, this, &ProductAssetDetailsPanel::OnScanFolderClicked);
connect(m_ui->ClearMissingDependenciesButton, &QPushButton::clicked, this, &ProductAssetDetailsPanel::OnClearScanFileClicked);
connect(m_ui->ClearScanFolderButton, &QPushButton::clicked, this, &ProductAssetDetailsPanel::OnClearScanFolderClicked);
}
ProductAssetDetailsPanel::~ProductAssetDetailsPanel()
{
}
void ProductAssetDetailsPanel::SetScanQueueEnabled(bool enabled)
{
// Don't change state if it's already the same.
if (m_ui->ScanMissingDependenciesButton->isEnabled() == enabled)
{
return;
}
m_ui->ScanMissingDependenciesButton->setEnabled(enabled);
m_ui->ScanFolderButton->setEnabled(enabled);
if (enabled)
{
m_ui->ScanMissingDependenciesButton->setToolTip(tr("Scans this file for missing dependencies. This may take some time."));
m_ui->ScanFolderButton->setToolTip(tr("Scans all files in this folder and subfolders for missing dependencies. This may take some time."));
}
else
{
QString disabledTooltip(tr("Scanning disabled until asset processing completes."));
m_ui->ScanMissingDependenciesButton->setToolTip(disabledTooltip);
m_ui->ScanFolderButton->setToolTip(disabledTooltip);
}
}
void ProductAssetDetailsPanel::AssetDataSelectionChanged(const QItemSelection& selected, const QItemSelection& /*deselected*/)
{
// Even if multi-select is enabled, only display the first selected item.
if (selected.indexes().count() == 0 || !selected.indexes()[0].isValid())
{
ResetText();
return;
}
QModelIndex productModelIndex = m_productFilterModel->mapToSource(selected.indexes()[0]);
if (!productModelIndex.isValid())
{
return;
}
m_currentItem = static_cast<AssetTreeItem*>(productModelIndex.internalPointer());
RefreshUI();
}
void ProductAssetDetailsPanel::RefreshUI()
{
const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData = AZStd::rtti_pointer_cast<const ProductAssetTreeItemData>(m_currentItem->GetData());
m_ui->assetNameLabel->setText(m_currentItem->GetData()->m_name);
if (m_currentItem->GetData()->m_isFolder || !productItemData)
{
// Folders don't have details.
SetDetailsVisible(false);
return;
}
SetDetailsVisible(true);
AZ::Data::AssetId assetId;
m_assetDatabaseConnection->QuerySourceByProductID(
productItemData->m_databaseInfo.m_productID,
[&](AzToolsFramework::AssetDatabase::SourceDatabaseEntry& sourceEntry)
{
assetId = AZ::Data::AssetId(sourceEntry.m_sourceGuid, productItemData->m_databaseInfo.m_subID);
// Use a decimal value to display the sub ID and not hex. Lumberyard is not consistent about
// how sub IDs are displayed, so it's important to double check what format a sub ID is in before using it elsewhere.
m_ui->productAssetIdValueLabel->setText(assetId.ToString<AZStd::string>(AZ::Data::AssetId::SubIdDisplayType::Decimal).c_str());
// Make sure this is the only connection to the button.
m_ui->gotoAssetButton->m_ui->goToPushButton->disconnect();
connect(m_ui->gotoAssetButton->m_ui->goToPushButton, &QPushButton::clicked, [=] {
GoToSource(sourceEntry.m_sourceName);
});
m_ui->sourceAssetValueLabel->setText(sourceEntry.m_sourceName.c_str());
return true;
});
AZStd::string platform;
m_assetDatabaseConnection->QueryJobByProductID(
productItemData->m_databaseInfo.m_productID,
[&](AzToolsFramework::AssetDatabase::JobDatabaseEntry& jobEntry)
{
QDateTime lastTimeProcessed = QDateTime::fromMSecsSinceEpoch(jobEntry.m_lastLogTime);
m_ui->lastTimeProcessedValueLabel->setText(lastTimeProcessed.toString());
m_ui->jobKeyValueLabel->setText(jobEntry.m_jobKey.c_str());
platform = jobEntry.m_platform;
m_ui->platformValueLabel->setText(jobEntry.m_platform.c_str());
return true;
});
BuildOutgoingProductDependencies(productItemData, platform);
BuildIncomingProductDependencies(productItemData, assetId, platform);
BuildMissingProductDependencies(productItemData);
}
void ProductAssetDetailsPanel::BuildOutgoingProductDependencies(
const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData,
const AZStd::string& platform)
{
// Clear & ClearContents leave the table dimensions the same, so set rowCount to zero to reset it.
m_ui->outgoingProductDependenciesTable->setRowCount(0);
m_ui->outgoingUnmetPathProductDependenciesList->clear();
int productDependencyCount = 0;
int productPathDependencyCount = 0;
m_assetDatabaseConnection->QueryProductDependencyByProductId(
productItemData->m_databaseInfo.m_productID,
[&](AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry& dependency)
{
if (!dependency.m_dependencySourceGuid.IsNull())
{
m_assetDatabaseConnection->QueryProductBySourceGuidSubID(
dependency.m_dependencySourceGuid,
dependency.m_dependencySubID,
[&](AzToolsFramework::AssetDatabase::ProductDatabaseEntry& product)
{
bool platformMatches = false;
m_assetDatabaseConnection->QueryJobByJobID(
product.m_jobPK,
[&](AzToolsFramework::AssetDatabase::JobDatabaseEntry& jobEntry)
{
if (platform.compare(jobEntry.m_platform) == 0)
{
platformMatches = true;
}
return true;
});
if (platformMatches)
{
m_ui->outgoingProductDependenciesTable->insertRow(productDependencyCount);
// Qt handles cleanup automatically, setting this as the parent means
// when this panel is torn down, these widgets will be destroyed.
GoToButton* rowGoToButton = new GoToButton(this);
connect(rowGoToButton->m_ui->goToPushButton, &QPushButton::clicked, [=] {
GoToProduct(product.m_productName);
});
m_ui->outgoingProductDependenciesTable->setCellWidget(productDependencyCount, 0, rowGoToButton);
QTableWidgetItem* rowName = new QTableWidgetItem(product.m_productName.c_str());
m_ui->outgoingProductDependenciesTable->setItem(productDependencyCount, 1, rowName);
++productDependencyCount;
}
return true;
});
}
// If there is both a path and an asset ID on this dependency, then something has gone wrong.
// Other tooling should have reported this error. In the UI, show both the asset ID and path.
if (!dependency.m_unresolvedPath.empty())
{
QListWidgetItem* listWidgetItem = new QListWidgetItem();
listWidgetItem->setText(dependency.m_unresolvedPath.c_str());
m_ui->outgoingUnmetPathProductDependenciesList->addItem(listWidgetItem);
++productPathDependencyCount;
}
return true;
});
m_ui->outgoingProductDependenciesValueLabel->setText(QString::number(productDependencyCount));
m_ui->outgoingUnmetPathProductDependenciesValueLabel->setText(QString::number(productPathDependencyCount));
if (productDependencyCount == 0)
{
m_ui->outgoingProductDependenciesTable->insertRow(productDependencyCount);
QTableWidgetItem* rowName = new QTableWidgetItem(tr("No product dependencies"));
m_ui->outgoingProductDependenciesTable->setItem(productDependencyCount, 1, rowName);
++productDependencyCount;
}
if (productPathDependencyCount == 0)
{
QListWidgetItem* listWidgetItem = new QListWidgetItem();
listWidgetItem->setText(tr("No unmet dependencies"));
m_ui->outgoingUnmetPathProductDependenciesList->addItem(listWidgetItem);
++productPathDependencyCount;
}
m_ui->outgoingProductDependenciesTable->setMinimumHeight(m_ui->outgoingProductDependenciesTable->rowHeight(0) * productDependencyCount + 2 * m_ui->outgoingProductDependenciesTable->frameWidth());
m_ui->outgoingProductDependenciesTable->adjustSize();
m_ui->outgoingUnmetPathProductDependenciesList->setMinimumHeight(m_ui->outgoingUnmetPathProductDependenciesList->sizeHintForRow(0) * productPathDependencyCount + 2 * m_ui->outgoingUnmetPathProductDependenciesList->frameWidth());
m_ui->outgoingUnmetPathProductDependenciesList->adjustSize();
}
void ProductAssetDetailsPanel::BuildIncomingProductDependencies(
const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData,
const AZ::Data::AssetId& assetId,
const AZStd::string& platform)
{
// Clear & ClearContents leave the table dimensions the same, so set rowCount to zero to reset it.
m_ui->incomingProductDependenciesTable->setRowCount(0);
int incomingProductDependencyCount = 0;
m_assetDatabaseConnection->QueryDirectReverseProductDependenciesBySourceGuidSubId(
assetId.m_guid,
assetId.m_subId,
[&](AzToolsFramework::AssetDatabase::ProductDatabaseEntry& incomingDependency)
{
bool platformMatches = false;
m_assetDatabaseConnection->QueryJobByJobID(
incomingDependency.m_jobPK,
[&](AzToolsFramework::AssetDatabase::JobDatabaseEntry& jobEntry)
{
if (platform.compare(jobEntry.m_platform) == 0)
{
platformMatches = true;
}
return true;
});
if (platformMatches)
{
m_ui->incomingProductDependenciesTable->insertRow(incomingProductDependencyCount);
GoToButton* rowGoToButton = new GoToButton(this);
connect(rowGoToButton->m_ui->goToPushButton, &QPushButton::clicked, [=] {
GoToProduct(incomingDependency.m_productName);
});
m_ui->incomingProductDependenciesTable->setCellWidget(incomingProductDependencyCount, 0, rowGoToButton);
QTableWidgetItem* rowName = new QTableWidgetItem(incomingDependency.m_productName.c_str());
m_ui->incomingProductDependenciesTable->setItem(incomingProductDependencyCount, 1, rowName);
++incomingProductDependencyCount;
}
return true;
});
m_ui->incomingProductDependenciesValueLabel->setText(QString::number(incomingProductDependencyCount));
if (incomingProductDependencyCount == 0)
{
m_ui->incomingProductDependenciesTable->insertRow(incomingProductDependencyCount);
QTableWidgetItem* rowName = new QTableWidgetItem(tr("No incoming product dependencies"));
m_ui->incomingProductDependenciesTable->setItem(incomingProductDependencyCount, 1, rowName);
++incomingProductDependencyCount;
}
m_ui->incomingProductDependenciesTable->setMinimumHeight(m_ui->incomingProductDependenciesTable->rowHeight(0) * incomingProductDependencyCount + 2 * m_ui->incomingProductDependenciesTable->frameWidth());
m_ui->incomingProductDependenciesTable->adjustSize();
}
struct MissingDependencyTableInfo
{
AzToolsFramework::AssetDatabase::MissingProductDependencyDatabaseEntry m_databaseEntry;
AZStd::string m_missingProductName;
};
void ProductAssetDetailsPanel::BuildMissingProductDependencies(
const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData)
{
// Clear & ClearContents leave the table dimensions the same, so set rowCount to zero to reset it.
m_ui->MissingProductDependenciesTable->setRowCount(0);
int missingDependencyRowCount = 0;
int missingDependencyCount = 0;
// Sort missing dependencies by scan time.
AZStd::vector<MissingDependencyTableInfo> missingDependenciesByScanTime;
m_assetDatabaseConnection->QueryMissingProductDependencyByProductId(
productItemData->m_databaseInfo.m_productID,
[&](AzToolsFramework::AssetDatabase::MissingProductDependencyDatabaseEntry& missingDependency)
{
AZStd::string missingProductName;
m_assetDatabaseConnection->QueryProductBySourceGuidSubID(
missingDependency.m_dependencySourceGuid,
missingDependency.m_dependencySubId,
[&](AzToolsFramework::AssetDatabase::ProductDatabaseEntry& missingProduct)
{
missingProductName = missingProduct.m_productName;
return false; // There should only be one matching product, stop looking.
});
AZStd::vector<MissingDependencyTableInfo>::iterator insertPosition = AZStd::upper_bound(
missingDependenciesByScanTime.begin(),
missingDependenciesByScanTime.end(),
missingDependency.m_scanTimeSecondsSinceEpoch,
[](AZ::u64 left, const MissingDependencyTableInfo& right) {
return left > right.m_databaseEntry.m_scanTimeSecondsSinceEpoch;
});
MissingDependencyTableInfo missingDependencyInfo;
missingDependencyInfo.m_databaseEntry = missingDependency;
missingDependencyInfo.m_missingProductName = missingProductName;
missingDependenciesByScanTime.insert(insertPosition, missingDependencyInfo);
return true;
});
bool hasMissingDependency = false;
for (const auto& missingDependency : missingDependenciesByScanTime)
{
m_ui->MissingProductDependenciesTable->insertRow(missingDependencyRowCount);
// To track if files have been scanned at all, rows with invalid source guids are added on a
// scan that had no missing dependencies. Don't show a button for those rows.
if (!missingDependency.m_databaseEntry.m_dependencySourceGuid.IsNull())
{
hasMissingDependency = true;
++missingDependencyCount;
GoToButton* rowGoToButton = new GoToButton(this);
connect(rowGoToButton->m_ui->goToPushButton, &QPushButton::clicked, [&, missingDependency] {
GoToProduct(missingDependency.m_missingProductName);
});
m_ui->MissingProductDependenciesTable->setCellWidget(missingDependencyRowCount, 0, rowGoToButton);
}
QTableWidgetItem* scanTime = new QTableWidgetItem(missingDependency.m_databaseEntry.m_lastScanTime.c_str());
m_ui->MissingProductDependenciesTable->setItem(missingDependencyRowCount, 1, scanTime);
QTableWidgetItem* rowName = new QTableWidgetItem(missingDependency.m_databaseEntry.m_missingDependencyString.c_str());
m_ui->MissingProductDependenciesTable->setItem(missingDependencyRowCount, 2, rowName);
++missingDependencyRowCount;
}
m_ui->MissingProductDependenciesValueLabel->setText(QString::number(missingDependencyCount));
if (missingDependencyRowCount == 0)
{
m_ui->MissingProductDependenciesTable->insertRow(missingDependencyRowCount);
QTableWidgetItem* rowName = new QTableWidgetItem(tr("File has not been scanned."));
m_ui->MissingProductDependenciesTable->setItem(missingDependencyRowCount, 1, rowName);
++missingDependencyRowCount;
}
else
{
m_ui->missingDependencyErrorIcon->setVisible(hasMissingDependency);
}
m_ui->MissingProductDependenciesTable->setMinimumHeight(m_ui->MissingProductDependenciesTable->rowHeight(0) * missingDependencyRowCount + 2 * m_ui->MissingProductDependenciesTable->frameWidth());
m_ui->MissingProductDependenciesTable->adjustSize();
}
void ProductAssetDetailsPanel::ResetText()
{
m_ui->assetNameLabel->setText(tr("Select an asset to see details"));
SetDetailsVisible(false);
}
void ProductAssetDetailsPanel::SetDetailsVisible(bool visible)
{
// The folder selected description has opposite visibility from everything else.
m_ui->folderSelectedDescription->setVisible(!visible);
m_ui->ScanFolderButton->setVisible(!visible);
m_ui->ClearScanFolderButton->setVisible(!visible);
m_ui->MissingProductDependenciesFolderTitleLabel->setVisible(!visible);
m_ui->productAssetIdTitleLabel->setVisible(visible);
m_ui->productAssetIdValueLabel->setVisible(visible);
m_ui->lastTimeProcessedTitleLabel->setVisible(visible);
m_ui->lastTimeProcessedValueLabel->setVisible(visible);
m_ui->jobKeyTitleLabel->setVisible(visible);
m_ui->jobKeyValueLabel->setVisible(visible);
m_ui->platformTitleLabel->setVisible(visible);
m_ui->platformValueLabel->setVisible(visible);
m_ui->sourceAssetTitleLabel->setVisible(visible);
m_ui->sourceAssetValueLabel->setVisible(visible);
m_ui->gotoAssetButton->setVisible(visible);
m_ui->outgoingProductDependenciesTitleLabel->setVisible(visible);
m_ui->outgoingProductDependenciesValueLabel->setVisible(visible);
m_ui->outgoingProductDependenciesTable->setVisible(visible);
m_ui->outgoingUnmetPathProductDependenciesTitleLabel->setVisible(visible);
m_ui->outgoingUnmetPathProductDependenciesValueLabel->setVisible(visible);
m_ui->outgoingUnmetPathProductDependenciesList->setVisible(visible);
m_ui->incomingProductDependenciesTitleLabel->setVisible(visible);
m_ui->incomingProductDependenciesValueLabel->setVisible(visible);
m_ui->incomingProductDependenciesTable->setVisible(visible);
m_ui->MissingProductDependenciesTitleLabel->setVisible(visible);
m_ui->MissingProductDependenciesValueLabel->setVisible(visible);
m_ui->MissingProductDependenciesTable->setVisible(visible);
m_ui->MissingProductDependenciesSupport->setVisible(visible);
m_ui->ScanMissingDependenciesButton->setVisible(visible);
m_ui->ClearMissingDependenciesButton->setVisible(visible);
m_ui->DependencySeparatorLine->setVisible(visible);
m_ui->missingDependencyErrorIcon->setVisible(false);
}
void ProductAssetDetailsPanel::OnSupportClicked(bool /*checked*/)
{
QDesktopServices::openUrl(
QStringLiteral("https://docs.aws.amazon.com/lumberyard/latest/userguide/asset-bundler-assets-resolving.html"));
}
void ProductAssetDetailsPanel::OnScanFileClicked(bool /*checked*/)
{
const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData = AZStd::rtti_pointer_cast<const ProductAssetTreeItemData>(m_currentItem->GetData());
ScanFileForMissingDependencies(productItemData->m_name, productItemData);
}
void ProductAssetDetailsPanel::ScanFileForMissingDependencies(QString scanName, const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData)
{
// If the file is already in the queue to scan, don't add it.
if (m_productIdToScanName.contains(productItemData->m_databaseInfo.m_productID))
{
return;
}
AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntryContainer existingDependencies;
m_assetDatabaseConnection->QueryProductDependencyByProductId(
productItemData->m_databaseInfo.m_productID,
[&](AzToolsFramework::AssetDatabase::ProductDependencyDatabaseEntry& entry)
{
existingDependencies.push_back();
existingDependencies.back() = AZStd::move(entry);
return true; // return true to keep iterating over further rows.
});
QDir cacheRootDir;
AssetUtilities::ComputeProjectCacheRoot(cacheRootDir);
QString pathOnDisk = cacheRootDir.filePath(productItemData->m_databaseInfo.m_productName.c_str());
AddProductIdToScanCount(productItemData->m_databaseInfo.m_productID, scanName);
// Run the scan on another thread so the UI remains responsive.
AZStd::thread scanningThread = AZStd::thread([=]() {
MissingDependencyScannerRequestBus::Broadcast(&MissingDependencyScannerRequestBus::Events::ScanFile,
pathOnDisk.toUtf8().constData(),
MissingDependencyScanner::DefaultMaxScanIteration,
productItemData->m_databaseInfo.m_productID,
existingDependencies,
m_assetDatabaseConnection,
/*queueDbCommandsOnMainThread*/ true,
[=](AZStd::string /*relativeDependencyFilePath*/) {
RemoveProductIdFromScanCount(productItemData->m_databaseInfo.m_productID, scanName);
// The MissingDependencyScannerRequestBus callback always runs on the main thread, so no need to queue again.
AzToolsFramework::AssetDatabase::AssetDatabaseNotificationBus::Broadcast(
&AzToolsFramework::AssetDatabase::AssetDatabaseNotificationBus::Events::OnProductFileChanged, productItemData->m_databaseInfo);
if (m_currentItem)
{
// Refresh the UI if the scan that just finished is selected.
const AZStd::shared_ptr<const ProductAssetTreeItemData> currentItemData = AZStd::rtti_pointer_cast<const ProductAssetTreeItemData>(m_currentItem->GetData());
if (currentItemData == productItemData)
{
RefreshUI();
}
}
});
});
scanningThread.detach();
}
void ProductAssetDetailsPanel::AddProductIdToScanCount(AZ::s64 scannedProductId, QString scanName)
{
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_scanCountMutex);
m_productIdToScanName.insert(scannedProductId, scanName);
QHash<QString, MissingDependencyScanGUIInfo>::iterator scanNameIter = m_scanNameToScanGUIInfo.find(scanName);
if (scanNameIter == m_scanNameToScanGUIInfo.end())
{
MissingDependencyScanGUIInfo scanGUIInfo;
scanGUIInfo.m_scanWidgetRow = new QListWidgetItem();
scanGUIInfo.m_scanTimeStart = QDateTime::currentDateTime();
if (m_missingDependencyScanResults)
{
m_missingDependencyScanResults->addItem(scanGUIInfo.m_scanWidgetRow);
// New items are added to the bottom, scroll to them when they are added.
m_missingDependencyScanResults->scrollToBottom();
}
scanNameIter = m_scanNameToScanGUIInfo.insert(scanName, scanGUIInfo);
}
// Update the remaining file count for this scan.
scanNameIter.value().m_remainingFiles++;
UpdateScannerUI(scanNameIter.value(), scanName);
}
void ProductAssetDetailsPanel::RemoveProductIdFromScanCount(AZ::s64 scannedProductId, QString scanName)
{
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_scanCountMutex);
m_productIdToScanName.remove(scannedProductId);
QHash<QString, MissingDependencyScanGUIInfo>::iterator scanNameIter = m_scanNameToScanGUIInfo.find(scanName);
if (scanNameIter != m_scanNameToScanGUIInfo.end())
{
// Update the remaining file count for this scan.
scanNameIter.value().m_remainingFiles--;
UpdateScannerUI(scanNameIter.value(), scanName);
if (scanNameIter.value().m_remainingFiles <= 0)
{
m_scanNameToScanGUIInfo.remove(scanName);
}
}
}
void ProductAssetDetailsPanel::UpdateScannerUI(MissingDependencyScanGUIInfo& scannerUIInfo, QString scanName)
{
if (scannerUIInfo.m_scanWidgetRow == nullptr)
{
return;
}
if (scannerUIInfo.m_remainingFiles == 0)
{
qint64 scanTimeInSeconds = scannerUIInfo.m_scanTimeStart.secsTo(QDateTime::currentDateTime());
scannerUIInfo.m_scanWidgetRow->setText(tr("Completed scanning %1 in %2 seconds").
arg(scanName).
arg(scanTimeInSeconds));
}
else
{
scannerUIInfo.m_scanWidgetRow->setText(tr("%1: Scanning %2 files for %3").
arg(QLocale::system().toString(scannerUIInfo.m_scanTimeStart, QLocale::ShortFormat)).
arg(scannerUIInfo.m_remainingFiles).
arg(scanName));
}
}
void ProductAssetDetailsPanel::OnScanFolderClicked(bool /*checked*/)
{
if (!m_currentItem)
{
return;
}
ScanFolderForMissingDependencies(m_currentItem->GetData()->m_name, *m_currentItem);
}
void ProductAssetDetailsPanel::ScanFolderForMissingDependencies(QString scanName, AssetTreeItem& folder)
{
for (int childIndex = 0; childIndex < folder.getChildCount(); ++childIndex)
{
AssetTreeItem* child = folder.GetChild(childIndex);
if (child->GetData()->m_isFolder)
{
ScanFolderForMissingDependencies(scanName, *child);
}
else
{
const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData = AZStd::rtti_pointer_cast<const ProductAssetTreeItemData>(child->GetData());
ScanFileForMissingDependencies(scanName, productItemData);
}
}
}
void ProductAssetDetailsPanel::OnClearScanFileClicked(bool /*checked*/)
{
const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData = AZStd::rtti_pointer_cast<const ProductAssetTreeItemData>(m_currentItem->GetData());
ClearMissingDependenciesForFile(productItemData);
}
void ProductAssetDetailsPanel::OnClearScanFolderClicked(bool /*checked*/)
{
if (!m_currentItem)
{
return;
}
ClearMissingDependenciesForFolder(*m_currentItem);
}
void ProductAssetDetailsPanel::ClearMissingDependenciesForFile(const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData)
{
m_assetDatabaseConnection->DeleteMissingProductDependencyByProductId(productItemData->m_databaseInfo.m_productID);
AzToolsFramework::AssetDatabase::AssetDatabaseNotificationBus::Broadcast(
&AzToolsFramework::AssetDatabase::AssetDatabaseNotificationBus::Events::OnProductFileChanged, productItemData->m_databaseInfo);
const AZStd::shared_ptr<const ProductAssetTreeItemData> currentItemData = AZStd::rtti_pointer_cast<const ProductAssetTreeItemData>(m_currentItem->GetData());
if (currentItemData == productItemData)
{
RefreshUI();
}
}
void ProductAssetDetailsPanel::ClearMissingDependenciesForFolder(AssetTreeItem& folder)
{
for (int childIndex = 0; childIndex < folder.getChildCount(); ++childIndex)
{
AssetTreeItem* child = folder.GetChild(childIndex);
if (child->GetData()->m_isFolder)
{
ClearMissingDependenciesForFolder(*child);
}
else
{
const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData = AZStd::rtti_pointer_cast<const ProductAssetTreeItemData>(child->GetData());
ClearMissingDependenciesForFile(productItemData);
}
}
}
}
@@ -0,0 +1,118 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include "AssetDetailsPanel.h"
#include <AzCore/std/parallel/mutex.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <QDateTime>
#include <QHash>
#include <QScopedPointer>
#endif
class QItemSelection;
class QLabel;
class QListWidget;
class QListWidgetItem;
namespace AZ
{
namespace Data
{
struct AssetId;
}
}
namespace Ui
{
class ProductAssetDetailsPanel;
}
namespace AssetProcessor
{
class AssetTreeItem;
class AssetDatabaseConnection;
class ProductAssetTreeItemData;
class ProductAssetDetailsPanel
: public AssetDetailsPanel
{
Q_OBJECT
public:
explicit ProductAssetDetailsPanel(QWidget* parent = nullptr);
~ProductAssetDetailsPanel() override;
// The scan results widget is in a separate section of the UI, but updates when scans are added / completed.
void SetScannerInformation(QListWidget* missingDependencyScanResults, AZStd::shared_ptr<AssetDatabaseConnection> assetDatabaseConnection)
{
m_missingDependencyScanResults = missingDependencyScanResults;
m_assetDatabaseConnection = assetDatabaseConnection;
}
void SetScanQueueEnabled(bool enabled);
public Q_SLOTS:
void AssetDataSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
protected:
struct MissingDependencyScanGUIInfo
{
QListWidgetItem* m_scanWidgetRow = nullptr;
size_t m_remainingFiles = 0;
QDateTime m_scanTimeStart;
};
void ResetText();
void SetDetailsVisible(bool visible);
void OnSupportClicked(bool checked);
void OnScanFileClicked(bool checked);
void OnScanFolderClicked(bool checked);
void OnClearScanFileClicked(bool checked);
void OnClearScanFolderClicked(bool checked);
void ScanFolderForMissingDependencies(QString scanName, AssetTreeItem& folder);
void ScanFileForMissingDependencies(QString scanName, const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData);
void ClearMissingDependenciesForFile(const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData);
void ClearMissingDependenciesForFolder(AssetTreeItem& folder);
void RefreshUI();
void BuildOutgoingProductDependencies(
const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData,
const AZStd::string& platform);
void BuildIncomingProductDependencies(
const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData,
const AZ::Data::AssetId& assetId,
const AZStd::string& platform);
void BuildMissingProductDependencies(
const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData);
void AddProductIdToScanCount(AZ::s64 scannedProductId, QString scanName);
void RemoveProductIdFromScanCount(AZ::s64 scannedProductId, QString scanName);
void UpdateScannerUI(MissingDependencyScanGUIInfo& scannerUIInfo, QString scanName);
QScopedPointer<Ui::ProductAssetDetailsPanel> m_ui;
AssetTreeItem* m_currentItem = nullptr;
// Track how many files are being scanned in the UI.
QHash<AZ::s64, QString> m_productIdToScanName;
QHash<QString, MissingDependencyScanGUIInfo> m_scanNameToScanGUIInfo;
mutable AZStd::recursive_mutex m_scanCountMutex;
QListWidget* m_missingDependencyScanResults = nullptr;
// The asset database connection in the AzToolsFramework namespace is read only. The AssetProcessor connection allows writing.
AZStd::shared_ptr<AssetDatabaseConnection> m_assetDatabaseConnection;
};
} // namespace AssetProcessor
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,84 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "ProductAssetTreeItemData.h"
#include "native/utilities/assetUtils.h"
#include <AzCore/std/smart_ptr/make_shared.h>
AZ_PUSH_DISABLE_WARNING(4127 4251 4800 4244, "-Wunknown-warning-option")
#include <QDir>
#include <QStack>
AZ_POP_DISABLE_WARNING
namespace AssetProcessor
{
AZStd::shared_ptr<ProductAssetTreeItemData> ProductAssetTreeItemData::MakeShared(const AzToolsFramework::AssetDatabase::ProductDatabaseEntry* databaseInfo, const AZStd::string& assetDbName, QString name, bool isFolder, const AZ::Uuid& uuid)
{
return AZStd::make_shared<ProductAssetTreeItemData>(databaseInfo, assetDbName, name, isFolder, uuid);
}
ProductAssetTreeItemData::ProductAssetTreeItemData(
const AzToolsFramework::AssetDatabase::ProductDatabaseEntry* databaseInfo,
const AZStd::string& assetDbName,
QString name,
bool isFolder,
const AZ::Uuid& uuid) :
AssetTreeItemData(assetDbName, name, isFolder, uuid)
{
if (databaseInfo)
{
m_hasDatabaseInfo = true;
m_databaseInfo = *databaseInfo;
}
else
{
m_hasDatabaseInfo = false;
}
}
AZ::Outcome<QString> GetAbsolutePathToProduct(const AssetTreeItem& product)
{
QDir cacheRootDir;
if (!AssetUtilities::ComputeProjectCacheRoot(cacheRootDir))
{
return AZ::Failure();
}
QString pathOnDisk;
if (product.getChildCount() > 0)
{
// Folders are special case, they only exist in the interface and don't exist in the asset database.
// Figure out the path to the folder by creating a stack of each folder in its hierarchy.
QStack<QString> folderStack;
for (const AssetTreeItem* folderHierarchy = &product; folderHierarchy != nullptr; folderHierarchy = folderHierarchy->GetParent())
{
folderStack.push(folderHierarchy->GetData()->m_name);
}
while (!folderStack.empty())
{
cacheRootDir.cd(folderStack.pop());
}
pathOnDisk = cacheRootDir.absolutePath();
}
else
{
const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData = AZStd::rtti_pointer_cast<const ProductAssetTreeItemData>(product.GetData());
if (!productItemData)
{
return AZ::Failure();
}
pathOnDisk = cacheRootDir.filePath(productItemData->m_databaseInfo.m_productName.c_str());
}
return AZ::Success(pathOnDisk);
}
}
@@ -0,0 +1,41 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include "AssetTreeItem.h"
#include <AzCore/Outcome/Outcome.h>
#include <AzToolsFramework/AssetDatabase/AssetDatabaseConnection.h>
namespace AZ
{
struct Uuid;
}
namespace AssetProcessor
{
class ProductAssetTreeItemData : public AssetTreeItemData
{
public:
AZ_RTTI(ProductAssetTreeItemData, "{6DEFC394-98A3-4EEA-9419-E8F51F447862}", AssetTreeItemData);
static AZStd::shared_ptr<ProductAssetTreeItemData> MakeShared(const AzToolsFramework::AssetDatabase::ProductDatabaseEntry* databaseInfo, const AZStd::string& assetDbName, QString name, bool isFolder, const AZ::Uuid& uuid);
ProductAssetTreeItemData(const AzToolsFramework::AssetDatabase::ProductDatabaseEntry* databaseInfo, const AZStd::string& assetDbName, QString name, bool isFolder, const AZ::Uuid& uuid);
~ProductAssetTreeItemData() override {}
AzToolsFramework::AssetDatabase::ProductDatabaseEntry m_databaseInfo;
bool m_hasDatabaseInfo = false;
};
AZ::Outcome<QString> GetAbsolutePathToProduct(const AssetTreeItem& product);
} // AssetProcessor
@@ -0,0 +1,253 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "ProductAssetTreeModel.h"
#include "ProductAssetTreeItemData.h"
#include <AzCore/Component/TickBus.h>
#include <AzFramework/StringFunc/StringFunc.h>
namespace AssetProcessor
{
ProductAssetTreeModel::ProductAssetTreeModel(AZStd::shared_ptr<AzToolsFramework::AssetDatabase::AssetDatabaseConnection> sharedDbConnection, QObject *parent) :
AssetTreeModel(sharedDbConnection, parent)
{
}
ProductAssetTreeModel::~ProductAssetTreeModel()
{
}
void ProductAssetTreeModel::ResetModel()
{
m_productToTreeItem.clear();
m_productIdToTreeItem.clear();
AZStd::string databaseLocation;
AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Broadcast(&AzToolsFramework::AssetDatabase::AssetDatabaseRequests::GetAssetDatabaseLocation, databaseLocation);
if (databaseLocation.empty())
{
return;
}
m_sharedDbConnection->QueryProductsTable(
[&](AzToolsFramework::AssetDatabase::ProductDatabaseEntry& product)
{
AddOrUpdateEntry(product, true);
return true; // return true to continue iterating over additional results, we are populating a container
});
}
void ProductAssetTreeModel::OnProductFileChanged(const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& entry)
{
// Model changes need to be run on the main thread.
AZ::SystemTickBus::QueueFunction([&, entry]()
{
AddOrUpdateEntry(entry, false);
});
}
void ProductAssetTreeModel::RemoveAsset(AZ::s64 productId)
{
auto existingProduct = m_productIdToTreeItem.find(productId);
if (existingProduct == m_productIdToTreeItem.end() || !existingProduct->second)
{
// If the product being removed wasn't cached, then something has gone wrong. Reset the model.
Reset();
return;
}
RemoveAssetTreeItem(existingProduct->second);
}
void ProductAssetTreeModel::RemoveAssetTreeItem(AssetTreeItem* assetToRemove)
{
if (!assetToRemove)
{
return;
}
AssetTreeItem* parent = assetToRemove->GetParent();
if (!parent)
{
return;
}
QModelIndex parentIndex = createIndex(parent->GetRow(), 0, parent);
beginRemoveRows(parentIndex, assetToRemove->GetRow(), assetToRemove->GetRow());
m_productToTreeItem.erase(assetToRemove->GetData()->m_assetDbName);
const AZStd::shared_ptr<const ProductAssetTreeItemData> productItemData = AZStd::rtti_pointer_cast<const ProductAssetTreeItemData>(assetToRemove->GetData());
if (productItemData && productItemData->m_hasDatabaseInfo)
{
m_productIdToTreeItem.erase(productItemData->m_databaseInfo.m_productID);
}
parent->EraseChild(assetToRemove);
endRemoveRows();
RemoveFoldersIfEmpty(parent);
}
void ProductAssetTreeModel::RemoveFoldersIfEmpty(AssetTreeItem* itemToCheck)
{
// Don't attempt to remove invalid items, non-folders, folders that still have items in them, or the root.
if (!itemToCheck || !itemToCheck->GetData()->m_isFolder || itemToCheck->getChildCount() > 0 || !itemToCheck->GetParent())
{
return;
}
RemoveAssetTreeItem(itemToCheck);
}
void ProductAssetTreeModel::OnProductFileRemoved(AZ::s64 productId)
{
// UI changes need to be done on the main thread.
AZ::SystemTickBus::QueueFunction([&, productId]()
{
RemoveAsset(productId);
});
}
void ProductAssetTreeModel::OnProductFilesRemoved(const AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer& products)
{
// UI changes need to be done on the main thread.
AZ::SystemTickBus::QueueFunction([&, products]()
{
for (const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& product : products)
{
RemoveAsset(product.m_productID);
}
});
}
QModelIndex ProductAssetTreeModel::GetIndexForProduct(const AZStd::string& product)
{
auto productItem = m_productToTreeItem.find(product);
if (productItem == m_productToTreeItem.end())
{
return QModelIndex();
}
return createIndex(productItem->second->GetRow(), 0, productItem->second);
}
void ProductAssetTreeModel::AddOrUpdateEntry(
const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& product,
bool modelIsResetting)
{
const auto& existingEntry = m_productIdToTreeItem.find(product.m_productID);
if (existingEntry != m_productIdToTreeItem.end())
{
AZStd::shared_ptr<ProductAssetTreeItemData> productItemData = AZStd::rtti_pointer_cast<ProductAssetTreeItemData>(existingEntry->second->GetData());
// This item already exists, refresh the related data.
productItemData->m_databaseInfo = product;
CheckForUnresolvedIssues(productItemData);
QModelIndex existingIndexStart = createIndex(existingEntry->second->GetRow(), 0, existingEntry->second);
QModelIndex existingIndexEnd = createIndex(existingEntry->second->GetRow(), existingEntry->second->GetColumnCount() - 1, existingEntry->second);
dataChanged(existingIndexStart, existingIndexEnd);
return;
}
AZStd::vector<AZStd::string> tokens;
AzFramework::StringFunc::Tokenize(product.m_productName.c_str(), tokens, AZ_CORRECT_DATABASE_SEPARATOR, false, true);
if (tokens.empty())
{
AZ_Warning("AssetProcessor", false, "Product id %d has an invalid name: %s", product.m_productID, product.m_productName.c_str());
return;
}
AssetTreeItem* parentItem = m_root.get();
AZStd::string fullFolderName;
for (int i = 0; i < tokens.size() - 1; ++i)
{
AzFramework::StringFunc::AssetDatabasePath::Join(fullFolderName.c_str(), tokens[i].c_str(), fullFolderName);
AssetTreeItem* nextParent = parentItem->GetChildFolder(tokens[i].c_str());
if (!nextParent)
{
if (!modelIsResetting)
{
QModelIndex parentIndex = parentItem == m_root.get() ? QModelIndex() : createIndex(parentItem->GetRow(), 0, parentItem);
beginInsertRows(parentIndex, parentItem->getChildCount(), parentItem->getChildCount());
}
nextParent = parentItem->CreateChild(ProductAssetTreeItemData::MakeShared(nullptr, fullFolderName, tokens[i].c_str(), true, AZ::Uuid::CreateNull()));
m_productToTreeItem[fullFolderName] = nextParent;
// m_productIdToTreeItem is not used for folders, folders don't have product IDs.
if (!modelIsResetting)
{
endInsertRows();
}
}
parentItem = nextParent;
}
AZ::Uuid sourceId;
m_sharedDbConnection->QuerySourceByProductID(
product.m_productID,
[&](AzToolsFramework::AssetDatabase::SourceDatabaseEntry& sourceEntry)
{
sourceId = sourceEntry.m_sourceGuid;
return true;
});
if (!modelIsResetting)
{
QModelIndex parentIndex = parentItem == m_root.get() ? QModelIndex() : createIndex(parentItem->GetRow(), 0, parentItem);
beginInsertRows(parentIndex, parentItem->getChildCount(), parentItem->getChildCount());
}
AZStd::shared_ptr<ProductAssetTreeItemData> productItemData =
ProductAssetTreeItemData::MakeShared(&product, product.m_productName, tokens[tokens.size() - 1].c_str(), false, sourceId);
m_productToTreeItem[product.m_productName] =
parentItem->CreateChild(productItemData);
m_productIdToTreeItem[product.m_productID] = m_productToTreeItem[product.m_productName];
CheckForUnresolvedIssues(productItemData);
if (!modelIsResetting)
{
endInsertRows();
}
}
void ProductAssetTreeModel::CheckForUnresolvedIssues(AZStd::shared_ptr<ProductAssetTreeItemData> productItemData)
{
productItemData->m_assetHasUnresolvedIssue = false;
// Start by clearing the tooltip, so any errors don't append to the existing text.
productItemData->m_unresolvedIssuesTooltip = QString();
if (!productItemData->m_hasDatabaseInfo)
{
// Folders can't have unresolved issues.
return;
}
m_sharedDbConnection->QueryMissingProductDependencyByProductId(
productItemData->m_databaseInfo.m_productID,
[&, productItemData](AzToolsFramework::AssetDatabase::MissingProductDependencyDatabaseEntry& missingDependency)
{
if (missingDependency.m_dependencySourceGuid.IsNull())
{
// This was an empty row that likely included information like the last time this file was scanned.
// Don't mark this product as having unresolved issues, and return true to continue looking through the scan results.
return true;
}
// If this asset has any missing dependencies, mark it as having an unresolved issue.
productItemData->m_assetHasUnresolvedIssue = true;
productItemData->m_unresolvedIssuesTooltip = tr("A missing product dependency has been detected for this asset.");
return false; // Don't keep iterating, an unresolved issue was found.
});
}
}
@@ -0,0 +1,52 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include "AssetTreeModel.h"
namespace AssetProcessor
{
class ProductAssetTreeItemData;
class ProductAssetTreeModel : public AssetTreeModel
{
public:
ProductAssetTreeModel(AZStd::shared_ptr<AzToolsFramework::AssetDatabase::AssetDatabaseConnection> sharedDbConnection, QObject *parent = nullptr);
virtual ~ProductAssetTreeModel();
// AssetDatabaseNotificationBus::Handler
void OnProductFileChanged(const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& entry) override;
void OnProductFileRemoved(AZ::s64 productId) override;
void OnProductFilesRemoved(const AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer& products) override;
QModelIndex GetIndexForProduct(const AZStd::string& product);
protected:
void ResetModel() override;
void AddOrUpdateEntry(
const AzToolsFramework::AssetDatabase::ProductDatabaseEntry& product,
bool modelIsResetting);
void RemoveAsset(AZ::s64 productId);
void RemoveAssetTreeItem(AssetTreeItem* assetToRemove);
void RemoveFoldersIfEmpty(AssetTreeItem* folderToCheck);
void CheckForUnresolvedIssues(AZStd::shared_ptr<ProductAssetTreeItemData> productItemData);
AZStd::unordered_map<AZStd::string, AssetTreeItem*> m_productToTreeItem;
// Mapping product Ids to asset tree items makes cleanup easier when files are deleted.
AZStd::unordered_map<AZ::s64, AssetTreeItem*> m_productIdToTreeItem;
};
}
@@ -0,0 +1,249 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "SourceAssetDetailsPanel.h"
#include "AssetTreeFilterModel.h"
#include "GoToButton.h"
#include "SourceAssetTreeItemData.h"
#include "SourceAssetTreeModel.h"
#include <native/ui/ui_GoToButton.h>
#include <native/ui/ui_SourceAssetDetailsPanel.h>
namespace AssetProcessor
{
SourceAssetDetailsPanel::SourceAssetDetailsPanel(QWidget* parent) : AssetDetailsPanel(parent), m_ui(new Ui::SourceAssetDetailsPanel)
{
m_ui->setupUi(this);
m_ui->scrollAreaWidgetContents->setLayout(m_ui->scrollableVerticalLayout);
ResetText();
}
SourceAssetDetailsPanel::~SourceAssetDetailsPanel()
{
}
void SourceAssetDetailsPanel::AssetDataSelectionChanged(const QItemSelection& selected, const QItemSelection& /*deselected*/)
{
QItemSelection sourceSelection = m_sourceFilterModel->mapSelectionToSource(selected);
// Even if multi-select is enabled, only display the first selected item.
if (sourceSelection.indexes().count() == 0 || !sourceSelection.indexes()[0].isValid())
{
ResetText();
return;
}
QModelIndex sourceModelIndex = sourceSelection.indexes()[0];
if (!sourceModelIndex.isValid())
{
return;
}
AssetTreeItem* childItem = static_cast<AssetTreeItem*>(sourceModelIndex.internalPointer());
const AZStd::shared_ptr<const SourceAssetTreeItemData> sourceItemData = AZStd::rtti_pointer_cast<const SourceAssetTreeItemData>(childItem->GetData());
m_ui->assetNameLabel->setText(childItem->GetData()->m_name);
if (childItem->GetData()->m_isFolder || !sourceItemData)
{
// Folders don't have details.
SetDetailsVisible(false);
return;
}
SetDetailsVisible(true);
m_ui->scanFolderValueLabel->setText(sourceItemData->m_scanFolderInfo.m_scanFolder.c_str());
m_ui->sourceGuidValueLabel->setText(sourceItemData->m_sourceInfo.m_sourceGuid.ToString<AZStd::string>().c_str());
AssetDatabaseConnection assetDatabaseConnection;
assetDatabaseConnection.OpenDatabase();
BuildProducts(assetDatabaseConnection, sourceItemData);
BuildOutgoingSourceDependencies(assetDatabaseConnection, sourceItemData);
BuildIncomingSourceDependencies(assetDatabaseConnection, sourceItemData);
}
void SourceAssetDetailsPanel::BuildProducts(
AssetDatabaseConnection& assetDatabaseConnection,
const AZStd::shared_ptr<const SourceAssetTreeItemData> sourceItemData)
{
// Clear & ClearContents leave the table dimensions the same, so set rowCount to zero to reset it.
m_ui->productTable->setRowCount(0);
int productCount = 0;
assetDatabaseConnection.QueryProductBySourceID(
sourceItemData->m_sourceInfo.m_sourceID,
[&](AzToolsFramework::AssetDatabase::ProductDatabaseEntry& productEntry)
{
m_ui->productTable->insertRow(productCount);
// Qt handles cleanup automatically, setting this as the parent means
// when this panel is torn down, these widgets will be destroyed.
GoToButton* rowGoToButton = new GoToButton(this);
connect(rowGoToButton->m_ui->goToPushButton, &QPushButton::clicked, [=] {
GoToProduct(productEntry.m_productName);
});
m_ui->productTable->setCellWidget(productCount, 0, rowGoToButton);
QTableWidgetItem* rowName = new QTableWidgetItem(productEntry.m_productName.c_str());
m_ui->productTable->setItem(productCount, 1, rowName);
++productCount;
return true;
});
m_ui->productsValueLabel->setText(QString::number(productCount));
if (productCount == 0)
{
m_ui->productTable->insertRow(productCount);
QTableWidgetItem* rowName = new QTableWidgetItem(tr("No products"));
m_ui->productTable->setItem(productCount, 1, rowName);
++productCount;
}
// The default list behavior is to maintain size and let you scroll within.
// The entire frame is scrollable here, so the list should adjust to fit the contents.
m_ui->productTable->setMinimumHeight(m_ui->productTable->rowHeight(0) * productCount + 2 * m_ui->productTable->frameWidth());
m_ui->productTable->adjustSize();
}
void SourceAssetDetailsPanel::BuildOutgoingSourceDependencies(
AssetDatabaseConnection& assetDatabaseConnection,
const AZStd::shared_ptr<const SourceAssetTreeItemData> sourceItemData)
{
m_ui->outgoingSourceDependenciesTable->setRowCount(0);
int sourceDependencyCount = 0;
assetDatabaseConnection.QueryDependsOnSourceBySourceDependency(
sourceItemData->m_sourceInfo.m_sourceName.c_str(),
nullptr,
AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_Any,
[&](AzToolsFramework::AssetDatabase::SourceFileDependencyEntry& sourceFileDependencyEntry)
{
m_ui->outgoingSourceDependenciesTable->insertRow(sourceDependencyCount);
// Some outgoing source dependencies are wildcard, or unresolved paths.
// Only add a button to link to rows that actually exist.
QModelIndex goToIndex = m_sourceTreeModel->GetIndexForSource(sourceFileDependencyEntry.m_dependsOnSource);
if (goToIndex.isValid())
{
// Qt handles cleanup automatically, setting this as the parent means
// when this panel is torn down, these widgets will be destroyed.
GoToButton* rowGoToButton = new GoToButton(this);
connect(rowGoToButton->m_ui->goToPushButton, &QPushButton::clicked, [=] {
GoToSource(sourceFileDependencyEntry.m_dependsOnSource);
});
m_ui->outgoingSourceDependenciesTable->setCellWidget(sourceDependencyCount, 0, rowGoToButton);
}
QTableWidgetItem* rowName = new QTableWidgetItem(sourceFileDependencyEntry.m_dependsOnSource.c_str());
m_ui->outgoingSourceDependenciesTable->setItem(sourceDependencyCount, 1, rowName);
++sourceDependencyCount;
return true;
});
m_ui->outgoingSourceDependenciesValueLabel->setText(QString::number(sourceDependencyCount));
if (sourceDependencyCount == 0)
{
m_ui->outgoingSourceDependenciesTable->insertRow(sourceDependencyCount);
QTableWidgetItem* rowName = new QTableWidgetItem(tr("No source dependencies"));
m_ui->outgoingSourceDependenciesTable->setItem(sourceDependencyCount, 1, rowName);
++sourceDependencyCount;
}
// The default list behavior is to maintain size and let you scroll within.
// The entire frame is scrollable here, so the list should adjust to fit the contents.
m_ui->outgoingSourceDependenciesTable->setMinimumHeight(m_ui->outgoingSourceDependenciesTable->rowHeight(0) * sourceDependencyCount + 2 * m_ui->outgoingSourceDependenciesTable->frameWidth());
m_ui->outgoingSourceDependenciesTable->adjustSize();
}
void SourceAssetDetailsPanel::BuildIncomingSourceDependencies(
AssetDatabaseConnection& assetDatabaseConnection,
const AZStd::shared_ptr<const SourceAssetTreeItemData> sourceItemData)
{
m_ui->incomingSourceDependenciesTable->setRowCount(0);
int sourceDependencyCount = 0;
assetDatabaseConnection.QuerySourceDependencyByDependsOnSource(
sourceItemData->m_sourceInfo.m_sourceName.c_str(),
nullptr,
AzToolsFramework::AssetDatabase::SourceFileDependencyEntry::DEP_Any,
[&](AzToolsFramework::AssetDatabase::SourceFileDependencyEntry& sourceFileDependencyEntry)
{
m_ui->incomingSourceDependenciesTable->insertRow(sourceDependencyCount);
// Qt handles cleanup automatically, setting this as the parent means
// when this panel is torn down, these widgets will be destroyed.
GoToButton* rowGoToButton = new GoToButton(this);
connect(rowGoToButton->m_ui->goToPushButton, &QPushButton::clicked, [=] {
GoToSource(sourceFileDependencyEntry.m_source);
});
m_ui->incomingSourceDependenciesTable->setCellWidget(sourceDependencyCount, 0, rowGoToButton);
QTableWidgetItem* rowName = new QTableWidgetItem(sourceFileDependencyEntry.m_source.c_str());
m_ui->incomingSourceDependenciesTable->setItem(sourceDependencyCount, 1, rowName);
++sourceDependencyCount;
return true;
});
m_ui->incomingSourceDependenciesValueLabel->setText(QString::number(sourceDependencyCount));
if (sourceDependencyCount == 0)
{
m_ui->incomingSourceDependenciesTable->insertRow(sourceDependencyCount);
QTableWidgetItem* rowName = new QTableWidgetItem(tr("No source dependencies"));
m_ui->incomingSourceDependenciesTable->setItem(sourceDependencyCount, 1, rowName);
++sourceDependencyCount;
}
// The default list behavior is to maintain size and let you scroll within.
// The entire frame is scrollable here, so the list should adjust to fit the contents.
m_ui->incomingSourceDependenciesTable->setMinimumHeight(m_ui->incomingSourceDependenciesTable->rowHeight(0) * sourceDependencyCount + 2 * m_ui->incomingSourceDependenciesTable->frameWidth());
m_ui->incomingSourceDependenciesTable->adjustSize();
}
void SourceAssetDetailsPanel::ResetText()
{
m_ui->assetNameLabel->setText(tr("Select an asset to see details"));
SetDetailsVisible(false);
}
void SourceAssetDetailsPanel::SetDetailsVisible(bool visible)
{
// The folder selected description has opposite visibility from everything else.
m_ui->folderSelectedDescription->setVisible(!visible);
m_ui->scanFolderTitleLabel->setVisible(visible);
m_ui->scanFolderValueLabel->setVisible(visible);
m_ui->sourceGuidTitleLabel->setVisible(visible);
m_ui->sourceGuidValueLabel->setVisible(visible);
m_ui->productsTitleLabel->setVisible(visible);
m_ui->productsValueLabel->setVisible(visible);
m_ui->productTable->setVisible(visible);
m_ui->outgoingSourceDependenciesTitleLabel->setVisible(visible);
m_ui->outgoingSourceDependenciesValueLabel->setVisible(visible);
m_ui->outgoingSourceDependenciesTable->setVisible(visible);
m_ui->incomingSourceDependenciesTitleLabel->setVisible(visible);
m_ui->incomingSourceDependenciesValueLabel->setVisible(visible);
m_ui->incomingSourceDependenciesTable->setVisible(visible);
m_ui->DependencySeparatorLine->setVisible(visible);
}
}
@@ -0,0 +1,59 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include "AssetDetailsPanel.h"
#include <QScopedPointer>
#endif
class QItemSelection;
namespace Ui
{
class SourceAssetDetailsPanel;
}
namespace AssetProcessor
{
class AssetDatabaseConnection;
class SourceAssetTreeItemData;
class SourceAssetDetailsPanel
: public AssetDetailsPanel
{
Q_OBJECT
public:
explicit SourceAssetDetailsPanel(QWidget* parent = nullptr);
~SourceAssetDetailsPanel() override;
public Q_SLOTS:
void AssetDataSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
protected:
void ResetText();
void SetDetailsVisible(bool visible);
void BuildProducts(
AssetDatabaseConnection& assetDatabaseConnection,
const AZStd::shared_ptr<const SourceAssetTreeItemData> sourceItemData);
void BuildOutgoingSourceDependencies(
AssetDatabaseConnection& assetDatabaseConnection,
const AZStd::shared_ptr<const SourceAssetTreeItemData> sourceItemData);
void BuildIncomingSourceDependencies(
AssetDatabaseConnection& assetDatabaseConnection,
const AZStd::shared_ptr<const SourceAssetTreeItemData> sourceItemData);
QScopedPointer<Ui::SourceAssetDetailsPanel> m_ui;
};
} // namespace AssetProcessor
@@ -0,0 +1,754 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SourceAssetDetailsPanel</class>
<widget class="QFrame" name="SourceAssetDetailsPanel">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>743</width>
<height>860</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">background-color: rgb(87, 87, 87);</string>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<item>
<widget class="QLabel" name="assetNameLabel">
<property name="font">
<font>
<family>16pt</family>
<weight>75</weight>
<italic>false</italic>
</font>
</property>
<property name="styleSheet">
<string notr="true">font: 12pt;</string>
</property>
<property name="text">
<string>Model1.obj</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading</set>
</property>
<property name="class" stdset="0">
<string>Title</string>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="sourcePanelLayout">
<item>
<widget class="Line" name="AssetDetailsSeparator">
<property name="palette">
<palette>
<active>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="200">
<red>200</red>
<green>200</green>
<blue>200</blue>
</color>
</brush>
</colorrole>
<colorrole role="Button">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>87</red>
<green>87</green>
<blue>87</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>87</red>
<green>87</green>
<blue>87</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>87</red>
<green>87</green>
<blue>87</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Button">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>87</red>
<green>87</green>
<blue>87</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>87</red>
<green>87</green>
<blue>87</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>87</red>
<green>87</green>
<blue>87</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>120</red>
<green>120</green>
<blue>120</blue>
</color>
</brush>
</colorrole>
<colorrole role="Button">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>87</red>
<green>87</green>
<blue>87</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>87</red>
<green>87</green>
<blue>87</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>87</red>
<green>87</green>
<blue>87</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="folderSelectedDescription">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>This folder has no additional info, select a file for details.</string>
</property>
</widget>
</item>
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>721</width>
<height>791</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<widget class="QWidget" name="verticalLayoutWidget">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>833</width>
<height>551</height>
</rect>
</property>
<layout class="QVBoxLayout" name="scrollableVerticalLayout">
<item>
<layout class="QHBoxLayout" name="ScanFolderLayout">
<item>
<widget class="QLabel" name="scanFolderTitleLabel">
<property name="styleSheet">
<string notr="true">font: bold;</string>
</property>
<property name="text">
<string>Scan Folder:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="scanFolderValueLabel">
<property name="text">
<string>Scan folder name here</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="sourceGuidLayout">
<item>
<widget class="QLabel" name="sourceGuidTitleLabel">
<property name="styleSheet">
<string notr="true">font: bold;</string>
</property>
<property name="text">
<string>Source Guid:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="sourceGuidValueLabel">
<property name="text">
<string>Source guid here</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="Line" name="DependencySeparatorLine">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>8</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QVBoxLayout" name="productVerticalLayout">
<property name="spacing">
<number>1</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<item>
<layout class="QHBoxLayout" name="ProductTitleLayout">
<item>
<widget class="QLabel" name="productsTitleLabel">
<property name="font">
<font>
<family>12pt</family>
<weight>75</weight>
<italic>false</italic>
<bold>true</bold>
</font>
</property>
<property name="styleSheet">
<string notr="true">font: bold, 12pt;</string>
</property>
<property name="text">
<string>Products:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="margin">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="productsValueLabel">
<property name="text">
<string>Number of products here</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QTableWidget" name="productTable">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>32</height>
</size>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="dragDropOverwriteMode">
<bool>false</bool>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<property name="columnCount">
<number>2</number>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>24</number>
</attribute>
<attribute name="horizontalHeaderMinimumSectionSize">
<number>24</number>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderStretchLastSection">
<bool>false</bool>
</attribute>
<column/>
<column/>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>8</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="OutgoingDependenciesLayout">
<item>
<widget class="QLabel" name="outgoingSourceDependenciesTitleLabel">
<property name="font">
<font>
<family>12pt</family>
<weight>75</weight>
<italic>false</italic>
<bold>true</bold>
</font>
</property>
<property name="styleSheet">
<string notr="true">font: bold, 12pt;</string>
</property>
<property name="text">
<string>Outgoing Source Dependencies:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="margin">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="outgoingSourceDependenciesValueLabel">
<property name="text">
<string>Number of outgoing source dependencies here</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QTableWidget" name="outgoingSourceDependenciesTable">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>32</height>
</size>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="dragDropOverwriteMode">
<bool>false</bool>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<property name="columnCount">
<number>2</number>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>24</number>
</attribute>
<attribute name="horizontalHeaderMinimumSectionSize">
<number>24</number>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderStretchLastSection">
<bool>false</bool>
</attribute>
<column/>
<column/>
</widget>
</item>
<item>
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>8</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="IncomingDependenciesLayout">
<item>
<widget class="QLabel" name="incomingSourceDependenciesTitleLabel">
<property name="font">
<font>
<family>12pt</family>
<weight>75</weight>
<italic>false</italic>
<bold>true</bold>
</font>
</property>
<property name="styleSheet">
<string notr="true">font: bold, 12pt;</string>
</property>
<property name="text">
<string>Incoming Source Dependencies:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="margin">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="incomingSourceDependenciesValueLabel">
<property name="text">
<string>Number of incoming source dependencies here</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QTableWidget" name="incomingSourceDependenciesTable">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>32</height>
</size>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="dragDropOverwriteMode">
<bool>false</bool>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<property name="columnCount">
<number>2</number>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>24</number>
</attribute>
<attribute name="horizontalHeaderMinimumSectionSize">
<number>24</number>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderStretchLastSection">
<bool>false</bool>
</attribute>
<column/>
<column/>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,115 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "SourceAssetTreeItemData.h"
#include <AzCore/std/smart_ptr/make_shared.h>
#include <QDir>
namespace AssetProcessor
{
AZStd::shared_ptr<SourceAssetTreeItemData> SourceAssetTreeItemData::MakeShared(
const AzToolsFramework::AssetDatabase::SourceDatabaseEntry* sourceInfo,
const AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry* scanFolderInfo,
const AZStd::string& assetDbName,
QString name,
bool isFolder)
{
return AZStd::make_shared<SourceAssetTreeItemData>(sourceInfo, scanFolderInfo, assetDbName, name, isFolder);
}
SourceAssetTreeItemData::SourceAssetTreeItemData(
const AzToolsFramework::AssetDatabase::SourceDatabaseEntry* sourceInfo,
const AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry* scanFolderInfo,
const AZStd::string& assetDbName,
QString name,
bool isFolder) :
AssetTreeItemData(assetDbName, name, isFolder, sourceInfo ? sourceInfo->m_sourceGuid : AZ::Uuid::CreateNull())
{
if (sourceInfo && scanFolderInfo)
{
m_hasDatabaseInfo = true;
m_sourceInfo = *sourceInfo;
m_scanFolderInfo = *scanFolderInfo;
}
else
{
m_hasDatabaseInfo = false;
}
}
QString BuildAbsolutePathToFile(const AZStd::shared_ptr<const SourceAssetTreeItemData> file)
{
QDir scanFolder(file->m_scanFolderInfo.m_scanFolder.c_str());
QString sourceName = file->m_sourceInfo.m_sourceName.c_str();
// If a scan folder has a prefix, then source files in those scan folders will have that
// prefix prepended in the asset database. Strip that prefix off for building the actual absolute path to the file.
if (!file->m_scanFolderInfo.m_outputPrefix.empty())
{
QRegExp prefixRemovalRegex(QString("^%1/").arg(file->m_scanFolderInfo.m_outputPrefix.c_str()));
sourceName.remove(prefixRemovalRegex);
}
return scanFolder.filePath(sourceName);
}
AZ::Outcome<QString> GetAbsolutePathToSource(const AssetTreeItem& source)
{
if (source.getChildCount() > 0)
{
// Folders are special case, they only exist in the interface and don't exist in the asset database.
// Figure out a path to this folder by finding a descendant that isn't a folder, taking the absolute
// path of that file, and stripping off the path after this folder.
size_t directoriesToRemove = 0;
const AssetTreeItem* searchForFile = &source;
while (searchForFile)
{
if (searchForFile->getChildCount() == 0)
{
const AZStd::shared_ptr<const SourceAssetTreeItemData> sourceItemData =
AZStd::rtti_pointer_cast<const SourceAssetTreeItemData>(searchForFile->GetData());
if (!sourceItemData)
{
return AZ::Failure();
}
QFileInfo fileInfo(BuildAbsolutePathToFile(sourceItemData));
QDir fileFolder(fileInfo.absoluteDir());
// The file found wasn't a directory, it was removed when absolute dir was called on the QFileInfo above.
while (directoriesToRemove > 1)
{
fileFolder.cdUp();
--directoriesToRemove;
}
return AZ::Success(fileFolder.absolutePath());
}
else
{
searchForFile = searchForFile->GetChild(0);
}
++directoriesToRemove;
}
return AZ::Failure();
}
else
{
const AZStd::shared_ptr<const SourceAssetTreeItemData> sourceItemData = AZStd::rtti_pointer_cast<const SourceAssetTreeItemData>(source.GetData());
if (!sourceItemData)
{
return AZ::Failure();
}
return AZ::Success(BuildAbsolutePathToFile(sourceItemData));
}
}
}
@@ -0,0 +1,45 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include "AssetTreeItem.h"
#include <AzToolsFramework/AssetDatabase/AssetDatabaseConnection.h>
namespace AssetProcessor
{
class SourceAssetTreeItemData : public AssetTreeItemData
{
public:
AZ_RTTI(SourceAssetTreeItemData, "{EF56D1E6-4C13-4494-9CB7-02B39A8E3639}", AssetTreeItemData);
static AZStd::shared_ptr<SourceAssetTreeItemData> MakeShared(
const AzToolsFramework::AssetDatabase::SourceDatabaseEntry* sourceInfo,
const AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry* scanFolderInfo,
const AZStd::string& assetDbName,
QString name,
bool isFolder);
SourceAssetTreeItemData(
const AzToolsFramework::AssetDatabase::SourceDatabaseEntry* sourceInfo,
const AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry* scanFolderInfo,
const AZStd::string& assetDbName,
QString name,
bool isFolder);
~SourceAssetTreeItemData() override {}
AzToolsFramework::AssetDatabase::SourceDatabaseEntry m_sourceInfo;
AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry m_scanFolderInfo;
bool m_hasDatabaseInfo = false;
};
AZ::Outcome<QString> GetAbsolutePathToSource(const AssetTreeItem& source);
} // AssetProcessor
@@ -0,0 +1,222 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "SourceAssetTreeModel.h"
#include "SourceAssetTreeItemData.h"
#include <AzCore/Component/TickBus.h>
#include <native/utilities/assetUtils.h>
namespace AssetProcessor
{
SourceAssetTreeModel::SourceAssetTreeModel(AZStd::shared_ptr<AzToolsFramework::AssetDatabase::AssetDatabaseConnection> sharedDbConnection, QObject *parent) :
AssetTreeModel(sharedDbConnection, parent)
{
}
SourceAssetTreeModel::~SourceAssetTreeModel()
{
}
void SourceAssetTreeModel::ResetModel()
{
m_sourceToTreeItem.clear();
m_sourceIdToTreeItem.clear();
m_sharedDbConnection->QuerySourceAndScanfolder(
[&](AzToolsFramework::AssetDatabase::SourceAndScanFolderDatabaseEntry& sourceAndScanFolder)
{
AddOrUpdateEntry(sourceAndScanFolder, sourceAndScanFolder, true);
return true; // return true to continue iterating over additional results, we are populating a container
});
}
void SourceAssetTreeModel::AddOrUpdateEntry(
const AzToolsFramework::AssetDatabase::SourceDatabaseEntry& source,
const AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry& scanFolder,
bool modelIsResetting)
{
const auto& existingEntry = m_sourceToTreeItem.find(source.m_sourceName);
if (existingEntry != m_sourceToTreeItem.end())
{
AZStd::shared_ptr<SourceAssetTreeItemData> sourceItemData = AZStd::rtti_pointer_cast<SourceAssetTreeItemData>(existingEntry->second->GetData());
// This item already exists, refresh the related data.
sourceItemData->m_scanFolderInfo = scanFolder;
sourceItemData->m_sourceInfo = source;
QModelIndex existingIndexStart = createIndex(existingEntry->second->GetRow(), 0, existingEntry->second);
QModelIndex existingIndexEnd = createIndex(existingEntry->second->GetRow(), existingEntry->second->GetColumnCount() - 1, existingEntry->second);
dataChanged(existingIndexStart, existingIndexEnd);
return;
}
AZStd::string fullPath = source.m_sourceName;
// The source assets should look like they do on disk.
// If the scan folder has an output prefix, strip it from the source file's path in the database, before
// the scan folder path is prepended to the source file.
if (!scanFolder.m_outputPrefix.empty())
{
AZStd::string prefixPath = scanFolder.m_outputPrefix;
AzFramework::StringFunc::Append(prefixPath, AZ_CORRECT_DATABASE_SEPARATOR);
AzFramework::StringFunc::Replace(fullPath, prefixPath.c_str(), "", false, true);
}
AzFramework::StringFunc::AssetDatabasePath::Join(scanFolder.m_scanFolder.c_str(), fullPath.c_str(), fullPath, true, false);
// It's common for Lumberyard game projects and scan folders to be in a subfolder
// of the engine install. To improve readability of the source files, strip out
// that portion of the path if it overlaps.
if (!m_assetRootSet)
{
m_assetRootSet = AssetUtilities::ComputeAssetRoot(m_assetRoot, nullptr);
}
if (m_assetRootSet)
{
AzFramework::StringFunc::Replace(fullPath, m_assetRoot.absolutePath().toUtf8(), "");
}
AZStd::vector<AZStd::string> tokens;
AzFramework::StringFunc::Tokenize(fullPath.c_str(), tokens, AZ_CORRECT_DATABASE_SEPARATOR, false, true);
if (tokens.empty())
{
AZ_Warning("AssetProcessor", false, "Source id %s has an invalid name: %s",
source.m_sourceGuid.ToString<AZStd::string>().c_str(), source.m_sourceName.c_str());
return;
}
QModelIndex newIndicesStart;
AssetTreeItem* parentItem = m_root.get();
AZStd::string fullFolderName;
for (int i = 0; i < tokens.size() - 1; ++i)
{
AzFramework::StringFunc::AssetDatabasePath::Join(fullFolderName.c_str(), tokens[i].c_str(), fullFolderName);
AssetTreeItem* nextParent = parentItem->GetChildFolder(tokens[i].c_str());
if (!nextParent)
{
if (!modelIsResetting)
{
QModelIndex parentIndex = parentItem == m_root.get() ? QModelIndex() : createIndex(parentItem->GetRow(), 0, parentItem);
beginInsertRows(parentIndex, parentItem->getChildCount(), parentItem->getChildCount());
}
nextParent = parentItem->CreateChild(SourceAssetTreeItemData::MakeShared(nullptr, nullptr, fullFolderName, tokens[i].c_str(), true));
m_sourceToTreeItem[fullFolderName] = nextParent;
// Folders don't have source IDs, don't add to m_sourceIdToTreeItem
if (!modelIsResetting)
{
endInsertRows();
}
}
parentItem = nextParent;
}
if (!modelIsResetting)
{
QModelIndex parentIndex = parentItem == m_root.get() ? QModelIndex() : createIndex(parentItem->GetRow(), 0, parentItem);
beginInsertRows(parentIndex, parentItem->getChildCount(), parentItem->getChildCount());
}
m_sourceToTreeItem[source.m_sourceName] =
parentItem->CreateChild(SourceAssetTreeItemData::MakeShared(&source, &scanFolder, source.m_sourceName, tokens[tokens.size() - 1].c_str(), false));
m_sourceIdToTreeItem[source.m_sourceID] = m_sourceToTreeItem[source.m_sourceName];
if (!modelIsResetting)
{
endInsertRows();
}
}
void SourceAssetTreeModel::OnSourceFileChanged(const AzToolsFramework::AssetDatabase::SourceDatabaseEntry& entry)
{
// Model changes need to be run on the main thread.
AZ::SystemTickBus::QueueFunction([&, entry]()
{
m_sharedDbConnection->QueryScanFolderBySourceID(entry.m_sourceID,
[&, entry](AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry& scanFolder)
{
AddOrUpdateEntry(entry, scanFolder, false);
return true;
});
});
}
void SourceAssetTreeModel::RemoveFoldersIfEmpty(AssetTreeItem* itemToCheck)
{
// Don't attempt to remove invalid items, non-folders, folders that still have items in them, or the root.
if (!itemToCheck || !itemToCheck->GetData()->m_isFolder || itemToCheck->getChildCount() > 0 || !itemToCheck->GetParent())
{
return;
}
RemoveAssetTreeItem(itemToCheck);
}
void SourceAssetTreeModel::RemoveAssetTreeItem(AssetTreeItem* assetToRemove)
{
if (!assetToRemove)
{
return;
}
AssetTreeItem* parent = assetToRemove->GetParent();
if (!parent)
{
return;
}
QModelIndex parentIndex = createIndex(parent->GetRow(), 0, parent);
beginRemoveRows(parentIndex, assetToRemove->GetRow(), assetToRemove->GetRow());
m_sourceToTreeItem.erase(assetToRemove->GetData()->m_assetDbName);
const AZStd::shared_ptr<const SourceAssetTreeItemData> sourceItemData = AZStd::rtti_pointer_cast<const SourceAssetTreeItemData>(assetToRemove->GetData());
if (sourceItemData && sourceItemData->m_hasDatabaseInfo)
{
m_sourceIdToTreeItem.erase(sourceItemData->m_sourceInfo.m_sourceID);
}
parent->EraseChild(assetToRemove);
endRemoveRows();
RemoveFoldersIfEmpty(parent);
}
void SourceAssetTreeModel::OnSourceFileRemoved(AZ::s64 sourceId)
{
// UI changes need to be done on the main thread.
AZ::SystemTickBus::QueueFunction([&, sourceId]()
{
auto existingSource = m_sourceIdToTreeItem.find(sourceId);
if (existingSource == m_sourceIdToTreeItem.end() || !existingSource->second)
{
// If the asset being removed wasn't previously cached, then something has gone wrong. Reset the model.
Reset();
return;
}
RemoveAssetTreeItem(existingSource->second);
});
}
QModelIndex SourceAssetTreeModel::GetIndexForSource(const AZStd::string& source)
{
auto sourceItem = m_sourceToTreeItem.find(source);
if (sourceItem == m_sourceToTreeItem.end())
{
return QModelIndex();
}
return createIndex(sourceItem->second->GetRow(), 0, sourceItem->second);
}
}
@@ -0,0 +1,50 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include "AssetTreeModel.h"
#include <AzToolsFramework/API/AssetDatabaseBus.h>
#include <AzCore/std/containers/unordered_map.h>
#include <native/utilities/ApplicationManagerAPI.h>
#include <QDir>
namespace AssetProcessor
{
class SourceAssetTreeModel : public AssetTreeModel
{
public:
SourceAssetTreeModel(AZStd::shared_ptr<AzToolsFramework::AssetDatabase::AssetDatabaseConnection> sharedDbConnection, QObject *parent = nullptr);
~SourceAssetTreeModel();
// AssetDatabaseNotificationBus::Handler
void OnSourceFileChanged(const AzToolsFramework::AssetDatabase::SourceDatabaseEntry& entry) override;
void OnSourceFileRemoved(AZ::s64 sourceId) override;
QModelIndex GetIndexForSource(const AZStd::string& source);
protected:
void ResetModel() override;
void AddOrUpdateEntry(
const AzToolsFramework::AssetDatabase::SourceDatabaseEntry& source,
const AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry& scanFolder,
bool modelIsResetting);
void RemoveAssetTreeItem(AssetTreeItem* assetToRemove);
void RemoveFoldersIfEmpty(AssetTreeItem* itemToCheck);
AZStd::unordered_map<AZStd::string, AssetTreeItem*> m_sourceToTreeItem;
AZStd::unordered_map<AZ::s64, AssetTreeItem*> m_sourceIdToTreeItem;
QDir m_assetRoot;
bool m_assetRootSet = false;
};
}
@@ -0,0 +1,21 @@
<RCC>
<qresource prefix="/AssetProcessor/style">
<file>AssetProcessor.qss</file>
<file>AssetProcessorConfig.ini</file>
<file>AssetsTab.qss</file>
<file>LogsTab.qss</file>
</qresource>
<qresource prefix="/">
<file>AssetProcessor_checkbox_blue_checked.png</file>
<file>AssetProcessor_checkbox_blue_unchecked.png</file>
<file>AssetProcessor_goto.svg</file>
<file>AssetProcessor_goto_hover.svg</file>
<file>AssetProcessor_arrow_left.svg</file>
<file>AssetProcessor_arrow_right.svg</file>
<file>AssetProcessor_plus.svg</file>
<file>AssetProcessor_arrow_down.svg</file>
<file>AssetProcessor_arrow_up.svg</file>
<file>AssetProcessor_refresh.png</file>
<file>lyassetprocessor.png</file>
</qresource>
</RCC>
@@ -0,0 +1,143 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
@import "AssetsTab.qss";
@import "LogsTab.qss";
@import "NewLogTabDialog.qss";
.APTimer {
font-size: 18px;
margin-top: 0px;
margin-bottom: 0px;
font-family: "Open Sans Light";
}
.TimerLine {
color: #888888;
}
QLabel#APStatusValueLabel {
margin-top: 12px;
margin-left: 8px;
}
QLabel#lastScanLabel,
QLabel#analysisLabel,
QLabel#processingLabel{
margin-top: 0px;
margin-left: 0px;
margin-bottom: 0px;
}
QLabel#projectLabel,
QLabel#rootLabel {
margin-top: 0px;
margin-bottom: 0px;
margin-left: 8px;
}
QLabel#portLabel {
margin-top: 0px;
margin-bottom: 24px;
margin-left: 8px;
}
QTreeView#SourceAssetsTreeView,
QTreeView#SourceAssetsTreeView::item,
QTreeView#SourceAssetsTreeView::branch,
QTreeView#ProductAssetsTreeView,
QTreeView#ProductAssetsTreeView::item,
QTreeView#ProductAssetsTreeView::branch {
background-color: rgb(45,45,45);
}
QTreeView#SourceAssetsTreeView::item:hover,
QTreeView#SourceAssetsTreeView::branch:hover,
QTreeView#ProductAssetsTreeView::item:hover,
QTreeView#ProductAssetsTreeView::branch:hover {
background-color: rgb(60,60,60);
}
QTreeView#SourceAssetsTreeView::item:selected,
QTreeView#SourceAssetsTreeView::branch:selected,
QTreeView#SourceAssetsTreeView::item:selected:active,
QTreeView#ProductAssetsTreeView::item:selected,
QTreeView#ProductAssetsTreeView::branch:selected,
QTreeView#ProductAssetsTreeView::item:selected:active {
background-color: rgb(73,73,73);
}
QTreeView#SourceAssetsTreeView::item:selected:!active,
QTreeView#ProductAssetsTreeView::item:selected:!active,
QTableWidget#outgoingProductDependenciesTable:item:selected:!active,
QListWidget#outgoingUnmetPathProductDependenciesList:item:selected:!active,
QTableWidget#incomingProductDependenciesTable:item:selected:!active,
QTableWidget#productTable:item:selected:!active,
QTableWidget#outgoingSourceDependenciesTable:item:selected:!active,
QTableWidget#incomingSourceDependenciesTable:item:selected:!active {
background-color: rgb(60,60,60);
selection-color: rgb(192, 192, 192);
}
QPushButton#supportButton,
QPushButton#MissingProductDependenciesSupport {
min-height: 24px; /* We have to set the min- and max-height otherwise the margins are not respected. */
max-height: 24px;
margin-top: 10px;
margin-right: 16px;
border: none;
qproperty-icon: url(:/stylesheet/img/help.svg);
qproperty-iconSize: 24px 24px;
}
ConnectionEditDialog QLabel {
background-color: transparent;
}
QTableWidget {
background-color: rgb(45,45,45); /* Odd row */
alternate-background-color: rgb(34,34,34); /* Even row */
selection-background-color: rgb(73,73,73);
}
QTableWidget::item:selected {
background: rgb(73,73,73);
}
QTableWidget::item:hover {
background: rgb(60,60,60);
}
QTableWidget QHeaderView::section {
background-color: rgb(34,34,34);
}
QListWidget {
background-color: rgb(45,45,45); /* Odd row */
alternate-background-color: rgb(34,34,34); /* Even row */
selection-background-color: rgb(73,73,73);
}
QListWidget::item:selected {
background: rgb(73,73,73);
}
QListWidget::item:hover {
background: rgb(60,60,60);
}
QListWidget QHeaderView::section {
background-color: rgb(34,34,34);
}
@@ -0,0 +1,14 @@
[AssetStatus]
JobCompletedColumnWidth=160
JobKeyColumnWidth=120
JobPlatformColumnWidth=100
JobSourceColumnWidth=230
JobStatusColumnWidth=100
[EventLogDetails]
LogTypeColumnWidth=180
[AssetData]
AssetDataNameColumnWidth=400
AssetDataDirectoryColumnWidth=400
AssetDataTypeColumnWidth=75
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Icons / System / Downward</title>
<g id="Icons-/-System-/-Downward" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Icon-Background" x="0" y="0" width="24" height="24"></rect>
<polygon id="Combined-Shape" fill="#FFFFFF" transform="translate(12.000000, 12.000000) rotate(90.000000) translate(-12.000000, -12.000000) " points="17 11 12 6 12 3 21 12 12 21 12 18 17 13 3 13 3 11"></polygon>
</g>
</svg>

After

Width:  |  Height:  |  Size: 643 B

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Icons / System / Backward</title>
<g id="Icons-/-System-/-Backward" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Icon-Background" x="0" y="0" width="24" height="24"></rect>
<polygon id="Combined-Shape" fill="#FFFFFF" points="7.02553943 13 12 18.0361734 12 21 3 12 12 3 12 5.98884985 7 11 21 11 21 13"></polygon>
</g>
</svg>

After

Width:  |  Height:  |  Size: 571 B

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Icons / System / Forward</title>
<g id="Icons-/-System-/-Forward" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Icon-Background" x="0" y="0" width="24" height="24"></rect>
<polygon id="Combined-Shape" fill="#FFFFFF" points="17 11 12 6 12 3 21 12 12 21 12 18 17 13 3 13 3 11"></polygon>
</g>
</svg>

After

Width:  |  Height:  |  Size: 544 B

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Icons / System / upward</title>
<g id="Icons-/-System-/-upward" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Icon-Background" x="0" y="0" width="24" height="24"></rect>
<polygon id="Combined-Shape" fill="#FFFFFF" transform="translate(12.000000, 12.000000) rotate(90.000000) translate(-12.000000, -12.000000) " points="7.02553943 13 12 18.0361734 12 21 3 12 12 3 12 5.98884985 7 11 21 11 21 13"></polygon>
</g>
</svg>

After

Width:  |  Height:  |  Size: 664 B

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fdac646f22b39d9adfc4819bcac0c66e0ba3e631464f7f568c7bbbd14850547e
size 11222
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3d5efd73e2d386ceb219ea6091c8b09ffec300015e7c84e0c81ecfd29505afb0
size 9285
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 60.1 (88133) - https://sketch.com -->
<title>Icons / System / Open / Open in Internal App</title>
<desc>Created with Sketch.</desc>
<g id="Icons-/-System-/-Open-/-Open-in-Internal-App" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Icon-Background" x="0" y="0" width="24" height="24"></rect>
<path d="M21,3 L21,19.5 L21.0070809,19.5 L21.0070809,20.9975284 L21,20.997 L21,21 L19.5,21 L19.5,20.997 L7.97261837,21 L9.45378213,19.5 L19.5,19.5 L19.5,7 L4.5,7 L4.5,14.7148938 L3,16.0495017 L3,3 L21,3 Z" id="Combined-Shape" fill="#FFFFFF"></path>
<path d="M13.5,17 L12,17 L12,13.063 L4.06066017,21.0033009 L3,19.9426407 L10.942,12 L7,12 L7,10.5 L13.5,10.5 L13.5,17 Z" id="Combined-Shape" fill="#FFFFFF"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 994 B

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 60.1 (88133) - https://sketch.com -->
<title>Icons / System / Open / Open in Internal App Hover</title>
<desc>Created with Sketch.</desc>
<g id="Icons-/-System-/-Open-/-Open-in-Internal-App-Hover" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Icon-Background" x="0" y="0" width="24" height="24"></rect>
<path d="M21,3 L21,19.5 L21.0070809,19.5 L21.0070809,20.9975284 L21,20.997 L21,21 L19.5,21 L19.5,20.997 L7.97261837,21 L9.45378213,19.5 L19.5,19.5 L19.5,7 L4.5,7 L4.5,14.7148938 L3,16.0495017 L3,3 L21,3 Z" id="Combined-Shape" fill="#17A3CD"></path>
<path d="M13.5,17 L12,17 L12,13.063 L4.06066017,21.0033009 L3,19.9426407 L10.942,12 L7,12 L7,10.5 L13.5,10.5 L13.5,17 Z" id="Combined-Shape" fill="#17A3CD"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1006 B

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Icons / System / Add</title>
<g id="Icons-/-System-/-Add" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Icon-Background" x="0" y="0" width="24" height="24"></rect>
<path d="M13,3 L13,11 L21,11 L21,13 L13,13 L13,21 L11,21 L11,13 L3,13 L3,11 L11,11 L11,3 L13,3 Z" id="Combined-Shape" fill="#FFFFFF" fill-rule="nonzero"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 583 B

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:99b78e940729a1615701479b9526ab5f7a153c34596fa8d128b869513536d10f
size 7424
@@ -0,0 +1,137 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
QSplitter#jobDialogSplitter {
qproperty-childrenCollapsible: False;
}
/* FilteredSearchWidget */
AzQtComponents--FilteredSearchWidget#jobFilteredSearchWidget,
AzQtComponents--FilteredSearchWidget#detailsFilterWidget,
AzQtComponents--FilteredSearchWidget#assetDataFilteredSearchWidget {
padding: 8px;
background-color: rgb(40,40,40);
qproperty-textFilterFillsWidth: False;
}
AzQtComponents--FilteredSearchWidget#assetDataFilteredSearchWidget {
padding: 8px;
background-color: rgb(40,40,40);
qproperty-textFilterFillsWidth: True;
}
AzQtComponents--FilteredSearchWidget #textSearchContainer,
AzQtComponents--FilteredSearchWidget #filteredParent,
AzQtComponents--FilteredSearchWidget #filteredLayout {
background-color: rgb(40,40,40);
}
AzQtComponents--FilteredSearchWidget QLineEdit {
background-color: #e9e9e9;
}
/* filteredParent is in FilteredSearchWidget */
AzQtComponents--FilteredSearchWidget #filteredParent {
margin-top: 6px;
}
/* Tighten things up a bit in the Asset Processor */
#jobFilteredSearchWidget AzQtComponents--FilterCriteriaButton,
#detailsFilterWidget AzQtComponents--FilterCriteriaButton
{
max-height: 22px;
}
/* Tighten things up a bit in the Asset Processor */
#jobFilteredSearchWidget AzQtComponents--FilterCriteriaButton QLabel,
#detailsFilterWidget AzQtComponents--FilterCriteriaButton QLabel
{
padding-left: 0px;
padding-right: 0px;
}
/* filteredLayout is in FilteredSearchWidget */
AzQtComponents--FilteredSearchWidget #filteredLayout {
/* Negative margin to negate the default FlowLayout margin */
margin: -4px;
}
#jobFilteredSearchWidget QTreeView,
#detailsFilterWidget QTreeView {
/* Apply a custom border to the inner tree view, to make it stand out a little */
background-color: rgb(34,34,34);
border: 1px solid rgb(70,70,70);
border-radius: 2px;
/* These filters show only one category for "Status" so remove the indentation and therefore the indicators */
qproperty-indentation: 0;
qproperty-expandsOnDoubleClick: 0;
}
#jobFilteredSearchWidget AzQtComponents--SearchTypeSelector,
#detailsFilterWidget AzQtComponents--SearchTypeSelector {
qproperty-lineEditSearchVisible: 0;
}
/* TableView */
AzQtComponents--TableView {
background-color: rgb(45,45,45); /* Odd row */
alternate-background-color: rgb(34,34,34); /* Even row */
selection-background-color: rgb(73,73,73);
}
AzQtComponents--TableView::branch:selected,
AzQtComponents--TableView::item:selected {
background: rgb(73,73,73);
}
AzQtComponents--TableView::branch:hover,
AzQtComponents--TableView::item:hover {
background: rgb(60,60,60);
}
AzQtComponents--TableView QHeaderView::section {
background-color: rgb(34,34,34);
}
AzQtComponents--TableView#jobTreeView {
margin-bottom: 0px;
}
QStackedWidget#jobLogStackedWidget {
margin-bottom: 0px;
}
.solo {
margin-top: 12px;
}
QFrame#jobLogLabel,
QFrame#jobContextLogLabel {
margin-top: 12px;
}
QSplitter:handle {
background-color: #222222;
}
QLabel#jobLogPlaceholderLabel,
QLabel#jobContextLogPlaceholderLabel {
background-color: rgb(34,34,34);
padding: 8px;
padding-top: 16px;
}
@@ -0,0 +1,18 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
/* We can't apply a margin to the logButton directly because of the PushButton style. Wrap it in a
* QFrame and apply the margin to that instead.
*/
QFrame#logButtonWrapper {
margin-top: 8px;
}
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9965c02521822e92baad09dde15064ac0f326d861533725de283bac6e0ce3618
size 108108
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:68f33fe204a433f8c765d524c9b9e42963f5d16c3442f36df87185bcb4555111
size 8686