Explorar el Código

Launcher now supports submods. See forum thread for details.

Ivan Savenko hace 11 años
padre
commit
333a51a48a

+ 42 - 9
launcher/modManager/cmodlist.cpp

@@ -106,6 +106,18 @@ QString CModEntry::getName() const
 
 QVariant CModEntry::getValue(QString value) const
 {
+	if (repository.contains(value) && localData.contains(value))
+	{
+		// value is present in both repo and locally installed. Select one from latest version
+		QString installedVer = localData["installedVersion"].toString();
+		QString availableVer = repository["latestVersion"].toString();
+
+		if (compareVersions(installedVer, availableVer))
+			return repository[value];
+		else
+			return localData[value];
+	}
+
 	if (repository.contains(value))
 		return repository[value];
 
@@ -149,15 +161,36 @@ void CModList::setModSettings(QVariant data)
 	modSettings = data.toMap();
 }
 
-CModEntry CModList::getMod(QString modname) const
+void CModList::modChanged(QString modID)
 {
-	assert(hasMod(modname));
+}
+
+static QVariant getValue(QVariantMap 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.value(entryName).toMap(), remainder);
+	}
+	else
+	{
+		return input;
+	}
+}
+
+CModEntry CModList::getMod(QString modname) const
+{
 	QVariantMap repo;
 	QVariantMap local = localModList[modname].toMap();
 	QVariantMap settings;
 
-	QVariant conf = modSettings[modname];
+	QString path = modname;
+	path = "/" + path.replace(".", "/mods/");
+	QVariant conf = getValue(modSettings, path);
+
 	if (conf.isNull())
 	{
 		settings["active"] = true; // default
@@ -165,22 +198,22 @@ CModEntry CModList::getMod(QString modname) const
 	else
 	{
 		if (conf.canConvert<QVariantMap>())
-			settings = modSettings[modname].toMap();
+			settings = conf.toMap();
 		else
 			settings.insert("active", conf);
 	}
 
 	for (auto entry : repositories)
 	{
-		if (entry.contains(modname))
+		QVariant repoVal = getValue(entry, path);
+		if (repoVal.isValid())
 		{
 			if (repo.empty())
-				repo = entry[modname].toMap();
+				repo = repoVal.toMap();
 			else
 			{
-				if (CModEntry::compareVersions(repo["version"].toString(),
-				                               entry[modname].toMap()["version"].toString()))
-					repo = entry[modname].toMap();
+				if (CModEntry::compareVersions(repo["version"].toString(), repoVal.toMap()["version"].toString()))
+					repo = repoVal.toMap();
 			}
 		}
 	}

+ 1 - 0
launcher/modManager/cmodlist.h

@@ -65,6 +65,7 @@ public:
 	virtual void addRepository(QVariantMap data);
 	virtual void setLocalModList(QVariantMap data);
 	virtual void setModSettings(QVariant data);
+	virtual void modChanged(QString modID);
 
 	// returns mod by name. Note: mod MUST exist
 	CModEntry getMod(QString modname) const;

+ 89 - 29
launcher/modManager/cmodlistmodel_moc.cpp

@@ -7,10 +7,10 @@ namespace ModFields
 {
 	static const QString names [ModFields::COUNT] =
 	{
+		"name",
 		"",
 		"",
 		"modType",
-		"name",
 		"version",
 		"size",
 		"author"
@@ -18,10 +18,10 @@ namespace ModFields
 
 	static const QString header [ModFields::COUNT] =
 	{
+		"Name",
 		"", // status icon
 		"", // status icon
 		"Type",
-		"Name",
 		"Version",
 		"Size",
 		"Author"
@@ -38,13 +38,17 @@ namespace ModStatus
 }
 
 CModListModel::CModListModel(QObject *parent) :
-    QAbstractTableModel(parent)
+	QAbstractItemModel(parent)
 {
 }
 
-QString CModListModel::modIndexToName(int index) const
+QString CModListModel::modIndexToName(const QModelIndex & index) const
 {
-	return indexToName[index];
+	if (index.isValid())
+	{
+		return modNameToID.at(index.internalId());
+	}
+	return "";
 }
 
 QVariant CModListModel::getValue(const CModEntry &mod, int field) const
@@ -95,30 +99,34 @@ QVariant CModListModel::getTextAlign(int field) const
 {
 	if (field == ModFields::SIZE)
 		return QVariant(Qt::AlignRight | Qt::AlignVCenter);
-	else
-		return QVariant(Qt::AlignLeft  | Qt::AlignVCenter);
+	//if (field == ModFields::NAME)
+	//	return QVariant(Qt::AlignHCenter | Qt::AlignVCenter);
+	return QVariant(Qt::AlignLeft  | Qt::AlignVCenter);
 }
 
 QVariant CModListModel::data(const QModelIndex &index, int role) const
 {
 	if (index.isValid())
 	{
-		auto mod = getMod(modIndexToName(index.row()));
+		auto mod = getMod(modIndexToName(index));
 
 		switch (role)
 		{
 			case Qt::DecorationRole:    return getIcon(mod, index.column());
 			case Qt::DisplayRole:       return getText(mod, index.column());
-			case Qt::UserRole:          return getValue(mod, index.column());
 			case Qt::TextAlignmentRole: return getTextAlign(index.column());
+			case ModRoles::ValueRole:   return getValue(mod, index.column());
+			case ModRoles::ModNameRole: return mod.getName();
 		}
 	}
 	return QVariant();
 }
 
-int CModListModel::rowCount(const QModelIndex &) const
+int CModListModel::rowCount(const QModelIndex & index) const
 {
-	return indexToName.size();
+	if (index.isValid())
+		return modIndex[modIndexToName(index)].size();
+	return modIndex[""].size();
 }
 
 int CModListModel::columnCount(const QModelIndex &) const
@@ -152,24 +160,58 @@ void CModListModel::addRepository(QVariantMap data)
 	endResetModel();
 }
 
-void CModListModel::setLocalModList(QVariantMap data)
+void CModListModel::modChanged(QString modID)
 {
-	beginResetModel();
-	CModList::setLocalModList(data);
-	endResetModel();
+	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::setModSettings(QVariant data)
+void CModListModel::endResetModel()
 {
-	beginResetModel();
-	CModList::setModSettings(data);
-	endResetModel();
+	modNameToID = getModList();
+	modIndex.clear();
+	for (const QString & str : modNameToID)
+	{
+		if (str.contains('.'))
+		{
+			modIndex[str.section('.', 0, -2)].append(str);
+		}
+		else
+		{
+			modIndex[""].append(str);
+		}
+	}
+	QAbstractItemModel::endResetModel();
 }
 
-void CModListModel::endResetModel()
+QModelIndex CModListModel::index(int row, int column, const QModelIndex &parent) const
 {
-	indexToName = getModList();
-	QAbstractItemModel::endResetModel();
+	if (parent.isValid())
+	{
+		if (modIndex[modIndexToName(parent)].size() > row)
+			return createIndex(row, column, modNameToID.indexOf(modIndex[modIndexToName(parent)][row]));
+	}
+	else
+	{
+		if (modIndex[""].size() > row)
+			return createIndex(row, column, modNameToID.indexOf(modIndex[""][row]));
+	}
+	return QModelIndex();
+}
+
+QModelIndex CModListModel::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
+	{
+		if (entry.key() != "" && entry.value().indexOf(modID) != -1)
+		{
+			return createIndex(entry.value().indexOf(modID), child.column(), modNameToID.indexOf(entry.key()));
+		}
+	}
+	return QModelIndex();
 }
 
 void CModFilterModel::setTypeFilter(int filteredType, int filterMask)
@@ -179,17 +221,35 @@ void CModFilterModel::setTypeFilter(int filteredType, int filterMask)
 	invalidateFilter();
 }
 
-bool CModFilterModel::filterMatches(int modIndex) const
+bool CModFilterModel::filterMatchesThis(const QModelIndex &source) const
 {
-	CModEntry mod = base->getMod(base->modIndexToName(modIndex));
-
-	return (mod.getModStatus() & filterMask) == filteredType;
+	CModEntry mod = base->getMod(source.data(ModRoles::ModNameRole).toString());
+	return (mod.getModStatus() & filterMask) == filteredType &&
+			QSortFilterProxyModel::filterAcceptsRow(source.row(), source.parent());
 }
 
 bool CModFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
 {
-	if (filterMatches(source_row))
-		return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
+	QModelIndex index = base->index(source_row, 0, source_parent);
+
+	if (filterMatchesThis(index))
+	{
+		return true;
+	}
+
+	for (size_t i=0; i<base->rowCount(index); i++)
+	{
+		if (filterMatchesThis(index.child(i, 0)))
+			return true;
+	}
+
+	QModelIndex parent = source_parent;
+	while (parent.isValid())
+	{
+		if (filterMatchesThis(parent))
+			return true;
+		parent = parent.parent();
+	}
 	return false;
 }
 
@@ -200,5 +260,5 @@ CModFilterModel::CModFilterModel(CModListModel * model, QObject * parent):
     filterMask(ModStatus::MASK_NONE)
 {
 	setSourceModel(model);
-	setSortRole(Qt::UserRole);
+	setSortRole(ModRoles::ValueRole);
 }

+ 30 - 16
launcher/modManager/cmodlistmodel_moc.h

@@ -9,10 +9,10 @@ namespace ModFields
 {
 	enum EModFields
 	{
+		NAME,
 		STATUS_ENABLED,
 		STATUS_UPDATE,
 		TYPE,
-		NAME,
 		VERSION,
 		SIZE,
 		AUTHOR,
@@ -20,36 +20,50 @@ namespace ModFields
 	};
 }
 
-class CModListModel : public QAbstractTableModel, public CModList
+namespace ModRoles
+{
+	enum EModRoles
+	{
+		ValueRole = Qt::UserRole,
+		ModNameRole
+	};
+}
+
+class CModListModel : public QAbstractItemModel, public CModList
 {
 	Q_OBJECT
 
-	QVector<QString> indexToName;
+	QVector<QString> modNameToID;
+	// contains mapping mod -> numbered list of submods
+	// mods that have no parent located under "" key (empty string)
+	QMap<QString, QVector<QString>> modIndex;
 
 	void endResetModel();
 
+	QString modIndexToName(const QModelIndex & index) 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;
 public:
+	explicit CModListModel(QObject *parent = 0);
+
 	/// CModListContainer overrides
-	void resetRepositories();
-	void addRepository(QVariantMap data);
-	void setLocalModList(QVariantMap data);
-	void setModSettings(QVariant data);
+	void resetRepositories() override;
+	void addRepository(QVariantMap data) override;
+	void modChanged(QString modID);
 
-	QString modIndexToName(int index) const;
+	QVariant data(const QModelIndex &index, int role) const override;
+	QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
 
-	explicit CModListModel(QObject *parent = 0);
-	
-	QVariant data(const QModelIndex &index, int role) const;
-	QVariant headerData(int section, Qt::Orientation orientation, int role) const;
+	int rowCount(const QModelIndex &parent) const override;
+	int columnCount(const QModelIndex &parent) const override;
 
-	int rowCount(const QModelIndex &parent) const;
-	int columnCount(const QModelIndex &parent) const;
+	QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
+	QModelIndex parent(const QModelIndex &child) const override;
 
-	Qt::ItemFlags flags(const QModelIndex &index) const;
+	Qt::ItemFlags flags(const QModelIndex &index) const override;
 signals:
 	
 public slots:
@@ -62,7 +76,7 @@ class CModFilterModel : public QSortFilterProxyModel
 	int filteredType;
 	int filterMask;
 
-	bool filterMatches(int modIndex) const;
+	bool filterMatchesThis(const QModelIndex & source) const;
 
 	bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const;
 public:

+ 60 - 43
launcher/modManager/cmodlistview_moc.cpp

@@ -24,6 +24,7 @@ void CModListView::setupFilterModel()
 
 	filterModel->setFilterKeyColumn(-1); // filter across all columns
 	filterModel->setSortCaseSensitivity(Qt::CaseInsensitive); // to make it more user-friendly
+	filterModel->setDynamicSortFilter(true);
 }
 
 void CModListView::setupModsView()
@@ -31,20 +32,26 @@ void CModListView::setupModsView()
 	ui->allModsView->setModel(filterModel);
 	// input data is not sorted - sort it before display
 	ui->allModsView->sortByColumn(ModFields::TYPE, Qt::AscendingOrder);
+	ui->allModsView->setColumnWidth(ModFields::NAME, 185);
 	ui->allModsView->setColumnWidth(ModFields::STATUS_ENABLED, 30);
 	ui->allModsView->setColumnWidth(ModFields::STATUS_UPDATE, 30);
-	ui->allModsView->setColumnWidth(ModFields::TYPE, 80);
-	ui->allModsView->setColumnWidth(ModFields::NAME, 180);
+	ui->allModsView->setColumnWidth(ModFields::TYPE, 75);
 	ui->allModsView->setColumnWidth(ModFields::SIZE, 80);
 	ui->allModsView->setColumnWidth(ModFields::VERSION, 60);
 
+	ui->allModsView->header()->setSectionResizeMode(ModFields::STATUS_ENABLED, QHeaderView::Fixed);
+	ui->allModsView->header()->setSectionResizeMode(ModFields::STATUS_UPDATE,  QHeaderView::Fixed);
+
+	ui->allModsView->setUniformRowHeights(true);
+
 	connect( ui->allModsView->selectionModel(), SIGNAL( currentRowChanged( const QModelIndex &, const QModelIndex & )),
 	         this, SLOT( modSelected( const QModelIndex &, const QModelIndex & )));
 
 	connect( filterModel, SIGNAL( modelReset()),
 	         this, SLOT( modelReset()));
 
-	selectMod(filterModel->rowCount() > 0 ? 0 : -1);
+	connect( modModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
+			 this, SLOT(dataChanged(QModelIndex,QModelIndex)));
 }
 
 CModListView::CModListView(QWidget *parent) :
@@ -61,7 +68,8 @@ CModListView::CModListView(QWidget *parent) :
 
 	ui->progressWidget->setVisible(false);
 	dlManager = nullptr;
-	loadRepositories();
+	//loadRepositories();
+	hideModInfo();
 }
 
 void CModListView::loadRepositories()
