From e7019763076cbbe3d52c2a03133c3ded5558f017 Mon Sep 17 00:00:00 2001 From: Nikita Kostovsky Date: Fri, 19 Sep 2025 20:54:13 +0200 Subject: initial commit --- .clang-format | 15 ++ .gitignore | 39 +++++ CMakeLists.txt | 42 +++++ LICENSE | 165 ++++++++++++++++++ README.md | 25 +++ basic_functions.cpp | 158 +++++++++++++++++ basic_functions.h | 92 ++++++++++ g_object.cpp | 180 ++++++++++++++++++++ g_object.h | 138 +++++++++++++++ g_property.cpp | 29 ++++ g_property.h | 420 ++++++++++++++++++++++++++++++++++++++++++++++ inisingleton.h | 51 ++++++ jsonobjectfilesingleton.h | 169 +++++++++++++++++++ keyvalue.cpp | 8 + keyvalue.h | 26 +++ logworker.cpp | 273 ++++++++++++++++++++++++++++++ logworker.h | 137 +++++++++++++++ serializablelist.cpp | 1 + serializablelist.h | 243 +++++++++++++++++++++++++++ seriesmodel.cpp | 146 ++++++++++++++++ seriesmodel.h | 48 ++++++ singleton.h | 44 +++++ 22 files changed, 2449 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 README.md create mode 100644 basic_functions.cpp create mode 100644 basic_functions.h create mode 100644 g_object.cpp create mode 100644 g_object.h create mode 100644 g_property.cpp create mode 100644 g_property.h create mode 100644 inisingleton.h create mode 100644 jsonobjectfilesingleton.h create mode 100644 keyvalue.cpp create mode 100644 keyvalue.h create mode 100644 logworker.cpp create mode 100644 logworker.h create mode 100644 serializablelist.cpp create mode 100644 serializablelist.h create mode 100644 seriesmodel.cpp create mode 100644 seriesmodel.h create mode 100644 singleton.h 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. + 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 +#include + +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::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 + +// qt +#include +#include +#include +#include +#include +#include +#include +#include + +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 static const QString enumToString(const T& value) +{ + return QMetaEnum::fromType().valueToKey(value); +} + +template static const T stringToEnum(const QString& name) +{ + return T(QMetaEnum::fromType().keyToValue(name.toStdString().c_str())); +} + +Q_DECL_CONST_FUNCTION uint qHash(const QVector3D& v) noexcept; + +template static constexpr T min() +{ + return std::numeric_limits::min(); +} + +template static constexpr T max() +{ + return std::numeric_limits::max(); +} + +template static constexpr T lowest() +{ + return std::numeric_limits::lowest(); +} + +#define INI_ENUM_FROM_JSON(type, name, default) \ + m_##name(json.contains(#name) ? stringToEnum(#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 +#include +#include + +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::quiet_NaN()); + } + else if (type == QMetaType::Float) + { + setProperty(key, std::numeric_limits::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 +#include +#include +#include +#include + +#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 + +QSharedPointer GlobalSettings::m_instance; + +QSharedPointer 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 +#include +#include +#include +#include +#include +#include + +#ifndef STRINGIFY +#define STRINGIFY(var) (#var) +#endif + +class GlobalSettings; +class GlobalSettings : public QSettings +{ + Q_OBJECT + +public: + static QSharedPointer instance(); + +private: + GlobalSettings( + GlobalSettings::Format format, + GlobalSettings::Scope scope, + const QString& organization, + const QString& application = QString() + ); + static QSharedPointer 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( \ + GlobalSettings::instance() \ + ->value( \ + QString(this->staticMetaObject.className()) + "/" + #name, \ + defaultValue \ + ) \ + .value() \ + ); \ + } + +#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(); \ + } + +#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() \ + ) + +#define INIT_FROM_JSON(type, var, default) \ + m_##var( \ + (json.contains(#var)) \ + ? (json[STRINGIFY(var)].toVariant().value()) \ + : (default) \ + ) + +#define INIT_OBJECT_JSON(var, default) \ + m_##var((json.contains(#var)) ? (json[STRINGIFY(var)]) : (default)) + +// for QJsonObject(std::initializer_list > 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().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 name##s READ name##s NOTIFY name##sChanged \ + ) \ +public: \ + QQmlListProperty name##s() \ + { \ + return QQmlListProperty( \ + 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& get_##name##s() \ + { \ + return m_##name##s; \ + } \ +Q_SIGNALS: \ + void name##sChanged(); \ + \ +protected: \ + static void append_##name(QQmlListProperty* list, type* item) \ + { \ + reinterpret_cast(list->data)->append_##name(item); \ + } \ + static int name##sCount(QQmlListProperty* list) \ + { \ + return reinterpret_cast(list->data)->name##sCount(); \ + } \ + static type* name(QQmlListProperty* list, int index) \ + { \ + return reinterpret_cast(list->data)->name(index); \ + } \ + static void clear_##name##s(QQmlListProperty* list) \ + { \ + reinterpret_cast(list->data)->clear_##name##s(); \ + } \ + QList 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() : default \ + ) + +#define SET_FROM_JSON_ENUM_KEY(name, default) \ + set_##name( \ + json.contains(#name) \ + ? (typeof(m_##name))(QMetaEnum::fromType() \ + .keyToValue( \ + json[#name] \ + .toString() \ + .toStdString() \ + .c_str() \ + )) \ + : default \ + ) + +#define COMPARE_WITH_OTHER(name) (get_##name() == other.get_##name()) + +template static QJsonArray listToJson(const QList& list) +{ + QJsonArray result; + + for (const auto& item : list) + { + result << QJsonValue(*item); + } + + return result; +} + +template +static QList listFromJson( + const QJsonArray& array, + QObject* parent = nullptr +) +{ + QList 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(json[#listName].toArray(), parent)) + +#define WRITE_LIST_TO_JSON(listName, className) \ + #listName, listToJson(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 +#include + +// goodies +#include "g_property.h" + +#define INI_SINGLETON(type) IniSingleton::get() + +template 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define JSON_OBJECT_FILE(classname) JsonObjectFile::get().data() + +template class JsonObjectFile +{ +public: + static QSharedPointer 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 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(); + } + + auto data = f.readAll(); + QJsonValue json = QJsonDocument::fromJson(data).object(); + return QSharedPointer( + new T(json[T::staticMetaObject.className()]), + &T::deleteLater + ); + } + + static bool write( + const QSharedPointer& 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 FactorySharedPointer; + +public: + static const QString extension; + +private: + static FactorySharedPointer m_instance; + static QFileSystemWatcher m_watcher; +}; + +template +QString const JsonObjectFile::extension = QStringLiteral("json"); + +template +typename JsonObjectFile::FactorySharedPointer JsonObjectFile::m_instance; + +template QFileSystemWatcher JsonObjectFile::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) 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 +#include +#include + +// cpp +#include + +// 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 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 +#include +#include +#include +#include +#include +#include +#include + +// 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 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 roleNames() const Q_DECL_OVERRIDE + { + return m_roleNames; + } + +protected: + QHash 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 +#include +#include +#include +#include + +#define INIT_ARRAY_FIELD_FROM_JSON(field) m_##field(json[#field].toArray()) + +template +/*! + * \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 +{ +public: + SerializableList() = default; + SerializableList(const QJsonArray& json) + : QList() + { + *this = json; + } + SerializableList(const QList& other) + : QList(other) + { + } + SerializableList(const QJsonObject& json) + : SerializableList(QJsonValue(json).toArray()) + { + } + SerializableList(const QJsonValue& json) + : SerializableList(json.toArray()) + { + } + SerializableList(std::initializer_list l) + : QList(l) + { + } + +public: + SerializableList& operator=(const QJsonArray& json) + { + for (const auto& item : json) + { + // *this << item.toVariant().value(); + *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 class SerializableVectorBasic : public QVector +{ +public: + SerializableVectorBasic() = default; + SerializableVectorBasic(const QJsonArray& json) + : QVector() + { + *this = json; + } + SerializableVectorBasic(const QVector& other) + : QVector(other) + { + } + SerializableVectorBasic(const QJsonObject& json) + : SerializableVectorBasic(QJsonValue(json).toArray()) + { + } + SerializableVectorBasic(const QJsonValue& json) + : SerializableVectorBasic(json.toArray()) + { + } + SerializableVectorBasic(std::initializer_list l) + : QVector(l) + { + } + +public: + SerializableVectorBasic& operator=(const QJsonArray& json) + { + for (const auto& item : json) + { + *this << item.toVariant().value(); + // *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 class SerializableVector : public QVector +{ +public: + SerializableVector() = default; + SerializableVector(const QJsonArray& json) + : QVector() + { + *this = json; + } + SerializableVector(const QVector& other) + : QVector(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 l) + : QVector(l) + { + } + +public: + SerializableVector& operator=(const QJsonArray& json) + { + for (const auto& item : json) + { + // *this << item.toVariant().value(); + *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 +#define emit // restore the macro definition of "emit", as it was defined in + // gtmetamacros.h +#else +#include +#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 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::max(); + m_maxX = std::numeric_limits::min(); + m_minY = std::numeric_limits::max(); + m_maxY = std::numeric_limits::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 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 +#include +#include + +// 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 points); + void replace(const QVector points); + void clear(); + void recalcLimits(); + +protected: + QVector 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 +#include + +#define SINGLETON(type) Singleton::get().data() + +template class Singleton +{ +public: + static QSharedPointer get() + { + static QMutex mtx; + QMutexLocker l(&mtx); + + if (!m_instance) + { + m_instance.reset( + static_cast(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 FactorySharedPointer; + +private: + static FactorySharedPointer m_instance; +}; + +template +typename Singleton::FactorySharedPointer Singleton::m_instance; -- cgit v1.2.3-70-g09d2