[LYN-2522] Filtering for gem catalog (#867)

* Added sort filter proxy model for gem model that can filter based on name, gem origin, supported platform, features and/or types.
* Added new filter pane on the left with several filter categories for gem origin, type, platform and feature.
* Added filter category widget which is a collapsable generalized checkbox group that can interact with the proxy model and thus control filtering.
* Removed fixed size of the project manager. The application should always be resizable.
main
Benjamin Jillich 5 years ago committed by GitHub
parent 8d7a1130d0
commit 36c23b5d1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -12,6 +12,8 @@
#include <GemCatalog/GemCatalogScreen.h> #include <GemCatalog/GemCatalogScreen.h>
#include <PythonBindingsInterface.h> #include <PythonBindingsInterface.h>
#include <GemCatalog/GemSortFilterProxyModel.h>
#include <GemCatalog/GemFilterWidget.h>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QPushButton> #include <QPushButton>
@ -25,15 +27,18 @@ namespace O3DE::ProjectManager
: ScreenWidget(parent) : ScreenWidget(parent)
{ {
m_gemModel = new GemModel(this); m_gemModel = new GemModel(this);
GemSortFilterProxyModel* proxyModel = new GemSortFilterProxyModel(m_gemModel, this);
QVBoxLayout* vLayout = new QVBoxLayout(); QVBoxLayout* vLayout = new QVBoxLayout();
vLayout->setMargin(0); vLayout->setMargin(0);
vLayout->setSpacing(0);
setLayout(vLayout); setLayout(vLayout);
QHBoxLayout* hLayout = new QHBoxLayout(); QHBoxLayout* hLayout = new QHBoxLayout();
hLayout->setMargin(0);
vLayout->addLayout(hLayout); vLayout->addLayout(hLayout);
m_gemListView = new GemListView(m_gemModel, this); m_gemListView = new GemListView(proxyModel, proxyModel->GetSelectionModel(), this);
m_gemInspector = new GemInspector(m_gemModel, this); m_gemInspector = new GemInspector(m_gemModel, this);
m_gemInspector->setFixedWidth(320); m_gemInspector->setFixedWidth(320);
@ -56,8 +61,19 @@ namespace O3DE::ProjectManager
} }
#endif #endif
hLayout->addWidget(m_gemListView); GemFilterWidget* filterWidget = new GemFilterWidget(proxyModel);
filterWidget->setFixedWidth(250);
QVBoxLayout* middleVLayout = new QVBoxLayout();
middleVLayout->setMargin(0);
middleVLayout->setSpacing(0);
middleVLayout->addWidget(m_gemListView);
hLayout->addWidget(filterWidget);
hLayout->addLayout(middleVLayout);
hLayout->addWidget(m_gemInspector); hLayout->addWidget(m_gemInspector);
proxyModel->InvalidateFilter();
} }
QVector<GemInfo> GemCatalogScreen::GenerateTestData() QVector<GemInfo> GemCatalogScreen::GenerateTestData()
@ -73,10 +89,12 @@ namespace O3DE::ProjectManager
gem.m_documentationLink = "http://www.amazon.com"; gem.m_documentationLink = "http://www.amazon.com";
gem.m_dependingGemUuids = QStringList({"EMotionFX", "Atom"}); gem.m_dependingGemUuids = QStringList({"EMotionFX", "Atom"});
gem.m_conflictingGemUuids = QStringList({"Vegetation", "Camera", "ScriptCanvas", "CloudCanvas", "Networking"}); gem.m_conflictingGemUuids = QStringList({"Vegetation", "Camera", "ScriptCanvas", "CloudCanvas", "Networking"});
gem.m_types = (GemInfo::Code | GemInfo::Asset);
gem.m_version = "v1.01"; gem.m_version = "v1.01";
gem.m_lastUpdatedDate = "24th April 2021"; gem.m_lastUpdatedDate = "24th April 2021";
gem.m_binarySizeInKB = 40; gem.m_binarySizeInKB = 40;
gem.m_features = QStringList({"Animation", "Assets", "Physics"}); gem.m_features = QStringList({"Animation", "Assets", "Physics"});
gem.m_gemOrigin = GemInfo::O3DEFoundation;
result.push_back(gem); result.push_back(gem);
gem.m_name = "Atom"; gem.m_name = "Atom";

@ -9,6 +9,7 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* *
*/ */
#pragma once #pragma once
#if !defined(Q_MOC_RUN) #if !defined(Q_MOC_RUN)