@@ -133,39 +141,45 @@ QString CModListView::genModInfoText(CModEntry &mod)
 	QString lineTemplate = prefix + "%2</p>";
 	QString urlTemplate  = prefix + "<a href=\"%2\"><span style=\" text-decoration: underline; color:#0000ff;\">%2</span></a></p>";
 	QString textTemplate = prefix + "</p><p align=\"justify\">%2</p>";
-	QString noteTemplate = "<p align=\"justify\">%1: %2</p>";
+	QString listTemplate = "<p align=\"justify\">%1: %2</p>";
+	QString noteTemplate = "<p align=\"justify\">%1</p>";
 
 	QString result;
 
 	result += "<html><body>";
-	result += replaceIfNotEmpty(mod.getValue("name"), lineTemplate.arg("Mod name"));
-	result += replaceIfNotEmpty(mod.getValue("installedVersion"), lineTemplate.arg("Installed version"));
-	result += replaceIfNotEmpty(mod.getValue("latestVersion"), lineTemplate.arg("Latest version"));
-	result += replaceIfNotEmpty(CModEntry::sizeToString(mod.getValue("size").toDouble()), lineTemplate.arg("Download size"));
-	result += replaceIfNotEmpty(mod.getValue("author"), lineTemplate.arg("Authors"));
-	result += replaceIfNotEmpty(mod.getValue("contact"), urlTemplate.arg("Home"));
-	result += replaceIfNotEmpty(mod.getValue("depends"), lineTemplate.arg("Required mods"));
-	result += replaceIfNotEmpty(mod.getValue("conflicts"), lineTemplate.arg("Conflicting mods"));
-	result += replaceIfNotEmpty(mod.getValue("description"), textTemplate.arg("Description"));
+	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")));
+	if (mod.getValue("size").toDouble() != 0)
+		result += replaceIfNotEmpty(CModEntry::sizeToString(mod.getValue("size").toDouble()), lineTemplate.arg(tr("Download size")));
+	result += replaceIfNotEmpty(mod.getValue("author"), lineTemplate.arg(tr("Authors")));
+	result += replaceIfNotEmpty(mod.getValue("contact"), urlTemplate.arg(tr("Home")));
+	result += replaceIfNotEmpty(mod.getValue("depends"), lineTemplate.arg(tr("Required mods")));
+	result += replaceIfNotEmpty(mod.getValue("conflicts"), lineTemplate.arg(tr("Conflicting mods")));
+	result += replaceIfNotEmpty(mod.getValue("description"), textTemplate.arg(tr("Description")));
 
 	result += "<p></p>"; // to get some empty space
 
