From 99a2df80258dafc4722634d0ff4f86224c33b44b Mon Sep 17 00:00:00 2001 From: Skycoder42 Date: Sun, 5 Aug 2018 12:34:46 +0200 Subject: [PATCH] wip doc --- README.md | 10 +- doc/coreapp.dox | 70 +++++++++- doc/isettingsaccessor.dox | 125 ++++++++++++++++++ src/mvvmcore/binding.h | 1 + src/mvvmcore/coreapp.h | 12 +- src/mvvmcore/isettingsaccessor.h | 15 ++- src/mvvmcore/mvvmcore.pro | 4 +- .../datasyncsettingsentry.cpp | 7 + 8 files changed, 229 insertions(+), 15 deletions(-) create mode 100644 doc/isettingsaccessor.dox diff --git a/README.md b/README.md index d5cf3a8..af371fb 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,14 @@ The main feature of QtMvvm is the seperation between ui and logic. With this lib The key features are: - Create ViewModels in the core application to prepare data for presentation without binding to any concret GUI + - Supports singleton ViewModels + - Supports automatic presentation of container ViewModels - Functions to show messageboxes (info, warning, error, etc.) from your core app - Asynchronous, with result handling - Supports input dialogs and native file dialogs out of the box - Supports native file pickers on Android + - Supports color picker dialog + - Supports progress and busy indicator dialogs - custom dialog types can be created - Methods to create Two-Way Bindings from C++ and QML - Macros and a ServiceRegistry to make Dependency Injection possible for Services and ViewModels @@ -136,8 +140,8 @@ The most important part is to know how to add new ViewModels and Views. ```qml import QtQuick 2.10 import QtQuick.Controls 2.3 -import de.skycoder42.QtMvvm.Core 1.0 -import de.skycoder42.QtMvvm.Quick 1.0 +import de.skycoder42.QtMvvm.Core 1.1 +import de.skycoder42.QtMvvm.Quick 1.1 import com.example.mvvmexample 1.0 //adjust to the module defined in your main.cpp ``` - Add a property named viewmodel to the root element: `property MyCustomViewModel viewmodel: null` (If you did not register the viewmodel, use `var` instead of `MyCustomViewModel` as property type) @@ -169,7 +173,7 @@ To create a presenter, the `QtMvvm::IPresenter` must be implemented and provided - The `QtMvvmApp` qml types automatically register themselves as presenter and perform the presentations - Supports Items as new fullscreen pages inside a stack view, as drawer or as tabs - Supports Popups as modal dialogs - - + ## Icons In many of the UI projects default icons are used for the views (if no icon theme is present). They are taken from: diff --git a/doc/coreapp.dox b/doc/coreapp.dox index 6be23d4..8756e13 100644 --- a/doc/coreapp.dox +++ b/doc/coreapp.dox @@ -92,10 +92,11 @@ CoreApp::disableAutoBoot */ /*! -@fn QtMvvm::CoreApp::show(const QVariantHash &) +@fn QtMvvm::CoreApp::show(const QVariantHash &, QPointer) @tparam TViewModel The type of the viewmodel to be presented @param params A map of named parameters to be passed to the viewmodel +@param parentViewModel The viewmodel to be used as logical parent to the new one, for presenting Creates a new instance of the Viewmodel class, creates to corresponding view and then shows it. This is done via the IPresenter that is currently registered. @@ -104,25 +105,29 @@ This is done via the IPresenter that is currently registered. */ /*! -@fn QtMvvm::CoreApp::show(const char *, const QVariantHash &) +@fn QtMvvm::CoreApp::show(const char *, const QVariantHash &, QPointer) @param viewModelName The name of the viewmodel to be presented @param params A map of named parameters to be passed to the viewmodel +@param parentViewModel The viewmodel to be used as logical parent to the new one, for presenting Creates a new instance of the Viewmodel class, creates to corresponding view and then shows it. -This is done via the IPresenter that is currently registered. +This is done via the IPresenter that is currently registered, taking the parent viewmodel into +consideration where neccessary. @sa ViewModel::show, CoreApp::showDialog */ /*! -@fn QtMvvm::CoreApp::show(const QMetaObject *, const QVariantHash &) +@fn QtMvvm::CoreApp::show(const QMetaObject *, const QVariantHash &, QPointer) @param viewModelMetaObject The metaobject of the viewmodel to be presented @param params A map of named parameters to be passed to the viewmodel +@param parentViewModel The viewmodel to be used as logical parent to the new one, for presenting Creates a new instance of the Viewmodel class, creates to corresponding view and then shows it. -This is done via the IPresenter that is currently registered. +This is done via the IPresenter that is currently registered, taking the parent viewmodel into +consideration where neccessary. @sa ViewModel::show, CoreApp::showDialog */ @@ -142,6 +147,61 @@ been closed. @sa CoreApp::show, MessageConfig, MessageResult */ +/*! +@fn QtMvvm::CoreApp::safeCastInputType + +@param type The edit type the given value is supposed to be of +@param value The value to be cast to the type specified by the edit type +@returns A variant of the given value, cast to that type + +The primary function of this method is to cast types like `url` to their respective C++ equivalent, +i.e. `QUrl`. The table below shows the default type conversion map, and custom entries can be +added via registerInputTypeMapping(). For all types not in that mapping, the function tries +to see `type` like a C++ typename, i.e. `int` and use that to convert the variant. If no type +can be determined, the value is returned without doing anything. + + Edit-Type | C++-Type +------------|---------- + switch | bool + string | QString + number | double + range | int + date | QDateTime + color | QColor + url | QUrl + var | QVariant + variant | QVariant + selection | QVariant + list | QVariant + radiolist | QVariant + +@sa CoreApp::registerInputTypeMapping +*/ + +/*! +@fn QtMvvm::CoreApp::registerInputTypeMapping(const QByteArray &) + +@tparam T The C++ type to be registered for the given edit type +@param type The edit type to register the C++ type for + +Registeres the given type, so that whenever type is passed to safeCastInputType(), the given +variant will be converter to T + +@sa CoreApp::safeCastInputType +*/ + +/*! +@fn QtMvvm::CoreApp::registerInputTypeMapping(const QByteArray &, int) + +@param type The edit type to register the C++ type for +@param targetType The id of C++ type to be registered for the given edit type + +Registeres the given type, so that whenever type is passed to safeCastInputType(), the given +variant will be converter to targetType + +@sa CoreApp::safeCastInputType +*/ + /*! @fn QtMvvm::CoreApp::bootApp diff --git a/doc/isettingsaccessor.dox b/doc/isettingsaccessor.dox new file mode 100644 index 0000000..dfdd0d3 --- /dev/null +++ b/doc/isettingsaccessor.dox @@ -0,0 +1,125 @@ +/*! +@class QtMvvm::ISettingsAccessor + +This class is used by the SettingsViewModel and the @ref settings_generator "qsettingsgenerator" +to access settings. The interface is meant to be used as proxy for some kind of settings +backend (like QSettings), so access is kept generic and you can decide to use a different +storage if needed. While this class can be used directly, it is dsigned to only be used +as backend for a settings viewmodel or a generated settings instance, and thus lacks many +"comfort" methods one would otherwise expect. + +@note All the semantincs desribed below, except for the change signals, are exactly the same +as for QSettings, as this interface is modeled after QSettings, in other words, the same rules +for keys and values as for QSettings apply to the methods of this interface. + +@section ISettingsAccessor_keys Semantics of keys +Keys follow the following semantics. All keys are of the format `group/subgroup/key`. Keys +may or may not be case sensitive, depending on the plattform. The best approach is to assume +keys are case insensitive when storing, but case sensitive when loading. While the keys are +seperated into groups and keys, all groups can also serve as keys, i.e. `group/subgroup` is a +valid key as well. + +@section ISettingsAccessor_storing Storing data +You can assume that data passed to save() is serializable via QDataStream. In other words, to +convert the variant to binary data, you can simply use operator<< and operator>> of the +QVariant. Also, just like with QSettings, the saved data does not have to retain the type. The +only condition is, that whatever you load() for the same key as used for storing must be +convertible to the same type as the variant, that was passed to the save() method. + +Finally, saving data via this method does not have to immediatly store it permanently. However, +calling load() with the same key right after save() must always return the same value as +passed to it. Permanent storing can be done asynchronously, but should happen automatically +without to much time passing after the save() (or remove()), and must always happen before +the destruction of this object. Furthermore, users can use the sync() method to immediatly store +data permanently and load all changes done externally. + +@section ISettingsAccessor_signals Change signals +Another specialty of this class are the change signals. They must be emitted whenever save() +or remove() are called, to ensure at least changes done within the same instance trigger the +according change signals. Changes done externally should lead to change signals, but don't have +to, if not supported. For example, the DataSyncSettingsAccessor can indeed detect changes as +they occur on other instances or externally, and thus emits the signals. QSettings however does +not support change signals of any kind, and thus external changes do change the data that can +be loaded, but do not emit any signals. + +@section ISettingsAccessor_impl Available implementations +Currently, the following backends are supported: + +- QSettingsAccessor: Wraps QSettings +- DataSyncSettingsAccessor: Part of mvvm datasync core, allows to store and sync settings via datasync +- AndroidSettingsAccessor: Wraps the SharedPreferences of the android API + +@sa #QtMvvm_ISettingsAccessorIID, QSettingsAccessor, DataSyncSettingsAccessor, +AndroidSettingsAccessor, SettingsViewModel, DataSyncSettingsViewModel, @ref settings_generator +*/ + +/*! +@fn QtMvvm::ISettingsAccessor::contains + +@param key The key of the settings entry to be checked for existance +@returns true, if a value for that key was stored, false if not + +This should only check for values, not for groups, i.e. if key referes to a group that has +child keys, but no value by itself, you should still return false. + +@sa ISettingsAccessor::load +*/ + +/*! +@fn QtMvvm::ISettingsAccessor::load + +@param key The key of the settings entry to be loaded +@param defaultValue A alternative value to be returned if there is no data stored for that key +@returns The data loaded for the key, or the default value + +If the given value does exist, it should be loaded and returned. If the is on data stored for +that key, simply return whatever is passed as the default value. + +@sa @ref ISettingsAccessor_storing, ISettingsAccessor::contains, ISettingsAccessor::save +*/ + +/*! +@fn QtMvvm::ISettingsAccessor::save + +@param key The key of the settings entry to be saved +@param value The data to be stored under that key + +Should simply store the passed data under the given key. You dont have to check if the variant +is actually serializable, as the meta system will automatically warn the user if thats not the +case. Permanent storing is done asynchronously, but can be forced via sync(). + +Any implementation of this method *must* emit the entryChanged() signal with the passed data and +key. + +@sa @ref ISettingsAccessor_storing, ISettingsAccessor::entryChanged, ISettingsAccessor::load, +ISettingsAccessor::remove, ISettingsAccessor::sync +*/ + +/*! +@fn QtMvvm::ISettingsAccessor::remove + +@param key The key of the settings entry to be removed + +This method should remove the given key and all of its subkeys. If for example the settings +contained the values `group/subgroup` and `group/subgroup/key`, after this method, both must +have been removed. Permanent storing is done asynchronously, but can be forced via sync(). + +Any implementation of this method *must* emit the entryRemoved() signal for **all** the removed +entries. I.e. for the example above, it must be emitted for both, `group/subgroup` and +`group/subgroup/key`. + +@sa @ref ISettingsAccessor_storing, ISettingsAccessor::entryRemoved, ISettingsAccessor::save, +ISettingsAccessor::sync +*/ + +/*! +@fn QtMvvm::ISettingsAccessor::sync + +You can call this method to immediatly store any changed data to permanent store. This exists +because typically, calls to save() and remove() only "cache" the changes, and then later write +them to the permanent store in a single transaction, as this can be an expensive operation. +Normally, you don't have to care about this, as this happens automatically, but sometimes you +might want to manually perform this step. Thats what this method is for. + +@sa ISettingsAccessor::save, ISettingsAccessor::remove, QSettings::sync +*/ diff --git a/src/mvvmcore/binding.h b/src/mvvmcore/binding.h index 4ef5825..92fe407 100644 --- a/src/mvvmcore/binding.h +++ b/src/mvvmcore/binding.h @@ -33,6 +33,7 @@ public: //! Specifies whether the binding is a valid and active binding bool isValid() const; + //! @copydoc Binding::isValid operator bool() const; //! Remove the binding by disconnecting all change signals diff --git a/src/mvvmcore/coreapp.h b/src/mvvmcore/coreapp.h index ea8ca9f..bfe4abb 100644 --- a/src/mvvmcore/coreapp.h +++ b/src/mvvmcore/coreapp.h @@ -37,21 +37,27 @@ public: //! Show a new ViewModel by its type template static inline void show(const QVariantHash ¶ms = {}, QPointer parentViewModel = nullptr); - //! Show a new ViewModel by its name + //! @copydoc CoreApp::show(const char *, const QVariantHash &, QPointer); static void show(const char *viewModelName, const QVariantHash ¶ms = {}); //MAJOR merge methods + //! Show a new ViewModel by its name static void show(const char *viewModelName, const QVariantHash ¶ms, QPointer parentViewModel); - //! Show a new ViewModel by its metaobject + //! @copydoc CoreApp::show(const QMetaObject *, const QVariantHash &, QPointer); static void show(const QMetaObject *viewModelMetaObject, const QVariantHash ¶ms = {}); //MAJOR merge methods + //! Show a new ViewModel by its metaobject static void show(const QMetaObject *viewModelMetaObject, const QVariantHash ¶ms, QPointer parentViewModel); //! Show a basic dialog static MessageResult *showDialog(const MessageConfig &config); + //! Safely casts a value of the given edit type to the corresponding variant type static QVariant safeCastInputType(const QByteArray &type, const QVariant &value); - static void registerInputTypeMapping(const QByteArray &type, int targetType); + //! Register a type to be used as variant type for the given edit type template static void registerInputTypeMapping(const QByteArray &type); + //! @copybrief CoreApp::registerInputTypeMapping(const QByteArray &) + static void registerInputTypeMapping(const QByteArray &type, int targetType); + //! Returns the currently used presenter static IPresenter *presenter(); public Q_SLOTS: diff --git a/src/mvvmcore/isettingsaccessor.h b/src/mvvmcore/isettingsaccessor.h index 35ed182..35c5b88 100644 --- a/src/mvvmcore/isettingsaccessor.h +++ b/src/mvvmcore/isettingsaccessor.h @@ -9,31 +9,42 @@ namespace QtMvvm { +//! An interface to provide a generic way to access settings of any origin class Q_MVVMCORE_EXPORT ISettingsAccessor : public QObject { Q_OBJECT Q_DISABLE_COPY(ISettingsAccessor) public: + //! Constructor ISettingsAccessor(QObject *parent = nullptr); + //! Checks if a value is stored for the given key virtual bool contains(const QString &key) const = 0; + //! Loads the value for the given key from the settings virtual QVariant load(const QString &key, const QVariant &defaultValue = {}) const = 0; + //! Stores the given value under the given key in the settings virtual void save(const QString &key, const QVariant &value) = 0; + //! Removes the key and all its subkeys from the settings virtual void remove(const QString &key) = 0; public Q_SLOTS: + //! Synchronizes the settings to the disk or whatever is needed to permanently store them virtual void sync() = 0; Q_SIGNALS: + //! Is emitted whenever a settings value was changed, at least via this instance void entryChanged(const QString &key, const QVariant &value); + //! Is emitted whenever a settings value was removed, at least via this instance void entryRemoved(const QString &key); }; } -#define ISettingsAccessorIid "de.skycoder42.qtmvvm.core.ISettingsAccessor" -Q_DECLARE_INTERFACE(QtMvvm::ISettingsAccessor, ISettingsAccessorIid) +//! The IID of the QtMvvm::IPresenter class +#define QtMvvm_ISettingsAccessorIID "de.skycoder42.qtmvvm.core.ISettingsAccessor" +Q_DECLARE_INTERFACE(QtMvvm::ISettingsAccessor, QtMvvm_ISettingsAccessorIID) Q_DECLARE_METATYPE(QtMvvm::ISettingsAccessor*) +//! @file isettingsaccessor.h The ISettingsAccessor class header #endif // QTMVVM_ISETTINGSACCESSOR_H diff --git a/src/mvvmcore/mvvmcore.pro b/src/mvvmcore/mvvmcore.pro index 6b27b66..78c407c 100644 --- a/src/mvvmcore/mvvmcore.pro +++ b/src/mvvmcore/mvvmcore.pro @@ -25,7 +25,7 @@ HEADERS += \ isettingsaccessor.h \ qsettingsaccessor.h \ settingsentry.h \ - settingsconfigloader_p.h + settingsconfigloader_p.h SOURCES += \ viewmodel.cpp \ @@ -39,7 +39,7 @@ SOURCES += \ isettingsaccessor.cpp \ qsettingsaccessor.cpp \ settingsentry.cpp \ - settingsconfigloader.cpp + settingsconfigloader.cpp include(../settingsconfig/settingsconfig.pri) diff --git a/src/mvvmdatasynccore/datasyncsettingsentry.cpp b/src/mvvmdatasynccore/datasyncsettingsentry.cpp index 02b6579..1fc08cc 100644 --- a/src/mvvmdatasynccore/datasyncsettingsentry.cpp +++ b/src/mvvmdatasynccore/datasyncsettingsentry.cpp @@ -1,5 +1,6 @@ #include "datasyncsettingsentry.h" #include +#include #if QT_HAS_INCLUDE() && __cplusplus >= 201703L #include #define QTMVVM_HAS_OPTIONAL @@ -65,14 +66,20 @@ QVariant DataSyncSettingsEntry::value() const QDataStream stream{d->data}; stream.setVersion(d->version); d->value = QVariant{}; + stream.startTransaction(); stream >> d->value.value(); + if(!stream.commitTransaction()) + logWarning() << "Failed to read data of entry with key" << d->key; } return d->value.value_or(QVariant{}); #else if(!d->data.isNull() && !d->value.isValid()) { QDataStream stream{d->data}; stream.setVersion(d->version); + stream.startTransaction(); stream >> d->value; + if(!stream.commitTransaction()) + logWarning() << "Failed to read data of entry with key" << d->key; } return d->value; #endif