cmodlistview_moc.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762
  1. /*
  2. * cmodlistview_moc.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "cmodlistview_moc.h"
  12. #include "ui_cmodlistview_moc.h"
  13. #include "imageviewer_moc.h"
  14. #include "../mainwindow_moc.h"
  15. #include <QJsonArray>
  16. #include <QCryptographicHash>
  17. #include <QRegularExpression>
  18. #include "cmodlistmodel_moc.h"
  19. #include "cmodmanager.h"
  20. #include "cdownloadmanager_moc.h"
  21. #include "../launcherdirs.h"
  22. #include "../../lib/CConfigHandler.h"
  23. void CModListView::setupModModel()
  24. {
  25. modModel = new CModListModel(this);
  26. manager = vstd::make_unique<CModManager>(modModel);
  27. }
  28. void CModListView::setupFilterModel()
  29. {
  30. filterModel = new CModFilterModel(modModel, this);
  31. filterModel->setFilterKeyColumn(-1); // filter across all columns
  32. filterModel->setSortCaseSensitivity(Qt::CaseInsensitive); // to make it more user-friendly
  33. filterModel->setDynamicSortFilter(true);
  34. }
  35. void CModListView::setupModsView()
  36. {
  37. ui->allModsView->setModel(filterModel);
  38. // input data is not sorted - sort it before display
  39. ui->allModsView->sortByColumn(ModFields::TYPE, Qt::AscendingOrder);
  40. ui->allModsView->header()->setSectionResizeMode(ModFields::STATUS_ENABLED, QHeaderView::Fixed);
  41. ui->allModsView->header()->setSectionResizeMode(ModFields::STATUS_UPDATE, QHeaderView::Fixed);
  42. QSettings s(Ui::teamName, Ui::appName);
  43. auto state = s.value("AllModsView/State").toByteArray();
  44. if(!state.isNull()) //read last saved settings
  45. {
  46. ui->allModsView->header()->restoreState(state);
  47. }
  48. else //default //TODO: default high-DPI scaling
  49. {
  50. ui->allModsView->setColumnWidth(ModFields::NAME, 185);
  51. ui->allModsView->setColumnWidth(ModFields::STATUS_ENABLED, 30);
  52. ui->allModsView->setColumnWidth(ModFields::STATUS_UPDATE, 30);
  53. ui->allModsView->setColumnWidth(ModFields::TYPE, 75);
  54. ui->allModsView->setColumnWidth(ModFields::SIZE, 80);
  55. ui->allModsView->setColumnWidth(ModFields::VERSION, 60);
  56. }
  57. ui->allModsView->setUniformRowHeights(true);
  58. connect(ui->allModsView->selectionModel(), SIGNAL(currentRowChanged(const QModelIndex&,const QModelIndex&)),
  59. this, SLOT(modSelected(const QModelIndex&,const QModelIndex&)));
  60. connect(filterModel, SIGNAL(modelReset()),
  61. this, SLOT(modelReset()));
  62. connect(modModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
  63. this, SLOT(dataChanged(QModelIndex,QModelIndex)));
  64. }
  65. CModListView::CModListView(QWidget * parent)
  66. : QWidget(parent), settingsListener(settings.listen["launcher"]["repositoryURL"]), ui(new Ui::CModListView)
  67. {
  68. settingsListener([&](const JsonNode &){ repositoriesChanged = true; });
  69. ui->setupUi(this);
  70. setupModModel();
  71. setupFilterModel();
  72. setupModsView();
  73. ui->progressWidget->setVisible(false);
  74. dlManager = nullptr;
  75. disableModInfo();
  76. if(settings["launcher"]["autoCheckRepositories"].Bool())
  77. {
  78. loadRepositories();
  79. }
  80. else
  81. {
  82. manager->resetRepositories();
  83. }
  84. }
  85. void CModListView::loadRepositories()
  86. {
  87. manager->resetRepositories();
  88. for(auto entry : settings["launcher"]["repositoryURL"].Vector())
  89. {
  90. QString str = QString::fromUtf8(entry.String().c_str());
  91. // URL must be encoded to something else to get rid of symbols illegal in file names
  92. auto hashed = QCryptographicHash::hash(str.toUtf8(), QCryptographicHash::Md5);
  93. auto hashedStr = QString::fromUtf8(hashed.toHex());
  94. downloadFile(hashedStr + ".json", str, "repository index");
  95. }
  96. }
  97. CModListView::~CModListView()
  98. {
  99. QSettings s(Ui::teamName, Ui::appName);
  100. s.setValue("AllModsView/State", ui->allModsView->header()->saveState());
  101. delete ui;
  102. }
  103. void CModListView::showEvent(QShowEvent * event)
  104. {
  105. QWidget::showEvent(event);
  106. if(repositoriesChanged)
  107. {
  108. repositoriesChanged = false;
  109. if(settings["launcher"]["autoCheckRepositories"].Bool())
  110. {
  111. loadRepositories();
  112. }
  113. }
  114. }
  115. void CModListView::showModInfo()
  116. {
  117. enableModInfo();
  118. ui->modInfoWidget->show();
  119. ui->hideModInfoButton->setArrowType(Qt::RightArrow);
  120. ui->showInfoButton->setVisible(false);
  121. loadScreenshots();
  122. }
  123. void CModListView::hideModInfo()
  124. {
  125. ui->modInfoWidget->hide();
  126. ui->hideModInfoButton->setArrowType(Qt::LeftArrow);
  127. ui->hideModInfoButton->setEnabled(true);
  128. ui->showInfoButton->setVisible(true);
  129. }
  130. static QString replaceIfNotEmpty(QVariant value, QString pattern)
  131. {
  132. if(value.canConvert<QStringList>())
  133. return pattern.arg(value.toStringList().join(", "));
  134. if(value.canConvert<QString>())
  135. return pattern.arg(value.toString());
  136. // all valid types of data should have been filtered by code above
  137. assert(!value.isValid());
  138. return "";
  139. }
  140. static QString replaceIfNotEmpty(QStringList value, QString pattern)
  141. {
  142. if(!value.empty())
  143. return pattern.arg(value.join(", "));
  144. return "";
  145. }
  146. QString CModListView::genChangelogText(CModEntry & mod)
  147. {
  148. QString headerTemplate = "<p><span style=\" font-weight:600;\">%1: </span></p>";
  149. QString entryBegin = "<p align=\"justify\"><ul>";
  150. QString entryEnd = "</ul></p>";
  151. QString entryLine = "<li>%1</li>";
  152. //QString versionSeparator = "<hr/>";
  153. QString result;
  154. QVariantMap changelog = mod.getValue("changelog").toMap();
  155. QList<QString> versions = changelog.keys();
  156. std::sort(versions.begin(), versions.end(), [](QString lesser, QString greater)
  157. {
  158. return !CModEntry::compareVersions(lesser, greater);
  159. });
  160. for(auto & version : versions)
  161. {
  162. result += headerTemplate.arg(version);
  163. result += entryBegin;
  164. for(auto & line : changelog.value(version).toStringList())
  165. result += entryLine.arg(line);
  166. result += entryEnd;
  167. }
  168. return result;
  169. }
  170. QString CModListView::genModInfoText(CModEntry & mod)
  171. {
  172. QString prefix = "<p><span style=\" font-weight:600;\">%1: </span>"; // shared prefix
  173. QString lineTemplate = prefix + "%2</p>";
  174. QString urlTemplate = prefix + "<a href=\"%2\">%3</a></p>";
  175. QString textTemplate = prefix + "</p><p align=\"justify\">%2</p>";
  176. QString listTemplate = "<p align=\"justify\">%1: %2</p>";
  177. QString noteTemplate = "<p align=\"justify\">%1</p>";
  178. QString result;
  179. result += replaceIfNotEmpty(mod.getValue("name"), lineTemplate.arg(tr("Mod name")));
  180. result += replaceIfNotEmpty(mod.getValue("installedVersion"), lineTemplate.arg(tr("Installed version")));
  181. result += replaceIfNotEmpty(mod.getValue("latestVersion"), lineTemplate.arg(tr("Latest version")));
  182. if(mod.getValue("size").isValid())
  183. result += replaceIfNotEmpty(CModEntry::sizeToString(mod.getValue("size").toDouble()), lineTemplate.arg(tr("Download size")));
  184. result += replaceIfNotEmpty(mod.getValue("author"), lineTemplate.arg(tr("Authors")));
  185. if(mod.getValue("licenseURL").isValid())
  186. result += urlTemplate.arg(tr("License")).arg(mod.getValue("licenseURL").toString()).arg(mod.getValue("licenseName").toString());
  187. if(mod.getValue("contact").isValid())
  188. result += urlTemplate.arg(tr("Home")).arg(mod.getValue("contact").toString()).arg(mod.getValue("contact").toString());
  189. result += replaceIfNotEmpty(mod.getValue("depends"), lineTemplate.arg(tr("Required mods")));
  190. result += replaceIfNotEmpty(mod.getValue("conflicts"), lineTemplate.arg(tr("Conflicting mods")));
  191. result += replaceIfNotEmpty(mod.getValue("description"), textTemplate.arg(tr("Description")));
  192. result += "<p></p>"; // to get some empty space
  193. QString unknownDeps = tr("This mod can not be installed or enabled because following dependencies are not present");
  194. QString blockingMods = tr("This mod can not be enabled because following mods are incompatible with this mod");
  195. QString hasActiveDependentMods = tr("This mod can not be disabled because it is required to run following mods");
  196. QString hasDependentMods = tr("This mod can not be uninstalled or updated because it is required to run following mods");
  197. QString thisIsSubmod = tr("This is submod and it can not be installed or uninstalled separately from parent mod");
  198. QString notes;
  199. notes += replaceIfNotEmpty(findInvalidDependencies(mod.getName()), listTemplate.arg(unknownDeps));
  200. notes += replaceIfNotEmpty(findBlockingMods(mod.getName()), listTemplate.arg(blockingMods));
  201. if(mod.isEnabled())
  202. notes += replaceIfNotEmpty(findDependentMods(mod.getName(), true), listTemplate.arg(hasActiveDependentMods));
  203. if(mod.isInstalled())
  204. notes += replaceIfNotEmpty(findDependentMods(mod.getName(), false), listTemplate.arg(hasDependentMods));
  205. if(mod.getName().contains('.'))
  206. notes += noteTemplate.arg(thisIsSubmod);
  207. if(notes.size())
  208. result += textTemplate.arg(tr("Notes")).arg(notes);
  209. return result;
  210. }
  211. void CModListView::enableModInfo()
  212. {
  213. ui->hideModInfoButton->setEnabled(true);
  214. ui->showInfoButton->setVisible(true);
  215. }
  216. void CModListView::disableModInfo()
  217. {
  218. hideModInfo();
  219. ui->hideModInfoButton->setEnabled(false);
  220. ui->showInfoButton->setVisible(false);
  221. ui->disableButton->setVisible(false);
  222. ui->enableButton->setVisible(false);
  223. ui->installButton->setVisible(false);
  224. ui->uninstallButton->setVisible(false);
  225. ui->updateButton->setVisible(false);
  226. }
  227. void CModListView::dataChanged(const QModelIndex & topleft, const QModelIndex & bottomRight)
  228. {
  229. selectMod(ui->allModsView->currentIndex());
  230. }
  231. void CModListView::selectMod(const QModelIndex & index)
  232. {
  233. if(!index.isValid())
  234. {
  235. disableModInfo();
  236. }
  237. else
  238. {
  239. auto mod = modModel->getMod(index.data(ModRoles::ModNameRole).toString());
  240. ui->modInfoBrowser->setHtml(genModInfoText(mod));
  241. ui->changelogBrowser->setHtml(genChangelogText(mod));
  242. bool hasInvalidDeps = !findInvalidDependencies(index.data(ModRoles::ModNameRole).toString()).empty();
  243. bool hasBlockingMods = !findBlockingMods(index.data(ModRoles::ModNameRole).toString()).empty();
  244. bool hasDependentMods = !findDependentMods(index.data(ModRoles::ModNameRole).toString(), true).empty();
  245. ui->hideModInfoButton->setEnabled(true);
  246. ui->showInfoButton->setVisible(!ui->modInfoWidget->isVisible());
  247. ui->disableButton->setVisible(mod.isEnabled());
  248. ui->enableButton->setVisible(mod.isDisabled());
  249. ui->installButton->setVisible(mod.isAvailable() && !mod.getName().contains('.'));
  250. ui->uninstallButton->setVisible(mod.isInstalled() && !mod.getName().contains('.'));
  251. ui->updateButton->setVisible(mod.isUpdateable());
  252. // Block buttons if action is not allowed at this time
  253. // TODO: automate handling of some of these cases instead of forcing player
  254. // to resolve all conflicts manually.
  255. ui->disableButton->setEnabled(!hasDependentMods && !mod.isEssential());
  256. ui->enableButton->setEnabled(!hasBlockingMods && !hasInvalidDeps);
  257. ui->installButton->setEnabled(!hasInvalidDeps);
  258. ui->uninstallButton->setEnabled(!hasDependentMods && !mod.isEssential());
  259. ui->updateButton->setEnabled(!hasInvalidDeps && !hasDependentMods);
  260. loadScreenshots();
  261. }
  262. }
  263. void CModListView::keyPressEvent(QKeyEvent * event)
  264. {
  265. if(event->key() == Qt::Key_Escape && ui->modInfoWidget->isVisible())
  266. {
  267. hideModInfo();
  268. }
  269. else
  270. {
  271. return QWidget::keyPressEvent(event);
  272. }
  273. }
  274. void CModListView::modSelected(const QModelIndex & current, const QModelIndex &)
  275. {
  276. selectMod(current);
  277. }
  278. void CModListView::on_hideModInfoButton_clicked()
  279. {
  280. if(ui->modInfoWidget->isVisible())
  281. hideModInfo();
  282. else
  283. showModInfo();
  284. }
  285. void CModListView::on_allModsView_activated(const QModelIndex & index)
  286. {
  287. showModInfo();
  288. selectMod(index);
  289. }
  290. void CModListView::on_lineEdit_textChanged(const QString & arg1)
  291. {
  292. #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
  293. auto baseStr = QRegularExpression::wildcardToRegularExpression(arg1, QRegularExpression::UnanchoredWildcardConversion);
  294. #else
  295. auto baseStr = QRegularExpression::wildcardToRegularExpression(arg1);
  296. //Hack due to lack QRegularExpression::UnanchoredWildcardConversion in Qt5
  297. baseStr.chop(3);
  298. baseStr.remove(0,5);
  299. #endif
  300. QRegularExpression regExp{baseStr, QRegularExpression::CaseInsensitiveOption};
  301. filterModel->setFilterRegularExpression(regExp);
  302. }
  303. void CModListView::on_comboBox_currentIndexChanged(int index)
  304. {
  305. switch(index)
  306. {
  307. case 0:
  308. filterModel->setTypeFilter(ModStatus::MASK_NONE, ModStatus::MASK_NONE);
  309. break;
  310. case 1:
  311. filterModel->setTypeFilter(ModStatus::MASK_NONE, ModStatus::INSTALLED);
  312. break;
  313. case 2:
  314. filterModel->setTypeFilter(ModStatus::INSTALLED, ModStatus::INSTALLED);
  315. break;
  316. case 3:
  317. filterModel->setTypeFilter(ModStatus::UPDATEABLE, ModStatus::UPDATEABLE);
  318. break;
  319. case 4:
  320. filterModel->setTypeFilter(ModStatus::ENABLED | ModStatus::INSTALLED, ModStatus::ENABLED | ModStatus::INSTALLED);
  321. break;
  322. case 5:
  323. filterModel->setTypeFilter(ModStatus::INSTALLED, ModStatus::ENABLED | ModStatus::INSTALLED);
  324. break;
  325. }
  326. }
  327. QStringList CModListView::findInvalidDependencies(QString mod)
  328. {
  329. QStringList ret;
  330. for(QString requrement : modModel->getRequirements(mod))
  331. {
  332. if(!modModel->hasMod(requrement))
  333. ret += requrement;
  334. }
  335. return ret;
  336. }
  337. QStringList CModListView::findBlockingMods(QString modUnderTest)
  338. {
  339. QStringList ret;
  340. auto required = modModel->getRequirements(modUnderTest);
  341. for(QString name : modModel->getModList())
  342. {
  343. auto mod = modModel->getMod(name);
  344. if(mod.isEnabled())
  345. {
  346. // one of enabled mods have requirement (or this mod) marked as conflict
  347. for(auto conflict : mod.getValue("conflicts").toStringList())
  348. {
  349. if(required.contains(conflict))
  350. ret.push_back(name);
  351. }
  352. }
  353. }
  354. return ret;
  355. }
  356. QStringList CModListView::findDependentMods(QString mod, bool excludeDisabled)
  357. {
  358. QStringList ret;
  359. for(QString modName : modModel->getModList())
  360. {
  361. auto current = modModel->getMod(modName);
  362. if(!current.isInstalled())
  363. continue;
  364. if(current.getValue("depends").toStringList().contains(mod))
  365. {
  366. if(!(current.isDisabled() && excludeDisabled))
  367. ret += modName;
  368. }
  369. }
  370. return ret;
  371. }
  372. void CModListView::on_enableButton_clicked()
  373. {
  374. QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString();
  375. assert(findBlockingMods(modName).empty());
  376. assert(findInvalidDependencies(modName).empty());
  377. for(auto & name : modModel->getRequirements(modName))
  378. {
  379. if(modModel->getMod(name).isDisabled())
  380. manager->enableMod(name);
  381. }
  382. checkManagerErrors();
  383. }
  384. void CModListView::on_disableButton_clicked()
  385. {
  386. QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString();
  387. if(modModel->hasMod(modName) && modModel->getMod(modName).isEnabled())
  388. manager->disableMod(modName);
  389. checkManagerErrors();
  390. }
  391. void CModListView::on_updateButton_clicked()
  392. {
  393. QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString();
  394. assert(findInvalidDependencies(modName).empty());
  395. for(auto & name : modModel->getRequirements(modName))
  396. {
  397. auto mod = modModel->getMod(name);
  398. // update required mod, install missing (can be new dependency)
  399. if(mod.isUpdateable() || !mod.isInstalled())
  400. downloadFile(name + ".zip", mod.getValue("download").toString(), "mods");
  401. }
  402. }
  403. void CModListView::on_uninstallButton_clicked()
  404. {
  405. QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString();
  406. // NOTE: perhaps add "manually installed" flag and uninstall those dependencies that don't have it?
  407. if(modModel->hasMod(modName) && modModel->getMod(modName).isInstalled())
  408. {
  409. if(modModel->getMod(modName).isEnabled())
  410. manager->disableMod(modName);
  411. manager->uninstallMod(modName);
  412. }
  413. checkManagerErrors();
  414. }
  415. void CModListView::on_installButton_clicked()
  416. {
  417. QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString();
  418. assert(findInvalidDependencies(modName).empty());
  419. for(auto & name : modModel->getRequirements(modName))
  420. {
  421. auto mod = modModel->getMod(name);
  422. if(!mod.isInstalled())
  423. downloadFile(name + ".zip", mod.getValue("download").toString(), "mods");
  424. }
  425. }
  426. void CModListView::downloadFile(QString file, QString url, QString description)
  427. {
  428. if(!dlManager)
  429. {
  430. dlManager = new CDownloadManager();
  431. ui->progressWidget->setVisible(true);
  432. connect(dlManager, SIGNAL(downloadProgress(qint64,qint64)),
  433. this, SLOT(downloadProgress(qint64,qint64)));
  434. connect(dlManager, SIGNAL(finished(QStringList,QStringList,QStringList)),
  435. this, SLOT(downloadFinished(QStringList,QStringList,QStringList)));
  436. connect(modModel, &CModListModel::dataChanged, filterModel, &QAbstractItemModel::dataChanged);
  437. QString progressBarFormat = "Downloading %s%. %p% (%v KB out of %m KB) finished";
  438. progressBarFormat.replace("%s%", description);
  439. ui->progressBar->setFormat(progressBarFormat);
  440. }
  441. dlManager->downloadFile(QUrl(url), file);
  442. }
  443. void CModListView::downloadProgress(qint64 current, qint64 max)
  444. {
  445. // display progress, in kilobytes
  446. ui->progressBar->setValue(current / 1024);
  447. ui->progressBar->setMaximum(max / 1024);
  448. }
  449. void CModListView::downloadFinished(QStringList savedFiles, QStringList failedFiles, QStringList errors)
  450. {
  451. QString title = "Download failed";
  452. QString firstLine = "Unable to download all files.\n\nEncountered errors:\n\n";
  453. QString lastLine = "\n\nInstall successfully downloaded?";
  454. // if all files were d/loaded there should be no errors. And on failure there must be an error
  455. assert(failedFiles.empty() == errors.empty());
  456. if(savedFiles.empty())
  457. {
  458. // no successfully downloaded mods
  459. QMessageBox::warning(this, title, firstLine + errors.join("\n"), QMessageBox::Ok, QMessageBox::Ok);
  460. }
  461. else if(!failedFiles.empty())
  462. {
  463. // some mods were not downloaded
  464. int result = QMessageBox::warning (this, title, firstLine + errors.join("\n") + lastLine,
  465. QMessageBox::Yes | QMessageBox::No, QMessageBox::No );
  466. if(result == QMessageBox::Yes)
  467. installFiles(savedFiles);
  468. }
  469. else
  470. {
  471. // everything OK
  472. installFiles(savedFiles);
  473. }
  474. // remove progress bar after some delay so user can see that download was complete and not interrupted.
  475. QTimer::singleShot(1000, this, SLOT(hideProgressBar()));
  476. dlManager->deleteLater();
  477. dlManager = nullptr;
  478. }
  479. void CModListView::hideProgressBar()
  480. {
  481. if(dlManager == nullptr) // it was not recreated meanwhile
  482. {
  483. ui->progressWidget->setVisible(false);
  484. ui->progressBar->setMaximum(0);
  485. ui->progressBar->setValue(0);
  486. }
  487. }
  488. void CModListView::installFiles(QStringList files)
  489. {
  490. QStringList mods;
  491. QStringList images;
  492. // TODO: some better way to separate zip's with mods and downloaded repository files
  493. for(QString filename : files)
  494. {
  495. if(filename.endsWith(".zip"))
  496. mods.push_back(filename);
  497. if(filename.endsWith(".json"))
  498. manager->loadRepository(filename);
  499. if(filename.endsWith(".png"))
  500. images.push_back(filename);
  501. }
  502. if(!mods.empty())
  503. installMods(mods);
  504. if(!images.empty())
  505. loadScreenshots();
  506. }
  507. void CModListView::installMods(QStringList archives)
  508. {
  509. QStringList modNames;
  510. for(QString archive : archives)
  511. {
  512. // get basename out of full file name
  513. // remove path remove extension
  514. QString modName = archive.section('/', -1, -1).section('.', 0, 0);
  515. modNames.push_back(modName);
  516. }
  517. QStringList modsToEnable;
  518. // disable mod(s), to properly recalculate dependencies, if changed
  519. for(QString mod : boost::adaptors::reverse(modNames))
  520. {
  521. CModEntry entry = modModel->getMod(mod);
  522. if(entry.isInstalled())
  523. {
  524. // enable mod if installed and enabled
  525. if(entry.isEnabled())
  526. modsToEnable.push_back(mod);
  527. }
  528. else
  529. {
  530. // enable mod if m
  531. if(settings["launcher"]["enableInstalledMods"].Bool())
  532. modsToEnable.push_back(mod);
  533. }
  534. }
  535. // uninstall old version of mod, if installed
  536. for(QString mod : boost::adaptors::reverse(modNames))
  537. {
  538. if(modModel->getMod(mod).isInstalled())
  539. manager->uninstallMod(mod);
  540. }
  541. for(int i = 0; i < modNames.size(); i++)
  542. manager->installMod(modNames[i], archives[i]);
  543. std::function<void(QString)> enableMod;
  544. enableMod = [&](QString modName)
  545. {
  546. auto mod = modModel->getMod(modName);
  547. if(mod.isInstalled() && !mod.getValue("keepDisabled").toBool())
  548. {
  549. if(mod.isDisabled() && manager->enableMod(modName))
  550. {
  551. for(QString child : modModel->getChildren(modName))
  552. enableMod(child);
  553. }
  554. }
  555. };
  556. for(QString mod : modsToEnable)
  557. {
  558. enableMod(mod);
  559. }
  560. for(QString archive : archives)
  561. QFile::remove(archive);
  562. checkManagerErrors();
  563. }
  564. void CModListView::on_refreshButton_clicked()
  565. {
  566. loadRepositories();
  567. }
  568. void CModListView::on_pushButton_clicked()
  569. {
  570. delete dlManager;
  571. dlManager = nullptr;
  572. hideProgressBar();
  573. }
  574. void CModListView::modelReset()
  575. {
  576. if(ui->modInfoWidget->isVisible())
  577. selectMod(filterModel->rowCount() > 0 ? filterModel->index(0, 0) : QModelIndex());
  578. }
  579. void CModListView::checkManagerErrors()
  580. {
  581. QString errors = manager->getErrors().join('\n');
  582. if(errors.size() != 0)
  583. {
  584. QString title = "Operation failed";
  585. QString description = "Encountered errors:\n" + errors;
  586. QMessageBox::warning(this, title, description, QMessageBox::Ok, QMessageBox::Ok);
  587. }
  588. }
  589. void CModListView::on_tabWidget_currentChanged(int index)
  590. {
  591. loadScreenshots();
  592. }
  593. void CModListView::loadScreenshots()
  594. {
  595. if(ui->tabWidget->currentIndex() == 2 && ui->modInfoWidget->isVisible())
  596. {
  597. ui->screenshotsList->clear();
  598. QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString();
  599. assert(modModel->hasMod(modName)); //should be filtered out by check above
  600. for(QString & url : modModel->getMod(modName).getValue("screenshots").toStringList())
  601. {
  602. // URL must be encoded to something else to get rid of symbols illegal in file names
  603. auto hashed = QCryptographicHash::hash(url.toUtf8(), QCryptographicHash::Md5);
  604. auto hashedStr = QString::fromUtf8(hashed.toHex());
  605. QString fullPath = CLauncherDirs::get().downloadsPath() + '/' + hashedStr + ".png";
  606. QPixmap pixmap(fullPath);
  607. if(pixmap.isNull())
  608. {
  609. // image file not exists or corrupted - try to redownload
  610. downloadFile(hashedStr + ".png", url, "screenshots");
  611. }
  612. else
  613. {
  614. // managed to load cached image
  615. QIcon icon(pixmap);
  616. QListWidgetItem * item = new QListWidgetItem(icon, QString(tr("Screenshot %1")).arg(ui->screenshotsList->count() + 1));
  617. ui->screenshotsList->addItem(item);
  618. }
  619. }
  620. }
  621. }
  622. void CModListView::on_screenshotsList_clicked(const QModelIndex & index)
  623. {
  624. if(index.isValid())
  625. {
  626. QIcon icon = ui->screenshotsList->item(index.row())->icon();
  627. auto pixmap = icon.pixmap(icon.availableSizes()[0]);
  628. ImageViewer::showPixmap(pixmap, this);
  629. }
  630. }
  631. void CModListView::on_showInfoButton_clicked()
  632. {
  633. showModInfo();
  634. }