-	QString unknownDeps  = "This mod can not be installed or enabled because following dependencies are not present";
-	QString blockingMods = "This mod can not be enabled because following mods are incompatible with this mod";
-	QString hasActiveDependentMods = "This mod can not be disabled because it is required to run following mods";
-	QString hasDependentMods = "This mod can not be uninstalled or updated because it is required to run following mods";
+	QString unknownDeps  = tr("This mod can not be installed or enabled because following dependencies are not present");
+	QString blockingMods = tr("This mod can not be enabled because following mods are incompatible with this mod");
+	QString hasActiveDependentMods = tr("This mod can not be disabled because it is required to run following mods");
+	QString hasDependentMods = tr("This mod can not be uninstalled or updated because it is required to run following mods");
+	QString thisIsSubmod = tr("This is submod and it can not be installed or uninstalled separately from parent mod");
 
 	QString notes;
 
-	notes += replaceIfNotEmpty(findInvalidDependencies(mod.getName()), noteTemplate.arg(unknownDeps));
-	notes += replaceIfNotEmpty(findBlockingMods(mod.getName()), noteTemplate.arg(blockingMods));
+	notes += replaceIfNotEmpty(findInvalidDependencies(mod.getName()), listTemplate.arg(unknownDeps));
+	notes += replaceIfNotEmpty(findBlockingMods(mod.getName()), listTemplate.arg(blockingMods));
 	if (mod.isEnabled())