@ -0,0 +1,412 @@
/*
* 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 <GemCatalog/GemFilterWidget.h>
#include <QButtonGroup>
#include <QCheckBox>
#include <QLabel>
#include <QMap>
#include <QLineEdit>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QPushButton>
namespace O3DE::ProjectManager
{
FilterCategoryWidget::FilterCategoryWidget(const QString& header,
const QVector<QString>& elementNames,
const QVector<int>& elementCounts,
bool showAllLessButton,
int defaultShowCount,
QWidget* parent)
: QWidget(parent)
, m_defaultShowCount(defaultShowCount)
{
AZ_Assert(elementNames.size() == elementCounts.size(), "Number of element names needs to match the counts.");
QVBoxLayout* vLayout = new QVBoxLayout();
setLayout(vLayout);
// Collapse button
QHBoxLayout* collapseLayout = new QHBoxLayout();
m_collapseButton = new QPushButton();
m_collapseButton->setCheckable(true);
m_collapseButton->setFlat(true);
m_collapseButton->setFocusPolicy(Qt::NoFocus);
m_collapseButton->setFixedWidth(s_collapseButtonSize);
m_collapseButton->setStyleSheet("border: 0px; border-radius: 0px;");
connect(m_collapseButton, &QPushButton::clicked, this, [=]()
{
UpdateCollapseState();
});
collapseLayout->addWidget(m_collapseButton);
// Category title
QLabel* headerLabel = new QLabel(header);
headerLabel->setStyleSheet("font-size: 11pt;");
collapseLayout->addWidget(headerLabel);
vLayout->addLayout(collapseLayout);
vLayout->addSpacing(5);
// Everything in the main widget will be collapsed/uncollapsed
{
m_mainWidget = new QWidget();
vLayout->addWidget(m_mainWidget);
QVBoxLayout* mainLayout = new QVBoxLayout();
mainLayout->setMargin(0);
mainLayout->setAlignment(Qt::AlignTop);
m_mainWidget->setLayout(mainLayout);
// Elements
m_buttonGroup = new QButtonGroup();
m_buttonGroup->setExclusive(false);
for (int i = 0; i < elementNames.size(); ++i)
{
QWidget* elementWidget = new QWidget();
QHBoxLayout* elementLayout = new QHBoxLayout();
elementLayout->setMargin(0);
elementWidget->setLayout(elementLayout);
QCheckBox* checkbox = new QCheckBox(elementNames[i]);
checkbox->setStyleSheet("font-size: 11pt;");
m_buttonGroup->addButton(checkbox);
elementLayout->addWidget(checkbox);
elementLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding));
QLabel* countLabel = new QLabel(QString::number(elementCounts[i]));
countLabel->setStyleSheet("font-size: 11pt; background-color: #333333; border-radius: 3px; color: #94D2FF;");
elementLayout->addWidget(countLabel);
m_elementWidgets.push_back(elementWidget);
mainLayout->addWidget(elementWidget);
}
// See more / less
if (showAllLessButton)
{
m_seeAllLessLabel = new LinkLabel();
connect(m_seeAllLessLabel, &LinkLabel::clicked, this, [=]()
{
m_seeAll = !m_seeAll;
UpdateSeeMoreLess();
});
mainLayout->addWidget(m_seeAllLessLabel);
}
else
{
mainLayout->addSpacing(5);
}
}
// Separating line
QFrame* hLine = new QFrame();
hLine->setFrameShape(QFrame::HLine);
hLine->setStyleSheet("color: #666666;");
vLayout->addWidget(hLine);
UpdateCollapseState();
UpdateSeeMoreLess();
}
void FilterCategoryWidget::UpdateCollapseState()
{
if (m_collapseButton->isChecked())
{
m_collapseButton->setIcon(QIcon(":/Resources/ArrowDownLine.svg"));
m_mainWidget->hide();
}
else
{
m_collapseButton->setIcon(QIcon(":/Resources/ArrowUpLine.svg"));
m_mainWidget->show();
}
}
void FilterCategoryWidget::UpdateSeeMoreLess()
{
if (!m_seeAllLessLabel)
{
return;
}
if (m_elementWidgets.isEmpty())
{
m_seeAllLessLabel->hide();
return;
}
else
{
m_seeAllLessLabel->show();
}
if (!m_seeAll)
{
m_seeAllLessLabel->setText("See all");
}
else
{
m_seeAllLessLabel->setText("See less");
}
int showCount = m_seeAll ? m_elementWidgets.size() : m_defaultShowCount;
showCount = AZ::GetMin(showCount, m_elementWidgets.size());
for (int i = 0; i < showCount; ++i)
{
m_elementWidgets[i]->show();
}
for (int i = showCount; i < m_elementWidgets.size(); ++i)
{
m_elementWidgets[i]->hide();
}
}
QButtonGroup* FilterCategoryWidget::GetButtonGroup()
{
return m_buttonGroup;
}
GemFilterWidget::GemFilterWidget(GemSortFilterProxyModel* filterProxyModel, QWidget* parent)
: QScrollArea(parent)
, m_filterProxyModel(filterProxyModel)
{
m_gemModel = m_filterProxyModel->GetSourceModel();
setWidgetResizable(true);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
QWidget* mainWidget = new QWidget();
setWidget(mainWidget);
m_mainLayout = new QVBoxLayout();
m_mainLayout->setAlignment(Qt::AlignTop);
mainWidget->setLayout(m_mainLayout);
QLabel* filterByLabel = new QLabel("Filter by");
filterByLabel->setStyleSheet("font-size: 15pt;");
m_mainLayout->addWidget(filterByLabel);
AddGemOriginFilter();
AddTypeFilter();
AddPlatformFilter();
AddFeatureFilter();
}
void GemFilterWidget::AddGemOriginFilter()
{
QVector<QString> elementNames;
QVector<int> elementCounts;
const int numGems = m_gemModel->rowCount();
for (int originIndex = 0; originIndex < GemInfo::NumGemOrigins; ++originIndex)
{
const GemInfo::GemOrigin gemOriginToBeCounted = static_cast<GemInfo::GemOrigin>(1 << originIndex);
int gemOriginCount = 0;
for (int gemIndex = 0; gemIndex < numGems; ++gemIndex)
{
const GemInfo::GemOrigin gemOrigin = m_gemModel->GetGemOrigin(m_gemModel->index(gemIndex, 0));
// Is the gem of the given origin?
if (gemOriginToBeCounted == gemOrigin)
{
gemOriginCount++;
}
}
elementNames.push_back(GemInfo::GetGemOriginString(gemOriginToBeCounted));
elementCounts.push_back(gemOriginCount);
}
FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Provider", elementNames, elementCounts, /*showAllLessButton=*/false);
m_mainLayout->addWidget(filterWidget);
const QList<QAbstractButton*> buttons = filterWidget->GetButtonGroup()->buttons();
for (int i = 0; i < buttons.size(); ++i)
{
const GemInfo::GemOrigin gemOrigin = static_cast<GemInfo::GemOrigin>(1 << i);
QAbstractButton* button = buttons[i];
connect(button, &QAbstractButton::toggled, this, [=](bool checked)
{
GemInfo::GemOrigins gemOrigins = m_filterProxyModel->GetGemOrigins();
if (checked)
{
gemOrigins |= gemOrigin;
}
else
{
gemOrigins &= ~gemOrigin;
}
m_filterProxyModel->SetGemOrigins(gemOrigins);
});
}
}
void GemFilterWidget::AddTypeFilter()
{
QVector<QString> elementNames;
QVector<int> elementCounts;
const int numGems = m_gemModel->rowCount();
for (int typeIndex = 0; typeIndex < GemInfo::NumTypes; ++typeIndex)
{
const GemInfo::Type type = static_cast<GemInfo::Type>(1 << typeIndex);
int typeGemCount = 0;
for (int gemIndex = 0; gemIndex < numGems; ++gemIndex)
{
const GemInfo::Types types = m_gemModel->GetTypes(m_gemModel->index(gemIndex, 0));
// Is type (Asset, Code, Tool) part of the gem?
if (types & type)
{
typeGemCount++;
}
}
elementNames.push_back(GemInfo::GetTypeString(type));
elementCounts.push_back(typeGemCount);
}
FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Type", elementNames, elementCounts, /*showAllLessButton=*/false);
m_mainLayout->addWidget(filterWidget);
const QList<QAbstractButton*> buttons = filterWidget->GetButtonGroup()->buttons();
for (int i = 0; i < buttons.size(); ++i)
{
const GemInfo::Type type = static_cast<GemInfo::Type>(1 << i);
QAbstractButton* button = buttons[i];
connect(button, &QAbstractButton::toggled, this, [=](bool checked)
{
GemInfo::Types types = m_filterProxyModel->GetTypes();
if (checked)
{
types |= type;
}
else
{
types &= ~type;
}
m_filterProxyModel->SetTypes(types);
});
}
}
void GemFilterWidget::AddPlatformFilter()
{
QVector<QString> elementNames;
QVector<int> elementCounts;
const int numGems = m_gemModel->rowCount();
for (int platformIndex = 0; platformIndex < GemInfo::NumPlatforms; ++platformIndex)
{
const GemInfo::Platform platform = static_cast<GemInfo::Platform>(1 << platformIndex);
int platformGemCount = 0;
for (int gemIndex = 0; gemIndex < numGems; ++gemIndex)
{
const GemInfo::Platforms platforms = m_gemModel->GetPlatforms(m_gemModel->index(gemIndex, 0));
// Is platform supported?
if (platforms & platform)
{
platformGemCount++;
}
}
elementNames.push_back(GemInfo::GetPlatformString(platform));
elementCounts.push_back(platformGemCount);
}
FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Supported Platforms", elementNames, elementCounts, /*showAllLessButton=*/false);
m_mainLayout->addWidget(filterWidget);
const QList<QAbstractButton*> buttons = filterWidget->GetButtonGroup()->buttons();
for (int i = 0; i < buttons.size(); ++i)
{
const GemInfo::Platform platform = static_cast<GemInfo::Platform>(1 << i);
QAbstractButton* button = buttons[i];
connect(button, &QAbstractButton::toggled, this, [=](bool checked)
{
GemInfo::Platforms platforms = m_filterProxyModel->GetPlatforms();
if (checked)
{
platforms |= platform;
}
else
{
platforms &= ~platform;
}
m_filterProxyModel->SetPlatforms(platforms);
});
}
}
void GemFilterWidget::AddFeatureFilter()
{
// Alphabetically sorted, unique features and their number of occurrences in the gem database.
QMap<QString, int> uniqueFeatureCounts;
const int numGems = m_gemModel->rowCount();
for (int gemIndex = 0; gemIndex < numGems; ++gemIndex)
{
const QStringList features = m_gemModel->GetFeatures(m_gemModel->index(gemIndex, 0));
for (const QString& feature : features)
{
if (!uniqueFeatureCounts.contains(feature))
{
uniqueFeatureCounts.insert(feature, 1);
}
else
{
int& featureeCount = uniqueFeatureCounts[feature];
featureeCount++;
}
}
}
QVector<QString> elementNames;
QVector<int> elementCounts;
for (auto iterator = uniqueFeatureCounts.begin(); iterator != uniqueFeatureCounts.end(); iterator++)
{
elementNames.push_back(iterator.key());
elementCounts.push_back(iterator.value());
}
FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Features", elementNames, elementCounts,
/*showAllLessButton=*/true, /*defaultShowCount=*/5);
m_mainLayout->addWidget(filterWidget);
const QList<QAbstractButton*> buttons = filterWidget->GetButtonGroup()->buttons();
for (int i = 0; i < buttons.size(); ++i)
{
const QString& feature = elementNames[i];
QAbstractButton* button = buttons[i];
connect(button, &QAbstractButton::toggled, this, [=](bool checked)
{
QSet<QString> features = m_filterProxyModel->GetFeatures();
if (checked)
{
features.insert(feature);
}
else
{
features.remove(feature);
}
m_filterProxyModel->SetFeatures(features);
});
}
}
} // namespace O3DE::ProjectManager

