23 changed files with 856 additions and 59 deletions
			
			
		@ -0,0 +1,40 @@ | 
				
			|||||
 | 
					TARGET = QtMvvmSettingsCore | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					QT = core gui mvvmcore mvvmcore-private | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					HEADERS += qtmvvmsettingscore_global.h \ | 
				
			||||
 | 
						settingsviewmodel.h \ | 
				
			||||
 | 
						settingssetup.h \ | 
				
			||||
 | 
						settingssetuploader_p.h \ | 
				
			||||
 | 
					    settingsviewmodel_p.h | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					SOURCES += \ | 
				
			||||
 | 
						settingsviewmodel.cpp \ | 
				
			||||
 | 
						settingssetuploader.cpp \ | 
				
			||||
 | 
					    qtmvvmsettingscore_global.cpp | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					TRANSLATIONS += \ | 
				
			||||
 | 
						translations/qtmvvsettingsmcore_de.ts \ | 
				
			||||
 | 
						translations/qtmvvsettingsmcore_template.ts | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					DISTFILES += $$TRANSLATIONS | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					qpmx_ts_target.path = $$[QT_INSTALL_TRANSLATIONS] | 
				
			||||
 | 
					qpmx_ts_target.depends += lrelease | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					load(qt_module) | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					win32 { | 
				
			||||
 | 
						QMAKE_TARGET_PRODUCT = "$$TARGET" | 
				
			||||
 | 
						QMAKE_TARGET_COMPANY = "Skycoder42" | 
				
			||||
 | 
						QMAKE_TARGET_COPYRIGHT = "Felix Barz" | 
				
			||||
 | 
					} else:mac { | 
				
			||||
 | 
						QMAKE_TARGET_BUNDLE_PREFIX = "com.skycoder42." | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					!ReleaseBuild:!DebugBuild:!system(qpmx -d $$shell_quote($$_PRO_FILE_PWD_) --qmake-run init $$QPMX_EXTRA_OPTIONS $$shell_quote($$QMAKE_QMAKE) $$shell_quote($$OUT_PWD)): error(qpmx initialization failed. Check the compilation log for details.) | 
				
			||||
 | 
					else: include($$OUT_PWD/qpmx_generated.pri) | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					qpmx_ts_target.files -= $$OUT_PWD/$$QPMX_WORKINGDIR/qtmvvsettingsmcore_template.qm | 
				
			||||
 | 
					qpmx_ts_target.files += translations/qtmvvsettingsmcore_template.ts | 
				
			||||
 | 
					
 | 
				
			||||
@ -0,0 +1,14 @@ | 
				
			|||||
 | 
					{ | 
				
			||||
 | 
					    "dependencies": [], | 
				
			||||
 | 
					    "license": { | 
				
			||||
 | 
					        "file": "", | 
				
			||||
 | 
					        "name": "" | 
				
			||||
 | 
					    }, | 
				
			||||
 | 
					    "prcFile": "", | 
				
			||||
 | 
					    "priFile": "", | 
				
			||||
 | 
					    "priIncludes": [ | 
				
			||||
 | 
					    ], | 
				
			||||
 | 
					    "publishers": { | 
				
			||||
 | 
					    }, | 
				
			||||
 | 
					    "source": false | 
				
			||||
 | 
					} | 
				
			||||
@ -0,0 +1,23 @@ | 
				
			|||||
 | 
					#include "qtmvvmsettingscore_global.h" | 
				
			||||
 | 
					#include <QtCore/QCoreApplication> | 
				
			||||
 | 
					#include <QtMvvmCore/ServiceRegistry> | 
				
			||||
 | 
					#include <QtMvvmCore/private/qtmvvm_logging_p.h> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#include "settingssetuploader_p.h" | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					using namespace QtMvvm; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					namespace { | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					void qtMvvmSettingsCoreStartup() | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						try { | 
				
			||||
 | 
							ServiceRegistry::instance()->registerInterface<ISettingsSetupLoader, SettingsSetupLoader>(true); | 
				
			||||
 | 
						} catch(ServiceExistsException &e) { | 
				
			||||
 | 
							logDebug() << "Unable to register default ISettingsSetupLoader with error:" << e.what(); | 
				
			||||
 | 
						} | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					Q_COREAPP_STARTUP_FUNCTION(qtMvvmSettingsCoreStartup) | 
				
			||||
