Initial commit

This commit is contained in:
torsten committed 2026-08-13 18:33:10 +02:00
commit b7d90b9afe
16245 files changed
+3396666

No files matched your search

+68
View File
@@ -0,0 +1,68 @@
#
# $Id$
# $HeadURL$
# $Author$
#
SET(SRCS_hdr_only
${CMAKE_CURRENT_SOURCE_DIR}/data_modification_enum.h
${CMAKE_CURRENT_SOURCE_DIR}/dds_bitarray.h
${CMAKE_CURRENT_SOURCE_DIR}/dds_vector.h
${CMAKE_CURRENT_SOURCE_DIR}/connection.h
${CMAKE_CURRENT_SOURCE_DIR}/json_object.h
${CMAKE_CURRENT_SOURCE_DIR}/outdated_position.h
${CMAKE_CURRENT_SOURCE_DIR}/qbytearray_byteaccess.h
${CMAKE_CURRENT_SOURCE_DIR}/qpoint3d.h
${CMAKE_CURRENT_SOURCE_DIR}/await.h
)
ADD_LIBRARY(LtsUtilsCoreHeaderOnly INTERFACE)
TARGET_INCLUDE_DIRECTORIES(LtsUtilsCoreHeaderOnly
INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/../
${CMAKE_CURRENT_BINARY_DIR}/../
)
TARGET_SOURCES(LtsUtilsCoreHeaderOnly INTERFACE ${SRCS_hdr_only})
SET(SRCS
pgarray.cpp
qplane3d.cpp
qpoint3d.cpp
scoped_connection.cpp
sfm_bytemessage_converter.cpp
promise.cpp
lts_translatable_identifier.cpp
lts_portrange.cpp
#variant_operators.h
)
NGL_ADD_LIBRARY2(ALL LtsUtilsCore SHARED ${SRCS})
NGL_TARGET_INCLUDE_DIRECTORIES(LtsUtilsCore
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../
${CMAKE_CURRENT_BINARY_DIR}/../
)
NGL_TARGET_LINK_LIBRARIES(LtsUtilsCore
PUBLIC
Qt5::Core
LtsUtilsCoreHeaderOnly
)
#
# --- Tests --------------------------------------------------------------
#
# IF (TARGET ddscore) // does not work, not yet included
IF((BUILD_WS OR BUILD_SV) AND BUILD_TESTING)
ADD_EXECUTABLE(utils_test
UnitTests/utils_test.cpp
UnitTests/utils_test.h
promise_examples.h
promise_examples.cpp
)
TARGET_LINK_LIBRARIES(utils_test
LtsUtilsCore
Qt5::Core
Qt5::Test
ddscore
)
ADD_TEST(NAME utils_test COMMAND $<TARGET_FILE:utils_test>)
ENDIF()
File diff suppressed because it is too large. Load diff
+66
View File
@@ -0,0 +1,66 @@
/**
* Unit test for zsdgim
* $Id$
* $HeadURL$
* $Author$
*/
#pragma once
#include <QObject>
class LtsUtilsTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
// PgArray
void test_toIntArray();
void test_fromIntArray();
void test_toBigintArray();
void test_fromBigintArray();
void test_toIntArray2D();
void test_fromIntArray2D();
void test_toBigintArray2D();
void test_fromBigintArray2D();
// QPoint3D
void test_QPoint3D();
// bitArray
void test_bitArray(); // also known as 'bitfield'
// convert<>
void test_convert();
// Connection
void test_connection();
void test_queuedConnection();
void test_signalSignalConnection();
void test_danglingConnection1();
void test_danglingConnection2();
void test_danglingConnection3();
// JsonObject, JsonArray
void test_jsonObject_fromJson();
void test_jsonObject_contains();
void test_jsonObject_value();
void test_jsonObject_valueWithError();
void test_jsonObject_valueOr();
void test_jsonObject_integers();
void test_jsonObject_nullValues();
void test_jsonArray_fromJson();
void test_jsonArray();
void test_jsonArray_nullArray();
// SfmByteMessageConverter
void test_byteStreamFromSfmMessage();
void test_byteStreamFromSfmMessage2();
void test_byteStreamToSfmMessage();
// Promise
void test_promise();
void test_promise_example1();
void test_promise_example2();
void test_promise_example3();
// QByteArray::ByteAccess
void byteAccess();
};
+124
View File
@@ -0,0 +1,124 @@
#pragma once
/**
* @RDE_HEADER@
* Function which waits (QEventLoop) for a success signal or a cancelation signal, optionally with a timeout
* Useful for blocking on async things
* $Id: connection.h 45834 2019-03-01 12:56:15Z ehrlicher $
* $HeadURL: svn://192.168.133.21/TDS/branches/product/features/swp_2023_devel/Lts/Common/LtsUtils/LtsUtilsCore/connection.h $
* $Author: ehrlicher $
*
* Created by Mike Wille, ESZS, 2024-03-03
*/
#include <QObject>
#include <QTimer>
#include <QEventLoop>
#include <memory>
namespace LtsUtils
{
template <typename Func>
void addConnect(QEventLoop &x, bool &success, const typename QtPrivate::FunctionPointer<Func>::Object *sender, Func signal, bool isSuccess)
{
if (isSuccess)
{
QObject::connect(sender, signal, &x, [&success]()
{
//fprintf(stderr, "await: success!\n");
success = true;
});
}
QObject::connect(sender, signal, &x, &QEventLoop::quit, Qt::QueuedConnection);
}
template <typename Func, typename... Connect>
void addConnect(QEventLoop &x, bool &success, const typename QtPrivate::FunctionPointer<Func>::Object *sender, Func signal, bool isSuccess, Connect... connects)
{
addConnect(x, success, sender, signal, isSuccess);
addConnect(x, success, connects...);
}
template <typename... Connect> // a 'Connect' consists of a Qt sender, a Qt signal and a bool if this signal is a signal for success or for failure.
void await(Connect... connects) noexcept(false)
{
QEventLoop x;
bool success = false;
addConnect(x, success, connects...);
// fprintf(stderr, "await: now waiting...\n");
x.exec();
// fprintf(stderr, "await: done! success? %d\n", success);
if (!success)
{
// fprintf(stderr, "await: !success - throwing error!\n");
throw std::runtime_error("Failed.");
}
}
template <typename... Connect>
void awaitTimeout(const int timeoutMs, Connect... connects) noexcept(false)
{
std::unique_ptr<QTimer> t(new QTimer);
bool timeout = false;
if (timeoutMs > -1)
{
t->setInterval(timeoutMs);
t->setSingleShot(true);
QObject::connect(t.get(), &QTimer::timeout, t.get(), [&timeout](){ fprintf(stderr, "awaitTimeout: timed out!\n"); timeout = true; });
t->start();
}
await(t.get(), &QTimer::timeout, false, connects...); // throws
// fprintf(stderr, "awaitTimeout: done waiting - timeout? %d\n", timeout);
if (timeout)
{
// fprintf(stderr, "await: timed out - throwing error!\n");
throw std::runtime_error("Timeout.");
}
}
//// non-looping version of the classic wait stdlib function which enters the event loop to prevent the application from hanging
//void waitEventLoop(const int timeoutMs);
//{
// try
// {
// awaitTimeout(timeoutMs);
// }
// catch(...){}
//}
// 2024.11.29 Norbert Thoden
// awaitWithCancelationSignal scheint unter Windows nicth zu funktioneren.
// Siehe auch GUEZE-3155 Replay auf ABT nicht funktional
// deprecated, do not use
template <typename Func1, typename Func2>
void awaitWithCancelationSignal(const typename QtPrivate::FunctionPointer<Func1>::Object *sender1, Func1 signal1,
const typename QtPrivate::FunctionPointer<Func2>::Object *sender2, Func2 signal2,
const int timeoutMs = 0) noexcept(false)
{
if (timeoutMs > 0)
{
awaitTimeout(timeoutMs,
sender1, signal1, true,
sender2, signal2, false);
return;
}
await(sender1, signal1, true,
sender2, signal2, false);
}
} // namespace LtsUtils
+153
View File
@@ -0,0 +1,153 @@
/**
* @RDE_HEADER@
* General Qt-connection
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Fröbel, SLSE, 2017-07-24
*/
#pragma once
#include <QObject>
#include <QMetaObject>
#include <QPointer>
#include <QtGlobal>
#include <type_traits>
namespace LtsUtils
{
/**
* IConnection represents a general Qt-connection.
* It is useful in cases where you need to store different Qt-connections
* for later use.
* @author Christian Fröbel
*/
class IConnection
{
public:
virtual ~IConnection() = default;
virtual void connect(Qt::ConnectionType = Qt::AutoConnection) = 0;
virtual void disconnect() = 0;
};
/**
* A DummyConnection can be used when there is no connection.
* connect() and disconnect() are no-ops.
*/
class DummyConnection : public IConnection
{
public:
void connect(Qt::ConnectionType = Qt::AutoConnection) override {}
void disconnect() override {}
};
namespace detail
{
/**
* Helper class to extract the class type from a given pointer to member,
* where member can be an object or a function (aka method).
* We need it only for methods though.
* This is just the main template declaration without implementation.
*/
template<class T>
struct ExtractClass;
/**
* Specialization vor pointer to member.
*/
template<class T, class Member>
struct ExtractClass<Member T::*>
{
using type = T;
};
} // namespace detail
/**
* Implements a Qt-connection based on the pointer to the methods being connected.
* The connection can be signal to signal or signal to slot.
*
* Connection is guarded against the case where either the signal-emitter
* or -target is deleted before conntect() is actually called.
*
* @param Signal Pointer to method of the Emitter
* @param Slot Pointer to method of the Target; might also be a signal.
* @author Christian Fröbel
*/
template<class Signal, class Slot>
class Connection : public IConnection
{
static_assert(std::is_member_function_pointer<Signal>::value, "Must be a pointer to method");
static_assert(std::is_member_function_pointer<Slot>::value, "Must be a pointer to method");
using Emitter = typename detail::ExtractClass<Signal>::type;
using Target = typename detail::ExtractClass<Slot>::type;
static_assert(std::is_base_of<QObject, Emitter>::value, "Must be derived from QObject");
static_assert(std::is_base_of<QObject, Target>::value, "Must be derived from QObject");
public:
Connection(Emitter *emitter, Signal signal, Target *target, Slot slot):
m_emitter(emitter),
m_signal(signal),
m_target(target),
m_slot(slot)
{
Q_ASSERT(emitter);
Q_ASSERT(signal);
Q_ASSERT(target);
Q_ASSERT(slot);
}
void connect(Qt::ConnectionType connectionType = Qt::AutoConnection) override
{
Q_ASSERT(!m_connected);
m_connected = true;
// Guard against the possibility when either m_emitter or m_target got deleted before
// the Connection is actually connected.
if (!m_emitter || !m_target)
return;
m_conn = QObject::connect(m_emitter.data(), m_signal, m_target.data(), m_slot, connectionType);
Q_ASSERT(m_conn);
}
void disconnect() override
{
Q_ASSERT(m_connected);
if (m_conn)
QObject::disconnect(m_conn);
m_connected = false;
}
private:
bool m_connected{false};
QPointer<Emitter> m_emitter;
Signal m_signal;
QPointer<Target> m_target;
Slot m_slot;
QMetaObject::Connection m_conn;
};
/**
* Helper function in order to deduce the template parameters.
*/
template<class Signal, class Slot>
Connection<Signal, Slot>* createConnection(typename detail::ExtractClass<Signal>::type *emitter, Signal signal,
typename detail::ExtractClass<Slot>::type *target, Slot slot)
{
return new Connection<Signal, Slot>(emitter, signal, target, slot);
}
} // namespace LtsUtils
+35
View File
@@ -0,0 +1,35 @@
/**
* Enum to describe the type of modification for an incoming data topic
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-02-26
*/
#pragma once
#include <ostream>
enum class DataModification
{
eAdded,
eModified,
eRemoved
};
inline std::ostream &operator<<(std::ostream &str, DataModification mod)
{
switch(mod)
{
case DataModification::eAdded:
str << "DataModification::eAdded";
break;
case DataModification::eModified:
str << "DataModification::eModified";
break;
case DataModification::eRemoved:
str << "DataModification::eRemoved";
break;
}
return str;
}
+103
View File
@@ -0,0 +1,103 @@
/**
* Convert a dds::vector<uint8_t> to a QByteArray
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-05-03
*/
#pragma once
#ifdef SUPPORT_QBITARRAY
# include <QtCore/QBitArray>
#endif
namespace LtsUtils
{
namespace BitArray
{
constexpr inline int bitNumber(int position)
{
return 7 - (position % 8); // LSB is left - prefer this to be compatible to TETRA ICD
// return (position % 8); // LSB is right
}
template<typename T>
inline bool testBit(const T &bitField, int position, bool *oob = nullptr)
{
const size_t byteNum = position / 8;
if (byteNum >= bitField.size())
{
if (oob)
*oob = true;
return false;
}
if (oob)
*oob = false;
const typename T::value_type mask = 1 << bitNumber(position);
return ((bitField.at(byteNum) & mask) == mask);
}
template<typename T>
inline bool setBit(T &bitField, int position, bool set = true)
{
const size_t byteNum = position / 8;
if (byteNum >= bitField.size())
return false;
const typename T::value_type mask = 1 << bitNumber(position);
if (set)
bitField[byteNum] |= mask; // set bit
else
bitField[byteNum] &= ~mask; // clear bit
return true;
}
template<typename T>
inline bool clearBit(T &bitField, int position)
{
return setBit(bitField, position, false);
}
#ifdef SUPPORT_QBITARRAY
template<typename T>
inline T convert(const QBitArray &bitArray)
{
T ret;
ret.resize((bitArray.size() + 7) / 8, 0);
for (int i = 0; i < bitArray.size(); ++i)
{
const size_t byteNum = i / 8;
const typename T::value_type mask = (1 << bitNumber(i));
if (bitArray[i])
ret[byteNum] |= mask; // set bit
else
ret[byteNum] &= ~mask; // clear bit
}
return ret;
}
template<typename T>
inline QBitArray convert(T &bitField, int realSize)
{
QBitArray ba(realSize);
for (int32_t i = 0; i < realSize; ++i)
{
const size_t byteNum = i / 8;
const typename T::value_type mask = (1 << bitNumber(i));
bool bit = ((bitField[byteNum] & mask) == mask); // access correct byte and mask correct bit
ba[i] = bit;
}
return ba;
}
#endif
} // namespace BitArray
} // namespace LtsUtils
+48
View File
@@ -0,0 +1,48 @@
/**
* Convert a dds::vector<uint8_t> to a QByteArray
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-05-03
*/
#pragma once
#include <QByteArray>
#include <QString>
namespace LtsUtils
{
template <typename T, typename S>
inline void convert(const S &in, T &out)
{
static_assert(!std::is_same<S, T>::value, "Try to convert a type to the same type - is useless");
out.reserve(static_cast<int>(in.size()));
std::copy(in.begin(), in.end(), std::back_inserter(out));
}
template <typename T, typename S>
inline T convert(const S &in)
{
static_assert(!std::is_same<S, T>::value, "Try to convert a type to the same type - is useless");
T out;
convert(in, out);
return out;
}
// convenience functions for better readability
template <typename T>
inline QByteArray toQByteArray(const T &in)
{
return convert<QByteArray>(in);
}
template <typename T, typename V = typename T::value_type>
bool isInVector(const T &vec, const V &valueToSearch)
{
const auto end = vec.end();
return std::find(vec.begin(), end, valueToSearch) != end;
}
} // namespace LtsUtils
+28
View File
@@ -0,0 +1,28 @@
/**
* Helper class to convert QByteArray/QPixmap to/from std::vector<uint8>
* $Id$
* $HeadURL$
* $Author$
*/
#pragma once
#include <LtsUtilsCore/ltsutilscore_export.h>
#include <cstdint>
#include <vector>
class QByteArray;
class QPixmap;
class QImage;
namespace LtsUtils
{
LTSUTILSCORE_EXPORT QPixmap getPixmap(const std::vector<uint8_t> &data);
LTSUTILSCORE_EXPORT QPixmap getPixmap(const QByteArray &data);
LTSUTILSCORE_EXPORT QImage getImage(const std::vector<uint8_t> &data);
LTSUTILSCORE_EXPORT QImage getImage(const QByteArray &data);
LTSUTILSCORE_EXPORT std::vector<uint8_t> fromPixmap(const QPixmap &pixmap, bool *bIsPng = nullptr);
LTSUTILSCORE_EXPORT std::vector<uint8_t> fromImage(const QImage &image, bool *bIsPng = nullptr);
} // namespace LtsUtils
+1
View File
@@ -0,0 +1 @@
#include <LtsUtilsCore/json_object.h> // redir
+755
View File
@@ -0,0 +1,755 @@
/**
* @RDE_HEADER@
* JsonObject
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Fröbel, SLSE, 2017-10-26
*/
#pragma once
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QJsonValue>
#include <QVector>
#include <QMap>
#include <QString>
#include <string>
#include <vector>
#include <map>
#include <exception>
#include <cstdint>
#include <limits>
namespace LtsUtils
{
class JsonParseError : public std::runtime_error
{
public:
JsonParseError(const QJsonParseError &err)
: std::runtime_error("json parse error: " + err.errorString().toStdString() + " - at offset " + std::to_string(err.offset)){}
};
class JsonError : public std::runtime_error
{
public:
using std::runtime_error::runtime_error;
};
class JsonKeyError : public JsonError
{
public:
using JsonError::JsonError;
};
class JsonRangeError : public JsonError
{
public:
using JsonError::JsonError;
};
class JsonTypeError : public JsonError
{
public:
using JsonError::JsonError;
};
/**
* A Wrapper for QJsonObject for typesafe, explicit and strict access.
* @author Christian Fröbel
*/
class JsonObject
{
public:
/**
* Just a small helper class we use for the key which can
* be implicitly created from a QString, std::string, const char*.
* Encoding of std::string and const char* is assumed to be utf-8.
* Should not be used explicitly be the user.
*/
class Key;
JsonObject() = default;
explicit JsonObject(const QJsonObject &json)
: m_json(json)
, m_jsonIsSet(true)
{}
explicit JsonObject(QJsonObject &&json)
: m_json(std::move(json))
, m_jsonIsSet(true)
{}
// JsonObject(JsonObject &&json)
// : m_json(std::move(json.m_json))
// , m_jsonIsSet(true)
// {}
/**
* create a JsonObject out of some data, returns an empty array if it could not be parsed
*/
///@{
static inline JsonObject fromJson(const char *json, int len = -1);
static inline JsonObject fromJson(const QByteArray &json) { return fromJson(json.data(), json.size()); }
static inline JsonObject fromJson(const std::string &json) { return fromJson(json.c_str(), json.size()); }
static inline JsonObject fromJsonSafe(const char *json, int len = -1) noexcept(false);
static inline JsonObject fromJsonSafe(const QByteArray &json) noexcept(false) { return fromJsonSafe(json.data(), json.size()); }
static inline JsonObject fromJsonSafe(const std::string &json) noexcept(false) { return fromJsonSafe(json.c_str(), json.size()); }
///@}
/**
* @return true if the JsonObject contains an element with that name and that element is of type T.
* @see value() for allowed types for T.
*/
template<class T>
bool contains(const Key &key) const;
bool isNull(const Key &key) const { return contains<void>(key); }
bool isEmpty() const { return m_json.isEmpty(); }
bool isValid() const { return m_jsonIsSet; }
/**
* Retrieve a value of a given type T.
* T can be an integer type (unsigned/signed 16-, 32-, 64-bit),
* double, bool, QString, std::string, QJsonObject, JsonObject, QJsonArray, JsonArray,
* QVector<T>, std::vector<T>, QMap<QString, T> and std::map<std::string, T>
* where T is one of the given types (recursively).
* @throw JsonKeyError if an element with that key does not exist.
* @throw JsonTypeError if the requested type of the element does not match.
*/
template<class T>
T value(const Key &key) const noexcept(false);
/**
* Same as value() but return a default value if no element with that key exists.
* @throw JsonTypeError if an element with that key exists but has a non-macthing type.
*/
template<class T>
T valueOr(const Key &key, const T &defaultValue) const noexcept(false);
/**
* Check whether all values of this JsonObject are of type T.
* If true, toMap()/toStdMap() will succeed.
*/
template<class T>
bool isMap() const;
/**
* Convert this JsonObject into a map with values of type T.
* @throw JsonTypeError if some value is not of type T.
* @see value() for allowed types of T.
*/
template<class T>
QMap<QString, T> toMap() const noexcept(false);
/** Same as toMap(). */
template<class T>
std::map<std::string, T> toStdMap() const;
bool operator==(const JsonObject &other) const { return m_json == other.m_json; }
bool operator!=(const JsonObject &other) const { return !(*this == other); }
class Key
{
public:
Key(const QString &s)
: m_s(s)
{}
Key(const std::string &s)
: m_s(QString::fromStdString(s))
{}
Key(const char *s)
: m_s(QString::fromUtf8(s))
{}
operator const QString &() const { return m_s; }
private:
const QString m_s;
};
operator const QJsonObject &() const { return m_json; }
const QJsonObject &object() const { return m_json; }
private:
bool containsAny(const QString &key) const { return m_json.contains(key); }
QJsonObject m_json;
bool m_jsonIsSet = false;
};
JsonObject JsonObject::fromJson(const char *json, int len)
{
if (len < 0)
len = strlen(json);
QJsonParseError error;
const auto doc = QJsonDocument::fromJson(QByteArray::fromRawData(json, len), &error);
if (error.error != QJsonParseError::NoError)
return JsonObject();
if (!doc.isObject())
return JsonObject();
return JsonObject(doc.object());
}
JsonObject JsonObject::fromJsonSafe(const char *json, int len) noexcept(false)
{
if (len < 0)
len = strlen(json);
QJsonParseError error;
const auto doc = QJsonDocument::fromJson(QByteArray::fromRawData(json, len), &error);
if (error.error != QJsonParseError::NoError)
throw JsonParseError(error);
if (!doc.isObject())
throw JsonTypeError("JSON root is not an object");
return JsonObject(doc.object());
}
//==============================================================================
/**
* A Wrapper for QJsonArray for typesafe, explicit and strict access.
* @author Christian Fröbel
*/
class JsonArray
{
public:
JsonArray() = default;
explicit JsonArray(const QJsonArray &json)
: m_json(json)
, m_jsonIsSet(true)
{}
explicit JsonArray(QJsonArray &&json)
: m_json(std::move(json))
, m_jsonIsSet(true)
{}
// JsonArray(JsonArray &&json)
// : m_json(std::move(json.m_json))
// , m_jsonIsSet(true)
// {}
// JsonArray& operator=(JsonArray&& json)
// {
// m_json = std::move(json.m_json);
// m_jsonIsSet = json.m_jsonIsSet;
// return *this;
// }
/**
* create a JsonArray out of some data, returns an empty array if it could not be parsed
*/
///@{
static inline JsonArray fromJson(const char *json, int len = -1);
static inline JsonArray fromJson(const QByteArray &json) { return fromJson(json.data(), json.size()); }
static inline JsonArray fromJson(const std::string &json) { return fromJson(json.c_str(), json.size()); }
static inline JsonArray fromJsonSafe(const char *json, int len = -1) noexcept(false);
static inline JsonArray fromJsonSafe(const QByteArray &json) noexcept(false) { return fromJson(json.data(), json.size()); }
static inline JsonArray fromJsonSafe(const std::string &json) noexcept(false) { return fromJson(json.c_str(), json.size()); }
///@}
int size() const { return m_json.size(); }
bool isEmpty() const { return m_json.isEmpty(); }
bool isValid() const { return m_jsonIsSet; }
/** @throw JsonRangeError if index is out of range. */
template<class T>
bool contains(int index) const noexcept(false);
/** @throw JsonRangeError if index is out of range. */
bool isNull(int index) const noexcept(false) { return contains<void>(index); }
/** @throw JsonRangeError if index is out of range. */
template<class T>
T at(int index) const noexcept(false);
/**
* Check whether all values of this JsonArray are of type T.
* If true, toVector()/toStdVector() will succeed.
*/
template<class T>
bool isVector() const;
/**
* Convert this JsonArray into a vector with values of type T.
* @throw JsonTypeError if some value is not of type T.
*/
template<class T>
QVector<T> toVector() const noexcept(false);
template<class T>
std::vector<T> toStdVector() const;
bool operator==(const JsonArray &other) const { return m_json == other.m_json; }
bool operator!=(const JsonArray &other) const { return !(*this == other); }
const QJsonArray &operator()() const { return m_json; }
private:
QJsonArray m_json;
bool m_jsonIsSet = false;
};
JsonArray JsonArray::fromJson(const char *json, int len)
{
if (len < 0)
len = strlen(json);
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(QByteArray::fromRawData(json, len), &error);
if (error.error != QJsonParseError::NoError)
return JsonArray();
if (!doc.isArray())
return JsonArray();
return JsonArray(doc.array());
}
JsonArray JsonArray::fromJsonSafe(const char *json, int len) noexcept(false)
{
if (len < 0)
len = strlen(json);
QJsonParseError error;
const auto doc = QJsonDocument::fromJson(QByteArray::fromRawData(json, len), &error);
if (error.error != QJsonParseError::NoError)
throw JsonParseError(error);
if (!doc.isArray())
throw JsonTypeError("JSON root is not an array");
return JsonArray(doc.array());
}
//==============================================================================
/**
* Primary template without implementation.
*/
template<class T>
struct JsonValue;
/**
* Limits used by JsonIntValue.
* Defaults to std::numeric_limits, except for int64_t and uint64_t; see below.
*/
template<class Int>
struct JsonLimits
{
static constexpr Int lowest() { return std::numeric_limits<Int>::lowest(); }
static constexpr Int max() { return std::numeric_limits<Int>::max(); }
};
/**
* Specialization for int64_t.
* As a JSON number is stored in a 64-bit double, maximum accuracy
* is limited by bit-width of mantissa (52 bit).
* @see RFC 7159, IEEE 754-2008
*/
template<>
struct JsonLimits<int64_t>
{
static constexpr int64_t lowest() { return -(static_cast<int64_t>(1) << 53) + 1; }
static constexpr int64_t max() { return (static_cast<int64_t>(1) << 53) - 1; }
};
/**
* Specialization for uint64_t.
*/
template<>
struct JsonLimits<uint64_t>
{
static constexpr uint64_t lowest() { return 0; }
static constexpr uint64_t max() { return JsonLimits<int64_t>::max(); }
};
/**
* Implementation for integer values.
*/
template<class Int/*, const char *typeName*/>
struct JsonIntValue
{
static bool matchesType(const QJsonValue &value)
{
if (!value.isDouble())
return false;
double val = value.toDouble();
if (val < JsonLimits<Int>::lowest())
return false;
if (val > JsonLimits<Int>::max())
return false;
double wholeInt = static_cast<Int>(val);
return val == wholeInt;
}
static Int value(const QJsonValue &value) noexcept(false)
{
if (!matchesType(value))
throw JsonTypeError("JSON value not of type Int");
return static_cast<Int>(value.toDouble());
}
};
/**
* Specializations for Integers.
*/
///@{
template<>
struct JsonValue<int16_t> : JsonIntValue<int16_t/*, TYPE_NAME_INT16*/>
{};
template<>
struct JsonValue<uint16_t> : JsonIntValue<uint16_t/*, TYPE_NAME_UINT16*/>
{};
template<>
struct JsonValue<int32_t> : JsonIntValue<int32_t/*, TYPE_NAME_INT32*/>
{};
template<>
struct JsonValue<uint32_t> : JsonIntValue<uint32_t/*, TYPE_NAME_UINT32*/>
{};
template<>
struct JsonValue<int64_t> : JsonIntValue<int64_t/*, TYPE_NAME_INT64*/>
{};
template<>
struct JsonValue<uint64_t> : JsonIntValue<uint64_t/*, TYPE_NAME_UINT64*/>
{};
///@}
/**
* Specialization for double.
*/
template<>
struct JsonValue<double>
{
static bool matchesType(const QJsonValue &value) { return value.isDouble(); }
static double value(const QJsonValue &value) noexcept(false)
{
if (!matchesType(value))
throw JsonTypeError("JSON value not of type Double");
return value.toDouble();
}
};
/**
* Specialization for bool.
*/
template<>
struct JsonValue<bool>
{
static bool matchesType(const QJsonValue &value) { return value.isBool(); }
static bool value(const QJsonValue &value) noexcept(false)
{
if (!matchesType(value))
throw JsonTypeError("JSON value not of type Bool");
return value.toBool();
}
};
/**
* Specialization for QString.
*/
template<>
struct JsonValue<QString>
{
static bool matchesType(const QJsonValue &value) { return value.isString(); }
static QString value(const QJsonValue &value) noexcept(false)
{
if (!matchesType(value))
throw JsonTypeError("JSON value not of type String");
return value.toString();
}
};
/**
* Specialization for std::string.
*/
template<>
struct JsonValue<std::string>
{
static bool matchesType(const QJsonValue &value) { return value.isString(); }
static std::string value(const QJsonValue &value)
{
return JsonValue<QString>::value(value).toStdString();
}
};
/**
* Specialization for QJsonObject.
*/
template<>
struct JsonValue<QJsonObject>
{
static bool matchesType(const QJsonValue &value) { return value.isObject(); }
static QJsonObject value(const QJsonValue &value) noexcept(false)
{
if (!matchesType(value))
throw JsonTypeError("JSON value not of type Object");
return value.toObject();
}
};
/**
* Specialization for JsonObject.
*/
template<>
struct JsonValue<JsonObject>
{
static bool matchesType(const QJsonValue &value) { return value.isObject(); }
static JsonObject value(const QJsonValue &value) { return JsonObject(JsonValue<QJsonObject>::value(value)); }
};
/**
* Specialization for Null.
*/
template<>
struct JsonValue<void>
{
static bool matchesType(const QJsonValue &value) { return value.isNull(); }
};
/**
* Specialization for QJsonArray.
*/
template<>
struct JsonValue<QJsonArray>
{
static bool matchesType(const QJsonValue &value) { return value.isArray(); }
static QJsonArray value(const QJsonValue &value) noexcept(false)
{
if (!matchesType(value))
throw JsonTypeError("JSON value not of type Array");
return value.toArray();
}
};
/**
* Specialization for JsonArray.
*/
template<>
struct JsonValue<JsonArray>
{
static bool matchesType(const QJsonValue &value) { return value.isArray(); }
static JsonArray value(const QJsonValue &value) { return JsonArray(JsonValue<QJsonArray>::value(value)); }
};
/**
* Partial specialisation for QVector<T>.
*/
template<class T>
struct JsonValue<QVector<T>>
{
static bool matchesType(const QJsonValue &value)
{
if (!value.isArray())
return false;
const JsonArray array(value.toArray());
return array.isVector<T>();
}
static QVector<T> value(const QJsonValue &value)
{
const JsonArray array(JsonValue<QJsonArray>::value(value));
return array.toVector<T>();
}
};
/**
* Partial specialisation for std::vector<T>.
*/
template<class T>
struct JsonValue<std::vector<T>>
{
static bool matchesType(const QJsonValue &value)
{
return JsonValue<QVector<T>>::matchesType(value);
}
static std::vector<T> value(const QJsonValue &value)
{
const JsonArray array(JsonValue<QJsonArray>::value(value));
return array.toStdVector<T>();
}
};
/**
* Partial specialisation for QMap<QString, T>.
*/
template<class T>
struct JsonValue<QMap<QString, T>>
{
static bool matchesType(const QJsonValue &value)
{
if (!value.isObject())
return false;
const JsonObject obj(JsonValue<QJsonObject>::value(value));
return obj.isMap<T>();
}
static QMap<QString, T> value(const QJsonValue &value)
{
const JsonObject obj(JsonValue<QJsonObject>::value(value));
return obj.toMap<T>();
}
};
/**
* Partial specialisation for std::map<std::string, T>.
*/
template<class T>
struct JsonValue<std::map<std::string, T>>
{
static bool matchesType(const QJsonValue &value)
{
return JsonValue<QMap<QString, T>>::matchesType(value);
}
static std::map<std::string, T> value(const QJsonValue &value)
{
const JsonObject obj(JsonValue<QJsonObject>::value(value));
return obj.toStdMap<T>();
}
};
//==============================================================================
template<class T>
bool JsonObject::contains(const Key &key) const
{
if (!m_json.contains(key))
return false;
return JsonValue<T>::matchesType(m_json.value(key));
}
template<class T>
T JsonObject::value(const Key &key) const noexcept(false)
{
if (!containsAny(key))
throw JsonKeyError("no element with key: " + static_cast<const QString &>(key).toStdString());
try
{
return JsonValue<T>::value(m_json.value(key));
}
catch(const JsonTypeError &typeError)
{
throw JsonTypeError(std::string(typeError.what()) + " - key: " + static_cast<const QString &>(key).toStdString());
}
}
template<class T>
T JsonObject::valueOr(const Key &key, const T &defaultValue) const
{
try
{
return value<T>(key);
}
// Only catch JsonKeyError, JsonTypeError shall be propagated.
catch (const JsonKeyError &)
{
return defaultValue;
}
}
template<class T>
bool JsonObject::isMap() const
{
const QStringList keys = m_json.keys();
for (const auto &key : keys)
if (!JsonValue<T>::matchesType(m_json.value(key)))
return false;
return true;
}
template<class T>
QMap<QString, T> JsonObject::toMap() const
{
QMap<QString, T> result;
for (auto current = m_json.begin(); current != m_json.end(); ++current)
result.insert(current.key(), JsonValue<T>::value(current.value()));
return result;
}
template<class T>
std::map<std::string, T> JsonObject::toStdMap() const
{
std::map<std::string, T> result;
for (auto current = m_json.begin(); current != m_json.end(); ++current)
result.insert({current.key().toStdString(), JsonValue<T>::value(current.value())});
return result;
}
//==============================================================================
template<class T>
bool JsonArray::contains(int index) const noexcept(false)
{
if (index < 0 || index >= m_json.size())
throw JsonRangeError("index out of range: " + std::to_string(index));
return JsonValue<T>::matchesType(m_json.at(index));
}
template<class T>
T JsonArray::at(int index) const noexcept(false)
{
if (index < 0 || index >= m_json.size())
throw JsonRangeError("index out of range: " + std::to_string(index));
return JsonValue<T>::value(m_json.at(index));
}
template<class T>
bool JsonArray::isVector() const
{
for (const QJsonValue &value : m_json)
if (!JsonValue<T>::matchesType(value))
return false;
return true;
}
template<class T>
QVector<T> JsonArray::toVector() const
{
QVector<T> result;
result.reserve(m_json.size());
for (const QJsonValue &value : m_json)
result.push_back(JsonValue<T>::value(value));
return result;
}
template<class T>
std::vector<T> JsonArray::toStdVector() const
{
std::vector<T> result;
result.reserve(m_json.size());
for (const QJsonValue &value : m_json)
result.push_back(JsonValue<T>::value(value));
return result;
}
} // namespace LtsUtils
+14
View File
@@ -0,0 +1,14 @@
#include "lts_portrange.h"
namespace LtsUtils
{
LtsUtils::LtsPortRange fromQPoint(const QPoint &point)
{
if (point.x() < 1025 || point.y() > 65535 || point.x() > point.y())
return {};
return {static_cast<quint16>(point.x()), static_cast<quint16>(point.y())};
}
} // namespace LtsUtils
+15
View File
@@ -0,0 +1,15 @@
#pragma once
#include <LtsUtilsCore/ltsutilscore_export.h>
#include <QPair>
#include <QPoint>
namespace LtsUtils
{
typedef QPair<quint16, quint16> LtsPortRange;
LTSUTILSCORE_EXPORT LtsPortRange fromQPoint(const QPoint &point);
} // namespace LtsUtils
@@ -0,0 +1,36 @@
/**
* @RDE_HEADER@
* A class that contains an identifier string and a dynmically translatable string. Useful for e.g. translating menu groups.
* Menu groups can be shared between plugins and multiple actions and are distinguished by the menu group identifier.
* Menu groups also need to be translatable as the Ui puts actions into group boxes, distinguished by a menu group.
* Menu groups are also used outside of the current Q_OBJECT context, so calling tr(..) would not work. translatedText(..)
* fixes this problem.
* $Id: $
* $HeadURL: $
* $Author: $
*
* Created by Mike Lennart Wille, SLTE, 2022-06-22
*/
#include "lts_translatable_identifier.h"
#include <QCoreApplication>
namespace LtsUtils
{
LtsTranslatableIdentifier::LtsTranslatableIdentifier(const QString &identifier, const QObject *context,
const char *translatableText)
: m_identifier(identifier)
, m_translationContext(context->metaObject()->className())
, m_translatableText(translatableText)
{}
const QString LtsTranslatableIdentifier::translatedText(const char *disambiguation, int n) const
{
if (m_identifier.isEmpty() || m_translationContext == nullptr || m_translatableText == nullptr)
return {};
return QCoreApplication::translate(m_translationContext, m_translatableText, disambiguation, n);
}
} // namespace LtsUtils
@@ -0,0 +1,44 @@
#pragma once
/**
* @RDE_HEADER@
* A class that contains an identifier string and a dynmically translatable string. Useful for e.g. translating menu groups.
* Menu groups can be shared between plugins and multiple actions and are distinguished by the menu group identifier.
* Menu groups also need to be translatable as the Ui puts actions into group boxes, distinguished by a menu group.
* Menu groups are also used outside of the current Q_OBJECT context, so calling tr(..) would not work. translatedText(..)
* fixes this problem.
* $Id: $
* $HeadURL: $
* $Author: $
*
* Created by Mike Lennart Wille, SLTE, 2022-06-22
*/
#include <LtsUtilsCore/ltsutilscore_export.h>
#include <QObject>
#include <QString>
namespace LtsUtils
{
class LTSUTILSCORE_EXPORT LtsTranslatableIdentifier
{
public:
LtsTranslatableIdentifier()
: m_translationContext(nullptr)
, m_translatableText("")
{}
LtsTranslatableIdentifier(const QString &identifier, const QObject *context, const char *translatableText);
const QString &identifier() const { return m_identifier; }
const QString translatedText(const char *disambiguation = nullptr, int n = -1) const;
const QString _context() const { return QString::fromUtf8(m_translationContext); }
private:
QString m_identifier;
const char *m_translationContext;
//const QMetaObject *m_translationContext;
const char *m_translatableText;
};
} // namespace LtsUtils
+20
View File
@@ -0,0 +1,20 @@
/**
* Helper function to check if a position is outdated or not
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-05-03
*/
#pragma once
#include <qglobal.h>
namespace LtsUtils
{
inline bool isPositionTimeOutdated(qint64 curTime, qint64 posTime)
{
// 10min in trainging area, so a little bit more in excon
return curTime > posTime + 11 * 60 * 1000;
}
}
+174
View File
@@ -0,0 +1,174 @@
/**
* Helper class for a vector of integer (stored as integer[] in the postgres database
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-05-03
*/
#include "pgarray.h"
#include <QVector>
#include <QDebug>
#include <functional>
#include <LtsUtilsCore/ltsutilscore_export.h>
namespace LtsUtils
{
template<typename T>
static inline QString convertToPgString(const std::vector<T> &data)
{
if (data.size() == 0)
return QLatin1String("{}");
QString ret(QLatin1Char('{'));
for (T i : data)
ret += QString::number(i) + QLatin1Char(',');
ret.chop(1);
ret += QLatin1Char('}');
return ret;
}
template<>
LTSUTILSCORE_EXPORT QString IntArray::toPgString() const
{
return convertToPgString(m_data);
}
template<>
LTSUTILSCORE_EXPORT QString BigintArray::toPgString() const
{
return convertToPgString(m_data);
}
template<typename T>
static inline T convertFromPgString(const QString &str, const std::function<typename T::Type(const QStringRef &val, bool *bOk)> &convertFunc)
{
typename T::ContainerType result;
int idxStart = str.indexOf(QLatin1Char('{'));
int idxEnd = str.lastIndexOf(QLatin1Char('}'));
if (idxEnd < idxStart)
return T(result);
if (idxStart < 0 && idxEnd < 0)
{
// no {} around - fake midRef call
idxStart = -1;
idxEnd = str.length() + 1;
}
const QStringRef strRef = str.midRef(idxStart + 1, idxEnd - idxStart - 1);
const QVector<QStringRef> vec = strRef.split(QLatin1Char(','), QString::SkipEmptyParts);
result.reserve(vec.size());
for (const QStringRef &s : vec)
{
bool ok = false;
const typename T::Type i = convertFunc(s, &ok);
if (ok)
result.push_back(i);
}
return T(result);
}
template<>
LTSUTILSCORE_EXPORT IntArray IntArray::fromPgString(const QString &str)
{
return convertFromPgString<IntArray>(str,
[](const QStringRef & val, bool * bOk) { return val.toInt(bOk);});
}
template<>
LTSUTILSCORE_EXPORT BigintArray BigintArray::fromPgString(const QString &str)
{
return convertFromPgString<BigintArray>(str,
[](const QStringRef & val, bool * bOk) { return val.toLongLong(bOk);});
}
//
// 2D
//
template<typename T>
static inline QString convertToPgString(const std::map<T, T> &data)
{
if (data.size() == 0)
return QLatin1String("{}");
QString ret(QLatin1Char('{'));
for (const auto &it : data)
{
ret += QLatin1Char('{') + QString::number(it.first) +
QLatin1Char(',') + QString::number(it.second) + QLatin1String("},");
}
ret.chop(1);
ret += QLatin1Char('}');
return ret;
}
template<>
LTSUTILSCORE_EXPORT QString IntArray2D::toPgString() const
{
return convertToPgString<IntArray2D::Type>(m_data);
}
template<>
LTSUTILSCORE_EXPORT QString BigintArray2D::toPgString() const
{
return convertToPgString<BigintArray2D::Type>(m_data);
}
template<typename T>
static inline T convertFromPgString2D(const QString &str, const std::function<typename T::Type(const QStringRef &val)> &convertFunc)
{
typename T::ContainerType ret;
int idxStart = str.indexOf(QLatin1Char('{'));
int idxEnd = str.lastIndexOf(QLatin1Char('}'));
if (idxEnd < idxStart)
return T(ret);
if (idxStart < 0 && idxEnd < 0)
{
// no {} around - fake midRef call
idxStart = -1;
idxEnd = str.length() + 1;
}
// remove first { and last } --> { 1, 2 }, { 4, 3 }, { 10, 1 }
const QStringRef strRef = str.midRef(idxStart + 1, idxEnd - idxStart - 1);
idxStart = strRef.indexOf(QLatin1Char('{'));
while (idxStart >= 0)
{
idxEnd = strRef.indexOf(QLatin1Char('}'), idxStart);
if (idxEnd < 0)
break;
// '{ 1, 2 }' -> ' 1, 2 '
const QStringRef keyValueStr = strRef.mid(idxStart + 1, idxEnd - idxStart - 1);
const QVector<QStringRef> keyValuePair = keyValueStr.split(QLatin1Char(','), QString::SkipEmptyParts);
if (keyValuePair.size() != 2)
break;
const typename T::Type key = convertFunc(keyValuePair.at(0).trimmed());
const typename T::Type value = convertFunc(keyValuePair.at(1).trimmed());
ret.insert(std::make_pair(key, value));
// search next
idxStart = strRef.indexOf(QLatin1Char('{'), idxEnd + 1);
}
return T(ret);
}
template<>
LTSUTILSCORE_EXPORT IntArray2D IntArray2D::fromPgString(const QString &str)
{
return convertFromPgString2D<IntArray2D>(str,
[](const QStringRef & val) { return val.toInt();});
}
template<>
LTSUTILSCORE_EXPORT BigintArray2D BigintArray2D::fromPgString(const QString &str)
{
return convertFromPgString2D<BigintArray2D>(str,
[](const QStringRef & val) { return val.toLongLong();});
}
} // namespace LtsUtils
+50
View File
@@ -0,0 +1,50 @@
/**
* Helper class for a vector of integer (stored as integer[] in the postgres database
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-05-03
*/
#pragma once
#include <QString>
#include <map>
#include <vector>
#include <cstdint>
namespace LtsUtils
{
template<typename T, typename C>
class PgArray
{
public:
using Type = T;
using ContainerType = C;
PgArray() = default;
Q_DECL_CONSTEXPR explicit PgArray(const C &data)
: m_data(data)
{}
Q_DECL_CONSTEXPR C operator()()const
{
return m_data;
}
QString toPgString() const;
static PgArray fromPgString(const QString &str);
private:
C m_data;
};
// helper to convert a postgres array (one-dimensional) into a std::vector<T> and vice versa
// { 1, 5, 5, 2, 9 }
using IntArray = PgArray<int, std::vector<int>>; // integer[]
using BigintArray = PgArray<int64_t, std::vector<int64_t>>; // bigint[]
// helper to convert a two-dimensional postgres array into a std::map<T, T> and vice versa
// { { 1, 2 }, { 4, 3 }, { 10, 1 } }
using IntArray2D = PgArray<int, std::map<int, int>>; // integer[][]
using BigintArray2D = PgArray<int64_t, std::map<int64_t, int64_t>>; // bigint[][]
} // namespace LtsUtils
+127
View File
@@ -0,0 +1,127 @@
/*
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
*/
#include "promise.h"
#include <QTimer>
namespace LtsUtils
{
Promise::Promise(const std::function<void(ResultFunction resolve, ResultFunction reject)> &function, QObject *parent)
: QObject(parent)
, m_functionAlreadySet(false)
, m_timeout(new QTimer(this))
, m_resultSet(false)
, m_timeoutSet(false)
{
m_timeout->setSingleShot(true);
connect(m_timeout, &QTimer::timeout,
this, &Promise::slotTimeout);
setFunction(function);
}
void Promise::setFunction(const std::function<void(ResultFunction resolve, ResultFunction reject)> &function)
{
if (!function || m_functionAlreadySet)
return;
m_functionAlreadySet = true;
if (m_timeoutSet)
m_timeout->start();
// run async (when back to event loop) - so there is enough time to set resolveFn & rejectFn
QTimer::singleShot(0, this, [this, function]()
{
// call function from parameter, to resolve or reject this promise
function([this](const QVariant &value)
{ // function called resolve
resolve(value);
}, [this](const QVariant &value)
{
// function valled reject
reject(value);
});
});
}
void Promise::setTimeout(int interval)
{
m_timeout->stop();
m_timeout->setInterval(interval);
m_timeoutSet = (interval != 0);
if (interval != 0 && m_functionAlreadySet)
m_timeout->start();
}
void Promise::slotTimeout()
{
reject(QStringLiteral("timeout"));
}
void Promise::resolve(const QVariant &value)
{
if (m_resultSet)
return;
m_resultSet = true;
m_timeout->stop();
m_resolveFn(value);
}
void Promise::reject(const QVariant &value)
{
if (m_resultSet)
return;
m_resultSet = true;
m_timeout->stop();
m_rejectFn(value);
}
Promise &Promise::resolved(ResultFunction resolveFn)
{
m_resolveFn = resolveFn;
return *this;
}
Promise &Promise::rejected(ResultFunction rejectFn)
{
m_rejectFn = rejectFn;
return *this;
}
} // namespace LtsUtils
+132
View File
@@ -0,0 +1,132 @@
#pragma once
/*
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
*/
#include <LtsUtilsCore/ltsutilscore_export.h>
#include <QObject>
#include <QVariant>
#include <functional>
class QTimer;
namespace LtsUtils
{
typedef std::function<void(const QVariant &value)> RawResultFunction;
typedef const RawResultFunction &ResultFunction;
/**
* @brief The Promise class is the (sort of) non-retarded brother of QFuture and a clone of Javascript's
* famous Promise. Its purpose is to have a function be executed asynchronously after Qt's event loop was
* entered, and having a positive (resolve) or negative (reject) result after execution.
* Otherwise a new class for every promise-like situation has to be created, maintained, and tested.
* See examples on how to use a Promise here: LtsUtilsCore/promise_examples.h
* Note that C(++) is a static typed language, this makes this C++ version of Promises need a little more
* boilerplate code than the original Javascript implementation. Excuse: But TypeScript causes this, too.
*/
class LTSUTILSCORE_EXPORT Promise : public QObject
{
Q_OBJECT
public:
/**
* @brief Promise creates a promise with the given function - the function is called after Qt entered the event loop
* so that a task can execute asyncronous. The code inside function has to call either resolve(..) or reject(..) (but never both)
* if its execution is done. Calling resolve(..) or reject(..) will call the function given beforehand with resolved(..)
* and / or with reject(..). Failing to call either resolve(..) or reject(..) will result in a promise that
* will never return and will never remove itself from heap. Don't forget to call either one!
* This class can be created without setting a function. But then a function has to be set with with setFunction. Otherwise
* the promise is not executed. The promise is queued for execution if either a function was set in the constructor or
* it was set with setFunction.
* @param function
*/
Promise(const std::function<void(ResultFunction resolve, ResultFunction reject)> &function, QObject *parent = nullptr);
/**
* @brief setFunction sets the function and queues it for execution into the Qt event loop. Do not use if a
* function was alread specified in the constructor, because the constructor already calls setFunction if a
* function was given to the constructor.
* Calling this method more than once, or a function was already specified in the constructor, does nothing.
* @param function
*/
void setFunction(const std::function<void(ResultFunction resolve, ResultFunction reject)> &function);
/**
* @brief resolve must only be called from the function given to the promise's constructor
* @param value the value to give as a parameter to resolveFn (resolveFn is set by calling resolved(..) after the promise was created)
*/
void resolve(const QVariant &value = QVariant());
/**
* @brief reject must only be called from the function given to the promise's constructor
* @param value the value to give as a parameter to rejectFn (rejectFn is set by calling rejected(..) after the promise was created)
*/
void reject(const QVariant &value = QVariant());
/**
* @brief setTimeout starts a timer that when interval is reached, the reject function is called with the value "timeout".
* Useful if you depend on the promise to always deliver, even though the asynchronous functionality behind the function
* is not returning. The timer behind the timeout is started right before the function is executed and is stopped right
* before the function resolves or rejects. Timeout is default off.
* @param interval milliseconds after the function was executed. Or 0 if timeout should be deactivated again.
*/
void setTimeout(int interval);
/**
* @brief resolved sets resolveFn - resolveFn is called if the function given to the promise's constructor calls resolve(..)
* @param resolveFn the function that is called if resolve(..) was called
* @return this promise, so that another ResultFunctin can be set directly, without having to call the promise again
*/
Promise &resolved(ResultFunction resolveFn);
/**
* @brief rejected sets rejectFn - rejectFn is called if the function given to the promise's constructor calls reject(..)
* @param rejectFn the function that is called if reject(..) was called
* @return this promise, so that another ResultFunctin can be set directly, without having to call the promise again
*/
Promise &rejected(ResultFunction rejectFn);
private:
RawResultFunction m_resolveFn;
RawResultFunction m_rejectFn;
bool m_functionAlreadySet;
QTimer *m_timeout;
bool m_resultSet;
bool m_timeoutSet;
private slots:
void slotTimeout();
};
} // namespace LtsUtils
+166
View File
@@ -0,0 +1,166 @@
#include "promise_examples.h"
#include "promise.h"
#include <QTimer>
#include <QTest>
#include <QDebug>
namespace LtsUtils
{
// First Example
void PromiseExample1_Class::doSomethingAsync1()
{
QTimer::singleShot(200, this, [this]()
{
emit signalDone1(true);
});
}
void PromiseExample1_Class::doSomethingAsync2()
{
QTimer::singleShot(450, this, [this]()
{
emit signalDone2(true, 1234);
});
}
PromiseExample1::PromiseExample1(QObject *parent)
: QObject(parent)
, m_success(false)
{
/* We have to work with a class that works asynchronous and need many emits and we have to wait for some results to come in.
* But we want the developer to use this function without rewriting everything that could be handled by a Promise that could be provided. */
auto func = [this](ResultFunction resolve /* call this function if the promise was successful */
, ResultFunction reject /* call this function if the promise was not successful */)
{
// we create the class (or you could use it from somewhere else)
PromiseExample1_Class *c = new PromiseExample1_Class(this);
// we connect to the done signal for the first action (before actually calling the function that can return this signal)
connect(c, &PromiseExample1_Class::signalDone1,
this, [this, c, resolve, reject](bool didThisWork)
{
// did the first action not work?
if (!didThisWork)
{
// something went wrong and we have to exit here with a reject
reject(QStringLiteral("First Action did not succeed")); // .. did not work.. at least tell the enduser what happened (the reject function has to print the string, though) <= promise is done, but failed early
return;
}
else // yes, it worked! we can continue!
{
// after the first action succeeded, we can call the next action, that would otherwise be useless to call if the first call failed
// we do the same thing as with the last signal connect here
connect(c, &PromiseExample1_Class::signalDone2,
this, [resolve, reject](bool didThisWork, int someNumberThatReturns)
{
if (didThisWork) // did the secondary action succeed?
resolve(someNumberThatReturns); // .. then return the returned number to the resolve function <= this is the final point where the promise is done!
else
reject(QStringLiteral("Second Action did not succeed")); // .. did not work.. at least tell the enduser what happened (the reject function has to print the string, though) <= promise is done, but failed
// everything done!
});
c->doSomethingAsync2(); // now, finally, execute the second action - should call signalDone2 soon!
}
});
c->doSomethingAsync1(); // now, finally, execute this async stuff and then signalDone1 should be called soon!
};
Promise *p = new Promise(func, this);
// the promise was created, but will not execute until Qt is back into the event loop
// and to receive resolve(..) or reject(..) messages, we have to connect to them first (but note that this works without connects)
p->resolved([this](const QVariant &value)
{
// "someNumberThatReturns" is inside value
qDebug() << "example1 promise succeeded! 'someNumberThatReturns' was" << value;
m_success = true;
}).rejected([](const QVariant &value) // <= you can directly call rejected(..) on resolved(..) to save time, as resolved(..) returns the promise itself, just like in js
{
// one or both of the actions didn't work - tell the user where it failed
qDebug() << "example1 promise failed.." << value;
});
}
// End of First Example
// Second Example
PromiseExample2::PromiseExample2(QObject *parent)
: QObject(parent)
, m_success(false)
{
/* There is an asynchonous function inside the Promise that might not resolve because of some error.
* A timeout is set for the Promise to reject so it doesn't get stuck. */
Promise *p = new Promise([](ResultFunction , ResultFunction )
{
// never resolve for this test case
}, this);
p->setTimeout(1000);
p->resolved([](const QVariant &)
{
QFAIL("This test case's promise should never resolve");
});
p->rejected([this](const QVariant &val)
{
QVERIFY2(val == QStringLiteral("timeout"), "Promise didn't reject with string \"timeout\"");
qDebug() << "Promise rejected as expected";
m_success = true;
});
}
// End of Second Example
// 3rd Example
PromiseExample3::PromiseExample3(QObject *parent)
: QObject(parent)
, m_success(true)
, m_alreadyRejected(false)
{
/* The promise's function failed with a reject, additionally a timeout was set
* to reject a promise that might never resolve/reject. After the promise was rejected
* the timeout should never trigger (because the promise was already rejected). */
Promise *p = new Promise([](ResultFunction , ResultFunction reject)
{
reject(QString()); // immediately reject
}, this);
p->setTimeout(1000); // set timeout
p->resolved([](const QVariant &)
{
QFAIL("This test case's promise should never resolve");
});
p->rejected([this](const QVariant &val)
{
qDebug() << "rejected with val" << val;
QVERIFY2(val == QString(), "Promise didn't reject with an expected empty string.");
QVERIFY2(!m_alreadyRejected, "Promise rejected more than once");
m_alreadyRejected = true;
qDebug() << "Promise rejected as expected";
});
}
// End of 3rd Example
} // namespace LtsUtils
+70
View File
@@ -0,0 +1,70 @@
#pragma once
#include <QObject>
namespace LtsUtils
{
// First Example
class PromiseExample1_Class : public QObject
{
Q_OBJECT
public:
using QObject::QObject;
void doSomethingAsync1();
void doSomethingAsync2();
signals:
void signalDone1(bool didThisWork);
void signalDone2(bool didThisWork, int someNumberThatReturns);
};
class PromiseExample1 : public QObject
{
Q_OBJECT
public:
explicit PromiseExample1(QObject *parent = nullptr);
bool isSuccess() const { return m_success; }
private:
bool m_success;
};
// End of First Example
// Second Example
class PromiseExample2 : public QObject
{
Q_OBJECT
public:
explicit PromiseExample2(QObject *parent = nullptr);
bool isSuccess() const { return m_success; }
private:
bool m_success;
};
// End of Second Example
// 3rd Example
class PromiseExample3 : public QObject
{
Q_OBJECT
public:
explicit PromiseExample3(QObject *parent = nullptr);
bool isSuccess() const { return m_success; }
private:
bool m_success;
bool m_alreadyRejected;
};
// End of 3rd Example
} // namespace LtsUtils
+288
View File
@@ -0,0 +1,288 @@
/**
* Convenient functions to get/set bytes in a QByteArray
* $Id$
* $HeadURL$
* $Author$
* Created by Philipp Dassow, NLST, 2018-02-20
*/
#pragma once
#include <QtCore/QByteArray>
#include <limits>
namespace LtsUtils
{
/**
* Basiscodierungen
*/
// 8 Bytes setzen/lesen
template<typename T>
void inline set8Byte(const T in, QByteArray& out)
{
static_assert(std::is_integral<T>::value || std::is_enum<T>::value, "Only integrals allowed for raw byte accessd");
out += (in & 0xFF00000000000000UL) >> 56;
out += (in & 0x00FF000000000000UL) >> 48;
out += (in & 0x0000FF0000000000UL) >> 40;
out += (in & 0x000000FF00000000UL) >> 32;
out += (in & 0x00000000FF000000UL) >> 24;
out += (in & 0x0000000000FF0000UL) >> 16;
out += (in & 0x000000000000FF00UL) >> 8;
out += (in & 0x00000000000000FFUL);
}
template<>
void inline set8Byte(const double in, QByteArray &out)
{
union hack
{
double d;
int64_t i;
} h;
h.d = in;
set8Byte(h.i, out);
}
template<typename T>
bool inline get8Byte(const QByteArray& baData,
size_t &pos,
T& out)
{
static_assert(std::is_integral<T>::value || std::is_enum<T>::value, "Only integrals allowed for raw byte accessd");
static_assert(sizeof(T) >= 8, "sizeof(out) is smaller than 8 bytes - will not fit");
out = T();
if(size_t(baData.size()) < pos + 8)
return false;
const char *data = baData.data() + pos;
// Signed-Bit korrekt expandieren wenn nötig
if(std::numeric_limits<T>::is_signed)
out |= static_cast<signed long long>(data[0]) << 56;
else
out |= (data[0] & 0xFFULL) << 56;
out |= (data[1] & 0xFFULL) << 48;
out |= (data[2] & 0xFFULL) << 40;
out |= (data[3] & 0xFFULL) << 32;
out |= (data[4] & 0xFFULL) << 24;
out |= (data[5] & 0xFFULL) << 16;
out |= (data[6] & 0xFFULL) << 8;
out |= (data[7] & 0xFFULL);
pos += 8;
return true;
}
template<>
bool inline get8Byte(const QByteArray& baData,
size_t &pos,
double& out)
{
out = 0.;
union hack
{
double d;
int64_t i;
} h;
if (!get8Byte(baData, pos, h.i))
return false;
out = h.d;
return true;
}
template<typename T>
bool inline get8Byte(const QByteArray& data, T& out)
{
size_t pos = 0;
return get8Byte(data, pos, out);
}
// 4 Bytes setzen/lesen
template<typename T>
void inline set4Byte(const T in, QByteArray& out)
{
static_assert(std::is_integral<T>::value || std::is_enum<T>::value, "Only integrals allowed for raw byte accessd");
out += (in & 0xFF000000) >> 24;
out += (in & 0x00FF0000) >> 16;
out += (in & 0x0000FF00) >> 8;
out += (in & 0x000000FF);
}
template<>
void inline set4Byte(const double in, QByteArray& out)
{
int tmp = qRound(in);
return set4Byte(tmp, out);
}
template<typename T>
bool inline get4Byte(const QByteArray& baData,
size_t &pos,
T& out)
{
static_assert(std::is_integral<T>::value || std::is_enum<T>::value, "Only integrals allowed for raw byte accessd");
static_assert(sizeof(T) >= 4, "sizeof(out) is smaller than 4 bytes - will not fit");
out = T();
if(size_t(baData.size()) < pos + 4)
return false;
const char *data = baData.data() + pos;
// Signed-Bit korrekt expandieren wenn nötig
if(std::numeric_limits<T>::is_signed)
out |= data[0] << 24;
else
out |= (data[0] & 0xFFU) << 24;
out |= (data[1] & 0xFFU) << 16;
out |= (data[2] & 0xFFU) << 8;
out |= (data[3] & 0xFFU);
pos += 4;
return true;
}
template<>
bool inline get4Byte(const QByteArray& baData,
size_t &pos,
double& out)
{
out = 0.;
int tmp;
if(!get4Byte(baData, pos, tmp))
return false;
out = tmp;
return true;
}
template<typename T>
bool inline get4Byte(const QByteArray& data, T& out)
{
size_t pos = 0;
return get4Byte(data, pos, out);
}
// 3 Bytes setzen/lesen
template<typename T>
void inline set3Byte(const T in, QByteArray& out)
{
static_assert(std::is_integral<T>::value || std::is_enum<T>::value, "Only integrals allowed for raw byte accessd");
out += (in & 0x00FF0000) >> 16;
out += (in & 0x0000FF00) >> 8;
out += (in & 0x000000FF);
}
template<typename T>
bool inline get3Byte(const QByteArray& baData,
size_t &pos,
T& out)
{
static_assert(std::is_integral<T>::value || std::is_enum<T>::value, "Only integrals allowed for raw byte accessd");
static_assert(sizeof(T) >= 3, "sizeof(out) is smaller than 3 bytes - will not fit");
out = T();
if(size_t(baData.size()) < pos + 3)
return false;
const char *data = baData.data() + pos;
// Signed-Bit korrekt expandieren wenn nötig
if(std::numeric_limits<T>::is_signed)
out |= data[0] << 16;
else
out |= (data[0] & 0xFFU) << 16;
out |= (data[1] & 0xFFU) << 8;
out |= (data[2] & 0xFFU);
pos += 3;
return true;
}
template<typename T>
bool inline get3Byte(const QByteArray& data, T& out)
{
size_t pos = 0;
return get3Byte(data, pos, out);
}
// 2 Bytes setzen/lesen
template<typename T>
void inline set2Byte(const T in, QByteArray& out)
{
static_assert(std::is_integral<T>::value || std::is_enum<T>::value, "Only integrals allowed for raw byte accessd");
out += (in & 0x0000FF00) >> 8;
out += (in & 0x000000FF);
}
template<typename T>
bool inline get2Byte(const QByteArray& baData,
size_t &pos,
T& out)
{
static_assert(std::is_integral<T>::value || std::is_enum<T>::value, "Only integrals allowed for raw byte accessd");
static_assert(sizeof(T) >= 2, "sizeof(out) is smaller than 2 bytes - will not fit");
out = T();
if(size_t(baData.size()) < pos + 2)
return false;
const char *data = baData.data() + pos;
// Signed-Bit korrekt expandieren wenn nötig
if(std::numeric_limits<T>::is_signed)
out |= data[0] << 8;
else
out |= (data[0] & 0xFFU) << 8;
out |= data[1] & 0xFFU;
pos += 2;
return true;
}
template<typename T>
bool inline get2Byte(const QByteArray& data, T& out)
{
size_t pos = 0;
return get2Byte(data, pos, out);
}
// 1 Byte setzen/lesen
template<typename T>
void inline set1Byte(const T in, QByteArray& out)
{
static_assert(std::is_integral<T>::value || std::is_enum<T>::value, "Only integrals allowed for raw byte accessd");
unsigned char c = in;
out += c;
}
template<typename T>
bool inline get1Byte(const QByteArray& baData,
size_t &pos,
T& out)
{
static_assert(std::is_integral<T>::value || std::is_enum<T>::value, "Only integrals allowed for raw byte accessd");
out = T();
if(size_t(baData.size()) < pos + 1)
return false;
const char *data = baData.data() + pos;
// Signed-Bit korrekt expandieren wenn nötig
if(std::numeric_limits<T>::is_signed)
out = (T)(data[0]);
else
out = (T)(data[0] & 0xFF);
pos += 1;
return true;
}
template<typename T>
bool inline get1Byte(const QByteArray& data, T& out)
{
size_t pos = 0;
return get1Byte(data, pos, out);
}
}
+88
View File
@@ -0,0 +1,88 @@
#include "qplane3d.h"
#include <cmath>
namespace LtsUtils
{
///////////////////////////////////////////////////////////////////////////////////////////////////
// QPlane3D
//
QPlane3D::QPlane3D(const QPoint3D &pos, const QPoint3D &vec1, const QPoint3D &vec2)
: m_pos(pos)
, m_vec1(vec1)
, m_vec2(vec2)
{
m_normal = QPoint3D::crossProduct(m_vec1, m_vec2);
m_planeC = QPoint3D::dotProduct(m_pos, m_normal);
}
QPoint3D QPlane3D::intersection(const QPoint3D &point, const QPoint3D &direction) const
{
const double dotProduct = QPoint3D::dotProduct(direction, m_normal);
if (qFuzzyIsNull(dotProduct))
return inPlane(point) ? point : QPoint3D();
const double distance = distanceTo(point) / std::abs(dotProduct);
return point + distance * direction;
}
bool QPlane3D::intersects(const QPoint3D &direction) const
{
return !isParallel(direction);
}
bool QPlane3D::isParallel(const QPoint3D &direction) const
{
return qFuzzyIsNull(QPoint3D::dotProduct(m_normal, direction));
}
bool QPlane3D::inPlane(const QPoint3D &point) const
{
if (point == m_pos)
return true;
return qFuzzyIsNull(QPoint3D::dotProduct(m_normal, point - m_pos));
}
double QPlane3D::distanceTo(const QPoint3D &point) const
{
return QPoint3D::dotProduct(point - m_pos, m_normal);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// QPolygon3D
//
QPolygon3D::QPolygon3D(const QVector<QPoint3D> &vertices)
: QPlane3D(vertices[0], vertices[1], vertices[2])
, m_vertices(vertices)
{
}
bool QPolygon3D::containsPoint(const QPoint3D &point) const
{
if (!inPlane(point))
return false;
if (contains(point))
return true;
double angleSum = 0.;
double cosTheta = 0.;
const int numberOfVertices = m_vertices.size();
for (int i = 0; i < m_vertices.size(); ++i)
{
const QPoint3D &first = m_vertices[i % numberOfVertices] - point;
const QPoint3D &second = m_vertices[(i + 1) % numberOfVertices] - point;
cosTheta = QPoint3D::dotProduct(first, second) / (first.length() * second.length());
angleSum += acos(cosTheta);
} // for
return qFuzzyIsNull(M_2_PI - angleSum);
}
} // namespace LtsUtils
+65
View File
@@ -0,0 +1,65 @@
/**
* Helper class since QVector3D does not provide rX() functions and only has float as data type
* It's a simple copy of QPointF with the z extension + removed (currently) unneeded functionality
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-05-03
*/
#pragma once
#include "qpoint3d.h"
#include <LtsUtilsCore/ltsutilscore_export.h>
#include <QVector>
#include <QPointF>
namespace LtsUtils
{
class LTSUTILSCORE_EXPORT QPlane3D
{
public:
QPlane3D(const QPoint3D &pos, const QPoint3D &vec1, const QPoint3D &vec2);
QPoint3D intersection(const QPoint3D &point, const QPoint3D &direction) const;
bool intersects(const QPoint3D &direction) const;
bool isParallel(const QPoint3D &direction) const;
bool inPlane(const QPoint3D &point) const;
double distanceTo(const QPoint3D &point) const;
const QPoint3D &pos() const { return m_pos; }
const QPoint3D &vec1() const { return m_vec1; }
const QPoint3D &vec2() const { return m_vec2; }
const QPoint3D &normal() const { return m_normal; }
double planeC() const { return m_planeC; }
protected:
QPoint3D m_pos;
QPoint3D m_vec1;
QPoint3D m_vec2;
QPoint3D m_normal;
double m_planeC;
};
class LTSUTILSCORE_EXPORT QPolygon3D : public QPlane3D
{
public:
QPolygon3D(const QVector<QPoint3D> &vertices);
bool contains(const QPoint3D &point) const { return m_vertices.contains(point); }
bool containsPoint(const QPoint3D &point) const;
private:
QVector<QPoint3D> m_vertices;
};
} // namespace LtsUtils
+53
View File
@@ -0,0 +1,53 @@
/**
* Helper class since QVector3D does not provide rX() functions and only has float as data type
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-05-03
*/
#include "qpoint3d.h"
#include <QDataStream>
using namespace LtsUtils;
double QPoint3D::distanceToLine(const QPoint3D &point, const QPoint3D &direction) const
{
if (direction.isNull())
return (*this - point).length();
QPoint3D p = point + dotProduct(*this - point, direction) * direction;
return (*this - p).length();
}
double QPoint3D::distanceToPlane(const QPoint3D &plane, const QPoint3D &normal) const
{
return dotProduct(*this - plane, normal);
}
double QPoint3D::distanceToPlane(const QPoint3D &plane1, const QPoint3D &plane2, const QPoint3D &plane3) const
{
QPoint3D n = normal(plane2 - plane1, plane3 - plane1);
return dotProduct(*this - plane1, n);
}
double QPoint3D::distanceToPoint(const QPoint3D &point) const
{
return (*this - point).length();
}
QDataStream &operator<<(QDataStream &s, const QPoint3D &p)
{
s << p.x() << p.y() << p.z();
return s;
}
QDataStream &operator>>(QDataStream &s, QPoint3D &p)
{
double x, y, z;
s >> x; p.rx() = x;
s >> y; p.ry() = y;
s >> z; p.rz() = z;
return s;
}
+304
View File
@@ -0,0 +1,304 @@
/**
* Helper class since QVector3D does not provide rX() functions and only has float as data type
* It's a simple copy of QPointF with the z extension + removed (currently) unneeded functionality
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-05-03
*/
#pragma once
#include <sstream>
#include <cmath>
#include <LtsUtilsCore/ltsutilscore_export.h>
#include <QtCore/qglobal.h>
#include <QtCore/qpoint.h> // QPointF
namespace LtsUtils
{
class QPoint3D
{
public:
enum class Axis { X, Y, Z };
Q_DECL_CONSTEXPR QPoint3D() = default;
Q_DECL_CONSTEXPR QPoint3D(double xpos, double ypos, double zpos);
template<typename T>
explicit QPoint3D(const T &data)
: xp(data.offsetX())
, yp(data.offsetY())
, zp(data.offsetZ())
{}
inline bool isNull() const;
Q_DECL_CONSTEXPR inline double x() const;
Q_DECL_CONSTEXPR inline double y() const;
Q_DECL_CONSTEXPR inline double z() const;
Q_DECL_RELAXED_CONSTEXPR inline void setX(double xpos);
Q_DECL_RELAXED_CONSTEXPR inline void setY(double ypos);
Q_DECL_RELAXED_CONSTEXPR inline void setZ(double zpos);
Q_DECL_RELAXED_CONSTEXPR inline double &rx();
Q_DECL_RELAXED_CONSTEXPR inline double &ry();
Q_DECL_RELAXED_CONSTEXPR inline double &rz();
Q_DECL_CONSTEXPR inline QPointF toPointF() const; // QPointF support
static QPoint3D fromQPointF(const QPointF &p) // QPointF support
{
return QPoint3D(p.x(), p.y(), .0);
}
inline double lengthSquared() const { return (xp * xp) + (yp * yp) + (zp * zp); }
inline double length() const { return std::sqrt(lengthSquared()); }
inline void normalize()
{
double len = xp * xp + yp * yp + zp * zp;
if (qFuzzyIsNull(len - 1.0f) || qFuzzyIsNull(len))
return;
len = std::sqrt(len);
xp /= len;
yp /= len;
zp /= len;
}
inline QPoint3D normalized() const
{
double len = xp * xp + yp * yp + zp * zp;
if (qFuzzyIsNull(len - 1.0f))
return *this;
else if (!qFuzzyIsNull(len))
return *this / std::sqrt(len);
else
return QPoint3D(*this);
}
inline void rotate(const double &phi, const Axis &axis)
{
const double sin = std::sin(phi);
const double cos = std::cos(phi);
switch (axis)
{
case Axis::X:
{
const double _yp = (cos * yp) - (sin * zp);
const double _zp = (sin * yp) + (cos * zp);
yp = _yp;
zp = _zp;
break;
}
case Axis::Y:
{
const double _xp = (cos * xp) + (sin * zp);
const double _zp = (-sin * xp) + (cos * zp);
xp = _xp;
zp = _zp;
break;
}
case Axis::Z:
{
const double _xp = (cos * xp) - (sin * yp);
const double _yp = (sin * xp) + (cos * yp);
xp = _xp;
yp = _yp;
break;
}
}
}
inline QPoint3D rotated(const double &phi, const Axis &axis) const
{
QPoint3D p(*this);
p.rotate(phi, axis);
return p;
}
inline double distanceToLine(const QPoint3D &point, const QPoint3D &direction) const;
inline double distanceToPlane(const QPoint3D &plane, const QPoint3D &normal) const;
inline double distanceToPlane(const QPoint3D &plane1, const QPoint3D &plane2, const QPoint3D &plane3) const;
inline double distanceToPoint(const QPoint3D &point) const;
friend Q_DECL_CONSTEXPR inline bool operator==(const QPoint3D &p1, const QPoint3D &p2);
friend Q_DECL_CONSTEXPR inline bool operator!=(const QPoint3D &p1, const QPoint3D &p2);
friend Q_DECL_CONSTEXPR inline QPoint3D operator +(const QPoint3D &p1, const QPoint3D &p2)
{
return QPoint3D(p1.xp + p2.xp, p1.yp + p2.yp, p1.zp + p2.zp);
}
friend Q_DECL_CONSTEXPR inline QPoint3D operator -(const QPoint3D &p1, const QPoint3D &p2)
{
return QPoint3D(p1.xp - p2.xp, p1.yp - p2.yp, p1.zp - p2.zp);
}
friend Q_DECL_CONSTEXPR inline QPoint3D operator *(const QPoint3D &p1, double factor)
{
return QPoint3D(p1.xp * factor, p1.yp * factor, p1.zp * factor);
}
friend Q_DECL_CONSTEXPR inline QPoint3D operator *(double factor, const QPoint3D &p1)
{
return QPoint3D(p1.xp * factor, p1.yp * factor, p1.zp * factor);
}
friend Q_DECL_CONSTEXPR inline QPoint3D operator /(const QPoint3D &p1, double factor)
{
return QPoint3D(p1.xp / factor, p1.yp / factor, p1.zp / factor);
}
Q_DECL_RELAXED_CONSTEXPR inline QPoint3D &operator +=(const QPoint3D &o)
{
xp += o.xp;
yp += o.yp;
zp += o.zp;
return *this;
}
Q_DECL_RELAXED_CONSTEXPR inline QPoint3D &operator -=(const QPoint3D &o)
{
xp -= o.xp;
yp -= o.yp;
zp -= o.zp;
return *this;
}
Q_DECL_RELAXED_CONSTEXPR inline QPoint3D &operator *=(double factor)
{
xp *= factor;
yp *= factor;
zp *= factor;
return *this;
}
Q_DECL_RELAXED_CONSTEXPR inline QPoint3D &operator /=(double divisor)
{
xp /= divisor;
yp /= divisor;
zp /= divisor;
return *this;
}
Q_DECL_CONSTEXPR static inline double dotProduct(const QPoint3D& v1, const QPoint3D& v2)
{
return v1.xp * v2.xp + v1.yp * v2.yp + v1.zp * v2.zp;
}
Q_DECL_CONSTEXPR static inline QPoint3D crossProduct(const QPoint3D& v1, const QPoint3D& v2)
{
return QPoint3D(v1.yp * v2.zp - v1.zp * v2.yp,
v1.zp * v2.xp - v1.xp * v2.zp,
v1.xp * v2.yp - v1.yp * v2.xp);
}
static inline QPoint3D normal(const QPoint3D &v1, const QPoint3D &v2)
{
return crossProduct(v1, v2).normalized();
}
static inline QPoint3D normal(const QPoint3D &v1, const QPoint3D &v2, const QPoint3D &v3)
{
return crossProduct((v2 - v1), (v3 - v1)).normalized();
}
Q_DECL_CONSTEXPR friend inline bool qFuzzyCompare(const QPoint3D& v1, const QPoint3D& v2);
private:
double xp = 0;
double yp = 0;
double zp = 0;
};
/*****************************************************************************
QPoint3D stream functions
*****************************************************************************/
LTSUTILSCORE_EXPORT QDataStream &operator<<(QDataStream &s, const QPoint3D &p);
LTSUTILSCORE_EXPORT QDataStream &operator>>(QDataStream &s, QPoint3D &p);
/*****************************************************************************
QPoint3D inline functions
*****************************************************************************/
Q_DECL_CONSTEXPR inline QPoint3D::QPoint3D(double xpos, double ypos, double zpos) : xp(xpos), yp(ypos), zp(zpos) { }
inline bool QPoint3D::isNull() const
{
return qIsNull(xp) && qIsNull(yp) && qIsNull(zp);
}
Q_DECL_CONSTEXPR inline double QPoint3D::x() const
{
return xp;
}
Q_DECL_CONSTEXPR inline double QPoint3D::y() const
{
return yp;
}
Q_DECL_CONSTEXPR inline double QPoint3D::z() const
{
return zp;
}
Q_DECL_RELAXED_CONSTEXPR inline void QPoint3D::setX(double xpos)
{
xp = xpos;
}
Q_DECL_RELAXED_CONSTEXPR inline void QPoint3D::setY(double ypos)
{
yp = ypos;
}
Q_DECL_RELAXED_CONSTEXPR inline void QPoint3D::setZ(double zpos)
{
zp = zpos;
}
Q_DECL_RELAXED_CONSTEXPR inline double &QPoint3D::rx()
{
return xp;
}
Q_DECL_RELAXED_CONSTEXPR inline double &QPoint3D::ry()
{
return yp;
}
Q_DECL_RELAXED_CONSTEXPR inline double &QPoint3D::rz()
{
return zp;
}
Q_DECL_CONSTEXPR inline QPointF QPoint3D::toPointF() const
{
return QPointF(xp, yp);
}
Q_DECL_CONSTEXPR inline bool operator==(const QPoint3D &p1, const QPoint3D &p2)
{
return p1.xp == p2.xp && p1.yp == p2.yp && p1.zp == p2.zp;
}
Q_DECL_CONSTEXPR inline bool operator!=(const QPoint3D &p1, const QPoint3D &p2)
{
return p1.xp != p2.xp || p1.yp != p2.yp || p1.zp != p2.zp;
}
Q_DECL_CONSTEXPR inline bool qFuzzyCompare(const QPoint3D& v1, const QPoint3D& v2)
{
return ::qFuzzyCompare(v1.xp, v2.xp) &&
::qFuzzyCompare(v1.yp, v2.yp) &&
::qFuzzyCompare(v1.zp, v2.zp);
}
inline std::ostream& operator<< (std::ostream& stream, const QPoint3D& pos)
{
stream << "(" << pos.x() << "," << pos.y() << "," << pos.z() << ")";
return stream;
}
} // namespace LtsUtils
Q_DECLARE_TYPEINFO(LtsUtils::QPoint3D, Q_MOVABLE_TYPE);
+28
View File
@@ -0,0 +1,28 @@
/**
* @RDE_HEADER@
* ScopedConnection
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Fröbel, SLSE, 2017-07-24
*/
#include "scoped_connection.h"
#include "connection.h"
namespace LtsUtils
{
ScopedConnection::ScopedConnection(IConnection *connection, Qt::ConnectionType connType)
: m_conn(connection)
{
Q_ASSERT(connection);
m_conn->connect(connType);
}
ScopedConnection::~ScopedConnection()
{
m_conn->disconnect();
}
} // namespace LtsUtils
+35
View File
@@ -0,0 +1,35 @@
/**
* @RDE_HEADER@
* ScopedConnection
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Fröbel, SLSE, 2017-07-24
*/
#pragma once
#include <LtsUtilsCore/ltsutilscore_export.h>
#include <Qt>
namespace LtsUtils
{
class IConnection;
/**
* A small janitor that connects the given IConnection in the constructor
* and disconnects it in the destructor.
* @author Christian Fröbel
*/
class LTSUTILSCORE_EXPORT ScopedConnection
{
public:
explicit ScopedConnection(IConnection *connection, Qt::ConnectionType = Qt::AutoConnection);
~ScopedConnection();
private:
IConnection *m_conn;
};
} // namespace LtsUtils
@@ -0,0 +1,372 @@
/**
* Helper to convert a QByteArray into a SFM-Message and vice versa
*
* $Id$
* $HeadURL$
* $Author$
* Created by Philipp Dassow, SLSE, 2018-01-23
*/
#include "sfm_bytemessage_converter.h"
namespace LtsUtils
{
// ------------------- Converter -------------------
static constexpr unsigned char XON = 0x11;
static constexpr unsigned char XOFF = 0x13;
static constexpr unsigned char STX = 0x02;
static constexpr unsigned char ETX = 0x03;
static constexpr unsigned char SUBST = 0x80;
static constexpr unsigned char SUB_XON = 0x91;
static constexpr unsigned char SUB_XOFF = 0x93;
static constexpr unsigned char SUB_STX = 0x82;
static constexpr unsigned char SUB_ETX = 0x83;
static constexpr unsigned char SUB_SUBST = 0x00;
static constexpr int HEADER_SIZE = 3;///< 2 Byte ID mit Rtr + 1 Byte Laenge
static constexpr int CHECKSUM_SIZE = 1;///< In Byte
static constexpr int MINIMUM_BYTE_SIZE = 1 + HEADER_SIZE + CHECKSUM_SIZE + 1; // STX + Header + Checksum +
/**
* \brief Initialisiert ein SfmMessage-Objekt
*
* \author B. Ziegler
*
* \param message Enthaelt die reinen Applikationsdaten der Nachricht
*
* \return SfmMessage
*/
static SfmMessage getSfmMessage(const QByteArray &p_data)
{
/// Einzelwerte
unsigned short id = 0;
id |= (((unsigned short)p_data[0]) & 0x7F) << 8; ///< High Anteil ID
id |= (((unsigned short)p_data[1]) & 0xFF) << 0; ///< Low Anteil ID
SfmMessage msg;
msg.setId(id);
msg.setRtr(p_data[0]&0x80); // RTR-bit
msg.setPayload(p_data.right(p_data.size() - HEADER_SIZE)); ///< Header und Payload trennen
return msg;
}
/**
* \brief Erzeugt aus einer SfmMessage Nachricht ein ByteArray mit den Headerinformationen
*
* \author B. Ziegler
*
* Bei den Header Inforamtionen werden das RTR und die ID mit einbezogen
*
* \param message Die Nachricht zu der die Headerinformationen erzeugt werden sollen
*
* \return Ein ByteArray mit dem Header
*/
static QByteArray getHeader(const SfmMessage &p_msg)
{
QByteArray header(2, Qt::Uninitialized);
/// Highbyte, RTR-Bit ist, wenn noetig, schon in der msg-Id drin
header[0] = ((p_msg.id() >> 8) & 0x7f) | (p_msg.rtr() << 7); ///< High-Anteil der ID inklusive RTR
/// Lowbyte
header[1] = (p_msg.id() >> 0) & 0xff; ///< Low-Anteil der ID
return header;
}
/**
* \brief Erzeugt eine Checksumme
*
* \author B. Ziegler
*
* \param message Basis fuer die zu erzeugende Checksumme
*
* \return Die entsprechende Checksumme
*/
static inline char getChecksum(const QByteArray &p_data)
{
char sum = 0;
/// Summierung der Bytes
for (const char c : p_data)
sum += c;
/// Negierung der Summe
return sum ^ 0xff;
}
/**
* \briefPrueft und entfernt die Checksumme einer Nachricht
*
* \author B. Ziegler
*
* \param message Die Nachricht bei der die Checksumme geprueft/entfernt werden soll
* Hierbei muessen die Daten der Nachricht vorliegen (ohne Steuerzeichen, etc.)
*
* \return Die 'message' ohne Checksumme
*/
static QByteArray removeChecksum(const QByteArray &p_data)
{
QByteArray withoutChecksum(p_data);
ConversionError error ("Checksum error");
if (p_data.size() >= CHECKSUM_SIZE)
{
withoutChecksum.chop(1); ///< Checksumme entfernen
/// Checksumme pruefen
if (p_data.at(p_data.size() - 1) != getChecksum(withoutChecksum))
throw error;
}
else
throw error;
return withoutChecksum;
}
/**
* \brief Maskiert/Substituiert die Daten
*
* \author B. Ziegler
*
* \param data die zu maskierenden Daten
*
* \return Die maskierten Daten
*/
static QByteArray maskData(const QByteArray &p_data)
{
QByteArray maskData;
maskData.reserve(p_data.size() * 8 / 6);
for (const unsigned char byte : p_data)
{
switch (byte)
{
case XON:
maskData.append(SUBST);
maskData.append(SUB_XON);
break;
case XOFF:
maskData.append(SUBST);
maskData.append(SUB_XOFF);
break;
case STX:
maskData.append(SUBST);
maskData.append(SUB_STX);
break;
case ETX:
maskData.append(SUBST);
maskData.append(SUB_ETX);
break;
case SUBST:
maskData.append(SUBST);
maskData.append(static_cast<char>(SUB_SUBST));
break;
default:
maskData.append(byte);
break;
}
}
return maskData;
}
static bool mustBeMasked(const unsigned char byte)
{
switch (byte)
{
case XON:
case XOFF:
case STX:
case ETX:
case SUBST:
return true;
default:
break;
}
return false;
}
int lenMaskedData(const QByteArray &data)
{
int ret = data.length();
const unsigned char upperLen = ((data.length() & 0xff00) >> 8);
const unsigned char lowerLen = ((data.length() & 0x00ff) >> 0);
if (mustBeMasked(upperLen))
++ret;
if (mustBeMasked(lowerLen))
++ret;
for (const unsigned char byte : data)
{
if (mustBeMasked(byte))
++ret;
}
if (mustBeMasked(getChecksum(data)))
++ret;
return ret;
}
/**
* \brief Demaskiert/Desubstituiert die Daten
*
* \author B. Ziegler
*
* \param data Die zu demaskierenden Daten
*
* \return Die demaskierten Daten
*/
static QByteArray unmaskData(const QByteArray &p_data)
{
QByteArray unmaskData;
unmaskData.reserve(p_data.size());
for (int position = 0; position < p_data.size(); ++position)
{
unsigned char byte = p_data.at(position);
if (byte == SUBST)
{
++position;
byte = p_data.at(position);
switch (byte)
{
case SUB_XON: unmaskData.append(XON); break;
case SUB_XOFF: unmaskData.append(XOFF); break;
case SUB_STX: unmaskData.append(STX); break;
case SUB_ETX: unmaskData.append(ETX); break;
case SUB_SUBST: unmaskData.append(SUBST); break;
default: break; ///< Unbekanntes Zeichen
}
}
else
unmaskData.append(byte);
}
return unmaskData;
}
/**
* \brief Fuegt das Telegramframing ein
*
* \author B. Ziegler
*
* \param message Einen maskierte Nachricht
*
* \return Die entsprechende Nachricht inklusive Framing
*/
static QByteArray addFrame(const QByteArray &p_data)
{
QByteArray ret;
ret.reserve(2 + p_data.size());
ret.append(STX);
ret.append(p_data);
ret.append(ETX);
return ret;
}
/**
* \brief Prueft ob ein Frame vorhanden ist und entfernt diesen ggf.
*
* \author B. Ziegler
*
* \param message Die Nachricht bei der das Framing geprueft/entfernt werden soll
*
* \return Die Nachricht ohne Frameinformationen
*/
static QByteArray removeFrame(const QByteArray &p_data)
{
/// Pruefen ob STX/ETX am Anfang/Ende vorliegen
if (p_data[0] == STX && p_data[p_data.size() - 1] == ETX)
{
///< Remove STX (erstes Byte) + ETX (letztes Byte)
return p_data.mid(1, p_data.size() - 2);
}
else
throw ConversionError("Framing Error");
}
/**
* \brief Prueft ob die Laenge korrekt in der Nachricht angegeben wurde
*
* \author B. Ziegler
*
* \param message Die Nachricht bei der die Laenge geprueft werden soll
* Hierbei muessen die Daten der Nachricht vorliegen (ohne Steuerzeichen, etc.)
*/
static void checkLength(const QByteArray &p_data)
{
if (p_data.size() < HEADER_SIZE)
throw ConversionError("Telegram too short");
unsigned char length = p_data[HEADER_SIZE - 1]; ///< Laengen Feld ist letztes Byte des Headers
if (length != p_data.size() - HEADER_SIZE)
throw ConversionError("Telegram length does not match");
}
QByteArray byteStreamFromSfmMessage(const SfmMessage &message)
{
QByteArray byteData(getHeader(message));///< Message mit Header erstellen
byteData.append(message.payload().size());///< Laengen Feld setzen
byteData.append(message.payload());///< Payload anhaengen
byteData.append(getChecksum(byteData));///< Checksumme bestimmen und anhaengen
return addFrame(maskData(byteData));
}
SfmMessage byteStreamToSfmMessage(const QByteArray &datastream)
{
if (datastream.size() < MINIMUM_BYTE_SIZE)
throw ConversionError("Can't convert an QByteArray that hasn't the minimum size!");
QByteArray checkMsg;
/// Framing korrekt?
checkMsg = removeFrame(datastream);
checkMsg = unmaskData(checkMsg);
/// Checksumme korrekt?
checkMsg = removeChecksum(checkMsg);
/// Laenge korrekt?
checkLength(checkMsg);
/// Nachricht bauen
return getSfmMessage(checkMsg);
}
QByteArray byteStreamFromEscMessage(const QByteArray &message)
{
QByteArray byteData = message;
if (message.size() == 1)
{
//byteData.append(QByteArray::fromHex("0x0"));
byteData.append(message);
}
else
byteData.append(getChecksum(byteData));///< Checksumme bestimmen und anhaengen
byteData = maskData(byteData);
byteData = addFrame(byteData);
return byteData;
}
QByteArray byteStreamToEscMessage(const QByteArray &datastream)
{
if (datastream.size() < 2)
return QByteArray("ERROR");
QByteArray checkMsg;
/// Framing korrekt?
checkMsg = removeFrame(datastream);
checkMsg = unmaskData(checkMsg);
/// Checksumme korrekt?
//checkMsg =
checkMsg.chop(1); //removeChecksum(checkMsg);
return checkMsg;
}
} // namespace LtsUtils
@@ -0,0 +1,93 @@
/**
* Helper to convert a QByteArray into a SFM-Message and vice versa
*
* $Id$
* $HeadURL$
* $Author$
* Created by Philipp Dassow, SLSE, 2018-01-23
*/
#pragma once
#include <LtsUtilsCore/ltsutilscore_export.h>
#include <QByteArray>
namespace LtsUtils
{
class LTSUTILSCORE_EXPORT SfmMessage
{
public:
unsigned int id() const { return m_id; }
void setId(unsigned int id) { m_id = id; }
bool rtr() const { return m_rtr; }
void setRtr(bool rtr) { m_rtr = rtr; }
QByteArray payload() const { return m_payload; }
void setPayload(const QByteArray &payload) { m_payload = payload; }
bool operator==(const SfmMessage &msg) const
{
return (m_id == msg.id() &&
m_rtr == msg.rtr() &&
m_payload == msg.payload());
}
protected:
unsigned int m_id = 0;
bool m_rtr = false;
QByteArray m_payload;
};
class ConversionError : public std::runtime_error
{
public:
using std::runtime_error::runtime_error;
};
/**
* @brief byteStreamFromSfmMessage
* Handles the bitcoding of the header bytes, calculates the checksum und substitutes the message
* @param message
* @return QByteArray containing the coded message including STX/ETX frame
*/
LTSUTILSCORE_EXPORT QByteArray byteStreamFromSfmMessage(const SfmMessage &message);
/**
* @brief byteStreamToSfmMessage
* desubstitutes the message, calculates the checksum and checks it and handles the bitcoding of the header bytes
* throws an exception if conversion fails
* @param byteData including STX/ETX frame
* @return QSharedPointer<SfmMessage> if
*/
LTSUTILSCORE_EXPORT SfmMessage byteStreamToSfmMessage(const QByteArray &datastream);
/**
* @brief byteStreamFromSfmMessage
* Calculates the checksum und substitutes the message
* @param message
* @return QByteArray containing the coded message including STX/ETX frame
*/
LTSUTILSCORE_EXPORT QByteArray byteStreamFromEscMessage(const QByteArray &message);
/**
* @brief byteStreamToEscMessage
* desubstitutes the message, calculates the checksum and checks it
* throws an exception if conversion fails
* @param byteData including STX/ETX frame
* @return QSharedPointer<SfmMessage> if
*/
LTSUTILSCORE_EXPORT QByteArray byteStreamToEscMessage(const QByteArray &datastream);
/**
* @brief lenMaskedData
* calculates the needed length for the masked data, it honors the checksum and the two length
* bytes during calculation
* @param data to calculate the masked length for
* @return length of masked data
*/
LTSUTILSCORE_EXPORT int lenMaskedData(const QByteArray &data);
} // namespace LtsUtils
+39
View File
@@ -0,0 +1,39 @@
#pragma once
#include <QVariant>
#include <QDateTime>
#include <ngltools/ngltools_dtg.h>
namespace LtsUtils
{
inline bool lessThan(const QVariant &var1, const QVariant &var2)
{
if (var1.canConvert(QMetaType::QString) && var2.canConvert(QMetaType::QString))
{
QString str1(var1.toString());
QString str2(var2.toString());
bool k1 = false;
double d1 = str1.toDouble(&k1);
if (!k1)
return str1 < str2;
bool k2 = false;
double d2 = str2.toDouble(&k2);
if (!k2)
return str1 < str2;
return d1 < d2; // TODO: localeAwareCompare
}
else if (var1.canConvert(QMetaType::Double) && var2.canConvert(QMetaType::Double))
return var1.toDouble() < var2.toDouble();
return false; // fallback
}
} // namespace LtsUtils