[education/rkward] /: Group pluginmaps by id

Thomas Friedrichsmeier null at kde.org
Mon May 9 17:11:10 BST 2022


Git commit b8be9a195c4cd5565b1f78003ebcc75c046c276f by Thomas Friedrichsmeier.
Committed on 09/05/2022 at 16:08.
Pushed by tfry into branch 'master'.

Group pluginmaps by id

M  +1    -0    ChangeLog
M  +5    -5    rkward/misc/multistringselector.cpp
M  +203  -182  rkward/settings/rksettingsmoduleplugins.cpp
M  +31   -18   rkward/settings/rksettingsmoduleplugins.h

https://invent.kde.org/education/rkward/commit/b8be9a195c4cd5565b1f78003ebcc75c046c276f

diff --git a/ChangeLog b/ChangeLog
index 12f3edba..76042f08 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -3,6 +3,7 @@ TODOs:
 	- Fix RKWard-app icon on Windows
 	- If stored config is older than 0.6.3, discard it, entirely
 
+- Plugin maps with the same id are grouped together, and the most recent version is used, automatically
 - Added functionality to install add-on packages directly from git (formerly available as external plugin rk.gitInstall)
    - TODO: add a plugintest
 - Fixed: Disabling a <row> element in plugins would not make the sub-elements non-required
diff --git a/rkward/misc/multistringselector.cpp b/rkward/misc/multistringselector.cpp
index 63324b9a..f2c37ea2 100644
--- a/rkward/misc/multistringselector.cpp
+++ b/rkward/misc/multistringselector.cpp
@@ -138,11 +138,11 @@ void RKMultiStringSelectorV2::setModel (QAbstractItemModel* model, int main_colu
 	}
 	tree_view->setModel (model);
 	connect (tree_view->selectionModel (), &QItemSelectionModel::currentChanged, this, &RKMultiStringSelectorV2::updateButtons);
-	connect (model, &QStringListModel::dataChanged, this, &RKMultiStringSelectorV2::anyModelDataChange);
-	connect (model, &QStringListModel::layoutChanged, this, &RKMultiStringSelectorV2::anyModelDataChange);
-	connect (model, &QStringListModel::rowsInserted, this, &RKMultiStringSelectorV2::anyModelDataChange);
-	connect (model, &QStringListModel::rowsRemoved, this, &RKMultiStringSelectorV2::anyModelDataChange);
-	connect (model, &QStringListModel::modelReset, this, &RKMultiStringSelectorV2::anyModelDataChange);
+	connect (model, &QAbstractItemModel::dataChanged, this, &RKMultiStringSelectorV2::anyModelDataChange);
+	connect (model, &QAbstractItemModel::layoutChanged, this, &RKMultiStringSelectorV2::anyModelDataChange);
+	connect (model, &QAbstractItemModel::rowsInserted, this, &RKMultiStringSelectorV2::anyModelDataChange);
+	connect (model, &QAbstractItemModel::rowsRemoved, this, &RKMultiStringSelectorV2::anyModelDataChange);
+	connect (model, &QAbstractItemModel::modelReset, this, &RKMultiStringSelectorV2::anyModelDataChange);
 
 	if (main_column >= 0) tree_view->resizeColumnToContents (main_column);
 	
diff --git a/rkward/settings/rksettingsmoduleplugins.cpp b/rkward/settings/rksettingsmoduleplugins.cpp
index 2f571b91..d6b2aee1 100644
--- a/rkward/settings/rksettingsmoduleplugins.cpp
+++ b/rkward/settings/rksettingsmoduleplugins.cpp
@@ -35,7 +35,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include "../debug.h"
 
 // static members
-RKSettingsModulePlugins::UniquePluginMapList RKSettingsModulePlugins::known_plugin_maps;
+RKSettingsModulePlugins::RKPluginMapList RKSettingsModulePlugins::known_plugin_maps;
 RKConfigValue<RKSettingsModulePlugins::PluginPrefs, int> RKSettingsModulePlugins::interface_pref {"Interface Preferences", RKSettingsModulePlugins::PreferRecommended};
 RKConfigValue<bool> RKSettingsModulePlugins::show_code {"Code display default", false};
 RKConfigValue<int> RKSettingsModulePlugins::code_size {"Code display size", 250};
@@ -104,12 +104,12 @@ void RKSettingsModulePlugins::applyChanges () {
 	interface_pref = static_cast<PluginPrefs> (button_group->checkedId ());
 }
 