@ -0,0 +1,12 @@ | 
				
			|||||
 | 
					#ifndef QTMVVMSETTINGSCORE_GLOBAL_H | 
				
			||||
 | 
					#define QTMVVMSETTINGSCORE_GLOBAL_H | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#include <QtCore/qglobal.h> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#if defined(QT_BUILD_MVVMSETTINGSCORE_LIB) | 
				
			||||
 | 
					#  define Q_MVVMSETTINGSCORE_EXPORT Q_DECL_EXPORT | 
				
			||||
 | 
					#else | 
				
			||||
 | 
					#  define Q_MVVMSETTINGSCORE_EXPORT Q_DECL_IMPORT | 
				
			||||
 | 
					#endif | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#endif // QTMVVMSETTINGSCORE_GLOBAL_H
 | 
				
			||||
@ -0,0 +1,83 @@ | 
				
			|||||
 | 
					#ifndef QTMVVM_SETTINGSSETUP_H | 
				
			||||
 | 
					#define QTMVVM_SETTINGSSETUP_H | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#include <QtCore/qstringlist.h> | 
				
			||||
 | 
					#include <QtCore/qvariant.h> | 
				
			||||
 | 
					#include <QtCore/qurl.h> | 
				
			||||
 | 
					#include <QtCore/qobject.h> | 
				
			||||
 | 
					#include <QtCore/qfileselector.h> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#include "QtMvvmSettingsCore/qtmvvmsettingscore_global.h" | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					namespace QtMvvm { | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					struct SettingsEntry | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						QString key; | 
				
			||||
 | 
						QByteArray type; | 
				
			||||
 | 
						QString title; | 
				
			||||
 | 
						QString tooltip; | 
				
			||||
 | 
						QVariant defaultValue; | 
				
			||||
 | 
						QStringList searchKeys; | 
				
			||||
 | 
						QVariantMap properties; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						QString frontends; | 
				
			||||
 | 
						QString selectors; | 
				
			||||
 | 
					}; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					struct SettingsGroup | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						QString title; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						QList<SettingsEntry> entries; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						QString frontends; | 
				
			||||
 | 
						QString selectors; | 
				
			||||
 | 
					}; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					struct SettingsSection | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						QString title; | 
				
			||||
 | 
						QUrl icon; | 
				
			||||
 | 
						QString tooltip; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						QList<SettingsGroup> groups; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						QString frontends; | 
				
			||||
 | 
						QString selectors; | 
				
			||||
 | 
					}; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					struct SettingsCategory | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						QString title; | 
				
			||||
 | 
						QUrl icon; | 
				
			||||
 | 
						QString tooltip; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						QList<SettingsSection> sections; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						QString frontends; | 
				
			||||
 | 
						QString selectors; | 
				
			||||
 | 
					}; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					struct SettingsSetup | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						bool allowSearch = false; | 
				
			||||
 | 
						bool allowRestore = false; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						QList<SettingsCategory> categories; | 
				
			||||
 | 
					}; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					class ISettingsSetupLoader | 
				
			||||
 | 
					{ | 
				
			||||
 | 
					public: | 
				
			||||
 | 
						virtual inline ~ISettingsSetupLoader() = default; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						virtual SettingsSetup loadSetup(const QString &platform, const QFileSelector *selector, const QString &filePath) const = 0; | 
				
			||||
 | 
					}; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#define QtMvvm_ISettingsSetupLoaderIid "de.skycoder42.qtmvvm.settings.core.ISettingsSetupLoader" | 
				
			||||
 | 
					Q_DECLARE_INTERFACE(QtMvvm::ISettingsSetupLoader, QtMvvm_ISettingsSetupLoaderIid) | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#endif // QTMVVM_SETTINGSSETUP_H
 | 
				
			||||
