浏览代码

Reworked mod handling in Launcher in order to unify code with lib

Ivan Savenko 11 月之前
父节点
当前提交
f8724b9558

+ 8 - 8
launcher/CMakeLists.txt

@@ -7,13 +7,13 @@ set(launcher_SRCS
 		StdInc.cpp
 		aboutProject/aboutproject_moc.cpp
 		modManager/cdownloadmanager_moc.cpp
-		modManager/cmodlist.cpp
-		modManager/cmodlistmodel_moc.cpp
+		modManager/modstateitemmodel_moc.cpp
 		modManager/cmodlistview_moc.cpp
-		modManager/cmodmanager.cpp
+		modManager/modstatecontroller.cpp
+		modManager/modstatemodel.cpp
+		modManager/modstate.cpp
 		modManager/imageviewer_moc.cpp
 		modManager/chroniclesextractor.cpp
-		modManager/modsettingsstorage.cpp
 		settingsView/csettingsview_moc.cpp
 		firstLaunch/firstlaunch_moc.cpp
 		main.cpp
@@ -38,13 +38,13 @@ set(launcher_HEADERS
 		StdInc.h
 		aboutProject/aboutproject_moc.h
 		modManager/cdownloadmanager_moc.h
-		modManager/cmodlist.h
-		modManager/cmodlistmodel_moc.h
+		modManager/modstateitemmodel_moc.h
 		modManager/cmodlistview_moc.h
-		modManager/cmodmanager.h
+		modManager/modstatecontroller.h
+		modManager/modstatemodel.h
+		modManager/modstate.h
 		modManager/imageviewer_moc.h
 		modManager/chroniclesextractor.h
-		modManager/modsettingsstorage.h
 		settingsView/csettingsview_moc.h
 		firstLaunch/firstlaunch_moc.h
 		mainwindow_moc.h

+ 4 - 4
launcher/mainwindow_moc.cpp

@@ -206,10 +206,10 @@ void MainWindow::on_startEditorButton_clicked()
 	startEditor({});
 }
 
-const CModList & MainWindow::getModList() const
-{
-	return ui->modlistView->getModList();
-}
+//const CModList & MainWindow::getModList() const
+//{
+//	return ui->modlistView->getModList();
+//}
 
 CModListView * MainWindow::getModView()
 {

+ 1 - 1
launcher/mainwindow_moc.h

@@ -46,7 +46,7 @@ public:
 	explicit MainWindow(QWidget * parent = nullptr);
 	~MainWindow() override;
 
-	const CModList & getModList() const;
+//	const CModList & getModList() const;
 	CModListView * getModView();
 
 	void updateTranslation();

+ 0 - 417
launcher/modManager/cmodlist.cpp

@@ -1,417 +0,0 @@
-/*
- * cmodlist.cpp, part of VCMI engine
- *
- * Authors: listed in file AUTHORS in main folder
- *
- * License: GNU General Public License v2.0 or later
- * Full text of license available in license.txt file, in main folder
- *
- */
-#include "StdInc.h"
-#include "cmodlist.h"
-
-#include "../lib/CConfigHandler.h"
-#include "../../lib/filesystem/CFileInputStream.h"
-#include "../../lib/GameConstants.h"
-#include "../../lib/modding/CModVersion.h"
-
-QString CModEntry::sizeToString(double size)
-{
-	static const std::array sizes {
-		QT_TRANSLATE_NOOP("File size", "%1 B"),
-		QT_TRANSLATE_NOOP("File size", "%1 KiB"),
-		QT_TRANSLATE_NOOP("File size", "%1 MiB"),
-		QT_TRANSLATE_NOOP("File size", "%1 GiB"),
-		QT_TRANSLATE_NOOP("File size", "%1 TiB")
-	};
-	size_t index = 0;
-	while(size > 1024 && index < sizes.size())
-	{
-		size /= 1024;
-		index++;
-	}
-	return QCoreApplication::translate("File size", sizes[index]).arg(QString::number(size, 'f', 1));
-}
-
-CModEntry::CModEntry(QVariantMap repository, QVariantMap localData, bool modActive, QString modname)
-	: repository(repository), localData(localData), modActive(modActive), modname(modname)
-{
-}
-
-bool CModEntry::isEnabled() const
-{
-	if(!isInstalled())
-		return false;
-
-	if (!isVisible())
-		return false;
-
-	return modActive;
-}
-
-bool CModEntry::isDisabled() const
-{
-	if(!isInstalled())
-		return false;
-	return !isEnabled();
-}
-
-bool CModEntry::isAvailable() const
-{
-	if(isInstalled())
-		return false;
-	return !repository.isEmpty();
-}
-
-bool CModEntry::isUpdateable() const
-{
-	if(!isInstalled())
-		return false;
-
-	auto installedVer = localData["installedVersion"].toString().toStdString();
-	auto availableVer = repository["latestVersion"].toString().toStdString();
-
-	return (CModVersion::fromString(installedVer) < CModVersion::fromString(availableVer));
-}
-
-bool isCompatible(const QVariantMap & compatibility)
-{
-	auto compatibleMin = CModVersion::fromString(compatibility["min"].toString().toStdString());
-	auto compatibleMax = CModVersion::fromString(compatibility["max"].toString().toStdString());
-
-	return (compatibleMin.isNull() || CModVersion::GameVersion().compatible(compatibleMin, true, true))
-			&& (compatibleMax.isNull() || compatibleMax.compatible(CModVersion::GameVersion(), true, true));
-}
-
-bool CModEntry::isCompatible() const
-{
-	return ::isCompatible(localData["compatibility"].toMap());
-}
-
-bool CModEntry::isEssential() const
-{
-	return getName() == "vcmi";
-}
-
-bool CModEntry::isInstalled() const
-{
-	return !localData.isEmpty();
-}
-
-bool CModEntry::isVisible() const
-{
-	if (isCompatibilityPatch())
-	{
-		if (isSubmod())
-			return false;
-	}
-
-	if (isTranslation())
-	{
-		// Do not show not installed translation mods to languages other than player language
-		if (localData.empty() && getBaseValue("language") != QString::fromStdString(settings["general"]["language"].String()) )
-			return false;
-	}
-
-	return !localData.isEmpty() || (!repository.isEmpty() && !repository.contains("mod"));
-}
-
-bool CModEntry::isTranslation() const
-{
-	return getBaseValue("modType").toString() == "Translation";
-}
-
-bool CModEntry::isCompatibilityPatch() const
-{
-	return getBaseValue("modType").toString() == "Compatibility";
-}
-
-bool CModEntry::isSubmod() const
-{
-	return getName().contains('.');
-}
-
-int CModEntry::getModStatus() const
-{
-	int status = 0;
-	if(isEnabled())
-		status |= ModStatus::ENABLED;
-	if(isInstalled())
-		status |= ModStatus::INSTALLED;
-	if(isUpdateable())
-		status |= ModStatus::UPDATEABLE;
-
-	return status;
-}
-
-QString CModEntry::getName() const
-{
-	return modname;
-}
-
-QString CModEntry::getTopParentName() const
-{
-	assert(isSubmod());
-	return modname.section('.', 0, 0);
-}
-
-QVariant CModEntry::getValue(QString value) const
-{
-	return getValueImpl(value, true);
-}
-
-QStringList CModEntry::getDependencies() const
-{
-	QStringList result;
-	for (auto const & entry : getValue("depends").toStringList())
-		result.push_back(entry.toLower());
-	return result;
-}
-
-QStringList CModEntry::getConflicts() const
-{
-	QStringList result;
-	for (auto const & entry : getValue("conflicts").toStringList())
-		result.push_back(entry.toLower());
-	return result;
-}
-
-QVariant CModEntry::getBaseValue(QString value) const
-{
-	return getValueImpl(value, false);
-}
-
-QVariant CModEntry::getValueImpl(QString value, bool localized) const
-
-{
-	QString langValue = QString::fromStdString(settings["general"]["language"].String());
-
-	// Priorities
-	// 1) data from newest version
-	// 2) data from preferred language
-
-	bool useRepositoryData = repository.contains(value);
-
-	if(repository.contains(value) && localData.contains(value))
-	{
-		// value is present in both repo and locally installed. Select one from latest version
-		auto installedVer = localData["installedVersion"].toString().toStdString();
-		auto availableVer = repository["latestVersion"].toString().toStdString();
-
-		useRepositoryData = CModVersion::fromString(installedVer) < CModVersion::fromString(availableVer);
-	}
-
-	auto & storage = useRepositoryData ? repository : localData;
-
-	if(localized && storage.contains(langValue))
-	{
-		auto langStorage = storage[langValue].toMap();
-		if (langStorage.contains(value))
-			return langStorage[value];
-	}
-
-	if(storage.contains(value))
-		return storage[value];
-
-	return QVariant();
-}
-
-QVariantMap CModList::copyField(QVariantMap data, QString from, QString to) const
-{
-	QVariantMap renamed;
-
-	for(auto it = data.begin(); it != data.end(); it++)
-	{
-		QVariantMap modConf = it.value().toMap();
-
-		modConf.insert(to, modConf.value(from));
-		renamed.insert(it.key(), modConf);
-	}
-	return renamed;
-}
-
-void CModList::reloadRepositories()
-{
-	cachedMods.clear();
-}
-
-void CModList::resetRepositories()
-{
-	repositories.clear();
-	cachedMods.clear();
-}
-
-void CModList::addRepository(QVariantMap data)
-{
-	for(auto & key : data.keys())
-		data[key.toLower()] = data.take(key);
-	repositories.push_back(copyField(data, "version", "latestVersion"));
-
-	cachedMods.clear();
-}
-
-void CModList::setLocalModList(QVariantMap data)
-{
-	localModList = copyField(data, "version", "installedVersion");
-	cachedMods.clear();
-}
-
-void CModList::setModSettings(std::shared_ptr<ModSettingsStorage> data)
-{
-	modSettings = data;
-	cachedMods.clear();
-}
-
-void CModList::modChanged(QString modID)
-{
-	cachedMods.clear();
-}
-
-static QVariant getValue(QVariant input, QString path)
-{
-	if(path.size() > 1)
-	{
-		QString entryName = path.section('/', 0, 1);
-		QString remainder = "/" + path.section('/', 2, -1);
-
-		entryName.remove(0, 1);
-		return getValue(input.toMap().value(entryName), remainder);
-	}
-	else
-	{
-		return input;
-	}
-}
-
-const CModEntry & CModList::getMod(QString modName) const
-{
-	modName = modName.toLower();
-
-	auto it = cachedMods.find(modName);
-
-	if (it != cachedMods.end())
-		return it.value();
-
-	auto itNew = cachedMods.insert(modName, getModUncached(modName));
-	return *itNew;
-}
-
-CModEntry CModList::getModUncached(QString modname) const
-{
-	QVariantMap repo;
-	QVariantMap local = localModList[modname].toMap();
-
-	QString path = modname;
-	path = "/" + path.replace(".", "/mods/");
-
-	bool modActive = modSettings->isModActive(modname);
-
-	if(modActive)
-	{
-		QString rootModName = modname.section('.', 0, 1);
-		if (!modSettings->isModActive(rootModName))
-			modActive = false; // parent mod is inactive -> submod is also inactive
-	}
-
-	if(modActive)
-	{
-		if(!::isCompatible(local.value("compatibility").toMap()))
-			modActive = false; // mod not compatible with our version of vcmi -> inactive
-	}
-
-	for(auto entry : repositories)
-	{
-		QVariant repoVal = getValue(entry, path);
-		if(repoVal.isValid())
-		{
-			auto repoValMap = repoVal.toMap();
-			if(::isCompatible(repoValMap["compatibility"].toMap()))
-			{
-				if(repo.empty()
-					|| CModVersion::fromString(repo["version"].toString().toStdString())
-					 < CModVersion::fromString(repoValMap["version"].toString().toStdString()))
-				{
-					//take valid download link, screenshots and mod size before assignment
-					auto download = repo.value("download");
-					auto screenshots = repo.value("screenshots");
-					auto size = repo.value("downloadSize");
-					repo = repoValMap;
-					if(repo.value("download").isNull())
-					{
-						repo["download"] = download;
-						if(repo.value("screenshots").isNull()) //taking screenshot from the downloadable version
-							repo["screenshots"] = screenshots;
-					}
-					if(repo.value("downloadSize").isNull())
-						repo["downloadSize"] = size;
-				}
-			}
-		}
-	}
-
-	return CModEntry(repo, local, modActive, modname);
-}
-
-bool CModList::hasMod(QString modname) const
-{
-	if(localModList.contains(modname))
-		return true;
-
-	for(auto entry : repositories)
-		if(entry.contains(modname))
-			return true;
-
-	return false;
-}
-
-QStringList CModList::getRequirements(QString modname)
-{
-	QStringList ret;
-
-	if(hasMod(modname))
-	{
-		auto mod = getMod(modname);
-
-		for(auto entry : mod.getDependencies())
-			ret += getRequirements(entry.toLower());
-	}
-	ret += modname;
-
-	return ret;
-}
-
-QVector<QString> CModList::getModList() const
-{
-	QSet<QString> knownMods;
-	QVector<QString> modList;
-	for(auto repo : repositories)
-	{
-		for(auto it = repo.begin(); it != repo.end(); it++)
-		{
-			knownMods.insert(it.key().toLower());
-		}
-	}
-	for(auto it = localModList.begin(); it != localModList.end(); it++)
-	{
-		knownMods.insert(it.key().toLower());
-	}
-
-	for(auto entry : knownMods)
-	{
-		modList.push_back(entry);
-	}
-	return modList;
-}
-
-QVector<QString> CModList::getChildren(QString parent) const
-{
-	QVector<QString> children;
-
-	int depth = parent.count('.') + 1;
-	for(const QString & mod : getModList())
-	{
-		if(mod.count('.') == depth && mod.startsWith(parent))
-			children.push_back(mod);
-	}
-	return children;
-}

+ 0 - 118
launcher/modManager/cmodlist.h

@@ -1,118 +0,0 @@
-/*
- * cmodlist.h, part of VCMI engine
- *
- * Authors: listed in file AUTHORS in main folder
- *
- * License: GNU General Public License v2.0 or later
- * Full text of license available in license.txt file, in main folder
- *
- */
-#pragma once
-
-#include <QVariantMap>
-#include <QVariant>
-#include <QVector>
-
-#include "modsettingsstorage.h"
-
-namespace ModStatus
-{
-enum EModStatus
-{
-	MASK_NONE = 0,
-	ENABLED = 1,
-	INSTALLED = 2,
-	UPDATEABLE = 4,
-	MASK_ALL = 255
-};
-}
-
-class CModEntry
-{
-	// repository contains newest version only (if multiple are available)
-	QVariantMap repository;
-	QVariantMap localData;
-	bool modActive;
-
-	QString modname;
-
-	QVariant getValueImpl(QString value, bool localized) const;
-public:
-	CModEntry(QVariantMap repository, QVariantMap localData, bool modActive, QString modname);
-
-	// installed and enabled
-	bool isEnabled() const;
-	// installed but disabled
-	bool isDisabled() const;
-	// available in any of repositories but not installed
-	bool isAvailable() const;
-	// installed and greater version exists in repository
-	bool isUpdateable() const;
-	// installed
-	bool isInstalled() const;
-	// vcmi essential files
-	bool isEssential() const;
-	// checks if version is compatible with vcmi
-	bool isCompatible() const;
-	// returns true if mod should be visible in Launcher
-	bool isVisible() const;
-	// returns true if mod type is Translation
-	bool isTranslation() const;
-	// returns true if mod type is Compatibility
-	bool isCompatibilityPatch() const;
-	// returns true if this is a submod
-	bool isSubmod() const;
-
-	// see ModStatus enum
-	int getModStatus() const;
-
-	// Returns mod name / identifier (not human-readable)
-	QString getName() const;
-
-	// For submods only. Returns mod name / identifier of a top-level parent mod
-	QString getTopParentName() const;
-
-	// get value of some field in mod structure. Returns empty optional if value is not present
-	QVariant getValue(QString value) const;
-	QVariant getBaseValue(QString value) const;
-
-	QStringList getDependencies() const;
-	QStringList getConflicts() const;
-
-	static QString sizeToString(double size);
-};
-
-class CModList
-{
-	QVector<QVariantMap> repositories;
-	QVariantMap localModList;
-	std::shared_ptr<ModSettingsStorage> modSettings;
-
-	mutable QMap<QString, CModEntry> cachedMods;
-
-	QVariantMap copyField(QVariantMap data, QString from, QString to) const;
-
-	CModEntry getModUncached(QString modname) const;
-public:
-	virtual void resetRepositories();
-	virtual void reloadRepositories();
-	virtual void addRepository(QVariantMap data);
-	virtual void setLocalModList(QVariantMap data);
-	virtual void setModSettings(std::shared_ptr<ModSettingsStorage> data);
-	virtual void modChanged(QString modID);
-
-	// returns mod by name. Note: mod MUST exist
-	const CModEntry & getMod(QString modname) const;
-
-	// returns list of all mods necessary to run selected one, including mod itself
-	// order is: first mods in list don't have any dependencies, last mod is modname
-	// note: may include mods not present in list
-	QStringList getRequirements(QString modname);
-
-	bool hasMod(QString modname) const;
-
-	// returns list of all available mods
-	QVector<QString> getModList() const;
-
-	QVector<QString> getChildren(QString parent) const;
-};

+ 103 - 138
launcher/modManager/cmodlistview_moc.cpp

@@ -17,8 +17,9 @@
 #include <QCryptographicHash>
 #include <QRegularExpression>
 
-#include "cmodlistmodel_moc.h"
-#include "cmodmanager.h"
+#include "modstatemodel.h"
+#include "modstateitemmodel_moc.h"
+#include "modstatecontroller.h"
 #include "cdownloadmanager_moc.h"
 #include "chroniclesextractor.h"
 #include "../settingsView/csettingsview_moc.h"
@@ -34,15 +35,11 @@
 
 #include <future>
 
-static double mbToBytes(double mb)
-{
-	return mb * 1024 * 1024;
-}
-
 void CModListView::setupModModel()
 {
-	modModel = new CModListModel(this);
-	manager = std::make_unique<CModManager>(modModel);
+	modStateModel = std::make_shared<ModStateModel>();
+	modModel = new ModStateItemModel(this);
+	manager = std::make_unique<ModStateController>(modStateModel);
 }
 
 void CModListView::changeEvent(QEvent *event)
@@ -148,13 +145,7 @@ CModListView::CModListView(QWidget * parent)
 	dlManager = nullptr;
 
 	if(settings["launcher"]["autoCheckRepositories"].Bool())
-	{
 		loadRepositories();
-	}
-	else
-	{
-		manager->resetRepositories();
-	}
 
 #ifdef VCMI_MOBILE
 	for(auto * scrollWidget : {
@@ -171,8 +162,6 @@ CModListView::CModListView(QWidget * parent)
 
 void CModListView::loadRepositories()
 {
-	manager->resetRepositories();
-
 	QStringList repositories;
 
 	if (settings["launcher"]["defaultRepositoryEnabled"].Bool())
@@ -223,7 +212,7 @@ static QString replaceIfNotEmpty(QStringList value, QString pattern)
 	return "";
 }
 
-QString CModListView::genChangelogText(CModEntry & mod)
+QString CModListView::genChangelogText(ModState & mod)
 {
 	QString headerTemplate = "<p><span style=\" font-weight:600;\">%1: </span></p>";
 	QString entryBegin = "<p align=\"justify\"><ul>";
@@ -233,7 +222,7 @@ QString CModListView::genChangelogText(CModEntry & mod)
 
 	QString result;
 
-	QVariantMap changelog = mod.getValue("changelog").toMap();
+	QMap<QString, QStringList> changelog = mod.getChangelog();
 	QList<QString> versions = changelog.keys();
 
 	std::sort(versions.begin(), versions.end(), [](QString lesser, QString greater)
@@ -246,7 +235,7 @@ QString CModListView::genChangelogText(CModEntry & mod)
 	{
 		result += headerTemplate.arg(version);
 		result += entryBegin;
-		for(auto & line : changelog.value(version).toStringList())
+		for(auto & line : changelog.value(version))
 			result += entryLine.arg(line);
 		result += entryEnd;
 	}
@@ -259,21 +248,21 @@ QStringList CModListView::getModNames(QStringList input)
 
 	for(const auto & modID : input)
 	{
-		auto mod = modModel->getMod(modID.toLower());
+		auto mod = modStateModel->getMod(modID);
 
-		QString displayName = mod.getValue("name").toString();
+		QString displayName = mod.getName();
 		if (displayName.isEmpty())
 			displayName = modID.toLower();
 
 		if (mod.isSubmod())
 		{
-			auto parentModID = mod.getTopParentName();
-			auto parentMod = modModel->getMod(parentModID.toLower());
-			QString parentDisplayName = parentMod.getValue("name").toString();
+			auto parentModID = mod.getTopParentID();
+			auto parentMod = modStateModel->getMod(parentModID);
+			QString parentDisplayName = parentMod.getName();
 			if (parentDisplayName.isEmpty())
 				parentDisplayName = parentModID.toLower();
-
-			if (mod.isInstalled())
+			
+			if (modStateModel->isModInstalled(modID))
 				displayName = QString("%1 (%2)").arg(displayName, parentDisplayName);
 			else
 				displayName = parentDisplayName;
@@ -283,7 +272,7 @@ QStringList CModListView::getModNames(QStringList input)
 	return result;
 }
 
-QString CModListView::genModInfoText(CModEntry & mod)
+QString CModListView::genModInfoText(ModState & mod)
 {
 	QString prefix = "<p><span style=\" font-weight:600;\">%1: </span>"; // shared prefix
 	QString redPrefix = "<p><span style=\" font-weight:600; color:red\">%1: </span>"; // shared prefix
@@ -297,29 +286,30 @@ QString CModListView::genModInfoText(CModEntry & mod)
 
 	QString result;
 
-	result += replaceIfNotEmpty(mod.getValue("name"), lineTemplate.arg(tr("Mod name")));
-	result += replaceIfNotEmpty(mod.getValue("installedVersion"), lineTemplate.arg(tr("Installed version")));
-	result += replaceIfNotEmpty(mod.getValue("latestVersion"), lineTemplate.arg(tr("Latest version")));
+	result += replaceIfNotEmpty(mod.getName(), lineTemplate.arg(tr("Mod name")));
+	result += replaceIfNotEmpty(mod.getInstalledVersion(), lineTemplate.arg(tr("Installed version")));
+	result += replaceIfNotEmpty(mod.getRepositoryVersion(), lineTemplate.arg(tr("Latest version")));
+
+	if(!mod.getLocalSizeFormatted().isEmpty())
+		result += replaceIfNotEmpty(mod.getLocalSizeFormatted(), lineTemplate.arg(tr("Size")));
 
-	if(mod.getValue("localSizeBytes").isValid())
-		result += replaceIfNotEmpty(CModEntry::sizeToString(mod.getValue("localSizeBytes").toDouble()), lineTemplate.arg(tr("Size")));
-	if((mod.isAvailable() || mod.isUpdateable()) && mod.getValue("downloadSize").isValid())
-		result += replaceIfNotEmpty(CModEntry::sizeToString(mbToBytes(mod.getValue("downloadSize").toDouble())), lineTemplate.arg(tr("Download size")));
+	if((!mod.isInstalled() || mod.isUpdateAvailable()) && !mod.getDownloadSizeFormatted().isEmpty())
+		result += replaceIfNotEmpty(mod.getDownloadSizeFormatted(), lineTemplate.arg(tr("Download size")));
 	
-	result += replaceIfNotEmpty(mod.getValue("author"), lineTemplate.arg(tr("Authors")));
+	result += replaceIfNotEmpty(mod.getAuthors(), lineTemplate.arg(tr("Authors")));
 
-	if(mod.getValue("licenseURL").isValid())
-		result += urlTemplate.arg(tr("License")).arg(mod.getValue("licenseURL").toString()).arg(mod.getValue("licenseName").toString());
+	if(mod.getLicenseName().isEmpty())
+		result += urlTemplate.arg(tr("License")).arg(mod.getLicenseUrl()).arg(mod.getLicenseName());
 
-	if(mod.getValue("contact").isValid())
-		result += urlTemplate.arg(tr("Contact")).arg(mod.getValue("contact").toString()).arg(mod.getValue("contact").toString());
+	if(mod.getContact().isEmpty())
+		result += urlTemplate.arg(tr("Contact")).arg(mod.getContact()).arg(mod.getContact());
 
 	//compatibility info
 	if(!mod.isCompatible())
 	{
-		auto compatibilityInfo = mod.getValue("compatibility").toMap();
-		auto minStr = compatibilityInfo.value("min").toString();
-		auto maxStr = compatibilityInfo.value("max").toString();
+		auto compatibilityInfo = mod.getCompatibleVersionRange();
+		auto minStr = compatibilityInfo.first;
+		auto maxStr = compatibilityInfo.second;
 
 		result += incompatibleString.arg(tr("Compatibility"));
 		if(minStr == maxStr)
@@ -338,31 +328,24 @@ QString CModListView::genModInfoText(CModEntry & mod)
 		}
 	}
 
-	QStringList supportedLanguages;
-	QVariant baseLanguageVariant = mod.getBaseValue("language");
+	QVariant baseLanguageVariant = mod.getBaseLanguage();
 	QString baseLanguageID = baseLanguageVariant.isValid() ? baseLanguageVariant.toString() : "english";
 
-	bool needToShowSupportedLanguages = false;
+	QStringList supportedLanguages = mod.getSupportedLanguages();
 
-	for(const auto & language : Languages::getLanguageList())
+	if(supportedLanguages.size() > 1)
 	{
-		QString languageID = QString::fromStdString(language.identifier);
+		QStringList supportedLanguagesTranslated;
 
-		if (languageID != baseLanguageID && !mod.getValue(languageID).isValid())
-			continue;
-
-		if (languageID != baseLanguageID)
-			needToShowSupportedLanguages = true;
+		for (const auto & languageID : supportedLanguages)
+			supportedLanguagesTranslated += QApplication::translate("Language", Languages::getLanguageOptions(languageID.toStdString()).nameEnglish.c_str());
 
-		supportedLanguages += QApplication::translate("Language", language.nameEnglish.c_str());
+		result += replaceIfNotEmpty(supportedLanguagesTranslated, lineTemplate.arg(tr("Languages")));
 	}
 
-	if(needToShowSupportedLanguages)
-		result += replaceIfNotEmpty(supportedLanguages, lineTemplate.arg(tr("Languages")));
-
 	result += replaceIfNotEmpty(getModNames(mod.getDependencies()), lineTemplate.arg(tr("Required mods")));
 	result += replaceIfNotEmpty(getModNames(mod.getConflicts()), lineTemplate.arg(tr("Conflicting mods")));
-	result += replaceIfNotEmpty(mod.getValue("description"), textTemplate.arg(tr("Description")));
+	result += replaceIfNotEmpty(mod.getDescription(), textTemplate.arg(tr("Description")));
 
 	result += "<p></p>"; // to get some empty space
 
@@ -413,7 +396,7 @@ void CModListView::selectMod(const QModelIndex & index)
 	else
 	{
 		const auto modName = index.data(ModRoles::ModNameRole).toString();
-		auto mod = modModel->getMod(modName);
+		auto mod = modStateModel->getMod(modName);
 
 		ui->modInfoBrowser->setHtml(genModInfoText(mod));
 		ui->changelogBrowser->setHtml(genChangelogText(mod));
@@ -429,15 +412,15 @@ void CModListView::selectMod(const QModelIndex & index)
 		ui->enableButton->setVisible(mod.isDisabled());
 		ui->installButton->setVisible(mod.isAvailable() && !mod.isSubmod());
 		ui->uninstallButton->setVisible(mod.isInstalled() && !mod.isSubmod());
-		ui->updateButton->setVisible(mod.isUpdateable());
+		ui->updateButton->setVisible(mod.isUpdateAvailable());
 
 		// Block buttons if action is not allowed at this time
 		// TODO: automate handling of some of these cases instead of forcing player
 		// to resolve all conflicts manually.
-		ui->disableButton->setEnabled(!hasDependentMods && !mod.isEssential());
+		ui->disableButton->setEnabled(!hasDependentMods && !mod.isHidden());
 		ui->enableButton->setEnabled(!hasBlockingMods && !hasInvalidDeps);
 		ui->installButton->setEnabled(!hasInvalidDeps);
-		ui->uninstallButton->setEnabled(!hasDependentMods && !mod.isEssential());
+		ui->uninstallButton->setEnabled(!hasDependentMods && !mod.isHidden());
 		ui->updateButton->setEnabled(!hasInvalidDeps && !hasDependentMods);
 
 		loadScreenshots();
@@ -471,35 +454,16 @@ void CModListView::on_lineEdit_textChanged(const QString & arg1)
 
 void CModListView::on_comboBox_currentIndexChanged(int index)
 {
-	switch(index)
-	{
-	case 0:
-		filterModel->setTypeFilter(ModStatus::MASK_NONE, ModStatus::MASK_NONE);
-		break;
-	case 1:
-		filterModel->setTypeFilter(ModStatus::MASK_NONE, ModStatus::INSTALLED);
-		break;
-	case 2:
-		filterModel->setTypeFilter(ModStatus::INSTALLED, ModStatus::INSTALLED);
-		break;
-	case 3:
-		filterModel->setTypeFilter(ModStatus::UPDATEABLE, ModStatus::UPDATEABLE);
-		break;
-	case 4:
-		filterModel->setTypeFilter(ModStatus::ENABLED | ModStatus::INSTALLED, ModStatus::ENABLED | ModStatus::INSTALLED);
-		break;
-	case 5:
-		filterModel->setTypeFilter(ModStatus::INSTALLED, ModStatus::ENABLED | ModStatus::INSTALLED);
-		break;
-	}
+	auto enumIndex = static_cast<ModFilterMask>(index);
+	filterModel->setTypeFilter(enumIndex);
 }
 
 QStringList CModListView::findInvalidDependencies(QString mod)
 {
 	QStringList ret;
-	for(QString requirement : modModel->getRequirements(mod))
+	for(QString requirement : modStateModel->getMod(mod).getDependencies())
 	{
-		if(!modModel->hasMod(requirement) && !modModel->hasMod(requirement.split(QChar('.'))[0]))
+		if(!modStateModel->isModExists(requirement) && !modStateModel->isModExists(modStateModel->getMod(requirement).getTopParentID()))
 			ret += requirement;
 	}
 	return ret;
@@ -508,11 +472,11 @@ QStringList CModListView::findInvalidDependencies(QString mod)
 QStringList CModListView::findBlockingMods(QString modUnderTest)
 {
 	QStringList ret;
-	auto required = modModel->getRequirements(modUnderTest);
+	auto required = modStateModel->getMod(modUnderTest).getDependencies();
 
-	for(QString name : modModel->getModList())
+	for(QString name : modStateModel->getAllMods())
 	{
-		auto mod = modModel->getMod(name);
+		auto mod = modStateModel->getMod(name);
 
 		if(mod.isEnabled())
 		{
@@ -531,9 +495,9 @@ QStringList CModListView::findBlockingMods(QString modUnderTest)
 QStringList CModListView::findDependentMods(QString mod, bool excludeDisabled)
 {
 	QStringList ret;
-	for(QString modName : modModel->getModList())
+	for(QString modName : modStateModel->getAllMods())
 	{
-		auto current = modModel->getMod(modName);
+		auto current = modStateModel->getMod(modName);
 
 		if(!current.isInstalled() || !current.isVisible())
 			continue;
@@ -561,9 +525,11 @@ void CModListView::enableModByName(QString modName)
 	assert(findBlockingMods(modName).empty());
 	assert(findInvalidDependencies(modName).empty());
 
-	for(auto & name : modModel->getRequirements(modName))
+	auto mod = modStateModel->getMod(modName);
+
+	for(auto & name : mod.getDependencies())
 	{
-		if(modModel->getMod(name).isDisabled())
+		if(modStateModel->getMod(name).isDisabled())
 			manager->enableMod(name);
 	}
 	emit modsChanged();
@@ -580,7 +546,7 @@ void CModListView::on_disableButton_clicked()
 
 void CModListView::disableModByName(QString modName)
 {
-	if(modModel->hasMod(modName) && modModel->getMod(modName).isEnabled())
+	if(modStateModel->isModExists(modName) && modStateModel->getMod(modName).isEnabled())
 		manager->disableMod(modName);
 
 	emit modsChanged();
@@ -592,12 +558,12 @@ void CModListView::on_updateButton_clicked()
 
 	assert(findInvalidDependencies(modName).empty());
 
-	for(auto & name : modModel->getRequirements(modName))
+	for(auto & name : modStateModel->getMod(modName).getDependencies())
 	{
-		auto mod = modModel->getMod(name);
+		auto mod = modStateModel->getMod(name);
 		// update required mod, install missing (can be new dependency)
-		if(mod.isUpdateable() || !mod.isInstalled())
-			downloadFile(name + ".zip", mod.getValue("download").toString(), name, mbToBytes(mod.getValue("downloadSize").toDouble()));
+		if(mod.isUpdateAvailable() || !mod.isInstalled())
+			downloadFile(name + ".zip", mod.getDownloadUrl(), name, mod.getDownloadSizeMegabytes());
 	}
 }
 
@@ -606,9 +572,9 @@ void CModListView::on_uninstallButton_clicked()
 	QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString();
 	// NOTE: perhaps add "manually installed" flag and uninstall those dependencies that don't have it?
 
-	if(modModel->hasMod(modName) && modModel->getMod(modName).isInstalled())
+	if(modStateModel->isModExists(modName) && modStateModel->getMod(modName).isInstalled())
 	{
-		if(modModel->getMod(modName).isEnabled())
+		if(modStateModel->getMod(modName).isEnabled())
 			manager->disableMod(modName);
 		manager->uninstallMod(modName);
 	}
@@ -622,19 +588,19 @@ void CModListView::on_installButton_clicked()
 	QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString();
 
 	assert(findInvalidDependencies(modName).empty());
-
-	for(auto & name : modModel->getRequirements(modName))
+	
+	for(auto & name : modStateModel->getMod(modName).getDependencies())
 	{
-		auto mod = modModel->getMod(name);
+		auto mod = modStateModel->getMod(name);
 		if(mod.isAvailable())
-			downloadFile(name + ".zip", mod.getValue("download").toString(), name, mbToBytes(mod.getValue("downloadSize").toDouble()));
+			downloadFile(name + ".zip", mod.getDownloadUrl(), name, mod.getDownloadSizeMegabytes());
 		else if(!mod.isEnabled())
 			enableModByName(name);
 	}
 
-	for(auto & name : modModel->getMod(modName).getConflicts())
+	for(auto & name : modStateModel->getMod(modName).getConflicts())
 	{
-		auto mod = modModel->getMod(name);
+		auto mod = modStateModel->getMod(name);
 		if(mod.isEnabled())
 		{
 			//TODO: consider reverse dependencies disabling
@@ -697,8 +663,8 @@ void CModListView::manualInstallFile(QString filePath)
 				for(auto widget : qApp->allWidgets())
 					if(auto settingsView = qobject_cast<CSettingsView *>(widget))
 						settingsView->loadSettings();
-				manager->loadMods();
-				manager->loadModSettings();
+//				manager->loadMods();
+//				manager->loadModSettings();
 			}
 		}
 	}
@@ -726,7 +692,7 @@ void CModListView::downloadFile(QString file, QUrl url, QString description, qin
 		connect(manager.get(), SIGNAL(extractionProgress(qint64,qint64)),
 			this, SLOT(extractionProgress(qint64,qint64)));
 
-		connect(modModel, &CModListModel::dataChanged, filterModel, &QAbstractItemModel::dataChanged);
+		connect(modModel, &ModStateItemModel::dataChanged, filterModel, &QAbstractItemModel::dataChanged);
 
 		const auto progressBarFormat = tr("Downloading %1. %p% (%v MB out of %m MB) finished").arg(description);
 		ui->progressBar->setFormat(progressBarFormat);
@@ -849,8 +815,8 @@ void CModListView::installFiles(QStringList files)
 			images.push_back(filename);
 	}
 
-	if (!repositories.empty())
-		manager->loadRepositories(repositories);
+//	if (!repositories.empty())
+//		manager->loadRepositories(repositories);
 
 	if(!mods.empty())
 		installMods(mods);
@@ -881,8 +847,8 @@ void CModListView::installFiles(QStringList files)
 		{
 			//update
 			CResourceHandler::get("initial")->updateFilteredFiles([](const std::string &){ return true; });
-			manager->loadMods();
-			modModel->reloadRepositories();
+//			manager->loadMods();
+//			modModel->reloadRepositories();
 			emit modsChanged();
 		}
 	}
@@ -909,7 +875,7 @@ void CModListView::installMods(QStringList archives)
 	// disable mod(s), to properly recalculate dependencies, if changed
 	for(QString mod : boost::adaptors::reverse(modNames))
 	{
-		CModEntry entry = modModel->getMod(mod);
+		ModState entry = modStateModel->getMod(mod);
 		if(entry.isInstalled())
 		{
 			// enable mod if installed and enabled
@@ -927,7 +893,7 @@ void CModListView::installMods(QStringList archives)
 	// uninstall old version of mod, if installed
 	for(QString mod : boost::adaptors::reverse(modNames))
 	{
-		if(modModel->getMod(mod).isInstalled())
+		if(modStateModel->getMod(mod).isInstalled())
 			manager->uninstallMod(mod);
 	}
 
@@ -941,19 +907,19 @@ void CModListView::installMods(QStringList archives)
 
 	enableMod = [&](QString modName)
 	{
-		auto mod = modModel->getMod(modName);
-		if(mod.isInstalled() && !mod.getValue("keepDisabled").toBool())
+		auto mod = modStateModel->getMod(modName);
+		if(mod.isInstalled() && !mod.isKeptDisabled())
 		{
 			for (auto const & dependencyName : mod.getDependencies())
 			{
-				auto dependency = modModel->getMod(dependencyName);
+				auto dependency = modStateModel->getMod(dependencyName);
 				if(dependency.isDisabled())
 					manager->enableMod(dependencyName);
 			}
 
 			if(mod.isDisabled() && manager->enableMod(modName))
 			{
-				for(QString child : modModel->getChildren(modName))
+				for(QString child : modStateModel->getSubmods(modName))
 					enableMod(child);
 			}
 		}
@@ -1025,9 +991,9 @@ void CModListView::loadScreenshots()
 		
 		ui->screenshotsList->clear();
 		QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString();
-		assert(modModel->hasMod(modName)); //should be filtered out by check above
+		assert(modStateModel->isModExists(modName)); //should be filtered out by check above
 
-		for(QString url : modModel->getMod(modName).getValue("screenshots").toStringList())
+		for(QString url : modStateModel->getMod(modName).getScreenshots())
 		{
 			// URL must be encoded to something else to get rid of symbols illegal in file names
 			const auto hashed = QCryptographicHash::hash(url.toUtf8(), QCryptographicHash::Md5);
@@ -1061,52 +1027,51 @@ void CModListView::on_screenshotsList_clicked(const QModelIndex & index)
 	}
 }
 
-const CModList & CModListView::getModList() const
-{
-	assert(modModel);
-	return *modModel;
-}
+//const CModList & CModListView::getModList() const
+//{
+//	assert(modModel);
+//	return *modModel;
+//}
 
 void CModListView::doInstallMod(const QString & modName)
 {
 	assert(findInvalidDependencies(modName).empty());
 
-	for(auto & name : modModel->getRequirements(modName))
+	for(auto & name : modStateModel->getMod(modName).getDependencies())
 	{
-		auto mod = modModel->getMod(name);
+		auto mod = modStateModel->getMod(name);
 		if(!mod.isInstalled())
-			downloadFile(name + ".zip", mod.getValue("download").toString(), name, mbToBytes(mod.getValue("downloadSize").toDouble()));
+			downloadFile(name + ".zip", mod.getDownloadUrl(), name, mod.getDownloadSizeMegabytes());
 	}
 }
 
 bool CModListView::isModAvailable(const QString & modName)
 {
-	auto mod = modModel->getMod(modName);
-	return mod.isAvailable();
+	return !modStateModel->isModInstalled(modName);
 }
 
 bool CModListView::isModEnabled(const QString & modName)
 {
-	auto mod = modModel->getMod(modName);
+	auto mod = modStateModel->getMod(modName);
 	return mod.isEnabled();
 }
 
 bool CModListView::isModInstalled(const QString & modName)
 {
-	auto mod = modModel->getMod(modName);
+	auto mod = modStateModel->getMod(modName);
 	return mod.isInstalled();
 }
 
 QString CModListView::getTranslationModName(const QString & language)
 {
-	for(const auto & modName : modModel->getModList())
+	for(const auto & modName : modStateModel->getAllMods())
 	{
-		auto mod = modModel->getMod(modName);
+		auto mod = modStateModel->getMod(modName);
 
 		if (!mod.isTranslation())
 			continue;
 
-		if (mod.getBaseValue("language").toString() != language)
+		if (mod.getBaseLanguage() != language)
 			continue;
 
 		return modName;
@@ -1121,7 +1086,7 @@ void CModListView::on_allModsView_doubleClicked(const QModelIndex &index)
 		return;
 	
 	auto modName = index.data(ModRoles::ModNameRole).toString();
-	auto mod = modModel->getMod(modName);
+	auto mod = modStateModel->getMod(modName);
 	
 	bool hasInvalidDeps = !findInvalidDependencies(modName).empty();
 	bool hasBlockingMods = !findBlockingMods(modName).empty();
@@ -1133,7 +1098,7 @@ void CModListView::on_allModsView_doubleClicked(const QModelIndex &index)
 		return;
 	}
 
-	if(!hasInvalidDeps && !hasDependentMods && mod.isUpdateable() && index.column() == ModFields::STATUS_UPDATE)
+	if(!hasInvalidDeps && !hasDependentMods && mod.isUpdateAvailable() && index.column() == ModFields::STATUS_UPDATE)
 	{
 		on_updateButton_clicked();
 		return;
@@ -1155,7 +1120,7 @@ void CModListView::on_allModsView_doubleClicked(const QModelIndex &index)
 		return;
 	}
 
-	if(!hasDependentMods && !mod.isEssential() && mod.isEnabled())
+	if(!hasDependentMods && !mod.isVisible() && mod.isEnabled())
 	{
 		on_disableButton_clicked();
 		return;

+ 10 - 8
launcher/modManager/cmodlistview_moc.h

@@ -17,21 +17,23 @@ namespace Ui
 class CModListView;
 }
 
-class CModManager;
+class ModStateController;
 class CModList;
-class CModListModel;
+class ModStateItemModel;
+class ModStateModel;
 class CModFilterModel;
 class CDownloadManager;
 class QTableWidgetItem;
 
-class CModEntry;
+class ModState;
 
 class CModListView : public QWidget
 {
 	Q_OBJECT
 
-	std::unique_ptr<CModManager> manager;
-	CModListModel * modModel;
+	std::shared_ptr<ModStateModel> modStateModel;
+	std::unique_ptr<ModStateController> manager;
+	ModStateItemModel * modModel;
 	CModFilterModel * filterModel;
 	CDownloadManager * dlManager;
 
@@ -59,8 +61,8 @@ class CModListView : public QWidget
 	void installMaps(QStringList maps);
 	void installFiles(QStringList mods);
 
-	QString genChangelogText(CModEntry & mod);
-	QString genModInfoText(CModEntry & mod);
+	QString genChangelogText(ModState & mod);
+	QString genModInfoText(ModState & mod);
 
 	void changeEvent(QEvent *event) override;
 	void dragEnterEvent(QDragEnterEvent* event) override;
@@ -79,7 +81,7 @@ public:
 
 	void selectMod(const QModelIndex & index);
 
-	const CModList & getModList() const;
+	//const CModList & getModList() const;
 	
 	// First Launch View interface
 

+ 0 - 191
launcher/modManager/modsettingsstorage.cpp

@@ -1,191 +0,0 @@
-/*
- * modsettignsstorage.cpp, part of VCMI engine
- *
- * Authors: listed in file AUTHORS in main folder
- *
- * License: GNU General Public License v2.0 or later
- * Full text of license available in license.txt file, in main folder
- *
- */
-#include "StdInc.h"
-#include "modsettingsstorage.h"
-
-#include "../../lib/VCMIDirs.h"
-#include "../vcmiqt/jsonutils.h"
-
-static QVariant writeValue(QString path, QVariantMap input, QVariant value)
-{
-	if(path.size() > 1)
-	{
-		QString entryName = path.section('/', 0, 1);
-		QString remainder = "/" + path.section('/', 2, -1);
-		entryName.remove(0, 1);
-		input.insert(entryName, writeValue(remainder, input.value(entryName).toMap(), value));
-		return input;
-	}
-	else
-	{
-		return value;
-	}
-}
-
-void ModSettingsStorage::createInitialPreset()
-{
-	// TODO: scan mods directory for all its content?
-
-	QStringList modList;
-	QVariantMap preset;
-	QVariantMap presetList;
-
-	modList.push_back("vcmi");
-	preset.insert("mods", modList);
-	presetList.insert("default", preset);
-	config.insert("presets", presetList);
-}
-
-void ModSettingsStorage::importInitialPreset()
-{
-	QStringList modList;
-	QMap<QString, QVariantMap> modSettings;
-
-	QVariantMap activeMods = config["activeMods"].toMap();
-	for (QVariantMap::const_iterator modEntry = activeMods.begin(); modEntry != activeMods.end(); ++modEntry)
-	{
-		if (modEntry.value().toMap()["active"].toBool())
-			modList.push_back(modEntry.key());
-
-		QVariantMap submods = modEntry.value().toMap()["mods"].toMap();
-		for (QVariantMap::const_iterator submodEntry = submods.begin(); submodEntry != submods.end(); ++submodEntry)
-			modSettings[modEntry.key()].insert(submodEntry.key(), submodEntry.value().toMap()["active"]);
-	}
-
-	QVariantMap importedPreset;
-	QVariantMap modSettingsVariant;
-	QVariantMap presetList;
-
-	for (QMap<QString, QVariantMap>::const_iterator modEntry = modSettings.begin(); modEntry != modSettings.end(); ++modEntry)
-		modSettingsVariant.insert(modEntry.key(), modEntry.value());
-
-	importedPreset.insert("mods", modList);
-	importedPreset.insert("settings", modSettingsVariant);
-	presetList.insert("default", importedPreset);
-	config.insert("presets", presetList);
-}
-
-ModSettingsStorage::ModSettingsStorage()
-{
-	config = JsonUtils::JsonFromFile(settingsPath()).toMap();
-
-	if (!config.contains("presets"))
-	{
-		config.insert("activePreset", QVariant("default"));
-		if (config.contains("activeMods"))
-			importInitialPreset(); // 1.5 format import
-		else
-			createInitialPreset(); // new install
-
-		JsonUtils::JsonToFile(settingsPath(), config);
-	}
-}
-
-QString ModSettingsStorage::settingsPath() const
-{
-	return pathToQString(VCMIDirs::get().userConfigPath() / "modSettings.json");
-}
-
-void ModSettingsStorage::setRootModActive(const QString & modName, bool on)
-{
-	QString presetName = getActivePreset();
-	QStringList activeMods = getActiveMods();
-
-	assert(modName.count('.') == 0); // this method should never be used for submods
-
-	if (on)
-		activeMods.push_back(modName);
-	else
-		activeMods.removeAll(modName);
-
-	config = writeValue("/presets/" + presetName + "/mods", config, activeMods).toMap();
-
-	JsonUtils::JsonToFile(settingsPath(), config);
-}
-
-void ModSettingsStorage::registerNewMod(const QString & modName, bool keepDisabled)
-{
-	if (!modName.contains('.'))
-		return;
-
-	QString rootModName = modName.section('.', 0, 0);
-	QString settingName = modName.section('.', 1);
-
-	QVariantMap modSettings = getModSettings(rootModName);
-
-	if (modSettings.contains(settingName))
-		return;
-
-	setModSettingActive(modName, !keepDisabled);
-}
-
-void ModSettingsStorage::setModSettingActive(const QString & modName, bool on)
-{
-	QString presetName = getActivePreset();
-	QString rootModName = modName.section('.', 0, 0);
-	QString settingName = modName.section('.', 1);
-	QVariantMap modSettings = getModSettings(rootModName);
-
-	assert(modName.count('.') != 0); // this method should only be used for submods
-
-	modSettings.insert(settingName, QVariant(on));
-
-	config = writeValue("/presets/" + presetName + "/settings/" + rootModName, config, modSettings).toMap();
-
-	JsonUtils::JsonToFile(settingsPath(), config);
-}
-
-void ModSettingsStorage::setModActive(const QString & modName, bool on)
-{
-	if (modName.contains('.'))
-		setModSettingActive(modName, on);
-	else
-		setRootModActive(modName, on);
-}
-
-void ModSettingsStorage::setActivePreset(const QString & presetName)
-{
-	config.insert("activePreset", QVariant(presetName));
-}
-
-QString ModSettingsStorage::getActivePreset() const
-{
-	return config["activePreset"].toString();
-}
-
-bool ModSettingsStorage::isModActive(const QString & modName) const
-{
-	if (modName.contains('.'))
-	{
-		QString rootModName = modName.section('.', 0, 0);
-		QString settingName = modName.section('.', 1);
-
-		return getModSettings(rootModName)[settingName].toBool();
-	}
-	else
-		return getActiveMods().contains(modName);
-}
-
-QStringList ModSettingsStorage::getActiveMods() const
-{
-	return getActivePresetData()["mods"].toStringList();
-}
-
-QVariantMap ModSettingsStorage::getActivePresetData() const
-{
-	QString presetName = getActivePreset();
-	return config["presets"].toMap()[presetName].toMap();
-}
-
-QVariantMap ModSettingsStorage::getModSettings(const QString & modName) const
-{
-	QString presetName = getActivePreset();
-	return getActivePresetData()["settings"].toMap()[modName].toMap();
-}

+ 0 - 42
launcher/modManager/modsettingsstorage.h

@@ -1,42 +0,0 @@
-/*
- * modsettignsstorage.h, part of VCMI engine
- *
- * Authors: listed in file AUTHORS in main folder
- *
- * License: GNU General Public License v2.0 or later
- * Full text of license available in license.txt file, in main folder
- *
- */
-#pragma once
-
-#include <QVariantMap>
-
-class ModSettingsStorage
-{
-	QVariantMap config;
-
-	QString settingsPath() const;
-
-	void createInitialPreset();
-	void importInitialPreset();
-
-	void setRootModActive(const QString & modName, bool on);
-	void setModSettingActive(const QString & modName, bool on);
-
-	QVariantMap getActivePresetData() const;
-	QStringList getActiveMods() const;
-	QVariantMap getModSettings(const QString & modName) const;
-
-public:
-	ModSettingsStorage();
-
-	void registerNewMod(const QString & modName, bool keepDisabled);
-
-	void setModActive(const QString & modName, bool on);
-	void setActivePreset(const QString & presetName);
-
-	QString getActivePreset() const;
-	bool isModActive(const QString & modName) const;
-	//QStringList getPresetsList() const;
-};
-

+ 212 - 0
launcher/modManager/modstate.cpp

@@ -0,0 +1,212 @@
+/*
+ * modstate.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "modstate.h"
+
+#include "../../lib/modding/ModDescription.h"
+#include "../../lib/json/JsonNode.h"
+#include "../../lib/texts/CGeneralTextHandler.h"
+
+ModState::ModState(const ModDescription & impl)
+	: impl(impl)
+{
+}
+
+QString ModState::getName() const
+{
+	return QString::fromStdString(impl.getName());
+}
+
+QString ModState::getType() const
+{
+	return QString::fromStdString(impl.getValue("modType").String());
+}
+
+QString ModState::getDescription() const
+{
+	return QString::fromStdString(impl.getValue("description").String());
+}
+
+QString ModState::getID() const
+{
+	return QString::fromStdString(impl.getID());
+}
+
+QString ModState::getParentID() const
+{
+	return QString::fromStdString(impl.getParentID());
+}
+
+QString ModState::getTopParentID() const
+{
+	return QString::fromStdString(impl.getParentID());
+}
+
+template<typename Container>
+QStringList stringListStdToQt(const Container & container)
+{
+	QStringList result;
+	for (const auto & str : container)
+		result.push_back(QString::fromStdString(str));
+	return result;
+}
+
+QStringList ModState::getDependencies() const
+{
+	return stringListStdToQt(impl.getDependencies());
+}
+
+QStringList ModState::getConflicts() const
+{
+	return stringListStdToQt(impl.getConflicts());
+}
+
+QStringList ModState::getScreenshots() const
+{
+	return {}; // TODO
+}
+
+QString ModState::getBaseLanguage() const
+{
+	return QString::fromStdString(impl.getBaseLanguage());
+}
+
+QStringList ModState::getSupportedLanguages() const
+{
+	return {}; //TODO
+}
+
+QMap<QString, QStringList> ModState::getChangelog() const
+{
+	return {}; //TODO
+}
+
+QString ModState::getInstalledVersion() const
+{
+	return QString::fromStdString(impl.getLocalValue("version").String());
+}
+
+QString ModState::getRepositoryVersion() const
+{
+	return QString::fromStdString(impl.getRepositoryValue("version").String());
+}
+
+double ModState::getDownloadSizeMegabytes() const
+{
+	return impl.getRepositoryValue("downloadSize").Float();
+}
+
+size_t ModState::getDownloadSizeBytes() const
+{
+	return getDownloadSizeMegabytes() * 1024 * 1024;
+}
+
+QString ModState::getDownloadSizeFormatted() const
+{
+	return {}; // TODO
+}
+
+QString ModState::getLocalSizeFormatted() const
+{
+	return {}; // TODO
+}
+
+QString ModState::getAuthors() const
+{
+	return QString::fromStdString(impl.getValue("authors").String());
+}
+
+QString ModState::getContact() const
+{
+	return QString::fromStdString(impl.getValue("contact").String());
+}
+
+QString ModState::getLicenseUrl() const
+{
+	return QString::fromStdString(impl.getValue("licenseUrl").String());
+}
+
+QString ModState::getLicenseName() const
+{
+	return QString::fromStdString(impl.getValue("licenseName").String());
+}
+
+QString ModState::getDownloadUrl() const
+{
+	return QString::fromStdString(impl.getRepositoryValue("download").String());
+}
+
+QPair<QString, QString> ModState::getCompatibleVersionRange() const
+{
+	return {}; // TODO
+}
+
+bool ModState::isSubmod() const
+{
+	return !getParentID().isEmpty();
+}
+
+bool ModState::isCompatibility() const
+{
+	return impl.isCompatibility();
+}
+
+bool ModState::isTranslation() const
+{
+	return impl.isTranslation();
+}
+
+bool ModState::isVisible() const
+{
+	return !isHidden();
+}
+
+bool ModState::isHidden() const
+{
+	if (isTranslation() && !isInstalled())
+		return impl.getBaseLanguage() == CGeneralTextHandler::getPreferredLanguage();
+
+	return isCompatibility() || getID() == "vcmi";
+}
+
+bool ModState::isDisabled() const
+{
+	return false; // TODO
+}
+
+bool ModState::isEnabled() const
+{
+	return true; // TODO
+}
+
+bool ModState::isAvailable() const
+{
+	return !isInstalled();
+}
+
+bool ModState::isInstalled() const
+{
+	return impl.isInstalled();
+}
+
+bool ModState::isUpdateAvailable() const
+{
+	return getInstalledVersion() != getRepositoryVersion() && !getRepositoryVersion().isEmpty();
+}
+
+bool ModState::isCompatible() const
+{
+	return true; //TODO
+}
+
+bool ModState::isKeptDisabled() const
+{
+	return impl.keepDisabled();
+}

+ 71 - 0
launcher/modManager/modstate.h

@@ -0,0 +1,71 @@
+/*
+ * modstate.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+VCMI_LIB_NAMESPACE_BEGIN
+class ModDescription;
+VCMI_LIB_NAMESPACE_END
+
+/// Class that represent current state of mod in Launcher
+/// Provides Qt-based interface to library class ModDescription
+class ModState
+{
+	const ModDescription & impl;
+
+public:
+	explicit ModState(const ModDescription & impl);
+
+	QString getName() const;
+	QString getType() const;
+	QString getDescription() const;
+
+	QString getID() const;
+	QString getParentID() const;
+	QString getTopParentID() const;
+
+	QStringList getDependencies() const;
+	QStringList getConflicts() const;
+	QStringList getScreenshots() const;
+
+	QString getBaseLanguage() const;
+	QStringList getSupportedLanguages() const;
+
+	QMap<QString, QStringList> getChangelog() const;
+
+	QString getInstalledVersion() const;
+	QString getRepositoryVersion() const;
+
+	double getDownloadSizeMegabytes() const;
+	size_t getDownloadSizeBytes() const;
+	QString getDownloadSizeFormatted() const;
+	QString getLocalSizeFormatted() const;
+	QString getAuthors() const;
+	QString getContact() const;
+	QString getLicenseUrl() const;
+	QString getLicenseName() const;
+
+	QString getDownloadUrl() const;
+
+	QPair<QString, QString> getCompatibleVersionRange() const;
+
+	bool isSubmod() const;
+	bool isCompatibility() const;
+	bool isTranslation() const;
+
+	bool isVisible() const;
+	bool isHidden() const;
+	bool isDisabled() const;
+	bool isEnabled() const;
+	bool isAvailable() const;
+	bool isInstalled() const;
+	bool isUpdateAvailable() const;
+	bool isCompatible() const;
+	bool isKeptDisabled() const;
+};

+ 47 - 58
launcher/modManager/cmodmanager.cpp → launcher/modManager/modstatecontroller.cpp

@@ -1,5 +1,5 @@
 /*
- * cmodmanager.cpp, part of VCMI engine
+ * modstatecontroller.cpp, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -8,13 +8,16 @@
  *
  */
 #include "StdInc.h"
-#include "cmodmanager.h"
+#include "modstatecontroller.h"
+
+#include "modstatemodel.h"
 
 #include "../../lib/VCMIDirs.h"
 #include "../../lib/filesystem/Filesystem.h"
 #include "../../lib/filesystem/CZipLoader.h"
 #include "../../lib/modding/CModHandler.h"
 #include "../../lib/modding/IdentifierStorage.h"
+#include "../../lib/json/JsonNode.h"
 
 #include "../vcmiqt/jsonutils.h"
 #include "../vcmiqt/launcherdirs.h"
@@ -61,37 +64,24 @@ QString detectModArchive(QString path, QString modName, std::vector<std::string>
 }
 
 
-CModManager::CModManager(CModList * modList)
+ModStateController::ModStateController(std::shared_ptr<ModStateModel> modList)
 	: modList(modList)
 {
-	modSettings = std::make_shared<ModSettingsStorage>();
-	loadMods();
-	loadModSettings();
-}
-
-void CModManager::loadModSettings()
-{
-	modList->setModSettings(modSettings);
 }
 
-void CModManager::resetRepositories()
-{
-	modList->resetRepositories();
-}
+ModStateController::~ModStateController() = default;
 
-void CModManager::loadRepositories(QVector<QVariantMap> repomap)
+void ModStateController::loadRepositories(QVector<JsonNode> repomap)
 {
-	for (auto const & entry : repomap)
-		modList->addRepository(entry);
-	modList->reloadRepositories();
+	modList->setRepositories(repomap);
 }
 
-void CModManager::loadMods()
-{
-	CModHandler handler;
-	auto installedMods = handler.getAllMods();
-	localMods.clear();
-
+//void ModStateController::loadMods()
+//{
+//	CModHandler handler;
+//	auto installedMods = handler.getAllMods();
+//	localMods.clear();
+//
 //	for(auto modname : installedMods)
 //	{
 //			//calculate mod size
@@ -115,43 +105,43 @@ void CModManager::loadMods()
 //			localMods.insert(modNameQt, mod);
 //			modSettings->registerNewMod(modNameQt, json["keepDisabled"].Bool());
 //	}
-	modList->setLocalModList(localMods);
-}
+//	modList->setLocalModList(localMods);
+//}
 
-bool CModManager::addError(QString modname, QString message)
+bool ModStateController::addError(QString modname, QString message)
 {
 	recentErrors.push_back(QString("%1: %2").arg(modname).arg(message));
 	return false;
 }
 
-QStringList CModManager::getErrors()
+QStringList ModStateController::getErrors()
 {
 	QStringList ret = recentErrors;
 	recentErrors.clear();
 	return ret;
 }
 
-bool CModManager::installMod(QString modname, QString archivePath)
+bool ModStateController::installMod(QString modname, QString archivePath)
 {
 	return canInstallMod(modname) && doInstallMod(modname, archivePath);
 }
 
-bool CModManager::uninstallMod(QString modname)
+bool ModStateController::uninstallMod(QString modname)
 {
 	return canUninstallMod(modname) && doUninstallMod(modname);
 }
 
-bool CModManager::enableMod(QString modname)
+bool ModStateController::enableMod(QString modname)
 {
 	return canEnableMod(modname) && doEnableMod(modname, true);
 }
 
-bool CModManager::disableMod(QString modname)
+bool ModStateController::disableMod(QString modname)
 {
 	return canDisableMod(modname) && doEnableMod(modname, false);
 }
 
-bool CModManager::canInstallMod(QString modname)
+bool ModStateController::canInstallMod(QString modname)
 {
 	auto mod = modList->getMod(modname);
 
@@ -163,7 +153,7 @@ bool CModManager::canInstallMod(QString modname)
 	return true;
 }
 
-bool CModManager::canUninstallMod(QString modname)
+bool ModStateController::canUninstallMod(QString modname)
 {
 	auto mod = modList->getMod(modname);
 
@@ -176,11 +166,11 @@ bool CModManager::canUninstallMod(QString modname)
 	return true;
 }
 
-bool CModManager::canEnableMod(QString modname)
+bool ModStateController::canEnableMod(QString modname)
 {
 	auto mod = modList->getMod(modname);
 
-	if(mod.isEnabled())
+	if(modList->isModEnabled(modname))
 		return addError(modname, tr("Mod is already enabled"));
 
 	if(!mod.isInstalled())
@@ -192,62 +182,61 @@ bool CModManager::canEnableMod(QString modname)
 
 	for(auto modEntry : mod.getDependencies())
 	{
-		if(!modList->hasMod(modEntry)) // required mod is not available
+		if(!modList->isModExists(modEntry)) // required mod is not available
 			return addError(modname, tr("Required mod %1 is missing").arg(modEntry));
 
-		CModEntry modData = modList->getMod(modEntry);
+		ModState modData = modList->getMod(modEntry);
 
-		if(!modData.isCompatibilityPatch() && !modData.isEnabled())
+		if(!modData.isCompatibility() && !modList->isModEnabled(modEntry))
 			return addError(modname, tr("Required mod %1 is not enabled").arg(modEntry));
 	}
 
-	for(QString modEntry : modList->getModList())
+	for(QString modEntry : modList->getAllMods())
 	{
 		auto mod = modList->getMod(modEntry);
 
 		// "reverse conflict" - enabled mod has this one as conflict
-		if(mod.isEnabled() && mod.getConflicts().contains(modname))
+		if(modList->isModEnabled(modname) && mod.getConflicts().contains(modname))
 			return addError(modname, tr("This mod conflicts with %1").arg(modEntry));
 	}
 
 	for(auto modEntry : mod.getConflicts())
 	{
 		// check if conflicting mod installed and enabled
-		if(modList->hasMod(modEntry) && modList->getMod(modEntry).isEnabled())
+		if(modList->isModExists(modEntry) && modList->isModEnabled(modEntry))
 			return addError(modname, tr("This mod conflicts with %1").arg(modEntry));
 	}
 	return true;
 }
 
-bool CModManager::canDisableMod(QString modname)
+bool ModStateController::canDisableMod(QString modname)
 {
 	auto mod = modList->getMod(modname);
 
-	if(mod.isDisabled())
+	if(!modList->isModEnabled(modname))
 		return addError(modname, tr("Mod is already disabled"));
 
 	if(!mod.isInstalled())
 		return addError(modname, tr("Mod must be installed first"));
 
-	for(QString modEntry : modList->getModList())
+	for(QString modEntry : modList->getAllMods())
 	{
 		auto current = modList->getMod(modEntry);
 
-		if(current.getDependencies().contains(modname) && current.isEnabled())
+		if(current.getDependencies().contains(modname) && modList->isModEnabled(modEntry))
 			return addError(modname, tr("This mod is needed to run %1").arg(modEntry));
 	}
 	return true;
 }
 
-bool CModManager::doEnableMod(QString mod, bool on)
+bool ModStateController::doEnableMod(QString mod, bool on)
 {
-	modSettings->setModActive(mod, on);
-	modList->modChanged(mod);
-
+	//modSettings->setModActive(mod, on);
+	//modList->modChanged(mod);
 	return true;
 }
 
-bool CModManager::doInstallMod(QString modname, QString archivePath)
+bool ModStateController::doInstallMod(QString modname, QString archivePath)
 {
 	const auto destDir = CLauncherDirs::modsPath() + QChar{'/'};
 
@@ -301,13 +290,13 @@ bool CModManager::doInstallMod(QString modname, QString archivePath)
 		removeModDir(destDir + upperLevel);
 	
 	CResourceHandler::get("initial")->updateFilteredFiles([](const std::string &) { return true; });
-	loadMods();
-	modList->reloadRepositories();
+	//loadMods();
+	//modList->reloadRepositories();
 
 	return true;
 }
 
-bool CModManager::doUninstallMod(QString modname)
+bool ModStateController::doUninstallMod(QString modname)
 {
 	ResourcePath resID(std::string("Mods/") + modname.toStdString(), EResType::DIRECTORY);
 	// Get location of the mod, in case-insensitive way
@@ -321,13 +310,13 @@ bool CModManager::doUninstallMod(QString modname)
 		return addError(modname, tr("Mod is located in protected directory, please remove it manually:\n") + modFullDir.absolutePath());
 
 	CResourceHandler::get("initial")->updateFilteredFiles([](const std::string &){ return true; });
-	loadMods();
-	modList->reloadRepositories();
+	//loadMods();
+	//modList->reloadRepositories();
 
 	return true;
 }
 
-bool CModManager::removeModDir(QString path)
+bool ModStateController::removeModDir(QString path)
 {
 	// issues 2673 and 2680 its why you do not recursively remove without sanity check
 	QDir checkDir(path);

+ 13 - 11
launcher/modManager/cmodmanager.h → launcher/modManager/modstatecontroller.h

@@ -1,5 +1,5 @@
 /*
- * cmodmanager.h, part of VCMI engine
+ * modstatecontroller.h, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -9,21 +9,25 @@
  */
 #pragma once
 
-#include "cmodlist.h"
-#include "modsettingsstorage.h"
+#include <QVector>
 
-class CModManager : public QObject
+VCMI_LIB_NAMESPACE_BEGIN
+class JsonNode;
+VCMI_LIB_NAMESPACE_END
+
+class ModStateModel;
+
+class ModStateController : public QObject, public boost::noncopyable
 {
 	Q_OBJECT
 
-	CModList * modList;
+	std::shared_ptr<ModStateModel> modList;
 
 	// check-free version of public method
 	bool doEnableMod(QString mod, bool on);
 	bool doInstallMod(QString mod, QString archivePath);
 	bool doUninstallMod(QString mod);
 
-	std::shared_ptr<ModSettingsStorage> modSettings;
 	QVariantMap localMods;
 
 	QStringList recentErrors;
@@ -31,12 +35,10 @@ class CModManager : public QObject
 	bool removeModDir(QString mod);
 
 public:
-	CModManager(CModList * modList);
+	ModStateController(std::shared_ptr<ModStateModel> modList);
+	~ModStateController();
 
-	void resetRepositories();
-	void loadRepositories(QVector<QVariantMap> repomap);
-	void loadModSettings();
-	void loadMods();
+	void loadRepositories(QVector<JsonNode> repositoriesList);
 
 	QStringList getErrors();
 

+ 47 - 52
launcher/modManager/cmodlistmodel_moc.cpp → launcher/modManager/modstateitemmodel_moc.cpp

@@ -1,5 +1,5 @@
 /*
- * cmodlistmodel_moc.cpp, part of VCMI engine
+ * modstateview_moc.cpp, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -8,7 +8,9 @@
  *
  */
 #include "StdInc.h"
-#include "cmodlistmodel_moc.h"
+#include "modstateitemmodel_moc.h"
+
+#include "modstatemodel.h"
 
 #include <QIcon>
 
@@ -23,12 +25,12 @@ static const QString iconEnabledSubmod = ":/icons/submod-enabled.png";
 static const QString iconUpdate = ":/icons/mod-update.png";
 }
 
-CModListModel::CModListModel(QObject * parent)
+ModStateItemModel::ModStateItemModel(QObject * parent)
 	: QAbstractItemModel(parent)
 {
 }
 
-QString CModListModel::modIndexToName(const QModelIndex & index) const
+QString ModStateItemModel::modIndexToName(const QModelIndex & index) const
 {
 	if(index.isValid())
 	{
@@ -38,7 +40,7 @@ QString CModListModel::modIndexToName(const QModelIndex & index) const
 }
 
 
-QString CModListModel::modTypeName(QString modTypeID) const
+QString ModStateItemModel::modTypeName(QString modTypeID) const
 {
 	static const QMap<QString, QString> modTypes = {
 		{"Translation", tr("Translation")},
@@ -71,28 +73,28 @@ QString CModListModel::modTypeName(QString modTypeID) const
 	return tr("Other");
 }
 
-QVariant CModListModel::getValue(const CModEntry & mod, int field) const
+QVariant ModStateItemModel::getValue(const ModState & mod, int field) const
 {
 	switch(field)
 	{
 		case ModFields::STATUS_ENABLED:
-			return mod.getModStatus() & (ModStatus::ENABLED | ModStatus::INSTALLED);
+			return model->isModEnabled(mod.getID());
 
 		case ModFields::STATUS_UPDATE:
-			return mod.getModStatus() & (ModStatus::UPDATEABLE | ModStatus::INSTALLED);
+			return model->isModUpdateAvailable(mod.getID());
 
 		case ModFields::NAME:
-			return mod.getValue("name");
+			return mod.getName();
 
 		case ModFields::TYPE:
-			return modTypeName(mod.getValue("modType").toString());
+			return modTypeName(mod.getType());
 
 		default:
 			return QVariant();
 	}
 }
 
-QVariant CModListModel::getText(const CModEntry & mod, int field) const
+QVariant ModStateItemModel::getText(const ModState & mod, int field) const
 {
 	switch(field)
 	{
@@ -104,49 +106,52 @@ QVariant CModListModel::getText(const CModEntry & mod, int field) const
 	}
 }
 
-QVariant CModListModel::getIcon(const CModEntry & mod, int field) const
+QVariant ModStateItemModel::getIcon(const ModState & mod, int field) const
 {
+
 	if (field == ModFields::STATUS_ENABLED)
 	{
+		if (!model->isModInstalled(mod.getID()))
+			return QVariant();
+
 		if(mod.isSubmod())
 		{
-			QString toplevelParent = mod.getName().section('.', 0, 0);
-			if (getMod(toplevelParent).isDisabled())
+			if (!model->isModEnabled(mod.getTopParentID()))
 			{
-				if (mod.isEnabled())
+				if (model->isModEnabled(mod.getID()))
 					return QIcon(ModStatus::iconEnabledSubmod);
-				if(mod.isDisabled())
+				else
 					return QIcon(ModStatus::iconDisabledSubmod);
 			}
 		}
 
-		if (mod.isEnabled())
+		if (model->isModEnabled(mod.getID()))
 			return QIcon(ModStatus::iconEnabled);
-		if(mod.isDisabled())
+		else
 			return QIcon(ModStatus::iconDisabled);
 	}
 
 	if(field == ModFields::STATUS_UPDATE)
 	{
-		if (mod.isUpdateable())
+		if (model->isModUpdateAvailable(mod.getID()))
 			return QIcon(ModStatus::iconUpdate);
-		if(!mod.isInstalled())
+		if (!model->isModInstalled(mod.getID()))
 			return QIcon(ModStatus::iconDownload);
 	}
 
 	return QVariant();
 }
 
-QVariant CModListModel::getTextAlign(int field) const
+QVariant ModStateItemModel::getTextAlign(int field) const
 {
 	return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
 }
 
-QVariant CModListModel::data(const QModelIndex & index, int role) const
+QVariant ModStateItemModel::data(const QModelIndex & index, int role) const
 {
 	if(index.isValid())
 	{
-		auto mod = getMod(modIndexToName(index));
+		auto mod = model->getMod(modIndexToName(index));
 
 		switch(role)
 		{
@@ -165,24 +170,24 @@ QVariant CModListModel::data(const QModelIndex & index, int role) const
 	return QVariant();
 }
 
-int CModListModel::rowCount(const QModelIndex & index) const
+int ModStateItemModel::rowCount(const QModelIndex & index) const
 {
 	if(index.isValid())
 		return modIndex[modIndexToName(index)].size();
 	return modIndex[""].size();
 }
 
-int CModListModel::columnCount(const QModelIndex &) const
+int ModStateItemModel::columnCount(const QModelIndex &) const
 {
 	return ModFields::COUNT;
 }
 
-Qt::ItemFlags CModListModel::flags(const QModelIndex &) const
+Qt::ItemFlags ModStateItemModel::flags(const QModelIndex &) const
 {
 	return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
 }
 
-QVariant CModListModel::headerData(int section, Qt::Orientation orientation, int role) const
+QVariant ModStateItemModel::headerData(int section, Qt::Orientation orientation, int role) const
 {
 	static const QString header[ModFields::COUNT] =
 	{
@@ -197,32 +202,23 @@ QVariant CModListModel::headerData(int section, Qt::Orientation orientation, int
 	return QVariant();
 }
 
-void CModListModel::reloadRepositories()
+void ModStateItemModel::reloadRepositories()
 {
 	beginResetModel();
 	endResetModel();
 }
 
-void CModListModel::resetRepositories()
+void ModStateItemModel::modChanged(QString modID)
 {
-	beginResetModel();
-	CModList::resetRepositories();
-	endResetModel();
-}
-
-void CModListModel::modChanged(QString modID)
-{
-	CModList::modChanged(modID);
-
 	int index = modNameToID.indexOf(modID);
 	QModelIndex parent = this->parent(createIndex(0, 0, index));
 	int row = modIndex[modIndexToName(parent)].indexOf(modID);
 	emit dataChanged(createIndex(row, 0, index), createIndex(row, 4, index));
 }
 
-void CModListModel::endResetModel()
+void ModStateItemModel::endResetModel()
 {
-	modNameToID = getModList();
+	modNameToID = model->getAllMods();
 	modIndex.clear();
 	for(const QString & str : modNameToID)
 	{
@@ -238,7 +234,7 @@ void CModListModel::endResetModel()
 	QAbstractItemModel::endResetModel();
 }
 
-QModelIndex CModListModel::index(int row, int column, const QModelIndex & parent) const
+QModelIndex ModStateItemModel::index(int row, int column, const QModelIndex & parent) const
 {
 	if(parent.isValid())
 	{
@@ -253,7 +249,7 @@ QModelIndex CModListModel::index(int row, int column, const QModelIndex & parent
 	return QModelIndex();
 }
 
-QModelIndex CModListModel::parent(const QModelIndex & child) const
+QModelIndex ModStateItemModel::parent(const QModelIndex & child) const
 {
 	QString modID = modNameToID[child.internalId()];
 	for(auto entry = modIndex.begin(); entry != modIndex.end(); entry++) // because using range-for entry type is QMap::value_type oO
@@ -266,26 +262,25 @@ QModelIndex CModListModel::parent(const QModelIndex & child) const
 	return QModelIndex();
 }
 
-void CModFilterModel::setTypeFilter(int filteredType, int filterMask)
+void CModFilterModel::setTypeFilter(ModFilterMask newFilterMask)
 {
-	this->filterMask = filterMask;
-	this->filteredType = filteredType;
+	filterMask = newFilterMask;
 	invalidateFilter();
 }
 
 bool CModFilterModel::filterMatchesThis(const QModelIndex & source) const
 {
-	CModEntry mod = base->getMod(source.data(ModRoles::ModNameRole).toString());
-	return (mod.getModStatus() & filterMask) == filteredType &&
+	//QString modID =source.data(ModRoles::ModNameRole).toString();
+	//ModState mod = base->model->getMod(modID);
+	return /*(mod.getModStatus() & filterMask) == filteredType &&*/
 	       QSortFilterProxyModel::filterAcceptsRow(source.row(), source.parent());
 }
 
 bool CModFilterModel::filterAcceptsRow(int source_row, const QModelIndex & source_parent) const
 {
 	QModelIndex index = base->index(source_row, 0, source_parent);
-
-	CModEntry mod = base->getMod(index.data(ModRoles::ModNameRole).toString());
-	if (!mod.isVisible())
+	QString modID = index.data(ModRoles::ModNameRole).toString();
+	if (base->model->isModVisible(modID))
 		return false;
 
 	if(filterMatchesThis(index))
@@ -309,8 +304,8 @@ bool CModFilterModel::filterAcceptsRow(int source_row, const QModelIndex & sourc
 	return false;
 }
 
-CModFilterModel::CModFilterModel(CModListModel * model, QObject * parent)
-	: QSortFilterProxyModel(parent), base(model), filteredType(ModStatus::MASK_NONE), filterMask(ModStatus::MASK_NONE)
+CModFilterModel::CModFilterModel(ModStateItemModel * model, QObject * parent)
+	: QSortFilterProxyModel(parent), base(model), filterMask(ModFilterMask::ALL)
 {
 	setSourceModel(model);
 	setSortRole(ModRoles::ValueRole);

+ 30 - 23
launcher/modManager/cmodlistmodel_moc.h → launcher/modManager/modstateitemmodel_moc.h

@@ -1,5 +1,5 @@
 /*
- * cmodlistmodel_moc.h, part of VCMI engine
+ * modstateview_moc.h, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -9,11 +9,12 @@
  */
 #pragma once
 
-#include "cmodlist.h"
-
 #include <QAbstractTableModel>
 #include <QSortFilterProxyModel>
 
+class ModStateModel;
+class ModState;
+
 namespace ModFields
 {
 enum EModFields
@@ -26,6 +27,16 @@ enum EModFields
 };
 }
 
+enum class ModFilterMask : uint8_t
+{
+	ALL,
+	AVAILABLE,
+	INSTALLED,
+	UPDATEABLE,
+	ENABLED,
+	DISABLED
+};
+
 namespace ModRoles
 {
 enum EModRoles
@@ -35,14 +46,17 @@ enum EModRoles
 };
 }
 
-class CModListModel : public QAbstractItemModel, public CModList
+class ModStateItemModel : public QAbstractItemModel
 {
+	friend class CModFilterModel;
 	Q_OBJECT
 
-	QVector<QString> modNameToID;
+	std::shared_ptr<ModStateModel> model;
+
+	QStringList modNameToID;
 	// contains mapping mod -> numbered list of submods
 	// mods that have no parent located under "" key (empty string)
-	QMap<QString, QVector<QString>> modIndex;
+	QMap<QString, QStringList> modIndex;
 
 	void endResetModel();
 
@@ -50,17 +64,16 @@ class CModListModel : public QAbstractItemModel, public CModList
 	QString modTypeName(QString modTypeID) const;
 
 	QVariant getTextAlign(int field) const;
-	QVariant getValue(const CModEntry & mod, int field) const;
-	QVariant getText(const CModEntry & mod, int field) const;
-	QVariant getIcon(const CModEntry & mod, int field) const;
+	QVariant getValue(const ModState & mod, int field) const;
+	QVariant getText(const ModState & mod, int field) const;
+	QVariant getIcon(const ModState & mod, int field) const;
 
 public:
-	explicit CModListModel(QObject * parent = nullptr);
+	explicit ModStateItemModel(QObject * parent = nullptr);
 
 	/// CModListContainer overrides
-	void resetRepositories() override;
-	void reloadRepositories() override;
-	void modChanged(QString modID) override;
+	void reloadRepositories();
+	void modChanged(QString modID);
 
 	QVariant data(const QModelIndex & index, int role) const override;
 	QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
@@ -72,25 +85,19 @@ public:
 	QModelIndex parent(const QModelIndex & child) const override;
 
 	Qt::ItemFlags flags(const QModelIndex & index) const override;
-
-signals:
-
-public slots:
-
 };
 
 class CModFilterModel : public QSortFilterProxyModel
 {
-	CModListModel * base;
-	int filteredType;
-	int filterMask;
+	ModStateItemModel * base;
+	ModFilterMask filterMask;
 
 	bool filterMatchesThis(const QModelIndex & source) const;
 
 	bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const override;
 
 public:
-	void setTypeFilter(int filteredType, int filterMask);
+	void setTypeFilter(ModFilterMask filterMask);
 
-	CModFilterModel(CModListModel * model, QObject * parent = nullptr);
+	CModFilterModel(ModStateItemModel * model, QObject * parent = nullptr);
 };

+ 73 - 0
launcher/modManager/modstatemodel.cpp

@@ -0,0 +1,73 @@
+/*
+ * modstatemodel.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "modstatemodel.h"
+
+#include "../../lib/modding/ModManager.h"
+
+ModStateModel::ModStateModel()
+    :modManager(std::make_unique<ModManager>())
+{}
+
+ModStateModel::~ModStateModel() = default;
+
+void ModStateModel::setRepositories(QVector<JsonNode> repositoriesList)
+{
+	//TODO
+}
+
+ModState ModStateModel::getMod(QString modName) const
+{
+	return ModState(modManager->getModDescription(modName.toStdString()));
+}
+
+template<typename Container>
+QStringList stringListStdToQt(const Container & container)
+{
+	QStringList result;
+	for (const auto & str : container)
+		result.push_back(QString::fromStdString(str));
+	return result;
+}
+
+QStringList ModStateModel::getAllMods() const
+{
+	return stringListStdToQt(modManager->getActiveMods());
+}
+
+QStringList ModStateModel::getSubmods(QString modName) const
+{
+	return {}; //TODO
+}
+
+bool ModStateModel::isModExists(QString modName) const
+{
+	return vstd::contains(modManager->getAllMods(), modName.toStdString());
+}
+
+bool ModStateModel::isModInstalled(QString modName) const
+{
+	return getMod(modName).isInstalled();
+}
+
+bool ModStateModel::isModEnabled(QString modName) const
+{
+	return getMod(modName).isEnabled(); // TODO
+}
+
+bool ModStateModel::isModUpdateAvailable(QString modName) const
+{
+	return getMod(modName).isUpdateAvailable();
+}
+
+bool ModStateModel::isModVisible(QString modName) const
+{
+	return getMod(modName).isVisible();
+}

+ 40 - 0
launcher/modManager/modstatemodel.h

@@ -0,0 +1,40 @@
+/*
+ * modstatemodel.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "modstate.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+class JsonNode;
+class ModManager;
+VCMI_LIB_NAMESPACE_END
+
+/// Class that represent current state of available mods
+/// Provides Qt-based interface to library class ModManager
+class ModStateModel
+{
+	std::unique_ptr<ModManager> modManager;
+
+public:
+	ModStateModel();
+	~ModStateModel();
+
+	void setRepositories(QVector<JsonNode> repositoriesList);
+
+	ModState getMod(QString modName) const;
+	QStringList getAllMods() const;
+	QStringList getSubmods(QString modName) const;
+
+	bool isModExists(QString modName) const;
+	bool isModInstalled(QString modName) const;
+	bool isModEnabled(QString modName) const;
+	bool isModUpdateAvailable(QString modName) const;
+	bool isModVisible(QString modName) const;
+};

+ 22 - 32
lib/VCMI_Lib.cpp

@@ -162,48 +162,38 @@ void LibClasses::loadModFilesystem()
 	logGlobal->info("\tMod filesystems: %d ms", loadTime.getDiff());
 }
 
-static void logHandlerLoaded(const std::string & name, CStopWatch & timer)
-{
-	logGlobal->info("\t\t %s handler: %d ms", name, timer.getDiff());
-}
-
-template <class Handler> void createHandler(std::shared_ptr<Handler> & handler, const std::string &name, CStopWatch &timer)
+template <class Handler> void createHandler(std::shared_ptr<Handler> & handler)
 {
 	handler = std::make_shared<Handler>();
-	logHandlerLoaded(name, timer);
 }
 
 void LibClasses::init(bool onlyEssential)
 {
-	CStopWatch pomtime;
-	CStopWatch totalTime;
-
-	createHandler(settingsHandler, "Game Settings", pomtime);
+	createHandler(settingsHandler);
 	modh->initializeConfig();
 
-	createHandler(generaltexth, "General text", pomtime);
-	createHandler(bth, "Bonus type", pomtime);
-	createHandler(roadTypeHandler, "Road", pomtime);
-	createHandler(riverTypeHandler, "River", pomtime);
-	createHandler(terrainTypeHandler, "Terrain", pomtime);
-	createHandler(heroh, "Hero", pomtime);
-	createHandler(heroclassesh, "Hero classes", pomtime);
-	createHandler(arth, "Artifact", pomtime);
-	createHandler(creh, "Creature", pomtime);
-	createHandler(townh, "Town", pomtime);
-	createHandler(biomeHandler, "Obstacle set", pomtime);
-	createHandler(objh, "Object", pomtime);
-	createHandler(objtypeh, "Object types information", pomtime);
-	createHandler(spellh, "Spell", pomtime);
-	createHandler(skillh, "Skill", pomtime);
-	createHandler(terviewh, "Terrain view pattern", pomtime);
-	createHandler(tplh, "Template", pomtime); //templates need already resolved identifiers (refactor?)
+	createHandler(generaltexth);
+	createHandler(bth);
+	createHandler(roadTypeHandler);
+	createHandler(riverTypeHandler);
+	createHandler(terrainTypeHandler);
+	createHandler(heroh);
+	createHandler(heroclassesh);
+	createHandler(arth);
+	createHandler(creh);
+	createHandler(townh);
+	createHandler(biomeHandler);
+	createHandler(objh);
+	createHandler(objtypeh);
+	createHandler(spellh);
+	createHandler(skillh);
+	createHandler(terviewh);
+	createHandler(tplh); //templates need already resolved identifiers (refactor?)
 #if SCRIPTING_ENABLED
-	createHandler(scriptHandler, "Script", pomtime);
+	createHandler(scriptHandler);
 #endif
-	createHandler(battlefieldsHandler, "Battlefields", pomtime);
-	createHandler(obstacleHandler, "Obstacles", pomtime);
-	logGlobal->info("\tInitializing handlers: %d ms", totalTime.getDiff());
+	createHandler(battlefieldsHandler);
+	createHandler(obstacleHandler);
 
 	modh->load();
 	modh->afterLoad(onlyEssential);

+ 4 - 4
lib/modding/CModHandler.cpp

@@ -259,8 +259,8 @@ void CModHandler::initializeConfig()
 	for(const TModID & modName : getActiveMods())
 	{
 		const auto & mod = getModInfo(modName);
-		if (!mod.getConfig()["settings"].isNull())
-			VLC->settingsHandler->loadBase(mod.getConfig()["settings"]);
+		if (!mod.getLocalConfig()["settings"].isNull())
+			VLC->settingsHandler->loadBase(mod.getLocalConfig()["settings"]);
 	}
 }
 
@@ -271,8 +271,8 @@ void CModHandler::loadTranslation(const TModID & modName)
 	std::string preferredLanguage = VLC->generaltexth->getPreferredLanguage();
 	std::string modBaseLanguage = getModInfo(modName).getBaseLanguage();
 
-	JsonNode baseTranslation = JsonUtils::assembleFromFiles(mod.getConfig()["translations"]);
-	JsonNode extraTranslation = JsonUtils::assembleFromFiles(mod.getConfig()[preferredLanguage]["translations"]);
+	JsonNode baseTranslation = JsonUtils::assembleFromFiles(mod.getLocalConfig()["translations"]);
+	JsonNode extraTranslation = JsonUtils::assembleFromFiles(mod.getLocalConfig()[preferredLanguage]["translations"]);
 
 	VLC->generaltexth->loadTranslationOverrides(modName, modBaseLanguage, baseTranslation);
 	VLC->generaltexth->loadTranslationOverrides(modName, preferredLanguage, extraTranslation);

+ 1 - 1
lib/modding/ContentTypeHandler.cpp

@@ -297,7 +297,7 @@ void CContentHandler::afterLoadFinalization()
 
 void CContentHandler::preloadData(const ModDescription & mod)
 {
-	preloadModData(mod.getID(), mod.getConfig(), false);
+	preloadModData(mod.getID(), mod.getLocalConfig(), false);
 
 //	bool validate = validateMod(mod);
 //

+ 30 - 10
lib/modding/ModDescription.cpp

@@ -19,7 +19,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 ModDescription::ModDescription(const TModID & fullID, const JsonNode & config)
 	: identifier(fullID)
-	, config(std::make_unique<JsonNode>(config))
+	, localConfig(std::make_unique<JsonNode>(config))
 	, dependencies(loadModList(config["depends"]))
 	, softDependencies(loadModList(config["softDepends"]))
 	, conflicts(loadModList(config["conflicts"]))
@@ -72,27 +72,42 @@ const std::string & ModDescription::getBaseLanguage() const
 {
 	static const std::string defaultLanguage = "english";
 
-	return getConfig()["language"].isString() ? getConfig()["language"].String() : defaultLanguage;
+	return getLocalConfig()["language"].isString() ? getLocalConfig()["language"].String() : defaultLanguage;
 }
 
 const std::string & ModDescription::getName() const
 {
-	return getConfig()["name"].String();
+	return getLocalConfig()["name"].String();
 }
 
 const JsonNode & ModDescription::getFilesystemConfig() const
 {
-	return getConfig()["filesystem"];
+	return getLocalConfig()["filesystem"];
 }
 
-const JsonNode & ModDescription::getConfig() const
+const JsonNode & ModDescription::getLocalConfig() const
 {
-	return *config;
+	return *localConfig;
+}
+
+const JsonNode & ModDescription::getValue(const std::string & keyName) const
+{
+	return getLocalConfig()[keyName];
+}
+
+const JsonNode & ModDescription::getLocalValue(const std::string & keyName) const
+{
+	return getLocalConfig()[keyName];
+}
+
+const JsonNode & ModDescription::getRepositoryValue(const std::string & keyName) const
+{
+	return (*repositoryConfig)[keyName];
 }
 
 CModVersion ModDescription::getVersion() const
 {
-	return CModVersion::fromString(getConfig()["version"].String());
+	return CModVersion::fromString(getLocalConfig()["version"].String());
 }
 
 ModVerificationInfo ModDescription::getVerificationInfo() const
@@ -108,17 +123,22 @@ ModVerificationInfo ModDescription::getVerificationInfo() const
 
 bool ModDescription::isCompatibility() const
 {
-	return getConfig()["modType"].String() == "Compatibility";
+	return getLocalConfig()["modType"].String() == "Compatibility";
 }
 
 bool ModDescription::isTranslation() const
 {
-	return getConfig()["modType"].String() == "Translation";
+	return getLocalConfig()["modType"].String() == "Translation";
 }
 
 bool ModDescription::keepDisabled() const
 {
-	return getConfig()["keepDisabled"].Bool();
+	return getLocalConfig()["keepDisabled"].Bool();
+}
+
+bool ModDescription::isInstalled() const
+{
+	return !localConfig->isNull();
 }
 
 bool ModDescription::affectsGameplay() const

+ 9 - 3
lib/modding/ModDescription.h

@@ -26,12 +26,14 @@ class DLL_LINKAGE ModDescription : boost::noncopyable
 	TModSet softDependencies;
 	TModSet conflicts;
 
-	std::unique_ptr<JsonNode> config;
+	std::unique_ptr<JsonNode> localConfig;
+	std::unique_ptr<JsonNode> repositoryConfig;
 
 	TModSet loadModList(const JsonNode & configNode) const;
 
 public:
-	ModDescription(const TModID & fullID, const JsonNode & config);
+	ModDescription(const TModID & fullID, const JsonNode & localConfig);
+	ModDescription(const TModID & fullID, const JsonNode & localConfig, const JsonNode & repositoryConfig);
 	~ModDescription();
 
 	const TModID & getID() const;
@@ -45,7 +47,10 @@ public:
 	const std::string & getName() const;
 
 	const JsonNode & getFilesystemConfig() const;
-	const JsonNode & getConfig() const;
+	const JsonNode & getLocalConfig() const;
+	const JsonNode & getValue(const std::string & keyName) const;
+	const JsonNode & getLocalValue(const std::string & keyName) const;
+	const JsonNode & getRepositoryValue(const std::string & keyName) const;
 
 	CModVersion getVersion() const;
 	ModVerificationInfo getVerificationInfo() const;
@@ -54,6 +59,7 @@ public:
 	bool isCompatibility() const;
 	bool isTranslation() const;
 	bool keepDisabled() const;
+	bool isInstalled() const;
 };
 
 VCMI_LIB_NAMESPACE_END

+ 16 - 3
lib/modding/ModManager.cpp

@@ -203,7 +203,7 @@ std::vector<TModID> ModsPresetState::getActiveMods() const
 
 	for(const auto & activeMod : activeRootMods)
 	{
-		activeRootMods.push_back(activeMod);
+		allActiveMods.push_back(activeMod);
 
 		for(const auto & submod : getModSettings(activeMod))
 			if(submod.second)
@@ -212,7 +212,7 @@ std::vector<TModID> ModsPresetState::getActiveMods() const
 	return allActiveMods;
 }
 
-ModsStorage::ModsStorage(const std::vector<TModID> & modsToLoad)
+ModsStorage::ModsStorage(const std::vector<TModID> & modsToLoad, const std::vector<JsonNode> & repositoryList)
 {
 	JsonNode coreModConfig(JsonPath::builtin("config/gameConfig.json"));
 	coreModConfig.setModScope(ModScope::scopeBuiltin());
@@ -245,14 +245,21 @@ const ModDescription & ModsStorage::getMod(const TModID & fullID) const
 }
 
 ModManager::ModManager()
+	:ModManager(std::vector<JsonNode>())
+{
+}
+
+ModManager::ModManager(const std::vector<JsonNode> & repositoryList)
 	: modsState(std::make_unique<ModsState>())
 	, modsPreset(std::make_unique<ModsPresetState>())
 {
+
 	eraseMissingModsFromPreset();
+	//TODO: load only active mods & all their submods in game mode
+	modsStorage = std::make_unique<ModsStorage>(modsState->getAllMods(), repositoryList);
 	addNewModsToPreset();
 
 	std::vector<TModID> desiredModList = modsPreset->getActiveMods();
-	modsStorage = std::make_unique<ModsStorage>(desiredModList);
 	generateLoadOrder(desiredModList);
 }
 
@@ -273,6 +280,11 @@ const TModList & ModManager::getActiveMods() const
 	return activeMods;
 }
 
+const TModList & ModManager::getAllMods() const
+{
+	return activeMods; //TODO
+}
+
 void ModManager::eraseMissingModsFromPreset()
 {
 	const TModList & installedMods = modsState->getAllMods();
@@ -383,6 +395,7 @@ void ModManager::generateLoadOrder(std::vector<TModID> modsToResolve)
 		break;
 	}
 
+	assert(!sortedValidMods.empty());
 	activeMods = sortedValidMods;
 	brokenMods = modsToResolve;
 }

+ 4 - 2
lib/modding/ModManager.h

@@ -69,13 +69,13 @@ class ModsStorage : boost::noncopyable
 	std::map<TModID, ModDescription> mods;
 
 public:
-	ModsStorage(const TModList & modsToLoad);
+	ModsStorage(const TModList & modsToLoad, const std::vector<JsonNode> & repositoryList);
 
 	const ModDescription & getMod(const TModID & fullID) const;
 };
 
 /// Provides public interface to access mod state
-class ModManager : boost::noncopyable
+class DLL_LINKAGE ModManager : boost::noncopyable
 {
 	/// all currently active mods, in their load order
 	TModList activeMods;
@@ -92,11 +92,13 @@ class ModManager : boost::noncopyable
 	void addNewModsToPreset();
 
 public:
+	ModManager(const std::vector<JsonNode> & repositoryList);
 	ModManager();
 	~ModManager();
 
 	const ModDescription & getModDescription(const TModID & modID) const;
 	const TModList & getActiveMods() const;
+	const TModList & getAllMods() const;
 	bool isModActive(const TModID & modID) const;
 };