GemModel.cpp 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AzCore/std/string/string.h>
  9. #include <AzCore/Dependency/Dependency.h>
  10. #include <GemCatalog/GemModel.h>
  11. #include <GemCatalog/GemSortFilterProxyModel.h>
  12. #include <AzCore/Casting/numeric_cast.h>
  13. #include <AzToolsFramework/UI/Notifications/ToastBus.h>
  14. #include <ProjectUtils.h>
  15. #include <QDir>
  16. #include <QList>
  17. namespace O3DE::ProjectManager
  18. {
  19. GemModel::GemModel(QObject* parent)
  20. : QStandardItemModel(parent)
  21. {
  22. m_selectionModel = new QItemSelectionModel(this, parent);
  23. connect(this, &QAbstractItemModel::rowsAboutToBeRemoved, this, &GemModel::OnRowsAboutToBeRemoved);
  24. connect(this, &QAbstractItemModel::rowsRemoved, this, &GemModel::OnRowsRemoved);
  25. }
  26. QItemSelectionModel* GemModel::GetSelectionModel() const
  27. {
  28. return m_selectionModel;
  29. }
  30. void SetItemDataFromGemInfo(QStandardItem* item, const GemInfo& gemInfo, bool metaDataOnly = false)
  31. {
  32. item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
  33. item->setData(gemInfo.m_name, GemModel::RoleName);
  34. item->setData(gemInfo.m_displayName, GemModel::RoleDisplayName);
  35. item->setData(gemInfo.m_dependencies, GemModel::RoleDependingGems);
  36. item->setData(gemInfo.m_version, GemModel::RoleVersion);
  37. item->setData(gemInfo.m_downloadStatus, GemModel::RoleDownloadStatus);
  38. if (!metaDataOnly)
  39. {
  40. item->setData(false, GemModel::RoleWasPreviouslyAdded);
  41. item->setData(gemInfo.m_isAdded, GemModel::RoleIsAdded);
  42. item->setData("", GemModel::RoleNewVersion);
  43. }
  44. }
  45. bool AddGemInfoVersion(QStandardItem* item, const GemInfo& gemInfo, [[maybe_unused]] bool update)
  46. {
  47. QList<QVariant> versionList;
  48. auto variant = item->data(GemModel::RoleGemInfoVersions);
  49. if (variant.isValid())
  50. {
  51. versionList = variant.value<QList<QVariant>>();
  52. }
  53. QVariant gemVariant;
  54. gemVariant.setValue(gemInfo);
  55. int versionToReplaceIndex = -1;
  56. for (int i = 0; i < versionList.size(); ++i)
  57. {
  58. const GemInfo& existingGemInfo = versionList.at(i).value<GemInfo>();
  59. if (existingGemInfo.m_version == gemInfo.m_version)
  60. {
  61. if(existingGemInfo.m_downloadStatus == GemInfo::NotDownloaded ||
  62. existingGemInfo.m_downloadStatus == GemInfo::DownloadFailed)
  63. {
  64. // gems that haven't been downloaded may have empty paths
  65. // always update data from the server
  66. versionToReplaceIndex = i;
  67. break;
  68. // once a gem has been downloaded we rely on the data on disk
  69. // and don't override it with remote data
  70. }
  71. else if (gemInfo.m_downloadStatus == GemInfo::NotDownloaded ||
  72. gemInfo.m_downloadStatus == GemInfo::DownloadFailed)
  73. {
  74. // never overwrite a downloaded version with a remote version
  75. return false;
  76. }
  77. else if (QDir(existingGemInfo.m_path) == QDir(gemInfo.m_path))
  78. {
  79. versionToReplaceIndex = i;
  80. break;
  81. }
  82. }
  83. else if (!existingGemInfo.m_path.isEmpty() && !gemInfo.m_path.isEmpty() &&
  84. QDir(existingGemInfo.m_path) == QDir(gemInfo.m_path))
  85. {
  86. // data on disk changed and version don't match anymore
  87. versionToReplaceIndex = i;
  88. break;
  89. }
  90. }
  91. if(versionToReplaceIndex != -1)
  92. {
  93. versionList.replace(versionToReplaceIndex, gemVariant);
  94. }
  95. else
  96. {
  97. versionList.append(gemVariant);
  98. }
  99. // it's possible a remote gem with a higher version gets added after a downloaded gem with a lower version
  100. // so sort by version if we have enough entries
  101. if (versionList.size() > 1)
  102. {
  103. std::sort(
  104. versionList.begin(),
  105. versionList.end(),
  106. [](const QVariant& a, const QVariant& b) -> bool
  107. {
  108. return ProjectUtils::VersionCompare(a.value<GemInfo>().m_version, b.value<GemInfo>().m_version) > 0;
  109. });
  110. }
  111. item->setData(versionList, GemModel::RoleGemInfoVersions);
  112. return true;
  113. }
  114. bool RemoveGemInfoVersion(QStandardItem* item, const QString& version, const QString& path)
  115. {
  116. QVariant variant = item->data(GemModel::RoleGemInfoVersions);
  117. auto versionList = variant.isValid() ? variant.value<QList<QVariant>>() : QList<QVariant>();
  118. const bool removeByPath = !path.isEmpty();
  119. QDir dir{ path };
  120. for (int i = 0; i < versionList.size(); ++i)
  121. {
  122. const QVariant& existingGemVariant = versionList.at(i);
  123. const GemInfo& existingGemInfo = existingGemVariant.value<GemInfo>();
  124. if (removeByPath)
  125. {
  126. if (QDir(existingGemInfo.m_path) == dir)
  127. {
  128. versionList.removeAt(i);
  129. break;
  130. }
  131. }
  132. else if (existingGemInfo.m_version == version)
  133. {
  134. // there could be multiple instances of the same version
  135. versionList.removeAt(i);
  136. }
  137. }
  138. item->setData(versionList, GemModel::RoleGemInfoVersions);
  139. return versionList.isEmpty();
  140. }
  141. bool GemModel::ShouldUpdateItemDataFromGemInfo(const QModelIndex& modelIndex, const GemInfo& gemInfo)
  142. {
  143. // get the most compatible version or empty string if none are compatible
  144. const QString mostCompatibleVersion = GetMostCompatibleVersion(modelIndex);
  145. const bool newVersionIsCompatible = gemInfo.IsCompatible();
  146. int versionResult = ProjectUtils::VersionCompare(gemInfo.m_version, modelIndex.data(RoleVersion).toString());
  147. if (mostCompatibleVersion.isEmpty() && !newVersionIsCompatible)
  148. {
  149. // no compatible versions available (yet) so refresh if version is the same or higher
  150. return versionResult >= 0;
  151. }
  152. const bool oldVersionIsCompatible = VersionIsCompatible(modelIndex, modelIndex.data(RoleVersion).toString());
  153. return (versionResult > 0 && newVersionIsCompatible) || // new higher version is compatible
  154. (versionResult == 0) || // version the same
  155. (!oldVersionIsCompatible && newVersionIsCompatible); // old version wasn't compatible but new is
  156. }
  157. QVector<QPersistentModelIndex> GemModel::AddGems(const QVector<GemInfo>& gemInfos, bool updateExisting)
  158. {
  159. QVector<QPersistentModelIndex> indexesChanged;
  160. const int initialNumRows = rowCount();
  161. // block dataChanged signal if we are adding a bunch of stuff
  162. // to avoid sending a ton of signals that might cause large UI updates
  163. // and slows us down till we are done
  164. blockSignals(true);
  165. for (const auto& gemInfo : gemInfos)
  166. {
  167. // ${Name} is a special name used in templates and should not be shown
  168. // Though potentially it should be swapped out with the name of the Project being created
  169. if (gemInfo.m_name == "${Name}")
  170. {
  171. continue;
  172. }
  173. auto modelIndex = FindIndexByNameString(gemInfo.m_name);
  174. if (modelIndex.isValid())
  175. {
  176. auto gemItem = itemFromIndex(modelIndex);
  177. AZ_Assert(gemItem, "Failed to retrieve existing gem item from model index");
  178. const bool updatedExistingInfo = AddGemInfoVersion(gemItem, gemInfo, updateExisting);
  179. if (updatedExistingInfo && ShouldUpdateItemDataFromGemInfo(modelIndex, gemInfo))
  180. {
  181. SetItemDataFromGemInfo(gemItem, gemInfo, /*metaDataOnly=*/ true);
  182. }
  183. indexesChanged.append(modelIndex);
  184. }
  185. else
  186. {
  187. auto gemItem = new QStandardItem();
  188. SetItemDataFromGemInfo(gemItem, gemInfo);
  189. AddGemInfoVersion(gemItem, gemInfo, updateExisting);
  190. appendRow(gemItem);
  191. modelIndex = index(rowCount() - 1, 0);
  192. indexesChanged.append(modelIndex);
  193. m_nameToIndexMap[gemInfo.m_name] = modelIndex;
  194. }
  195. }
  196. blockSignals(false);
  197. // send a single dataChanged signal now that we've added everything
  198. // this does not include rows that were changed and not added
  199. const int startRow = AZStd::max(0, initialNumRows - 1);
  200. const int endRow = AZStd::max(0, rowCount() - 1);
  201. emit dataChanged(index(startRow, 0), index(endRow, 0));
  202. return indexesChanged;
  203. }
  204. void GemModel::ActivateGems(const QHash<QString, QString>& enabledGemNames)
  205. {
  206. // block dataChanged signal if we are modifying a bunch of data
  207. // to avoid sending a many signals that might cause large UI updates
  208. // and slows us down till we are done
  209. blockSignals(true);
  210. for (auto itr = enabledGemNames.cbegin(); itr != enabledGemNames.cend(); itr++)
  211. {
  212. const QString& gemPath = itr.value();
  213. const QString& gemNameWithSpecifier = itr.key();
  214. QString gemName, gemVersion;
  215. ProjectUtils::Comparison comparator;
  216. ProjectUtils::GetDependencyNameAndVersion(gemNameWithSpecifier, gemName, comparator, gemVersion);
  217. if (gemName == "${Name}")
  218. {
  219. // ${Name} is a special name used in templates and is replaced with a real gem name later
  220. // in theory we could replace the name here with the project gem's name
  221. continue;
  222. }
  223. if (auto nameFoundIter = m_nameToIndexMap.find(gemName); nameFoundIter != m_nameToIndexMap.end())
  224. {
  225. const QModelIndex modelIndex = nameFoundIter.value();
  226. QStandardItem* gemItem = itemFromIndex(modelIndex);
  227. AZ_Assert(gemItem, "Failed to retrieve enabled gem item from model index");
  228. GemInfo gemInfo = GetGemInfo(modelIndex, gemVersion, gemPath);
  229. if (!gemInfo.IsValid())
  230. {
  231. // This gem version info is missing, but the project uses it so show it to the user
  232. // so they can remove it or change versions if they want to
  233. // In the future we want to let the user browse to this gem's location on disk, or
  234. // let them download it
  235. gemInfo.m_name = gemName;
  236. gemInfo.m_displayName = gemName;
  237. gemInfo.m_version = gemVersion;
  238. gemInfo.m_summary = QString("This project uses %1 but a compatible gem was not found, or has not been registered yet.")
  239. .arg(gemNameWithSpecifier);
  240. gemInfo.m_isAdded = true;
  241. AddGemInfoVersion(gemItem, gemInfo, /*updateExisting=*/false);
  242. }
  243. SetItemDataFromGemInfo(gemItem, gemInfo);
  244. // Set Added/PreviouslyAdded after potentially updating data these settings
  245. GemModel::SetWasPreviouslyAdded(*this, modelIndex, true);
  246. GemModel::SetIsAdded(*this, modelIndex, true);
  247. continue;
  248. }
  249. // This gem info is missing, but the project uses it so show it to the user
  250. // so they can remove it if they want to
  251. // In the future we want to let the user browse to this gem's location on disk, or
  252. // let them download it
  253. GemInfo gemInfo;
  254. gemInfo.m_name = gemName;
  255. gemInfo.m_displayName = gemName;
  256. gemInfo.m_version = gemVersion;
  257. gemInfo.m_summary = QString("This project uses %1 but a compatible gem was not found, or has not been registered yet.").arg(gemNameWithSpecifier);
  258. gemInfo.m_isAdded = true;
  259. QStandardItem* gemItem = new QStandardItem();
  260. SetItemDataFromGemInfo(gemItem, gemInfo);
  261. AddGemInfoVersion(gemItem, gemInfo, /*updateExisting=*/false);
  262. appendRow(gemItem);
  263. const auto modelIndex = index(rowCount() - 1, 0);
  264. GemModel::SetWasPreviouslyAdded(*this, modelIndex, true);
  265. GemModel::SetIsAdded(*this, modelIndex, true);
  266. m_nameToIndexMap[gemInfo.m_name] = modelIndex;
  267. AZ_Warning("ProjectManager::GemCatalog", false,
  268. "Cannot find entry for gem with name '%s'. The CMake target name probably does not match the specified name in the gem.json.",
  269. gemName.toUtf8().constData());
  270. }
  271. blockSignals(false);
  272. // send a single dataChanged signal now that we've added everything
  273. emit dataChanged(index(0, 0), index(AZStd::max(0, rowCount() - 1), 0));
  274. }
  275. QPersistentModelIndex GemModel::AddGem(const GemInfo& gemInfo)
  276. {
  277. if (const auto& indexes = AddGems({gemInfo}); !indexes.isEmpty())
  278. {
  279. return indexes.at(0);
  280. }
  281. return index(rowCount()-1, 0);
  282. }
  283. void GemModel::RemoveGem(const QModelIndex& modelIndex)
  284. {
  285. removeRow(modelIndex.row());
  286. }
  287. void GemModel::RemoveGem(const QString& gemName, const QString& version, const QString& path)
  288. {
  289. auto nameFind = m_nameToIndexMap.find(gemName);
  290. if (nameFind != m_nameToIndexMap.end())
  291. {
  292. if (!version.isEmpty() || !path.isEmpty())
  293. {
  294. const bool removedAllVersions = RemoveGemInfoVersion(itemFromIndex(nameFind.value()), version, path);
  295. if (removedAllVersions)
  296. {
  297. removeRow(nameFind->row());
  298. }
  299. }
  300. else
  301. {
  302. removeRow(nameFind->row());
  303. }
  304. }
  305. }
  306. void GemModel::Clear()
  307. {
  308. clear();
  309. m_nameToIndexMap.clear();
  310. }
  311. void GemModel::UpdateGemDependencies()
  312. {
  313. m_gemDependencyMap.clear();
  314. m_gemReverseDependencyMap.clear();
  315. for (auto iter = m_nameToIndexMap.begin(); iter != m_nameToIndexMap.end(); ++iter)
  316. {
  317. const QString& key = iter.key();
  318. const QModelIndex modelIndex = iter.value();
  319. QSet<QPersistentModelIndex> dependencies;
  320. GetAllDependingGems(modelIndex, dependencies);
  321. if (!dependencies.isEmpty())
  322. {
  323. m_gemDependencyMap.insert(key, dependencies);
  324. }
  325. }
  326. for (auto iter = m_gemDependencyMap.begin(); iter != m_gemDependencyMap.end(); ++iter)
  327. {
  328. const QString& dependant = iter.key();
  329. for (const QModelIndex& dependency : iter.value())
  330. {
  331. const QString& dependencyName = dependency.data(RoleName).toString();
  332. if (!m_gemReverseDependencyMap.contains(dependencyName))
  333. {
  334. m_gemReverseDependencyMap.insert(dependencyName, QSet<QPersistentModelIndex>());
  335. }
  336. m_gemReverseDependencyMap[dependencyName].insert(m_nameToIndexMap[dependant]);
  337. }
  338. }
  339. }
  340. const GemInfo GemModel::GetGemInfo(const QPersistentModelIndex& modelIndex, const QString& version, const QString& path)
  341. {
  342. const auto& versionList = modelIndex.data(RoleGemInfoVersions).value<QList<QVariant>>();
  343. const QString& gemVersion = modelIndex.data(RoleVersion).toString();
  344. if (versionList.isEmpty())
  345. {
  346. return {};
  347. }
  348. else if (gemVersion.isEmpty() && version.isEmpty() && path.isEmpty())
  349. {
  350. // the currently displayed version has no version info so just return it
  351. return versionList.at(0).value<GemInfo>();
  352. }
  353. bool usePath = !path.isEmpty();
  354. bool useVersion = !version.isEmpty();
  355. bool useCurrentVersion = !useVersion && !usePath;
  356. for (const auto& versionVariant : versionList)
  357. {
  358. // there may be multiple instances of the same gem with the same version
  359. // at different paths
  360. const GemInfo& gemInfo = versionVariant.value<GemInfo>();
  361. const QString& variantVersion = gemInfo.m_version;
  362. const QString& variantPath = gemInfo.m_path;
  363. // if no version is provided, try to find the one that matches the current version
  364. // if a path and/or version is provided try to find an exact match
  365. if ((useCurrentVersion && gemVersion == variantVersion) ||
  366. (usePath && QFileInfo(variantPath) == QFileInfo(path)) ||
  367. (!usePath && useVersion && variantVersion == version))
  368. {
  369. return gemInfo;
  370. }
  371. }
  372. // no gem info found for this version
  373. return {};
  374. }
  375. const QList<QVariant> GemModel::GetGemVersions(const QModelIndex& modelIndex)
  376. {
  377. return modelIndex.data(RoleGemInfoVersions).value<QList<QVariant>>();
  378. }
  379. QString GemModel::GetName(const QModelIndex& modelIndex)
  380. {
  381. return modelIndex.data(RoleName).toString();
  382. }
  383. QString GemModel::GetDisplayName(const QModelIndex& modelIndex)
  384. {
  385. QString displayName = modelIndex.data(RoleDisplayName).toString();
  386. if (displayName.isEmpty())
  387. {
  388. return GetName(modelIndex);
  389. }
  390. else
  391. {
  392. return displayName;
  393. }
  394. }
  395. GemInfo::DownloadStatus GemModel::GetDownloadStatus(const QModelIndex& modelIndex)
  396. {
  397. return static_cast<GemInfo::DownloadStatus>(modelIndex.data(RoleDownloadStatus).toInt());
  398. }
  399. QPersistentModelIndex GemModel::FindIndexByNameString(const QString& nameString) const
  400. {
  401. const auto iterator = m_nameToIndexMap.find(nameString);
  402. if (iterator != m_nameToIndexMap.end())
  403. {
  404. return iterator.value();
  405. }
  406. return {};
  407. }
  408. QStringList GemModel::GetDependingGems(const QModelIndex& modelIndex)
  409. {
  410. return modelIndex.data(RoleDependingGems).toStringList();
  411. }
  412. void GemModel::OnRowsRemoved(const QModelIndex& parent, int first, [[maybe_unused]] int last)
  413. {
  414. // fix up the name to index map for all rows that changed
  415. for (int row = first; row < rowCount(); ++row)
  416. {
  417. const QModelIndex modelIndex = index(row, 0, parent);
  418. m_nameToIndexMap[GetName(modelIndex)] = modelIndex;
  419. }
  420. }
  421. void GemModel::GetAllDependingGems(const QModelIndex& modelIndex, QSet<QPersistentModelIndex>& inOutGems)
  422. {
  423. QStringList dependencies = GetDependingGems(modelIndex);
  424. for (const QString& dependency : dependencies)
  425. {
  426. QModelIndex dependencyIndex = FindIndexByNameString(dependency);
  427. if (!inOutGems.contains(dependencyIndex))
  428. {
  429. inOutGems.insert(dependencyIndex);
  430. GetAllDependingGems(dependencyIndex, inOutGems);
  431. }
  432. }
  433. }
  434. QVector<Tag> GemModel::GetDependingGemTags(const QModelIndex& modelIndex)
  435. {
  436. QVector<Tag> tags;
  437. QStringList dependingGemNames = GetDependingGems(modelIndex);
  438. tags.reserve(dependingGemNames.size());
  439. for (QString& gemName : dependingGemNames)
  440. {
  441. const QModelIndex dependingIndex = FindIndexByNameString(gemName);
  442. if (dependingIndex.isValid())
  443. {
  444. tags.push_back({ GetDisplayName(dependingIndex), GetName(dependingIndex) });
  445. }
  446. }
  447. return tags;
  448. }
  449. QString GemModel::GetVersion(const QModelIndex& modelIndex)
  450. {
  451. return modelIndex.data(RoleVersion).toString();
  452. }
  453. QString GemModel::GetNewVersion(const QModelIndex& modelIndex)
  454. {
  455. return modelIndex.data(RoleNewVersion).toString();
  456. }
  457. QString GemModel::GetMostCompatibleVersion(const QModelIndex& modelIndex)
  458. {
  459. const auto& versionList = modelIndex.data(RoleGemInfoVersions).value<QList<QVariant>>();
  460. if (versionList.isEmpty())
  461. {
  462. return {};
  463. }
  464. // versions are sorted from highest to lowest so return the first compatible version
  465. for (const auto& versionVariant : versionList)
  466. {
  467. const GemInfo& variantGemInfo = versionVariant.value<GemInfo>();
  468. if(variantGemInfo.IsCompatible())
  469. {
  470. return variantGemInfo.m_version;
  471. }
  472. }
  473. // no compatible version found
  474. return {};
  475. }
  476. bool GemModel::VersionIsCompatible(const QModelIndex& modelIndex, const QString& version)
  477. {
  478. return GemModel::GetGemInfo(modelIndex, version).IsCompatible();
  479. }
  480. GemModel* GemModel::GetSourceModel(QAbstractItemModel* model)
  481. {
  482. GemSortFilterProxyModel* proxyModel = qobject_cast<GemSortFilterProxyModel*>(model);
  483. if (proxyModel)
  484. {
  485. return proxyModel->GetSourceModel();
  486. }
  487. else
  488. {
  489. return qobject_cast<GemModel*>(model);
  490. }
  491. }
  492. const GemModel* GemModel::GetSourceModel(const QAbstractItemModel* model)
  493. {
  494. const GemSortFilterProxyModel* proxyModel = qobject_cast<const GemSortFilterProxyModel*>(model);
  495. if (proxyModel)
  496. {
  497. return proxyModel->GetSourceModel();
  498. }
  499. else
  500. {
  501. return qobject_cast<const GemModel*>(model);
  502. }
  503. }
  504. bool GemModel::IsAdded(const QModelIndex& modelIndex)
  505. {
  506. return modelIndex.data(RoleIsAdded).toBool();
  507. }
  508. bool GemModel::IsAddedDependency(const QModelIndex& modelIndex)
  509. {
  510. return modelIndex.data(RoleIsAddedDependency).toBool();
  511. }
  512. void GemModel::SetIsAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded, const QString& version)
  513. {
  514. // get the gemName first, because the modelIndex data change after adding because of filters
  515. QString gemName = modelIndex.data(RoleName).toString();
  516. model.setData(modelIndex, isAdded, RoleIsAdded);
  517. if (!version.isEmpty())
  518. {
  519. QString gemVersion = modelIndex.data(RoleVersion).toString();
  520. model.setData(modelIndex, version == gemVersion ? "" : version, RoleNewVersion);
  521. }
  522. UpdateDependencies(model, gemName, isAdded);
  523. }
  524. void GemModel::SetNewVersion(QAbstractItemModel& model, const QModelIndex& modelIndex, const QString& version)
  525. {
  526. model.setData(modelIndex, version, RoleNewVersion);
  527. }
  528. bool GemModel::HasDependentGems(const QModelIndex& modelIndex) const
  529. {
  530. auto dependentGems = GatherDependentGems(modelIndex);
  531. for (const QModelIndex& dependency : dependentGems)
  532. {
  533. if (IsAdded(dependency))
  534. {
  535. return true;
  536. }
  537. }
  538. return false;
  539. }
  540. void GemModel::UpdateDependencies(QAbstractItemModel& model, const QString& gemName, bool isAdded)
  541. {
  542. GemModel* gemModel = GetSourceModel(&model);
  543. AZ_Assert(gemModel, "Failed to obtain GemModel");
  544. auto modelIndex = gemModel->FindIndexByNameString(gemName);
  545. auto dependencies = gemModel->GatherGemDependencies(modelIndex);
  546. uint32_t numChangedDependencies = 0;
  547. if (isAdded)
  548. {
  549. for (const QModelIndex& dependency : dependencies)
  550. {
  551. if (!IsAddedDependency(dependency))
  552. {
  553. SetIsAddedDependency(*gemModel, dependency, true);
  554. // if the gem was already added then the state didn't really change
  555. if (!IsAdded(dependency))
  556. {
  557. numChangedDependencies++;
  558. const QString dependencyName = gemModel->GetName(dependency);
  559. gemModel->emit dependencyGemStatusChanged(dependencyName);
  560. }
  561. }
  562. }
  563. }
  564. else
  565. {
  566. // still a dependency if some added gem depends on this one
  567. bool hasDependentGems = gemModel->HasDependentGems(modelIndex);
  568. if (IsAddedDependency(modelIndex) != hasDependentGems)
  569. {
  570. SetIsAddedDependency(*gemModel, modelIndex, hasDependentGems);
  571. }
  572. for (const auto& dependency : dependencies)
  573. {
  574. hasDependentGems = gemModel->HasDependentGems(dependency);
  575. if (IsAddedDependency(dependency) != hasDependentGems)
  576. {
  577. SetIsAddedDependency(*gemModel, dependency, hasDependentGems);
  578. // if the gem was already added then the state didn't really change
  579. if (!IsAdded(dependency))
  580. {
  581. numChangedDependencies++;
  582. const QString dependencyName = gemModel->GetName(dependency);
  583. gemModel->emit dependencyGemStatusChanged(dependencyName);
  584. }
  585. }
  586. }
  587. }
  588. gemModel->emit gemStatusChanged(gemName, numChangedDependencies);
  589. }
  590. void GemModel::UpdateWithVersion(QAbstractItemModel& model, const QPersistentModelIndex& modelIndex, const QString& version, const QString& path)
  591. {
  592. GemModel* gemModel = GetSourceModel(&model);
  593. AZ_Assert(gemModel, "Failed to obtain GemModel");
  594. AZ_Assert(&model == modelIndex.model(), "Model is different - did you use the proxy or selection model instead of source?");
  595. AZ_Assert(modelIndex.isValid(), "Invalid model index");
  596. auto gemItem = gemModel->itemFromIndex(modelIndex);
  597. AZ_Assert(gemItem, "Failed to obtain gem model item");
  598. SetItemDataFromGemInfo(gemItem, GetGemInfo(modelIndex, version, path), /*metaDataOnly*/ true);
  599. }
  600. void GemModel::OnRowsAboutToBeRemoved(const QModelIndex& parent, int first, int last)
  601. {
  602. bool selectedRowRemoved = false;
  603. for (int i = first; i <= last; ++i)
  604. {
  605. QModelIndex modelIndex = index(i, 0, parent);
  606. const QString& gemName = GetName(modelIndex);
  607. m_nameToIndexMap.remove(gemName);
  608. if (GetSelectionModel()->isRowSelected(i))
  609. {
  610. selectedRowRemoved = true;
  611. }
  612. }
  613. // Select a valid row if currently selected row was removed
  614. if (selectedRowRemoved)
  615. {
  616. for (const QModelIndex& index : m_nameToIndexMap)
  617. {
  618. if (index.isValid())
  619. {
  620. GetSelectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
  621. break;
  622. }
  623. }
  624. }
  625. }
  626. void GemModel::SetIsAddedDependency(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded)
  627. {
  628. model.setData(modelIndex, isAdded, RoleIsAddedDependency);
  629. }
  630. void GemModel::SetWasPreviouslyAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool wasAdded)
  631. {
  632. model.setData(modelIndex, wasAdded, RoleWasPreviouslyAdded);
  633. if (wasAdded)
  634. {
  635. // update all dependencies
  636. GemModel* gemModel = GetSourceModel(&model);
  637. AZ_Assert(gemModel, "Failed to obtain GemModel");
  638. auto dependencies = gemModel->GatherGemDependencies(modelIndex);
  639. for (const QModelIndex& dependency : dependencies)
  640. {
  641. SetWasPreviouslyAddedDependency(*gemModel, dependency, true);
  642. }
  643. }
  644. }
  645. void GemModel::SetWasPreviouslyAddedDependency(QAbstractItemModel& model, const QModelIndex& modelIndex, bool wasAdded)
  646. {
  647. model.setData(modelIndex, wasAdded, RoleWasPreviouslyAddedDependency);
  648. }
  649. bool GemModel::WasPreviouslyAdded(const QModelIndex& modelIndex)
  650. {
  651. return modelIndex.data(RoleWasPreviouslyAdded).toBool();
  652. }
  653. bool GemModel::WasPreviouslyAddedDependency(const QModelIndex& modelIndex)
  654. {
  655. return modelIndex.data(RoleWasPreviouslyAddedDependency).toBool();
  656. }
  657. bool GemModel::NeedsToBeAdded(const QModelIndex& modelIndex, bool includeDependencies)
  658. {
  659. bool previouslyAdded = modelIndex.data(RoleWasPreviouslyAdded).toBool();
  660. bool added = modelIndex.data(RoleIsAdded).toBool();
  661. QString newVersion = modelIndex.data(RoleNewVersion).toString();
  662. if (includeDependencies)
  663. {
  664. previouslyAdded |= modelIndex.data(RoleWasPreviouslyAddedDependency).toBool();
  665. added |= modelIndex.data(RoleIsAddedDependency).toBool();
  666. }
  667. return (!previouslyAdded && added) || (added && !newVersion.isEmpty());
  668. }
  669. bool GemModel::NeedsToBeRemoved(const QModelIndex& modelIndex, bool includeDependencies)
  670. {
  671. bool previouslyAdded = modelIndex.data(RoleWasPreviouslyAdded).toBool();
  672. bool added = modelIndex.data(RoleIsAdded).toBool();
  673. if (includeDependencies)
  674. {
  675. previouslyAdded |= modelIndex.data(RoleWasPreviouslyAddedDependency).toBool();
  676. added |= modelIndex.data(RoleIsAddedDependency).toBool();
  677. }
  678. return previouslyAdded && !added;
  679. }
  680. void GemModel::DeactivateDependentGems(QAbstractItemModel& model, const QModelIndex& modelIndex)
  681. {
  682. GemModel* gemModel = GetSourceModel(&model);
  683. AZ_Assert(gemModel, "Failed to obtain GemModel");
  684. auto dependentGems = gemModel->GatherDependentGems(modelIndex);
  685. if (!dependentGems.isEmpty())
  686. {
  687. // we need to deactivate all gems that depend on this one
  688. for (auto dependentModelIndex : dependentGems)
  689. {
  690. SetIsAdded(model, dependentModelIndex, false);
  691. }
  692. }
  693. }
  694. void GemModel::ShowCompatibleGems()
  695. {
  696. for (int row = 0; row < rowCount(); ++row)
  697. {
  698. const QModelIndex modelIndex = index(row, 0);
  699. const GemInfo& gemInfo = GetGemInfo(modelIndex);
  700. if (!gemInfo.IsCompatible() && !IsAdded(modelIndex))
  701. {
  702. // does a compatible version exist?
  703. QString compatibleVersion = GetMostCompatibleVersion(modelIndex);
  704. if(!compatibleVersion.isEmpty())
  705. {
  706. // show the compatible version
  707. UpdateWithVersion(*this, modelIndex, compatibleVersion);
  708. }
  709. }
  710. }
  711. }
  712. void GemModel::SetDownloadStatus(QAbstractItemModel& model, const QModelIndex& modelIndex, GemInfo::DownloadStatus status)
  713. {
  714. model.setData(modelIndex, status, RoleDownloadStatus);
  715. }
  716. bool GemModel::HasRequirement(const QModelIndex& modelIndex)
  717. {
  718. return !GemModel::GetGemInfo(modelIndex).m_requirement.isEmpty();
  719. }
  720. bool GemModel::HasUpdates(const QModelIndex& modelIndex, bool compatibleOnly)
  721. {
  722. // get the currently displayed item
  723. const auto& gemInfo = GemModel::GetGemInfo(modelIndex);
  724. if (gemInfo.m_isEngineGem)
  725. {
  726. // engine gems are only updated with the engine
  727. return false;
  728. }
  729. const auto& versions = GemModel::GetGemVersions(modelIndex);
  730. if (versions.count() < 2)
  731. {
  732. // there is only one version available
  733. return false;
  734. }
  735. auto currentVersion = modelIndex.data(RoleVersion).toString();
  736. if (compatibleOnly)
  737. {
  738. // versions are ordered from highest to lowest
  739. for (auto itr = versions.cbegin(); itr != versions.cend(); itr++)
  740. {
  741. const GemInfo& versionGemInfo = itr->value<GemInfo>();
  742. if (versionGemInfo.IsCompatible())
  743. {
  744. if (currentVersion != versionGemInfo.m_version)
  745. {
  746. return true;
  747. }
  748. // if this is a remote gem, show that the update is available if we
  749. // haven't downloaded it yet and the user has downloaded an older version
  750. if (gemInfo.m_gemOrigin == GemInfo::Remote && gemInfo.m_downloadStatus == GemInfo::NotDownloaded)
  751. {
  752. itr++;
  753. while (itr != versions.cend())
  754. {
  755. const GemInfo& olderVersionGemInfo = itr->value<GemInfo>();
  756. if (olderVersionGemInfo.m_version != currentVersion &&
  757. (olderVersionGemInfo.m_downloadStatus == GemInfo::DownloadSuccessful ||
  758. olderVersionGemInfo.m_downloadStatus == GemInfo::Downloaded))
  759. {
  760. // found an older version that was downloaded
  761. return true;
  762. }
  763. itr++;
  764. }
  765. // did not find an older version that was downloaded
  766. return false;
  767. }
  768. return currentVersion != versionGemInfo.m_version;
  769. }
  770. }
  771. return false;
  772. }
  773. else
  774. {
  775. if (currentVersion != versions.at(0).value<GemInfo>().m_version)
  776. {
  777. return true;
  778. }
  779. // if this is a remote gem that hasn't been downloaded, show that the update is
  780. // available if the user has downloaded an older version
  781. if (gemInfo.m_gemOrigin == GemInfo::Remote &&
  782. gemInfo.m_downloadStatus == GemInfo::NotDownloaded)
  783. {
  784. // we've already verified versions.count() > 1 above
  785. for (int i = 1; i < versions.count(); ++i)
  786. {
  787. const GemInfo& variantGemInfo = versions.at(i).value<GemInfo>();
  788. if (variantGemInfo.m_version != currentVersion &&
  789. (variantGemInfo.m_downloadStatus == GemInfo::DownloadSuccessful ||
  790. variantGemInfo.m_downloadStatus == GemInfo::Downloaded))
  791. {
  792. // found an older version that was downloaded
  793. return true;
  794. }
  795. }
  796. }
  797. return false;
  798. }
  799. }
  800. bool GemModel::IsCompatible(const QModelIndex& modelIndex)
  801. {
  802. return GemModel::GetGemInfo(modelIndex).IsCompatible();
  803. }
  804. bool GemModel::IsAddedMissing(const QModelIndex& modelIndex)
  805. {
  806. return GemModel::IsAdded(modelIndex) && GemModel::GetGemInfo(modelIndex).m_path.isEmpty();
  807. }
  808. bool GemModel::DoGemsToBeAddedHaveRequirements() const
  809. {
  810. for (int row = 0; row < rowCount(); ++row)
  811. {
  812. const QModelIndex modelIndex = index(row, 0);
  813. if (NeedsToBeAdded(modelIndex) && HasRequirement(modelIndex))
  814. {
  815. return true;
  816. }
  817. }
  818. return false;
  819. }
  820. bool GemModel::HasDependentGemsToRemove() const
  821. {
  822. for (int row = 0; row < rowCount(); ++row)
  823. {
  824. const QModelIndex modelIndex = index(row, 0);
  825. if (GemModel::NeedsToBeRemoved(modelIndex, /*includeDependencies=*/true) &&
  826. GemModel::WasPreviouslyAddedDependency(modelIndex))
  827. {
  828. return true;
  829. }
  830. }
  831. return false;
  832. }
  833. QVector<QPersistentModelIndex> GemModel::GatherGemDependencies(const QPersistentModelIndex& modelIndex) const
  834. {
  835. QVector<QPersistentModelIndex> result;
  836. const QString& gemName = modelIndex.data(RoleName).toString();
  837. if (m_gemDependencyMap.contains(gemName))
  838. {
  839. for (const auto& dependency : m_gemDependencyMap[gemName])
  840. {
  841. result.push_back(dependency);
  842. }
  843. }
  844. return result;
  845. }
  846. QVector<QPersistentModelIndex> GemModel::GatherDependentGems(const QPersistentModelIndex& modelIndex, bool addedOnly) const
  847. {
  848. QVector<QPersistentModelIndex> result;
  849. const QString& gemName = modelIndex.data(RoleName).toString();
  850. if (m_gemReverseDependencyMap.contains(gemName))
  851. {
  852. for (const auto& dependency : m_gemReverseDependencyMap[gemName])
  853. {
  854. if (!addedOnly || GemModel::IsAdded(dependency))
  855. {
  856. result.push_back(dependency);
  857. }
  858. }
  859. }
  860. return result;
  861. }
  862. QVector<QModelIndex> GemModel::GatherGemsToBeAdded(bool includeDependencies) const
  863. {
  864. QVector<QModelIndex> result;
  865. for (int row = 0; row < rowCount(); ++row)
  866. {
  867. const QModelIndex modelIndex = index(row, 0);
  868. if (NeedsToBeAdded(modelIndex, includeDependencies))
  869. {
  870. result.push_back(modelIndex);
  871. }
  872. }
  873. return result;
  874. }
  875. QVector<QModelIndex> GemModel::GatherGemsToBeRemoved(bool includeDependencies) const
  876. {
  877. QVector<QModelIndex> result;
  878. for (int row = 0; row < rowCount(); ++row)
  879. {
  880. const QModelIndex modelIndex = index(row, 0);
  881. if (NeedsToBeRemoved(modelIndex, includeDependencies))
  882. {
  883. result.push_back(modelIndex);
  884. }
  885. }
  886. return result;
  887. }
  888. int GemModel::TotalAddedGems(bool includeDependencies) const
  889. {
  890. int result = 0;
  891. for (int row = 0; row < rowCount(); ++row)
  892. {
  893. const QModelIndex modelIndex = index(row, 0);
  894. if (IsAdded(modelIndex) || (includeDependencies && IsAddedDependency(modelIndex)))
  895. {
  896. ++result;
  897. }
  898. }
  899. return result;
  900. }
  901. } // namespace O3DE::ProjectManager