/* * Copyright (c) Contributors to the Open 3D Engine Project. * For complete copyright and license terms please see the LICENSE at the root of this distribution. * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ #include #include #include #include #include namespace O3DE::ProjectManager { GemModel::GemModel(QObject* parent) : QStandardItemModel(parent) { m_selectionModel = new QItemSelectionModel(this, parent); } QItemSelectionModel* GemModel::GetSelectionModel() const { return m_selectionModel; } void GemModel::AddGem(const GemInfo& gemInfo) { if (FindIndexByNameString(gemInfo.m_name).isValid()) { // do not add gems with duplicate names // this can happen by mistake or when a gem repo has a gem with the same name as a local gem AZ_TracePrintf("GemModel", "Ignoring duplicate gem: %s", gemInfo.m_name.toUtf8().constData()); return; } QStandardItem* item = new QStandardItem(); item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); item->setData(gemInfo.m_name, RoleName); item->setData(gemInfo.m_displayName, RoleDisplayName); item->setData(gemInfo.m_creator, RoleCreator); item->setData(gemInfo.m_gemOrigin, RoleGemOrigin); item->setData(aznumeric_cast(gemInfo.m_platforms), RolePlatforms); item->setData(aznumeric_cast(gemInfo.m_types), RoleTypes); item->setData(gemInfo.m_summary, RoleSummary); item->setData(false, RoleWasPreviouslyAdded); item->setData(gemInfo.m_isAdded, RoleIsAdded); item->setData(gemInfo.m_directoryLink, RoleDirectoryLink); item->setData(gemInfo.m_documentationLink, RoleDocLink); item->setData(gemInfo.m_dependencies, RoleDependingGems); item->setData(gemInfo.m_version, RoleVersion); item->setData(gemInfo.m_lastUpdatedDate, RoleLastUpdated); item->setData(gemInfo.m_binarySizeInKB, RoleBinarySize); item->setData(gemInfo.m_features, RoleFeatures); item->setData(gemInfo.m_path, RolePath); item->setData(gemInfo.m_requirement, RoleRequirement); item->setData(gemInfo.m_downloadStatus, RoleDownloadStatus); item->setData(gemInfo.m_licenseText, RoleLicenseText); item->setData(gemInfo.m_licenseLink, RoleLicenseLink); appendRow(item); const QModelIndex modelIndex = index(rowCount()-1, 0); m_nameToIndexMap[gemInfo.m_displayName] = modelIndex; m_nameToIndexMap[gemInfo.m_name] = modelIndex; } void GemModel::Clear() { clear(); m_nameToIndexMap.clear(); } void GemModel::UpdateGemDependencies() { m_gemDependencyMap.clear(); m_gemReverseDependencyMap.clear(); for (auto iter = m_nameToIndexMap.begin(); iter != m_nameToIndexMap.end(); ++iter) { const QString& key = iter.key(); const QModelIndex modelIndex = iter.value(); QSet dependencies; GetAllDependingGems(modelIndex, dependencies); if (!dependencies.isEmpty()) { m_gemDependencyMap.insert(key, dependencies); } } for (auto iter = m_gemDependencyMap.begin(); iter != m_gemDependencyMap.end(); ++iter) { const QString& dependant = iter.key(); for (const QModelIndex& dependency : iter.value()) { const QString& dependencyName = dependency.data(RoleName).toString(); if (!m_gemReverseDependencyMap.contains(dependencyName)) { m_gemReverseDependencyMap.insert(dependencyName, QSet()); } m_gemReverseDependencyMap[dependencyName].insert(m_nameToIndexMap[dependant]); } } } QString GemModel::GetName(const QModelIndex& modelIndex) { return modelIndex.data(RoleName).toString(); } QString GemModel::GetDisplayName(const QModelIndex& modelIndex) { QString displayName = modelIndex.data(RoleDisplayName).toString(); if (displayName.isEmpty()) { return GetName(modelIndex); } else { return displayName; } } QString GemModel::GetCreator(const QModelIndex& modelIndex) { return modelIndex.data(RoleCreator).toString(); } GemInfo::GemOrigin GemModel::GetGemOrigin(const QModelIndex& modelIndex) { return static_cast(modelIndex.data(RoleGemOrigin).toInt()); } GemInfo::Platforms GemModel::GetPlatforms(const QModelIndex& modelIndex) { return static_cast(modelIndex.data(RolePlatforms).toInt()); } GemInfo::Types GemModel::GetTypes(const QModelIndex& modelIndex) { return static_cast(modelIndex.data(RoleTypes).toInt()); } GemInfo::DownloadStatus GemModel::GetDownloadStatus(const QModelIndex& modelIndex) { return static_cast(modelIndex.data(RoleDownloadStatus).toInt()); } QString GemModel::GetSummary(const QModelIndex& modelIndex) { return modelIndex.data(RoleSummary).toString(); } QString GemModel::GetDirectoryLink(const QModelIndex& modelIndex) { return modelIndex.data(RoleDirectoryLink).toString(); } QString GemModel::GetDocLink(const QModelIndex& modelIndex) { return modelIndex.data(RoleDocLink).toString(); } QModelIndex GemModel::FindIndexByNameString(const QString& nameString) const { const auto iterator = m_nameToIndexMap.find(nameString); if (iterator != m_nameToIndexMap.end()) { return iterator.value(); } return {}; } void GemModel::FindGemDisplayNamesByNameStrings(QStringList& inOutGemNames) { for (QString& name : inOutGemNames) { QModelIndex modelIndex = FindIndexByNameString(name); if (modelIndex.isValid()) { name = GetDisplayName(modelIndex); } } } QStringList GemModel::GetDependingGems(const QModelIndex& modelIndex) { return modelIndex.data(RoleDependingGems).toStringList(); } void GemModel::GetAllDependingGems(const QModelIndex& modelIndex, QSet& inOutGems) { QStringList dependencies = GetDependingGems(modelIndex); for (const QString& dependency : dependencies) { QModelIndex dependencyIndex = FindIndexByNameString(dependency); if (!inOutGems.contains(dependencyIndex)) { inOutGems.insert(dependencyIndex); GetAllDependingGems(dependencyIndex, inOutGems); } } } QStringList GemModel::GetDependingGemNames(const QModelIndex& modelIndex) { QStringList result = GetDependingGems(modelIndex); if (result.isEmpty()) { return {}; } FindGemDisplayNamesByNameStrings(result); return result; } QString GemModel::GetVersion(const QModelIndex& modelIndex) { return modelIndex.data(RoleVersion).toString(); } QString GemModel::GetLastUpdated(const QModelIndex& modelIndex) { return modelIndex.data(RoleLastUpdated).toString(); } int GemModel::GetBinarySizeInKB(const QModelIndex& modelIndex) { return modelIndex.data(RoleBinarySize).toInt(); } QStringList GemModel::GetFeatures(const QModelIndex& modelIndex) { return modelIndex.data(RoleFeatures).toStringList(); } QString GemModel::GetPath(const QModelIndex& modelIndex) { return modelIndex.data(RolePath).toString(); } QString GemModel::GetRequirement(const QModelIndex& modelIndex) { return modelIndex.data(RoleRequirement).toString(); } QString GemModel::GetLicenseText(const QModelIndex& modelIndex) { return modelIndex.data(RoleLicenseText).toString(); } QString GemModel::GetLicenseLink(const QModelIndex& modelIndex) { return modelIndex.data(RoleLicenseLink).toString(); } GemModel* GemModel::GetSourceModel(QAbstractItemModel* model) { GemSortFilterProxyModel* proxyModel = qobject_cast(model); if (proxyModel) { return proxyModel->GetSourceModel(); } else { return qobject_cast(model); } } const GemModel* GemModel::GetSourceModel(const QAbstractItemModel* model) { const GemSortFilterProxyModel* proxyModel = qobject_cast(model); if (proxyModel) { return proxyModel->GetSourceModel(); } else { return qobject_cast(model); } } bool GemModel::IsAdded(const QModelIndex& modelIndex) { return modelIndex.data(RoleIsAdded).toBool(); } bool GemModel::IsAddedDependency(const QModelIndex& modelIndex) { return modelIndex.data(RoleIsAddedDependency).toBool(); } void GemModel::SetIsAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded) { // get the gemName first, because the modelIndex data change after adding because of filters QString gemName = modelIndex.data(RoleName).toString(); model.setData(modelIndex, isAdded, RoleIsAdded); UpdateDependencies(model, gemName, isAdded); } bool GemModel::HasDependentGems(const QModelIndex& modelIndex) const { QVector dependentGems = GatherDependentGems(modelIndex); for (const QModelIndex& dependency : dependentGems) { if (IsAdded(dependency)) { return true; } } return false; } void GemModel::UpdateDependencies(QAbstractItemModel& model, const QString& gemName, bool isAdded) { GemModel* gemModel = GetSourceModel(&model); AZ_Assert(gemModel, "Failed to obtain GemModel"); QModelIndex modelIndex = gemModel->FindIndexByNameString(gemName); QVector dependencies = gemModel->GatherGemDependencies(modelIndex); uint32_t numChangedDependencies = 0; if (isAdded) { for (const QModelIndex& dependency : dependencies) { if (!IsAddedDependency(dependency)) { SetIsAddedDependency(*gemModel, dependency, true); // if the gem was already added then the state didn't really change if (!IsAdded(dependency)) { numChangedDependencies++; } } } } else { // still a dependency if some added gem depends on this one bool hasDependentGems = gemModel->HasDependentGems(modelIndex); if (IsAddedDependency(modelIndex) != hasDependentGems) { SetIsAddedDependency(*gemModel, modelIndex, hasDependentGems); } for (const QModelIndex& dependency : dependencies) { hasDependentGems = gemModel->HasDependentGems(dependency); if (IsAddedDependency(dependency) != hasDependentGems) { SetIsAddedDependency(*gemModel, dependency, hasDependentGems); // if the gem was already added then the state didn't really change if (!IsAdded(dependency)) { numChangedDependencies++; } } } } gemModel->emit gemStatusChanged(gemName, numChangedDependencies); } void GemModel::SetIsAddedDependency(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded) { model.setData(modelIndex, isAdded, RoleIsAddedDependency); } void GemModel::SetWasPreviouslyAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool wasAdded) { model.setData(modelIndex, wasAdded, RoleWasPreviouslyAdded); if (wasAdded) { // update all dependencies GemModel* gemModel = GetSourceModel(&model); AZ_Assert(gemModel, "Failed to obtain GemModel"); QVector dependencies = gemModel->GatherGemDependencies(modelIndex); for (const QModelIndex& dependency : dependencies) { SetWasPreviouslyAddedDependency(*gemModel, dependency, true); } } } void GemModel::SetWasPreviouslyAddedDependency(QAbstractItemModel& model, const QModelIndex& modelIndex, bool wasAdded) { model.setData(modelIndex, wasAdded, RoleWasPreviouslyAddedDependency); } bool GemModel::WasPreviouslyAdded(const QModelIndex& modelIndex) { return modelIndex.data(RoleWasPreviouslyAdded).toBool(); } bool GemModel::WasPreviouslyAddedDependency(const QModelIndex& modelIndex) { return modelIndex.data(RoleWasPreviouslyAddedDependency).toBool(); } bool GemModel::NeedsToBeAdded(const QModelIndex& modelIndex, bool includeDependencies) { bool previouslyAdded = modelIndex.data(RoleWasPreviouslyAdded).toBool(); bool added = modelIndex.data(RoleIsAdded).toBool(); if (includeDependencies) { previouslyAdded |= modelIndex.data(RoleWasPreviouslyAddedDependency).toBool(); added |= modelIndex.data(RoleIsAddedDependency).toBool(); } return !previouslyAdded && added; } bool GemModel::NeedsToBeRemoved(const QModelIndex& modelIndex, bool includeDependencies) { bool previouslyAdded = modelIndex.data(RoleWasPreviouslyAdded).toBool(); bool added = modelIndex.data(RoleIsAdded).toBool(); if (includeDependencies) { previouslyAdded |= modelIndex.data(RoleWasPreviouslyAddedDependency).toBool(); added |= modelIndex.data(RoleIsAddedDependency).toBool(); } return previouslyAdded && !added; } void GemModel::SetDownloadStatus(QAbstractItemModel& model, const QModelIndex& modelIndex, GemInfo::DownloadStatus status) { model.setData(modelIndex, status, RoleDownloadStatus); } bool GemModel::HasRequirement(const QModelIndex& modelIndex) { return !modelIndex.data(RoleRequirement).toString().isEmpty(); } bool GemModel::DoGemsToBeAddedHaveRequirements() const { for (int row = 0; row < rowCount(); ++row) { const QModelIndex modelIndex = index(row, 0); if (NeedsToBeAdded(modelIndex) && HasRequirement(modelIndex)) { return true; } } return false; } bool GemModel::HasDependentGemsToRemove() const { for (int row = 0; row < rowCount(); ++row) { const QModelIndex modelIndex = index(row, 0); if (GemModel::NeedsToBeRemoved(modelIndex, /*includeDependencies=*/true) && GemModel::WasPreviouslyAddedDependency(modelIndex)) { return true; } } return false; } QVector GemModel::GatherGemDependencies(const QModelIndex& modelIndex) const { QVector result; const QString& gemName = modelIndex.data(RoleName).toString(); if (m_gemDependencyMap.contains(gemName)) { for (const QModelIndex& dependency : m_gemDependencyMap[gemName]) { result.push_back(dependency); } } return result; } QVector GemModel::GatherDependentGems(const QModelIndex& modelIndex, bool addedOnly) const { QVector result; const QString& gemName = modelIndex.data(RoleName).toString(); if (m_gemReverseDependencyMap.contains(gemName)) { for (const QModelIndex& dependency : m_gemReverseDependencyMap[gemName]) { if (!addedOnly || GemModel::IsAdded(dependency)) { result.push_back(dependency); } } } return result; } QVector GemModel::GatherGemsToBeAdded(bool includeDependencies) const { QVector result; for (int row = 0; row < rowCount(); ++row) { const QModelIndex modelIndex = index(row, 0); if (NeedsToBeAdded(modelIndex, includeDependencies)) { result.push_back(modelIndex); } } return result; } QVector GemModel::GatherGemsToBeRemoved(bool includeDependencies) const { QVector result; for (int row = 0; row < rowCount(); ++row) { const QModelIndex modelIndex = index(row, 0); if (NeedsToBeRemoved(modelIndex, includeDependencies)) { result.push_back(modelIndex); } } return result; } int GemModel::TotalAddedGems(bool includeDependencies) const { int result = 0; for (int row = 0; row < rowCount(); ++row) { const QModelIndex modelIndex = index(row, 0); if (IsAdded(modelIndex) || (includeDependencies && IsAddedDependency(modelIndex))) { ++result; } } return result; } } // namespace O3DE::ProjectManager