diff options
| author | Nikita Kostovsky <nikita@kostovsky.me> | 2025-09-19 20:54:13 +0200 |
|---|---|---|
| committer | Nikita Kostovsky <nikita@kostovsky.me> | 2025-09-19 20:54:13 +0200 |
| commit | e7019763076cbbe3d52c2a03133c3ded5558f017 (patch) | |
| tree | bcf5ff116630bebaa09492bdcdaf08a54dfd1e28 | |
initial commit
| -rw-r--r-- | .clang-format | 15 | ||||
| -rw-r--r-- | .gitignore | 39 | ||||
| -rw-r--r-- | CMakeLists.txt | 42 | ||||
| -rw-r--r-- | LICENSE | 165 | ||||
| -rw-r--r-- | README.md | 25 | ||||
| -rw-r--r-- | basic_functions.cpp | 158 | ||||
| -rw-r--r-- | basic_functions.h | 92 | ||||
| -rw-r--r-- | g_object.cpp | 180 | ||||
| -rw-r--r-- | g_object.h | 138 | ||||
| -rw-r--r-- | g_property.cpp | 29 | ||||
| -rw-r--r-- | g_property.h | 420 | ||||
| -rw-r--r-- | inisingleton.h | 51 | ||||
| -rw-r--r-- | jsonobjectfilesingleton.h | 169 | ||||
| -rw-r--r-- | keyvalue.cpp | 8 | ||||
| -rw-r--r-- | keyvalue.h | 26 | ||||
| -rw-r--r-- | logworker.cpp | 273 | ||||
| -rw-r--r-- | logworker.h | 137 | ||||
| -rw-r--r-- | serializablelist.cpp | 1 | ||||
| -rw-r--r-- | serializablelist.h | 243 | ||||
| -rw-r--r-- | seriesmodel.cpp | 146 | ||||
| -rw-r--r-- | seriesmodel.h | 48 | ||||
| -rw-r--r-- | singleton.h | 44 |
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() @@ -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; |
