Initial commit
This commit is contained in:
commit
b7d90b9afe
16245 files changed
+3396666
No files matched your search
+68
@@ -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()
|
||||
+1206
File diff suppressed because it is too large.
Load diff
+66
@@ -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();
|
||||
};
|
||||
Executable
+124
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
#include <LtsUtilsCore/json_object.h> // redir
|
||||
+755
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+174
@@ -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
|
||||
Executable
+50
@@ -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
|
||||
Executable
+127
@@ -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
|
||||
Executable
+132
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
Executable
+65
@@ -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
@@ -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;
|
||||
}
|
||||
Executable
+304
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
Reference in new issue
Block a user