-		notes += replaceIfNotEmpty(findDependentMods(mod.getName(), true), noteTemplate.arg(hasActiveDependentMods));
+		notes += replaceIfNotEmpty(findDependentMods(mod.getName(), true), listTemplate.arg(hasActiveDependentMods));
 	if (mod.isInstalled())
-		notes += replaceIfNotEmpty(findDependentMods(mod.getName(), false), noteTemplate.arg(hasDependentMods));
+		notes += replaceIfNotEmpty(findDependentMods(mod.getName(), false), listTemplate.arg(hasDependentMods));
+
+	if (mod.getName().contains('.'))
+		notes += noteTemplate.arg(thisIsSubmod);
 
 	if (notes.size())
-		result += textTemplate.arg("Notes").arg(notes);
+		result += textTemplate.arg(tr("Notes")).arg(notes);
 
 	result += "</body></html>";
 	return result;
@@ -183,28 +197,31 @@ void CModListView::disableModInfo()
 	ui->hideModInfoButton->setEnabled(false);
 }
 
-void CModListView::selectMod(int index)
+void CModListView::dataChanged(const QModelIndex & topleft, const QModelIndex & bottomRight)
 {
-	if (index < 0)
+	selectMod(ui->allModsView->currentIndex());
+}
+
+void CModListView::selectMod(const QModelIndex & index)
+{
+	if (!index.isValid())
 	{
 		disableModInfo();
 	}
 	else
 	{
-		enableModInfo();
-
-		auto mod = modModel->getMod(modModel->modIndexToName(index));
+		auto mod = modModel->getMod(index.data(ModRoles::ModNameRole).toString());
 
 		ui->textBrowser->setHtml(genModInfoText(mod));
 
-		bool hasInvalidDeps = !findInvalidDependencies(modModel->modIndexToName(index)).empty();
-		bool hasBlockingMods = !findBlockingMods(modModel->modIndexToName(index)).empty();
-		bool hasDependentMods = !findDependentMods(modModel->modIndexToName(index), true).empty();
+		bool hasInvalidDeps = !findInvalidDependencies(index.data(ModRoles::ModNameRole).toString()).empty();
+		bool hasBlockingMods = !findBlockingMods(index.data(ModRoles::ModNameRole).toString()).empty();
+		bool hasDependentMods = !findDependentMods(index.data(ModRoles::ModNameRole).toString(), true).empty();
 
 		ui->disableButton->setVisible(mod.isEnabled());
 		ui->enableButton->setVisible(mod.isDisabled());
-		ui->installButton->setVisible(mod.isAvailable());
-		ui->uninstallButton->setVisible(mod.isInstalled());
+		ui->installButton->setVisible(mod.isAvailable() && !mod.getName().contains('.'));
+		ui->uninstallButton->setVisible(mod.isInstalled() && !mod.getName().contains('.'));
 		ui->updateButton->setVisible(mod.isUpdateable());
 
 		// Block buttons if action is not allowed at this time
@@ -232,7 +249,7 @@ void CModListView::keyPressEvent(QKeyEvent * event)
 
 void CModListView::modSelected(const QModelIndex & current, const QModelIndex & )
 {
-	selectMod(filterModel->mapToSource(current).row());
+	selectMod(current);
 }
 
 void CModListView::on_hideModInfoButton_clicked()
@@ -243,10 +260,10 @@ void CModListView::on_hideModInfoButton_clicked()
 		showModInfo();
 }
 
-void CModListView::on_allModsView_doubleClicked(const QModelIndex &index)
+void CModListView::on_allModsView_activated(const QModelIndex &index)
 {
 	showModInfo();
-	selectMod(filterModel->mapToSource(index).row());
+	selectMod(index);
 }
 
 void CModListView::on_lineEdit_textChanged(const QString &arg1)
@@ -319,7 +336,7 @@ QStringList CModListView::findDependentMods(QString mod, bool excludeDisabled)
 
 void CModListView::on_enableButton_clicked()
 {
-	QString modName = modModel->modIndexToName(filterModel->mapToSource(ui->allModsView->currentIndex()).row());
+	QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString();
 
 	assert(findBlockingMods(modName).empty());
 	assert(findInvalidDependencies(modName).empty());
@@ -332,7 +349,7 @@ void CModListView::on_enableButton_clicked()
 
 void CModListView::on_disableButton_clicked()
 {
-	QString modName = modModel->modIndexToName(filterModel->mapToSource(ui->allModsView->currentIndex()).row());
+	QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString();
 
 	if (modModel->hasMod(modName) &&
 	    modModel->getMod(modName).isEnabled())
@@ -343,7 +360,7 @@ void CModListView::on_disableButton_clicked()
 
 void CModListView::on_updateButton_clicked()
 {
-	QString modName = modModel->modIndexToName(filterModel->mapToSource(ui->allModsView->currentIndex()).row());
+	QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString();
 
 	assert(findInvalidDependencies(modName).empty());
 
@@ -358,7 +375,7 @@ void CModListView::on_updateButton_clicked()
 
 void CModListView::on_uninstallButton_clicked()
 {
-	QString modName = modModel->modIndexToName(filterModel->mapToSource(ui->allModsView->currentIndex()).row());
+	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) &&
@@ -373,7 +390,7 @@ void CModListView::on_uninstallButton_clicked()
 
 void CModListView::on_installButton_clicked()
 {
-	QString modName = modModel->modIndexToName(filterModel->mapToSource(ui->allModsView->currentIndex()).row());
+	QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString();
 
 	assert(findInvalidDependencies(modName).empty());
 
@@ -537,8 +554,8 @@ void CModListView::on_pushButton_clicked()
 
 void CModListView::modelReset()
 {
-	//selectMod(filterModel->mapToSource(ui->allModsView->currentIndex()).row());
-	selectMod(filterModel->rowCount() > 0 ? 0 : -1);
+	if (ui->modInfoWidget->isVisible())
+		selectMod(filterModel->rowCount() > 0 ? filterModel->index(0,0) : QModelIndex());
 }
 
 void CModListView::checkManagerErrors()

+ 4 - 3
launcher/modManager/cmodlistview_moc.h

@@ -61,9 +61,10 @@ public:
 	void enableModInfo();
 	void disableModInfo();
 
-	void selectMod(int index);
+	void selectMod(const QModelIndex & index);
 
 private slots:
+	void dataChanged(const QModelIndex & topleft, const QModelIndex & bottomRight);
 	void modSelected(const QModelIndex & current, const QModelIndex & previous);
 	void downloadProgress(qint64 current, qint64 max);
 	void downloadFinished(QStringList savedFiles, QStringList failedFiles, QStringList errors);
@@ -72,8 +73,6 @@ private slots:
 
 	void on_hideModInfoButton_clicked();
 
-	void on_allModsView_doubleClicked(const QModelIndex &index);
-
 	void on_lineEdit_textChanged(const QString &arg1);
 
 	void on_comboBox_currentIndexChanged(int index);
@@ -90,6 +89,8 @@ private slots:
 
 	void on_pushButton_clicked();
 
+	void on_allModsView_activated(const QModelIndex &index);
+
 private:
 	Ui::CModListView *ui;
 };

+ 6 - 9
launcher/modManager/cmodlistview_moc.ui

@@ -109,7 +109,7 @@
       </widget>
      </item>
      <item row="1" column="0" colspan="2">
-      <widget class="QTableView" name="allModsView">
+      <widget class="QTreeView" name="allModsView">
        <property name="selectionMode">
         <enum>QAbstractItemView::SingleSelection</enum>
        </property>
@@ -119,7 +119,7 @@
        <property name="iconSize">
         <size>
          <width>32</width>
-         <height>20</height>
+         <height>32</height>
         </size>
        </property>
        <property name="verticalScrollMode">
@@ -131,12 +131,9 @@
        <property name="sortingEnabled">
         <bool>true</bool>
        </property>
-       <attribute name="horizontalHeaderStretchLastSection">
-        <bool>true</bool>
-       </attribute>
-       <attribute name="verticalHeaderVisible">
+       <property name="expandsOnDoubleClick">
         <bool>false</bool>
-       </attribute>
+       </property>
       </widget>
      </item>
     </layout>
@@ -220,8 +217,8 @@
          <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
 &lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
 p, li { white-space: pre-wrap; }
-&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
-&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:11pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
         </property>
         <property name="openExternalLinks">
          <bool>true</bool>

+ 31 - 13
launcher/modManager/cmodmanager.cpp

@@ -66,7 +66,7 @@ void CModManager::loadMods()
 
 	for (auto modname : installedMods)
 	{
-		ResourceID resID("Mods/" + modname + "/mod.json");
+		ResourceID resID(CModInfo::getModFile(modname));
 		if (CResourceHandler::get()->existsResource(resID))
 		{
 			std::string name = *CResourceHandler::get()->getResourceName(resID);
@@ -114,6 +114,9 @@ bool CModManager::canInstallMod(QString modname)
 {
 	auto mod = modList->getMod(modname);
 
+	if (mod.getName().contains('.'))
+		return addError(modname, "Can not install submod");
+
 	if (mod.isInstalled())
 		return addError(modname, "Mod is already installed");
 
@@ -126,6 +129,9 @@ bool CModManager::canUninstallMod(QString modname)
 {
 	auto mod = modList->getMod(modname);
 
+	if (mod.getName().contains('.'))
+		return addError(modname, "Can not uninstall submod");
+
 	if (!mod.isInstalled())
 		return addError(modname, "Mod is not installed");
 
@@ -191,17 +197,32 @@ bool CModManager::canDisableMod(QString modname)
 	return true;
 }
 
-bool CModManager::doEnableMod(QString mod, bool on)
+static QVariant writeValue(QString path, QVariantMap input, QVariant value)
 {
-	QVariant value(on);
-	QVariantMap list = modSettings["activeMods"].toMap();
-	QVariantMap modData = list[mod].toMap();
+	if (path.size() > 1)
+	{
 
-	modData.insert("active", value);
-	list.insert(mod, modData);
-	modSettings.insert("activeMods", list);
+		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;
+	}
+}
+
+bool CModManager::doEnableMod(QString mod, bool on)
+{
+	QString path = mod;
+	path = "/activeMods/" + path.replace(".", "/mods/") + "/active";
+
+	modSettings = writeValue(path, modSettings, QVariant(on)).toMap();
 	modList->setModSettings(modSettings["activeMods"]);
+	modList->modChanged(mod);
 
 	JsonUtils::JsonToFile(settingsPath(), modSettings);
 
@@ -215,11 +236,6 @@ bool CModManager::doInstallMod(QString modname, QString archivePath)
 	if (!QFile(archivePath).exists())
 		return addError(modname, "Mod archive is missing");
 
-	// FIXME: recheck wog/vcmi data behavior - they have bits of data in our trunk
-	// FIXME: breaks when there is Era mod with same name
-	//if (QDir(destDir + modname).exists())
-	//	return addError(modname, "Mod with such name is already installed");
-
 	if (localMods.contains(modname))
 		return addError(modname, "Mod with such name is already installed");
 
@@ -237,6 +253,7 @@ bool CModManager::doInstallMod(QString modname, QString archivePath)
 
 	localMods.insert(modname, json);
 	modList->setLocalModList(localMods);
+	modList->modChanged(modname);
 
 	return true;
 }
@@ -258,6 +275,7 @@ bool CModManager::doUninstallMod(QString modname)
 
 	localMods.remove(modname);
 	modList->setLocalModList(localMods);
+	modList->modChanged(modname);
 
 	return true;
 }

+ 5 - 6
lib/CModHandler.cpp

@@ -668,7 +668,7 @@ std::vector<std::string> CModHandler::getModList(std::string path)
 	return foundMods;
 }
 
-void CModHandler::loadMods(std::string path, std::string parent, const JsonNode & modSettings)
+void CModHandler::loadMods(std::string path, std::string parent, const JsonNode & modSettings, bool enableMods)
 {
 	for (std::string modName : getModList(path))
 	{
@@ -682,11 +682,10 @@ void CModHandler::loadMods(std::string path, std::string parent, const JsonNode
 				mod.dependencies.insert(parent);
 
 			allMods[modFullName] = mod;
-			if (mod.enabled)
-			{
+			if (mod.enabled && enableMods)
 				activeMods.push_back(modFullName);
-				loadMods(CModInfo::getModDir(modFullName) + '/', modFullName, modSettings[modName]["mods"]);
-			}
+
+			loadMods(CModInfo::getModDir(modFullName) + '/', modFullName, modSettings[modName]["mods"], enableMods && mod.enabled);
 		}
 	}
 }
@@ -695,7 +694,7 @@ void CModHandler::loadMods()
 {
 	const JsonNode modConfig = loadModSettings("config/modSettings.json");
 
-	loadMods("", "", modConfig["activeMods"]);
+	loadMods("", "", modConfig["activeMods"], true);
 
 	coreMod = CModInfo("core", modConfig["core"], JsonNode(ResourceID("config/gameConfig.json")));
 	coreMod.name = "Original game files";

+ 1 - 1
lib/CModHandler.h

@@ -214,7 +214,7 @@ class DLL_LINKAGE CModHandler
 	std::vector <TModID> resolveDependencies(std::vector<TModID> input) const;
 
 	std::vector<std::string> getModList(std::string path);
-	void loadMods(std::string path, std::string namePrefix, const JsonNode & modSettings);
+	void loadMods(std::string path, std::string namePrefix, const JsonNode & modSettings, bool enableMods);
 public:
 
 	CIdentifierStorage identifiers;