@ -0,0 +1,400 @@ | 
				
			|||||
 | 
					#include "settingssetuploader_p.h" | 
				
			||||
 | 
					using namespace QtMvvm; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#define throwXmlError(...) throw SettingsXmlException(__VA_ARGS__) | 
				
			||||
 | 
					#define throwAttrib(reader, attrib) throwXmlError(reader, "Attribute \"" attrib "\" is required, but was not set!") | 
				
			||||
 | 
					#define throwElement(reader, element) throwXmlError(reader, "Expected element of type <" element ">") | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#define testXmlValid(reader) if(reader.hasError()) throwXmlError(reader) | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#define hasValue(key) attributes().hasAttribute(QStringLiteral(key)) | 
				
			||||
 | 
					#define stringValue(key) attributes().value(QStringLiteral(key)).toString() | 
				
			||||
 | 
					#define boolValue(key) attributes().value(QStringLiteral(key)) == QStringLiteral("true") | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					QUrl SettingsSetupLoader::defaultIcon(QStringLiteral("qrc:/qtmvvm/icons/settings.svg")); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					SettingsSetupLoader::SettingsSetupLoader(QObject *parent) : | 
				
			||||
 | 
						QObject(parent), | 
				
			||||
 | 
						_cache() | 
				
			||||
 | 
					{} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					SettingsSetup SettingsSetupLoader::loadSetup(const QString &platform, const QFileSelector *selector, const QString &filePath) const | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						SettingsSetup setup; | 
				
			||||
 | 
						if(!_cache.contains(filePath)) { | 
				
			||||
 | 
							QFile file(filePath); | 
				
			||||
 | 
							if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) | 
				
			||||
 | 
								throw SettingsXmlException(file); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
							QXmlStreamReader reader(&file); | 
				
			||||
 | 
							testXmlValid(reader); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
							if(!reader.readNextStartElement() || reader.name() != QStringLiteral("SettingsConfig")) | 
				
			||||
 | 
								throwElement(reader, "SettingsConfig"); | 
				
			||||
 | 
							setup.allowSearch = reader.boolValue("allowSearch"); | 
				
			||||
 | 
							setup.allowRestore = reader.boolValue("allowRestore"); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
							if(reader.readNextStartElement()) { | 
				
			||||
 | 
								if(reader.name() == QStringLiteral("Category")) { | 
				
			||||
 | 
									do | 
				
			||||
 | 
										setup.categories.append(readCategory(reader)); | 
				
			||||
 | 
									while(reader.readNextStartElement()); | 
				
			||||
 | 
								} else | 
				
			||||
 | 
									setup.categories.append(readDefaultCategory(reader)); | 
				
			||||
 | 
							} | 
				
			||||
 | 
							testXmlValid(reader); | 
				
			||||
 | 
							file.close(); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
							_cache.insert(filePath, new SettingsSetup(setup)); | 
				
			||||
 | 
						} else | 
				
			||||
 | 
							setup = *(_cache.object(filePath)); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						//todo platform/selector
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						return setup; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					SettingsCategory SettingsSetupLoader::readCategory(QXmlStreamReader &reader) const | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						testXmlValid(reader); | 
				
			||||
 | 
						if(reader.name() != QStringLiteral("Category")) | 
				
			||||
 | 
							throwElement(reader, "Category"); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						auto category = createDefaultCategory(); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						if(reader.hasValue("title")) | 
				
			||||
 | 
							category.title = reader.stringValue("title"); | 
				
			||||
 | 
						if(reader.hasValue("icon")) | 
				
			||||
 | 
							category.icon = reader.stringValue("icon"); | 
				
			||||
 | 
						if(reader.hasValue("tooltip")) | 
				
			||||
 | 
							category.tooltip = reader.stringValue("tooltip"); | 
				
			||||
 | 
						category.frontends = reader.stringValue("frontends"); | 
				
			||||
 | 
						category.selectors = reader.stringValue("selectors"); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						if(reader.readNextStartElement()) { | 
				
			||||
 | 
							if(reader.name() == QStringLiteral("Section")) { | 
				
			||||
 | 
								do | 
				
			||||
 | 
									category.sections.append(readSection(reader)); | 
				
			||||
 | 
								while(reader.readNextStartElement()); | 
				
			||||
 | 
							} else | 
				
			||||
 | 
								category.sections.append(readDefaultSection(reader)); | 
				
			||||
 | 
						} | 
				
			||||
 | 
						testXmlValid(reader); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						return category; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					SettingsCategory SettingsSetupLoader::readDefaultCategory(QXmlStreamReader &reader) const | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						testXmlValid(reader); | 
				
			||||
 | 
						auto category = createDefaultCategory(); | 
				
			||||
 | 
						if(reader.name() == QStringLiteral("Section")) { | 
				
			||||
 | 
							do | 
				
			||||
 | 
								category.sections.append(readSection(reader)); | 
				
			||||
 | 
							while(reader.readNextStartElement()); | 
				
			||||
 | 
						} else | 
				
			||||
 | 
							category.sections.append(readDefaultSection(reader)); | 
				
			||||
 | 
						testXmlValid(reader); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						return category; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					SettingsSection SettingsSetupLoader::readSection(QXmlStreamReader &reader) const | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						testXmlValid(reader); | 
				
			||||
 | 
						if(reader.name() != QStringLiteral("Section")) | 
				
			||||
 | 
							throwElement(reader, "Section"); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						auto section = createDefaultSection(); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						if(reader.hasValue("title")) | 
				
			||||
 | 
							section.title = reader.stringValue("title"); | 
				
			||||
 | 
						if(reader.hasValue("icon")) | 
				
			||||
 | 
							section.icon = reader.stringValue("icon"); | 
				
			||||
 | 
						if(reader.hasValue("tooltip")) | 
				
			||||
 | 
							section.tooltip = reader.stringValue("tooltip"); | 
				
			||||
 | 
						section.frontends = reader.stringValue("frontends"); | 
				
			||||
 | 
						section.selectors = reader.stringValue("selectors"); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						if(reader.readNextStartElement()) { | 
				
			||||
 | 
							if(reader.name() == QStringLiteral("Group")) { | 
				
			||||
 | 
								do | 
				
			||||
 | 
									section.groups.append(readGroup(reader)); | 
				
			||||
 | 
								while(reader.readNextStartElement()); | 
				
			||||
 | 
							} else | 
				
			||||
 | 
								section.groups.append(readDefaultGroup(reader)); | 
				
			||||
 | 
						} | 
				
			||||
 | 
						testXmlValid(reader); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						return section; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					SettingsSection SettingsSetupLoader::readDefaultSection(QXmlStreamReader &reader) const | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						testXmlValid(reader); | 
				
			||||
 | 
						auto section = createDefaultSection(); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						if(reader.name() == QStringLiteral("Group")) { | 
				
			||||
 | 
							do | 
				
			||||
 | 
								section.groups.append(readGroup(reader)); | 
				
			||||
 | 
							while(reader.readNextStartElement()); | 
				
			||||
 | 
						} else | 
				
			||||
 | 
							section.groups.append(readDefaultGroup(reader)); | 
				
			||||
 | 
						testXmlValid(reader); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						return section; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					SettingsGroup SettingsSetupLoader::readGroup(QXmlStreamReader &reader) const | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						testXmlValid(reader); | 
				
			||||
 | 
						if(reader.name() != QStringLiteral("Group")) | 
				
			||||
 | 
							throwElement(reader, "Group"); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						SettingsGroup group; | 
				
			||||
 | 
						if(reader.hasValue("title")) | 
				
			||||
 | 
							group.title = reader.stringValue("title"); | 
				
			||||
 | 
						group.frontends = reader.stringValue("frontends"); | 
				
			||||
 | 
						group.selectors = reader.stringValue("selectors"); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						if(reader.readNextStartElement()) { | 
				
			||||
 | 
							if(reader.name() == QStringLiteral("Entry")) { | 
				
			||||
 | 
								do | 
				
			||||
 | 
									group.entries.append(readEntry(reader)); | 
				
			||||
 | 
								while(reader.readNextStartElement()); | 
				
			||||
 | 
							} else | 
				
			||||
 | 
								throwElement(reader, "Entry"); | 
				
			||||
 | 
						} | 
				
			||||
 | 
						testXmlValid(reader); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						return group; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					SettingsGroup SettingsSetupLoader::readDefaultGroup(QXmlStreamReader &reader) const | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						testXmlValid(reader); | 
				
			||||
 | 
						SettingsGroup group; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						if(reader.name() == QStringLiteral("Entry")) { | 
				
			||||
 | 
							do | 
				
			||||
 | 
								group.entries.append(readEntry(reader)); | 
				
			||||
 | 
							while(reader.readNextStartElement()); | 
				
			||||
 | 
						} else | 
				
			||||
 | 
							throwElement(reader, "Entry"); | 
				
			||||
 | 
						testXmlValid(reader); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						return group; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					SettingsEntry SettingsSetupLoader::readEntry(QXmlStreamReader &reader) const | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						testXmlValid(reader); | 
				
			||||
 | 
						SettingsEntry entry; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						if(!reader.hasValue("key")) | 
				
			||||
 | 
							throwAttrib(reader, "key"); | 
				
			||||
 | 
						entry.key = reader.stringValue("key"); | 
				
			||||
 | 
						if(!reader.hasValue("type")) | 
				
			||||
 | 
							throwAttrib(reader, "type"); | 
				
			||||
 | 
						entry.type = reader.stringValue("type").toLatin1(); | 
				
			||||
 | 
						if(!reader.hasValue("title")) | 
				
			||||
 | 
							throwAttrib(reader, "title"); | 
				
			||||
 | 
						entry.title = reader.stringValue("title"); | 
				
			||||
 | 
						entry.tooltip = reader.stringValue("tooltip"); | 
				
			||||
 | 
						entry.defaultValue = reader.stringValue("default"); | 
				
			||||
 | 
						entry.frontends = reader.stringValue("frontends"); | 
				
			||||
 | 
						entry.selectors = reader.stringValue("selectors"); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						while(reader.readNextStartElement()) { | 
				
			||||
 | 
							if(reader.name() == QStringLiteral("SearchKey")) | 
				
			||||
 | 
								entry.searchKeys.append(reader.readElementText()); | 
				
			||||
 | 
							else if(reader.name() == QStringLiteral("Property")) { | 
				
			||||
 | 
								auto prop = readProperty(reader); | 
				
			||||
 | 
								entry.properties.insert(std::get<0>(prop), std::get<1>(prop)); | 
				
			||||
 | 
							} else | 
				
			||||
 | 
								throwXmlError(reader, "Expected element of type <SearchKey> or <Property>"); | 
				
			||||
 | 
						} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						return entry; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					SettingsCategory SettingsSetupLoader::createDefaultCategory() const | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						SettingsCategory category; | 
				
			||||
 | 
						category.title = tr("General Settings"); | 
				
			||||
 | 
						category.icon = defaultIcon; | 
				
			||||
 | 
						return category; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					SettingsSection SettingsSetupLoader::createDefaultSection() const | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						SettingsSection section; | 
				
			||||
 | 
						section.title = tr("General"); | 
				
			||||
 | 
						return section; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					std::tuple<QString, QVariant> SettingsSetupLoader::readProperty(QXmlStreamReader &reader) const | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						if(!reader.hasValue("key")) | 
				
			||||
 | 
							throwAttrib(reader, "key"); | 
				
			||||
 | 
						return std::make_tuple(reader.stringValue("key"), readElement(reader)); | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					QVariant SettingsSetupLoader::readElement(QXmlStreamReader &reader) const | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						if(!reader.hasValue("type")) | 
				
			||||
 | 
							throwAttrib(reader, "type"); | 
				
			||||
 | 
						auto type = reader.stringValue("type"); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						//special type mappings
 | 
				
			||||
 | 
						if(type == QStringLiteral("string")) | 
				
			||||
 | 
							type = QString::fromUtf8(QMetaType::typeName(QMetaType::QString)); | 
				
			||||
 | 
						else if(type == QStringLiteral("number")) | 
				
			||||
 | 
							type = QString::fromUtf8(QMetaType::typeName(QMetaType::Int)); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						//special types
 | 
				
			||||
 | 
						if(type == QStringLiteral("list")) { | 
				
			||||
 | 
							QVariantList list; | 
				
			||||
 | 
							while (reader.readNextStartElement()) { | 
				
			||||
 | 
								if(reader.name() != QStringLiteral("Element")) | 
				
			||||
 | 
									throwElement(reader, "Element"); | 
				
			||||
 | 
								list.append(readElement(reader)); | 
				
			||||
 | 
							} | 
				
			||||
 | 
							testXmlValid(reader); | 
				
			||||
 | 
							return list; | 
				
			||||
 | 
						} else if(type == QStringLiteral("object")) { | 
				
			||||
 | 
							QVariantMap map; | 
				
			||||
 | 
							while (reader.readNextStartElement()) { | 
				
			||||
 | 
								if(reader.name() != QStringLiteral("Property")) | 
				
			||||
 | 
									throwElement(reader, "Property"); | 
				
			||||
 | 
								auto property = readProperty(reader); | 
				
			||||
 | 
								map.insert(std::get<0>(property), std::get<1>(property)); | 
				
			||||
 | 
							} | 
				
			||||
 | 
							testXmlValid(reader); | 
				
			||||
 | 
							return map; | 
				
			||||
 | 
						} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						//common types
 | 
				
			||||
 | 
						auto typeId = QMetaType::type(qUtf8Printable(type)); | 
				
			||||
 | 
						if(typeId == QMetaType::UnknownType) | 
				
			||||
 | 
							throwXmlError(reader, "Unknown type: " + type.toUtf8()); | 
				
			||||
 | 
						QVariant mVariant = reader.readElementText(); | 
				
			||||
 | 
						if(!mVariant.convert(typeId)) | 
				
			||||
 | 
							throwXmlError(reader, "Failed to convert element data to type: " + type.toUtf8()); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						return mVariant; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					void SettingsSetupLoader::clearSetup(SettingsSetup &setup, const QString &frontend, const QStringList &selectors) const | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						for(auto it = setup.categories.begin(); it != setup.categories.end();) { | 
				
			||||
 | 
							if(isUsable(*it, frontend, selectors)) { | 
				
			||||
 | 
								clearCategory(*it, frontend, selectors); | 
				
			||||
 | 
								it++; | 
				
			||||
 | 
							} else | 
				
			||||
 | 
								it = setup.categories.erase(it); | 
				
			||||
 | 
						} | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					void SettingsSetupLoader::clearCategory(SettingsCategory &category, const QString &frontend, const QStringList &selectors) const | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						for(auto it = category.sections.begin(); it != category.sections.end();) { | 
				
			||||
 | 
							if(isUsable(*it, frontend, selectors)) { | 
				
			||||
 | 
								clearSection(*it, frontend, selectors); | 
				
			||||
 | 
								it++; | 
				
			||||
 | 
							} else | 
				
			||||
 | 
								it = category.sections.erase(it); | 
				
			||||
 | 
						} | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					void SettingsSetupLoader::clearSection(SettingsSection §ion, const QString &frontend, const QStringList &selectors) const | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						for(auto it = section.groups.begin(); it != section.groups.end();) { | 
				
			||||
 | 
							if(isUsable(*it, frontend, selectors)) { | 
				
			||||
 | 
								clearGroup(*it, frontend, selectors); | 
				
			||||
 | 
								it++; | 
				
			||||
 | 
							} else | 
				
			||||
 | 
								it = section.groups.erase(it); | 
				
			||||
 | 
						} | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					void SettingsSetupLoader::clearGroup(SettingsGroup &group, const QString &frontend, const QStringList &selectors) const | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						for(auto it = group.entries.begin(); it != group.entries.end();) { | 
				
			||||
 | 
							if(isUsable(*it, frontend, selectors)) | 
				
			||||
 | 
								it++; | 
				
			||||
 | 
							else | 
				
			||||
 | 
								it = group.entries.erase(it); | 
				
			||||
 | 
						} | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					template<typename T> | 
				
			||||
 | 
					bool SettingsSetupLoader::isUsable(const T &configElement, const QString &frontend, const QStringList &selectors) const | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						auto fronts = configElement.frontends.split(QLatin1Char('|'), QString::SkipEmptyParts); | 
				
			||||
 | 
						if(!fronts.isEmpty() && !fronts.contains(frontend)) | 
				
			||||
 | 
							return false; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						auto selects = configElement.selectors.split(QLatin1Char('|'), QString::SkipEmptyParts); | 
				
			||||
 | 
						for(auto select : selects) { | 
				
			||||
 | 
							auto sels = select.split(QLatin1Char('&'), QString::SkipEmptyParts); | 
				
			||||
 | 
							auto allSelected = true; | 
				
			||||
 | 
							for(auto sel : sels) { | 
				
			||||
 | 
								if(!selectors.contains(sel)) { | 
				
			||||
 | 
									allSelected = false; | 
				
			||||
 | 
									break; | 
				
			||||
 | 
								} | 
				
			||||
 | 
							} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
							if(allSelected) | 
				
			||||
 | 
								return true; | 
				
			||||
 | 
						} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						return selects.isEmpty(); | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					SettingsXmlException::SettingsXmlException(const QXmlStreamReader &reader) : | 
				
			||||
 | 
						_what(QStringLiteral("XML Error at %1:%2. Error: %3") | 
				
			||||
 | 
							  .arg(reader.lineNumber()) | 
				
			||||
 | 
							  .arg(reader.columnNumber()) | 
				
			||||
 | 
							  .arg(reader.errorString()) | 
				
			||||
 | 
							  .toUtf8()) | 
				
			||||
 | 
					{} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					SettingsXmlException::SettingsXmlException(QXmlStreamReader &reader, const QByteArray &customError, bool forceOverwrite) : | 
				
			||||
 | 
						SettingsXmlException([&]() -> QXmlStreamReader & { | 
				
			||||
 | 
							if(forceOverwrite || !reader.hasError()) | 
				
			||||
 | 
								reader.raiseError(QString::fromUtf8(customError)); | 
				
			||||
 | 
							return reader; | 
				
			||||
 | 
						}()) | 
				
			||||
 | 
					{} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					SettingsXmlException::SettingsXmlException(const QFile &fileError) : | 
				
			||||
 | 
						_what(QStringLiteral("Failed to open file \"%1\" with error: %2") | 
				
			||||
 | 
							  .arg(fileError.fileName()) | 
				
			||||
 | 
							  .arg(fileError.errorString()) | 
				
			||||
 | 
							  .toUtf8()) | 
				
			||||
 | 
					{} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					SettingsXmlException::SettingsXmlException(const SettingsXmlException * const other) : | 
				
			||||
 | 
						_what(other->_what) | 
				
			||||
 | 
					{} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					const char *SettingsXmlException::what() const noexcept | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						return _what.constData(); | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					void SettingsXmlException::raise() const | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						throw (*this); | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					QException *SettingsXmlException::clone() const | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						return new SettingsXmlException(this); | 
				
			||||
 | 
					} | 
				
			||||
