summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNikita Kostovsky <nikita@kostovsky.me>2025-09-19 20:54:13 +0200
committerNikita Kostovsky <nikita@kostovsky.me>2025-09-19 20:54:13 +0200
commite7019763076cbbe3d52c2a03133c3ded5558f017 (patch)
treebcf5ff116630bebaa09492bdcdaf08a54dfd1e28
initial commit
-rw-r--r--.clang-format15
-rw-r--r--.gitignore39
-rw-r--r--CMakeLists.txt42
-rw-r--r--LICENSE165
-rw-r--r--README.md25
-rw-r--r--basic_functions.cpp158
-rw-r--r--basic_functions.h92
-rw-r--r--g_object.cpp180
-rw-r--r--g_object.h138
-rw-r--r--g_property.cpp29
-rw-r--r--g_property.h420
-rw-r--r--inisingleton.h51
-rw-r--r--jsonobjectfilesingleton.h169
-rw-r--r--keyvalue.cpp8
-rw-r--r--keyvalue.h26
-rw-r--r--logworker.cpp273
-rw-r--r--logworker.h137
-rw-r--r--serializablelist.cpp1
-rw-r--r--serializablelist.h243
-rw-r--r--seriesmodel.cpp146
-rw-r--r--seriesmodel.h48
-rw-r--r--singleton.h44
22 files changed, 2449 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..56838cf
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,15 @@
+AccessModifierOffset: -4
+AlignAfterOpenBracket: BlockIndent
+AllowAllArgumentsOnNextLine: false
+AllowAllParametersOfDeclarationOnNextLine: false
+BasedOnStyle: Microsoft
+BinPackParameters: false
+BinPackArguments: false
+BreakConstructorInitializers: BeforeComma
+ColumnLimit: 80
+InsertNewlineAtEOF: true
+IndentWidth: 4
+SpacesBeforeTrailingComments: 2
+UseTab: Never
+LineEnding: LF
+PointerAlignment: Left
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..68581de
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,39 @@
+# C++ objects and libs
+
+*.slo
+*.lo
+*.o
+*.a
+*.la
+*.lai
+*.so
+*.dll
+*.dylib
+
+# Qt-es
+
+/.qmake.cache
+/.qmake.stash
+*.pro.user
+*.pro.user.*
+*.qbs.user
+*.qbs.user.*
+*.moc
+moc_*.cpp
+moc_*.h
+qrc_*.cpp
+ui_*.h
+Makefile*
+*build*
+
+# QtCreator
+
+*.autosave
+
+# QtCtreator Qml
+*.qmlproject.user
+*.qmlproject.user.*
+
+# QtCtreator CMake
+CMakeLists.txt.user*
+
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..53ce171
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,42 @@
+cmake_minimum_required(VERSION 3.10.2)
+
+project(goodies LANGUAGES CXX)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
+set(CMAKE_CXX_STANDARD 23)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+find_package(Qt6 REQUIRED COMPONENTS
+ Core
+ Gui
+ Qml
+ Quick
+ QuickControls2
+ QuickControls2
+ Concurrent
+ Charts
+ REQUIRED)
+
+file(GLOB SOURCES *.h *.cpp)
+
+add_library(${PROJECT_NAME} STATIC ${SOURCES})
+target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR})
+target_link_libraries(${PROJECT_NAME}
+ Qt::Core
+ Qt::Gui
+ Qt::Qml
+ Qt::Quick
+ Qt::QuickControls2
+ Qt::Concurrent
+ Qt::Charts)
+
+if(NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE Release)
+endif()
+
+find_library(FOUND_TBB tbb REQUIRED)
+if(FOUND_TBB)
+target_link_libraries(${PROJECT_NAME} tbb)
+endif()
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..65c5ca8
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..07423b9
--- /dev/null
+++ b/README.md
@@ -0,0 +1,25 @@
+# goodies
+Some good snippets for qt
+
+# per-project usage
+
+## qmake
+
+Add this to your .pro-file:
+
+```pro
+include(path_to_this_repo/goodies.pri)
+```
+
+## cmake
+
+```cmake
+add_subdirectory(goodies)
+...
+target_link_libraries(${PROJECT_NAME}
+ PRIVATE
+ ...
+ Qt::Widgets
+ goodies
+)
+```
diff --git a/basic_functions.cpp b/basic_functions.cpp
new file mode 100644
index 0000000..c6640b0
--- /dev/null
+++ b/basic_functions.cpp
@@ -0,0 +1,158 @@
+#include "basic_functions.h"
+
+// qt
+#include <QDebug>
+#include <QDir>
+
+bool copyDir(const QString& src, const QString& dst)
+{
+ QDir dir(src);
+
+ if (!dir.exists())
+ {
+ qDebug() << "src dir doesn't exist:" << src;
+ return false;
+ }
+
+ for (const auto& d : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot))
+ {
+ QString dst_path = dst + QDir::separator() + d;
+
+ if (!dir.mkpath(dst_path))
+ {
+ qDebug() << "cannot make path:" << dst_path;
+ return false;
+ }
+
+ if (!copyDir(src + QDir::separator() + d, dst_path))
+ {
+ qDebug() << "cannot copy dir " << src + QDir::separator() + d
+ << "to" << dst_path;
+ return false;
+ }
+ }
+
+ for (const auto f : dir.entryList(QDir::Files))
+ {
+ if (!QFile::copy(
+ src + QDir::separator() + f,
+ dst + QDir::separator() + f
+ ))
+ {
+ qDebug() << "cannot copy file" << src + QDir::separator() + f
+ << "to" << dst + QDir::separator() + f;
+ return false;
+ }
+ }
+
+ return true;
+}
+
+QHostAddress getLocalNetworkIntefaceBySubnet(const QHostAddress& remote)
+{
+ const auto allInterfaces = QNetworkInterface::allInterfaces();
+
+ for (const auto& i : allInterfaces)
+ {
+ if (!(i.flags() & QNetworkInterface::IsUp))
+ {
+ continue;
+ }
+
+ for (const auto& a : i.addressEntries())
+ {
+ if (remote.isInSubnet(a.ip(), a.netmask().toIPv4Address()))
+ {
+ return a.ip();
+ }
+ }
+ }
+
+ return QHostAddress();
+}
+
+quint16 CalculateChecksum(quint16* usBuf, int size)
+{
+ unsigned long usChksum = 0;
+ while (size > 1)
+ {
+ usChksum += *usBuf++;
+ size -= sizeof(quint16);
+ }
+
+ if (size)
+ {
+ usChksum += *(quint8*)usBuf;
+ }
+
+ usChksum = (usChksum >> 16) + (usChksum & 0xffff);
+ usChksum += (usChksum >> 16);
+
+ return quint8(~usChksum);
+}
+
+quint16 bit_reverse_word(quint16 value)
+{
+ const quint16 mask0 = 0x5555;
+ const quint16 mask1 = 0x3333;
+ const quint16 mask2 = 0x0F0F;
+ const quint16 mask3 = 0x00FF;
+
+ auto tmp = value;
+
+ tmp = (((~mask0) & tmp) >> 1) | ((mask0 & tmp) << 1);
+ tmp = (((~mask1) & tmp) >> 2) | ((mask1 & tmp) << 2);
+ tmp = (((~mask2) & tmp) >> 4) | ((mask2 & tmp) << 4);
+ tmp = (((~mask3) & tmp) >> 8) | ((mask3 & tmp) << 8);
+
+ return tmp;
+}
+
+quint8 reverse(quint8 b)
+{
+ b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
+ b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
+ b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
+ return b;
+}
+
+const QJsonObject point2json(const QPointF& point)
+{
+ return QJsonObject{{"x", point.x()}, {"y", point.y()}};
+}
+
+const QPointF pointFromJson(const QJsonObject& json)
+{
+ return QPointF(json["x"].toDouble(), json["y"].toDouble());
+}
+
+const QJsonObject line2json(const QLineF& line)
+{
+ return QJsonObject{
+ {"p1", point2json(line.p1())},
+ {"p2", point2json(line.p2())},
+ };
+}
+
+const QLineF lineFromJson(const QJsonObject& json)
+{
+ return QLineF(
+ pointFromJson(json["p1"].toObject()),
+ pointFromJson(json["p2"].toObject())
+ );
+}
+
+const QJsonValue real2Json(const qreal& real)
+{
+ return qIsNaN(real) ? QJsonValue(QJsonValue::Undefined) : real;
+}
+
+qreal realFromJson(const QJsonValue& json)
+{
+ return json.toDouble(std::numeric_limits<qreal>::quiet_NaN());
+}
+
+uint qHash(const QVector3D& v) noexcept
+{
+ return qHash(v.x() + v.y() + v.z(), 0xa03f); // arbitrary value
+}
diff --git a/basic_functions.h b/basic_functions.h
new file mode 100644
index 0000000..be58d0d
--- /dev/null
+++ b/basic_functions.h
@@ -0,0 +1,92 @@
+#pragma once
+
+// cpp
+#include <limits>
+
+// qt
+#include <QHash>
+#include <QHostAddress>
+#include <QJsonObject>
+#include <QLineF>
+#include <QMetaEnum>
+#include <QNetworkInterface>
+#include <QPointF>
+#include <QVector3D>
+
+quint16 CalculateChecksum(quint16* usBuf, int size);
+
+quint16 bit_reverse_word(quint16 value);
+
+quint8 reverse(quint8 b);
+
+const QJsonObject point2json(const QPointF& point);
+
+const QPointF pointFromJson(const QJsonObject& json);
+
+const QJsonObject line2json(const QLineF& line);
+
+const QLineF lineFromJson(const QJsonObject& json);
+
+const QJsonValue real2Json(const qreal& real);
+
+qreal realFromJson(const QJsonValue& json);
+
+template <typename T> static const QString enumToString(const T& value)
+{
+ return QMetaEnum::fromType<T>().valueToKey(value);
+}
+
+template <typename T> static const T stringToEnum(const QString& name)
+{
+ return T(QMetaEnum::fromType<T>().keyToValue(name.toStdString().c_str()));
+}
+
+Q_DECL_CONST_FUNCTION uint qHash(const QVector3D& v) noexcept;
+
+template <typename T> static constexpr T min()
+{
+ return std::numeric_limits<T>::min();
+}
+
+template <typename T> static constexpr T max()
+{
+ return std::numeric_limits<T>::max();
+}
+
+template <typename T> static constexpr T lowest()
+{
+ return std::numeric_limits<T>::lowest();
+}
+
+#define INI_ENUM_FROM_JSON(type, name, default) \
+ m_##name(json.contains(#name) ? stringToEnum<type>(#name) : default)
+
+template <
+ typename T,
+ typename retT,
+ retT (T::*func)() const,
+ Qt::SortOrder order>
+Q_DECL_CONST_FUNCTION Q_DECL_CONSTEXPR inline bool comparator(
+ const T& a,
+ const T& b
+) Q_DECL_NOTHROW
+{
+ if constexpr (order == Qt::AscendingOrder)
+ {
+ return ((&a)->*func)() < ((&b)->*func)();
+ }
+ else
+ {
+ return ((&a)->*func)() > ((&b)->*func)();
+ }
+}
+
+bool copyDir(const QString& src, const QString& dst);
+
+/*!
+ * \brief getLocalNetworkIntefaceBySubnet - get local address from the same
+ * subnet as remote
+ * \param remote - remote address
+ * \return valid local address on success, QHostAddress() otherwise
+ */
+QHostAddress getLocalNetworkIntefaceBySubnet(const QHostAddress& remote);
diff --git a/g_object.cpp b/g_object.cpp
new file mode 100644
index 0000000..09cbc27
--- /dev/null
+++ b/g_object.cpp
@@ -0,0 +1,180 @@
+#include "g_object.h"
+
+// qt
+#include <QDataStream>
+#include <QLineF>
+#include <QPointF>
+
+G_Object::G_Object(QObject* parent)
+ : QObject(parent)
+{
+}
+
+G_Object::G_Object(const QJsonObject& json, QObject* parent)
+ : QObject(parent)
+{
+ // use G_Object static offset, not virtual
+ constructFromJson(json);
+}
+
+G_Object::G_Object(const QJsonValue& json, QObject* parent)
+ : QObject(parent)
+{
+ constructFromJson(json.toObject());
+}
+
+G_Object::G_Object(const G_Object& other, QObject* parent)
+ : QObject(parent)
+{
+ // NOTE: always G_Object's assignment operator will be called, not derived
+ *this = other;
+}
+
+G_Object& G_Object::operator=(const G_Object& other)
+{
+ // use G_Object static offset, not virtual
+ for (int i = staticMetaObject.propertyOffset();
+ i < metaObject()->propertyCount();
+ i++)
+ {
+ auto key = metaObject()->property(i).name();
+ auto value = metaObject()->property(i).read(this);
+ if (value != other.property(key))
+ {
+ setProperty(key, other.property(key));
+ }
+ }
+
+ return *this;
+}
+
+G_Object& G_Object::operator=(const QJsonObject& json)
+{
+ constructFromJson(json);
+
+ return *this;
+}
+
+G_Object& G_Object::operator=(const QJsonValue& json)
+{
+ constructFromJson(json.toObject());
+
+ return *this;
+}
+
+G_Object::operator const QJsonValue() const
+{
+ return this->operator const QJsonObject();
+}
+
+bool G_Object::operator==(const G_Object& other) const
+{
+ for (int i = staticMetaObject.propertyOffset();
+ i < metaObject()->propertyCount();
+ i++)
+ {
+ auto key = metaObject()->property(i).name();
+
+ if (property(key) != other.property(key))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool G_Object::constructFromJson(const QJsonObject& json)
+{
+ auto offset = staticMetaObject.propertyOffset();
+ auto properiesCount = metaObject()->propertyCount();
+
+ for (int i = offset; i < properiesCount; i++)
+ {
+ auto key = metaObject()->property(i).name();
+ auto type = metaObject()->property(i).userType();
+ auto jsonValue = json[key];
+ auto variantValue = jsonValue.toVariant();
+
+ if (variantValue.isNull())
+ {
+ if (type == QMetaType::Double)
+ {
+ setProperty(key, std::numeric_limits<double>::quiet_NaN());
+ }
+ else if (type == QMetaType::Float)
+ {
+ setProperty(key, std::numeric_limits<float>::quiet_NaN());
+ }
+ else
+ {
+ return false;
+ }
+
+ continue;
+ }
+
+ auto converted = variantValue.convert(type);
+
+ if (!converted)
+ {
+ qDebug() << Q_FUNC_INFO << "cannot convert" << key
+ << "property from type"
+ << QMetaType::typeName(variantValue.userType())
+ << "to type" << QMetaType::typeName(type);
+
+ return false;
+ }
+
+ setProperty(key, variantValue);
+ }
+
+ return true;
+}
+
+G_Object::operator const QJsonObject() const
+{
+ QJsonObject result;
+
+ // use G_Object static offset, not virtual
+ for (int i = staticMetaObject.propertyOffset();
+ i < metaObject()->propertyCount();
+ i++)
+ {
+ auto key = metaObject()->property(i).name();
+ auto type = metaObject()->property(i).userType();
+ auto value = metaObject()->property(i).read(this);
+
+ if (value.isNull())
+ {
+ continue;
+ }
+
+ auto converted = value.convert(type);
+
+ if (!converted)
+ {
+ qDebug() << "G_Object::operator const QJsonObject() const:"
+ << "cannot convert" << key << "property from type"
+ << QMetaType::typeName(value.userType()) << "to type"
+ << QMetaType::typeName(type);
+ }
+
+ result[key] = QJsonValue::fromVariant(value);
+ }
+
+ return result;
+}
+
+QDebug operator<<(QDebug d, const G_Object& o)
+{
+ d << o.metaObject()->className() << ":\n";
+
+ for (int i = 0; i < o.metaObject()->propertyCount(); i++)
+ {
+ d << "\t" << o.metaObject()->property(i).name() << ":\t"
+ << o.metaObject()->property(i).read(&o) << Qt::endl;
+ }
+
+ return d;
+}
diff --git a/g_object.h b/g_object.h
new file mode 100644
index 0000000..725fbc1
--- /dev/null
+++ b/g_object.h
@@ -0,0 +1,138 @@
+#pragma once
+
+// qt
+#include <QDebug>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QMetaProperty>
+#include <QObject>
+
+#define JSON_CONSTRUCTOR(base_class, derived_class) \
+public: \
+ Q_INVOKABLE derived_class( \
+ const QJsonObject& json, \
+ QObject* parent = nullptr \
+ ) \
+ : base_class(json, parent) \
+ { \
+ constructFromJson(json); \
+ }
+
+#define JSON_VALUE_CONSTRUCTOR(base_class, derived_class) \
+public: \
+ Q_INVOKABLE derived_class( \
+ const QJsonValue& json, \
+ QObject* parent = nullptr \
+ ) \
+ : base_class(json.toObject(), parent) \
+ { \
+ constructFromJson(json.toObject()); \
+ }
+
+#define JSON_ASSING_OPERATOR(base_class, derived_class) \
+public: \
+ Q_INVOKABLE derived_class& operator=(const QJsonObject& json) \
+ { \
+ constructFromJson(json); \
+ return *this; \
+ }
+
+#define JSON_VALUE_ASSING_OPERATOR(base_class, derived_class) \
+public: \
+ Q_INVOKABLE derived_class& operator=(const QJsonValue& json) \
+ { \
+ *this = json.toObject(); \
+ return *this; \
+ }
+
+#define COPY_CONSTRUCTOR(base_class, derived_class) \
+public: \
+ Q_INVOKABLE derived_class( \
+ const derived_class& other, \
+ QObject* parent = nullptr \
+ ) \
+ : base_class(other, parent) \
+ { \
+ *this = other; \
+ }
+
+#define ASSIGN_OPERATOR(base_class, derived_class) \
+public: \
+ derived_class& operator=(const derived_class& other) \
+ { \
+ base_class::operator=(other); \
+ return *this; \
+ }
+
+#define COMPARE_OPERATOR(base_class, derived_class) \
+public: \
+ bool operator==(const derived_class& other) const \
+ { \
+ if (!base_class::operator==(other)) \
+ return false; \
+ for (int i = metaObject()->propertyOffset(); \
+ i < metaObject()->propertyCount(); \
+ i++) \
+ { \
+ auto key = metaObject()->property(i).name(); \
+ \
+ if (property(key) != other.property(key)) \
+ { \
+ return false; \
+ } \
+ } \
+ \
+ return true; \
+ }
+
+#define JSON_VALUE_OPERATOR(base_class) \
+public: \
+ operator const QJsonValue() const \
+ { \
+ return (base_class::operator const QJsonObject()); \
+ }
+
+#define DEFAULT_DESTRUCTOR(classname) \
+public: \
+ Q_INVOKABLE ~classname() Q_DECL_OVERRIDE = default
+
+#define DEFAULT_CONSTRUCTOR(base_class, derived_class) \
+public: \
+ Q_INVOKABLE derived_class(QObject* parent = nullptr) \
+ : base_class(parent) \
+ { \
+ }
+
+#define G_OBJECT(base_class, derived_class) \
+ /*Q_OBJECT*/ \
+ JSON_CONSTRUCTOR(base_class, derived_class) \
+ JSON_VALUE_CONSTRUCTOR(base_class, derived_class) \
+ COPY_CONSTRUCTOR(base_class, derived_class)
+
+class G_Object : public QObject
+{
+ Q_OBJECT
+
+ // constructors
+public:
+ G_Object(QObject* parent = nullptr);
+
+ G_Object(const QJsonObject& json, QObject* parent = nullptr);
+ G_Object(const QJsonValue& json, QObject* parent = nullptr);
+
+ G_Object(const G_Object& other, QObject* parent = nullptr);
+
+ // operators
+public:
+ virtual G_Object& operator=(const G_Object& other);
+ virtual G_Object& operator=(const QJsonObject& json);
+ virtual G_Object& operator=(const QJsonValue& json);
+ virtual operator const QJsonObject() const;
+ virtual operator const QJsonValue() const;
+ virtual bool operator==(const G_Object& other) const;
+
+protected:
+ bool constructFromJson(const QJsonObject& json);
+};
+
+QDebug operator<<(QDebug d, const G_Object& o);
diff --git a/g_property.cpp b/g_property.cpp
new file mode 100644
index 0000000..a852081
--- /dev/null
+++ b/g_property.cpp
@@ -0,0 +1,29 @@
+#include "g_property.h"
+
+// qt
+#include <QCoreApplication>
+
+QSharedPointer<GlobalSettings> GlobalSettings::m_instance;
+
+QSharedPointer<GlobalSettings> GlobalSettings::instance()
+{
+ if (!m_instance)
+ m_instance.reset(new GlobalSettings(
+ QSettings::IniFormat,
+ QSettings::UserScope,
+ QCoreApplication::organizationName(),
+ QCoreApplication::applicationName()
+ ));
+
+ return m_instance;
+}
+
+GlobalSettings::GlobalSettings(
+ QSettings::Format format,
+ QSettings::Scope scope,
+ const QString& organization,
+ const QString& application
+)
+ : QSettings(format, scope, organization, application)
+{
+}
diff --git a/g_property.h b/g_property.h
new file mode 100644
index 0000000..fa743cd
--- /dev/null
+++ b/g_property.h
@@ -0,0 +1,420 @@
+#pragma once
+
+// qt
+#include <QCoreApplication>
+#include <QJsonArray>
+#include <QJsonObject>
+#include <QObject>
+#include <QQmlListProperty>
+#include <QSettings>
+#include <QSharedPointer>
+
+#ifndef STRINGIFY
+#define STRINGIFY(var) (#var)
+#endif
+
+class GlobalSettings;
+class GlobalSettings : public QSettings
+{
+ Q_OBJECT
+
+public:
+ static QSharedPointer<GlobalSettings> instance();
+
+private:
+ GlobalSettings(
+ GlobalSettings::Format format,
+ GlobalSettings::Scope scope,
+ const QString& organization,
+ const QString& application = QString()
+ );
+ static QSharedPointer<GlobalSettings> m_instance;
+};
+
+#define QML_DECLARATION(type, name) \
+protected: \
+ Q_PROPERTY( \
+ type name READ get_##name WRITE set_##name NOTIFY name##Changed FINAL \
+ )
+
+#define G_PROPERTY_DECL(type, name) \
+QML_DECLARATION(type, name) protected : type m_##name;
+
+#define G_PROPERTY_DECL_DEFAULT(type, name, default) \
+QML_DECLARATION(type, name) protected : type m_##name = default;
+
+#define G_PROPERTY_SIGNAL(type, name) \
+Q_SIGNALS: \
+ void name##Changed(); \
+ void name##Changed(type value)
+
+#define G_PROPERTY_GET(type, name) \
+public: \
+ Q_INVOKABLE virtual type& get_##name() \
+ { \
+ return m_##name; \
+ } \
+ Q_INVOKABLE virtual type get_##name() const \
+ { \
+ return m_##name; \
+ }
+
+#define G_PROPERTY_GET_DEBUG(type, name) \
+public: \
+ virtual type get_##name() const \
+ { \
+ qDebug() << "PROPERTY: GET" << #name << m_##name; \
+ return m_##name; \
+ }
+
+#define G_PROPERTY_SET(type, name) \
+public Q_SLOTS: \
+ virtual void set_##name(type a) \
+ { \
+ if (m_##name == a) \
+ return; \
+ m_##name = a; \
+ emit name##Changed(); \
+ emit name##Changed(m_##name); \
+ }
+
+#define G_PROPERTY_SET_DEBUG(type, name) \
+public Q_SLOTS: \
+ virtual void set_##name(type a) \
+ { \
+ DEBUG_INFO("PROPERTY: SET" << #name << a); \
+ m_##name = a; \
+ emit name##Changed(); \
+ emit name##Changed(m_##name); \
+ }
+
+#define G_PROPERTY(type, name) \
+ G_PROPERTY_DECL(type, name) \
+ G_PROPERTY_SET(type, name) \
+ G_PROPERTY_GET(type, name) \
+ G_PROPERTY_SIGNAL(type, name)
+
+#define G_PROPERTY_DEFAULT(type, name, default) \
+ G_PROPERTY_DECL_DEFAULT(type, name, default) \
+ G_PROPERTY_SET(type, name) \
+ G_PROPERTY_GET(type, name) \
+ G_PROPERTY_SIGNAL(type, name)
+
+#define G_PROPERTY_CUSTOM_SET(type, name) \
+ G_PROPERTY_DECL(type, name) \
+ G_PROPERTY_GET(type, name) \
+ G_PROPERTY_SIGNAL(type, name) \
+public Q_SLOTS: \
+ virtual void set_##name(type a);
+
+#define G_PROPERTY_DEBUG(type, name) \
+ G_PROPERTY_DECL(type, name) \
+ G_PROPERTY_SET_DEBUG(type, name) \
+ G_PROPERTY_GET_DEBUG(type, name) \
+ G_PROPERTY_SIGNAL(type, name)
+
+#define PROPERTY(type, name) \
+ G_PROPERTY_SET(type, name) \
+ G_PROPERTY_GET(type, name) \
+ G_PROPERTY_SIGNAL(type, name)
+
+#define G_PROPERTY_BASE(type, name) \
+ G_PROPERTY_DECL(type, name) \
+ G_PROPERTY_SIGNAL(type, name)
+
+#define G_PROPERTY_WRAP(type, name) \
+ QML_DECLARATION(type, name) \
+public Q_SLOTS: \
+ virtual void set_##name(type a) \
+ { \
+ *(type*)&name = a; \
+ emit name##Changed(); \
+ emit name##Changed(name); \
+ } \
+ \
+public: \
+ virtual type get_##name() const \
+ { \
+ return name; \
+ } \
+ G_PROPERTY_SIGNAL(type, name)
+
+#define INI_PROPERTY_SET(type, name) \
+public Q_SLOTS: \
+ virtual void set_##name(const type& a) \
+ { \
+ auto old = get_##name(); \
+ if (old == a) \
+ { \
+ return; \
+ } \
+ auto n = QString(this->staticMetaObject.className()) + "/" + #name; \
+ GlobalSettings::instance()->setValue(n, QVariant::fromValue(a)); \
+ GlobalSettings::instance()->sync(); \
+ emit name##Changed(); \
+ emit name##Changed(a); \
+ }
+
+#define INI_PROPERTY_GET(type, name, defaultValue) \
+public: \
+ virtual type get_##name() const \
+ { \
+ return static_cast<type>( \
+ GlobalSettings::instance() \
+ ->value( \
+ QString(this->staticMetaObject.className()) + "/" + #name, \
+ defaultValue \
+ ) \
+ .value<type>() \
+ ); \
+ }
+
+#define INI_PROPERTY_GET_CAST(type, name, defaultValue) \
+public: \
+ virtual type get_##name() const \
+ { \
+ return GlobalSettings::instance()->value( \
+ QString(this->staticMetaObject.className()) + "/" + #name, \
+ defaultValue \
+ ); \
+ }
+
+#define INI_PROPERTY_GET_VAR(type, name, defaultValue) \
+public: \
+ virtual type get_##name() \
+ { \
+ return GlobalSettings::instance() \
+ ->value( \
+ QString(this->staticMetaObject.className()) + "/" + #name, \
+ QVariant::fromValue(defaultValue) \
+ ) \
+ .value<type>(); \
+ }
+
+#define INI_PROPERTY(type, name, defaultValue) \
+ INI_PROPERTY_SET(type, name) \
+ INI_PROPERTY_GET(type, name, defaultValue) \
+ QML_DECLARATION(type, name) \
+ G_PROPERTY_SIGNAL(type, name)
+
+#define INI_PROPERTY_CAST(type, name, defaultValue) \
+ INI_PROPERTY_SET(type, name) \
+ INI_PROPERTY_GET_CAST(type, name, defaultValue) \
+ QML_DECLARATION(type, name) \
+ G_PROPERTY_SIGNAL(type, name)
+
+#define INI_PROPERTY_VAR(type, name, defaultValue) \
+ INI_PROPERTY_SET(type, name) \
+ INI_PROPERTY_GET_VAR(type, name, defaultValue) \
+ QML_DECLARATION(type, name) \
+ G_PROPERTY_SIGNAL(type, name)
+
+#define INI_PROPERTY_DEBUG(name) \
+ INI_PROPERTY_SET(name) \
+ INI_PROPERTY_GET(name) \
+ QML_DECLARATION(type, name) \
+ G_PROPERTY_SIGNAL(type, name)
+
+#define RO_PROPERTY(type, name, value) \
+protected: \
+ Q_PROPERTY(type name READ get_##name NOTIFY name##Changed) \
+public: \
+ inline bool get_##name() \
+ { \
+ return m_##value; \
+ } \
+Q_SIGNALS: \
+ void name##Changed(); \
+ \
+protected: \
+ type m_##name;
+
+#define STATIC_PROPERTY(type, name) \
+public: \
+ static inline type get_##name() \
+ { \
+ return m_##name; \
+ } \
+ static inline void set_##name(type value) \
+ { \
+ m_##name = value; \
+ } \
+ \
+private: \
+ static type m_##name
+
+// there must be QJsonObject json in scope;
+// TODO: use QVariant::value()
+#define READ_FROM_JSON(type, name, default) \
+ set_##name( \
+ json[#name] == QJsonValue::Undefined \
+ ? default \
+ : json[#name].toVariant().value<type>() \
+ )
+
+#define INIT_FROM_JSON(type, var, default) \
+ m_##var( \
+ (json.contains(#var)) \
+ ? (json[STRINGIFY(var)].toVariant().value<type>()) \
+ : (default) \
+ )
+
+#define INIT_OBJECT_JSON(var, default) \
+ m_##var((json.contains(#var)) ? (json[STRINGIFY(var)]) : (default))
+
+// for QJsonObject(std::initializer_list<QPair<QString, QJsonValue> > args)
+// TODO: make type first argument
+#define WRITE_TO_JSON(var, cast) (#var), cast(m_##var)
+
+#define WRITE_ENUM_TO_JSON(var) (#var), QVariant(m_##var).toJsonValue()
+
+#define WRITE_ENUM_KEY_TO_JSON(var) \
+ (#var), \
+ QString( \
+ QMetaEnum::fromType<typeof(m_##var)>().valueToKey(int(m_##var)) \
+ )
+
+#define WRITE_VARIANT_TO_JSON(var) (#var), QJsonValue::fromVariant(m_##var)
+
+#define WRITE_ARRAY_TO_JSON(var) (#var), QJsonArray(m_##var)
+
+#define WRITE_STRINGLIST_TO_JSON(var) \
+ (#var), QJsonArray::fromStringList(m_##var)
+
+#define WRITE_TO_JSON_NO_CAST(var) (#var), (m_##var)
+
+#define WRITE_PTR_TO_JSON(var) (#var), QJsonObject(*m_##var)
+
+// without 's' in the end of name please
+#define G_QML_LIST_PROPERTY(classname, type, name) \
+protected: \
+ Q_PROPERTY( \
+ QQmlListProperty<type> name##s READ name##s NOTIFY name##sChanged \
+ ) \
+public: \
+ QQmlListProperty<type> name##s() \
+ { \
+ return QQmlListProperty<type>( \
+ this, \
+ this, \
+ &classname ::append_##name, \
+ &classname ::name##sCount, \
+ &classname ::name, \
+ &classname ::clear_##name##s \
+ ); \
+ } \
+ void append_##name(type* item) \
+ { \
+ m_##name##s.append(item); \
+ emit name##sChanged(); \
+ emit name##sChanged(m_##name##s); \
+ } \
+ int name##sCount() const \
+ { \
+ return m_##name##s.count(); \
+ } \
+ type* name(int index) const \
+ { \
+ return m_##name##s.at(index); \
+ } \
+ void clear_##name##s() \
+ { \
+ while (!m_##name##s.isEmpty()) \
+ { \
+ auto tmp = m_##name##s.takeLast(); \
+ if (tmp->parent() == this) \
+ delete tmp; \
+ } \
+ emit name##sChanged(); \
+ emit name##sChanged(m_##name##s); \
+ } \
+ inline QList<type*>& get_##name##s() \
+ { \
+ return m_##name##s; \
+ } \
+Q_SIGNALS: \
+ void name##sChanged(); \
+ \
+protected: \
+ static void append_##name(QQmlListProperty<type>* list, type* item) \
+ { \
+ reinterpret_cast<classname*>(list->data)->append_##name(item); \
+ } \
+ static int name##sCount(QQmlListProperty<type>* list) \
+ { \
+ return reinterpret_cast<classname*>(list->data)->name##sCount(); \
+ } \
+ static type* name(QQmlListProperty<type>* list, int index) \
+ { \
+ return reinterpret_cast<classname*>(list->data)->name(index); \
+ } \
+ static void clear_##name##s(QQmlListProperty<type>* list) \
+ { \
+ reinterpret_cast<classname*>(list->data)->clear_##name##s(); \
+ } \
+ QList<type*> m_##name##s
+
+#define INIT_FIELD(name) m_##name(name)
+
+#define INIT_FROM_OTHER(name) m_##name(other.m_##name)
+
+#define SET_FROM_OTHER(name) set_##name(other.m_##name)
+
+#define SET_FROM_OTHER_PTR(name) \
+ *m_##name = *other.m_##name; \
+ emit name##Changed(); \
+ emit name##Changed(m_##name)
+
+#define SET_FROM_JSON(type, name, default) \
+ set_##name( \
+ json.contains(#name) ? json[#name].toVariant().value<type>() : default \
+ )
+
+#define SET_FROM_JSON_ENUM_KEY(name, default) \
+ set_##name( \
+ json.contains(#name) \
+ ? (typeof(m_##name))(QMetaEnum::fromType<typeof(m_##name)>() \
+ .keyToValue( \
+ json[#name] \
+ .toString() \
+ .toStdString() \
+ .c_str() \
+ )) \
+ : default \
+ )
+
+#define COMPARE_WITH_OTHER(name) (get_##name() == other.get_##name())
+
+template <class T> static QJsonArray listToJson(const QList<T*>& list)
+{
+ QJsonArray result;
+
+ for (const auto& item : list)
+ {
+ result << QJsonValue(*item);
+ }
+
+ return result;
+}
+
+template <class T>
+static QList<T*> listFromJson(
+ const QJsonArray& array,
+ QObject* parent = nullptr
+)
+{
+ QList<T*> result;
+
+ for (const auto& jsonItem : array)
+ {
+ result << new T(jsonItem.toObject(), parent);
+ }
+
+ return result;
+}
+
+#define INIT_LIST_FROM_JSON(listName, className, parent) \
+ m_##listName(listFromJson<className>(json[#listName].toArray(), parent))
+
+#define WRITE_LIST_TO_JSON(listName, className) \
+ #listName, listToJson<className>(m_##listName)
diff --git a/inisingleton.h b/inisingleton.h
new file mode 100644
index 0000000..6650c11
--- /dev/null
+++ b/inisingleton.h
@@ -0,0 +1,51 @@
+#pragma once
+
+// qt
+#include <QMutex>
+#include <QVariant>
+
+// goodies
+#include "g_property.h"
+
+#define INI_SINGLETON(type) IniSingleton<type>::get()
+
+template <class T> class IniSingleton
+{
+public:
+ static T get()
+ {
+ static QMutex mtx;
+ QMutexLocker l(&mtx);
+
+ auto variant =
+ GlobalSettings::instance()->value(T::staticMetaObject.className());
+ qDebug() << "classname" << T::staticMetaObject.className();
+ qDebug() << "VARIANT" << variant;
+
+ return T(variant);
+ }
+
+ static void set(const T& value)
+ {
+ static QMutex mtx;
+ QMutexLocker l(&mtx);
+
+ GlobalSettings::instance()->setValue(
+ T::staticMetaObject.className(),
+ QVariant(value)
+ );
+ GlobalSettings::instance()->sync();
+ }
+
+ static void cleanup()
+ {
+ static QMutex mtx;
+ QMutexLocker l(&mtx);
+
+ GlobalSettings::instance()->setValue(
+ T::staticMetaObject.className(),
+ QVariant()
+ );
+ GlobalSettings::instance()->sync();
+ }
+};
diff --git a/jsonobjectfilesingleton.h b/jsonobjectfilesingleton.h
new file mode 100644
index 0000000..f11b30a
--- /dev/null
+++ b/jsonobjectfilesingleton.h
@@ -0,0 +1,169 @@
+#pragma once
+
+// qt
+#include <QDebug>
+#include <QDir>
+#include <QFile>
+#include <QFileSystemWatcher>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QMutex>
+#include <QSharedPointer>
+#include <QStandardPaths>
+#include <QVariant>
+
+#define JSON_OBJECT_FILE(classname) JsonObjectFile<classname>::get().data()
+
+template <class T> class JsonObjectFile
+{
+public:
+ static QSharedPointer<T> get()
+ {
+ static QMutex mtx;
+ QMutexLocker l(&mtx);
+
+ if (!m_instance)
+ {
+ m_instance = read();
+
+ qDebug() << "add filepath" << filepath();
+ if (!m_watcher.addPath(filepath()))
+ {
+ qt_noop();
+ }
+ qDebug() << m_watcher.directories() << m_watcher.files();
+
+ QObject::connect(
+ &m_watcher,
+ &QFileSystemWatcher::directoryChanged,
+ &m_watcher,
+ [&](const QString& path) {
+ qDebug() << filepath() << "changed";
+ if (path == filename())
+ {
+ qDebug() << "file changed:" << filename();
+ *get() = *read();
+ }
+ },
+ Qt::DirectConnection
+ );
+ }
+
+ return m_instance;
+ }
+
+ static QSharedPointer<T> read(const QString fileName = QString())
+ {
+ static QMutex mtx;
+ QMutexLocker l(&mtx);
+
+ if (!QDir(filepath()).exists())
+ {
+ QDir().mkpath(filepath());
+ }
+
+ QFile f(!fileName.isEmpty() ? fileName : filename());
+
+ if (!f.open(QFile::ReadOnly))
+ {
+ qDebug() << Q_FUNC_INFO << ":"
+ << "cannot open file" << f.fileName() << ":\n"
+ << f.errorString();
+
+ return QSharedPointer<T>();
+ }
+
+ auto data = f.readAll();
+ QJsonValue json = QJsonDocument::fromJson(data).object();
+ return QSharedPointer<T>(
+ new T(json[T::staticMetaObject.className()]),
+ &T::deleteLater
+ );
+ }
+
+ static bool write(
+ const QSharedPointer<T>& value,
+ const QString fileName = QString()
+ )
+ {
+ static QMutex mtx;
+ QMutexLocker l(&mtx);
+
+ QFile f(!fileName.isEmpty() ? fileName : filename());
+
+ if (!QDir(filepath()).exists())
+ {
+ QDir().mkpath(filepath());
+ }
+
+ // bool fileExists = f.exists();
+
+ if (!f.open(QFile::WriteOnly))
+ {
+ qDebug() << Q_FUNC_INFO << ":"
+ << "cannot open file" << f.fileName() << ":\n"
+ << f.errorString();
+
+ return false;
+ }
+
+ const auto json =
+ QJsonObject{{T::staticMetaObject.className(), *value}};
+
+ f.write(QJsonDocument(json).toJson());
+ f.flush();
+
+ m_instance = read();
+
+ return true;
+ }
+
+ static bool remove(const QString fileName = QString())
+ {
+ static QMutex mtx;
+ QMutexLocker l(&mtx);
+
+ QFile f(!fileName.isEmpty() ? fileName : filename());
+
+ if (!f.remove())
+ {
+ qDebug() << Q_FUNC_INFO << ":"
+ << "cannot delete file" << f.fileName() << ":\n"
+ << f.errorString();
+
+ return false;
+ }
+
+ return true;
+ }
+
+ static QString filepath()
+ {
+ return QStandardPaths::writableLocation(
+ QStandardPaths::AppConfigLocation
+ );
+ }
+
+ static QString filename()
+ {
+ return filepath() + QDir::separator() +
+ T::staticMetaObject.className() + "." + extension;
+ }
+
+ typedef QSharedPointer<T> FactorySharedPointer;
+
+public:
+ static const QString extension;
+
+private:
+ static FactorySharedPointer m_instance;
+ static QFileSystemWatcher m_watcher;
+};
+
+template <typename T>
+QString const JsonObjectFile<T>::extension = QStringLiteral("json");
+
+template <class T>
+typename JsonObjectFile<T>::FactorySharedPointer JsonObjectFile<T>::m_instance;
+
+template <class T> QFileSystemWatcher JsonObjectFile<T>::m_watcher;
diff --git a/keyvalue.cpp b/keyvalue.cpp
new file mode 100644
index 0000000..1dcaa92
--- /dev/null
+++ b/keyvalue.cpp
@@ -0,0 +1,8 @@
+#include "keyvalue.h"
+
+KeyValue::KeyValue(const QVariant& key, const QVariant& value, QObject* parent)
+ : G_Object(parent)
+ , INIT_FIELD(key)
+ , INIT_FIELD(value)
+{
+}
diff --git a/keyvalue.h b/keyvalue.h
new file mode 100644
index 0000000..b5b7b79
--- /dev/null
+++ b/keyvalue.h
@@ -0,0 +1,26 @@
+#pragma once
+
+// goodies
+#include "g_object.h"
+#include "g_property.h"
+
+class Q_DECL_EXPORT KeyValue : public G_Object
+{
+ Q_OBJECT
+
+ G_PROPERTY(QVariant, key);
+ G_PROPERTY(QVariant, value);
+
+public:
+ explicit KeyValue(
+ const QVariant& key = "",
+ const QVariant& value = "",
+ QObject* parent = nullptr
+ );
+
+ G_OBJECT(G_Object, KeyValue)
+ ASSIGN_OPERATOR(G_Object, KeyValue)
+};
+
+Q_DECLARE_METATYPE(KeyValue)
+Q_DECLARE_METATYPE(QVector<KeyValue>)
diff --git a/logworker.cpp b/logworker.cpp
new file mode 100644
index 0000000..b165e4c
--- /dev/null
+++ b/logworker.cpp
@@ -0,0 +1,273 @@
+#include "logworker.h"
+
+// qt
+#include <QFile>
+#include <QMetaEnum>
+#include <QMutex>
+
+// cpp
+#include <iostream>
+
+// goodies
+#include "singleton.h"
+
+#define REGISTER_QT_MSG_TYPE(type) \
+ {QtMsgType::type, QString(#type).replace("Qt", "").replace("Msg", "")}
+
+namespace goodies
+{
+
+// TODO: to avoid errors replace me via magic_enum lib or any other compile-time
+// stuff when qt-distributed mingw will support it
+static QHash<QtMsgType, QString> qtMsgTypes = {
+ REGISTER_QT_MSG_TYPE(QtDebugMsg),
+ REGISTER_QT_MSG_TYPE(QtWarningMsg),
+ REGISTER_QT_MSG_TYPE(QtCriticalMsg),
+ REGISTER_QT_MSG_TYPE(QtFatalMsg),
+ REGISTER_QT_MSG_TYPE(QtInfoMsg)
+};
+
+QString appLogFileName()
+{
+ QString path =
+ QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) +
+ QDir::separator() + "logs";
+
+ QDir dir(path);
+
+ if (!dir.exists())
+ {
+ if (!dir.mkpath(path))
+ {
+ // we'll get recursion here by using qWarning or some other
+ // QMessageLogger
+ std::cerr << "cannot create dir: " << path.toStdString()
+ << std::endl;
+ }
+ }
+
+ QString filename =
+ QDateTime::currentDateTime().toString("yyyy.MM.dd_hh.mm.ss.zzz") +
+ ".log";
+
+ return path + QDir::separator() + filename;
+}
+
+void myMessageHandler(
+ QtMsgType type,
+ const QMessageLogContext& context,
+ const QString& msg
+)
+{
+ goodies::defaultMessageHandler(type, context, msg);
+ // std::cout << msg.toStdString() << std::endl;
+ SINGLETON(LogWorker)->writeMessage(type, context, msg);
+}
+
+LogMsg::LogMsg(
+ QDateTime dateTime,
+ MessageType messageType,
+ QString message,
+ QObject* parent
+)
+ : G_Object(parent)
+ , INIT_FIELD(dateTime)
+ , INIT_FIELD(messageType)
+ , INIT_FIELD(message)
+{
+}
+
+LogWorker::LogWorker(const QString filepath)
+ : QAbstractListModel(nullptr)
+ , INIT_FIELD(filepath)
+ , m_file(filepath)
+ , m_fileStream(&m_file)
+{
+ QMutexLocker l(&m_mtx);
+
+ auto onIsLoggingEnabledChanged =
+ [&]() {
+ if (get_isLoggingEnabled())
+ {
+ if (!m_file.open(QFile::ReadWrite | QFile::Append))
+ {
+ qWarning()
+ << tr("cannot open log file for reading and writing: ")
+ << m_file.fileName();
+ set_isLoggingEnabled(false);
+ return;
+ }
+
+ qDebug() << "log file opened reading and writing";
+ }
+ else
+ {
+ if (!m_file.open(QFile::ReadOnly))
+ {
+ qWarning() << tr("cannot open log file for reading: ")
+ << m_file.fileName();
+ }
+
+ qDebug() << "log file opened for reading";
+ }
+ };
+
+ connect(
+ this,
+ qOverload<>(&LogWorker::isLoggingEnabledChanged),
+ onIsLoggingEnabledChanged
+ );
+ onIsLoggingEnabledChanged();
+}
+
+LogWorker::~LogWorker()
+{
+ QMutexLocker l(&m_mtx);
+}
+
+QString LogWorker::filepath() const
+{
+ return m_filepath;
+}
+
+void LogWorker::initialize(QApplication& app)
+{
+ // LogWorker uses app info to open log file
+ Q_UNUSED(app)
+ SINGLETON(LogWorker);
+ goodies::defaultMessageHandler = qInstallMessageHandler(myMessageHandler);
+}
+
+// LogWorker &LogWorker::operator<<(const QString &msg)
+//{
+// QMutexLocker l(&m_mtx);
+
+// if(!m_file.isOpen())
+// return *this;
+
+// auto date =
+// QDateTime::currentDateTime().toString("yyyy.MM.dd_hh.mm.ss.zzz");
+
+// auto m = date + ": " + msg;
+
+// m_fileStream << m << Qt::endl << Qt::flush;
+// beginInsertRows(QModelIndex(), m_messages.count(), m_messages.count());
+// m_messages << m;
+// endInsertRows();
+
+// auto delta = m_messages.count() - get_maxLinesCount();
+
+// if(get_maxLinesCount() > 0 && delta > 0)
+// {
+// beginRemoveRows(QModelIndex(), 0, delta - 1);
+// m_messages.erase(m_messages.begin(),
+// m_messages.begin() + delta);
+// endRemoveRows();
+// }
+
+// return *this;
+//}
+
+// void LogWorker::writeMessage(QString msg)
+//{
+// (*this) << msg;
+// }
+
+void LogWorker::writeMessage(
+ QtMsgType type,
+ const QMessageLogContext& context,
+ const QString& msg
+)
+{
+ QMutexLocker l(&m_mtx);
+
+ // extract filename from filepath (not tested on windows \\\\\\\ )
+ const char* file = context.file ? FILENAME(context.file) : "";
+ // convert QtSomethingMsg to Something
+ // auto typeName =
+ // QString::fromStdString(std::string(magic_enum::enum_name(type)))
+ // .replace("Qt", "").replace("Msg", "");
+ auto typeName = qtMsgTypes[type];
+ auto newLineOrSpace = msg.contains("\n") ? "\n" : " ";
+
+ if (!m_file.isOpen())
+ return;
+
+ // auto date =
+ // QDateTime::currentDateTime().toString("yyyy.MM.dd_hh.mm.ss.zzz");
+ auto dt = QDateTime::currentDateTime();
+
+ auto m = QString("%1:%2: %3: %4%5")
+ .arg(file)
+ .arg(context.line)
+ .arg(typeName)
+ .arg(newLineOrSpace)
+ .arg(msg);
+
+ auto full_m = dt.toString(Qt::ISODateWithMs) + ": " + m;
+
+ m_fileStream << full_m << Qt::endl << Qt::flush;
+
+ beginInsertRows(QModelIndex(), m_messages.count(), m_messages.count());
+ m_messages << LogMsg(dt, LogMsg::MessageType(type), msg);
+ endInsertRows();
+
+ auto delta = m_messages.count() - get_maxLinesCount();
+
+ if (get_maxLinesCount() > 0 && delta > 0)
+ {
+ beginRemoveRows(QModelIndex(), 0, delta - 1);
+ m_messages.erase(m_messages.begin(), m_messages.begin() + delta);
+ endRemoveRows();
+ }
+
+ // writeMessage(QString("%1:%2: %3:%4%5")
+ // .arg(file)
+ // .arg(context.line)
+ // .arg(typeName)
+ // .arg(newLineOrSpace)
+ // .arg(msg));
+}
+
+int LogWorker::rowCount(const QModelIndex& parent) const
+{
+ Q_UNUSED(parent)
+
+ return m_messages.count();
+}
+
+QVariant LogWorker::data(const QModelIndex& index, int role) const
+{
+ if (!index.isValid())
+ return QVariant();
+
+ // std::cout << Q_FUNC_INFO << std::endl;
+ if (role >= Qt::UserRole)
+ {
+ auto m = m_messages.at(index.row());
+
+ switch (role)
+ {
+ case Date:
+ return m.get_dateTime().toUTC();
+ break;
+ case MsgType:
+ return m.get_messageType();
+ break;
+ case Msg:
+ return m.get_message();
+ break;
+ default:
+ break;
+ }
+ return m_messages.at(index.row()).get_message();
+ }
+
+ return QVariant();
+}
+
+LogWorker::LogWorker(const LogWorker& other)
+{
+ Q_ASSERT_X(false, Q_FUNC_INFO, "don't call me");
+}
+} // namespace goodies
diff --git a/logworker.h b/logworker.h
new file mode 100644
index 0000000..dc7c3fb
--- /dev/null
+++ b/logworker.h
@@ -0,0 +1,137 @@
+#pragma once
+
+// qt
+#include <QAbstractListModel>
+#include <QApplication>
+#include <QDebug>
+#include <QDir>
+#include <QFile>
+#include <QMutex>
+#include <QStandardPaths>
+#include <QTextStream>
+
+// goodies
+#include "g_object.h"
+#include "g_property.h"
+
+#define FILENAME(fn) (strrchr(fn, '/') ? strrchr(fn, '/') + 1 : fn)
+#define WRAP_ENUM(ext_enum, field) field = ext_enum::field
+
+namespace goodies
+{
+
+static QtMessageHandler defaultMessageHandler;
+
+QString appLogFileName();
+void myMessageHandler(
+ QtMsgType type,
+ const QMessageLogContext& context,
+ const QString& msg
+);
+
+class LogMsg : public G_Object
+{
+ Q_OBJECT
+ G_OBJECT(G_Object, LogMsg)
+
+public:
+ enum MessageType
+ {
+ QtDebugMsg = QtMsgType::QtDebugMsg,
+ QtWarningMsg = QtMsgType::QtWarningMsg,
+ QtCriticalMsg = QtMsgType::QtCriticalMsg,
+ QtFatalMsg = QtMsgType::QtFatalMsg,
+ QtInfoMsg = QtMsgType::QtInfoMsg
+ };
+ Q_ENUM(MessageType)
+
+ G_PROPERTY(QDateTime, dateTime);
+ G_PROPERTY(MessageType, messageType);
+ G_PROPERTY(QString, message);
+
+public:
+ LogMsg(
+ QDateTime dateTime,
+ MessageType messageType,
+ QString message,
+ QObject* parent = nullptr
+ );
+};
+
+class LogWorker : public QAbstractListModel
+{
+ Q_OBJECT
+
+ INI_PROPERTY(bool, isLoggingEnabled, true);
+ INI_PROPERTY(int, maxLinesCount, 10 * 1000);
+
+public:
+ enum LogWorkerRoles
+ {
+ Date = Qt::UserRole + 1,
+ MsgType,
+ Msg
+ };
+ Q_ENUM(LogWorkerRoles)
+
+public:
+ Q_INVOKABLE LogWorker(const QString filepath = appLogFileName());
+ Q_INVOKABLE ~LogWorker();
+
+public:
+ Q_INVOKABLE QString filepath() const;
+ static void initialize(QApplication& app);
+
+private:
+ friend void myMessageHandler(
+ QtMsgType type,
+ const QMessageLogContext& context,
+ const QString& msg
+ );
+
+public:
+ Q_INVOKABLE void writeMessage(
+ QtMsgType type,
+ const QMessageLogContext& context,
+ const QString& msg
+ );
+
+private:
+ QString m_filepath;
+ QFile m_file;
+ QTextStream m_fileStream;
+ QMutex m_mtx;
+ QVector<LogMsg> m_messages;
+
+ // QAbstractListModel stuff
+public:
+ Q_INVOKABLE int rowCount(
+ const QModelIndex& parent = QModelIndex()
+ ) const Q_DECL_OVERRIDE;
+ Q_INVOKABLE QVariant data(
+ const QModelIndex& index,
+ int role = Qt::DisplayRole
+ ) const Q_DECL_OVERRIDE;
+
+protected:
+ inline QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE
+ {
+ return m_roleNames;
+ }
+
+protected:
+ QHash<int, QByteArray> m_roleNames = {
+ {int(LogWorkerRoles::Date), "date"},
+ {int(LogWorkerRoles::MsgType), "msg_type"},
+ {int(LogWorkerRoles::Msg), "msg"}
+ };
+
+ // Q_REGISTER_METATYPE stuff
+public:
+ LogWorker(const LogWorker& other);
+};
+
+} // namespace goodies
+
+Q_DECLARE_METATYPE(goodies::LogWorker)
+Q_DECLARE_METATYPE(goodies::LogMsg)
diff --git a/serializablelist.cpp b/serializablelist.cpp
new file mode 100644
index 0000000..6661d0f
--- /dev/null
+++ b/serializablelist.cpp
@@ -0,0 +1 @@
+#include "serializablelist.h"
diff --git a/serializablelist.h b/serializablelist.h
new file mode 100644
index 0000000..1628840
--- /dev/null
+++ b/serializablelist.h
@@ -0,0 +1,243 @@
+#pragma once
+
+// qt
+#include <QJsonArray>
+#include <QJsonObject>
+#include <QJsonValue>
+#include <QList>
+#include <QVariant>
+
+#define INIT_ARRAY_FIELD_FROM_JSON(field) m_##field(json[#field].toArray())
+
+template <typename T>
+/*!
+ * \brief The SerializableList class - (de)serializable (from)to json QList
+ * \warning only use this class with basic types or basic G_Object-classes.
+ * don't use pointers as T
+ */
+class SerializableList : public QList<T>
+{
+public:
+ SerializableList() = default;
+ SerializableList(const QJsonArray& json)
+ : QList<T>()
+ {
+ *this = json;
+ }
+ SerializableList(const QList<T>& other)
+ : QList<T>(other)
+ {
+ }
+ SerializableList(const QJsonObject& json)
+ : SerializableList(QJsonValue(json).toArray())
+ {
+ }
+ SerializableList(const QJsonValue& json)
+ : SerializableList(json.toArray())
+ {
+ }
+ SerializableList(std::initializer_list<T> l)
+ : QList<T>(l)
+ {
+ }
+
+public:
+ SerializableList& operator=(const QJsonArray& json)
+ {
+ for (const auto& item : json)
+ {
+ // *this << item.toVariant().value<T>();
+ *this << T(item);
+ }
+
+ return *this;
+ }
+
+ SerializableList& operator=(const QJsonObject& json)
+ {
+ return *this = QJsonValue(json);
+ }
+
+ SerializableList& operator=(const QJsonValue& json)
+ {
+ return *this = json.toArray();
+ }
+
+ virtual operator QJsonArray() const
+ {
+ QJsonArray array;
+
+ for (const auto& item : *this)
+ {
+ array << item;
+ }
+
+ return array;
+ }
+
+ virtual operator QJsonValue() const
+ {
+ return QJsonValue(QJsonArray(*this));
+ }
+
+ virtual operator QJsonObject() const
+ {
+ return *this;
+ }
+};
+
+template <typename T> class SerializableVectorBasic : public QVector<T>
+{
+public:
+ SerializableVectorBasic() = default;
+ SerializableVectorBasic(const QJsonArray& json)
+ : QVector<T>()
+ {
+ *this = json;
+ }
+ SerializableVectorBasic(const QVector<T>& other)
+ : QVector<T>(other)
+ {
+ }
+ SerializableVectorBasic(const QJsonObject& json)
+ : SerializableVectorBasic(QJsonValue(json).toArray())
+ {
+ }
+ SerializableVectorBasic(const QJsonValue& json)
+ : SerializableVectorBasic(json.toArray())
+ {
+ }
+ SerializableVectorBasic(std::initializer_list<T> l)
+ : QVector<T>(l)
+ {
+ }
+
+public:
+ SerializableVectorBasic& operator=(const QJsonArray& json)
+ {
+ for (const auto& item : json)
+ {
+ *this << item.toVariant().value<T>();
+ // *this << T(item);
+ }
+
+ return *this;
+ }
+
+ SerializableVectorBasic& operator=(const QJsonObject& json)
+ {
+ return *this = QJsonValue(json);
+ }
+
+ SerializableVectorBasic& operator=(const QJsonValue& json)
+ {
+ return *this = json.toArray();
+ }
+
+ virtual operator QJsonArray() const
+ {
+ QJsonArray array;
+
+ for (const auto& item : *this)
+ {
+ // auto v = QJsonValue::fromVariant(item);
+ // array << QJsonValue(item);
+ array << QJsonValue::fromVariant(item);
+ }
+
+ return array;
+ }
+
+ virtual operator QJsonValue() const
+ {
+ return QJsonValue(QJsonArray(*this));
+ }
+
+ virtual operator QJsonObject() const
+ {
+ return *this;
+ }
+};
+
+template <typename T> class SerializableVector : public QVector<T>
+{
+public:
+ SerializableVector() = default;
+ SerializableVector(const QJsonArray& json)
+ : QVector<T>()
+ {
+ *this = json;
+ }
+ SerializableVector(const QVector<T>& other)
+ : QVector<T>(other)
+ {
+ }
+ SerializableVector(const QJsonObject& json)
+ : SerializableVector(QJsonValue(json).toArray())
+ {
+ }
+ SerializableVector(const QJsonValue& json)
+ : SerializableVector(json.toArray())
+ {
+ }
+ SerializableVector(const QVariant& variant)
+ : SerializableVector(variant.toJsonArray())
+ {
+ }
+ SerializableVector(std::initializer_list<T> l)
+ : QVector<T>(l)
+ {
+ }
+
+public:
+ SerializableVector& operator=(const QJsonArray& json)
+ {
+ for (const auto& item : json)
+ {
+ // *this << item.toVariant().value<T>();
+ *this << T(item);
+ }
+
+ return *this;
+ }
+
+ SerializableVector& operator=(const QJsonObject& json)
+ {
+ return *this = QJsonValue(json);
+ }
+
+ SerializableVector& operator=(const QJsonValue& json)
+ {
+ return *this = json.toArray();
+ }
+
+ virtual operator QJsonArray() const
+ {
+ QJsonArray array;
+
+ for (const auto& item : *this)
+ {
+ // auto v = QJsonValue::fromVariant(item);
+ array << item;
+ // array << QJsonValue::fromVariant(item);
+ qt_noop();
+ }
+
+ return array;
+ }
+
+ virtual operator QJsonValue() const
+ {
+ return QJsonValue(QJsonArray(*this));
+ }
+
+ virtual operator QJsonObject() const
+ {
+ return *this;
+ }
+
+ virtual operator QVariant() const
+ {
+ return QVariant(QJsonArray(*this));
+ }
+};
diff --git a/seriesmodel.cpp b/seriesmodel.cpp
new file mode 100644
index 0000000..628cb60
--- /dev/null
+++ b/seriesmodel.cpp
@@ -0,0 +1,146 @@
+#include "seriesmodel.h"
+
+// cpp
+// tbb
+#ifdef __linux__
+#ifndef Q_MOC_RUN
+#if defined(emit)
+#undef emit
+#include <execution>
+#define emit // restore the macro definition of "emit", as it was defined in
+ // gtmetamacros.h
+#else
+#include <execution>
+#endif // defined(emit)
+#endif // Q_MOC_RUN
+#endif
+
+SeriesModel::SeriesModel(QObject* parent)
+ : QAbstractTableModel(parent)
+{
+}
+
+SeriesModel::SeriesModel(const SeriesModel& other)
+ : QAbstractTableModel()
+{
+ append(other.m_points);
+}
+
+int SeriesModel::rowCount(const QModelIndex& parent) const
+{
+ Q_UNUSED(parent);
+ return m_points.count();
+}
+
+int SeriesModel::columnCount(const QModelIndex& parent) const
+{
+ Q_UNUSED(parent);
+ return 2;
+}
+
+QVariant SeriesModel::data(const QModelIndex& modelIndex, int role) const
+{
+ if (!modelIndex.isValid())
+ return QVariant();
+
+ if (modelIndex.row() < 0 || modelIndex.row() >= m_points.count())
+ return QVariant();
+
+ if (role == Qt::DisplayRole)
+ {
+ if (modelIndex.column() == 0)
+ return m_points.at(modelIndex.row()).x();
+ else if (modelIndex.column() == 1)
+ return m_points.at(modelIndex.row()).y();
+ }
+
+ return QVariant();
+}
+
+void SeriesModel::append(const QVector<QVector2D> points)
+{
+ QMutexLocker l(&m_mtx);
+ beginInsertRows(
+ QModelIndex(),
+ m_points.count(),
+ m_points.count() + points.count() - 1
+ );
+
+ m_points << points;
+ endInsertRows();
+ recalcLimits();
+}
+
+void SeriesModel::clear()
+{
+ QMutexLocker l(&m_mtx);
+
+ if (m_points.isEmpty())
+ return;
+
+ beginRemoveRows(QModelIndex(), 0, m_points.count() - 1);
+ m_points.clear();
+ endRemoveRows();
+}
+
+void SeriesModel::recalcLimits()
+{
+ // WARNING: not thread safe
+ m_minX = std::numeric_limits<float>::max();
+ m_maxX = std::numeric_limits<float>::min();
+ m_minY = std::numeric_limits<float>::max();
+ m_maxY = std::numeric_limits<float>::min();
+
+#ifdef __linux__
+ const auto [minX, maxX] = std::minmax_element(
+ std::execution::par_unseq,
+ m_points.constBegin(),
+ m_points.constEnd(),
+ [](const auto& a, const auto& b) { return a.x() < b.x(); }
+ );
+
+ const auto [minY, maxY] = std::minmax_element(
+ std::execution::par_unseq,
+ m_points.constBegin(),
+ m_points.constEnd(),
+ [](const auto& a, const auto& b) { return a.y() < b.y(); }
+ );
+
+ float min = std::min(minX->x(), minY->y());
+ float max = std::max(maxX->x(), maxY->y());
+#else
+ for (const auto& p : m_points)
+ {
+ if (p.x() < m_minX)
+ m_minX = p.x();
+
+ if (p.x() > m_maxX)
+ m_maxX = p.x();
+
+ if (p.y() < m_minY)
+ m_minY = p.y();
+
+ if (p.y() > m_maxY)
+ m_maxY = p.y();
+ }
+
+ float min = std::min(m_minX, m_minY);
+ float max = std::max(m_maxX, m_maxY);
+#endif
+
+ m_minX = min;
+ m_maxX = max;
+ m_minY = min;
+ m_maxY = max;
+
+ emit minXChanged();
+ emit maxXChanged();
+ emit minYChanged();
+ emit maxYChanged();
+}
+
+void SeriesModel::replace(const QVector<QVector2D> points)
+{
+ clear();
+ append(points);
+}
diff --git a/seriesmodel.h b/seriesmodel.h
new file mode 100644
index 0000000..5aca1f4
--- /dev/null
+++ b/seriesmodel.h
@@ -0,0 +1,48 @@
+#pragma once
+
+// qt
+#include <QAbstractTableModel>
+#include <QMutex>
+#include <QVector2D>
+
+// goodies
+#include "g_property.h"
+
+class SeriesModel : public QAbstractTableModel
+{
+ Q_OBJECT
+
+ G_PROPERTY(float, minX);
+ G_PROPERTY(float, maxX);
+ G_PROPERTY(float, minY);
+ G_PROPERTY(float, maxY);
+
+public:
+ Q_INVOKABLE SeriesModel(QObject* parent = nullptr);
+ Q_INVOKABLE SeriesModel(const SeriesModel& other);
+ Q_INVOKABLE ~SeriesModel() Q_DECL_OVERRIDE = default;
+
+public:
+ Q_INVOKABLE int rowCount(
+ const QModelIndex& parent = QModelIndex()
+ ) const Q_DECL_OVERRIDE;
+ Q_INVOKABLE int columnCount(
+ const QModelIndex& parent = QModelIndex()
+ ) const Q_DECL_OVERRIDE;
+ Q_INVOKABLE QVariant data(
+ const QModelIndex& modelIndex,
+ int role = Qt::DisplayRole
+ ) const Q_DECL_OVERRIDE;
+
+public slots:
+ void append(const QVector<QVector2D> points);
+ void replace(const QVector<QVector2D> points);
+ void clear();
+ void recalcLimits();
+
+protected:
+ QVector<QVector2D> m_points;
+ QMutex m_mtx;
+};
+
+Q_DECLARE_METATYPE(SeriesModel);
diff --git a/singleton.h b/singleton.h
new file mode 100644
index 0000000..b170210
--- /dev/null
+++ b/singleton.h
@@ -0,0 +1,44 @@
+#pragma once
+
+// qt
+#include <QMutex>
+#include <QSharedPointer>
+
+#define SINGLETON(type) Singleton<type>::get().data()
+
+template <class T> class Singleton
+{
+public:
+ static QSharedPointer<T> get()
+ {
+ static QMutex mtx;
+ QMutexLocker l(&mtx);
+
+ if (!m_instance)
+ {
+ m_instance.reset(
+ static_cast<T*>(T::staticMetaObject.newInstance())
+ // FIXME: deleteLater doesn't work. Crashes without it
+ ,
+ &T::deleteLater
+ );
+ }
+
+ return m_instance;
+ }
+ static void cleanup()
+ {
+ static QMutex mtx;
+ QMutexLocker l(&mtx);
+
+ m_instance.clear();
+ }
+
+ typedef QSharedPointer<T> FactorySharedPointer;
+
+private:
+ static FactorySharedPointer m_instance;
+};
+
+template <class T>
+typename Singleton<T>::FactorySharedPointer Singleton<T>::m_instance;