-RKSettingsModulePlugins::UniquePluginMapList RKSettingsModulePlugins::setPluginMaps(const UniquePluginMapList &new_list) {
+RKSettingsModulePlugins::RKPluginMapList RKSettingsModulePlugins::setPluginMaps(const RKPluginMapList &new_list) {
 	RK_TRACE (SETTINGS);
 
 	known_plugin_maps = new_list;
-	fixPluginMapLists ();
-	RKWardMainWindow::getMain ()->initPlugins();
+	known_plugin_maps.removeObsoleteMaps();
+	RKWardMainWindow::getMain()->initPlugins();
 	return known_plugin_maps;
 }
 
@@ -119,28 +119,68 @@ void RKSettingsModulePlugins::configurePluginmaps () {
 	RKLoadLibsDialog::showPluginmapConfig (this, commandChain ());
 }
 
-void savePluginMaps(KConfigGroup &cg, const RKSettingsModulePlugins::UniquePluginMapList &known_plugin_maps) {
+void RKSettingsModulePlugins::RKPluginMapList::saveToConfig(KConfigGroup& cg) {
 	RK_TRACE(SETTINGS);
 
 	cg.deleteGroup("Known Plugin maps");	// always start from scratch to remove cruft from pluginmaps
 	KConfigGroup pmg = cg.group("Known Plugin maps");
-	QStringList all_known_maps;
-	for (int i = 0; i < known_plugin_maps.size(); ++i) {
-		auto &variants = known_plugin_maps[i].maps;
-		for (int j = 0; j < variants.size(); ++j) {
-			const RKSettingsModulePlugins::PluginMapStoredInfo &inf = variants[j];
+	QStringList kplugin_maps;
+	for (auto it = all_maps.constBegin(); it != all_maps.constEnd(); ++it) {
+		const auto &maps = it.value().list;
+		for (int j = 0; j < maps.size(); ++j) {
+			const RKSettingsModulePlugins::PluginMapStoredInfo &inf = maps[j];
 			KConfigGroup ppmg = pmg.group (inf.filename);
-			ppmg.writeEntry("Active", known_plugin_maps[i].active);
+			kplugin_maps.append(inf.filename);
+			ppmg.writeEntry("Active", it.value().active);
 			ppmg.writeEntry("State", (int) inf.state);
 			ppmg.writeEntry("timestamp", inf.last_modified);
 			ppmg.writeEntry("id", inf.id);
 			ppmg.writeEntry("version", inf.version.toString());
 			ppmg.writeEntry("priority", inf.priority);
-			all_known_maps.append(inf.filename);
 		}
 	}
-	// NOTE: The group list is always sorted alphabetically, which is why we need a separate list setting for saving info on order.
-	cg.writeEntry("All known plugin maps", all_known_maps);
+	// NOTE: The group list is always sorted alphabetically, which is why we need to save a separate list setting for saving info on order.
+	cg.writeEntry("Pluginmap id order", ordered_ids);
+	// I'd like to store this implicitly as cg.groupList(), alas that doesn't seem to work (KF5 5.68.0)
+	cg.readEntry("All known plugin maps", kplugin_maps);
+}
+
+void RKSettingsModulePlugins::RKPluginMapList::readFromConfig(KConfigGroup& cg) {
+	RK_TRACE(SETTINGS);
+	ordered_ids.clear();
+	all_maps.clear();
+
+	/* Known maps are stored at runtime as a nested list id -> variants, but stored in config as a plain list of variants. This is for historic reasons, but may be too much trouble to change. */
+	KConfigGroup pmg = cg.group ("Known Plugin maps");
+	QStringList kplugin_maps = cg.readEntry("All known plugin maps", QStringList());
+	for (int i = 0; i < kplugin_maps.size (); ++i) {
+		KConfigGroup ppmg = pmg.group (kplugin_maps[i]);	// unadjusted path on purpose!
+		PluginMapStoredInfo inf (RKSettingsModuleGeneral::checkAdjustLoadedPath (kplugin_maps[i]));
+		// Pluginmaps which are broken with one version of RKWard may be alright with other versions. So reset flags, if version has changed.
+		if (RKSettingsModuleGeneral::rkwardVersionChanged()) inf.state = Working;
+		else inf.state = (PluginMapState) ppmg.readEntry("State", (int) Working);
+		inf.last_modified = ppmg.readEntry ("timestamp", QDateTime ());
+		inf.id = ppmg.readEntry ("id");
+		inf.version = ppmg.readEntry ("version");
+		inf.priority = ppmg.readEntry ("priority", (int) PriorityMedium);
+		addMap(inf, ppmg.readEntry("Active", false) ? ForceActivate : DoNotActivate);
+	}
+	if (RKSettingsModuleGeneral::rkwardVersionChanged () || RKSettingsModuleGeneral::installationMoved ()) {
+		// if it is the first start this version or from a new path, scan the installation for new pluginmaps
+		// Note that in the case of installationMoved(), checkAdjustLoadedPath() has already kicked in, above, but rescanning is still useful
+		// e.g. if users have installed to a new location, because they had botched their previous installation
+		registerDefaultPluginMaps(AutoActivateIfNew);
+	}
+	removeObsoleteMaps();
+	QStringList nordered_ids = cg.readEntry("Pluginmap id order", ordered_ids);
+	// make sure order has all relevant ids, and only those, before applying
+	for (int i = nordered_ids.size() - 1; i >= 0; --i) {
+		if (!ordered_ids.contains(nordered_ids[i])) nordered_ids.removeAt(i);
+	}
+	for (int i = 0; i < ordered_ids.size(); ++i) {
+		if (!nordered_ids.contains(ordered_ids[i])) nordered_ids.append(ordered_ids[i]);
+	}
+	ordered_ids = nordered_ids;
 }
 
 void RKSettingsModulePlugins::syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction a) {
@@ -153,76 +193,133 @@ void RKSettingsModulePlugins::syncConfig(KConfig *config, RKConfigBase::ConfigSy
 	side_preview_width.syncConfig(cg, a);
 
 	if (a == RKConfigBase::SaveConfig) {
-		savePluginMaps(cg, known_plugin_maps);
+		known_plugin_maps.saveToConfig(cg);
 	} else {
-		/* Known maps are stored at runtime as a nested list id -> variants, but stored in config as a plain list of variants. This is for historic reasons, but may be too much trouble to change. */
-		KConfigGroup pmg = cg.group ("Known Plugin maps");
-		QStringList kplugin_maps = cg.readEntry ("All known plugin maps", QStringList ());
-		for (int i = 0; i < kplugin_maps.size (); ++i) {
-			KConfigGroup ppmg = pmg.group (kplugin_maps[i]);	// unadjusted path on purpose!
-			PluginMapStoredInfo inf (RKSettingsModuleGeneral::checkAdjustLoadedPath (kplugin_maps[i]));
-			// Pluginmaps which are broken with one version of RKWard may be alright with other versions. So reset flags, if version has changed.
-			if (RKSettingsModuleGeneral::rkwardVersionChanged()) inf.state = Working;
-			else inf.state = (PluginMapState) ppmg.readEntry("State", (int) Working);
-			inf.last_modified = ppmg.readEntry ("timestamp", QDateTime ());
-			inf.id = ppmg.readEntry ("id");
-			inf.version = ppmg.readEntry ("version");
-			inf.priority = ppmg.readEntry ("priority", (int) PriorityMedium);
-			addPluginMapToList(inf, ppmg.readEntry("Active", false));
-		}
-		if (RKSettingsModuleGeneral::rkwardVersionChanged () || RKSettingsModuleGeneral::installationMoved ()) {
-			// if it is the first start this version or from a new path, scan the installation for new pluginmaps
-			// Note that in the case of installationMoved(), checkAdjustLoadedPath() has already kicked in, above, but rescanning is still useful
-			// e.g. if users have installed to a new location, because they had botched their previous installation
-			registerDefaultPluginMaps(AutoActivateIfNew);
-		}
-		fixPluginMapLists ();	// removes any maps which don't exist any more
+		known_plugin_maps.readFromConfig(cg);
 	}
 }
 
-bool RKSettingsModulePlugins::addPluginMapToList(const RKSettingsModulePlugins::PluginMapStoredInfo& inf, bool active) {
-	RK_TRACE (SETTINGS);
+bool RKSettingsModulePlugins::PluginMapStoredInfo::betterThan(const RKSettingsModulePlugins::PluginMapStoredInfo& other) const {
+	if (version > other.version) return true;
+	if (version == other.version && last_modified > other.last_modified) return true;
+	return false;
+}
+
+bool RKSettingsModulePlugins::RKPluginMapList::addMap(const PluginMapStoredInfo &inf, AddMode add_mode) {
+	RK_TRACE(SETTINGS);
+
+	QFileInfo info(inf.filename);
+	if (!info.isReadable()) return false;
+	if (info.lastModified () != inf.last_modified || inf.version.isNull()) {
+		auto inf2 = parsePluginMapBasics(inf.filename);
+		inf2.last_modified = info.lastModified();
+		if (inf2.version.isNull()) inf2.version = RKParsedVersion("0.0.0.1");  // prevent infinite recursion
+		return addMap(inf2, add_mode);
+	}
+
+	bool activate = (add_mode == ForceActivate || (add_mode == AutoActivate && inf.priority >= PriorityMedium));
 	QString id = inf.id;
 	QString filename = inf.filename;
-	for (int i = 0; i < known_plugin_maps.size(); ++i) {
-		UniquePluginMap &unique = known_plugin_maps[i];
-		if (id == unique.id) {
-			PluginMapList &variants = unique.maps;
-			for (int j = 0; j < variants.size(); ++j) {
-				if (variants[j].filename == filename) {
-					variants[j] = inf;
-					return false;
-				}
-			}
-			variants.append(inf);
-			unique.active = unique.active || active;
-			return true;
+	bool known = false;
+	PluginMapInfoList &sub_list = all_maps[id].list;
+	activate |= all_maps[id].active;
+	for (int i = 0; i < sub_list.size(); ++i) {
+		if (sub_list[i].filename == filename) {
+			sub_list[i] = inf;
+			known = true;
+			break;
 		}
 	}
-	UniquePluginMap new_entry(id, active);
-	new_entry.maps.append(inf);
-	known_plugin_maps.append(new_entry);
-	return true;
+	if (!known) {
+		activate = activate || (add_mode == AutoActivateIfNew && inf.priority >= PriorityMedium);
+		if (sub_list.isEmpty()) ordered_ids.append(id);
+		sub_list.append(inf);
+		std::sort(sub_list.begin(), sub_list.end(), [](const PluginMapStoredInfo &a, const PluginMapStoredInfo &b) { return a.betterThan(b); });
+	}
+	if (activate) {
+		setIdActive(id, activate);
+	}
+
+	return !known;
 }
 
-RKSettingsModulePlugins::PluginMapStoredInfo RKSettingsModulePlugins::UniquePluginMap::bestMap() const {
+void RKSettingsModulePlugins::RKPluginMapList::removeObsoleteMaps() {
 	RK_TRACE(SETTINGS);
-	RK_ASSERT(!maps.isEmpty());
-
-	PluginMapStoredInfo candidate = maps.first();
-	for (int i = 1; i < maps.size(); ++i) {
-		const PluginMapStoredInfo &other = maps[i];
-		bool other_is_better = true;
-		if (candidate.version > other.version) {
-			other_is_better = false;
-		} else if (candidate.version == other.version && candidate.last_modified > other.last_modified) {
-			other_is_better = false;
+
+	QSet<QString> ids_to_remove;
+	for (auto it = all_maps.begin(); it != all_maps.end(); ++it) {
+		auto &sublist = it.value();
+		for (int i = 0; i < sublist.list.size(); ++i) {
+			QString filename = sublist.list[i].filename;
+			if (!QFile::exists(filename)) {
+				sublist.list.removeAt(i);
+				--i;
+				break;
+			}
 		}
-		if (!other_is_better) {
-			candidate = other;
+		if (sublist.list.isEmpty()) ids_to_remove.insert(it.key());
+	}
+	for (auto it = ids_to_remove.constBegin(); it != ids_to_remove.constEnd(); ++it) {
+		forgetId(*it);
+	}
+}
+
+bool RKSettingsModulePlugins::RKPluginMapList::setMapfileState(const QString &mapfile, PluginMapState state) {
+	RK_TRACE(SETTINGS);
+	for (auto it = all_maps.begin(); it != all_maps.end(); ++it) {
+		auto &sublist = it.value().list;
+		for (int i = 0; i < sublist.size(); ++i) {
+			if (mapfile == sublist[i].filename) {
+				bool ret = (sublist[i].state != state);
+				sublist[i].state = state;
+				if (state == Broken) it.value().active = false;
+				return ret;
+			}
 		}
 	}
-	return candidate;
+	return false;
+}
+
+void RKSettingsModulePlugins::RKPluginMapList::forgetId(const QString &id) {
+	RK_TRACE(SETTINGS);
+	all_maps.remove(id);
+	ordered_ids.removeAll(id);
+}
+
+void RKSettingsModulePlugins::RKPluginMapList::setIdActive(const QString &id, bool active) {
+	RK_TRACE(SETTINGS);
+	all_maps[id].active = active;
+	if (all_maps[id].list.isEmpty()) {
+		RK_ASSERT(false);
+		forgetId(id);
+	}
+}
+
+QString RKSettingsModulePlugins::RKPluginMapList::mapFileForId(const QString &id) const {
+	RK_TRACE(SETTINGS);
+	auto val = all_maps.value(id);
+	if (val.list.isEmpty()) return QString();
+	return val.list.first().filename;
+}
+
+QList<RKSettingsModulePlugins::PluginMapStoredInfo> RKSettingsModulePlugins::RKPluginMapList::allUniqueMaps() {
+	RK_TRACE(SETTINGS);
+	QList<RKSettingsModulePlugins::PluginMapStoredInfo> ret;
+	for (int i = 0; i < ordered_ids.size(); ++i) {
+		auto val = all_maps[ordered_ids[i]];
+		if (!val.list.isEmpty()) ret.append(val.list.first());
+	}
+	return ret;
+}
+
+QStringList RKSettingsModulePlugins::RKPluginMapList::activeMapFiles() {
+	RK_TRACE(SETTINGS);
+	QStringList ret;
+	for (int i = 0; i < ordered_ids.size(); ++i) {
+		auto val = all_maps[ordered_ids[i]];
+		if (val.active && !val.list.isEmpty()) ret.append(val.list.first().filename);
+	}
+	return ret;
 }
 
 void RKSettingsModulePlugins::registerDefaultPluginMaps(AddMode add_mode) {
@@ -240,22 +337,12 @@ void RKSettingsModulePlugins::registerDefaultPluginMaps(AddMode add_mode) {
 	registerPluginMaps (def_pluginmaps, add_mode, false, add_mode == AutoActivateIfNew);
 }
 
-int findKnownPluginMap (const QString& filename, const RKSettingsModulePlugins::PluginMapList& haystack) {
-	RK_TRACE (SETTINGS);
-
-	int i;
-	for (i = haystack.size () - 1; i >= 0; --i) {
-		if (haystack[i].filename == filename) return i;
-	}
-	return i;
-}
-
 QString RKSettingsModulePlugins::findPluginMapById (const QString &id) {
 	RK_TRACE (SETTINGS);
 
-	for (int i = 0; i < known_plugin_maps.size (); ++i) {
-		if (known_plugin_maps[i].id == id) return known_plugin_maps[i].bestMap().filename;
-	}
+	QString ret = known_plugin_maps.mapFileForId(id);
+	if (!ret.isNull()) return ret;
+
 	// for "rkward::" namespace, try a little harded:
 	if (id.startsWith (QLatin1String ("rkward::"))) {
 		QFileInfo info (RKCommonFunctions::getRKWardDataDir () + '/' + id.mid (8));
@@ -268,29 +355,13 @@ QString RKSettingsModulePlugins::findPluginMapById (const QString &id) {
 bool RKSettingsModulePlugins::markPluginMapState(const QString& map, PluginMapState state) {
 	RK_TRACE (SETTINGS);
 
-	for (int i = 0; i < known_plugin_maps.size(); ++i) {
-		auto &variants = known_plugin_maps[i].maps;
-		for (int j = 0; j < variants.size(); ++j) {
-			if (variants[j].filename == map) {
-				bool ret = variants[j].state != state;
-				variants[j].state = state;
-				if (state == Broken) known_plugin_maps[i].active = false;
-				return ret;
-			}
-		}
-	}
-	RK_ASSERT(false);
-	return false;
+	return known_plugin_maps.setMapfileState(map, state);
 }
 
 QStringList RKSettingsModulePlugins::pluginMaps () {
 	RK_TRACE (SETTINGS);
 
-	QStringList ret;
-	for (int i = 0; i < known_plugin_maps.size (); ++i) {
-		if (known_plugin_maps[i].active) ret.append (known_plugin_maps[i].bestMap().filename);
-	}
-	return ret;
+	return known_plugin_maps.activeMapFiles();
 }
 
 // static
@@ -300,64 +371,14 @@ void RKSettingsModulePlugins::registerPluginMaps (const QStringList &maps, AddMo
 	QStringList added;
 	foreach (const QString &map, maps) {
 		if (map.isEmpty ()) continue;
-		bool found = false;
-		for (int i = 0; i < known_plugin_maps.size(); ++i) {
-			const PluginMapList &variants = known_plugin_maps[i].maps;
-			for (int j = 0; j < variants.size(); ++j) {
-				const auto &inf = variants[j];
-				if (inf.filename == map) {
-					found = true;
-					if (!known_plugin_maps[i].active) {
-						if (add_mode == ForceActivate || (add_mode == AutoActivate && inf.priority >= PriorityMedium)) {
-							known_plugin_maps[i].active = true;
-							added.append(map);
-						}
-					}
-					break;
-				}
-			}
-			if (found) break;
-		}
-
-		if (!found) {
-			PluginMapStoredInfo inf = parsePluginMapBasics(map);
-			bool activate = (add_mode == ForceActivate) || (inf.priority >= PriorityMedium);
-			addPluginMapToList(map, activate);
-			if (activate) added.append(map);
+		if (known_plugin_maps.addMap(map, add_mode)) {
+			added.append(map);
 		}
 	}
 
 	if (suppress_reload) return;
 	if (force_reload || (!added.isEmpty ())) {
-		RKWardMainWindow::getMain ()->initPlugins(added);
-	}
-}
-
-void RKSettingsModulePlugins::fixPluginMapLists () {
-	RK_TRACE(SETTINGS);
-
-	for (int i = 0; i < known_plugin_maps.size(); ++i) {
-		PluginMapList &variants = known_plugin_maps[i].maps;
-		for (int j = 0; j < variants.size(); ++j) {
-			PluginMapStoredInfo &inf = variants[j];
-			QFileInfo info(inf.filename);
-			if (!info.isReadable()) {
-				variants.removeAt(j);
-				--j;
-				continue;
-			}
-
-			if (info.lastModified () != inf.last_modified || inf.version.isNull()) {
-				inf = parsePluginMapBasics(inf.filename);
-				inf.last_modified = info.lastModified();
-				// TOOD: Handle the case that it might have changed id? Should be remedied on the next config load, anyway, however.
-			}
-		}
-		if (variants.isEmpty()) {
-			known_plugin_maps.removeAt(i);
-			--i;
-			continue;
-		}
+		RKWardMainWindow::getMain ()->initPlugins(added); // NOTE: All maps get initialized, "added" is just for user notification of what got added
 	}
 }
 
@@ -401,7 +422,7 @@ RKSettingsModulePluginsModel::~RKSettingsModulePluginsModel() {
 	}
 }
 
-void RKSettingsModulePluginsModel::init (const RKSettingsModulePlugins::UniquePluginMapList& known_plugin_maps) {
+void RKSettingsModulePluginsModel::init (const RKSettingsModulePlugins::RKPluginMapList& known_plugin_maps) {
 	RK_TRACE (SETTINGS);
 	beginResetModel ();
 	plugin_maps = known_plugin_maps;
@@ -411,7 +432,7 @@ void RKSettingsModulePluginsModel::init (const RKSettingsModulePlugins::UniquePl
 int RKSettingsModulePluginsModel::rowCount (const QModelIndex& parent) const {
 	//RK_TRACE (SETTINGS);
 	if (parent.isValid ()) return 0;
-	return plugin_maps.size ();
+	return plugin_maps.ordered_ids.size();
 }
 
 #define COLUMN_CHECKED 0
@@ -431,8 +452,10 @@ QVariant RKSettingsModulePluginsModel::data (const QModelIndex& index, int role)
 	if (!index.isValid ()) return QVariant ();
 	int col = index.column ();
 
-	const auto &unique = plugin_maps[index.row()];
-	const auto &inf = unique.bestMap();  // TODO: efficiency
+	const auto id = plugin_maps.ordered_ids.value(index.row());
+	const auto &handle = plugin_maps.all_maps.value(id);
+	if (handle.list.isEmpty()) return QVariant();
+	const auto &inf = handle.list.first();
 
 	if (role == Qt::BackgroundRole) {
 		if (inf.state == RKSettingsModulePlugins::Broken) return QColor(Qt::red);
@@ -454,7 +477,7 @@ QVariant RKSettingsModulePluginsModel::data (const QModelIndex& index, int role)
 	if (col == COLUMN_CHECKED) {
 		if (role == Qt::CheckStateRole) {
 			if (inf.priority < RKSettingsModulePlugins::PriorityLow) return QVariant ();
-			return (unique.active ? Qt::Checked : Qt::Unchecked);
+			return (handle.active ? Qt::Checked : Qt::Unchecked);
 		}
 	} else if (col == COLUMN_ID) {
 		if (role == Qt::DisplayRole) {
@@ -485,7 +508,10 @@ Qt::ItemFlags RKSettingsModulePluginsModel::flags (const QModelIndex& index) con
 	// RK_TRACE (SETTINGS);
 	Qt::ItemFlags flags = QAbstractTableModel::flags (index);
 	if (index.isValid () && (index.column () == COLUMN_CHECKED)) {
-		if (plugin_maps[index.row()].bestMap().priority > RKSettingsModulePlugins::PriorityHidden) flags |= Qt::ItemIsUserCheckable | Qt::ItemIsEditable;
+		const auto id = plugin_maps.ordered_ids.value(index.row());
+		const auto &handle = plugin_maps.all_maps.value(id);
+		if (handle.list.isEmpty()) return flags;
+		if (handle.list.first().priority > RKSettingsModulePlugins::PriorityHidden) flags |= Qt::ItemIsUserCheckable | Qt::ItemIsEditable;
 	}
 	return flags;
 }
@@ -507,7 +533,10 @@ bool RKSettingsModulePluginsModel::setData (const QModelIndex& index, const QVar
 
 	if (role == Qt::CheckStateRole) {
 		if (index.isValid () && (index.column () == COLUMN_CHECKED)) {
-			plugin_maps[index.row ()].active = value.toBool ();
+			const auto id = plugin_maps.ordered_ids.value(index.row());
+			if (!plugin_maps.all_maps.contains(id)) return false;
+			auto &handle = plugin_maps.all_maps[id];
+			handle.active = value.toBool ();
 			emit dataChanged(index, index);
 			return true;
 		}
@@ -521,24 +550,16 @@ void RKSettingsModulePluginsModel::insertNewStrings (int above_row) {
 
 	QStringList files = QFileDialog::getOpenFileNames (static_cast<QWidget*> (QObject::parent ()), i18n ("Select .pluginmap-file"), RKCommonFunctions::getRKWardDataDir (), "RKWard pluginmap files [*.pluginmap](*.pluginmap)");
 
-	// already known files are activated, but not added
-	for (int i = files.size () -1; i >= 0; --i) {
-		int pos = findKnownPluginMap (files[i], plugin_maps);
-		if (pos >= 0) {
-			if (!plugin_maps[pos].active) {
-				plugin_maps[pos].active = true;
-				emit dataChanged(index(pos, 0), index(pos, COLUMN_COUNT - 1));
-			}
-			files.removeAt (i);
-		}
-	}
-
-	beginInsertRows (QModelIndex (), above_row, files.size ());
-	for (int i = files.size () - 1; i >= 0; --i) {
+	beginResetModel();
+	// not bothering with proper rowsInserted signals, this does not need to be efficient, anyway.
+	for (int i = files.size() -1; i >= 0; --i) {
 		auto inf = RKSettingsModulePlugins::parsePluginMapBasics(files[i]);
-		RKSettingsModulePlugins::UniquePluginMap unique(files[i], true);
-		unique.maps.append(inf);
-		plugin_maps.insert(above_row, unique);
+		plugin_maps.addMap(inf, RKSettingsModulePlugins::ForceActivate);
+		int index = plugin_maps.ordered_ids.indexOf(inf.id);
+		if (index < 0 || index > above_row) {
+			plugin_maps.ordered_ids.removeAll(inf.id);
+			plugin_maps.ordered_ids.insert(above_row, inf.id);
+		}
 	}
 	endInsertRows ();
 }
@@ -547,9 +568,9 @@ void RKSettingsModulePluginsModel::swapRows (int rowa, int rowb) {
 	RK_TRACE (SETTINGS);
 
 	RK_ASSERT ((rowa >= 0) && (rowa < rowCount ()) && (rowb >= 0) && (rowb < rowCount ()));
-	RKSettingsModulePlugins::PluginMapStoredInfo inf = plugin_maps[rowa];
-	plugin_maps[rowa] = plugin_maps[rowb];
-	plugin_maps[rowb] = inf;
+	QString id = plugin_maps.ordered_ids[rowa];
+	plugin_maps.ordered_ids[rowa] = plugin_maps.ordered_ids[rowb];
+	plugin_maps.ordered_ids[rowb] = id;
 }
 
 bool RKSettingsModulePluginsModel::removeRows (int row, int count, const QModelIndex& parent) {
@@ -558,7 +579,7 @@ bool RKSettingsModulePluginsModel::removeRows (int row, int count, const QModelI
 
 	if ((row < 0) || (count < 1) || (row + count > rowCount ())) return false;
 	for (int i = row + count - 1; i >= row; --i) {
-		plugin_maps.removeAt (i);
+		plugin_maps.forgetId(plugin_maps.ordered_ids.value(i));
 	}
 	return true;
 }
diff --git a/rkward/settings/rksettingsmoduleplugins.h b/rkward/settings/rksettingsmoduleplugins.h
index 7af848fd..acc06ae8 100644
--- a/rkward/settings/rksettingsmoduleplugins.h
+++ b/rkward/settings/rksettingsmoduleplugins.h
@@ -49,6 +49,7 @@ public:
 	static int defaultSidePreviewWidth () { return side_preview_width; };
 	static void setDefaultSidePreviewWidth (int new_width) { side_preview_width = new_width; }
 	enum AddMode {
+		DoNotActivate,
 		ForceActivate,
 		AutoActivate,
 		AutoActivateIfNew
@@ -77,17 +78,31 @@ public:
 		RKParsedVersion version;
 		QString id;
 		QDateTime last_modified;
+		bool betterThan(const PluginMapStoredInfo &other) const;
 	};
-	typedef QList<PluginMapStoredInfo> PluginMapList;
-	struct UniquePluginMap {
-		UniquePluginMap(const QString &id, bool active) : id(id), active(active) {};
-		QString id;
-		bool active;
-		PluginMapList maps;
-		PluginMapStoredInfo bestMap() const;
+	typedef QList<PluginMapStoredInfo> PluginMapInfoList;
+	class RKPluginMapList {
+	public:
+		/** @returns true if the _effective_ list changed (false, if file already known, or another file by the same id) */
+		bool addMap(const PluginMapStoredInfo &inf, AddMode add_mode);
+		void removeObsoleteMaps();
+		bool setMapfileState(const QString &mapfile, PluginMapState state);
+		void forgetId(const QString &id);
+		void setIdActive(const QString &id, bool active);
+		QString mapFileForId(const QString &id) const;
+		PluginMapInfoList allUniqueMaps();
+		QStringList activeMapFiles();
+		void saveToConfig(KConfigGroup &cg);
+		void readFromConfig(KConfigGroup &cg);
+	private:
+friend class RKSettingsModulePluginsModel;
+		struct DummyListStruct {
+			bool active;
+			PluginMapInfoList list;
+		};
+		QHash<QString, DummyListStruct> all_maps;
+		QStringList ordered_ids;
 	};
-	typedef QList<UniquePluginMap> UniquePluginMapList;
-	static UniquePluginMapList knownPluginmaps () { return known_plugin_maps; };
 	/** Registers the plugin maps that are shipped with RKWard.
 	 * @param force_add All default maps are also activated, even if they were already known, and disabled by the user. */
 	static void registerDefaultPluginMaps (AddMode add_mode);
@@ -98,11 +113,10 @@ private:
 	QButtonGroup *button_group;
 
 	/** plugin maps which are not necessarily active, but have been encountered, before. @see plugin_maps */
-	static UniquePluginMapList known_plugin_maps;
+	static RKPluginMapList known_plugin_maps;
 friend class RKSettingsModulePluginsModel;
-	static PluginMapStoredInfo parsePluginMapBasics (const QString &filename);
-	/* @returns true, if map was new, false if already known */
-	static bool addPluginMapToList(const PluginMapStoredInfo &inf, bool active);
+	static RKPluginMapList knownPluginmaps() { return known_plugin_maps; };
+	static PluginMapStoredInfo parsePluginMapBasics(const QString &filename);
 
 	static RKConfigValue<PluginPrefs,int> interface_pref;
 	static RKConfigValue<bool> show_code;
@@ -111,10 +125,9 @@ friend class RKSettingsModulePluginsModel;
 
 /* TODO: This one is currently unused (leftover of GHNS-based plugin installation), but might still be of interest */
 	static QStringList findPluginMapsRecursive (const QString &basedir);
-	static void fixPluginMapLists ();
 friend class RKPluginMapSelectionWidget;
 /** Sets the new list of plugins. Potentially removes unreadable ones, and returns the effective list. */
-	static UniquePluginMapList setPluginMaps (const UniquePluginMapList &new_list);
+	static RKPluginMapList setPluginMaps (const RKPluginMapList &new_list);
 };
 
 class RKSettingsModulePluginsModel : public QAbstractTableModel {
@@ -123,13 +136,13 @@ public:
 	explicit RKSettingsModulePluginsModel (QObject* parent);
 	virtual ~RKSettingsModulePluginsModel ();
 /** (re-)initialize the model */
-	void init (const RKSettingsModulePlugins::UniquePluginMapList &known_plugin_maps);
-	RKSettingsModulePlugins::UniquePluginMapList pluginMaps () { return plugin_maps; };
+	void init (const RKSettingsModulePlugins::RKPluginMapList &known_plugin_maps);
+	RKSettingsModulePlugins::RKPluginMapList pluginMaps () { return plugin_maps; };
 public slots:
 	void swapRows (int rowa, int rowb);
 	void insertNewStrings (int above_row);
 private:
-	RKSettingsModulePlugins::UniquePluginMapList plugin_maps;
+	RKSettingsModulePlugins::RKPluginMapList plugin_maps;
 	struct PluginMapMetaInfo {
 		RKComponentAboutData *about;
 		QList<RKComponentDependency> dependencies;


More information about the rkward-tracker mailing list