@ -0,0 +1,81 @@ | 
				
			|||||
 | 
					#ifndef QTMVVM_SETTINGSSETUPLOADER_P_H | 
				
			||||
 | 
					#define QTMVVM_SETTINGSSETUPLOADER_P_H | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#include <tuple> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#include <QtCore/QObject> | 
				
			||||
 | 
					#include <QtCore/QIODevice> | 
				
			||||
 | 
					#include <QtCore/QCache> | 
				
			||||
 | 
					#include <QtCore/QXmlStreamReader> | 
				
			||||
 | 
					#include <QtCore/QException> | 
				
			||||
 | 
					#include <QtCore/QFile> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#include "qtmvvmsettingscore_global.h" | 
				
			||||
 | 
					#include "settingssetup.h" | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					namespace QtMvvm { | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					class SettingsSetupLoader : public QObject, public ISettingsSetupLoader | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						Q_OBJECT | 
				
			||||
 | 
						Q_INTERFACES(QtMvvm::ISettingsSetupLoader) | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					public: | 
				
			||||
 | 
						SettingsSetupLoader(QObject *parent = nullptr); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						SettingsSetup loadSetup(const QString &platform, const QFileSelector *selector, const QString &filePath) const override; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					private: | 
				
			||||
 | 
						static QUrl defaultIcon; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						mutable QCache<QString, SettingsSetup> _cache; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						//functions to read the settings XML
 | 
				
			||||
 | 
						SettingsCategory readCategory(QXmlStreamReader &reader) const; | 
				
			||||
 | 
						SettingsCategory readDefaultCategory(QXmlStreamReader &reader) const; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						SettingsSection readSection(QXmlStreamReader &reader) const; | 
				
			||||
 | 
						SettingsSection readDefaultSection(QXmlStreamReader &reader) const; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						SettingsGroup readGroup(QXmlStreamReader &reader) const; | 
				
			||||
 | 
						SettingsGroup readDefaultGroup(QXmlStreamReader &reader) const; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						SettingsEntry readEntry(QXmlStreamReader &reader) const; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						SettingsCategory createDefaultCategory() const; | 
				
			||||
 | 
						SettingsSection createDefaultSection() const; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						std::tuple<QString, QVariant> readProperty(QXmlStreamReader &reader) const; | 
				
			||||
 | 
						QVariant readElement(QXmlStreamReader &reader) const; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						//Functions to filter the elements
 | 
				
			||||
 | 
						void clearSetup(SettingsSetup &setup, const QString &frontend, const QStringList &selectors) const; | 
				
			||||
 | 
						void clearCategory(SettingsCategory &category, const QString &frontend, const QStringList &selectors) const; | 
				
			||||
 | 
						void clearSection(SettingsSection §ion, const QString &frontend, const QStringList &selectors) const; | 
				
			||||
 | 
						void clearGroup(SettingsGroup &group, const QString &frontend, const QStringList &selectors) const; | 
				
			||||
 | 
						template <typename T> | 
				
			||||
 | 
						bool isUsable(const T &configElement, const QString &frontend, const QStringList &selectors) const; | 
				
			||||
 | 
					}; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					class SettingsXmlException : public QException | 
				
			||||
 | 
					{ | 
				
			||||
 | 
					public: | 
				
			||||
 | 
						SettingsXmlException(const QXmlStreamReader &reader); | 
				
			||||
 | 
						SettingsXmlException(QXmlStreamReader &reader, const QByteArray &customError, bool forceOverwrite = false); | 
				
			||||
 | 
						SettingsXmlException(const QFile &fileError); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						const char *what() const noexcept override; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						void raise() const override; | 
				
			||||
 | 
						QException *clone() const override; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					protected: | 
				
			||||
 | 
						SettingsXmlException(const SettingsXmlException * const other); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					private: | 
				
			||||
 | 
						const QByteArray _what; | 
				
			||||
 | 
					}; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#endif // QTMVVM_SETTINGSSETUPLOADER_P_H
 | 
				
			||||