@ -0,0 +1,79 @@
/*
* 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 <LinkWidget.h>
#include <GemCatalog/GemSortFilterProxyModel.h>
#include <QScrollArea>
#include <QVBoxLayout>
#include <QWidget>
#include <QCheckBox>
#include <QVector>
#include <QPushButton>
#endif
QT_FORWARD_DECLARE_CLASS(QButtonGroup)
namespace O3DE::ProjectManager
{
class FilterCategoryWidget
: public QWidget
{
Q_OBJECT // AUTOMOC
public:
explicit FilterCategoryWidget(const QString& header,
const QVector<QString>& elementNames,
const QVector<int>& elementCounts,
bool showAllLessButton = true,
int defaultShowCount = 4,
QWidget* parent = nullptr);
QButtonGroup* GetButtonGroup();
private:
void UpdateCollapseState();
void UpdateSeeMoreLess();
inline constexpr static int s_collapseButtonSize = 16;
QPushButton* m_collapseButton = nullptr;
QWidget* m_mainWidget = nullptr;
QButtonGroup* m_buttonGroup = nullptr;
QVector<QWidget*> m_elementWidgets; //! Includes checkbox and the count labl.
LinkLabel* m_seeAllLessLabel = nullptr;
int m_defaultShowCount = 0;
bool m_seeAll = false;
};
class GemFilterWidget
: public QScrollArea
{
Q_OBJECT // AUTOMOC
public:
explicit GemFilterWidget(GemSortFilterProxyModel* filterProxyModel, QWidget* parent = nullptr);
~GemFilterWidget() = default;
private:
void AddGemOriginFilter();
void AddTypeFilter();
void AddPlatformFilter();
void AddFeatureFilter();
QVBoxLayout* m_mainLayout = nullptr;
GemModel* m_gemModel = nullptr;
GemSortFilterProxyModel* m_filterProxyModel = nullptr;
};
} // namespace O3DE::ProjectManager

@ -62,6 +62,19 @@ namespace O3DE::ProjectManager
} }
} }
QString GemInfo::GetGemOriginString(GemOrigin origin)
{
switch (origin)
{
case O3DEFoundation:
return "Open 3D Foundation";
case Local:
return "Local";
default:
return "<Unknown Gem Origin>";
}
}
bool GemInfo::IsPlatformSupported(Platform platform) const bool GemInfo::IsPlatformSupported(Platform platform) const
{ {
return (m_platforms & platform); return (m_platforms & platform);

@ -46,6 +46,15 @@ namespace O3DE::ProjectManager
Q_DECLARE_FLAGS(Types, Type) Q_DECLARE_FLAGS(Types, Type)
static QString GetTypeString(Type type); static QString GetTypeString(Type type);
enum GemOrigin
{
O3DEFoundation = 1 << 0,
Local = 1 << 1,
NumGemOrigins = 2
};
Q_DECLARE_FLAGS(GemOrigins, GemOrigin)
static QString GetGemOriginString(GemOrigin origin);
GemInfo() = default; GemInfo() = default;
GemInfo(const QString& name, const QString& creator, const QString& summary, Platforms platforms, bool isAdded); GemInfo(const QString& name, const QString& creator, const QString& summary, Platforms platforms, bool isAdded);
bool IsPlatformSupported(Platform platform) const; bool IsPlatformSupported(Platform platform) const;
@ -57,6 +66,7 @@ namespace O3DE::ProjectManager
QString m_displayName; QString m_displayName;
AZ::Uuid m_uuid; AZ::Uuid m_uuid;
QString m_creator; QString m_creator;
GemOrigin m_gemOrigin = Local;
bool m_isAdded = false; //! Is the gem currently added and enabled in the project? bool m_isAdded = false; //! Is the gem currently added and enabled in the project?
QString m_summary; QString m_summary;
Platforms m_platforms; Platforms m_platforms;
@ -74,3 +84,4 @@ namespace O3DE::ProjectManager
Q_DECLARE_OPERATORS_FOR_FLAGS(O3DE::ProjectManager::GemInfo::Platforms) Q_DECLARE_OPERATORS_FOR_FLAGS(O3DE::ProjectManager::GemInfo::Platforms)
Q_DECLARE_OPERATORS_FOR_FLAGS(O3DE::ProjectManager::GemInfo::Types) Q_DECLARE_OPERATORS_FOR_FLAGS(O3DE::ProjectManager::GemInfo::Types)
Q_DECLARE_OPERATORS_FOR_FLAGS(O3DE::ProjectManager::GemInfo::GemOrigins)

@ -70,8 +70,8 @@ namespace O3DE::ProjectManager
m_documentationLinkLabel->SetUrl(m_model->GetDocLink(modelIndex)); m_documentationLinkLabel->SetUrl(m_model->GetDocLink(modelIndex));
// Depending and conflicting gems // Depending and conflicting gems
m_dependingGems->Update("Depending Gems", "The following Gems will be automatically enabled with this Gem.", m_model->GetDependingGems(modelIndex)); m_dependingGems->Update("Depending Gems", "The following Gems will be automatically enabled with this Gem.", m_model->GetDependingGemNames(modelIndex));
m_conflictingGems->Update("Conflicting Gems", "The following Gems will be automatically disabled with this Gem.", m_model->GetConflictingGems(modelIndex)); m_conflictingGems->Update("Conflicting Gems", "The following Gems will be automatically disabled with this Gem.", m_model->GetConflictingGemNames(modelIndex));
// Additional information // Additional information
m_versionLabel->setText(QString("Gem Version: %1").arg(m_model->GetVersion(modelIndex))); m_versionLabel->setText(QString("Gem Version: %1").arg(m_model->GetVersion(modelIndex)));

@ -10,7 +10,7 @@
* *
*/ */
#include "GemItemDelegate.h" #include <GemCatalog/GemItemDelegate.h>
#include "GemModel.h" #include "GemModel.h"
#include <QEvent> #include <QEvent>
#include <QPainter> #include <QPainter>
@ -18,9 +18,9 @@
namespace O3DE::ProjectManager namespace O3DE::ProjectManager
{ {
GemItemDelegate::GemItemDelegate(GemModel* gemModel, QObject* parent) GemItemDelegate::GemItemDelegate(QAbstractItemModel* model, QObject* parent)
: QStyledItemDelegate(parent) : QStyledItemDelegate(parent)
, m_gemModel(gemModel) , m_model(model)
{ {
AddPlatformIcon(GemInfo::Android, ":/Android.svg"); AddPlatformIcon(GemInfo::Android, ":/Android.svg");
AddPlatformIcon(GemInfo::iOS, ":/iOS.svg"); AddPlatformIcon(GemInfo::iOS, ":/iOS.svg");
@ -78,7 +78,7 @@ namespace O3DE::ProjectManager
} }
// Gem name // Gem name
const QString gemName = m_gemModel->GetName(modelIndex); const QString gemName = GemModel::GetName(modelIndex);
QFont gemNameFont(options.font); QFont gemNameFont(options.font);
gemNameFont.setPixelSize(s_gemNameFontSize); gemNameFont.setPixelSize(s_gemNameFontSize);
gemNameFont.setBold(true); gemNameFont.setBold(true);
@ -90,7 +90,7 @@ namespace O3DE::ProjectManager
painter->drawText(gemNameRect, Qt::TextSingleLine, gemName); painter->drawText(gemNameRect, Qt::TextSingleLine, gemName);
// Gem creator // Gem creator
const QString gemCreator = m_gemModel->GetCreator(modelIndex); const QString gemCreator = GemModel::GetCreator(modelIndex);
QRect gemCreatorRect = GetTextRect(standardFont, gemCreator, s_fontSize); QRect gemCreatorRect = GetTextRect(standardFont, gemCreator, s_fontSize);
gemCreatorRect.moveTo(contentRect.left(), contentRect.top() + gemNameRect.height()); gemCreatorRect.moveTo(contentRect.left(), contentRect.top() + gemNameRect.height());
@ -105,7 +105,7 @@ namespace O3DE::ProjectManager
painter->setFont(standardFont); painter->setFont(standardFont);
painter->setPen(m_textColor); painter->setPen(m_textColor);
const QString summary = m_gemModel->GetSummary(modelIndex); const QString summary = GemModel::GetSummary(modelIndex);
painter->drawText(summaryRect, Qt::AlignLeft | Qt::TextWordWrap, summary); painter->drawText(summaryRect, Qt::AlignLeft | Qt::TextWordWrap, summary);
@ -158,7 +158,7 @@ namespace O3DE::ProjectManager
void GemItemDelegate::DrawPlatformIcons(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const void GemItemDelegate::DrawPlatformIcons(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const
{ {
const GemInfo::Platforms platforms = m_gemModel->GetPlatforms(modelIndex); const GemInfo::Platforms platforms = GemModel::GetPlatforms(modelIndex);
int startX = 0; int startX = 0;
// Iterate and draw the platforms in the order they are defined in the enum. // Iterate and draw the platforms in the order they are defined in the enum.
@ -188,7 +188,7 @@ namespace O3DE::ProjectManager
QPoint circleCenter; QPoint circleCenter;
QString buttonText; QString buttonText;
const bool isAdded = m_gemModel->IsAdded(modelIndex); const bool isAdded = GemModel::IsAdded(modelIndex);
if (isAdded) if (isAdded)
{ {
painter->setBrush(m_buttonEnabledColor); painter->setBrush(m_buttonEnabledColor);

@ -15,7 +15,7 @@
#if !defined(Q_MOC_RUN) #if !defined(Q_MOC_RUN)
#include <QStyledItemDelegate> #include <QStyledItemDelegate>
#include "GemInfo.h" #include "GemInfo.h"
#include "GemModel.h" #include <QAbstractItemModel>
#include <QHash> #include <QHash>
#endif #endif
@ -29,22 +29,13 @@ namespace O3DE::ProjectManager
Q_OBJECT // AUTOMOC Q_OBJECT // AUTOMOC
public: public:
explicit GemItemDelegate(GemModel* gemModel, QObject* parent = nullptr); explicit GemItemDelegate(QAbstractItemModel* model, QObject* parent = nullptr);
~GemItemDelegate() = default; ~GemItemDelegate() = default;
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const override; void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const override;
bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) override; bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) override;
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const override; QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const override;
private:
void CalcRects(const QStyleOptionViewItem& option, const QModelIndex& modelIndex, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const;
QRect GetTextRect(QFont& font, const QString& text, qreal fontSize) const;
QRect CalcButtonRect(const QRect& contentRect) const;
void DrawPlatformIcons(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const;
void DrawButton(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const;
GemModel* m_gemModel = nullptr;
// Colors // Colors
const QColor m_textColor = QColor("#FFFFFF"); const QColor m_textColor = QColor("#FFFFFF");
const QColor m_linkColor = QColor("#94D2FF"); const QColor m_linkColor = QColor("#94D2FF");
@ -71,6 +62,15 @@ namespace O3DE::ProjectManager
inline constexpr static int s_buttonCircleRadius = s_buttonBorderRadius - 3; inline constexpr static int s_buttonCircleRadius = s_buttonBorderRadius - 3;
inline constexpr static qreal s_buttonFontSize = 12.0; inline constexpr static qreal s_buttonFontSize = 12.0;
private:
void CalcRects(const QStyleOptionViewItem& option, const QModelIndex& modelIndex, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const;
QRect GetTextRect(QFont& font, const QString& text, qreal fontSize) const;
QRect CalcButtonRect(const QRect& contentRect) const;
void DrawPlatformIcons(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const;
void DrawButton(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const;
QAbstractItemModel* m_model = nullptr;
// Platform icons // Platform icons
void AddPlatformIcon(GemInfo::Platform platform, const QString& iconPath); void AddPlatformIcon(GemInfo::Platform platform, const QString& iconPath);
inline constexpr static int s_platformIconSize = 16; inline constexpr static int s_platformIconSize = 16;

@ -18,17 +18,15 @@
namespace O3DE::ProjectManager namespace O3DE::ProjectManager
{ {
GemListView::GemListView(GemModel* model, QWidget *parent) : GemListView::GemListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QWidget* parent)
QListView(parent) : QListView(parent)
{ {
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
QPalette palette; setStyleSheet("background-color: #333333;");
palette.setColor(QPalette::Window, QColor("#333333"));
setPalette(palette);
setModel(model); setModel(model);
setSelectionModel(model->GetSelectionModel()); setSelectionModel(selectionModel);
setItemDelegate(new GemItemDelegate(model, this)); setItemDelegate(new GemItemDelegate(model, this));
} }
} // namespace O3DE::ProjectManager } // namespace O3DE::ProjectManager

@ -14,7 +14,8 @@
#if !defined(Q_MOC_RUN) #if !defined(Q_MOC_RUN)
#include "GemInfo.h" #include "GemInfo.h"
#include "GemModel.h" #include <QAbstractItemModel>
#include <QItemSelectionModel>
#include <QListView> #include <QListView>
#endif #endif
@ -24,8 +25,9 @@ namespace O3DE::ProjectManager
: public QListView : public QListView
{ {
Q_OBJECT // AUTOMOC Q_OBJECT // AUTOMOC
public: public:
explicit GemListView(GemModel* model, QWidget *parent = nullptr); explicit GemListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QWidget* parent = nullptr);
~GemListView() = default; ~GemListView() = default;
}; };
} // namespace O3DE::ProjectManager } // namespace O3DE::ProjectManager

@ -36,11 +36,11 @@ namespace O3DE::ProjectManager
const QString uuidString = gemInfo.m_uuid.ToString<AZStd::string>().c_str(); const QString uuidString = gemInfo.m_uuid.ToString<AZStd::string>().c_str();
item->setData(uuidString, RoleUuid); item->setData(uuidString, RoleUuid);
item->setData(gemInfo.m_creator, RoleCreator); item->setData(gemInfo.m_creator, RoleCreator);
item->setData(gemInfo.m_gemOrigin, RoleGemOrigin);
item->setData(aznumeric_cast<int>(gemInfo.m_platforms), RolePlatforms); item->setData(aznumeric_cast<int>(gemInfo.m_platforms), RolePlatforms);
item->setData(aznumeric_cast<int>(gemInfo.m_types), RoleTypes); item->setData(aznumeric_cast<int>(gemInfo.m_types), RoleTypes);
item->setData(gemInfo.m_summary, RoleSummary); item->setData(gemInfo.m_summary, RoleSummary);
item->setData(gemInfo.m_isAdded, RoleIsAdded); item->setData(gemInfo.m_isAdded, RoleIsAdded);
item->setData(gemInfo.m_directoryLink, RoleDirectoryLink); item->setData(gemInfo.m_directoryLink, RoleDirectoryLink);
item->setData(gemInfo.m_documentationLink, RoleDocLink); item->setData(gemInfo.m_documentationLink, RoleDocLink);
item->setData(gemInfo.m_dependingGemUuids, RoleDependingGems); item->setData(gemInfo.m_dependingGemUuids, RoleDependingGems);
@ -48,12 +48,12 @@ namespace O3DE::ProjectManager
item->setData(gemInfo.m_version, RoleVersion); item->setData(gemInfo.m_version, RoleVersion);
item->setData(gemInfo.m_lastUpdatedDate, RoleLastUpdated); item->setData(gemInfo.m_lastUpdatedDate, RoleLastUpdated);
item->setData(gemInfo.m_binarySizeInKB, RoleBinarySize); item->setData(gemInfo.m_binarySizeInKB, RoleBinarySize);
item->setData(gemInfo.m_features, RoleFeatures); item->setData(gemInfo.m_features, RoleFeatures);
appendRow(item); appendRow(item);
m_uuidToNameMap[uuidString] = gemInfo.m_displayName; const QModelIndex modelIndex = index(rowCount()-1, 0);
m_uuidToIndexMap[uuidString] = modelIndex;
} }
void GemModel::Clear() void GemModel::Clear()
@ -71,6 +71,11 @@ namespace O3DE::ProjectManager
return modelIndex.data(RoleCreator).toString(); return modelIndex.data(RoleCreator).toString();
} }
GemInfo::GemOrigin GemModel::GetGemOrigin(const QModelIndex& modelIndex)
{
return static_cast<GemInfo::GemOrigin>(modelIndex.data(RoleGemOrigin).toInt());
}
QString GemModel::GetUuidString(const QModelIndex& modelIndex) QString GemModel::GetUuidString(const QModelIndex& modelIndex)
{ {
return modelIndex.data(RoleUuid).toString(); return modelIndex.data(RoleUuid).toString();
@ -106,42 +111,63 @@ namespace O3DE::ProjectManager
return modelIndex.data(RoleDocLink).toString(); return modelIndex.data(RoleDocLink).toString();
} }
AZ::Outcome<QString> GemModel::FindGemNameByUuidString(const QString& uuidString) const QModelIndex GemModel::FindIndexByUuidString(const QString& uuidString) const
{ {
const auto iterator = m_uuidToNameMap.find(uuidString); const auto iterator = m_uuidToIndexMap.find(uuidString);
if (iterator != m_uuidToNameMap.end()) if (iterator != m_uuidToIndexMap.end())
{ {
return AZ::Success(iterator.value()); return iterator.value();
} }
return AZ::Failure(); return {};
} }
QStringList GemModel::GetDependingGems(const QModelIndex& modelIndex) void GemModel::FindGemNamesByUuidStrings(QStringList& inOutGemNames)
{ {
QStringList result = modelIndex.data(RoleDependingGems).toStringList(); for (QString& dependingGemString : inOutGemNames)
if (result.isEmpty())
{ {
return {}; QModelIndex modelIndex = FindIndexByUuidString(dependingGemString);
if (modelIndex.isValid())
{
dependingGemString = GetName(modelIndex);
}
}
} }
for (QString& dependingGemString : result) QStringList GemModel::GetDependingGemUuids(const QModelIndex& modelIndex)
{
AZ::Outcome<QString> gemNameOutcome = FindGemNameByUuidString(dependingGemString);
if (gemNameOutcome.IsSuccess())
{ {
dependingGemString = gemNameOutcome.GetValue(); return modelIndex.data(RoleDependingGems).toStringList();
} }
QStringList GemModel::GetDependingGemNames(const QModelIndex& modelIndex)
{
QStringList result = GetDependingGemUuids(modelIndex);
if (result.isEmpty())
{
return {};
} }
FindGemNamesByUuidStrings(result);
return result; return result;
} }
QStringList GemModel::GetConflictingGems(const QModelIndex& modelIndex) QStringList GemModel::GetConflictingGemUuids(const QModelIndex& modelIndex)
{ {
return modelIndex.data(RoleConflictingGems).toStringList(); return modelIndex.data(RoleConflictingGems).toStringList();
} }
QStringList GemModel::GetConflictingGemNames(const QModelIndex& modelIndex)
{
QStringList result = GetConflictingGemUuids(modelIndex);
if (result.isEmpty())
{
return {};
}
FindGemNamesByUuidStrings(result);
return result;
}
QString GemModel::GetVersion(const QModelIndex& modelIndex) QString GemModel::GetVersion(const QModelIndex& modelIndex)
{ {
return modelIndex.data(RoleVersion).toString(); return modelIndex.data(RoleVersion).toString();

@ -13,7 +13,6 @@
#pragma once #pragma once
#if !defined(Q_MOC_RUN) #if !defined(Q_MOC_RUN)
#include <AzCore/Outcome/Outcome.h>
#include <GemCatalog/GemInfo.h> #include <GemCatalog/GemInfo.h>
#include <QAbstractItemModel> #include <QAbstractItemModel>
#include <QStandardItemModel> #include <QStandardItemModel>
@ -34,11 +33,16 @@ namespace O3DE::ProjectManager
void AddGem(const GemInfo& gemInfo); void AddGem(const GemInfo& gemInfo);
void Clear(); void Clear();
AZ::Outcome<QString> FindGemNameByUuidString(const QString& uuidString) const; QModelIndex FindIndexByUuidString(const QString& uuidString) const;
QStringList GetDependingGems(const QModelIndex& modelIndex); void FindGemNamesByUuidStrings(QStringList& inOutGemNames);
QStringList GetDependingGemUuids(const QModelIndex& modelIndex);
QStringList GetDependingGemNames(const QModelIndex& modelIndex);
QStringList GetConflictingGemUuids(const QModelIndex& modelIndex);
QStringList GetConflictingGemNames(const QModelIndex& modelIndex);
static QString GetName(const QModelIndex& modelIndex); static QString GetName(const QModelIndex& modelIndex);
static QString GetCreator(const QModelIndex& modelIndex); static QString GetCreator(const QModelIndex& modelIndex);
static GemInfo::GemOrigin GetGemOrigin(const QModelIndex& modelIndex);
static QString GetUuidString(const QModelIndex& modelIndex); static QString GetUuidString(const QModelIndex& modelIndex);
static GemInfo::Platforms GetPlatforms(const QModelIndex& modelIndex); static GemInfo::Platforms GetPlatforms(const QModelIndex& modelIndex);
static GemInfo::Types GetTypes(const QModelIndex& modelIndex); static GemInfo::Types GetTypes(const QModelIndex& modelIndex);
@ -46,7 +50,6 @@ namespace O3DE::ProjectManager
static bool IsAdded(const QModelIndex& modelIndex); static bool IsAdded(const QModelIndex& modelIndex);
static QString GetDirectoryLink(const QModelIndex& modelIndex); static QString GetDirectoryLink(const QModelIndex& modelIndex);
static QString GetDocLink(const QModelIndex& modelIndex); static QString GetDocLink(const QModelIndex& modelIndex);
static QStringList GetConflictingGems(const QModelIndex& modelIndex);
static QString GetVersion(const QModelIndex& modelIndex); static QString GetVersion(const QModelIndex& modelIndex);
static QString GetLastUpdated(const QModelIndex& modelIndex); static QString GetLastUpdated(const QModelIndex& modelIndex);
static int GetBinarySizeInKB(const QModelIndex& modelIndex); static int GetBinarySizeInKB(const QModelIndex& modelIndex);
@ -58,6 +61,7 @@ namespace O3DE::ProjectManager
RoleName = Qt::UserRole, RoleName = Qt::UserRole,
RoleUuid, RoleUuid,
RoleCreator, RoleCreator,
RoleGemOrigin,
RolePlatforms, RolePlatforms,
RoleSummary, RoleSummary,
RoleIsAdded, RoleIsAdded,
@ -72,7 +76,7 @@ namespace O3DE::ProjectManager
RoleTypes RoleTypes
}; };
QHash<QString, QString> m_uuidToNameMap; QHash<QString, QModelIndex> m_uuidToIndexMap;
QItemSelectionModel* m_selectionModel = nullptr; QItemSelectionModel* m_selectionModel = nullptr;
}; };
} // namespace O3DE::ProjectManager } // namespace O3DE::ProjectManager

@ -0,0 +1,133 @@
/*
* 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 <GemCatalog/GemSortFilterProxyModel.h>
#include <QItemSelectionModel>
namespace O3DE::ProjectManager
{
GemSortFilterProxyModel::GemSortFilterProxyModel(GemModel* sourceModel, QObject* parent)
: QSortFilterProxyModel(parent)
, m_sourceModel(sourceModel)
{
setSourceModel(sourceModel);
m_selectionProxyModel = new AzQtComponents::SelectionProxyModel(sourceModel->GetSelectionModel(), this, parent);
}
bool GemSortFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
{
// Do not use sourceParent->child because an invalid parent does not produce valid children (which our index function does)
QModelIndex sourceIndex = sourceModel()->index(sourceRow, 0, sourceParent);
if (!sourceIndex.isValid())
{
return false;
}
if (!m_sourceModel->GetName(sourceIndex).contains(m_searchString, Qt::CaseInsensitive))
{
return false;
}
// Gem origins
if (m_gemOriginFilter)
{
bool supportsAnyFilteredGemOrigin = false;
for (int i = 0; i < GemInfo::NumGemOrigins; ++i)
{
const GemInfo::GemOrigin filteredGemOrigin = static_cast<GemInfo::GemOrigin>(1 << i);
if (m_gemOriginFilter & filteredGemOrigin)
{
if ((GemModel::GetGemOrigin(sourceIndex) == filteredGemOrigin))
{
supportsAnyFilteredGemOrigin = true;
break;
}
}
}
if (!supportsAnyFilteredGemOrigin)
{
return false;
}
}
// Platform
if (m_platformFilter)
{
bool supportsAnyFilteredPlatform = false;
for (int i = 0; i < GemInfo::NumPlatforms; ++i)
{
const GemInfo::Platform filteredPlatform = static_cast<GemInfo::Platform>(1 << i);
if (m_platformFilter & filteredPlatform)
{
if ((GemModel::GetPlatforms(sourceIndex) & filteredPlatform))
{
supportsAnyFilteredPlatform = true;
break;
}
}
}
if (!supportsAnyFilteredPlatform)
{
return false;
}
}
// Types (Asset, Code, Tool)
if (m_typeFilter)
{
bool supportsAnyFilteredType = false;
for (int i = 0; i < GemInfo::NumTypes; ++i)
{
const GemInfo::Type filteredType = static_cast<GemInfo::Type>(1 << i);
if (m_typeFilter & filteredType)
{
if ((GemModel::GetTypes(sourceIndex) & filteredType))
{
supportsAnyFilteredType = true;
break;
}
}
}
if (!supportsAnyFilteredType)
{
return false;
}
}
// Features
if (!m_featureFilter.isEmpty())
{
bool containsFilterFeature = false;
const QStringList features = m_sourceModel->GetFeatures(sourceIndex);
for (const QString& feature : features)
{
if (m_featureFilter.contains(feature))
{
containsFilterFeature = true;
break;
}
}
if (!containsFilterFeature)
{
return false;
}
}
return true;
}
void GemSortFilterProxyModel::InvalidateFilter()
{
invalidate();
emit OnInvalidated();
}
} // namespace O3DE::ProjectManager

@ -0,0 +1,68 @@
/*
* 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 <AzQtComponents/Utilities/SelectionProxyModel.h>
#include <GemCatalog/GemModel.h>
#include <QtCore/QSortFilterProxyModel>
#include <QSet>
#endif
QT_FORWARD_DECLARE_CLASS(QItemSelectionModel)
namespace O3DE::ProjectManager
{
class GemSortFilterProxyModel
: public QSortFilterProxyModel
{
Q_OBJECT // AUTOMOC
public:
GemSortFilterProxyModel(GemModel* sourceModel, QObject* parent = nullptr);
bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override;
GemModel* GetSourceModel() const { return m_sourceModel; }
AzQtComponents::SelectionProxyModel* GetSelectionModel() const { return m_selectionProxyModel; }
void SetSearchString(const QString& searchString) { m_searchString = searchString; InvalidateFilter(); }
GemInfo::GemOrigins GetGemOrigins() const { return m_gemOriginFilter; }
void SetGemOrigins(const GemInfo::GemOrigins& gemOrigins) { m_gemOriginFilter = gemOrigins; InvalidateFilter(); }
GemInfo::Platforms GetPlatforms() const { return m_platformFilter; }
void SetPlatforms(const GemInfo::Platforms& platforms) { m_platformFilter = platforms; InvalidateFilter(); }
GemInfo::Types GetTypes() const { return m_typeFilter; }
void SetTypes(const GemInfo::Types& types) { m_typeFilter = types; InvalidateFilter(); }
const QSet<QString>& GetFeatures() const { return m_featureFilter; }
void SetFeatures(const QSet<QString>& features) { m_featureFilter = features; InvalidateFilter(); }
void InvalidateFilter();
signals:
void OnInvalidated();
private:
GemModel* m_sourceModel = nullptr;
AzQtComponents::SelectionProxyModel* m_selectionProxyModel = nullptr;
QString m_searchString;
GemInfo::GemOrigins m_gemOriginFilter = {};
GemInfo::Platforms m_platformFilter = {};
GemInfo::Types m_typeFilter = {};
QSet<QString> m_featureFilter;
};
} // namespace O3DE::ProjectManager

@ -32,8 +32,6 @@ namespace O3DE::ProjectManager
layout->setSpacing(0); layout->setSpacing(0);
layout->setContentsMargins(0, 0, 0, 0); layout->setContentsMargins(0, 0, 0, 0);
setFixedSize(this->geometry().width(), this->geometry().height());
m_pythonBindings = AZStd::make_unique<PythonBindings>(engineRootPath); m_pythonBindings = AZStd::make_unique<PythonBindings>(engineRootPath);
m_screensCtrl = new ScreensCtrl(); m_screensCtrl = new ScreensCtrl();

@ -11,7 +11,7 @@
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed"> <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>

@ -57,6 +57,8 @@ set(FILES
Source/TagWidget.cpp Source/TagWidget.cpp
Source/GemCatalog/GemCatalogScreen.h Source/GemCatalog/GemCatalogScreen.h
Source/GemCatalog/GemCatalogScreen.cpp Source/GemCatalog/GemCatalogScreen.cpp
Source/GemCatalog/GemFilterWidget.h
Source/GemCatalog/GemFilterWidget.cpp
Source/GemCatalog/GemInfo.h Source/GemCatalog/GemInfo.h
Source/GemCatalog/GemInfo.cpp Source/GemCatalog/GemInfo.cpp
Source/GemCatalog/GemInspector.h Source/GemCatalog/GemInspector.h
@ -67,4 +69,6 @@ set(FILES
Source/GemCatalog/GemListView.cpp Source/GemCatalog/GemListView.cpp
Source/GemCatalog/GemModel.h Source/GemCatalog/GemModel.h
Source/GemCatalog/GemModel.cpp Source/GemCatalog/GemModel.cpp
Source/GemCatalog/GemSortFilterProxyModel.h
Source/GemCatalog/GemSortFilterProxyModel.cpp
) )

Loading…
Cancel
Save