@ -0,0 +1,54 @@ | 
				
			|||||
 | 
					#include "settingsviewmodel.h" | 
				
			||||
 | 
					#include "settingsviewmodel_p.h" | 
				
			||||
 | 
					#include <QtMvvmCore/CoreApp> | 
				
			||||
 | 
					#include <QtMvvmCore/private/qtmvvm_logging_p.h> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#include "settingssetuploader_p.h" | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					using namespace QtMvvm; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					SettingsViewModel::SettingsViewModel(QObject *parent) : | 
				
			||||
 | 
						ViewModel(parent), | 
				
			||||
 | 
						d(new SettingsViewModelPrivate()) | 
				
			||||
 | 
					{} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					SettingsViewModel::~SettingsViewModel() {} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					void SettingsViewModel::showSettings(ViewModel *parent) | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						showSettings(nullptr, {}, parent); | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					void SettingsViewModel::showSettings(QSettings *settings, const QString &setupFile, ViewModel *parent) | 
				
			||||
 | 
					{ | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					bool SettingsViewModel::canRestoreDefaults() const | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						return true; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					ISettingsSetupLoader *SettingsViewModel::settingsSetupLoader() const | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						return d->settingsSetupLoader; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					void SettingsViewModel::callAction(const QString &entryId) | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						logWarning() << "Unknown action requested with entry id:" << entryId; | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					void SettingsViewModel::setSettingsSetupLoader(ISettingsSetupLoader *settingsSetupLoader) | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						if (d->settingsSetupLoader == settingsSetupLoader) | 
				
			||||
 | 
							return; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						d->settingsSetupLoader = settingsSetupLoader; | 
				
			||||
 | 
						emit settingsSetupLoaderChanged(d->settingsSetupLoader); | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					void SettingsViewModel::onInit(const QVariantHash ¶ms) | 
				
			||||
 | 
					{ | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					} | 
				
			||||
@ -0,0 +1,52 @@ | 
				
			|||||
 | 
					#ifndef QTMVVM_SETTINGSVIEWMODEL_H | 
				
			||||
 | 
					#define QTMVVM_SETTINGSVIEWMODEL_H | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#include <QtCore/qsettings.h> | 
				
			||||
 | 
					#include <QtCore/qscopedpointer.h> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#include <QtMvvmCore/viewmodel.h> | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#include "QtMvvmSettingsCore/qtmvvmsettingscore_global.h" | 
				
			||||
 | 
					#include "QtMvvmSettingsCore/settingssetuploader.h" | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					namespace QtMvvm { | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					class SettingsViewModelPrivate; | 
				
			||||
 | 
					class SettingsViewModel : public ViewModel | 
				
			||||
 | 
					{ | 
				
			||||
 | 
						Q_OBJECT | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						Q_PROPERTY(bool canRestoreDefaults READ canRestoreDefaults CONSTANT) | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						Q_PROPERTY(QtMvvm::ISettingsSetupLoader* settingsSetupLoader READ settingsSetupLoader WRITE setSettingsSetupLoader NOTIFY settingsSetupLoaderChanged) | 
				
			||||
 | 
						QTMVVM_INJECT(QtMvvm::ISettingsSetupLoader*, settingsSetupLoader) | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					public: | 
				
			||||
 | 
						Q_INVOKABLE explicit SettingsViewModel(QObject *parent = nullptr); | 
				
			||||
 | 
						~SettingsViewModel(); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						static void showSettings(ViewModel *parent); | 
				
			||||
 | 
						static void showSettings(QSettings *settings = nullptr, const QString &setupFile = {}, ViewModel *parent = nullptr); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						virtual bool canRestoreDefaults() const; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						QtMvvm::ISettingsSetupLoader* settingsSetupLoader() const; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					public Q_SLOTS: | 
				
			||||
 | 
						virtual void callAction(const QString &entryId); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
						void setSettingsSetupLoader(QtMvvm::ISettingsSetupLoader* settingsSetupLoader); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					Q_SIGNALS: | 
				
			||||
 | 
						void settingsSetupLoaderChanged(QtMvvm::ISettingsSetupLoader* settingsSetupLoader); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					protected: | 
				
			||||
 | 
						void onInit(const QVariantHash ¶ms) override; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					private: | 
				
			||||
 | 
						QScopedPointer<SettingsViewModelPrivate> d; | 
				
			||||
 | 
					}; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#endif // QTMVVM_SETTINGSVIEWMODEL_H
 | 
				
			||||
@ -0,0 +1,17 @@ | 
				
			|||||
 | 
					#ifndef QTMVVM_SETTINGSVIEWMODEL_P_H | 
				
			||||
 | 
					#define QTMVVM_SETTINGSVIEWMODEL_P_H | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#include "qtmvvmsettingscore_global.h" | 
				
			||||
 | 
					#include "settingsviewmodel.h" | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					namespace QtMvvm { | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					class SettingsViewModelPrivate | 
				
			||||
 | 
					{ | 
				
			||||
 | 
					public: | 
				
			||||
 | 
						ISettingsSetupLoader *settingsSetupLoader = nullptr; | 
				
			||||
 | 
					}; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#endif // QTMVVM_SETTINGSVIEWMODEL_P_H
 | 
				
			||||
					Loading…
					
					
				
		Reference in new issue