diff --git a/Lts/CMakeLists.txt b/Lts/CMakeLists.txt index 530b62af8..f75cff26c 100644 --- a/Lts/CMakeLists.txt +++ b/Lts/CMakeLists.txt @@ -1,4 +1,33 @@ -# Placeholder Lts CMakeLists to allow configure +# +# $Id$ +# $HeadURL$ +# $Author$ +# + cmake_minimum_required(VERSION 3.16) project(LtsPlaceholder) -# nothing +SET(DOXYGEN_OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/doxygen_Lts) +ADD_DOXYGEN_TARGET(Lts ${CMAKE_SOURCE_DIR}/doxygen/DoxyfileLts.in ${CMAKE_BINARY_DIR}/DoxyfileLts + ${CMAKE_SOURCE_DIR}/doxygen/rheinmetall_defence_logo.jpg + ${DOXYGEN_OUT_DIR}) + +ADD_SUBDIRECTORY(Tools) +ADD_SUBDIRECTORY(Application) +ADD_SUBDIRECTORY(Common) +ADD_SUBDIRECTORY(DDS) +ADD_SUBDIRECTORY(Plugins) +ADD_SUBDIRECTORY(Projects) +ADD_SUBDIRECTORY(Proxies) +ADD_SUBDIRECTORY(Services) +ADD_SUBDIRECTORY(Viewer) + +IF (BUILD_TESTING) + ADD_SUBDIRECTORY(Tests) +ENDIF() + +OPTION(BUILD_DOCS "Build documentation as html files from Lts/Documentation" OFF) +IF (BUILD_DOCS) + ADD_SUBDIRECTORY(Documentation) +ENDIF() + +CODEGEN_WRITE_SERVICEFILE_INFO() diff --git a/Lts/Common/CMakeLists.txt b/Lts/Common/CMakeLists.txt new file mode 100755 index 000000000..d9f00c8eb --- /dev/null +++ b/Lts/Common/CMakeLists.txt @@ -0,0 +1,35 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +ADD_SUBDIRECTORY(CodeGen) +ADD_SUBDIRECTORY(httq) +#ADD_SUBDIRECTORY(LtsBotan) +ADD_SUBDIRECTORY(LtsCommandLineParser) +ADD_SUBDIRECTORY(LtsCoordTrans) +ADD_SUBDIRECTORY(LtsCoordinate) +ADD_SUBDIRECTORY(LtsElevation) +ADD_SUBDIRECTORY(LtsLicence) +ADD_SUBDIRECTORY(LtsPluginManager) +ADD_SUBDIRECTORY(LtsSession) +ADD_SUBDIRECTORY(LtsUtils) +ADD_SUBDIRECTORY(LtsWidgets) +ADD_SUBDIRECTORY(LtsAppDescriptor) +ADD_SUBDIRECTORY(LtsAppManager) +ADD_SUBDIRECTORY(LtsMultiMonitor) +ADD_SUBDIRECTORY(LtsLogin) +ADD_SUBDIRECTORY(LtsNotification) +ADD_SUBDIRECTORY(LtsTimeline) +ADD_SUBDIRECTORY(LtsPrinting) +ADD_SUBDIRECTORY(LtsSerialPortQuerry) +ADD_SUBDIRECTORY(LtsScriptTimeline) +ADD_SUBDIRECTORY(LtsWebSocket) +ADD_SUBDIRECTORY(LtsProjectionWorkstation) +ADD_SUBDIRECTORY(LtsVirtualKeyboard) +ADD_SUBDIRECTORY(PluginInterfaces) +ADD_SUBDIRECTORY(ProjectConfig) +ADD_SUBDIRECTORY(TopicEditorBase) # TODO: rename / move ? +#ADD_SUBDIRECTORY(LtsEventTimeline) +#ADD_SUBDIRECTORY(LtsTranslator) diff --git a/Lts/Common/CodeGen/CMakeLists.txt b/Lts/Common/CodeGen/CMakeLists.txt new file mode 100755 index 000000000..a08b3948c --- /dev/null +++ b/Lts/Common/CodeGen/CMakeLists.txt @@ -0,0 +1,128 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +FIND_PACKAGE(RTI REQUIRED) +FIND_PACKAGE(Grantlee5 REQUIRED) + +NGL_ADD_LIBRARY(ALL codegen_base SHARED + codegen_export.h + SqlGenerator.cpp + SqlGenerator.h + StructBuilder.cpp + StructBuilder.h + XmlReader.cpp + XmlReader.h +) +NGL_TARGET_LINK_LIBRARIES(codegen_base + PRIVATE + Qt5::Xml + NGLLogger + ngltools + PUBLIC + Qt5::Core +) +NGL_TARGET_INCLUDE_DIRECTORIES(codegen_base + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ + $ +) +NGL_TARGET_COMPILE_DEFINITIONS(codegen_base + PUBLIC + $ +) +NGL_INSTALL_TARGET(codegen_base) + +NGL_ADD_LIBRARY(ALL codegen_grantlee STATIC + CppTopicGeneratorGrantlee.cpp + CppTopicGeneratorGrantlee.h + DaoGeneratorGrantlee.cpp + DaoGeneratorGrantlee.h + DatabaseGeneratorGrantlee.cpp + DatabaseGeneratorGrantlee.h + GeneratorGrantleeBase.cpp + GeneratorGrantleeBase.h + GrantleeWrappers.cpp + GrantleeWrappers.h + + svc.idl.grantlee + svc_enums.idl.grantlee + + svc_translation.cpp.grantlee + svc_translation.h.grantlee + + svc_dao.cpp.grantlee + svc_dao.h.grantlee + + svc_db.h.grantlee + svc_db_impl.h.grantlee + svc_db_impl.cpp.grantlee + + svc_dtc.h.grantlee + svc_dtc_impl.cpp.grantlee + svc_dtc_impl.h.grantlee + + svc_dts.h.grantlee + svc_dts_impl.cpp.grantlee + svc_dts_impl.h.grantlee + + svc_ftc.h.grantlee + svc_ftc_impl.cpp.grantlee + svc_ftc_impl.h.grantlee + + svc_fts.h.grantlee + svc_fts_impl.cpp.grantlee + svc_fts_impl.h.grantlee + + svc_json.cpp.grantlee + svc_json.h.grantlee + + svc_topic_type_plugin.cpp.grantlee + svc_topic_type_plugin.h.grantlee + + svc_record_replay_plugin.cpp.grantlee + svc_record_replay_plugin.h.grantlee + + codegen_simple.py +) + +NGL_TARGET_LINK_LIBRARIES(codegen_grantlee + PRIVATE + Grantlee5::Templates + NGLLogger + ProjectConfig + PUBLIC + codegen_base +) +NGL_TARGET_INCLUDE_DIRECTORIES(codegen_grantlee + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) + +NGL_ADD_EXECUTABLE(ALL codegen + main_grantlee.cpp +) +NGL_TARGET_LINK_LIBRARIES(codegen + codegen_grantlee + LtsCommandLineParser + nglcoreapplication + Qt5::Core +) + +NGL_WRAP_UI(codegen_gui_uics + main_grantlee_gui.ui +) +NGL_ADD_EXECUTABLE(WS AAR SV NO_TRANSLATION codegen_gui + ${codegen_gui_uics} + main_grantlee_gui.cpp + main_grantlee_gui.ui +) +NGL_TARGET_LINK_LIBRARIES(codegen_gui + codegen_grantlee + LtsCommandLineParser + nglapplication + Qt5::Widgets + Grantlee5::Templates +) diff --git a/Lts/Common/CodeGen/CppTopicGeneratorGrantlee.cpp b/Lts/Common/CodeGen/CppTopicGeneratorGrantlee.cpp new file mode 100755 index 000000000..a041f8b6b --- /dev/null +++ b/Lts/Common/CodeGen/CppTopicGeneratorGrantlee.cpp @@ -0,0 +1,345 @@ + + + +#include "CppTopicGeneratorGrantlee.h" +#include "StructBuilder.h" +#include +#include +#include + +using namespace CodeGen; + +CppTopicGeneratorGrantlee::CppTopicGeneratorGrantlee() + : GeneratorGrantleeBase("CodeGen.CppTopicGeneratorGrantlee") +{} + +bool CppTopicGeneratorGrantlee::init(const XmlReader *reader, StructBuilder *structBuilder) +{ + m_reader = reader; + if (!GeneratorGrantleeBase::initInternal(reader, structBuilder)) + return false; + + addCustomFunctions(reader->getCustomFunctions()); + + return initDataTopics() && initFunctionTopics() && initIdlStructs(); +} + + + +bool CppTopicGeneratorGrantlee::initDataTopics() +{ + DataTopicsWrapper *dataTopicsWrapper = new DataTopicsWrapper(QString(), this); + const auto dataTopics = m_reader->getIdlStructInformation(); + QVector keyColumns; + QVector trColumns; + for (const CodeGen::TopicTypeInformation &tti : dataTopics) + { + if (!tti.isDataObject) + continue; + + keyColumns.clear(); + trColumns.clear(); + const CompleteTopicTypeInformation ctti = m_structBuilder->topicTypeInformationByStructType(tti.name); + if (!ctti.isValid()) + { + ERROR_LOG(m_logger) << "could not find informations for struct " << tti.name; + continue; + } + DataTopicWrapper *dtw = dataTopicsWrapper->addDataTopic(ctti); + getSpecialColumns(ctti, keyColumns, trColumns); + if (keyColumns.size() == 1) + dtw->setKeyColumnData(keyColumns.first()); + dtw->setTrColumns(trColumns); + if (ctti.recordTopic) + { + const CompleteTableInformation cti(m_structBuilder->getRecordingTopicTable(ctti.name)); + if (!cti.isEmpty()) + dtw->setRecordTableInformation(cti); + } + else + { + if (ctti.isDataObject) + m_structBuilder->buildJSonStructureInformationFromIdlData(ctti); + } + } + m_ctx->insert(QStringLiteral("dataTopics"), dataTopicsWrapper); + + QSet exportedSubTopics; + DataTopicsWrapper *subTopicsWrapper = new DataTopicsWrapper(QString(), this); + const QVector> serializeTopics = m_structBuilder->getSerializeFromToJsonTopics(); + for (const QSharedPointer &dti : serializeTopics) + { + if (exportedSubTopics.contains(dti->idlType())) + continue; + if (dti->isStructure()) + { + const CompleteTopicTypeInformation ctti = m_structBuilder->topicTypeInformationByStructType(dti->idlType()); + if (!ctti.isValid()) + { + ERROR_LOG(m_logger) << "could not find informations for struct " << dti->idlType(); + continue; + } + const CompleteTableInformation cti(m_structBuilder->getRecordingTopicTable(ctti.name)); + if (cti.isEmpty()) + { + ERROR_LOG(m_logger) << "could not find informations for struct " << dti->idlType(); + continue; + } + DEBUG_LOG(m_logger) << "Adding substruct topic " << ctti.name; + DataTopicWrapper *dtw = subTopicsWrapper->addDataTopic(ctti); + dtw->setRecordTableInformation(cti); + exportedSubTopics += dti->idlType(); + } + } + m_ctx->insert(QStringLiteral("serializeTopics"), subTopicsWrapper); + + QVector jsonTopics; + const QMap> jsonExport = m_structBuilder->jsonStructureInformation(); + for (auto it = jsonExport.begin(); it != jsonExport.end(); ++it) + { + MemberInformationListWrapper *mil = new MemberInformationListWrapper(it.key(), it.value(), this); + jsonTopics += mil; + + } + m_ctx->insert(QStringLiteral("jsonTopics"), QVariant::fromValue(jsonTopics)); + + return true; +} + +bool CppTopicGeneratorGrantlee::getSpecialColumns(const CompleteTopicTypeInformation &tti, + QVector &keyColumns, + QVector &trColumns) const +{ + DEBUG_LOG(m_logger) << "Get special columns for " << tti.name << " - base is " << tti.baseValueType; + if (!tti.isValid()) + { + ERROR_LOG(m_logger) << "tti is not valid ..."; + return false; + } + for (const IdlData &ci : tti.members) + { + if (ci.isKey) + keyColumns += ci; + if (ci.isTranslatable) + trColumns += ci; + } + if (tti.baseValueType.isEmpty()) + return true; + return getSpecialColumns(topicTypeInformationByDerivedTopic(tti), keyColumns, trColumns); +} + + + +bool CppTopicGeneratorGrantlee::initFunctionTopics() +{ + + const QVector functionTopics = m_reader->getFunctionTopics(); + FunctionTopicsWrapper *functionTopicsWrapper(new FunctionTopicsWrapper(QStringLiteral("functionTopics"), this)); + for (const FunctionTopic &ft : functionTopics) + { + if (!handleStruct(ft, functionTopicsWrapper)) + return false; + } + + m_ctx->insert(QStringLiteral("functionTopics"), functionTopicsWrapper); + return true; +} + +bool CppTopicGeneratorGrantlee::handleStruct(const FunctionTopic &ft, + FunctionTopicsWrapper *functionTopicsWrapper) +{ + DEBUG_LOG(m_logger) << "handling FunctionTopic: " << ft.name; + + const CompleteTopicTypeInformation siRequest = m_structBuilder->topicTypeInformationByStructType(ft.requestTopicType); + if (!siRequest.isValid()) + { + WARNING_LOG(m_logger) << "Could not get topic informations about request topic " + << ft.requestTopicName; + return false; + } + + const CompleteTopicTypeInformation siReply = m_structBuilder->topicTypeInformationByStructType(ft.replyTopicType); + if (!siReply.isValid()) + { + WARNING_LOG(m_logger) << "Could not get topic informations about reply topic " + << ft.replyTopicType; + return false; + } + + FunctionTopicWrapper *ftw = functionTopicsWrapper->addFunctionTopic(ft, siRequest, siReply); + const QByteArray hash = QCryptographicHash::hash(ft.requestTopicName.toUtf8(), QCryptographicHash::Sha3_512).toHex().left(16); + ftw->setReplyHandlerHash(QStringLiteral("0x") + QString::fromLatin1(hash) + QStringLiteral("LL")); + if (!addRequesterRequestParams(ft.name, siRequest, ftw) || + !addRequesterReplyParams(ft.name, siReply, ftw)) + return false; + + return true; +} + +bool CppTopicGeneratorGrantlee::addRequesterRequestParams(const QString &request_name, + const CompleteTopicTypeInformation &request, + FunctionTopicWrapper *ftw) +{ + if (request.hasBaseValue()) + { + const CompleteTopicTypeInformation siBase = m_structBuilder->topicTypeInformationByStructType(request.baseValueType); + if (!siBase.isValid()) + { + WARNING_LOG(m_logger) << "Could not get topic informations about base request topic " + << request.baseValueType; + return false; + } + + if (!addRequesterRequestParams(request_name, siBase, ftw)) + return false; + } + ftw->addRequestParams(request.members); + + return true; +} + +bool CppTopicGeneratorGrantlee::addRequesterReplyParams(const QString &request_name, + const CompleteTopicTypeInformation &reply, + FunctionTopicWrapper *ftw) +{ + if (reply.hasBaseValue()) + { + const CompleteTopicTypeInformation siBase = m_structBuilder->topicTypeInformationByStructType(reply.baseValueType); + if (!siBase.isValid()) + { + WARNING_LOG(m_logger) << "Could not get topic informations about base reply topic " + << reply.baseValueType; + return false; + } + + if (!addRequesterReplyParams(request_name, siBase, ftw)) + return false; + } + ftw->addReplyParams(reply.members); + + return true; +} + + + +bool CppTopicGeneratorGrantlee::handleIdlStruct(const CompleteTopicTypeInformation &csi, + QSet &idlStructNames, + QVector &idlStructs) +{ + if (!csi.isValid()) + return true; + + if (idlStructNames.contains(csi.name)) + return true; + + + + + if (csi.hasBaseValue()) + { + const CompleteTopicTypeInformation ctti = m_structBuilder->topicTypeInformationByStructType(csi.baseValueType, false); + if (ctti.isValid()) + { + if (ctti.extensibility == dds::core::xtypes::ExtensibilityKind_def::FINAL) + { + ERROR_LOG(m_logger) << "Base struct (" << ctti.name + << ") has final extensibility - this is not allowed"; + return false; + } + if (!idlStructNames.contains(csi.baseValueType)) + { + if (!handleIdlStruct(ctti, idlStructNames, idlStructs)) + return false; + } + } + else + { + const int idx = csi.baseValueType.indexOf(QLatin1String("::")); + if (idx < 0) + { + ERROR_LOG(m_logger) << "idlStruct '" << csi.baseValueType + << "' not found in idl-file"; + return false; + } + } + } + + + for (const IdlData &idlData : csi.members) + { + QString idlType; + DEBUG_LOG(m_logger) << "member: " << idlData.type << " - " << idlData.name + << " " << idlData.dataTypeInfo->isStructure() + << " " << idlData.dataTypeInfo->isSequence(); + if (idlData.dataTypeInfo->isStructure()) + idlType = idlData.dataTypeInfo->idlType(); + else if (idlData.dataTypeInfo->isSequence()) + { + if (idlData.dataTypeInfo->innerDataType()->isStructure()) + idlType = idlData.dataTypeInfo->innerDataType()->idlType(); + } + if (idlType.isEmpty()) + continue; + if (!handleIdlStruct(m_structBuilder->topicTypeInformationByStructType(idlType, + false), + idlStructNames, + idlStructs)) + return false; + } + + + DEBUG_LOG(m_logger) << "Current structName: " << csi.name; + idlStructs += new IdlStructWrapper(csi, this); + + + idlStructNames.insert(csi.name); + + return true; +} + +bool CppTopicGeneratorGrantlee::initIdlStructs() +{ + + const QVector csiData = m_structBuilder->topicTypeInformations(); + + QSet idlStructNames; + QVector idlStructs; + idlStructs.reserve(csiData.size()); + for (const CompleteTopicTypeInformation &csi : csiData) + { + if (!handleIdlStruct(csi, idlStructNames, idlStructs)) + return false; + } + + m_ctx->insert(QStringLiteral("idl_structs"), QVariant::fromValue(idlStructs)); + + const QVector enumerations = m_structBuilder->enumInformations(); + QVector idlEnumerations; + idlEnumerations.reserve(enumerations.size()); + for (const EnumInformation &ei : enumerations) + idlEnumerations += new IdlEnumWrapper(ei, this); + m_ctx->insert(QStringLiteral("idl_enums"), QVariant::fromValue(idlEnumerations)); + + + exportUsedServices(m_reader); + return true; +} + + +CompleteTopicTypeInformation CppTopicGeneratorGrantlee::topicTypeInformationByDerivedTopic(const CompleteTopicTypeInformation &tti) const +{ + if (tti.baseValueType.isEmpty()) + return CompleteTopicTypeInformation(); + QString baseValueType = tti.baseValueType; + + + if (tti.name.contains(QLatin1String("::")) && + !tti.baseValueType.contains(QLatin1String("::"))) + { + + const QVector sl = tti.name.splitRef(QStringLiteral("::")); + baseValueType = sl.at(0) + QLatin1String("::") + tti.baseValueType; + } + DEBUG_LOG(m_logger) << "topicTypeInformationByStructName(" << baseValueType << ")"; + return m_structBuilder->topicTypeInformationByStructType(baseValueType); +} diff --git a/Lts/Common/CodeGen/CppTopicGeneratorGrantlee.h b/Lts/Common/CodeGen/CppTopicGeneratorGrantlee.h new file mode 100755 index 000000000..c8f3b111c --- /dev/null +++ b/Lts/Common/CodeGen/CppTopicGeneratorGrantlee.h @@ -0,0 +1,47 @@ + + +#pragma once + +#include "GeneratorGrantleeBase.h" +#include "StructBuilder.h" +#include "GrantleeWrappers.h" +#include "codegen_export.h" + +namespace CodeGen +{ + +class CppTopicGeneratorGrantlee final : public GeneratorGrantleeBase +{ + Q_OBJECT +public: + explicit CppTopicGeneratorGrantlee(); + + bool init(const XmlReader *reader, StructBuilder *structBuilder) override; + +private: + + bool initDataTopics(); + bool getSpecialColumns(const CompleteTopicTypeInformation &tti, + QVector &keyColumns, + QVector &trColumns) const; + + bool initFunctionTopics(); + bool handleStruct(const FunctionTopic &ft, + FunctionTopicsWrapper *functionTopicsWrapper); + bool addRequesterRequestParams(const QString &request_name, + const CompleteTopicTypeInformation &request, + FunctionTopicWrapper *ftw); + bool addRequesterReplyParams(const QString &request_name, + const CompleteTopicTypeInformation &reply, + FunctionTopicWrapper *ftw); + + bool handleIdlStruct(const CompleteTopicTypeInformation &csi, + QSet &idlStructNames, + QVector &idlStructs); + bool initIdlStructs(); + + CompleteTopicTypeInformation topicTypeInformationByDerivedTopic(const CompleteTopicTypeInformation &tti) const; + + const XmlReader *m_reader = nullptr; +}; +} diff --git a/Lts/Common/CodeGen/DaoGeneratorGrantlee.cpp b/Lts/Common/CodeGen/DaoGeneratorGrantlee.cpp new file mode 100755 index 000000000..2397892db --- /dev/null +++ b/Lts/Common/CodeGen/DaoGeneratorGrantlee.cpp @@ -0,0 +1,57 @@ + + + +#include "DaoGeneratorGrantlee.h" +#include "StructBuilder.h" +#include +#include + +using namespace CodeGen; + +DaoGeneratorGrantlee::DaoGeneratorGrantlee() + : GeneratorGrantleeBase("CodeGen.DaoGeneratorGrantlee") +{} + +bool DaoGeneratorGrantlee::init(const XmlReader *reader, + StructBuilder *structBuilder) +{ + if (!GeneratorGrantleeBase::initInternal(reader, structBuilder)) + return false; + + + if (reader->isServiceExerciseIndependent()) + m_ctx->insert(QStringLiteral("isExerciseIndependent"), QLatin1String("1")); + + bool bHasDbNotifications = false; + QVector vlServiceStructs; + QVector vlStaticDataStructs; + const QVector csiData = structBuilder->structInformations(); + vlServiceStructs.reserve(csiData.size()); + vlStaticDataStructs.reserve(csiData.size()); + for (const CompleteStructInformation &csi : csiData) + { + DbStructWrapper *dbStructWrapper(new DbStructWrapper(csi, this)); + + if (csi.hasStaticContent) + vlStaticDataStructs += dbStructWrapper; + else + vlServiceStructs += dbStructWrapper; + if (!csi.dbNotifications.isEmpty()) + bHasDbNotifications = true; + } + + if (structBuilder->needPoint3d()) + m_ctx->insert(QStringLiteral("need_point3d"), QLatin1String("1")); + if (structBuilder->needPgArrayInclude()) + m_ctx->insert(QStringLiteral("need_pgarray"), QLatin1String("1")); + m_ctx->insert(QStringLiteral("cpp_structs"), QVariant::fromValue(vlServiceStructs)); + m_ctx->insert(QStringLiteral("data_structs"), QVariant::fromValue(vlStaticDataStructs)); + if (bHasDbNotifications) + m_ctx->insert(QStringLiteral("has_db_notifications"), QLatin1String("1")); + + m_ctx->insert(QStringLiteral("library_target"), QStringLiteral("COMMON")); + + exportUsedServices(reader); + + return true; +} diff --git a/Lts/Common/CodeGen/DaoGeneratorGrantlee.h b/Lts/Common/CodeGen/DaoGeneratorGrantlee.h new file mode 100755 index 000000000..06b48b586 --- /dev/null +++ b/Lts/Common/CodeGen/DaoGeneratorGrantlee.h @@ -0,0 +1,25 @@ + + +#pragma once + +#include "GeneratorGrantleeBase.h" +#include "StructBuilder.h" +#include "GrantleeWrappers.h" +#include "codegen_export.h" + +namespace CodeGen +{ +struct CompleteStructInformation; +class StructBuilder; +class XmlReader; + + +class DaoGeneratorGrantlee final : public GeneratorGrantleeBase +{ + Q_OBJECT +public: + explicit DaoGeneratorGrantlee(); + + bool init(const XmlReader *reader, StructBuilder *structBuilder) override; +}; +} diff --git a/Lts/Common/CodeGen/DatabaseGeneratorGrantlee.cpp b/Lts/Common/CodeGen/DatabaseGeneratorGrantlee.cpp new file mode 100755 index 000000000..53a472082 --- /dev/null +++ b/Lts/Common/CodeGen/DatabaseGeneratorGrantlee.cpp @@ -0,0 +1,59 @@ + + + +#include "DatabaseGeneratorGrantlee.h" +#include "StructBuilder.h" +#include +#include + +using namespace CodeGen; + +DatabaseGeneratorGrantlee::DatabaseGeneratorGrantlee(bool targetIsServer) + : GeneratorGrantleeBase("CodeGen.CppStructGeneratorGrantlee") + , m_targetIsServer(targetIsServer) +{} + +bool DatabaseGeneratorGrantlee::init(const XmlReader *reader, + StructBuilder *structBuilder) +{ + if (!GeneratorGrantleeBase::initInternal(reader, structBuilder)) + return false; + + + if (reader->isServiceExerciseIndependent()) + m_ctx->insert(QStringLiteral("isExerciseIndependent"), QLatin1String("1")); + + bool bHasDbNotifications = false; + QVector vlServiceStructs; + QVector vlStaticDataStructs; + const QVector csiData = structBuilder->structInformations(); + vlServiceStructs.reserve(csiData.size()); + vlStaticDataStructs.reserve(csiData.size()); + for (const CompleteStructInformation &csi : csiData) + { + DbStructWrapper *dbStructWrapper(new DbStructWrapper(csi, this)); + + if (csi.hasStaticContent) + vlStaticDataStructs += dbStructWrapper; + else + vlServiceStructs += dbStructWrapper; + if (!csi.dbNotifications.isEmpty()) + bHasDbNotifications = true; + } + addCustomFunctions(reader->getCustomFunctions()); + + if (structBuilder->needPoint3d()) + m_ctx->insert(QStringLiteral("need_point3d"), QLatin1String("1")); + if (structBuilder->needPgArrayInclude()) + m_ctx->insert(QStringLiteral("need_pgarray"), QLatin1String("1")); + m_ctx->insert(QStringLiteral("cpp_structs"), QVariant::fromValue(vlServiceStructs)); + m_ctx->insert(QStringLiteral("data_structs"), QVariant::fromValue(vlStaticDataStructs)); + if (bHasDbNotifications) + m_ctx->insert(QStringLiteral("has_db_notifications"), QLatin1String("1")); + + m_ctx->insert(QStringLiteral("library_target"), m_targetIsServer ? QStringLiteral("SERVER") : QStringLiteral("COMMON")); + + exportUsedServices(reader); + + return true; +} diff --git a/Lts/Common/CodeGen/DatabaseGeneratorGrantlee.h b/Lts/Common/CodeGen/DatabaseGeneratorGrantlee.h new file mode 100755 index 000000000..8204b5d8f --- /dev/null +++ b/Lts/Common/CodeGen/DatabaseGeneratorGrantlee.h @@ -0,0 +1,27 @@ + + +#pragma once + +#include "GeneratorGrantleeBase.h" +#include "StructBuilder.h" +#include "GrantleeWrappers.h" +#include "codegen_export.h" + +namespace CodeGen +{ +struct CompleteStructInformation; +class StructBuilder; +class XmlReader; + + +class DatabaseGeneratorGrantlee final : public GeneratorGrantleeBase +{ + Q_OBJECT +public: + explicit DatabaseGeneratorGrantlee(bool targetIsServer); + + bool init(const XmlReader *reader, StructBuilder *structBuilder) override; +private: + bool m_targetIsServer; +}; +} diff --git a/Lts/Common/CodeGen/GeneratorGrantleeBase.cpp b/Lts/Common/CodeGen/GeneratorGrantleeBase.cpp new file mode 100755 index 000000000..704eb6170 --- /dev/null +++ b/Lts/Common/CodeGen/GeneratorGrantleeBase.cpp @@ -0,0 +1,200 @@ + + + +#include "GeneratorGrantleeBase.h" +#include "XmlReader.h" +#include "GrantleeWrappers.h" +#include +#include +#include +#include + +using namespace CodeGen; +GeneratorGrantleeBase::GeneratorGrantleeBase(const char *loggerName) + : m_engine(new Grantlee::Engine) + , m_ctx(new Grantlee::Context) + , m_structBuilder(nullptr) + , m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger(loggerName)) +{ +} + +GeneratorGrantleeBase::~GeneratorGrantleeBase() +{ + delete m_engine; + delete m_ctx; +} + +bool GeneratorGrantleeBase::initInternal(const XmlReader *reader, StructBuilder *structBuilder) +{ + m_structBuilder = structBuilder; + + if (reader) + { + m_ctx->insert(QStringLiteral("namespace"), reader->getServiceNamespace()); + m_ctx->insert(QStringLiteral("namespace_abbr"), reader->getServiceAbbr().toLower()); + m_ctx->insert(QStringLiteral("namespace_abbr_upper"), reader->getServiceAbbr().toUpper()); + if (reader->getServiceNamespace() == QLatin1String("Core")) + m_ctx->insert(QStringLiteral("include_prefix"), QStringLiteral("DDS/%1").arg(reader->getServiceNamespace())); + else + m_ctx->insert(QStringLiteral("include_prefix"), reader->getServiceNamespace()); + m_ctx->insert(QStringLiteral("custom_record_replay"), reader->hasCustomRecordReplayPlugin()); + } + return true; +} + +bool GeneratorGrantleeBase::setTemplate(const QString &templateName, const QString &tmpl) +{ + m_template = m_engine->newTemplate(tmpl, QStringLiteral("myTemplate")); + if (m_template->error()) + { + WARNING_LOG(m_logger) << "Error parsing template '" << templateName + << "': " << m_template->errorString(); + return false; + } + return true; +} + +QString GeneratorGrantleeBase::render() const +{ + QString out = m_template->render(m_ctx); + QRegExp rx(QLatin1String(";\\n([ ]+\\n)*")); + out = out.replace(rx, QStringLiteral(";\n")); + return out; +} + +QString GeneratorGrantleeBase::errorString() const +{ + return m_template->errorString(); +} + + +bool GeneratorGrantleeBase::generateData(const QString &templateName, + const QString &outDirectory) +{ + QString content; + if (!readFile(templateName, content)) + return false; + if (!setTemplate(templateName, content)) + return false; + + content = render(); + + QString outFilename; + if (!getFilenameFromData(content, outFilename)) + return false; + + + QFile f(outDirectory + QLatin1Char('/') + outFilename); + if (!f.open(QIODevice::WriteOnly)) + { + ERROR_LOG(m_logger) << "Error opening file '" << f.fileName() << "' for writing: " + << f.errorString() << "(" << f.error() << ")"; + return false; + } + const QByteArray outData = content.toUtf8(); + f.write(outData); + f.close(); + + DEBUG_LOG(m_logger) << "Written data to " << f.fileName(); + return true; +} + +bool GeneratorGrantleeBase::readFile(const QString &filename, QString &data) const +{ + QFile f(filename); + if (!f.open(QIODevice::ReadOnly)) + { + ERROR_LOG(m_logger) << "Error opening file " << filename + << " - error: " << f.errorString(); + return false; + } + data = QString::fromUtf8(f.readAll()); + f.close(); + return true; +} + +bool GeneratorGrantleeBase::getFilenameFromData(const QString &dataToParse, QString &filename) const +{ + + const QString filenameMarker(QStringLiteral("// filename:")); + int idx = dataToParse.indexOf(filenameMarker); + if (idx < 0) + { + ERROR_LOG(m_logger) << "Could not find marker '" << filenameMarker + << "' in data!"; + return false; + } + idx += filenameMarker.length() + 1; + int endIdx = dataToParse.indexOf(QLatin1Char('\n'), idx); + if (endIdx < 0) + { + ERROR_LOG(m_logger) << "Could not find end marker '\\n' in data!"; + return false; + } + filename = dataToParse.midRef(idx, endIdx - idx).trimmed().toString(); + + return true; +} + +void GeneratorGrantleeBase::addCustomFunctions(const QVector &customFunctions) +{ + + QVariantList vlCustomDbFunctions; + QVariantList vlCustomDtcFunctions; + vlCustomDbFunctions.reserve(customFunctions.size()); + vlCustomDtcFunctions.reserve(customFunctions.size()); + for (const CustomServiceFunctionInformation &csfi : customFunctions) + { + const QString signature(csfi.returnValue + QLatin1Char(' ') + + csfi.methodName + QLatin1Char('(') + csfi.signature + QLatin1Char(')') + + (csfi.isConst ? QStringLiteral(" const") : QString())); + CustomFunctionWrapper *customFunctionWrapper(new CustomFunctionWrapper(signature, this)); + switch (csfi.type) + { + case CustomServiceFunctionInformation::Type::Db: + vlCustomDbFunctions += QVariant::fromValue(customFunctionWrapper); + break; + case CustomServiceFunctionInformation::Type::Dtc: + vlCustomDtcFunctions += QVariant::fromValue(customFunctionWrapper); + break; + case CustomServiceFunctionInformation::Type::DbNotify: + + WARNING_LOG(m_logger) << "Internal error - Trying to handle DbNotify in addCustomFunctions(): " + << csfi.methodName; + delete customFunctionWrapper; + break; + } + DEBUG_LOG(m_logger) << "Added custom db function " << csfi.methodName; + } + m_ctx->insert(QStringLiteral("custom_dtc_procs"), vlCustomDtcFunctions); + m_ctx->insert(QStringLiteral("custom_db_procs"), vlCustomDbFunctions); +} + +void GeneratorGrantleeBase::exportUsedServices(const XmlReader *reader) +{ + IdlServicesWrapper *idlServicesWrapper(new IdlServicesWrapper(QString(), this)); + const QMap usedServices = reader->usedServices(); + for (auto it = usedServices.begin(); it != usedServices.end(); ++it) + { + + if (!it.value()) + continue; + const QString &svc = it.key(); + const QString fullPath = reader->idlFileForService(svc); + const int idx = fullPath.lastIndexOf(QLatin1Char('.')); + if (idx < 0) + { + ERROR_LOG(m_logger) << "Could not parse file path: " << fullPath; + continue; + } + const QString idlFile = fullPath.left(idx); + if (idlFile.isEmpty()) + { + WARNING_LOG(m_logger) << "Could not find idl file for service " << svc; + continue; + } + idlServicesWrapper->addService(svc, idlFile); + DEBUG_LOG(m_logger) << "Added service " << svc; + } + m_ctx->insert(QStringLiteral("ext_services"), idlServicesWrapper); +} diff --git a/Lts/Common/CodeGen/GeneratorGrantleeBase.h b/Lts/Common/CodeGen/GeneratorGrantleeBase.h new file mode 100755 index 000000000..a06998d91 --- /dev/null +++ b/Lts/Common/CodeGen/GeneratorGrantleeBase.h @@ -0,0 +1,53 @@ + + +#pragma once + +#include "codegen_export.h" +#include "XmlReader.h" +#include +#include + +namespace NGL +{ +class INGLLogger; +} + +namespace Grantlee +{ +class Context; +class Engine; +class TemplateImpl; +using Template = QSharedPointer; +} +namespace CodeGen +{ +class StructBuilder; + +class GeneratorGrantleeBase : public QObject +{ + Q_OBJECT +public: + virtual ~GeneratorGrantleeBase(); + virtual bool init(const XmlReader *reader, StructBuilder *structBuilder) = 0; + bool setTemplate(const QString &templateName, const QString &tmpl); + QString render() const; + QString errorString() const; + bool generateData(const QString &templateName, + const QString &outDirectory); + +protected: + explicit GeneratorGrantleeBase(const char *loggerName); + + bool initInternal(const XmlReader *reader, StructBuilder *structBuilder); + bool readFile(const QString &filename, QString &data) const; + bool getFilenameFromData(const QString &dataToParse, QString &filename) const; + void addCustomFunctions(const QVector &customFunctions); + void exportUsedServices(const XmlReader *reader); +protected: + Grantlee::Engine *m_engine; + Grantlee::Context *m_ctx; + Grantlee::Template m_template; + StructBuilder *m_structBuilder; + NGL::INGLLogger &m_logger; +}; +} diff --git a/Lts/Common/CodeGen/GrantleeWrappers.cpp b/Lts/Common/CodeGen/GrantleeWrappers.cpp new file mode 100755 index 000000000..b2317a3cc --- /dev/null +++ b/Lts/Common/CodeGen/GrantleeWrappers.cpp @@ -0,0 +1,226 @@ + + + +#include "GrantleeWrappers.h" +#include + +using namespace CodeGen; + +QString IdlStructWrapper::extensibility2String(dds::core::xtypes::ExtensibilityKind extensiblity) +{ + switch (extensiblity.underlying()) + { + case dds::core::xtypes::ExtensibilityKind_def::FINAL: + return QStringLiteral("FINAL_EXTENSIBILITY"); + case dds::core::xtypes::ExtensibilityKind_def::MUTABLE: + return QStringLiteral("MUTABLE_EXTENSIBILITY"); + case dds::core::xtypes::ExtensibilityKind_def::EXTENSIBLE: + return QStringLiteral("EXTENSIBLE_EXTENSIBILITY"); + } + Q_ASSERT_X(false, "IdlStructWrapper::extensibility2String", "Unknown extensiblity kind"); + return QString(); +} + +QString DataTopicWrapper::typeFq() const +{ + const QString &t = type(); + const QString &s = serviceName(); + if (s.isEmpty()) + return t; + if (t.startsWith(s)) + return t; + return s + QStringLiteral("::") + t; +} + +QString MemberInformationWrapper::ddsToQVariant() const +{ + return QStringLiteral("toQVariant(data.") + cppMemberName() + QStringLiteral("())"); +} + +QString MemberInformationWrapper::qVariantToDds() const +{ + const QString ddsType = isOptional() ? + (QStringLiteral("dds::core::optional<") + cppDdsType() + QLatin1Char('>')) : + (cppDdsType()); + + return QStringLiteral("data.") + cppMemberName() + + QStringLiteral("() = fromQVariant<") + ddsType + QStringLiteral(">(q.value(i++))"); +} + +QString MemberInformationWrapper::ddsToJsonElement() const +{ + + + + + + + + + + if (cppMemberName().isEmpty()) + return QStringLiteral("obj[QStringLiteral(\"") + cppType() + QStringLiteral("\")] = element;"); + + if (!isOptional()) + return QStringLiteral("obj[QStringLiteral(\"") + cppMemberName() + QStringLiteral("\")] = ") + + cppToJsonPre() + QStringLiteral("element.") + cppMemberName() + QStringLiteral("()") + cppToJsonPost(); + + return QStringLiteral("if (element.") + cppMemberName() + QStringLiteral("().is_set()) ") + + QStringLiteral("obj[QStringLiteral(\"") + cppMemberName() + QStringLiteral("\")] = ") + + cppToJsonPre() + QStringLiteral("element.") + cppMemberName() + QStringLiteral("().get()") + cppToJsonPost(); +} + +QString MemberInformationWrapper::jsonElementToDds() const +{ + + + + + + + + + + + + + + if (cppMemberName().isEmpty()) + return QStringLiteral("element = ") + + cppFromQvPre2() + QStringLiteral("objElement.value(QStringLiteral(\"") + cppType() + QStringLiteral("\"))") + cppFromJsonPost(); + + QString ret(QStringLiteral("const QJsonValue val(objElement.value(QStringLiteral(\"") + cppMemberName() + QStringLiteral("\"))); ")); + if (isOptional()) + ret += QStringLiteral("if (!val.isNull()) "); + + ret += QStringLiteral("element.") + cppMemberName() + QStringLiteral("(") + + cppFromJsonPre2() + QStringLiteral("val") + cppFromJsonPost() + QStringLiteral(")"); + + return ret; +} + +QString MemberInformationWrapper::dbToJsonElement() const +{ + + + + + + + + + + if (cppMemberName().isEmpty()) + return QStringLiteral("obj[QStringLiteral(\"") + cppType() + QStringLiteral("\")] = element;"); + + if (isOptional() || cppType() == QStringLiteral("QDateTime")) + { + QString ret = QStringLiteral("if (") + ((cppType() == QStringLiteral("QDateTime")) ? QString() : QStringLiteral("!")) + + QStringLiteral("element.") + cppMemberName() + + ((cppType() == QStringLiteral("QDateTime")) ? QStringLiteral(".isValid()) ") : QStringLiteral(".isNull()) ")) + + QStringLiteral("obj[QStringLiteral(\"") + cppMemberName() + QStringLiteral("\")] = "); + if (isOptional()) + return ret += cppFromQvPre2() + QStringLiteral("element.") + cppMemberName() + cppFromQvPost(); + } + + return QStringLiteral("obj[QStringLiteral(\"") + cppMemberName() + QStringLiteral("\")] = ") + + cppToJsonPre() + QStringLiteral("element.") + cppMemberName() + cppToJsonPost(); +} + +QString MemberInformationWrapper::jsonElementToDb() const +{ + + + + + + + + + + + + + + if (cppMemberName().isEmpty()) + return QStringLiteral("element = ") + + cppFromQvPre2() + QStringLiteral("objElement.value(QStringLiteral(\"") + cppType() + QStringLiteral("\"))") + cppFromJsonPost(); + + QString ret(QStringLiteral("const QJsonValue val(objElement.value(QStringLiteral(\"") + cppMemberName() + QStringLiteral("\"))); ")); + if (isOptional()) + ret += QStringLiteral("if (!val.isUndefined()) "); + + ret += QStringLiteral("element.") + cppMemberName() + QStringLiteral(" = ") + + cppFromJsonPre2() + QStringLiteral("val") + cppFromJsonPost(); + + return ret; +} + +QVariantList DbStructWrapper::insertStructures() +{ + + QStringList tableNames; + QMap> structures; + QMap primaryKeys; + QMap fkComplexTableKeys; + + tableNames.reserve(5); + for (const QVariant &v : qAsConst(m_member)) + { + MemberInformationWrapper *mi = v.value(); + const QString &tableName = mi->info().db.table; + if (mi->info().db.usedForInsert) + { + auto &tnStructure = structures[tableName]; + tnStructure.append(mi); + if (!tableNames.contains(tableName)) + tableNames.push_back(tableName); + } + if (mi->info().fk) + fkComplexTableKeys[mi->info().fk->destination.tableName] = mi; + if (mi->info().db.isPrimaryKey) + { + if (!primaryKeys.contains(tableName)) + primaryKeys[tableName] = mi; + } + if (mi->info().db.isAssignmentForPrimaryTablePk) + fkComplexTableKeys[mi->info().db.table] = mi; + } + + + QVariantList vl; + vl.reserve(tableNames.size()); + for (int i = 0; i < tableNames.size(); ++i) + { + const QString &tn = tableNames.at(i); + MemberInformationWrapper *serialMember = fkComplexTableKeys.value(tn); + MemberInformationWrapper *baseId = nullptr; + MemberInformationWrapper *primaryKey = primaryKeys.value(tn); + if (i == 0 && tableNames.size() > 1) + baseId = primaryKeys.value(tn); + + + + if (tableNames.size() == 1) + { + baseId = nullptr; + + if (!primaryKey) + { + NGL::INGLLogger &logger = NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("DbStructWrapper"); + WARNING_LOG(logger) << "Table " << tn << " has no primary key!"; + } + else + { + if (primaryKey->dbHasSequence()) + serialMember = primaryKey; + } + } + + const QVector members = structures.value(tn); + TableWrapper *tw = new TableWrapper(tn, members, serialMember, baseId, primaryKey, this); + vl += QVariant::fromValue(tw); + } + + return vl; +} diff --git a/Lts/Common/CodeGen/GrantleeWrappers.h b/Lts/Common/CodeGen/GrantleeWrappers.h new file mode 100755 index 000000000..5e5b02db0 --- /dev/null +++ b/Lts/Common/CodeGen/GrantleeWrappers.h @@ -0,0 +1,643 @@ + + + +#pragma once + +#include +#include +#include +#include "StructBuilder.h" + +namespace CodeGen +{ +class NameWrapper : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString name READ name) + Q_PROPERTY(QString nameCc READ nameCc) + Q_PROPERTY(QString nameFlc READ nameFlc) +public: + NameWrapper(const QString &name, QObject *parent) + : QObject(parent) + , m_name(name) + {} + + QString name() const { return m_name; } + QString nameCc() const { return StructBuilder::camelCaseString(name(), true); } + QString nameFlc() const { return StructBuilder::firstCharLowercase(nameCc()); } +private: + const QString m_name; +}; + +class VariantListWrapper : public NameWrapper +{ + Q_OBJECT + Q_PROPERTY(QVariantList member READ member) +public: + + using NameWrapper::NameWrapper; + ~VariantListWrapper() + { + for (const QVariant &v : qAsConst(m_member)) + { + auto ptr = v.value(); + delete ptr; + } + } + + QVariantList member() const { return m_member; } +protected: + QVariantList m_member; +}; + + +class MemberInformationWrapper final : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString cppMemberName READ cppMemberName) + Q_PROPERTY(QString cppMemberNameCc READ cppMemberNameCc) + Q_PROPERTY(QString cppStructName READ cppStructName) + Q_PROPERTY(QString cppType READ cppType) + Q_PROPERTY(QString cppToQvPre READ cppToQvPre) + Q_PROPERTY(QString cppToQvPost READ cppToQvPost) + Q_PROPERTY(QString cppFromQvPre1 READ cppFromQvPre1) + Q_PROPERTY(QString cppFromQvPre2 READ cppFromQvPre2) + Q_PROPERTY(QString cppFromQvPost READ cppFromQvPost) + Q_PROPERTY(QString cppDefault READ cppDefault) + Q_PROPERTY(QString dbTableName READ dbTableName) + Q_PROPERTY(QString dbColumnName READ dbColumnName) + Q_PROPERTY(QString dbColumnNameView READ dbColumnNameView) + Q_PROPERTY(QString ddsToQVariant READ ddsToQVariant) + Q_PROPERTY(QString qVariantToDds READ qVariantToDds) + Q_PROPERTY(QString ddsToJsonElement READ ddsToJsonElement) + Q_PROPERTY(QString jsonElementToDds READ jsonElementToDds) + Q_PROPERTY(QString dbToJsonElement READ dbToJsonElement) + Q_PROPERTY(QString jsonElementToDb READ jsonElementToDb) + Q_PROPERTY(bool dbHasSequence READ dbHasSequence) + Q_PROPERTY(bool isOptional READ isOptional) +public: + MemberInformationWrapper(const MemberInformation &mi, QObject *parent) + : QObject(parent) + , m_mi(mi) + {} + + QString cppMemberName() const { return m_mi.cpp.memberName; } + QString cppMemberNameCc() const { return StructBuilder::camelCaseString(m_mi.cpp.memberName, true); } + QString cppStructName() const { return m_mi.cpp.structName; } + QString cppType() const { return m_mi.cpp.type; } + QString cppToQvPre() const { return m_mi.cpp.toVariantPre; } + QString cppToQvPost() const { return m_mi.cpp.toVariantPost; } + QString cppFromQvPre1() const { return m_mi.cpp.fromVariantPre1; } + QString cppFromQvPre2() const { return m_mi.cpp.fromVariantPre2; } + QString cppFromQvPost() const { return m_mi.cpp.fromVariantPost; } + QString cppDefault() const { return m_mi.cpp.defaultValue; } + QString dbTableName() const { return m_mi.db.table; } + QString dbColumnName() const { return m_mi.db.columnName; } + QString dbColumnNameView() const { return m_mi.db.getColumnNameView(); } + QString cppDdsType() const { return m_mi.cpp.ddsType.isEmpty() ? cppType() : m_mi.cpp.ddsType; } + QString ddsToQVariant() const; + QString qVariantToDds() const; + QString ddsToJsonElement() const; + QString jsonElementToDds() const; + QString dbToJsonElement() const; + QString jsonElementToDb() const; + bool dbHasSequence() const { return m_mi.db.hasSequence; } + bool isOptional() const { return m_mi.cpp.isOptional; } + + + QString cppToJsonPre() const { return m_mi.cpp.toJsonPre.isNull() ? m_mi.cpp.toVariantPre : m_mi.cpp.toJsonPre; } + QString cppToJsonPost() const { return m_mi.cpp.toJsonPost.isNull() ? m_mi.cpp.toVariantPost : m_mi.cpp.toJsonPost; } + QString cppFromJsonPre2() const { return m_mi.cpp.fromJsonPre2.isNull() ? m_mi.cpp.fromVariantPre2: m_mi.cpp.fromJsonPre2; } + QString cppFromJsonPost() const { return m_mi.cpp.fromJsonPost.isNull() ? m_mi.cpp.fromVariantPost : m_mi.cpp.fromJsonPost; } + + MemberInformation info() const + { + return m_mi; + } +private: + const MemberInformation m_mi; +}; + +class MemberInformationListWrapper : public VariantListWrapper +{ + Q_OBJECT + Q_PROPERTY(QVariantList cppMember READ cppMember) + Q_PROPERTY(QVariantList jsonMember READ jsonMember) + Q_PROPERTY(QVariantList fkMember READ fkMember) + Q_PROPERTY(QVariantList selectRemoveMember READ selectRemoveMember) + Q_PROPERTY(QVariantList viewMember READ viewMember) + Q_PROPERTY(MemberInformationWrapper *ddsKey READ ddsKey) + Q_PROPERTY(MemberInformationWrapper *primaryKey READ primaryKey) +public: + explicit MemberInformationListWrapper(const QString &name, const QVector &member, QObject *parent) + : VariantListWrapper(name, parent) + { + for (const MemberInformation &mi : member) + m_member += QVariant::fromValue(new MemberInformationWrapper(mi, this)); + } + + QVariantList memberByFilter(const std::function &acceptFilter) const + { + QVariantList vl; + vl.reserve(m_member.size()); + for (const QVariant &v : m_member) + { + MemberInformationWrapper *mi = v.value(); + if (mi && acceptFilter(mi->info())) + vl += v; + } + return vl; + } + + QVariantList cppMember() const + { + return memberByFilter([](const MemberInformation &mi) { return mi.cpp.usedForCppStruct; }); + } + QVariantList jsonMember() const + { + return memberByFilter([](const MemberInformation &mi) { return mi.cpp.usedForJson; }); + } + QVariantList fkMember() const + { + return memberByFilter([](const MemberInformation &mi) { return mi.db.isForeignKey; }); + } + QVariantList selectRemoveMember() const + { + return memberByFilter([](const MemberInformation &mi) { return mi.db.generateSelectRemove; }); + } + QVariantList viewMember() const + { + return memberByFilter([](const MemberInformation &mi) { return mi.db.usedForView; }); + } + MemberInformationWrapper *ddsKey() const + { + for (const QVariant &v : m_member) + { + MemberInformationWrapper *mi = v.value(); + if (mi->info().isDdsKey) + return mi; + } + return nullptr; + } + MemberInformationWrapper *primaryKey() const + { + for (const QVariant &v : m_member) + { + MemberInformationWrapper *mi = v.value(); + if (mi->info().db.isPrimaryKey) + return mi; + } + return nullptr; + } +}; + + +class TableWrapper final : public NameWrapper +{ + Q_OBJECT + Q_PROPERTY(QVector member READ member) + + Q_PROPERTY(MemberInformationWrapper *serialMember READ serialMember) + + + Q_PROPERTY(MemberInformationWrapper *baseId READ baseId) + + Q_PROPERTY(MemberInformationWrapper *primaryKey READ primaryKey) +public: + + explicit TableWrapper(const QString &name, + const QVector &member, + MemberInformationWrapper *serialMember, + MemberInformationWrapper *baseId, + MemberInformationWrapper *primaryKey, + QObject *parent) + : NameWrapper(name, parent) + , m_member(member) + , m_serialMember(serialMember) + , m_baseId(baseId) + , m_primaryKey(primaryKey) + {} + + QVector member() const { return m_member; } + MemberInformationWrapper *serialMember() const { return m_serialMember; } + MemberInformationWrapper *baseId() const { return m_baseId; } + MemberInformationWrapper *primaryKey() const { return m_primaryKey; } + +private: + QVector m_member; + MemberInformationWrapper *m_serialMember; + MemberInformationWrapper *m_baseId; + MemberInformationWrapper *m_primaryKey; +}; + +class DbStructWrapper final : public MemberInformationListWrapper +{ + Q_OBJECT + Q_PROPERTY(QVariantList insertStructures READ insertStructures) + Q_PROPERTY(QString viewTableName READ viewTableName) + Q_PROPERTY(bool deleteWithoutPkAllowed READ deleteWithoutPkAllowed) + Q_PROPERTY(MemberInformationWrapper *primaryKey READ primaryKey) + Q_PROPERTY(QVariantList dbNotifications READ dbNotifications) + +public: + explicit DbStructWrapper(const CompleteStructInformation &csi, QObject *parent) + : MemberInformationListWrapper(csi.structName, csi.member, parent) + , m_csi(csi) + { + m_dbNotifications.reserve(m_csi.dbNotifications.size()); + for (const auto ¬ification : qAsConst(m_csi.dbNotifications)) + m_dbNotifications += QVariant(notification.methodName); + } + + MemberInformationWrapper *primaryKey() const + { + for (const QVariant &v : m_member) + { + MemberInformationWrapper *mi = v.value(); + if (mi->info().db.isPrimaryKey && mi->info().db.table == primaryTableName()) + return mi; + } + return nullptr; + } + QVariantList insertStructures(); + + bool deleteWithoutPkAllowed() const { return m_csi.tables.size() == 1; } + QVariantList dbNotifications() const { return m_dbNotifications; } + QString primaryTableName() const { return m_csi.primaryTable->name; } + QString viewTableName() const { return (m_csi.tables.size() == 1) ? primaryTableName() : m_csi.viewName; } +private: + const CompleteStructInformation m_csi; + QVariantList m_dbNotifications; +}; + +class CustomFunctionWrapper final : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString signature READ signature) +public: + explicit CustomFunctionWrapper(const QString &signature, QObject *parent) + : QObject(parent) + , m_signature(signature) + {} + QString signature() const { return m_signature; } +private: + const QString m_signature; +}; + + +class IdlEnumKeyValueWrapper final : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString key READ key) + Q_PROPERTY(int value READ value) +public: + explicit IdlEnumKeyValueWrapper(const EnumKeyValuePair &ekvp, QObject *parent) + : QObject(parent) + , m_ekvp(ekvp) + {} + + QString key() const { return m_ekvp.key; } + int value() const { return m_ekvp.value; } + +private: + const EnumKeyValuePair m_ekvp; +}; + +class IdlDataWrapper final : public NameWrapper +{ + Q_OBJECT + Q_PROPERTY(QString type READ type) + Q_PROPERTY(QString comment READ comment) + Q_PROPERTY(QString isKey READ isKey) + Q_PROPERTY(QString isOptional READ isOptional) + Q_PROPERTY(QString cppType READ cppType) + Q_PROPERTY(QString cppTypeCr READ cppTypeCr) + Q_PROPERTY(QString nameGetter READ nameGetter) +public: + IdlDataWrapper(const IdlData &data, + QObject *parent) + : NameWrapper(data.name, parent) + , m_data(data) + {} + + QString type() const { return m_data.dataTypeInfo->idlType(); } + QString comment() const { return m_data.comment; } + QString isKey() const { return m_data.isKey ? QStringLiteral(" \t //@key") : QString(); } + QString isOptional() const { return m_data.isOptional ? QStringLiteral(" \t //@Optional") : QString(); } + QString cppType() const { return m_data.dataTypeInfo->cppType(); } + QString cppTypeCr() const { return m_data.dataTypeInfo->cppTypeConstRef(); } + QString nameGetter() const { return m_data.name + (m_data.isOptional ? QStringLiteral("().get") : QString()); } +private: + const IdlData m_data; +}; + +class IdlEnumWrapper final : public VariantListWrapper +{ + Q_OBJECT +public: + explicit IdlEnumWrapper(const EnumInformation &ei, QObject *parent) + : VariantListWrapper(ei.enumName, parent) + { + m_member.reserve(ei.values.size()); + for (const auto &val : ei.values) + m_member += QVariant::fromValue(new IdlEnumKeyValueWrapper(val, this)); + } +}; + +class IdlStructWrapper final : public VariantListWrapper +{ + Q_OBJECT + Q_PROPERTY(QString comment READ comment) + Q_PROPERTY(QString baseValueType READ baseValueType) + Q_PROPERTY(QString extensibility READ extensibility) +public: + explicit IdlStructWrapper(const CompleteTopicTypeInformation &ctti, QObject *parent) + : VariantListWrapper(ctti.name, parent) + , m_ctti(ctti) + { + m_member.reserve(m_ctti.members.size()); + for (const auto &val : qAsConst(m_ctti.members)) + m_member += QVariant::fromValue(new IdlDataWrapper(val, this)); + } + + QString comment() const { return m_ctti.comment; } + QString baseValueType() const { return m_ctti.baseValueType; } + QString extensibility() const { return extensibility2String(m_ctti.extensibility); } + +private: + const CompleteTopicTypeInformation m_ctti; + static QString extensibility2String(dds::core::xtypes::ExtensibilityKind extensiblity); +}; + +class IdlServiceWrapper final : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString svcName READ svcName) + Q_PROPERTY(QString baseFilePath READ baseFilePath) +public: + IdlServiceWrapper(const QString &svcName, const QString &baseFilePath) + : m_svcName(svcName) + , m_baseFilePath(baseFilePath) + {} + QString svcName() const { return m_svcName; } + QString baseFilePath() const { return m_baseFilePath; } +private: + const QString m_svcName; + const QString m_baseFilePath; +}; + +class FunctionTopicWrapper final : public NameWrapper +{ + Q_OBJECT + Q_PROPERTY(QString onReply READ onReply) + Q_PROPERTY(QString onRequest READ onRequest) + Q_PROPERTY(QString onError READ onError) + Q_PROPERTY(bool isCoreFt READ isCoreFt) + Q_PROPERTY(bool routeInReplay READ routeInReplay) + + Q_PROPERTY(QString reply READ reply) + Q_PROPERTY(QString replyFlc READ replyFlc) + Q_PROPERTY(QString replyHandler READ replyHandler) + Q_PROPERTY(QString replyHandlerFlc READ replyHandlerFlc) + Q_PROPERTY(QString replyType READ replyType) + Q_PROPERTY(QString replyTypeFro READ replyTypeFro) + Q_PROPERTY(QVariantList replyParams READ replyParams) + Q_PROPERTY(QString replyHandlerHash READ replyHandlerHash WRITE setReplyHandlerHash) + + Q_PROPERTY(QString request READ request) + Q_PROPERTY(QString requestFlc READ requestFlc) + Q_PROPERTY(QString requestHandler READ requestHandler) + Q_PROPERTY(QString requestHandlerFlc READ requestHandlerFlc) + Q_PROPERTY(QString requestType READ requestType) + Q_PROPERTY(QVariantList requestParams READ requestParams) + +public: + FunctionTopicWrapper(const FunctionTopic &ft, + const CompleteTopicTypeInformation &siRequest, + const CompleteTopicTypeInformation &siReply, + QObject *parent) + : NameWrapper(ft.name, parent) + , m_ft(ft) + , m_request(siRequest) + , m_reply(siReply) + {} + QString onReply() const { return QLatin1String("on") + m_ft.replyTopicName; } + QString onRequest() const { return QLatin1String("on") + m_ft.requestTopicName; } + QString onError() const { return QLatin1String("on") + m_ft.name + QLatin1String("Error"); } + bool isCoreFt() const { return (m_ft.replyTopicType == QStringLiteral("Core::FunctionReply") || m_ft.replyTopicType == QStringLiteral("FunctionReply")); } + bool routeInReplay() const { return m_ft.routeInReplay; } + + QString reply() const { return m_ft.replyTopicName; } + QString replyFlc() const { return StructBuilder::firstCharLowercase(reply()); } + QString replyHandler() const { return m_ft.replyTopicName + QLatin1String("Handler"); } + QString replyHandlerFlc() const { return StructBuilder::firstCharLowercase(replyHandler()); } + QString replyType() const { return m_reply.name; } + QString replyTypeFro() const { return isCoreFt() ? QStringLiteral("Core::FunctionReplyObject") : m_reply.name; } + + QString request() const { return m_ft.requestTopicName; } + QString requestFlc() const { return StructBuilder::firstCharLowercase(request()); } + QString requestHandler() const { return m_ft.requestTopicName + QLatin1String("Handler"); } + QString requestHandlerFlc() const { return StructBuilder::firstCharLowercase(requestHandler()); } + QString requestType() const { return m_request.name; } + + void addRequestParams(const QVector ¶ms) + { + for (const auto ¶m : params) + m_requestParams.push_back(QVariant::fromValue(new IdlDataWrapper(param, this))); + } + QVariantList requestParams() const + { + return m_requestParams; + } + void addReplyParams(const QVector ¶ms) + { + for (const auto ¶m : params) + m_replyParams.push_back(QVariant::fromValue(new IdlDataWrapper(param, this))); + } + QVariantList replyParams() const + { + return m_replyParams; + } + + QString replyHandlerHash() const + { + return m_replyHandlerHash; + } + void setReplyHandlerHash(const QString &hash) + { + m_replyHandlerHash = hash; + } + +private: + const FunctionTopic m_ft; + const CompleteTopicTypeInformation m_request; + const CompleteTopicTypeInformation m_reply; + QVariantList m_requestParams; + QVariantList m_replyParams; + QString m_replyHandlerHash; +}; + +class FunctionTopicsWrapper final : public VariantListWrapper +{ + Q_OBJECT +public: + Q_PROPERTY(QVariantList topicsToRouteInReplay READ topicsToRouteInReplay) + + using VariantListWrapper::VariantListWrapper; + + FunctionTopicWrapper *addFunctionTopic(const FunctionTopic &ft, + const CompleteTopicTypeInformation &siRequest, + const CompleteTopicTypeInformation &siReply) + { + FunctionTopicWrapper *ret = new FunctionTopicWrapper(ft, siRequest, siReply, this); + m_member += QVariant::fromValue(ret); + return ret; + } + + + QVariantList topicsToRouteInReplay() const + { + QVariantList vl; + vl.reserve(m_member.size()); + for (const QVariant &v : m_member) + { + FunctionTopicWrapper *ft = v.value(); + if (ft->routeInReplay()) + vl += v; + } + return vl; + } +}; + +class DataTopicWrapper final : public NameWrapper +{ + Q_OBJECT + Q_PROPERTY(QString serviceName READ serviceName) + Q_PROPERTY(QString type READ type) + Q_PROPERTY(QString typeFq READ typeFq) + Q_PROPERTY(QVariant key READ key) + Q_PROPERTY(bool dtcImpl READ dtcImpl) + Q_PROPERTY(bool dtsImpl READ dtsImpl) + Q_PROPERTY(bool recordTopic READ recordTopic) + Q_PROPERTY(QVariant recTable READ recTable) + Q_PROPERTY(QVariantList trColumns READ trColumns) + Q_PROPERTY(bool hasTrColumns READ hasTrColumns) + Q_PROPERTY(bool routeToClient READ routeToClient) + Q_PROPERTY(bool routeToServer READ routeToServer) + Q_PROPERTY(bool routeInReplay READ routeInReplay) + Q_PROPERTY(bool autosubscribe READ isAutoSubscribe) + +public: + DataTopicWrapper(const CompleteTopicTypeInformation &ctti, QObject *parent) + : NameWrapper(ctti.name, parent) + , m_ctti(ctti) + , m_keyData(nullptr) + , m_recordData(nullptr) + {} + + void setKeyColumnData(const IdlData &data) + { + delete m_keyData; + m_keyData = new IdlDataWrapper(data, this); + } + + void setTrColumns(const QVector ¶ms) + { + for (const auto ¶m : params) + m_trColumns.push_back(QVariant::fromValue(new IdlDataWrapper(param, this))); + } + + QVariantList trColumns() const + { + return m_trColumns; + } + + bool hasTrColumns() const + { + return !m_trColumns.empty(); + } + + void setRecordTableInformation(const CompleteTableInformation &cti) + { + delete m_recordData; + m_recordData = new MemberInformationListWrapper(cti.name, cti.columns, this); + m_ctti.recordTopic = true; + } + + QString serviceName() const { return m_ctti.serviceName; } + QString type() const { return m_ctti.name; } + QString typeFq() const; + QVariant key() const { return m_keyData ? QVariant::fromValue(m_keyData) : QVariant(); } + bool dtsImpl() const { return m_ctti.dtsAutogen; } + bool dtcImpl() const { return m_ctti.dtcAutogen; } + bool recordTopic() const { return m_ctti.recordTopic && m_recordData; } + bool routeToClient() const { return m_ctti.routeToClient; } + bool routeToServer() const { return m_ctti.routeToServer; } + bool routeInReplay() const { return m_ctti.routeInReplay; } + bool isAutoSubscribe() const { return m_ctti.autosubscribe; } + QVariant recTable() const { return recordTopic() ? QVariant::fromValue(m_recordData) : QVariant(); } +private: + CompleteTopicTypeInformation m_ctti; + IdlDataWrapper *m_keyData; + MemberInformationListWrapper *m_recordData; + QVariantList m_trColumns; +}; + +class DataTopicsWrapper final : public VariantListWrapper +{ + Q_OBJECT +public: + Q_PROPERTY(QVariantList topicsToRouteInReplay READ topicsToRouteInReplay) + Q_PROPERTY(QVariantList topicsToRouteToClient READ topicsToRouteToClient) + Q_PROPERTY(QVariantList topicsToRouteToServer READ topicsToRouteToServer) + + using VariantListWrapper::VariantListWrapper; + + DataTopicWrapper *addDataTopic(const CompleteTopicTypeInformation &ctti) + { + DataTopicWrapper *ret = new DataTopicWrapper(ctti, this); + m_member += QVariant::fromValue(ret); + return ret; + } + + + QVariantList memberByFilter(const std::function &acceptFilter) const + { + QVariantList vl; + vl.reserve(m_member.size()); + for (const QVariant &v : m_member) + { + DataTopicWrapper *dtw = v.value(); + if (dtw && acceptFilter(dtw)) + vl += v; + } + return vl; + } + + QVariantList topicsToRouteInReplay() const + { + return memberByFilter([](const DataTopicWrapper *dtw) { return dtw->routeInReplay(); }); + } + QVariantList topicsToRouteToClient() const + { + return memberByFilter([](const DataTopicWrapper *dtw) { return dtw->routeToClient(); }); + } + QVariantList topicsToRouteToServer() const + { + return memberByFilter([](const DataTopicWrapper *dtw) { return dtw->routeToServer(); }); + } +}; + +class IdlServicesWrapper final : public VariantListWrapper +{ + Q_OBJECT +public: + using VariantListWrapper::VariantListWrapper; + + void addService(const QString &svcName, const QString &baseFilePath) + { + m_member += QVariant::fromValue(new IdlServiceWrapper(svcName, baseFilePath)); + } +}; + +} diff --git a/Lts/Common/CodeGen/README.txt b/Lts/Common/CodeGen/README.txt new file mode 100755 index 000000000..f128ada89 --- /dev/null +++ b/Lts/Common/CodeGen/README.txt @@ -0,0 +1,37 @@ +# +# Hinweise für den CodeGen +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +Erstellt: Mai 2022, Norbert Thoden + +Der Aufruf des CodeGen kann z.B. so aussehen: + +codegen -s /tmp/ppm_serviceORG.xml -i /shd/CTC/TOOLS/TDS/TDS-build/service_idl_files.txt -a dds_topics -o /tmp /shd/CTC/TOOLS/TDS/TDS-source/Lts/Common/CodeGen/svc_record_replay_plugin.cpp.grantlee +Edit Juni 2023, Niklas Schmidt + +Ebenso ist es nicht möglich einen Struct aus einem anderen Service(Service B) zu verwenden, +der in sich widerum einen anderen Struct aus dem anderen Service (Service B) beinhaltet. + +Bsp: + +Service A = Struct1{ int id, + Struct2 test + } + +Service B = Struct2{ + int id2, + Struct3 test + } + +Service B = Struct3{ + std::string test + } + +Mögliche Diagnose: + +Wenn Struct3 aus Service A aufgerufen wird, ist wahrscheinlich nicht mehr gekennzeichnet dass der Struct3 aus Service B kommt. Dieser kann somit nicht +aufgelöst werden diff --git a/Lts/Common/CodeGen/SqlGenerator.cpp b/Lts/Common/CodeGen/SqlGenerator.cpp new file mode 100755 index 000000000..bd800cc79 --- /dev/null +++ b/Lts/Common/CodeGen/SqlGenerator.cpp @@ -0,0 +1,943 @@ + + + +#include "SqlGenerator.h" +#include "StructBuilder.h" +#include "XmlReader.h" + +#include +#include +#include + +#include + +using namespace CodeGen; + + +static QString getExerciseSchemaName(int exerciseId) +{ + return QStringLiteral("ex_") + QString::number(exerciseId); +} + +SqlGenerator::SqlGenerator(XmlReader *xmlReader, StructBuilder *structBuilder) + : m_xmlReader(xmlReader) + , m_structBuilder(structBuilder) + , m_createdByXmlFile(false) + , m_isValid(true) + , m_exerciseId(0) + , m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("CodeGen.SqlGenerator")) +{} + +SqlGenerator::SqlGenerator(const QString &xmlFile) + : m_xmlReader(new XmlReader) + , m_structBuilder(new StructBuilder(m_xmlReader)) + , m_createdByXmlFile(true) + , m_isValid(false) + , m_exerciseId(0) + , m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("CodeGen.SqlGenerator")) +{ + m_isValid = m_xmlReader->readServiceFile(xmlFile, QString()) && m_structBuilder->buildStructures(); +} + +SqlGenerator::~SqlGenerator() +{ + if (m_createdByXmlFile) + { + delete m_xmlReader; + delete m_structBuilder; + } +} + + +bool SqlGenerator::generateData(int exerciseId) +{ + DEBUG_LOG(m_logger) << "Generating SQL data for service " << getServiceAbbr() << " in exercise " << exerciseId << ", this: " << this; + + m_exerciseId = exerciseId; + m_sqlGlobal = SqlStatements(); + m_sqlExercise = SqlStatements(); + m_sqlItemsGlobal = SqlItems(); + m_sqlItemsExercise = SqlItems(); + m_referencedSevices.clear(); + + if (!handleTables()) + return false; + + if (!handleViews()) + return false; + + if (!handleEnumerations()) + return false; + + return true; +} + +bool SqlGenerator::createStatementsForTable(const QString &tableName, const QString &tableNameOut) +{ + const QMap tables = m_structBuilder->getDbTables(); + for (const CompleteTableInformation &cti : tables) + { + if (cti.name != tableName) + continue; + CompleteTableInformation ctiOut = cti; + ctiOut.name = tableNameOut; + return handleTable(ctiOut); + } + return false; +} + +bool SqlGenerator::handleTables() +{ + const QMap tables = m_structBuilder->getDbTables(); + for (const CompleteTableInformation &cti : tables) + { + if (!handleTable(cti)) + return false; + } + + const QMap recTables = m_structBuilder->getRecordingTopicTables(); + MemberInformation miColumnIsValid; + miColumnIsValid.db.columnName = QStringLiteral("dds_is_alive"); + miColumnIsValid.db.datatype = QStringLiteral("boolean"); + miColumnIsValid.db.defaultValue = QStringLiteral("true"); + MemberInformation miInsertionTime; + miInsertionTime.cpp.type = QStringLiteral("timestamp"); + miInsertionTime.db.columnName = QStringLiteral("insert_time"); + miInsertionTime.db.defaultValue = QStringLiteral("now()"); + miInsertionTime.db.usedForInsert = false; + m_structBuilder->fixupQVariantConversions(miInsertionTime); + ConstraintInformation ci; + ci.name = QStringLiteral("IDX_sample_time"); + ci.affectedColumns = QStringList({QStringLiteral("sample_time")}); + ci.type = ConstraintInformation::Type::index; + for (const CompleteTableInformation &cti : recTables) + { + CompleteTableInformation ctiTmp(cti); + ctiTmp.columns.push_back(miColumnIsValid); + ctiTmp.columns.push_back(miInsertionTime); + ctiTmp.constraints.push_back(ci); + if (!handleTable(ctiTmp)) + return false; + } + return true; +} + +bool SqlGenerator::handleViews() +{ + const QMap views = m_structBuilder->getDbViews(); + for (const ViewInformation &vi : views) + { + if (!handleView(vi)) + return false; + } + + return true; +} + +bool SqlGenerator::handleView(const ViewInformation &vi) +{ + DEBUG_LOG(m_logger) << "Handle view " << vi.name; + + const QString parent = vi.baseTable; + + DEBUG_LOG(m_logger) << "Parent = " << parent; + + if (!parent.isEmpty()) + { + const CompleteStructInformation csi = m_structBuilder->structInformationByTableName(parent); + const QString createViewStmt(QStringLiteral("CREATE OR REPLACE VIEW %1 AS SELECT %2\nFROM %3\nWHERE %4;\n")); + const QString dropViewStmt(QStringLiteral("DROP VIEW IF EXISTS %1;")); + + DEBUG_LOG(m_logger) << "csi.primaryTable = " << csi.primaryTable.data()->name; + + if (!csi.primaryTable.isNull()) + { + QStringList fromTables; + fromTables += vi.baseSchema+QLatin1Char('.')+vi.baseTable; + + QStringList whereConditions; + if (vi.filter.contains(QLatin1String("exercise_id"))) + whereConditions += QLatin1String("exercise_id=")+QString::number(m_exerciseId); + + QStringList fqColumnNames; + + + for (const MemberInformation &mi : csi.member) + { + if (mi.db.usedForView) + fqColumnNames += mi.db.table + QLatin1Char('.') + mi.db.columnName + QLatin1String(" AS ") + mi.db.getColumnNameView(); + } + + DEBUG_LOG(m_logger) << "fqColumnNames = " << fqColumnNames.join(QStringLiteral(", ")); + + + if (!whereConditions.isEmpty()) + addToCreateViews(createViewStmt.arg(csi.viewName, + fqColumnNames.join(QStringLiteral(", ")), + fromTables.join(QStringLiteral(", ")), + whereConditions.join(QStringLiteral(" AND\n"))), + vi.exerciseDependency); + addToDropViews(dropViewStmt.arg(csi.viewName), vi.exerciseDependency); + } + } + + return true; + +} + +bool SqlGenerator::handleTable(const CompleteTableInformation &cti) +{ + DEBUG_LOG(m_logger) << "Handle table " << cti.name; + + if (cti.columns.isEmpty()) + { + WARNING_LOG(m_logger) << "Table " << cti.name + << " has no DatabaseServiceStruct - not generating anything for this!"; + return true; + } + + const QString dropTableStmt(QStringLiteral("DROP TABLE IF EXISTS %1 CASCADE;")); + const QString createTableStmt(QStringLiteral("CREATE TABLE IF NOT EXISTS %1 (%2\n);")); + const QString commentTableStmt(QStringLiteral("COMMENT ON TABLE %1 IS '%2';")); + + QStringList tableComments; + addToDropTables(dropTableStmt.arg(cti.name), cti.exerciseDependency); + + if ((cti.exerciseDependency & Global) == Global) + { + addToCreateTables(createTableStmt.arg(cti.name, + getTableColumnCreateStatements(cti.name, + cti.columns, + &tableComments, + Global)), + Global); + } + if ((cti.exerciseDependency & Exercise) == Exercise) + { + addToCreateTables(createTableStmt.arg(cti.name, + getTableColumnCreateStatements(cti.name, + cti.columns, + &tableComments, + Exercise)), + Exercise); + } + addToTables(cti.name, cti.exerciseDependency); + + if (!cti.comment.isEmpty()) + addToCreateTables(commentTableStmt.arg(cti.name, fixupComment(cti.comment)), cti.exerciseDependency); + addToCreateTables(tableComments, cti.exerciseDependency); + + const QString parent = m_xmlReader->getStruct2BaseClassRelations().value(cti.name); + const CompleteStructInformation csi = m_structBuilder->structInformationByTableName(cti.name); + if (!parent.isEmpty()) + { + const QString createViewStmt(QStringLiteral("CREATE OR REPLACE VIEW %1 AS SELECT %2\nFROM %3\nWHERE %4;\n")); + const QString dropViewStmt(QStringLiteral("DROP VIEW IF EXISTS %1;")); + if (!csi.primaryTable.isNull()) + { + QStringList fqColumnNames; + QStringList fromTables; + QStringList whereConditions; + fromTables += cti.name; + + + for (const MemberInformation &mi : csi.member) + { + if (mi.db.usedForView) + fqColumnNames += mi.db.table + QLatin1Char('.') + mi.db.columnName + QLatin1String(" AS ") + mi.db.getColumnNameView(); + if (mi.cpp.isReadOnly && mi.db.isForeignKey) + { + + + + for (const QSharedPointer &tiPtr : csi.tables) + { + if (tiPtr->name == mi.db.table) + { + const QSharedPointer fki = tiPtr->fkInformationForColumn(mi.db.table, mi.db.columnName); + if (!fki.isNull()) + { + + if (!fromTables.contains(fki->destination.tableName)) + { + whereConditions += fki->destination.tableName + QLatin1Char('.') + fki->destination.columnName + + QLatin1String(" = ") + + fki->source.tableName + QLatin1Char('.') + fki->source.columnName; + fromTables += fki->destination.tableName; + } + } + break; + } + } + } + } + if (!whereConditions.isEmpty()) + { + addToCreateViews(createViewStmt.arg(csi.viewName, + fqColumnNames.join(QStringLiteral(", ")), + fromTables.join(QStringLiteral(", ")), + whereConditions.join(QStringLiteral(" AND\n"))), + cti.exerciseDependency); + } + addToDropViews(dropViewStmt.arg(csi.viewName), cti.exerciseDependency); + } + } + + if (!generateDbNotifications(cti.name, csi.dbNotifications, cti.exerciseDependency)) + return false; + + generateForeignKeys(cti.foreignKeys, cti.exerciseDependency); + + generateConstraints(cti.name, cti.constraints, cti.exerciseDependency); + + return true; +} + + +QString SqlGenerator::getTableColumnCreateStatements(const QString &tableName, + const QVector &columns, + QStringList *tableComments, + ExerciseDependency ed) +{ + Q_ASSERT(ed == Global || ed == Exercise); + + const QString commentColumnStmt(QStringLiteral("COMMENT ON COLUMN %1 IS '%2';")); + + + int maxLength = 1; + for (const MemberInformation &mi : columns) + maxLength = std::max(maxLength, mi.db.columnName.length()); + maxLength += 1; + + QStringList retCols; + retCols.reserve(columns.size() + 1); + retCols += QString(); + for (int i = 0; i < columns.size(); ++i) + { + const MemberInformation &mi = columns.at(i); + const FullQualifiedColumnInformation &ci = mi.db; + const QString spaces(maxLength - ci.columnName.length(), QLatin1Char(' ')); + QString column = ci.columnName + spaces + ci.datatype; + if (ci.hasSequence) + { + + const QString seqDescr = generateSequence(tableName, ci, ed); + QString schema; + if (ed == Global) + schema = m_xmlReader->getServiceAbbr(); + else + schema = getExerciseSchemaName(m_exerciseId); + + column += QLatin1String(" DEFAULT nextval(( '") + + schema + QLatin1Char('.') + seqDescr + + QLatin1String("'::text)::regclass)"); + } + if (!ci.canBeNull) + column += QLatin1String(" NOT NULL"); + if (!ci.hasSequence && !ci.defaultValue.isEmpty()) + column += QLatin1String(" DEFAULT ") + ci.defaultValue; + if (i < columns.size() - 1) + column += QLatin1Char(','); + if (!ci.comment.isEmpty()) + { + const QString comment(fixupComment(ci.comment)); + const QString fqn = ci.table + QLatin1Char('.') + ci.columnName; + *tableComments += commentColumnStmt.arg(fqn, comment); + column += QLatin1String(" -- ") + comment; + } + + retCols += column; + + if (ci.isPrimaryKey) + generatePrimaryKey(tableName, ci, ed); + } + return retCols.join(QStringLiteral("\n ")); +} + +QString SqlGenerator::generateSequence(const QString &tableName, + const FullQualifiedColumnInformation &ci, + ExerciseDependency ed) +{ + const QString sequenceName = tableName + QLatin1Char('_') + ci.columnName + QLatin1String("_seq"); + + + + const QString createSequenceStmt(QLatin1String("CREATE SEQUENCE IF NOT EXISTS %1 INCREMENT %2 START %3 MINVALUE %4;")); + + const int minValue = ci.sequenceStart < 1 ? ci.sequenceStart : 1; + + addToCreateSequences(createSequenceStmt.arg(sequenceName).arg(ci.sequenceIncrement).arg(ci.sequenceStart).arg(minValue), ed); + addToSequences(sequenceName, ed); + + return sequenceName; +} + +void SqlGenerator::generatePrimaryKey(const QString &tableName, + const FullQualifiedColumnInformation &ci, + ExerciseDependency ed) +{ + QString pkName = QLatin1String("PK_") + tableName + QLatin1Char('_') + ci.columnName; + + if (pkName.size() > 63) pkName.resize(63); + + addToPks(pkName, ed); + + + + const QString pkConstraint(QStringLiteral("ALTER TABLE IF EXISTS %1 ADD CONSTRAINT %2 PRIMARY KEY (%3);")); + addToCreatePks(pkConstraint.arg(tableName, pkName, ci.columnName), ed); + + const QString pkDropConstraint(QStringLiteral("ALTER TABLE IF EXISTS %1 DROP CONSTRAINT IF EXISTS %2 CASCADE;")); + addToDropConstraints(pkDropConstraint.arg(tableName, pkName), ed); +} + +void SqlGenerator::generateForeignKeys(const QVector > &foreignKeys, + ExerciseDependency ed) +{ + for (const QSharedPointer &fk : foreignKeys) + generateForeignKey(fk, ed); +} + +void SqlGenerator::generateForeignKey(const QSharedPointer &foreignKey, + ExerciseDependency ed) +{ + QString fkName = QLatin1String("FK_") + foreignKey->source.tableName + + QLatin1Char('_') + foreignKey->source.columnName + + QLatin1Char('_') + foreignKey->destination.tableName + + QLatin1Char('_') + foreignKey->destination.columnName; + + + + if (fkName.size() > 63) fkName = foreignKey->name; + + addToFks(fkName, ed); + + QString fkConstraint(QStringLiteral("ALTER TABLE %1 ADD CONSTRAINT %2 FOREIGN KEY (%3) REFERENCES %4 (%5)")); + switch (foreignKey->deleteAction) + { + case ForeignKeyInformation::OnDeleteAction::NoAction: + fkConstraint += QStringLiteral(" ON DELETE NO ACTION"); + break; + case ForeignKeyInformation::OnDeleteAction::Cascade: + fkConstraint += QStringLiteral(" ON DELETE CASCADE ON UPDATE CASCADE"); + break; + case ForeignKeyInformation::OnDeleteAction::Restrict: + fkConstraint += QStringLiteral(" ON DELETE RESTRICT"); + break; + case ForeignKeyInformation::OnDeleteAction::SetNull: + fkConstraint += QStringLiteral(" ON DELETE SET NULL"); + break; + case ForeignKeyInformation::OnDeleteAction::SetDefault: + fkConstraint += QStringLiteral(" ON DELETE SET DEFAULT"); + break; + } + + fkConstraint += QLatin1Char(';'); + + + + QString destination; + if (!foreignKey->destination.schema.isEmpty()) + { + const QString &extSchema = foreignKey->destination.schema; + destination = extSchema + QLatin1Char('.') + foreignKey->destination.tableName; + } + else + destination = foreignKey->destination.tableName; + + const QString &extSvcAbbr = foreignKey->destination.serviceAbbr; + + if (!extSvcAbbr.isEmpty() && !m_referencedSevices.contains(extSvcAbbr)) + m_referencedSevices += extSvcAbbr; + + addToCreateFks(fkConstraint.arg(foreignKey->source.tableName, fkName, foreignKey->source.columnName, + destination, foreignKey->destination.columnName), ed); + + const QString fkDropConstraint(QStringLiteral("ALTER TABLE IF EXISTS %1 DROP CONSTRAINT IF EXISTS %2 CASCADE;")); + addToDropConstraints(fkDropConstraint.arg(foreignKey->source.tableName, fkName), ed); + +} + + +void SqlGenerator::generateConstraints(const QString &tableName, + const QVector &constraints, + ExerciseDependency ed) +{ + for (const ConstraintInformation &constraint : constraints) + generateConstraint(tableName, constraint, ed); +} + +void SqlGenerator::generateConstraint(const QString &tableName, + const ConstraintInformation &constraint, + ExerciseDependency ed) +{ + const QString dropConstraint(QStringLiteral("ALTER TABLE IF EXISTS %1 DROP CONSTRAINT IF EXISTS %2 CASCADE;")); + switch (constraint.type) + { + case ConstraintInformation::Type::index: + { + QString constraintName = QLatin1String("IDX_") + tableName + QLatin1Char('_') + + constraint.affectedColumns.join(QStringLiteral("_")); + + if (constraintName.size() > 63) constraintName.resize(63); + + const QString index(QStringLiteral("CREATE INDEX %1 ON %2 (%3);")); + + addToCreateConstraints(index.arg(constraintName, tableName, constraint.affectedColumns.join(QStringLiteral(", "))), ed); + addToConstraints(constraintName, ed); + + addToDropConstraints(dropConstraint.arg(tableName, constraintName), ed); + + break; + } + case ConstraintInformation::Type::unique: + { + QString constraintName = QLatin1String("UQ_") + tableName + QLatin1Char('_') + + constraint.affectedColumns.join(QStringLiteral("_")); + + if (constraintName.size() > 63) constraintName.resize(63); + const QString unique(QStringLiteral("ALTER TABLE %1 ADD CONSTRAINT %2 UNIQUE (%3);")); + + addToCreateConstraints(unique.arg(tableName, constraintName, constraint.affectedColumns.join(QStringLiteral(", "))), ed); + addToConstraints(constraintName, ed); + + addToDropConstraints(dropConstraint.arg(tableName, constraintName), ed); + + break; + } + case ConstraintInformation::Type::unknown: + break; + } +} + +bool SqlGenerator::handleEnumerations() +{ + const QVector enumerations = m_xmlReader->getEnumerations(); + for (const EnumInformation &ei : enumerations) + { + if (!handleEnumeration(ei)) + return false; + } + return true; +} + +bool SqlGenerator::handleEnumeration(const EnumInformation &ei) +{ + const QStringList sql = handleEnumeration(ei, QString()); + if (m_sqlItemsGlobal.tables.contains(ei.tableName)) + m_sqlGlobal.enumerationValues += sql; + if (m_sqlItemsExercise.tables.contains(ei.tableName)) + m_sqlExercise.enumerationValues += sql; + + return true; +} + +QStringList SqlGenerator::handleEnumeration(const EnumInformation &ei, const QString &dbSchema) const +{ + DEBUG_LOG(m_logger) << "Handle enumeration " << ei.enumName; + + if (ei.tableName.isEmpty()) + { + INFO_LOG(m_logger) << "No table for enumeration " << ei.enumName << " - not creating inserts for this enum"; + return QStringList(); + } + + const QString insertEnum(QStringLiteral("INSERT INTO %1 (%2) VALUES (%3);")); + const QString columnNames(QStringLiteral("id, name")); + const QString tablename = dbSchema.isEmpty() ? ei.tableName : dbSchema + QLatin1Char('.') + ei.tableName; + + + QStringList enumerationValues; + for (const EnumKeyValuePair &evp : ei.values) + { + const QString values(QString::number(evp.value) + QLatin1String(", '") + + fixupComment(evp.key) + QLatin1Char('\'')); + enumerationValues += insertEnum.arg(tablename, columnNames, values); + } + + return enumerationValues; +} + +bool SqlGenerator::generateDbNotifications(const QString &tableName, + const QVector ¬ifications, + ExerciseDependency ed) +{ + for (const DbNotifcationInformation ¬ification : notifications) + { + if (notification.paramNames.isEmpty() || + notification.paramNames.size() > 1) + { + ERROR_LOG(m_logger) << "Can not handle more than one parameter"; + continue; + } + generateDbNotification(notification, ed); + if (!generateDbNotificationTrigger(tableName, notification, ed)) + return false; + } + return true; +} + +void SqlGenerator::generateDbNotification(const DbNotifcationInformation ¬ification, + ExerciseDependency ed) +{ + const QString func(QStringLiteral("CREATE OR REPLACE FUNCTION public.%1()\n" + "RETURNS TRIGGER AS $$\n" + "DECLARE\n" + " toNotify RECORD;\n" + " toReturn RECORD;\n" + "BEGIN\n" + " IF (TG_OP = 'INSERT') THEN\n" + " toNotify = NEW;\n" + " toReturn = NEW;\n" + " ELSIF (TG_OP = 'UPDATE') THEN\n" + " toNotify = OLD;\n" + " toReturn = NEW;\n" + " ELSIF (TG_OP = 'DELETE') THEN\n" + " toNotify = OLD;\n" + " toReturn = OLD;\n" + " END IF;\n" + " \n" + " PERFORM pg_notify('%2', CAST(toNotify.%3 AS text));\n" + " RETURN toReturn;\n" + "END;\n" + "$$ LANGUAGE plpgsql;")); + + const QString triggerFuncName(getNotificationFunctionName(notification)); + addToDbFunctions(func.arg(triggerFuncName, notification.methodName, notification.paramNames.first()), ed); +} + +bool SqlGenerator::generateDbNotificationTrigger(const QString &tableName, + const DbNotifcationInformation ¬ification, + ExerciseDependency ed) +{ + + const QString drop(QStringLiteral("DROP TRIGGER IF EXISTS %1 ON %2;")); + const QString func(QStringLiteral("CREATE TRIGGER %1\n") + + QStringLiteral("%2 ON %3 FOR EACH ROW EXECUTE PROCEDURE public.%4();")); + + for (DbNotifcationInformation::NotificationType notifyType : notification.notificationType) + { + QString when; + QString triggerNameShortcut; + switch (notifyType) + { + case DbNotifcationInformation::NotificationType::BeforeInsert: + when = QStringLiteral("BEFORE INSERT"); + triggerNameShortcut = QStringLiteral("bi"); + break; + case DbNotifcationInformation::NotificationType::AfterInsert: + when = QStringLiteral("AFTER INSERT"); + triggerNameShortcut = QStringLiteral("ai"); + break; + case DbNotifcationInformation::NotificationType::BeforeUpdate: + when = QStringLiteral("BEFORE UPDATE"); + triggerNameShortcut = QStringLiteral("bu"); + break; + case DbNotifcationInformation::NotificationType::AfterUpdate: + when = QStringLiteral("AFTER UPDATE"); + triggerNameShortcut = QStringLiteral("au"); + break; + case DbNotifcationInformation::NotificationType::BeforeDelete: + when = QStringLiteral("BEFORE DELETE"); + triggerNameShortcut = QStringLiteral("bd"); + break; + case DbNotifcationInformation::NotificationType::AfterDelete: + when = QStringLiteral("AFTER DELETE"); + triggerNameShortcut = QStringLiteral("ad"); + break; + } + const QString triggerName(QLatin1String("trg_") + triggerNameShortcut + QLatin1Char('_') + notification.methodName); + const QString triggerFuncName(getNotificationFunctionName(notification)); + addToDbFunctionTrigger(drop.arg(triggerName, tableName), ed); + addToDbFunctionTrigger(func.arg(triggerName, when, tableName, triggerFuncName), ed); + } + return true; +} + +QString SqlGenerator::getNotificationFunctionName(const DbNotifcationInformation ¬ification) const +{ + return getServiceAbbr() + QLatin1Char('_') + notification.methodName; +} + + +QString SqlGenerator::fixupComment(const QString &comment) +{ + + QString ret = comment; + ret = ret.remove(QLatin1Char('\'')); + ret = ret.replace(QLatin1Char(';'), QLatin1Char(',')); + return ret; +} + +QStringList SqlGenerator::getDbSettings(const QString &dbSchema) const +{ + QStringList data; + + + QString dbUser(QStringLiteral("ets_")); + if (m_exerciseId > 0) + dbUser += QStringLiteral("ex_00"); + else + dbUser += m_xmlReader->getServiceAbbr().toLower(); + + + data.append(QLatin1String("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + dbSchema + QLatin1Char('\n') + + QLatin1String(" GRANT SELECT,INSERT,UPDATE,DELETE,TRUNCATE ON TABLES TO ") + dbUser + QLatin1Char(';')); + + + data.append(QLatin1String("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + dbSchema + QLatin1Char('\n') + + QLatin1String(" GRANT SELECT, REFERENCES ON TABLES TO ets_ro;")); + + data.append(QLatin1String("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + dbSchema + QLatin1Char('\n') + + QLatin1String(" GRANT EXECUTE ON FUNCTIONS TO ") + dbUser + QLatin1Char(';')); + + data.append(QLatin1String("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + dbSchema + QLatin1Char('\n') + + QLatin1String(" GRANT ALL ON SEQUENCES TO ") + dbUser + QLatin1Char(';')); + + + data.append(QLatin1String("SET SEARCH_PATH TO ") + dbSchema + QLatin1String(", PUBLIC;")); + + QStringList deps; + deps.reserve(m_referencedSevices.size()); + for(const QString &svc : m_referencedSevices) + deps += QStringLiteral("-- DEPENDS: ") + svc; + + return deps + data; +} + +QStringList SqlGenerator::getAllSqlStmts(const QString &dbSchema, ExerciseDependency ed) const +{ + DEBUG_LOG(m_logger) << "Creating statements for service " << getServiceAbbr() << " in exercise " << m_exerciseId << ", this: " << this; + + QStringList data; + + const SqlStatements &sql = getStatements(ed); + if (sql.isEmpty()) + return data; + + if (!dbSchema.isEmpty()) + data << getDbSettings(dbSchema); + + + data.append(QStringLiteral("\n-- Drop tables/views/sequences/constraints")); + data.append(sql.dropViews); + data.append(sql.dropConstraints); + data.append(sql.dropTables); + data.append(sql.dropSequences); + + + data.append(QStringLiteral("\n-- Create tables")); + data.append(sql.createTables); + + + data.append(QStringLiteral("\n-- Create sequence(s)")); + data.append(sql.createSequences); + + + data.append(QStringLiteral("\n-- Create primary keys")); + data.append(sql.createPrimaryKeys); + + + data.append(QStringLiteral("\n-- Create foreign keys")); + data.append(sql.createForeignKeys); + + + data.append(QStringLiteral("\n-- Create views")); + data.append(sql.createViews); + + + data.append(QStringLiteral("\n-- --------------------------------------------------------------------------------")); + data.append(QStringLiteral("\n-- Inserts for static data i.e. enums\n")); + data.append(sql.enumerationValues); + + + data.append(QStringLiteral("\n-- --------------------------------------------------------------------------------")); + data.append(QStringLiteral("\n-- Notify functions for callbacks (signals in db interface)\n")); + data.append(sql.dbFunctions); + + + data.append(QStringLiteral("\n-- --------------------------------------------------------------------------------")); + data.append(QStringLiteral("\n-- Notify function trigger for callbacks (signals in db interface)\n")); + data.append(sql.dbFunctionTrigger); + + + data.append(QStringLiteral("\n-- --------------------------------------------------------------------------------")); + data.append(QStringLiteral("\n-- Indexes \n")); + data.append(sql.createConstraints); + + return data; +} + +QStringList SqlGenerator::getEnumerationInsertValues(const QString &table, const QString &dbSchema) const +{ + const QVector enumerations = m_xmlReader->getEnumerations(); + + for (const EnumInformation &ei : enumerations) + { + if (ei.tableName == table) + return handleEnumeration(ei, dbSchema); + } + return QStringList(); +} + +bool SqlGenerator::writeSqlToFile(const QString &dbSchema, const QString &sqlFile, ExerciseDependency ed) const +{ + return writeFile(sqlFile, getAllSqlStmts(dbSchema, ed).join(QLatin1Char('\n'))); +} + +bool SqlGenerator::writeSql(const QString &filePath, ExerciseDependency ed) +{ + Q_ASSERT(ed == Global || ed == Exercise); + + + QString sqlFile = filePath + getServiceAbbr(); + if (ed == ExerciseDependency::Exercise) + sqlFile += QLatin1Char('_') + getExerciseSchemaName(m_exerciseId); + sqlFile += QLatin1String(".sql"); + + const SqlStatements &sql = getStatements(ed); + if (sql.isEmpty()) + { + DEBUG_LOG(m_logger) << "Sql file is emtpy: " << sqlFile; + return writeFile(sqlFile, QString()); + } + + + + const QString dbSchema = (ed == ExerciseDependency::Exercise ? + getExerciseSchemaName(m_exerciseId) : + getServiceAbbr()); + + INFO_LOG(m_logger) << "Creating sql-stmt for schema (" << dbSchema << ")"; + return writeSqlToFile(dbSchema, sqlFile, ed); +} + + +template +static void addInternal(QStringList &global, + QStringList &exercise, + const T &stmt, + ExerciseDependency ed) +{ + switch (ed) + { + case ExerciseDependency::Global: + global += stmt; + break; + case ExerciseDependency::Exercise: + exercise += stmt; + break; + case ExerciseDependency::GlobalAndExercise: + global += stmt; + exercise += stmt; + break; + case ExerciseDependency::Undefined: + break; + } +} + +void SqlGenerator::addToDropSequences(const QString &stmt, ExerciseDependency ed) +{ + addInternal(m_sqlGlobal.dropSequences, m_sqlExercise.dropSequences, stmt, ed); +} + +void SqlGenerator::addToDropTables(const QString &stmt, ExerciseDependency ed) +{ + addInternal(m_sqlGlobal.dropTables, m_sqlExercise.dropTables, stmt, ed); +} + +void SqlGenerator::addToDropViews(const QString &stmt, ExerciseDependency ed) +{ + addInternal(m_sqlGlobal.dropViews, m_sqlExercise.dropViews, stmt, ed); +} + +void SqlGenerator::addToCreateConstraints(const QString &stmt, ExerciseDependency ed) +{ + addInternal(m_sqlGlobal.createConstraints, m_sqlExercise.createConstraints, stmt, ed); +} + +void SqlGenerator::addToDropConstraints(const QString &stmt, ExerciseDependency ed) +{ + addInternal(m_sqlGlobal.dropConstraints, m_sqlExercise.dropConstraints, stmt, ed); +} + +void SqlGenerator::addToCreateFks(const QString &stmt, ExerciseDependency ed) +{ + addInternal(m_sqlGlobal.createForeignKeys, m_sqlExercise.createForeignKeys, stmt, ed); +} + +void SqlGenerator::addToCreatePks(const QString &stmt, ExerciseDependency ed) +{ + addInternal(m_sqlGlobal.createPrimaryKeys, m_sqlExercise.createPrimaryKeys, stmt, ed); +} + +void SqlGenerator::addToCreateSequences(const QString &stmt, ExerciseDependency ed) +{ + addInternal(m_sqlGlobal.createSequences, m_sqlExercise.createSequences, stmt, ed); +} + +void SqlGenerator::addToCreateTables(const QString &stmt, ExerciseDependency ed) +{ + addInternal(m_sqlGlobal.createTables, m_sqlExercise.createTables, stmt, ed); +} + +void SqlGenerator::addToCreateTables(const QStringList &stmts, ExerciseDependency ed) +{ + addInternal(m_sqlGlobal.createTables, m_sqlExercise.createTables, stmts, ed); +} + +void SqlGenerator::addToCreateViews(const QString &stmt, ExerciseDependency ed) +{ + addInternal(m_sqlGlobal.createViews, m_sqlExercise.createViews, stmt, ed); +} + +void SqlGenerator::addToDbFunctions(const QString &stmt, ExerciseDependency ed) +{ + addInternal(m_sqlGlobal.dbFunctions, m_sqlExercise.dbFunctions, stmt, ed); +} + +void SqlGenerator::addToDbFunctionTrigger(const QString &stmt, ExerciseDependency ed) +{ + addInternal(m_sqlGlobal.dbFunctionTrigger, m_sqlExercise.dbFunctionTrigger, stmt, ed); +} + +void SqlGenerator::addToConstraints(const QString &name, ExerciseDependency ed) +{ + addInternal(m_sqlItemsGlobal.constraints, m_sqlItemsExercise.constraints, name, ed); +} + +void SqlGenerator::addToFks(const QString &name, ExerciseDependency ed) +{ + addInternal(m_sqlItemsGlobal.foreignKeys, m_sqlItemsExercise.foreignKeys, name, ed); +} + +void SqlGenerator::addToPks(const QString &name, ExerciseDependency ed) +{ + addInternal(m_sqlItemsGlobal.primaryKeys, m_sqlItemsExercise.primaryKeys, name, ed); +} + +void SqlGenerator::addToSequences(const QString &name, ExerciseDependency ed) +{ + addInternal(m_sqlItemsGlobal.sequences, m_sqlItemsExercise.sequences, name, ed); +} + +void SqlGenerator::addToTables(const QString &name, ExerciseDependency ed) +{ + addInternal(m_sqlItemsGlobal.tables, m_sqlItemsExercise.tables, name, ed); +} + +bool SqlGenerator::writeFile(const QString &fn, const QString &content) const +{ + QFile f(fn); + if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) + { + ERROR_LOG(m_logger) << "Error opening file '" << f.fileName() << "' for writing: " + << f.errorString(); + return false; + } + f.write(content.toUtf8()); + if (!content.isEmpty()) + f.write("\n"); + f.close(); + return true; +} diff --git a/Lts/Common/CodeGen/SqlGenerator.h b/Lts/Common/CodeGen/SqlGenerator.h new file mode 100755 index 000000000..409949df5 --- /dev/null +++ b/Lts/Common/CodeGen/SqlGenerator.h @@ -0,0 +1,175 @@ + + +#pragma once + +#include +#include +#include "StructBuilder.h" +#include "codegen_export.h" + +namespace NGL +{ +class INGLLogger; +} + +namespace CodeGen +{ +struct ColumnInformation; +struct ConstraintInformation; +struct EnumInformation; +struct ForeignKeyInformation; +struct TableInformation; +class XmlReader; + +class CODEGEN_BASE_EXPORT SqlGenerator final +{ +public: + struct SqlStatements + { + QStringList dropViews; + QStringList dropTables; + QStringList dropSequences; + QStringList dropConstraints; + + QStringList createTables; + QStringList createSequences; + QStringList createPrimaryKeys; + QStringList createForeignKeys; + QStringList createViews; + QStringList createConstraints; + + QStringList enumerationValues; + QStringList dbFunctions; + QStringList dbFunctionTrigger; + + bool isEmpty() const + { + return dropViews.isEmpty() && dropTables.isEmpty() && dropSequences.isEmpty() && dropConstraints.isEmpty(); + } + }; + + struct SqlItems + { + QStringList tables; + QStringList sequences; + QStringList primaryKeys; + QStringList foreignKeys; + QStringList constraints; + }; + +public: + Q_DISABLE_COPY(SqlGenerator) + + explicit SqlGenerator(XmlReader *xmlReader, StructBuilder *structBuilder); + explicit SqlGenerator(const QString &xmlFile); + + ~SqlGenerator(); + + bool isValid() const { return m_isValid; } + + bool generateData(int exerciseId); + + bool createStatementsForTable(const QString &tableName, const QString &tableNameOut); + + SqlStatements getStatements(ExerciseDependency ed) const + { + return (ed == ExerciseDependency::Exercise ? m_sqlExercise : m_sqlGlobal); + } + + SqlItems getSqlItems(ExerciseDependency ed) const + { + return (ed == ExerciseDependency::Exercise ? m_sqlItemsExercise : m_sqlItemsGlobal); + } + + QStringList getEnumerationInsertValues(const QString &table, const QString &dbSchema) const; + QStringList getReferencedServices() const + { + return m_referencedSevices; + } + + QStringList getDbSettings(const QString &dbSchema) const; + + + bool writeSql(const QString &filePath, ExerciseDependency ed); + + bool writeSqlToFile(const QString &dbSchema, const QString &sqlFile, ExerciseDependency ed) const; + + + QString getServiceAbbr() const { return m_xmlReader->getServiceAbbr(); } + bool isServiceExerciseIndependent() const { return m_xmlReader->isServiceExerciseIndependent(); } + bool isServiceGlobal() const { return (m_xmlReader->defaultExerciseDependency() == CodeGen::ExerciseDependency::Global || m_xmlReader->defaultExerciseDependency() == CodeGen::ExerciseDependency::GlobalAndExercise); } + CodeGen::ExerciseDependency getExerciseDependency() const { return m_xmlReader->defaultExerciseDependency();} + QStringList getGroupNames() const { return m_xmlReader->getGroupNames(); } + + QStringList getAllSqlStmts(const QString &dbSchema, ExerciseDependency ed) const; + + +protected: + +private: + bool handleTables(); + bool handleViews(); + bool handleTable(const CompleteTableInformation &cti); + bool handleView(const ViewInformation &vi); + QString getTableColumnCreateStatements(const QString &tableName, + const QVector &columns, + QStringList *tableComments, ExerciseDependency ed); + QString generateSequence(const QString &tableName, const FullQualifiedColumnInformation &ci, ExerciseDependency ed); + void generatePrimaryKey(const QString &tableName, const FullQualifiedColumnInformation &ci, ExerciseDependency ed); + void generateForeignKeys(const QVector > &foreignKeys, ExerciseDependency ed); + void generateForeignKey(const QSharedPointer &foreignKey, ExerciseDependency ed); + void generateConstraints(const QString &tableName, const QVector &constraints, ExerciseDependency ed); + void generateConstraint(const QString &tableName, const ConstraintInformation &constraint, ExerciseDependency ed); + bool handleEnumerations(); + bool handleEnumeration(const EnumInformation &ei); + QStringList handleEnumeration(const EnumInformation &ei, const QString &dbSchema) const; + bool generateDbNotifications(const QString &tableName, const QVector ¬ifications, ExerciseDependency ed); + void generateDbNotification(const DbNotifcationInformation ¬ification, ExerciseDependency ed); + bool generateDbNotificationTrigger(const QString &tableName, const DbNotifcationInformation ¬ification, ExerciseDependency ed); + QString getNotificationFunctionName(const DbNotifcationInformation ¬ification) const; + static QString fixupComment(const QString &comment); + +private: + XmlReader *m_xmlReader; + StructBuilder *m_structBuilder; + bool m_createdByXmlFile; + + void addToDropSequences(const QString &stmt, ExerciseDependency ed); + void addToDropTables(const QString &stmt, ExerciseDependency ed); + void addToDropViews(const QString &stmt, ExerciseDependency ed); + void addToDropConstraints(const QString &stmt, ExerciseDependency ed); + + void addToCreateConstraints(const QString &stmt, ExerciseDependency ed); + void addToCreateFks(const QString &stmt, ExerciseDependency ed); + void addToCreatePks(const QString &stmt, ExerciseDependency ed); + void addToCreateSequences(const QString &stmt, ExerciseDependency ed); + void addToCreateTables(const QString &stmt, ExerciseDependency ed); + void addToCreateTables(const QStringList &stmts, ExerciseDependency ed); + void addToCreateViews(const QString &stmt, ExerciseDependency ed); + + void addToDbFunctions(const QString &stmt, ExerciseDependency ed); + void addToDbFunctionTrigger(const QString &stmt, ExerciseDependency ed); + + void addToConstraints(const QString &name, ExerciseDependency ed); + void addToFks(const QString &name, ExerciseDependency ed); + void addToPks(const QString &name, ExerciseDependency ed); + void addToSequences(const QString &name, ExerciseDependency ed); + void addToTables(const QString &name, ExerciseDependency ed); + + bool writeFile(const QString &fn, const QString &content) const; + + SqlStatements m_sqlGlobal; + SqlStatements m_sqlExercise; + + SqlItems m_sqlItemsGlobal; + SqlItems m_sqlItemsExercise; + + QStringList m_referencedSevices; + + bool m_isValid; + int m_exerciseId; + + NGL::INGLLogger &m_logger; +}; + +} diff --git a/Lts/Common/CodeGen/StructBuilder.cpp b/Lts/Common/CodeGen/StructBuilder.cpp new file mode 100755 index 000000000..b5315c55f --- /dev/null +++ b/Lts/Common/CodeGen/StructBuilder.cpp @@ -0,0 +1,1431 @@ + + + +#include "StructBuilder.h" + +#include + +#include + +using namespace CodeGen; + + + + +class DataTypeInfoIdl : public DataTypeInfo +{ +protected: + DataTypeInfoIdl(const QString &name, const QString &idlType, bool isPod) + : DataTypeInfo(name, isPod) + , m_idlType(idlType) + {} +public: + QString idlType() const override final { return m_idlType; } +private: + const QString m_idlType; +}; + +class DataTypeInfoSimple : public DataTypeInfoIdl +{ +public: + DataTypeInfoSimple(const QString &name, + const QString &idlType, + const QString &ddsType, + const QString &sqlType, + const QString &cppType, + bool isPod) + : DataTypeInfoIdl(name, idlType, isPod) + , m_ddsType(ddsType) + , m_sqlType(sqlType) + , m_cppType(cppType) + {} + QString ddsType() const override final { return m_ddsType; } + QString sqlType() const override final { return m_sqlType; } + QString cppType() const override final { return m_cppType; } +protected: + QString m_ddsType; + QString m_sqlType; + QString m_cppType; +}; + +class DataTypeInfoStructure final : public DataTypeInfoSimple +{ +public: + DataTypeInfoStructure(const QString &name, const QString &idlType, StructBuilder *structBuilder) + : DataTypeInfoSimple(name, idlType, QString(), idlType, idlType, false) + { + const CompleteTopicTypeInformation ctti = structBuilder->topicTypeInformationByStructType(idlType); + Q_ASSERT(ctti.isValid()); + + m_member.reserve(ctti.members.size()); + for (const IdlData &idlData : ctti.members) + m_member += DataTypeInfo::fromIdlDataType(idlData.name, idlData.type, structBuilder); + m_serviceName = ctti.serviceName; + if (ctti.name.startsWith(m_serviceName + QStringLiteral("::"))) + m_ddsType = ctti.name; + else + m_ddsType = m_serviceName + QStringLiteral("::") + ctti.name; + } + bool isStructure() const override final { return true; } + virtual QVector> member() const override final { return m_member; } +private: + QVector> m_member; + QString m_serviceName; +}; + +class DataTypeInfoSequence final : public DataTypeInfoIdl +{ +public: + DataTypeInfoSequence(const QString &name, const QString &idlType, StructBuilder *structBuilder, const QString &svcName) + : DataTypeInfoIdl(name, idlType, false) + { + + const int start = idlType.indexOf(QLatin1Char('<')); + const int end = idlType.lastIndexOf(QLatin1Char('>')); + Q_ASSERT(start >= 0 && end >= 0 && end > start); + const QString inner = idlType.mid(start + 1, end - start - 1); + m_innerDataType = DataTypeInfo::fromIdlDataType(QStringLiteral("inner"), inner, structBuilder, svcName); + Q_ASSERT(m_innerDataType); + } + + QString ddsType() const override final + { + return QLatin1String("std::vector<") + m_innerDataType->ddsType() + QLatin1Char('>'); + } + QString sqlType() const override final + { + return QStringLiteral("jsonb"); + } + QString cppType() const override final + { + return QLatin1String("std::vector<") + m_innerDataType->cppType() + QLatin1Char('>'); + } + QString cppTypeConstRef() const override final + { + return QLatin1String("const std::vector<") + m_innerDataType->cppType() + QLatin1String("> &"); + } + virtual bool isSequence() const override final { return true; } + QSharedPointer innerDataType() const override final { return m_innerDataType; } +private: + QSharedPointer m_innerDataType; +}; + +QSharedPointer DataTypeInfo::fromIdlDataType(const QString &name, + const QString &idlType, + StructBuilder *structBuilder, + const QString &svcName) +{ + const QString fullIdlType = (svcName.isEmpty() || idlType.contains(QLatin1String("::"))) + ? idlType : svcName + QLatin1String("::") + idlType; + + if (idlType == QLatin1String("sequence")) + return QSharedPointer(new DataTypeInfoSimple(name, + idlType, + QStringLiteral("std::vector"), + QStringLiteral("bytea"), + QStringLiteral("QByteArray"), + false)); + if (idlType == QLatin1String("boolean")) + return QSharedPointer(new DataTypeInfoSimple(name, + idlType, + QStringLiteral("bool"), + QStringLiteral("boolean"), + QStringLiteral("bool"), + true)); + if (idlType == QLatin1String("char")) + return QSharedPointer(new DataTypeInfoSimple(name, + idlType, + QStringLiteral("char"), + QStringLiteral("char"), + QStringLiteral("char"), + true)); + if (idlType == QLatin1String("double")) + return QSharedPointer(new DataTypeInfoSimple(name, + idlType, + QStringLiteral("double"), + QStringLiteral("double precision"), + QStringLiteral("double"), + true)); + if (idlType == QLatin1String("long")) + return QSharedPointer(new DataTypeInfoSimple(name, + idlType, + QStringLiteral("int32_t"), + QStringLiteral("integer"), + QStringLiteral("qint32"), + true)); + if (idlType == QLatin1String("long long")) + return QSharedPointer(new DataTypeInfoSimple(name, + idlType, + QStringLiteral("rti::core::int64"), + QStringLiteral("bigint"), + QStringLiteral("qint64"), + true)); + if (idlType == QLatin1String("short")) + return QSharedPointer(new DataTypeInfoSimple(name, + idlType, + QStringLiteral("int16_t"), + QStringLiteral("smallint"), + QStringLiteral("qint16"), + true)); + if (idlType.startsWith(QLatin1String("string"))) + return QSharedPointer(new DataTypeInfoSimple(name, + idlType, + QStringLiteral("std::string"), + QStringLiteral("text"), + QStringLiteral("std::string"), + false)); + if (idlType.startsWith(QLatin1String("sequence"))) + return QSharedPointer(new DataTypeInfoSequence(name, idlType, structBuilder, svcName)); + + + const EnumInformation ei = structBuilder->enumInformationByType(fullIdlType, true, false); + if (ei.isValid()) + return QSharedPointer(new DataTypeInfoSimple(name, idlType, ei.enumName, QStringLiteral("integer"), ei.enumName, true)); + + + const CompleteTopicTypeInformation ctti = structBuilder->topicTypeInformationByStructType(fullIdlType, true); + if (ctti.isValid()) + return QSharedPointer(new DataTypeInfoStructure(name, idlType, structBuilder)); + + + return QSharedPointer(new DataTypeInfoSimple(name, idlType, idlType, QStringLiteral("integer"), idlType, true)); +} + + + + + +StructBuilder::StructBuilder(XmlReader *xmlReader) + : m_needPoint3d(false) + , m_needPgArrayInclude(false) + , m_xmlReader(xmlReader) + , m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("CodeGen.StructBuilder")) + , m_loggerCSV(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("fileCSV")) +{} + +bool StructBuilder::buildStructures() +{ + m_structInfos.clear(); + m_topicTypeInfos.clear(); + m_enumInfos.clear(); + m_sqlTableInformations.clear(); + m_sqlTableInformationsAlreadyAvailable.clear(); + m_sqlViewInformations.clear(); + m_externalTopicTypes.clear(); + m_serializeFromToJsonTopics.clear(); + m_jsonStructureInformation.clear(); + + m_needPoint3d = false; + m_needPgArrayInclude = false; + + + const QMap > tables = m_xmlReader->getTables(); + for (const QSharedPointer &ti : tables) + { + CompleteTableInformation cti(*ti); + if (cti.exerciseDependency == ExerciseDependency::Undefined) + cti.exerciseDependency = m_xmlReader->defaultExerciseDependency(); + + m_sqlTableInformations[ti->name] = cti; + } + + const QMap > views = m_xmlReader->getViews(); + for (const QSharedPointer &vi : views) + { + ViewInformation cti(*vi); + if (cti.exerciseDependency == ExerciseDependency::Undefined) + cti.exerciseDependency = m_xmlReader->defaultExerciseDependency(); + + m_sqlViewInformations[vi->name] = cti; + } + + + const QVector enumData = m_xmlReader->getEnumerations(); + for (const EnumInformation &ei : enumData) + { + if (!handleEnum(ei)) + return false; + } + + const QVector structsToGenerate = m_xmlReader->getStructToGenerate(); + for (const ServiceStructInformation &ssi : structsToGenerate) + { + if (!handleTable(ssi)) + return false; + } + + const QVector idlStructsToGenerate = m_xmlReader->getIdlStructToGenerate(); + for (const TopicTypeInformation &tti : idlStructsToGenerate) + { + DEBUG_LOG(m_loggerCSV) << tti.name; + if (!handleIdlStructInfo(tti, m_topicTypeInfos)) + return false; + } + const QVector idlExternalTopicTypes = m_xmlReader->getExternalTopicTypes(); + for (const TopicTypeInformation &tti : idlExternalTopicTypes) + { + const ServiceTypeStruct sts = extractServiceAndTypeFromTypename(tti.name); + if (!sts.isExternalType()) + { + ERROR_LOG(m_logger) << "Invalid external topic type name: '" << tti.name << "'"; + return false; + } + if (!handleIdlStructInfo(tti, m_externalTopicTypes, sts.service)) + return false; + } + + return true; +} + +bool StructBuilder::handleEnum(const EnumInformation &ei) +{ + m_enumInfos.push_back(ei); + + + if (ei.tableName.isEmpty()) + return true; + + QSharedPointer ti = m_xmlReader->getTable(ei.tableName); + if (ti.isNull()) + { + WARNING_LOG(m_logger) << "Could not find table information for enum: " + << ei.enumName << ", table: " << ei.tableName; + return false; + } + + CompleteTableInformation cti = m_sqlTableInformations[ei.tableName]; + for (const ColumnInformation &ci : qAsConst(ti->plainColumns)) + { + MemberInformation mi; + mi.db = FullQualifiedColumnInformation(ci, ei.tableName); + cti.columns.push_back(std::move(mi)); + } + m_sqlTableInformations[ei.tableName] = cti; + + return true; +} + +CompleteStructInformation StructBuilder::structInformationByTableName(const QString &tableName) const +{ + for (const CompleteStructInformation &csi : m_structInfos) + { + if (csi.primaryTable->name == tableName) + return csi; + } + return CompleteStructInformation(); +} + +EnumInformation StructBuilder::enumInformationByType(const QString &type, bool checkExternals, bool verbose) +{ + if (type.isEmpty()) + return EnumInformation(); + const ServiceTypeStruct extInfo = extractServiceAndTypeFromTypename(type); + if (extInfo.isExternalType()) + { + if (!checkExternals) + { + ERROR_LOG(m_logger) << "Could not find enumeration '" << type << "'"; + return EnumInformation(); + } + if (extInfo.type.isEmpty()) + { + WARNING_LOG(m_logger) << "Could not parse external enumeration '" << type << "'"; + return EnumInformation(); + } + return m_xmlReader->getExternalEnumeration(extInfo.service, extInfo.type, verbose); + } + + for (const EnumInformation &ei : qAsConst(m_enumInfos)) + { + if (ei.enumName == type) + return ei; + } + if (verbose) + ERROR_LOG(m_logger) << "Could not find enumeration '" << type << "'"; + return EnumInformation(); +} + +CompleteTopicTypeInformation StructBuilder::topicTypeInformationByStructType(const QString &type, bool checkExternals) +{ + DEBUG_LOG(m_logger) << "topicTypeInformationByStructType(" << type << ")"; + if (type.isEmpty()) + return CompleteTopicTypeInformation(); + + const ServiceTypeStruct extInfo = extractServiceAndTypeFromTypename(type); + if (extInfo.isExternalType()) + { + if (!checkExternals) + return CompleteTopicTypeInformation(); + + return externalTopicTypeByStructType(extInfo); + } + for (const CompleteTopicTypeInformation &csi : qAsConst(m_topicTypeInfos)) + { + if (csi.name == type) + return csi; + } + + DEBUG_LOG(m_logger) << "Type '" << type << "' not found in idlStructs - maybe not yet exported or an enum"; + const QVector idlStructsToGenerate = m_xmlReader->getIdlStructToGenerate(); + for (const TopicTypeInformation &tti : idlStructsToGenerate) + { + if (tti.name == type) + { + DEBUG_LOG(m_logger) << "Found not yet exported type " << type; + if (!handleIdlStructInfo(tti, m_topicTypeInfos)) + return CompleteTopicTypeInformation(); + return topicTypeInformationByStructType(type); + } + } + ERROR_LOG(m_logger) << "Could not find type '" << type << "' in idlStructs"; + return CompleteTopicTypeInformation(); +} + +CompleteTopicTypeInformation StructBuilder::externalTopicTypeByStructType(const ServiceTypeStruct &sts) +{ + const QString typeToSearch = sts.fqType(); + DEBUG_LOG(m_logger) << "externalTopicTypeByStructType " << typeToSearch; + for (const CompleteTopicTypeInformation &ctti : qAsConst(m_externalTopicTypes)) + { + if (ctti.name == typeToSearch) + return ctti; + } + if (!loadExternalTopicType(sts)) + return CompleteTopicTypeInformation(); + return externalTopicTypeByStructType(sts); +} + +bool StructBuilder::loadExternalTopicType(const ServiceTypeStruct &sts) +{ + DEBUG_LOG(m_logger) << "loadExternalTopicType(" << sts.fqType() << ")"; + + + const TopicTypeInformation tti = m_xmlReader->loadExternalTopicType(sts.service, sts.type); + if (!tti.isValid()) + { + DEBUG_LOG(m_logger) << "Could not find external topic '" << sts.fqType() << "'"; + return false; + } + + if (!handleIdlStructInfo(tti, m_externalTopicTypes, sts.service)) + return false; + return true; +} + + +bool StructBuilder::getColumnsFromTable(const QString &tableName, + QVector &columns, + QVector &tableNames) const +{ + if (tableName.isEmpty()) + return true; + + const QString parentTable = m_xmlReader->getStruct2BaseClassRelations().value(tableName); + if (!getColumnsFromTable(parentTable, columns, tableNames)) + return false; + tableNames += tableName; + const QSharedPointer ti = m_xmlReader->getTable(tableName); + QVector constraints; + constraints.reserve(ti->constraints.size()); + for (const ConstraintInformation &ci : qAsConst(ti->constraints)) + { + if (ci.affectedColumns.size() != 1) + continue; + if (ci.type != ConstraintInformation::Type::index) + continue; + constraints += ci; + } + + columns.reserve(columns.size() + ti->plainColumns.size()); + for (const auto &plainColum : qAsConst(ti->plainColumns)) + { + ColumnInformationEx cie(ColumnInformationEx(plainColum, ti)); + for (int j = 0; j < constraints.size(); ++j) + { + const ConstraintInformation &ci = constraints.at(j); + if (ci.affectedColumns.first() == cie.columnName) + { + cie.generateSelectRemove = true; + constraints.remove(j); + break; + } + } + columns += cie; + } + return true; +} + +bool StructBuilder::handleTable(const ServiceStructInformation &ssi) +{ + DEBUG_LOG(m_loggerCSV) << "StructInfo name: #" << ssi.name; + + CompleteStructInformation csi; + csi.structName = ssi.name; + csi.primaryTable = m_xmlReader->getTable(ssi.primaryTable); + csi.viewName = csi.primaryTable->name + QLatin1String("_view"); + csi.hasStaticContent = ssi.hasStaticContent; + csi.dbNotifications = ssi.dbNotifications; + + DEBUG_LOG(m_loggerCSV) << "primaryTable: " << csi.primaryTable; + DEBUG_LOG(m_loggerCSV) << "hasStaticContent: " << csi.hasStaticContent; + + + QVector columns; + QVector tableNames; + if (!getColumnsFromTable(ssi.primaryTable, columns, tableNames)) + { + WARNING_LOG(m_logger) << "Could not retrieve columns from table " + << ssi.primaryTable; + return false; + } + DEBUG_LOG(m_logger) << "Got " << columns.size() << " columns for table " + << ssi.primaryTable; + + QSet sqlTables; + + + csi.member.reserve(columns.size() + 1); + MemberInformation miPkFirstTable, miPkLastTable; + for (const ColumnInformationEx &ci : qAsConst(columns)) + { + DEBUG_LOG(m_logger) << "handling column " << ci.columnName; + + const QSharedPointer ti = ci.table; + if (ti.isNull()) + { + WARNING_LOG(m_logger) << "Could not find table for column " << ci.columnName + << " primary table is " << ssi.primaryTable; + return false; + } + csi.tables += ti; + sqlTables += ti->name; + + MemberInformation memberInfo; + memberInfo.ea.column = ci.columnName; + memberInfo.ea.table = ti->name; + memberInfo.ea.type = ci.datatype; + + memberInfo.db = FullQualifiedColumnInformation(ci, memberInfo.ea.table); + + if (ci.hasSequence) + { + memberInfo.db.usedForInsert = !ci.isPrimaryKey; + memberInfo.db.hasSequence = true; + } + else + memberInfo.db.usedForInsert = true; + + memberInfo.db.usedForView = !ci.isPrimaryKey; + + memberInfo.cpp.structName = ssi.name; + memberInfo.cpp.memberName = camelCaseString(ci.columnName); + memberInfo.cpp.type = ci.datatype; + memberInfo.cpp.usedForCppStruct = !ci.isPrimaryKey; + + if (ci.isForeignKey) + { + const QSharedPointer fki = ti->fkInformationForColumn(ti->name, ci.columnName); + if (!fki.isNull()) + { + + for(const auto &tbl : qAsConst(csi.tables)) + { + if (tbl->name == fki->destination.tableName) + { + memberInfo.cpp.isReadOnly = true; + memberInfo.fk = fki; + memberInfo.cpp.memberName = QLatin1String("priv_") + memberInfo.cpp.memberName; + break; + } + } + + if (memberInfo.fk.isNull()) + { + + if (fki->destination.service.isEmpty()) + { + + for (const EnumInformation &ei : qAsConst(m_enumInfos)) + { + if (ei.tableName == fki->destination.tableName) + { + memberInfo.enumeration = QSharedPointer(new EnumInformation(ei)); + break; + } + } + } + else + { + + QString enumType = fki->destination.service + QStringLiteral("::") + + camelCaseString(fki->destination.tableName, true); + + EnumInformation ei = enumInformationByType(enumType, true, false); + DEBUG_LOG(m_logger) << "Checking if " << enumType << " is an enum: " << ei.isValid(); + if (!ei.isValid() && fki->destination.tableName.contains(QLatin1Char('_'))) + { + + enumType = fki->destination.service + QStringLiteral("::") + + camelCaseString(fki->destination.tableName.section(QLatin1Char('_'), 1), true); + ei = enumInformationByType(enumType, true, false); + DEBUG_LOG(m_logger) << "Checking if " << enumType << " is an enum: " << ei.isValid(); + } + if (ei.isValid() && + ei.tableName == fki->destination.tableName) + memberInfo.enumeration = QSharedPointer(new EnumInformation(ei)); + } + } + } + } + MemberInformation memberInfoJson = memberInfo; + memberInfoJson.cpp.usedForJson = true; + memberInfoJson.cpp.usedForCppStruct = false; + memberInfoJson.db.isForeignKey = false; + memberInfoJson.db.generateSelectRemove = false; + memberInfoJson.db.usedForView = false; + memberInfoJson.db.usedForInsert = false; + + if (ci.isOptional) + { + memberInfo.cpp.isOptional = true; + memberInfoJson.cpp.isOptional = true; + memberInfo.ea.type = QStringLiteral("QVariant"); + memberInfo.cpp.type = memberInfo.ea.type; + } + + if (!fixupCustomDbDatatypes(memberInfo, csi.member) || + !fixupQVariantConversions(memberInfo)) + return false; + + if (!fixupCustomDbDatatypes(memberInfoJson, csi.member) || + !fixupQVariantConversions(memberInfoJson)) + return false; + + if (ci.isPrimaryKey && tableNames.first() == ti->name) + { + + miPkFirstTable = memberInfo; + } + if (ci.isPrimaryKey && tableNames.last() == ti->name) + { + + miPkLastTable = memberInfo; + } + + csi.member.push_back(memberInfo); + csi.member.push_back(memberInfoJson); + + addToSqlTableInformation(memberInfo); + + DEBUG_LOG(m_loggerCSV) << memberInfo.toString(); + } + + if (!miPkFirstTable.isEmpty()) + { + miPkFirstTable.db.usedForInsert = false; + miPkFirstTable.db.usedForView = true; + miPkFirstTable.cpp.usedForCppStruct = true; + csi.member.push_front(miPkFirstTable); + } + + if (tableNames.size() > 1 && !miPkLastTable.isEmpty()) + { + + + miPkLastTable.db.usedForInsert = false; + miPkLastTable.db.usedForView = true; + miPkLastTable.cpp.usedForCppStruct = true; + miPkLastTable.db.isAssignmentForPrimaryTablePk = true; + miPkLastTable.db.columnNameView = + miPkLastTable.db.table + QLatin1Char('_') + miPkLastTable.db.columnName; + + QString name = miPkLastTable.db.table; + if (name.startsWith(m_xmlReader->getServiceAbbr().toLower())) + name = name.mid(m_xmlReader->getServiceAbbr().size() + 1); + miPkLastTable.cpp.memberName = camelCaseString(QLatin1String("priv_") + name + QLatin1String("_id")); + csi.member.push_back(miPkLastTable); + } + + + m_structInfos.push_back(csi); + + m_sqlTableInformationsAlreadyAvailable += sqlTables; + return true; +} + +bool StructBuilder::handleIdlStructInfo(const TopicTypeInformation &tti, + QVector &destination, + const QString &svcName) +{ + + for (const CompleteTopicTypeInformation &c : qAsConst(destination)) + { + if (c.name == tti.name) + return true; + } + + CompleteTopicTypeInformation ctti(tti); + ctti.members.reserve(tti.columns.size()); + + + for (const auto &ci : tti.columns) + { + IdlData idlData(ci); + idlData.dataTypeInfo = DataTypeInfo::fromIdlDataType(idlData.name, idlData.type, this, svcName); + ctti.members.push_back(idlData); + + + if (idlData.isKey && idlData.isOptional) + { + ERROR_LOG(m_logger) << "Column option isKey & isOptional are mutually exclusive!"; + ERROR_LOG(m_logger) << "Change column '" << tti.name << "." + << idlData.name << "'' to have either key or optional parameter "; + return false; + } + } + + + if (!svcName.isEmpty() && + !ctti.baseValueType.isEmpty() && + !ctti.baseValueType.contains(QLatin1String("::"))) + ctti.baseValueType = svcName + QStringLiteral("::") + ctti.baseValueType; + + ctti.serviceName = svcName.isEmpty() ? m_xmlReader->getServiceNamespace() : svcName; + + destination.push_back(ctti); + + return true; +} + +bool StructBuilder::fixupCustomDbDatatypes(MemberInformation &memberInfo, QVector &members) +{ + if (!fixupDatatypePoint2d(memberInfo, members)) + return false; + if (!fixupDatatypePoint3d(memberInfo, members)) + return false; + if (!fixupDatatypeIntegerArray(memberInfo)) + return false; + return true; +} + +bool StructBuilder::fixupDatatypePoint2d(MemberInformation &memberInfo, + QVector &members) +{ + if (memberInfo.ea.type.toLower() != QLatin1String("point_2d")) + return true; + + MemberInformation mi = memberInfo; + mi.db.datatype = QStringLiteral("double precision"); + mi.cpp.type = QStringLiteral("QPointF"); + mi.cpp.fromVariantPost = QStringLiteral(".toDouble()"); + + + MemberInformation miX = mi; + MemberInformation miY = mi; + + miX.db.columnName = memberInfo.db.columnName + QLatin1String("_x"); + miY.db.columnName = memberInfo.db.columnName + QLatin1String("_y"); + + miY.cpp.usedForCppStruct = false; + miY.cpp.usedForJson = false; + + miX.cpp.toVariantPost = QStringLiteral(".x()"); + miY.cpp.toVariantPost = QStringLiteral(".y()"); + + miX.cpp.fromVariantPre1 = QStringLiteral(".rx()"); + miY.cpp.fromVariantPre1 = QStringLiteral(".ry()"); + + miX.cpp.memberName = camelCaseString(miX.cpp.memberName); + + + miX.cpp.toJsonPre = QStringLiteral("toJson("); + miX.cpp.toJsonPost = QStringLiteral(")"); + miX.cpp.fromJsonPre2 = QStringLiteral("fromJson("); + miX.cpp.fromJsonPost = QStringLiteral(")"); + + members.push_back(miX); + + if (memberInfo.cpp.usedForCppStruct) + addToSqlTableInformation(miX); + + DEBUG_LOG(m_loggerCSV) << miX.toString(); + + memberInfo = miY; + + return true; +} + +bool StructBuilder::fixupDatatypePoint3d(MemberInformation &memberInfo, + QVector &members) +{ + if (memberInfo.ea.type.toLower() != QLatin1String("point_3d")) + return true; + + MemberInformation mi = memberInfo; + mi.db.datatype = QStringLiteral("double precision"); + mi.cpp.type = QStringLiteral("LtsUtils::QPoint3D"); + mi.cpp.fromVariantPost = QStringLiteral(".toDouble()"); + + + MemberInformation miX = mi; + MemberInformation miY = mi; + MemberInformation miZ = mi; + + miX.db.columnName = memberInfo.db.columnName + QLatin1String("_x"); + miY.db.columnName = memberInfo.db.columnName + QLatin1String("_y"); + miZ.db.columnName = memberInfo.db.columnName + QLatin1String("_z"); + + miY.cpp.usedForCppStruct = false; + miZ.cpp.usedForCppStruct = false; + miY.cpp.usedForJson = false; + miZ.cpp.usedForJson = false; + + miX.cpp.toVariantPost = QStringLiteral(".x()"); + miY.cpp.toVariantPost = QStringLiteral(".y()"); + miZ.cpp.toVariantPost = QStringLiteral(".z()"); + + miX.cpp.fromVariantPre1 = QStringLiteral(".rx()"); + miY.cpp.fromVariantPre1 = QStringLiteral(".ry()"); + miZ.cpp.fromVariantPre1 = QStringLiteral(".rz()"); + + miX.cpp.memberName = camelCaseString(miX.cpp.memberName); + + + miX.cpp.toJsonPre = QStringLiteral("toJson("); + miX.cpp.toJsonPost = QStringLiteral(")"); + miX.cpp.fromJsonPre2 = QStringLiteral("fromJson("); + miX.cpp.fromJsonPost = QStringLiteral(")"); + + members.push_back(miX); + members.push_back(miY); + + if (memberInfo.cpp.usedForCppStruct) + { + addToSqlTableInformation(miX); + addToSqlTableInformation(miY); + } + DEBUG_LOG(m_loggerCSV) << miX.toString(); + + memberInfo = miZ; + + m_needPoint3d = true; + + return true; +} + +bool StructBuilder::fixupDatatypeIntegerArray(MemberInformation &memberInfo) +{ + + if (memberInfo.ea.type.toLower() == QLatin1String("integer[]")) + { + memberInfo.db.datatype = memberInfo.ea.type.toLower(); + memberInfo.cpp.type = QStringLiteral("LtsUtils::IntArray"); + memberInfo.cpp.fromVariantPre2 = QStringLiteral("LtsUtils::IntArray::fromPgString("); + memberInfo.cpp.fromVariantPost = QStringLiteral(".toString())"); + memberInfo.cpp.toVariantPost = QStringLiteral(".toPgString()"); + + m_needPgArrayInclude = true; + } + else if (memberInfo.ea.type.toLower() == QLatin1String("bigint[]")) + { + memberInfo.db.datatype = memberInfo.ea.type.toLower(); + memberInfo.cpp.type = QStringLiteral("LtsUtils::BigintArray"); + memberInfo.cpp.fromVariantPre2 = QStringLiteral("LtsUtils::BigintArray::fromPgString("); + memberInfo.cpp.fromVariantPost = QStringLiteral(".toString())"); + memberInfo.cpp.toVariantPost = QStringLiteral(".toPgString()"); + + m_needPgArrayInclude = true; + } + else if (memberInfo.cpp.type.toLower() == QLatin1String("sequence")) + { + memberInfo.db.datatype = QStringLiteral("integer[]"); + memberInfo.cpp.type = QStringLiteral("std::vector"); + memberInfo.cpp.fromVariantPre2 = QStringLiteral("std::vector(LtsUtils::IntArray::fromPgString("); + memberInfo.cpp.fromVariantPost = QStringLiteral(".toString())())"); + memberInfo.cpp.toVariantPre = QStringLiteral("LtsUtils::IntArray("); + memberInfo.cpp.toVariantPost = QStringLiteral(").toPgString()"); + + m_needPgArrayInclude = true; + } + else if (memberInfo.ea.type.toLower() == QLatin1String("integer[][]")) + { + memberInfo.db.datatype = memberInfo.ea.type.toLower(); + memberInfo.cpp.type = QStringLiteral("LtsUtils::IntArray2D"); + memberInfo.cpp.fromVariantPre2 = QStringLiteral("LtsUtils::IntArray2D::fromPgString("); + memberInfo.cpp.fromVariantPost = QStringLiteral(".toString())"); + memberInfo.cpp.toVariantPost = QStringLiteral(".toPgString()"); + + m_needPgArrayInclude = true; + } + else if (memberInfo.ea.type.toLower() == QLatin1String("bigint[][]")) + { + memberInfo.db.datatype = memberInfo.ea.type.toLower(); + memberInfo.cpp.type = QStringLiteral("LtsUtils::BigintArray2D"); + memberInfo.cpp.fromVariantPre2 = QStringLiteral("LtsUtils::BigintArray2D::fromPgString("); + memberInfo.cpp.fromVariantPost = QStringLiteral(".toString())"); + memberInfo.cpp.toVariantPost = QStringLiteral(".toPgString()"); + + m_needPgArrayInclude = true; + } + + return true; +} + + +bool StructBuilder::fixupQVariantConversions(MemberInformation &mi) const +{ + + if (!mi.cpp.toVariantPost.isEmpty()) + return true; + mi.cpp.toVariantPre.clear(); + mi.cpp.toVariantPost.clear(); + mi.cpp.fromVariantPre1.clear(); + mi.cpp.fromVariantPre2.clear(); + mi.cpp.fromVariantPost.clear(); + + if (mi.enumeration) + { + QString svcName(mi.enumeration->serviceName); + if (!svcName.isEmpty()) + svcName += QStringLiteral("::"); + mi.cpp.type = svcName + mi.enumeration->enumName; + mi.cpp.fromVariantPre2 = QStringLiteral("static_cast<") + + svcName + mi.enumeration->enumName + + QStringLiteral("_def::type>("); + mi.cpp.fromVariantPost = QStringLiteral(".toInt())"); + mi.cpp.toVariantPre = QStringLiteral("static_cast("); + mi.cpp.toVariantPost = QStringLiteral(".underlying())"); + if (!mi.enumeration->values.isEmpty()) + { + mi.cpp.defaultValue = svcName + mi.enumeration->enumName + QStringLiteral("::") + + mi.enumeration->values.first().key; + } + return true; + } + const QString datatype = mi.cpp.type.toLower(); + if (datatype == QLatin1String("bigint") || + datatype == QLatin1String("qint64") || + datatype == QLatin1String("long long")) + { + mi.cpp.type = QStringLiteral("qint64"); + mi.cpp.fromVariantPost = QStringLiteral(".toLongLong()"); + mi.cpp.fromJsonPost = QStringLiteral(".toDouble()"); + mi.cpp.defaultValue = QLatin1Char('0'); + } + else if (datatype == QLatin1String("boolean") || + datatype == QLatin1String("bool")) + { + mi.cpp.type = QStringLiteral("bool"); + mi.cpp.fromVariantPost = QStringLiteral(".toBool()"); + mi.cpp.defaultValue = QStringLiteral("false"); + } + else if (datatype == QLatin1String("bytea") || + datatype == QLatin1String("QByteArray")) + { + + mi.cpp.type = QStringLiteral("QByteArray"); + mi.cpp.fromVariantPost = QStringLiteral(".toByteArray()"); + mi.cpp.toJsonPre = QStringLiteral("QString::fromLatin1("); + mi.cpp.toJsonPost = QStringLiteral(".toBase64())"); + mi.cpp.fromJsonPre2 = QStringLiteral("QByteArray::fromBase64("); + mi.cpp.fromJsonPost = QStringLiteral(".toString().toLatin1())"); + mi.cpp.defaultValue.clear(); + } + else if (datatype == QLatin1String("sequence")) + { + + mi.cpp.type = QStringLiteral("QByteArray"); + mi.cpp.ddsType = QStringLiteral("std::vector"); + + mi.cpp.toVariantPre = QStringLiteral("octetSequenceToQVariant("); + mi.cpp.toVariantPost = QStringLiteral(")"); + + mi.cpp.fromVariantPre2 = QStringLiteral("qVariantToOctetSequence("); + mi.cpp.fromVariantPost = QStringLiteral(")"); + + mi.cpp.toJsonPre = QStringLiteral("toJson("); + mi.cpp.toJsonPost = QStringLiteral(")"); + mi.cpp.fromJsonPre2 = QStringLiteral("fromJson>("); + mi.cpp.fromJsonPost = QStringLiteral(")"); + + mi.cpp.defaultValue.clear(); + } + else if (datatype == QLatin1String("char")) + { + mi.cpp.type = QStringLiteral("char"); + mi.cpp.fromVariantPost = QStringLiteral(".toChar().toLatin1()"); + mi.cpp.fromJsonPost = QStringLiteral(".toInt()"); + mi.cpp.defaultValue = QStringLiteral("\\0"); + } + else if (datatype == QLatin1String("integer") || + datatype == QLatin1String("qint32") || + datatype == QLatin1String("long")) + { + mi.cpp.type = QStringLiteral("qint32"); + mi.cpp.fromVariantPost = QStringLiteral(".toInt()"); + mi.cpp.defaultValue = QLatin1Char('0'); + } + else if (datatype == QLatin1String("real") || + datatype == QLatin1String("double") || + datatype == QLatin1String("double precision") || + datatype == QLatin1String("float")) + { + mi.cpp.type = QStringLiteral("double"); + mi.cpp.fromVariantPost = QStringLiteral(".toDouble()"); + mi.cpp.defaultValue = QStringLiteral("0.0"); + mi.db.datatype = QStringLiteral("double precision"); + } + else if (datatype == QLatin1String("numeric") || + datatype == QLatin1String("decimal")) + { + mi.cpp.type = QStringLiteral("double"); + mi.cpp.fromVariantPost = QStringLiteral(".toDouble()"); + mi.cpp.defaultValue = QStringLiteral("0.0"); + mi.db.datatype = QStringLiteral("numeric"); + } + else if (datatype == QLatin1String("smallint") || + datatype == QLatin1String("qint16") || + datatype == QLatin1String("short")) + { + mi.cpp.type = QStringLiteral("qint16"); + mi.cpp.fromVariantPost = QStringLiteral(".toInt()"); + mi.cpp.defaultValue = QLatin1Char('0'); + } + else if (datatype == QLatin1String("text") || + datatype == QLatin1String("QString")) + { + + mi.cpp.type = QStringLiteral("QString"); + mi.cpp.fromVariantPost = QStringLiteral(".toString()"); + mi.cpp.defaultValue = QString(); + } + else if (datatype == QLatin1String("json") || + datatype == QLatin1String("jsonb")) + { + + mi.cpp.type = QStringLiteral("QString"); + mi.cpp.fromVariantPre2 = QStringLiteral("qVariantToJsonString("); + mi.cpp.fromVariantPost = QStringLiteral(")"); + mi.cpp.fromJsonPre2 = QStringLiteral("jsonValueToJsonString("); + mi.cpp.fromJsonPost = QStringLiteral(")"); + mi.cpp.toVariantPre = QStringLiteral("fixupEmptyJsonString("); + mi.cpp.toVariantPost = QStringLiteral(")"); + mi.cpp.defaultValue = QStringLiteral("QLatin1String(\"{}\")"); + } + else if (datatype.startsWith(QLatin1String("string"))) + { + + mi.cpp.type = QStringLiteral("std::string"); + mi.cpp.toVariantPre = QStringLiteral("QString::fromStdString("); + mi.cpp.toVariantPost = QStringLiteral(")"); + + mi.cpp.fromVariantPost = QStringLiteral(".toString().toStdString()"); + + mi.cpp.fromJsonPre2 = QStringLiteral("fromJson("); + mi.cpp.fromJsonPost = QStringLiteral(")"); + } + else if (datatype == QLatin1String("timestamp") || + datatype == QLatin1String("qdatetime")) + { + mi.db.datatype = QStringLiteral("bigint"); + if (mi.db.defaultValue.toLower() == QStringLiteral("now()")) + mi.db.defaultValue = QStringLiteral("(extract(epoch from now()) * 1000)"); + mi.cpp.type = QStringLiteral("QDateTime"); + mi.cpp.fromVariantPre2 = QStringLiteral("NSqlQuery::qVariantToQDateTime("); + mi.cpp.fromVariantPost = QStringLiteral(")"); + mi.cpp.toVariantPre = QStringLiteral("NSqlQuery::qDateTimeToQVariant("); + mi.cpp.toVariantPost = QStringLiteral(")"); + mi.cpp.toJsonPre = QLatin1String(""); + mi.cpp.toJsonPost = QStringLiteral(".toMSecsSinceEpoch()"); + mi.cpp.fromJsonPre2 = QStringLiteral("QDateTime::fromMSecsSinceEpoch("); + mi.cpp.fromJsonPost = QStringLiteral(".toDouble())"); + } + else if (datatype == QLatin1String("qvariant")) + { + + } + else + { + ERROR_LOG(m_logger) << "Unknown cpp datatype: " << mi.cpp.type; + return false; + } + return true; +} + + + + + + +QVector StructBuilder::buildDbTableInformationFromIdlData(const CompleteTopicTypeInformation &ctti, + const QString &tableName, + const QString &columnNamePrefix, + const QString &cppStructure) +{ + QVector ret; + if (!ctti.isValid()) + return ret; + + if (!ctti.baseValueType.isEmpty()) + { + ret += buildDbTableInformationFromIdlData(topicTypeInformationByStructType(ctti.baseValueType), + tableName, + columnNamePrefix, + cppStructure); + } + + for (const IdlData &member : ctti.members) + { + if (member.dataTypeInfo->isStructure() && !member.isOptional) + { + const CompleteTopicTypeInformation cttiChild = topicTypeInformationByStructType(member.type); + if (!cttiChild.isValid()) + { + ERROR_LOG(m_logger) << "Can not find structure '" << member.type << "'"; + continue; + } + QString pre; + if (!columnNamePrefix.isEmpty()) + pre += columnNamePrefix; + pre += member.name + QLatin1Char('_'); + ret += buildDbTableInformationFromIdlData(cttiChild, + tableName, + StructBuilder::camelCaseToUnderline(pre), + cppStructure + member.name + QStringLiteral("().") + ); + continue; + } + + if(member.dataTypeInfo->isSequence() && member.dataTypeInfo->innerDataType()->isStructure()) + { + const CompleteTopicTypeInformation &cttiChild = topicTypeInformationByStructType(member.dataTypeInfo->innerDataType()->idlType()); + if (!cttiChild.isValid()) + { + ERROR_LOG(m_logger) << "Can not find structure '" << member.type << "'"; + continue; + } + buildDbTableInformationFromIdlData(cttiChild, + tableName, + columnNamePrefix, + member.name + ); + + } + + MemberInformation mi; + mi.isDdsKey = member.isKey; + mi.db.table = tableName; + mi.db.columnName = columnNamePrefix + StructBuilder::camelCaseToUnderline(member.name); + mi.db.datatype = member.dataTypeInfo->sqlType(); + mi.db.usedForView = true; + mi.cpp.memberName = cppStructure + member.name; + mi.cpp.type = member.type; + mi.cpp.usedForCppStruct = true; + mi.cpp.isOptional = member.isOptional; + + + const EnumInformation ei = enumInformationByType(member.type, true, false); + if (ei.isValid()) + mi.enumeration = QSharedPointer(new EnumInformation(ei)); + + if ((member.dataTypeInfo->isSequence() && member.dataTypeInfo->innerDataType()->idlType() != QLatin1String("octet")) || + (member.dataTypeInfo->isStructure() && member.isOptional)) + { + + + mi.db.datatype = QStringLiteral("jsonb"); + mi.cpp.toJsonPre = QStringLiteral("toJson("); + mi.cpp.toJsonPost = QStringLiteral(")"); + mi.cpp.type = member.dataTypeInfo->ddsType(); + + + mi.cpp.fromVariantPre2 = QStringLiteral("fromJson<") + member.dataTypeInfo->ddsType() + QStringLiteral(">("); + mi.cpp.fromVariantPost = QStringLiteral(")"); + + if (member.dataTypeInfo->isSequence()) + addToSerializeFromToJsonTopics(member.dataTypeInfo->innerDataType()); + else + addToSerializeFromToJsonTopics(DataTypeInfo::fromIdlDataType(mi.cpp.memberName, member.type, this)); + } + else + { + fixupCustomDbDatatypes(mi, ret); + fixupQVariantConversions(mi); + } + ret += mi; + } + return ret; +} + + + + + + +bool StructBuilder::buildJSonStructureInformationFromIdlData(const CompleteTopicTypeInformation &ctti) +{ + if (!ctti.isValid()) + return false; + if (m_jsonStructureInformation.contains(ctti.name)) + return true; + + if (!ctti.baseValueType.isEmpty()) + { + if (!buildJSonStructureInformationFromIdlData(topicTypeInformationByStructType(ctti.baseValueType))) + return false; + } + + QVector content; + content.reserve(ctti.members.size()); + for (const IdlData &member : ctti.members) + { + MemberInformation mi; + mi.isDdsKey = member.isKey; + mi.db.table = ctti.name; + mi.db.columnName = StructBuilder::camelCaseToUnderline(member.name); + mi.db.datatype = member.dataTypeInfo->sqlType(); + mi.db.usedForView = true; + mi.cpp.memberName = member.name; + mi.cpp.type = member.type; + mi.cpp.usedForCppStruct = true; + mi.cpp.isOptional = member.isOptional; + + if (member.dataTypeInfo->isStructure()) + { + const CompleteTopicTypeInformation cttiChild = topicTypeInformationByStructType(member.type); + if (!cttiChild.isValid()) + { + ERROR_LOG(m_logger) << "Can not find structure '" << member.type << "'"; + continue; + } + if (!buildJSonStructureInformationFromIdlData(cttiChild)) + return false; + + mi.cpp.toJsonPre = QStringLiteral("toJson("); + mi.cpp.toJsonPost = QStringLiteral(")"); + mi.cpp.type = member.dataTypeInfo->ddsType(); + + + mi.cpp.fromVariantPre2 = QStringLiteral("fromJson<") + member.dataTypeInfo->ddsType() + QStringLiteral(">("); + mi.cpp.fromVariantPost = QStringLiteral(")"); + } + else if (member.dataTypeInfo->isSequence() && + member.dataTypeInfo->innerDataType()->idlType() != QLatin1String("octet")) + { + auto inner = member.dataTypeInfo->innerDataType(); + if (inner->isStructure()) + { + const CompleteTopicTypeInformation cttiChild = topicTypeInformationByStructType(inner->idlType()); + if (cttiChild.isValid()) + { + if (!buildJSonStructureInformationFromIdlData(cttiChild)) + return false; + } + else + WARNING_LOG(m_logger) << "Can not find structure '" << member.type << "'"; + } + + + + mi.db.datatype = QStringLiteral("jsonb"); + mi.cpp.toJsonPre = QStringLiteral("toJson("); + mi.cpp.toJsonPost = QStringLiteral(")"); + mi.cpp.type = member.dataTypeInfo->ddsType(); + + + mi.cpp.fromVariantPre2 = QStringLiteral("fromJson<") + member.dataTypeInfo->ddsType() + QStringLiteral(">("); + mi.cpp.fromVariantPost = QStringLiteral(")"); + } + else + { + + const EnumInformation ei = enumInformationByType(member.type, true, false); + if (ei.isValid()) + mi.enumeration = QSharedPointer(new EnumInformation(ei)); + + fixupCustomDbDatatypes(mi, content); + fixupQVariantConversions(mi); + } + content += mi; + } + m_jsonStructureInformation[ctti.name] = content; + + return true; +} + +CompleteTableInformation StructBuilder::completeTableInformation(const CompleteTopicTypeInformation &ctti) +{ + CompleteTableInformation cti; + cti.structName = ctti.name; + cti.name = QStringLiteral("rec_") + camelCaseToUnderline(ctti.name); + cti.comment = QStringLiteral("recorded data for topic ") + ctti.name; + cti.columns = buildDbTableInformationFromIdlData(ctti, cti.name, QString(), QString()); + buildJSonStructureInformationFromIdlData(ctti); + cti.exerciseDependency = ExerciseDependency::Exercise; + return cti; +} + +MemberInformation StructBuilder::getRecordPrimaryKey() const +{ + MemberInformation miPrimaryKey; + miPrimaryKey.db.isPrimaryKey = true; + miPrimaryKey.db.columnName = QStringLiteral("rec_id"); + miPrimaryKey.db.datatype = QStringLiteral("bigint"); + miPrimaryKey.db.hasSequence = true; + miPrimaryKey.db.sequenceStart = 1; + miPrimaryKey.db.sequenceIncrement = 1; + miPrimaryKey.db.usedForView = false; + miPrimaryKey.cpp.type = QStringLiteral("bigint"); + miPrimaryKey.cpp.usedForCppStruct = false; + fixupQVariantConversions(miPrimaryKey); + return miPrimaryKey; +} + +CompleteTableInformation StructBuilder::getRecordingTopicTable(const QString &structName) +{ + const auto vec = structName.contains(QLatin1String("::")) ? m_externalTopicTypes : m_topicTypeInfos; + + for (const CompleteTopicTypeInformation &ctti : qAsConst(vec)) + { + if (structName != ctti.name) + continue; + MemberInformation miPrimaryKey(getRecordPrimaryKey()); + miPrimaryKey.db.table = ctti.name; + return getRecordingTopicTable(ctti, getRecordPrimaryKey()); + } + return CompleteTableInformation(); +} + +CompleteTableInformation StructBuilder::getRecordingTopicTable(const CompleteTopicTypeInformation &ctti, + const MemberInformation &miPrimaryKey) +{ + CompleteTableInformation cti = completeTableInformation(ctti); + cti.columns.push_back(miPrimaryKey); + return cti; +} + +QMap StructBuilder::getRecordingTopicTables() +{ + + QMap ret; + + MemberInformation miPrimaryKey(getRecordPrimaryKey()); + + for (const CompleteTopicTypeInformation &ctti : qAsConst(m_topicTypeInfos)) + { + if (!ctti.recordTopic) + continue; + if (!ctti.isDataObject) + { + WARNING_LOG(m_logger) << "Trying to record topic '" << ctti.name + << "' which is not directly published - ignoring!"; + continue; + } + DEBUG_LOG(m_logger) << "Rec table: " << ctti.name; + miPrimaryKey.db.table = ctti.name; + ret.insert(ctti.name, getRecordingTopicTable(ctti, miPrimaryKey)); + } + return ret; +} + +void StructBuilder::addToSerializeFromToJsonTopics(const QSharedPointer &toAdd) +{ + typedef QSharedPointer sDTI; + for (const sDTI &dti : qAsConst(m_serializeFromToJsonTopics)) + { + if (dti->cppType() == toAdd->cppType()) + return; + } + m_serializeFromToJsonTopics += toAdd; +} + +QVector> StructBuilder::getSerializeFromToJsonTopics() const +{ + return m_serializeFromToJsonTopics; +} + +QString StructBuilder::firstCharLowercase(const QString &txt) +{ + QString ret = txt; + if (txt.isEmpty()) + return ret; + ret[0] = ret[0].toLower(); + return ret; +} + +QString StructBuilder::camelCaseString(const QString &in, bool bFirstUppercase) +{ + Q_DECL_CONSTEXPR const QChar s_underline(QLatin1Char('_')); + QString out; + out.reserve(in.size()); + bool bNextIsUpperCase = bFirstUppercase; + for (const auto c : in) + { + if (c == s_underline) + bNextIsUpperCase = true; + else + { + out += (bNextIsUpperCase ? c.toUpper() : c); + bNextIsUpperCase = false; + } + } + return out; +} + +QString StructBuilder::camelCaseToUnderline(const QString &in) +{ + QString out; + if (in.isEmpty()) + return out; + out.reserve(in.size()); + out += in.at(0).toLower(); + for (int i = 1; i < in.size(); ++i) + { + const QChar c = in.at(i); + if (!c.isLetter() || c.isLower()) + out += c; + else + out += QLatin1Char('_') + c.toLower(); + } + return out; +} + +StructBuilder::ServiceTypeStruct StructBuilder::extractServiceAndTypeFromTypename(const QString &type) +{ + const QVector sl = type.splitRef(QStringLiteral("::")); + QStringRef extServiceRef;; + QStringRef extTypeRef; + if (sl.size() == 1) + extTypeRef = sl.at(0); + else if (sl.size() == 2) + { + extServiceRef = sl.at(0); + extTypeRef = sl.at(1); + } + else + WARNING_LOG(m_logger) << "Error parsing type: " << type; + StructBuilder::ServiceTypeStruct ret; + if (!extServiceRef.isEmpty()) + { + + if (extServiceRef.contains(QLatin1Char('<'))) + ret.service = extServiceRef.split(QLatin1Char('<')).at(1).toString(); + else + ret.service = extServiceRef.toString(); + } + if (!extTypeRef.isEmpty()) + { + + if (extTypeRef.contains(QLatin1Char('>'))) + ret.type = extTypeRef.split(QLatin1Char('>')).at(0).toString(); + else + ret.type = extTypeRef.toString(); + } + return ret; +} + +void StructBuilder::addToSqlTableInformation(const MemberInformation &mi) +{ + if (!m_sqlTableInformationsAlreadyAvailable.contains(mi.db.table)) + m_sqlTableInformations[mi.db.table].columns.append(mi); +} diff --git a/Lts/Common/CodeGen/StructBuilder.h b/Lts/Common/CodeGen/StructBuilder.h new file mode 100755 index 000000000..595da91a9 --- /dev/null +++ b/Lts/Common/CodeGen/StructBuilder.h @@ -0,0 +1,341 @@ + + +#pragma once + +#include +#include "XmlReader.h" +#include "codegen_export.h" + +namespace NGL +{ +class INGLLogger; +} + +namespace CodeGen +{ +class StructBuilder; + +struct EaData +{ + QString table; + QString column; + QString type; + + QString toString() const + { + return table + + QLatin1Char(';') + column + + QLatin1Char(';') + type; + } +}; + +struct FullQualifiedColumnInformation : public ColumnInformation +{ + QString table; + bool usedForInsert = false; + bool usedForView = false; + QString columnNameView; + + QString getColumnNameView() const { return columnNameView.isEmpty() ? columnName : columnNameView; } + + FullQualifiedColumnInformation() = default; + FullQualifiedColumnInformation(const ColumnInformation &ci, const QString &tableName) + : ColumnInformation(ci) + , table(tableName) + {} + + QString toString() const + { + return table + + QLatin1Char(';') + columnName + + QLatin1Char(';') + datatype + + + QLatin1Char(';') + (isPrimaryKey ? QLatin1String("true") : QLatin1String("false")) + + QLatin1Char(';') + (isForeignKey ? QLatin1String("true") : QLatin1String("false")) + + QLatin1Char(';') + (usedForInsert ? QLatin1String("true") : QLatin1String("false")) + + QLatin1Char(';') + (usedForView ? QLatin1String("true") : QLatin1String("false")); + } +}; + +class DataTypeInfo +{ +public: + DataTypeInfo(const QString &name, bool isPod) + : m_name(name) + , m_isPod(isPod) + {} + virtual ~DataTypeInfo() = default; + virtual QString name() const { return m_name; } + virtual QString idlType() const = 0; + virtual QString ddsType() const = 0; + virtual QString sqlType() const = 0; + virtual QString cppType() const = 0; + virtual QString cppTypeConstRef() const { return isPod() ? cppType() : QLatin1String("const ") + cppType() + QLatin1String(" &"); } + virtual bool isPod() const { return m_isPod; } + virtual bool isSequence() const { return false; } + virtual bool isStructure() const { return false; } + virtual QVector> member() const { return QVector>(); } + virtual QSharedPointer innerDataType() const { return QSharedPointer(); } + + static QSharedPointer fromIdlDataType(const QString &name, + const QString &idlType, + StructBuilder *structBuilder, + const QString &svcName = QString()); +protected: + const QString m_name; + const bool m_isPod; +}; + +struct IdlData : public IdlColumnInformation +{ + QSharedPointer dataTypeInfo; + + IdlData() = default; + explicit IdlData(const IdlColumnInformation &ci) + : IdlColumnInformation(ci) + {} + + QString toString() const + { + return name + + QLatin1Char(';') + type + + + QLatin1Char(';') + (isKey ? QLatin1String("true") : QLatin1String("false")) + + QLatin1Char(';') + (isOptional ? QLatin1String("true") : QLatin1String("false")); + } +}; + + + +struct CppData +{ + QString structName; + QString memberName; + QString defaultValue; + bool usedForCppStruct = false; + bool usedForJson = false; + bool isReadOnly = false; + bool isOptional = false; + QString type; + QString ddsType; + QString toVariantPre; + QString toVariantPost; + QString fromVariantPre1; + QString fromVariantPre2; + QString fromVariantPost; + + QString toJsonPre; + QString toJsonPost; + QString fromJsonPre2; + QString fromJsonPost; + + QString toString() const + { + return structName + + QLatin1Char(';') + memberName + + QLatin1Char(';') + (usedForCppStruct ? QLatin1String("true") : QLatin1String("false")) + + QLatin1Char(';') + (isReadOnly ? QLatin1String("true") : QLatin1String("false")) + + QLatin1Char(';') + type + + QLatin1Char(';') + toVariantPre + + QLatin1Char(';') + toVariantPost + + QLatin1Char(';') + fromVariantPre1 + + QLatin1Char(';') + fromVariantPre2 + + QLatin1Char(';') + fromVariantPost; + } +}; + +struct MemberInformation +{ + EaData ea; + FullQualifiedColumnInformation db; + CppData cpp; + bool isDdsKey = false; + QSharedPointer fk; + QSharedPointer enumeration; + + QString toString() const + { + return ea.toString() + QLatin1Char(';') + + db.toString() + QLatin1Char(';') + + cpp.toString(); + } + bool isEmpty() const + { + return ea.column.isEmpty(); + } +}; + +struct CompleteStructInformation +{ + QString structName; + QString viewName; + QSet > tables; + QSharedPointer primaryTable; + QVector member; + QVector dbNotifications; + bool hasStaticContent = false; + + bool isEmpty() const + { + return primaryTable.isNull(); + } +}; + +struct CompleteTopicTypeInformation final : public TopicTypeInformationBase +{ + QString serviceName; + + QVector members; + + CompleteTopicTypeInformation() = default; + explicit CompleteTopicTypeInformation(const TopicTypeInformationBase &s) + : TopicTypeInformationBase(s) + {} + + bool hasBaseValue() const { return !baseValueType.isEmpty(); } + bool isValid() const { return TopicTypeInformationBase::isValid() && !members.isEmpty(); } +}; + + +struct CompleteTableInformation : public TableInformation +{ + QVector columns; + explicit CompleteTableInformation(const TableInformation &o) + : TableInformation(o) + {} + CompleteTableInformation() = default; + bool isEmpty() const { return columns.isEmpty(); } +}; + +class CODEGEN_BASE_EXPORT StructBuilder +{ + struct ColumnInformationEx : public ColumnInformation + { + QSharedPointer table; + + ColumnInformationEx() = default; + ColumnInformationEx(const ColumnInformation &o, + const QSharedPointer &ti) + : ColumnInformation(o) + , table(ti) + {} + }; + +public: + struct ServiceTypeStruct + { + QString service; + QString type; + bool bIsSequence = false; + bool isExternalType() const { return !service.isEmpty(); } + bool isSequence() const { return bIsSequence; } + QString fqType() const { return service + QStringLiteral("::") + type; } + }; + + explicit StructBuilder(XmlReader *xmlReader); + ~StructBuilder() = default; + + bool buildStructures(); + QVector structInformations() const + { + return m_structInfos; + } + QVector topicTypeInformations() const + { + return m_topicTypeInfos; + } + + CompleteStructInformation structInformationByTableName(const QString &tableName) const; + + EnumInformation enumInformationByType(const QString &type, bool checkExternals = true, bool verbose = true); + + CompleteTopicTypeInformation topicTypeInformationByStructType(const QString &type, bool checkExternals = true); + CompleteTopicTypeInformation externalTopicTypeByStructType(const ServiceTypeStruct &sts); + + QVector enumInformations() const + { + return m_enumInfos; + } + + QMap getDbTables() const + { + return m_sqlTableInformations; + } + + QMap getDbViews() const + { + return m_sqlViewInformations; + } + + bool needPoint3d() const + { + return m_needPoint3d; + } + + bool needPgArrayInclude() const + { + return m_needPgArrayInclude; + } + + CompleteTableInformation completeTableInformation(const CompleteTopicTypeInformation &ctti); + CompleteTableInformation getRecordingTopicTable(const QString &structName); + QMap getRecordingTopicTables(); + QVector > getSerializeFromToJsonTopics() const; + bool fixupQVariantConversions(MemberInformation &mi) const; + + Q_REQUIRED_RESULT static QString firstCharLowercase(const QString &txt); + Q_REQUIRED_RESULT static QString camelCaseString(const QString &in, bool bFirstUppercase = false); + Q_REQUIRED_RESULT static QString camelCaseToUnderline(const QString &in); + + bool buildJSonStructureInformationFromIdlData(const CompleteTopicTypeInformation &ctti); + QMap> jsonStructureInformation() const { return m_jsonStructureInformation; } + +private: + bool handleEnum(const EnumInformation &ei); + bool handleTable(const ServiceStructInformation &ssi); + bool handleIdlStructInfo(const TopicTypeInformation &tti, + QVector &destination, + const QString &svcName = QString()); + bool loadExternalTopicType(const ServiceTypeStruct &sts); + + bool getColumnsFromTable(const QString &tableName, + QVector &columns, + QVector &tableNames) const; + + bool fixupCustomDbDatatypes(MemberInformation &memberInfo, QVector &members); + bool fixupDatatypePoint2d(MemberInformation &memberInfo, QVector &members); + bool fixupDatatypePoint3d(MemberInformation &memberInfo, QVector &members); + bool fixupDatatypeIntegerArray(MemberInformation &memberInfo); + + QVector buildDbTableInformationFromIdlData(const CompleteTopicTypeInformation &ctti, + const QString &tableName, + const QString &columnNamePrefix, + const QString &cppStructure); + void addToSerializeFromToJsonTopics(const QSharedPointer &toAdd); + CompleteTableInformation getRecordingTopicTable(const CompleteTopicTypeInformation &ctti, + const MemberInformation &miPrimaryKey); + MemberInformation getRecordPrimaryKey() const; + + ServiceTypeStruct extractServiceAndTypeFromTypename(const QString &type); + + void addToSqlTableInformation(const MemberInformation &mi); +private: + QVector m_structInfos; + QVector m_topicTypeInfos; + mutable QVector m_externalTopicTypes; + QVector m_enumInfos; + QMap m_sqlTableInformations; + QMap m_sqlViewInformations; + QSet m_sqlTableInformationsAlreadyAvailable; + QVector> m_serializeFromToJsonTopics; + + QMap> m_jsonStructureInformation; + + bool m_needPoint3d; + bool m_needPgArrayInclude; + + XmlReader *m_xmlReader; + NGL::INGLLogger &m_logger; + NGL::INGLLogger &m_loggerCSV; +}; +} diff --git a/Lts/Common/CodeGen/XmlReader.cpp b/Lts/Common/CodeGen/XmlReader.cpp new file mode 100755 index 000000000..532a5877f --- /dev/null +++ b/Lts/Common/CodeGen/XmlReader.cpp @@ -0,0 +1,927 @@ + + +#include "XmlReader.h" + +#include + +#include +#include +#include +#include +#include + +using namespace CodeGen; + + +class XmlReaderException : public std::exception +{ +public: + explicit XmlReaderException(const QString &msg) noexcept + : m_msg(msg.toUtf8()) + {} + + ~XmlReaderException() = default; + + virtual const char *what() const noexcept + { + return m_msg.data(); + } +private: + const QByteArray m_msg; +}; + +XmlReader::XmlReader() + : m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("CodeGen.XmlReader")) +{} + +bool XmlReader::readServiceFile(const QString &svcFile, + const QString &idlServiceInfoFile, + const XmlReader *parent) +{ + m_externalXmlInformations.clear(); + m_idlFiles.clear(); + m_xmlFiles.clear(); + m_usedServices.clear(); + m_fullServiceName2Abbreviation.clear(); + + m_svcNamespace.clear(); + m_svcAbbr.clear(); + m_svcProcNames.clear(); + m_customRecordReplay = false; + m_exerciseDependency = ExerciseDependency::Exercise; + m_dbClassTables.clear(); + m_struct2BaseClass.clear(); + m_structsToGenerate.clear(); + m_idlStructsToGenerate.clear(); + m_tables.clear(); + m_views.clear(); + m_enumerations.clear(); + m_functionTopics.clear(); + m_externalTopicTypes.clear(); + m_customFunctions.clear(); + m_prio = 0; + m_svcDevMode = false; + + + if (parent) + { + m_idlFiles = parent->m_idlFiles; + m_xmlFiles = parent->m_xmlFiles; + m_fullServiceName2Abbreviation = parent->m_fullServiceName2Abbreviation; + m_externalXmlInformations = parent->m_externalXmlInformations; + } + + + + loadIdlInfofile(idlServiceInfoFile); + + + QFile f(svcFile); + if (!f.exists()) + { + ERROR_LOG(m_logger) << "Service file '" << svcFile << "' not found!"; + return false; + } + if (!f.open(QIODevice::ReadOnly)) + { + m_errorString = QLatin1String("Can not open service file '") + svcFile + + QLatin1String("' for reading: ") + f.errorString(); + ERROR_LOG(m_logger) << m_errorString; + return false; + } + + INFO_LOG(m_logger) << "SvcFile: " << svcFile; + + + QString errMsg; + int errLine = 0; + int errColumn = 0; + QDomDocument doc; + if (!doc.setContent(&f, &errMsg, &errLine, &errColumn)) + { + m_errorString = QLatin1String("Error parsing service file '") + svcFile + QLatin1String("': ") + + errMsg + QLatin1String(" at ") + QString::number(errLine) + + QLatin1Char(':') + QString::number(errColumn); + ERROR_LOG(m_logger) << m_errorString; + return false; + } + + + + + + + + + QDomElement domRoot = doc.documentElement(); + if (!domRoot.hasAttribute(QStringLiteral("namespace"))) + { + m_errorString = QLatin1String("Xml file '") + svcFile + + QLatin1String("' is not a valid service file: service name/namespace is missing"); + ERROR_LOG(m_logger) << m_errorString; + return false; + } + m_svcNamespace = getAttribute(domRoot, QStringLiteral("namespace")); + m_svcAbbr = getAttribute(domRoot, QStringLiteral("abbreviation")).toLower(); + m_svcProcNames = getAttribute(domRoot, QStringLiteral("procNames")).split(QLatin1Char(',')); + m_customRecordReplay = getAttributeBool(domRoot, QStringLiteral("customRecordReplay"), true, false); + + QString devModeStr = getAttribute(domRoot, QStringLiteral("devMode"), true); + m_svcDevMode = devModeStr == QStringLiteral("1") || + devModeStr == QStringLiteral("true"); + INFO_LOG(m_logger) << "devMode: " << m_svcDevMode; + + const QString groupNamesString = getAttribute(domRoot, QStringLiteral("groups"), true); + if (!groupNamesString.isEmpty()) + { + m_groupNames = groupNamesString.split(QLatin1Char(','), QString::SkipEmptyParts); + INFO_LOG(m_logger) << "groups: " << groupNamesString; + } + else + m_groupNames.clear(); + + m_exerciseDependency = string2ExerciseDependency(getAttribute(domRoot, QStringLiteral("exerciseDependency"), true)); + + + + if (m_groupNames.contains(QStringLiteral("Core")) || + m_groupNames.contains(QStringLiteral("Master"))) + { + if (m_exerciseDependency != ExerciseDependency::Global && + m_exerciseDependency != ExerciseDependency::GlobalAndExercise) + { + WARNING_LOG(m_logger) << "Service " << getServiceNamespace() + << " is not marked as exercise independent but should be started in group Core/Master"; + } + } + + INFO_LOG(m_logger) << "exerciseDependency: " << (int)m_exerciseDependency; + if ( domRoot.hasAttribute(QStringLiteral("prio"))) + { + m_prio = getAttribute(domRoot, QStringLiteral("prio"), 0).toInt(); + } + try + { + parseDBSserviceStructs(domRoot.firstChildElement(QStringLiteral("dbServiceStructs"))); + } + catch (const XmlReaderException &ex) + { + m_errorString = QLatin1String("Error parsing dbServiceStructs: ") + QLatin1String(ex.what()); + ERROR_LOG(m_logger) << m_errorString; + return false; + } + + try + { + parseDBTables(domRoot.firstChildElement(QStringLiteral("tables"))); + } + catch (const XmlReaderException &ex) + { + m_errorString = QLatin1String("Error parsing dbtables: ") + QLatin1String(ex.what()); + ERROR_LOG(m_logger) << m_errorString; + return false; + } + + try + { + parseDBViews(domRoot.firstChildElement(QStringLiteral("views"))); + } + catch (const XmlReaderException &ex) + { + m_errorString = QLatin1String("Error parsing dbviews: ") + QLatin1String(ex.what()); + ERROR_LOG(m_logger) << m_errorString; + return false; + } + + try + { + parseEnumerations(domRoot.firstChildElement(QStringLiteral("enumerations"))); + } + catch (const XmlReaderException &ex) + { + m_errorString = QLatin1String("Error parsing enumerations: ") + QLatin1String(ex.what()); + ERROR_LOG(m_logger) << m_errorString; + return false; + } + + try + { + parseCustomDtcFunctions(domRoot.firstChildElement(QStringLiteral("customDtcFunctions"))); + } + catch (const XmlReaderException &ex) + { + m_errorString = QLatin1String("Error parsing custom dtc functions: ") + QLatin1String(ex.what()); + ERROR_LOG(m_logger) << m_errorString; + return false; + } + + try + { + parseTopicTypes(domRoot.firstChildElement(QStringLiteral("topicTypes"))); + } + catch (const XmlReaderException &ex) + { + m_errorString = QLatin1String("Error parsing idl strucs: ") + QLatin1String(ex.what()); + ERROR_LOG(m_logger) << m_errorString; + return false; + } + + try + { + parseFunctionTopics(domRoot.firstChildElement(QStringLiteral("functionTopics"))); + } + catch (const XmlReaderException &ex) + { + m_errorString = QLatin1String("Error parsing function topics: ") + QLatin1String(ex.what()); + ERROR_LOG(m_logger) << m_errorString; + return false; + } + + return true; +} + +QString XmlReader::getAttribute(const QDomElement &element, + const QString &attributeName, + bool bOptional) +{ + if (!element.hasAttribute(attributeName)) + { + if (bOptional) + return QString(); + throw XmlReaderException(QLatin1String("Element ") + element.tagName() + + QLatin1String(" has no attribute ") + attributeName); + } + return element.attribute(attributeName); +} + +bool XmlReader::getAttributeBool(const QDomElement &element, + const QString &attributeName, + bool bOptional, bool defaultValue) +{ + QString str = getAttribute(element, attributeName, bOptional); + if (str.isEmpty()) + return defaultValue; + bool bOk; + bool ret = (str.toInt(&bOk) != 0); + if (!bOk) + throw XmlReaderException(QLatin1String("Can not parse boolean value: ") + str); + return ret; +} + +int XmlReader::getAttributeInt(const QDomElement &element, + const QString &attributeName, + bool bOptional, int defaultValue) +{ + QString str = getAttribute(element, attributeName, bOptional); + if (str.isEmpty()) + return defaultValue; + bool bOk; + int ret = str.toInt(&bOk); + if (!bOk) + { + + ret = str.toUInt(&bOk,16); + if(!bOk) + throw XmlReaderException(QLatin1String("Can not parse int value: ") + str); + } + return ret; +} + +void XmlReader::parseDBSserviceStructs(const QDomElement &rootElement) +{ + if (rootElement.isNull()) + return; + + + QDomElement domDbStruct = rootElement.firstChildElement(QStringLiteral("serviceStruct")); + while (!domDbStruct.isNull()) + { + ServiceStructInformation ssi; + ssi.name = getAttribute(domDbStruct, QStringLiteral("name")); + ssi.primaryTable = getAttribute(domDbStruct, QStringLiteral("table")); + ssi.hasStaticContent = getAttributeBool(domDbStruct, QStringLiteral("static"), true, false); + + QDomElement domFunc = domDbStruct.firstChildElement(QStringLiteral("function")); + while (!domFunc.isNull()) + { + m_customFunctions += parseCustomFunction(domFunc, CustomServiceFunctionInformation::Type::Db); + domFunc = domFunc.nextSiblingElement(domFunc.tagName()); + } + + QDomElement domNotify = domDbStruct.firstChildElement(QStringLiteral("notification")); + while (!domNotify.isNull()) + { + ssi.dbNotifications.append(parseDbNotification(domNotify)); + domNotify = domNotify.nextSiblingElement(domNotify.tagName()); + } + + m_structsToGenerate.push_back(ssi); + + domDbStruct = domDbStruct.nextSiblingElement(domDbStruct.tagName()); + } + std::sort(m_structsToGenerate.begin(), m_structsToGenerate.end()); + + + domDbStruct = rootElement.firstChildElement(QStringLiteral("table")); + while (!domDbStruct.isNull()) + { + const QString table = getAttribute(domDbStruct, QStringLiteral("name")); + const QString parentTable = getAttribute(domDbStruct, QStringLiteral("base"), true); + m_struct2BaseClass[table] = parentTable; + + domDbStruct = domDbStruct.nextSiblingElement(domDbStruct.tagName()); + } + INFO_LOG(m_logger) << "Read " << m_structsToGenerate.size() << " database service struct(s)"; +} + +void XmlReader::parseDBTables(const QDomElement &rootElement) +{ + if (rootElement.isNull()) + return; + + QDomElement domDbTable = rootElement.firstChildElement(QStringLiteral("table")); + while (!domDbTable.isNull()) + { + QSharedPointer ti(new TableInformation); + ti->name = getAttribute(domDbTable, QStringLiteral("name")); + ti->structName = QLatin1String("t_") + ti->name; + ti->comment = getAttribute(domDbTable, QStringLiteral("comment"), true); + ti->exerciseDependency = string2ExerciseDependency(getAttribute(domDbTable, QStringLiteral("exerciseDependency"), true)); + + QDomElement domColumn = domDbTable.firstChildElement(QStringLiteral("column")); + while (!domColumn.isNull()) + { + ti->plainColumns.append(parseTableColumn(domColumn, ti->name)); + domColumn = domColumn.nextSiblingElement(domColumn.tagName()); + } + + QDomElement domForeignKey = domDbTable.firstChildElement(QStringLiteral("foreignKey")); + while (!domForeignKey.isNull()) + { + ti->foreignKeys.append(parseForeignKey(domForeignKey)); + domForeignKey = domForeignKey.nextSiblingElement(domForeignKey.tagName()); + } + QDomElement domConstraint = domDbTable.firstChildElement(QStringLiteral("constraint")); + while (!domConstraint.isNull()) + { + ti->constraints.append(parseConstraint(domConstraint)); + domConstraint = domConstraint.nextSiblingElement(domConstraint.tagName()); + } + + m_tables.insert(ti->name, ti); + + domDbTable = domDbTable.nextSiblingElement(domDbTable.tagName()); + } + + INFO_LOG(m_logger) << "Read " << m_tables.size() << " database table(s)"; +} + +void XmlReader::parseDBViews(const QDomElement &rootElement) +{ + if (rootElement.isNull()) + return; + + QDomElement domDbView = rootElement.firstChildElement(QStringLiteral("view")); + while (!domDbView.isNull()) + { + QSharedPointer vi(new ViewInformation); + vi->name = getAttribute(domDbView, QStringLiteral("name")); + vi->structName = QLatin1String("v_") + vi->name; + vi->baseSchema = m_svcAbbr; + vi->baseTable = getAttribute(domDbView, QStringLiteral("base")); + vi->comment = getAttribute(domDbView, QStringLiteral("comment"), true); + vi->filter = getAttribute(domDbView, QStringLiteral("filter")); + vi->exerciseDependency = string2ExerciseDependency(getAttribute(domDbView, QStringLiteral("exerciseDependency"), true)); + + DEBUG_LOG(m_logger) << "Base table = " << vi->baseTable; + + + m_views.insert(vi->name, vi); + + domDbView = domDbView.nextSiblingElement(domDbView.tagName()); + } + + INFO_LOG(m_logger) << "Read " << m_views.size() << " database view(s)"; +} + +void XmlReader::parseEnumerations(const QDomElement &rootElement) +{ + if (rootElement.isNull()) + return; + + QDomElement domEnum = rootElement.firstChildElement(QStringLiteral("enumeration")); + while (!domEnum.isNull()) + { + EnumInformation enumeration; + enumeration.serviceName = getServiceNamespace(); + enumeration.enumName = getAttribute(domEnum, QStringLiteral("name")); + enumeration.tableName = getAttribute(domEnum, QStringLiteral("table"), true); + + QDomElement enumVal = domEnum.firstChildElement(QStringLiteral("attribute")); + while (!enumVal.isNull()) + { + EnumKeyValuePair evp; + evp.key = getAttribute(enumVal, QStringLiteral("key")); + evp.value = getAttributeInt(enumVal, QStringLiteral("value")); + enumeration.values.append(evp); + + enumVal = enumVal.nextSiblingElement(enumVal.tagName()); + } + + m_enumerations.push_back(enumeration); + + domEnum = domEnum.nextSiblingElement(domEnum.tagName()); + } + INFO_LOG(m_logger) << "Read " << m_enumerations.size() << " enumeration(s)"; +} + +void XmlReader::parseCustomDtcFunctions(const QDomElement &rootElement) +{ + if (rootElement.isNull()) + return; + + QDomElement domElement = rootElement.firstChildElement(QStringLiteral("customDtcFunction")); + while (!domElement.isNull()) + { + + QDomElement domFunc = domElement.firstChildElement(QStringLiteral("function")); + while (!domFunc.isNull()) + { + m_customFunctions += parseCustomFunction(domFunc, CustomServiceFunctionInformation::Type::Dtc); + domFunc = domFunc.nextSiblingElement(domFunc.tagName()); + } + domElement = domElement.nextSiblingElement(domElement.tagName()); + } + INFO_LOG(m_logger) << "Read " << m_enumerations.size() << " customDtcFunction(s)"; +} + + + +void XmlReader::parseTopicTypes(const QDomElement &rootElement) +{ + if (rootElement.isNull()) + return; + + + QDomElement domIdlStruct = rootElement.firstChildElement(QStringLiteral("topicType")); + while (!domIdlStruct.isNull()) + { + TopicTypeInformation isi; + isi.name = getAttribute(domIdlStruct, QStringLiteral("name")); + isi.baseValueType = getAttribute(domIdlStruct, QStringLiteral("baseValueType"), true); + + + const QVector sl = isi.baseValueType.splitRef(QStringLiteral("::")); + if (sl.size() == 2) + loadExternalTopicType(sl.at(0).toString(), sl.at(1).toString()); + + const QString tmp = getAttribute(domIdlStruct, QStringLiteral("extensibility"), true); + + if (tmp.compare(QLatin1String("FINAL"), Qt::CaseInsensitive) == 0) isi.extensibility = dds::core::xtypes::ExtensibilityKind::FINAL; + if (tmp.compare(QLatin1String("MUTABLE"), Qt::CaseInsensitive) == 0) isi.extensibility = dds::core::xtypes::ExtensibilityKind::MUTABLE; + if (tmp.compare(QLatin1String("EXTENSIBLE"), Qt::CaseInsensitive) == 0) isi.extensibility = dds::core::xtypes::ExtensibilityKind::EXTENSIBLE; + + isi.comment = getAttribute(domIdlStruct, QStringLiteral("comment"), true); + isi.isDataObject = getAttributeBool(domIdlStruct, QStringLiteral("isDataObject"), true, false); + isi.dtsAutogen = getAttributeBool(domIdlStruct, QStringLiteral("autogenDTS"), true, true); + isi.dtcAutogen = getAttributeBool(domIdlStruct, QStringLiteral("autogenDTC"), true, true); + isi.recordTopic = getAttributeBool(domIdlStruct, QStringLiteral("recordTopic"), true, false); + isi.routeToClient = getAttributeBool(domIdlStruct, QStringLiteral("routeToClient"), true, true); + isi.routeToServer = getAttributeBool(domIdlStruct, QStringLiteral("routeToServer"), true, false); + isi.routeInReplay = getAttributeBool(domIdlStruct, QStringLiteral("routeInReplay"), true, false); + isi.autosubscribe = getAttributeBool(domIdlStruct, QStringLiteral("autosubscribe"), true, true); + + QDomElement domColumn = domIdlStruct.firstChildElement(QStringLiteral("column")); + while (!domColumn.isNull()) + { + isi.columns.append(parseIdlStructColumn(domColumn)); + domColumn = domColumn.nextSiblingElement(domColumn.tagName()); + } + + + m_idlStructsToGenerate.insert(isi.name, isi); + + domIdlStruct = domIdlStruct.nextSiblingElement(domIdlStruct.tagName()); + } + + + INFO_LOG(m_logger) << "Read " << m_idlStructsToGenerate.size() << " idl struct(s)"; +} + +void XmlReader::parseFunctionTopics(const QDomElement &rootElement) +{ + if (rootElement.isNull()) + return; + + + QDomElement domFunctionTopic = rootElement.firstChildElement(QStringLiteral("functionTopic")); + while (!domFunctionTopic.isNull()) + { + FunctionTopic ft; + ft.name = getAttribute(domFunctionTopic, QStringLiteral("name")); + ft.requestTopicType = getAttribute(domFunctionTopic, QStringLiteral("requestTopicType")); + ft.replyTopicType = getAttribute(domFunctionTopic, QStringLiteral("replyTopicType")); + ft.requestTopicName = getAttribute(domFunctionTopic, QStringLiteral("requestTopicName")); + ft.replyTopicName = getAttribute(domFunctionTopic, QStringLiteral("replyTopicName")); + ft.routeInReplay = getAttributeBool(domFunctionTopic, QStringLiteral("routeInReplay"), true, false); + + m_functionTopics.append(ft); + + domFunctionTopic = domFunctionTopic.nextSiblingElement(domFunctionTopic.tagName()); + } + + for (const FunctionTopic &ft : qAsConst(m_functionTopics)) + { + const QVector sl1 = ft.requestTopicType.splitRef(QStringLiteral("::")); + if (sl1.size() == 2) + loadExternalTopicType(sl1.at(0).toString(), sl1.at(1).toString()); + const QVector sl2 = ft.replyTopicType.splitRef(QStringLiteral("::")); + if (sl2.size() == 2) + loadExternalTopicType(sl2.at(0).toString(), sl2.at(1).toString()); + } +} + +ColumnInformation XmlReader::parseTableColumn(const QDomElement &column, const QString &tableName) const +{ + ColumnInformation ci; + + ci.columnName = getAttribute(column, QStringLiteral("name")); + ci.datatype = getAttribute(column, QStringLiteral("datatype")); + ci.defaultValue = getAttribute(column, QStringLiteral("default"), true); + ci.comment = getAttribute(column, QStringLiteral("comment"), true); + ci.isPrimaryKey = getAttributeBool(column, QStringLiteral("isPrimaryKey"), true, false); + ci.isForeignKey = getAttributeBool(column, QStringLiteral("isForeignKey"), true, false); + ci.isOptional = getAttributeBool(column, QStringLiteral("isOptional"), true, false); + const QDomElement seq = column.firstChildElement(QStringLiteral("sequence")); + if (!seq.isNull()) + { + ci.sequenceStart = getAttributeInt(seq, QStringLiteral("start"), true, -1); + ci.sequenceIncrement = getAttributeInt(seq, QStringLiteral("increment"), true, -1); + ci.hasSequence = ci.sequenceStart >= 0 && ci.sequenceIncrement > 0; + } + + if (ci.columnName.toLower() != ci.columnName) + WARNING_LOG(m_logger) << "Column " << ci.columnName << " in Table " + << tableName << " has has upper-case chars which is not allowed!"; + + return ci; +} + +IdlColumnInformation XmlReader::parseIdlStructColumn(const QDomElement &column) const +{ + IdlColumnInformation ci; + + ci.name = getAttribute(column, QStringLiteral("name")); + ci.type = getAttribute(column, QStringLiteral("idlType")); + ci.comment = getAttribute(column, QStringLiteral("comment"), true); + ci.isKey = getAttributeBool(column, QStringLiteral("isKey"), true, false); + ci.isOptional = getAttributeBool(column, QStringLiteral("isOptional"), true, false); + ci.isTranslatable = getAttributeBool(column, QStringLiteral("isTranslatable"), true, false); + + return ci; +} + +QSharedPointer XmlReader::parseForeignKey(const QDomElement &foreignKey) const +{ + QSharedPointer fk(new ForeignKeyInformation); + + fk->name = getAttribute(foreignKey, QStringLiteral("name")); + + const QString deleteAction = getAttribute(foreignKey, QStringLiteral("referential_integrity"), true); + if (deleteAction == QLatin1String("DeleteNoAction")) + fk->deleteAction = ForeignKeyInformation::OnDeleteAction::NoAction; + else if (deleteAction == QLatin1String("DeleteCascade")) + fk->deleteAction = ForeignKeyInformation::OnDeleteAction::Cascade; + else if (deleteAction == QLatin1String("DeleteRestrict")) + fk->deleteAction = ForeignKeyInformation::OnDeleteAction::Restrict; + else if (deleteAction == QLatin1String("DeleteSetNull")) + fk->deleteAction = ForeignKeyInformation::OnDeleteAction::SetNull; + else if (deleteAction == QLatin1String("DeleteSetDefault")) + fk->deleteAction = ForeignKeyInformation::OnDeleteAction::SetDefault; + else + fk->deleteAction = ForeignKeyInformation::OnDeleteAction::Cascade; + + fk->source = parseForeignKeyEndpoint(fk->name, foreignKey.firstChildElement(QStringLiteral("source"))); + fk->destination = parseForeignKeyEndpoint(fk->name, foreignKey.firstChildElement(QStringLiteral("destination"))); + + return fk; +} + +ForeignKeyInformation::Endpoint XmlReader::parseForeignKeyEndpoint(const QString &foreignKeyName, + const QDomElement &endpoint) const +{ + if (endpoint.isNull()) + throw XmlReaderException(QLatin1String("No endpoint for foreign key ") + foreignKeyName); + + ForeignKeyInformation::Endpoint fkEndpoint; + + fkEndpoint.service = getAttribute(endpoint, QStringLiteral("service"), true); + fkEndpoint.serviceAbbr= getAttribute(endpoint, QStringLiteral("serviceAbbr"), true); + fkEndpoint.schema = getAttribute(endpoint, QStringLiteral("schema"), true); + fkEndpoint.tableName = getAttribute(endpoint, QStringLiteral("table")); + fkEndpoint.columnName = getAttribute(endpoint, QStringLiteral("column")); + + return fkEndpoint; +} + +ConstraintInformation XmlReader::parseConstraint(const QDomElement &constraint) const +{ + ConstraintInformation ret; + + ret.name = getAttribute(constraint, QStringLiteral("name"), true); + const QString type = getAttribute(constraint, QStringLiteral("type")).toLower(); + if (type == QLatin1String("unique")) + ret.type = ConstraintInformation::Type::unique; + else if (type == QLatin1String("index")) + ret.type = ConstraintInformation::Type::index; + + QDomElement domColumn = constraint.firstChildElement(QStringLiteral("column")); + if (domColumn.isNull()) + throw XmlReaderException(QLatin1String("No column for constraint") + ret.name); + while (!domColumn.isNull()) + { + ret.affectedColumns += getAttribute(domColumn, QStringLiteral("name")); + domColumn = domColumn.nextSiblingElement(domColumn.tagName()); + } + return ret; +} + +CustomServiceFunctionInformation XmlReader::parseCustomFunction(const QDomElement &customFunc, + CustomServiceFunctionInformation::Type funcType) const +{ + CustomServiceFunctionInformation ret; + ret.methodName = getAttribute(customFunc, QStringLiteral("name")); + ret.returnValue = getAttribute(customFunc, QStringLiteral("returns"), true); + ret.isConst = getAttributeBool(customFunc, QStringLiteral("isConst"), true, false); + if (ret.returnValue.isEmpty()) + ret.returnValue = QStringLiteral("bool"); + QMap> paramHelper; + QDomElement domParam = customFunc.firstChildElement(QStringLiteral("parameter")); + while (!domParam.isNull()) + { + int pos = getAttributeInt(domParam, QStringLiteral("pos")); + if (paramHelper.contains(pos)) + throw XmlReaderException(QLatin1String("Doubled position entry for custom function ") + ret.methodName); + QString paramName = getAttribute(domParam, QStringLiteral("name")); + const QString paramType = getAttribute(domParam, QStringLiteral("type")); + const QString paramDefault = getAttribute(domParam, QStringLiteral("default"), true); + if (!paramDefault.isEmpty()) + paramName.append(QLatin1String(" = ") + paramDefault); + paramHelper[pos] = qMakePair(paramName, paramType); + domParam = domParam.nextSiblingElement(domParam.tagName()); + + } + ret.paramNames.reserve(paramHelper.size()); + ret.paramTypes.reserve(paramHelper.size()); + ret.type = funcType; + QStringList signature; + for (auto it = paramHelper.constBegin(); it != paramHelper.constEnd(); ++it) + { + ret.paramNames += it.value().first; + ret.paramTypes += it.value().second; + QString tmp = it.value().second; + if (!it.value().second.endsWith(QLatin1Char('&'))) + tmp += QLatin1Char(' '); + tmp += it.value().first; + signature += tmp; + } + ret.signature += signature.join(QStringLiteral(", ")); + return ret; +} + +DbNotifcationInformation XmlReader::parseDbNotification(const QDomElement &customFunc) const +{ + DbNotifcationInformation ret(parseCustomFunction(customFunc, CustomServiceFunctionInformation::Type::DbNotify)); + + const QString attribute(getAttribute(customFunc, QStringLiteral("notificationTypes")).toLower()); + const QStringList nfTypes = attribute.split(QLatin1Char(',')); + static const QMap s_nfTypeMap = + { + { QLatin1String("beforeinsert"), DbNotifcationInformation::NotificationType::BeforeInsert }, + { QLatin1String("afterinsert"), DbNotifcationInformation::NotificationType::AfterInsert }, + { QLatin1String("beforeupdate"), DbNotifcationInformation::NotificationType::BeforeUpdate }, + { QLatin1String("afterupdate"), DbNotifcationInformation::NotificationType::AfterUpdate }, + { QLatin1String("beforedelete"), DbNotifcationInformation::NotificationType::BeforeDelete }, + { QLatin1String("afterdelete"), DbNotifcationInformation::NotificationType::AfterDelete } + }; + for (const QString &nfType : nfTypes) + { + const auto it = s_nfTypeMap.find(nfType); + if (it == s_nfTypeMap.end()) + throw XmlReaderException(QStringLiteral("Can not find notification type %2 in type map for function %1").arg(ret.methodName, nfType)); + ret.notificationType += it.value(); + } + if (ret.notificationType.isEmpty()) + throw XmlReaderException(QStringLiteral("Notification function '%1' without a valid notification type").arg(ret.methodName)); + if (ret.paramTypes.size() != 1) + throw XmlReaderException(QStringLiteral("Notification function '%1' must have exactly one parameter").arg(ret.methodName)); + + return ret; +} + +TopicTypeInformation XmlReader::loadExternalTopicType(const QString &externalServiceName, const QString &topicTypeName) +{ + DEBUG_LOG(m_logger) << "externalServiceName: " << externalServiceName << ", topicTypeName: " << topicTypeName; + + if (!loadExternalService(externalServiceName)) + { + WARNING_LOG(m_logger) << "Could not find service " << externalServiceName + << " referenced by external topic " << topicTypeName; + return TopicTypeInformation(); + } + auto extXmlReader = m_externalXmlInformations.value(externalServiceName); + if (!extXmlReader) + { + ERROR_LOG(m_logger) << "Could not find service xml file for service '" << externalServiceName << "'"; + return TopicTypeInformation(); + } + + TopicTypeInformation tti = extXmlReader->getIdlStructInformation().value(topicTypeName); + if (tti.isValid()) + { + tti.name = externalServiceName + QLatin1String("::") + tti.name; + m_externalTopicTypes += tti; + m_usedServices[externalServiceName] = true; + return tti; + } + WARNING_LOG(m_logger) << "Could not find topic " << topicTypeName + << " in external service definition for service " + << externalServiceName; + return TopicTypeInformation(); +} + +EnumInformation XmlReader::getExternalEnumeration(const QString &externalServiceName, const QString &enumName, bool verbose) +{ + DEBUG_LOG(m_logger) << "externalServiceName: " << externalServiceName << ", topicTypeName: " << enumName; + + if (!loadExternalService(externalServiceName)) + { + WARNING_LOG(m_logger) << "Could not find service '" << externalServiceName << "'" + << " referenced by external enumeration " << enumName; + return EnumInformation(); + } + auto extXmlReader = m_externalXmlInformations.value(externalServiceName); + if (!extXmlReader) + { + ERROR_LOG(m_logger) << "Could not find service xml file for service '" << externalServiceName << "'"; + return EnumInformation(); + } + + const QVector enums = extXmlReader->getEnumerations(); + for (const EnumInformation &ei : enums) + { + if (ei.enumName == enumName) + { + m_usedServices[externalServiceName] = true; + return ei; + } + } + if (verbose) + { + WARNING_LOG(m_logger) << "Could not find enumeration " << enumName + << " in external service definition for '" + << externalServiceName << "'"; + } + return EnumInformation(); +} + + +bool XmlReader::loadExternalService(const QString &serviceName) +{ + if (serviceName == getServiceNamespace()) + return false; + if (m_externalXmlInformations.contains(serviceName)) + return true; + + QString fileName = xmlFileForService(serviceName); + if (fileName.isEmpty()) + { + const QVector dirs = NGL::directories(NGL::FileType::ConfigFile); + for (const QString &dirname : dirs) + { + QDirIterator dirIt(dirname + QStringLiteral("/xml"), QDir::Files | QDir::NoDotAndDotDot | QDir::Readable); + while (dirIt.hasNext()) + { + dirIt.next(); + if (!dirIt.fileName().endsWith(QLatin1String(".xml"))) + continue; + + fileName = dirIt.filePath(); + const QString svcNamespace = serviceNamespaceFromFile(fileName); + if (svcNamespace == serviceName) + break; + } + } + } + if (fileName.isEmpty()) + return false; + + DEBUG_LOG(m_logger) << "xml file for " << serviceName << ": " << fileName; + auto extXmlReader = QSharedPointer(new XmlReader); + if (!extXmlReader->readServiceFile(fileName, QString(), this)) + { + WARNING_LOG(m_logger) << "Error reading service file " << fileName + << " for service " << serviceName; + return false; + } + m_externalXmlInformations[serviceName] = extXmlReader; + if (!m_usedServices.contains(serviceName)) + m_usedServices[serviceName] = false; + return true; +} + +void XmlReader::loadIdlInfofile(const QString &idlServiceInfoFile) +{ + if (idlServiceInfoFile.isEmpty()) + return; + QFile f(idlServiceInfoFile); + if (!f.open(QIODevice::ReadOnly)) + { + WARNING_LOG(m_logger) << "Could not read idl service info file " << idlServiceInfoFile; + return; + } + const QList files = f.readAll().split('\n'); + for (const QByteArray &filePath : files) + { + + if (filePath.startsWith("--") || filePath.trimmed().isEmpty()) + continue; + const QString fileName = QString::fromLocal8Bit(filePath.trimmed()); + const QVector parts = fileName.splitRef(QLatin1Char('/')); + getServiceNameInfoFromFilePath(parts); + const auto fn = parts.last().trimmed(); + if (fn.endsWith(QLatin1String(".xml"))) + { + const auto svcAbbreveation = fn.split(QLatin1Char('.')).at(0).split(QLatin1Char('_')).at(0); + m_xmlFiles[svcAbbreveation.toString()] = fileName; + DEBUG_LOG(m_logger) << "xml: " << svcAbbreveation.toString() << " = " << fileName; + } + else + { + const auto svcAbbreveation = fn.split(QLatin1Char('.')).at(0).toString().toLower(); + if (svcAbbreveation.endsWith(QLatin1String("_enums"))) + continue; + m_idlFiles[svcAbbreveation] = fileName; + DEBUG_LOG(m_logger) << "idl: " << svcAbbreveation << " = " << fileName; + } + } +} + +QString XmlReader::getIdlFileForService(const QMap &data, + const QString &serviceName) const +{ + const QString serviceAbbr = serviceNameAbbreviation(serviceName); + return data.value(serviceAbbr); +} + + +QString XmlReader::serviceNameAbbreviation(const QString &serviceName) const +{ + return m_fullServiceName2Abbreviation.value(serviceName); +} + +void XmlReader::getServiceNameInfoFromFilePath(const QVector &parts) +{ + if (parts.size() < 2) + return; + + + const QString abbreviation = parts.last().split(QLatin1Char('.')).first().split(QLatin1Char('_')).first().toString(); + const QString serviceName = parts.at(parts.size() - 2).toString(); + DEBUG_LOG(m_logger) << "serviceName: " << serviceName << " = " << abbreviation; + m_fullServiceName2Abbreviation[serviceName] = abbreviation; +} + +ExerciseDependency XmlReader::string2ExerciseDependency(const QString &str) +{ + if (str.isEmpty()) + return ExerciseDependency::Undefined; + const QStringList sd = str.toLower().split(QLatin1Char(',')); + int ret = ExerciseDependency::Undefined; + for (const QString &s : sd) + { + if (s == QLatin1String("global")) + ret |= ExerciseDependency::Global; + else if (s == QLatin1String("exercise")) + ret |= ExerciseDependency::Exercise; + else if (s == QLatin1String("globalandexercise")) + ret |= ExerciseDependency::GlobalAndExercise; + } + return static_cast(ret); +} + +QString XmlReader::serviceNamespaceFromFile(const QString &fn) +{ + QFile f(fn); + if (!f.open(QIODevice::ReadOnly)) + return QString(); + QXmlStreamReader sr(&f); + while (!sr.atEnd()) + { + sr.readNext(); + if (sr.isStartElement() && sr.name() == QLatin1String("service")) + return (sr.attributes().value(QLatin1String("namespace"))).toString(); + } + return QString(); +} + diff --git a/Lts/Common/CodeGen/XmlReader.h b/Lts/Common/CodeGen/XmlReader.h new file mode 100755 index 000000000..b825b4f5c --- /dev/null +++ b/Lts/Common/CodeGen/XmlReader.h @@ -0,0 +1,427 @@ + + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +#include "codegen_export.h" + +namespace NGL +{ +class INGLLogger; +} +class QDomElement; + +namespace CodeGen +{ + +enum ExerciseDependency +{ + Undefined = 0, + Global = 0x1, + Exercise = 0x2, + GlobalAndExercise = 0x3 +}; + +struct EnumKeyValuePair +{ + QString key; + int value = 0; +}; + +struct EnumInformation +{ + QString enumName; + QString tableName; + QString serviceName; + QVector values; + + bool isValid() const { return !enumName.isEmpty(); } +}; + +struct ColumnInformation +{ + QString columnName; + QString datatype; + QString comment; + QString defaultValue; + bool isPrimaryKey = false; + bool isAssignmentForPrimaryTablePk = false; + bool isForeignKey = false; + bool generateSelectRemove = false; + bool canBeNull = true; + bool hasSequence = false; + bool isOptional = false; + int sequenceStart = 0; + int sequenceIncrement = 0; +}; + +struct IdlColumnInformation +{ + QString name; + QString type; + QString comment; + bool isKey = false; + bool isOptional = false; + bool isTranslatable = false; +}; + + +struct ForeignKeyInformation +{ + struct Endpoint + { + QString service; + QString serviceAbbr; + QString schema; + QString tableName; + QString columnName; + }; + + QString name; + Endpoint source; + Endpoint destination; + enum class OnDeleteAction + { + NoAction, + Cascade, + Restrict, + SetNull, + SetDefault + } deleteAction = OnDeleteAction::Cascade; +}; + +struct CustomServiceFunctionInformation +{ + QString methodName; + QString returnValue; + QString signature; + bool isConst = false; + QVector paramTypes; + QVector paramNames; + enum class Type + { + Db, + DbNotify, + Dtc + } type = Type::Db; +}; + +struct DbNotifcationInformation : public CustomServiceFunctionInformation +{ + DbNotifcationInformation() = default; + explicit DbNotifcationInformation(const CustomServiceFunctionInformation &d) + : CustomServiceFunctionInformation(d) + {} + enum class NotificationType + { + BeforeInsert, + AfterInsert, + BeforeUpdate, + AfterUpdate, + BeforeDelete, + AfterDelete + }; + QVector notificationType; +}; + +struct ConstraintInformation +{ + enum struct Type + { + unknown = 0x00, + unique = 0x01, + index = 0x02 + } type = Type::unknown; + QString name; + QStringList affectedColumns; +}; + +struct TableInformation +{ + QString name; + QString structName; + QString comment; + QVector plainColumns; + QVector > foreignKeys; + QVector constraints; + ExerciseDependency exerciseDependency = ExerciseDependency::Undefined; + + QSharedPointer fkInformationForColumn(const QString &tableName, + const QString &columnName) const + { + for (const QSharedPointer &fki : foreignKeys) + { + if (fki->source.columnName == columnName && + fki->source.tableName == tableName) + return fki; + } + return QSharedPointer(); + } +}; + +struct ViewInformation +{ + QString name; + QString comment; + QString structName; + QString baseTable; + QString baseSchema; + QString filter; + ExerciseDependency exerciseDependency = ExerciseDependency::Undefined; +}; + +struct ServiceStructInformation +{ + QString name; + QString primaryTable; + bool hasStaticContent = false; + + bool operator<(const ServiceStructInformation &o) const + { + if (hasStaticContent != o.hasStaticContent) + return hasStaticContent < o.hasStaticContent; + if (name != o.name) + return name < o.name; + return primaryTable < o.primaryTable; + } + QVector dbNotifications; +}; + +struct TopicTypeInformationBase +{ + TopicTypeInformationBase() = default; + virtual ~TopicTypeInformationBase() = default; + + QString name; + QString baseValueType; + QString comment; + bool isDataObject = false; + bool dtsAutogen = true; + bool dtcAutogen = true; + bool recordTopic = false; + bool routeToClient = true; + bool routeToServer = false; + bool routeInReplay = true; + bool autosubscribe = true; + + dds::core::xtypes::ExtensibilityKind extensibility = dds::core::xtypes::ExtensibilityKind::EXTENSIBLE; + + virtual bool isValid() const { return !name.isEmpty(); } + + bool operator<(const TopicTypeInformationBase &o) const + { + if (name != o.name) + return name < o.name; + return baseValueType < o.baseValueType; + } +}; + +struct TopicTypeInformation : public TopicTypeInformationBase +{ + QVector columns; +}; + +struct FunctionTopic +{ + QString name; + QString requestTopicType; + QString replyTopicType; + QString requestTopicName; + QString replyTopicName; + bool routeInReplay = false; +}; + + +class CODEGEN_BASE_EXPORT XmlReader final +{ +public: + XmlReader(); + + bool readServiceFile(const QString &svcFile, + const QString &idlServiceInfoFile = QString(), + const XmlReader *parent = nullptr); + QString errorString() const + { + return m_errorString; + } + + QString getServiceNamespace() const { return m_svcNamespace; } + QString getServiceAbbr() const { return m_svcAbbr; } + QStringList getGroupNames() const { return m_groupNames; } + int getPrio() const { return m_prio;} + QStringList getProcNames() const { return m_svcProcNames; } + bool hasCustomRecordReplayPlugin() const { return m_customRecordReplay; } + bool getDevMode() const { return m_svcDevMode; } + + bool isServiceExerciseIndependent() const { return m_exerciseDependency == ExerciseDependency::Global; } + ExerciseDependency defaultExerciseDependency() const { return m_exerciseDependency; } + + QMap > getDbClass2TableMapping() const { return m_dbClassTables; } + + + QMap getStruct2BaseClassRelations() const { return m_struct2BaseClass; } + QVector getStructToGenerate() const { return m_structsToGenerate; } + + + QMap getIdlStructInformation() const { return m_idlStructsToGenerate; } + QVector getIdlStructToGenerate() const + { + QVector res; + res.reserve(m_idlStructsToGenerate.size()); + auto it = m_idlStructsToGenerate.constBegin(); + while (it != m_idlStructsToGenerate.constEnd()) + { + res.append(it.value()); + ++it; + } + return res; + } + + + QMap > getTables() const + { + return m_tables; + } + QMap > getViews() const + { + return m_views; + } + QSharedPointer getTable(const QString &table) const + { + return m_tables.value(table); + } + QVector getEnumerations() const + { + return m_enumerations; + } + QVector getIdlColumns(const QString &structName) const + { + return m_idlStructsToGenerate.value(structName).columns; + } + + QVector getFunctionTopics() const + { + return m_functionTopics; + } + QVector getExternalTopicTypes() const + { + return m_externalTopicTypes; + } + + QString idlFileForService(const QString &serviceName) const + { + return getIdlFileForService(m_idlFiles, serviceName); + } + + QString xmlFileForService(const QString &serviceName) const + { + return getIdlFileForService(m_xmlFiles, serviceName); + } + + QMap usedServices() const + { + return m_usedServices; + } + + QVector getCustomFunctions() const + { + return m_customFunctions; + } + + + TopicTypeInformation loadExternalTopicType(const QString &externalServiceName, const QString &topicTypeName); + EnumInformation getExternalEnumeration(const QString &externalServiceName, const QString &enumName, bool verbose); + + static ExerciseDependency string2ExerciseDependency(const QString &str); + + XmlReader(const XmlReader &) = delete; + XmlReader &operator=(const XmlReader &) = delete; + +private: + void parseDBSserviceStructs(const QDomElement &rootElement); + void parseDBTables(const QDomElement &rootElement); + void parseDBViews(const QDomElement &rootElement); + void parseEnumerations(const QDomElement &rootElement); + void parseTopicTypes(const QDomElement &rootElement); + void parseFunctionTopics(const QDomElement &rootElement); + void parseCustomDtcFunctions(const QDomElement &rootElement); + + static QString getAttribute(const QDomElement &element, + const QString &attributeName, + bool bOptional = false); + static bool getAttributeBool(const QDomElement &element, + const QString &attributeName, + bool bOptional, bool defaultValue); + static int getAttributeInt(const QDomElement &element, + const QString &attributeName, + bool bOptional = false, int defaultValue = 0); + + ColumnInformation parseTableColumn(const QDomElement &column, const QString &tableName) const; + IdlColumnInformation parseIdlStructColumn(const QDomElement &column) const; + + QSharedPointer parseForeignKey(const QDomElement &foreignKey) const; + ForeignKeyInformation::Endpoint parseForeignKeyEndpoint(const QString &foreignKeyName, + const QDomElement &endpoint) const; + ConstraintInformation parseConstraint(const QDomElement &constraint) const; + CustomServiceFunctionInformation parseCustomFunction(const QDomElement &customFunc, CustomServiceFunctionInformation::Type funcType) const; + DbNotifcationInformation parseDbNotification(const QDomElement &customFunc) const; + bool loadExternalService(const QString &serviceName); + void loadIdlInfofile(const QString &idlServiceInfoFile); + QString getIdlFileForService(const QMap &data, const QString &serviceName) const; + QString serviceNameAbbreviation(const QString &serviceName) const; + void getServiceNameInfoFromFilePath(const QVector &parts); + static QString serviceNamespaceFromFile(const QString &fn); + + + QString m_svcNamespace; + + QString m_svcAbbr; + + QStringList m_svcProcNames; + + bool m_customRecordReplay; + + + ExerciseDependency m_exerciseDependency{ExerciseDependency::Exercise}; + + + QMap > m_dbClassTables; + + + QMap m_struct2BaseClass; + QVector m_structsToGenerate; + + QMap m_idlStructsToGenerate; + + QVector m_functionTopics; + QVector m_externalTopicTypes; + QVector m_customFunctions; + + + QMap > m_tables; + QMap > m_views; + QVector m_enumerations; + QString m_errorString; + + + QMap > m_externalXmlInformations; + QMap m_idlFiles; + QMap m_xmlFiles; + QMap m_usedServices; + QMap m_fullServiceName2Abbreviation; + + QStringList m_groupNames; + int m_prio; + bool m_svcDevMode{false}; + + NGL::INGLLogger &m_logger; +}; +} diff --git a/Lts/Common/CodeGen/codegen_export.h b/Lts/Common/CodeGen/codegen_export.h new file mode 100755 index 000000000..698bad14f --- /dev/null +++ b/Lts/Common/CodeGen/codegen_export.h @@ -0,0 +1,17 @@ + + +#pragma once + +#include + +#ifndef CODEGEN_BASE_EXPORT +# ifdef BUILD_STATIC_LIBRARIES +# define CODEGEN_BASE_EXPORT +# else +# if defined(MAKE_CODEGEN_BASE_LIB) +# define CODEGEN_BASE_EXPORT Q_DECL_EXPORT +# else +# define CODEGEN_BASE_EXPORT Q_DECL_IMPORT +# endif +# endif +#endif diff --git a/Lts/Common/CodeGen/codegen_log4cxx.props b/Lts/Common/CodeGen/codegen_log4cxx.props new file mode 100755 index 000000000..889482757 --- /dev/null +++ b/Lts/Common/CodeGen/codegen_log4cxx.props @@ -0,0 +1,101 @@ +log4j.reset=true +log4j.rootLogger=WARN, stdout + +# CodeGeneration Tool +########################################################### + +log4j.logger.CodeGen.main=WARN +log4j.logger.CodeGen=INFO +log4j.logger.CodeGen.Settings=INFO +log4j.logger.CodeGen.FtsImpl=INFO +log4j.logger.CodeGen.FtcImpl=INFO + +# DTS/DTC, FTS/FTC +log4j.logger.Generic.DTS=INFO +log4j.logger.Generic.DTC=INFO +log4j.logger.Generic.FTS.Function=INFO +log4j.logger.Generic.FTC=INFO +log4j.logger.DTC.Subscriber=INFO +log4j.logger.DTC.SubscriberListener=INFO +log4j.logger.FTS=INFO +log4j.logger.FTC=WARN + +# connext (request/reply feature) +log4j.logger.connext.requester=WARN +log4j.logger.connext.replier=WARN + +# CodeGen +log4j.logger.CodeGen.XmlReader=WARN + +# SqlGenerator +log4j.logger.CodeGen.SqlGenerator=WARN +log4j.logger.CodeGen.StructBuilder=INFO + +log4j.appender.file=org.apache.log4j.RollingFileAppender +log4j.appender.file.MaxFileSize=1MB +log4j.appender.file.MaxBackupIndex=9 +log4j.appender.file.file=${LOG4QT_LOGDIR}/${LOG4QT_USER}_codeGen.log +log4j.appender.file.layout=org.apache.log4j.PatternLayout +log4j.appender.file.layout.ConversionPattern=%d %-5p %c |#| %m (%.50F:%L) %n + + +# Appnd_Console +########################################################### +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern= %d %p %c %c{1} - %m (%-.30F:%L) %n + + +# Appnd_Syslog +########################################################### +log4j.appender.syslog=org.apache.log4j.SyslogAppender +log4j.appender.syslog.Threshold=ERROR +log4j.appender.syslog.SysLogHost=localhost +log4j.appender.syslog.Facility=LOCAL7 +log4j.appender.syslog.layout=org.apache.log4j.PatternLayout +log4j.appender.syslog.layout.ConversionPattern= %-4r %-5p %c %x - %m +log4j.appender.syslog.LevelRangeFilter.AcceptOnMatch=true +log4j.appender.syslog.LevelRangeFilter.LevelMin=INFO +log4j.appender.syslog.LevelRangeFilter.LevelMax=FATAL + + +########################################################### +########################################################### +########################################################### +# 1) Available Levels in LOG4CXX Version 0.10.0: +# TRACE, +# DEBUG, +# INFO, +# WARN, +# ERROR +# FATAL +# +# d = date/time +# p = priority (Level) +# c = category/channel +# m = message +# F = file +# L = line +# n = platform dependent line separator character or characters. +# .... +# +# Examples for console: +# a) +# %d %p %c #%m# (%.50F:%L)%n +# date/time +# priority +# category/channel +# #message# +# (Maximal 50Character des Filename von rechts:Line) +# line separator +# +# b) +# %d (%.15F:%L) %-5p %c |#| %m%n +# date/time +# (Maximal 15Character des Filename von rechts:Line) +# priority, Right pad with spaces if the priority name is less than 5 characters long. +# category/channel +# message +# line separator +# +# Examples for files: diff --git a/Lts/Common/CodeGen/codegen_simple.py b/Lts/Common/CodeGen/codegen_simple.py new file mode 100755 index 000000000..a6d08ee58 --- /dev/null +++ b/Lts/Common/CodeGen/codegen_simple.py @@ -0,0 +1,477 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + + + + + + +from __future__ import print_function + +import xml.etree.ElementTree as ET +import os +import re +import string +import sys +import time + +class FtData(object): + def __init__( self ): + self.name = '' + self.namespace = '' + self.requestTopicType = '' + self.requestTopicName = '' + self.replyTopicType = '' + self.replyTopicName = '' + + def fqnVar( self, name ): + if name.find('::') < 0: + name = self.namespace + '::' + name + return name + def stripNamespace( self, str ): + return str.split('::')[-1] + + def getReplyTopicName( self ): + return 'LTS_' + self.stripNamespace( self.replyTopicName ).replace( '::', '_' ) + + def getReplyTopicNameType( self, withNamespace = True ): + var = self.replyTopicName + return self.fqnVar( var ) if withNamespace else self.stripNamespace( var ) + + def getRequestTopicName( self ): + return 'LTS_' + self.stripNamespace( self.requestTopicName ).replace( '::', '_' ) + + def getRequestTopicNameType( self, withNamespace = True ): + var = self.requestTopicName + return self.fqnVar( var ) if withNamespace else self.stripNamespace( var ) + + def getReplyTopicType( self ): + return self.fqnVar( self.replyTopicType ) + def getRequestTopicType( self ): + return self.fqnVar( self.requestTopicType ) + + def needReplyTopicTypedef( self ): + return self.getReplyTopicNameType() != self.getReplyTopicType() + def needRequestTopicTypedef( self ): + return self.getRequestTopicNameType() != self.getRequestTopicType() + +class GuiPluginRights(object): + def __init__( self ): + self.name = '' + self.offset = 0 + self.groups = [] +class GuiPluginRightsGroup(object): + def __init__( self ): + self.name = '' + self.desc = '' + self.values = [] + +class FileParser(object): + def __init__( self ): + self.filename = '' + self.service_abbr = '' + self.module_name = '' + self.struct_names = [] + self.ft_enums = [] + self.ft_replies = [] + self.ft_data = dict() + self.op_right_offset = 0 + self.op_rights = [] + + def build(self, path): + """ Parse the whole stuff ... """ + self.filename = path + self.read_file_xml(self.filename) + + def read_file_xml(self, filename): + """ Read the given xml service file""" + + """print >> sys.stdout, 'Reading xml file %s' % filename""" + xmlTree = ET.parse(filename) + root = xmlTree.getroot() + self.module_name = root.attrib['namespace'] + self.service_abbr = root.attrib['abbreviation'].lower() + + for child in root.iter('topicType'): + struct_name = child.attrib['name'] + if 'isDataObject' in child.attrib and int(child.attrib['isDataObject']) != 0: + self.struct_names.append(struct_name) + for child in root.iter('functionTopic'): + ft_name = child.attrib['name'] + self.ft_enums.append(ft_name) + + ft_data = FtData() + ft_data.name = ft_name + ft_data.namespace = self.module_name + ft_data.requestTopicType = child.attrib['requestTopicType'] + ft_data.requestTopicName = child.attrib['requestTopicName'] + ft_data.replyTopicType = child.attrib['replyTopicType'] + ft_data.replyTopicName = child.attrib['replyTopicName'] + self.ft_data[ft_name] = ft_data + + for child in root.iter('operatorRights'): + self.op_right_offset = int(child.attrib['offset']) + for pluginChild in child.iter('guiPlugin'): + r = GuiPluginRights() + r.name = pluginChild.attrib['name'] + r.offset = int(pluginChild.attrib['offset']) + r.groups = []; + for groups in pluginChild.iter('operatorRightGroup'): + g = GuiPluginRightsGroup(); + g.name = groups.attrib['name'] + g.desc = groups.attrib['description'] if 'description' in groups.attrib else '' + + for right in groups.iter('operatorRight'): + desc = right.attrib['description'] if 'description' in right.attrib else '' + g.values.append((right.attrib['right'], int(right.attrib['value']), desc)) + r.groups.append(g) + self.op_rights.append(r) + + +def outputHeader( outfile, parser, description, with_dds_include = True, with_pragma = True ): + outfile.write('\n') + outfile.write('/**\n') + outfile.write('* %s definitions\n' % description) + outfile.write('*/\n') + outfile.write('\n') + outfile.write('// warning: this file is generated with command\n') + outfile.write('// %s\n' % ( " ".join(sys.argv) )) + outfile.write('// at %s\n' % time.strftime( '%c' )) + outfile.write('// it will be overwritten by next run - do not modify!\n') + outfile.write('\n') + if with_pragma: + outfile.write('#pragma once\n') + outfile.write('\n') + if with_dds_include: + outfile.write('#include "%s.hpp"\n' % ( parser.service_abbr )) + +class DtsGenerator(object): + def generate( self, outfile, parser ): + outputHeader( outfile, parser, 'DTS' ) + outfile.write('\n') + outfile.write('/**\n') + outfile.write(' * Type definitions \n') + outfile.write(' * => DataWriterObjects (DWOs)\n') + outfile.write(' * => DataTopicServers (DTSs)\n') + outfile.write(' */\n') + outfile.write('\n') + outfile.write('namespace DDSGeneric\n') + outfile.write('{\n') + outfile.write('template \n') + outfile.write('class GenericDataWriter;\n') + outfile.write('}\n') + outfile.write('\n') + outfile.write('namespace DTS\n') + outfile.write('{\n') + outfile.write(' template class GenericDts;\n') + outfile.write(' template class GenericDtsImpl;\n') + outfile.write('\n') + + for struct_name in sorted(parser.struct_names): + + topic_name = "%s::%s" % ( parser.module_name, struct_name ) + dwo_name = "Dwo%s" % ( struct_name ) + outfile.write(' // Topic %s\n' % ( topic_name )) + outfile.write(' // Interface\n') + outfile.write(' using %s = GenericDts<%s>;\n' % ( struct_name, topic_name )) + outfile.write(' // Interface impl\n') + outfile.write(' using %s = DDSGeneric::GenericDataWriter<%s>;\n' % ( dwo_name, topic_name )) + outfile.write(' using %sImpl = GenericDtsImpl<%s>;\n' % ( struct_name, dwo_name )) + outfile.write('\n') + + outfile.write('} // namespace DTS\n') + outfile.write('\n') + +class DtcGenerator(object): + def generate( self, outfile, parser ): + outputHeader( outfile, parser, 'DTC' ) + outfile.write('\n') + outfile.write('/**\n') + outfile.write(' * Type definitions \n') + outfile.write(' * => DataTopicClients (DTCs)\n') + outfile.write(' */\n') + outfile.write('\n') + outfile.write('namespace DDSGeneric\n') + outfile.write('{\n') + outfile.write('template \n') + outfile.write('class GenericDataReader;\n') + outfile.write('template \n') + outfile.write('class GenericDataReaderContentFilter;\n') + outfile.write('class DynamicDataReader;\n') + outfile.write('}\n') + outfile.write('\n') + outfile.write('namespace DTC\n') + outfile.write('{\n') + outfile.write(' template class GenericDtc;\n') + outfile.write(' template class GenericDtcImpl;\n') + outfile.write(' template class GenericDtcSampleInfo;\n') + outfile.write(' template class GenericDtcImplSampleInfo;\n') + outfile.write('\n') + + for struct_name in sorted(parser.struct_names): + + topic_name = "%s::%s" % ( parser.module_name, struct_name ) + dro_name = "Dro%s" % ( struct_name ) + dro_name_cf = "Dro%sCF" % ( struct_name ) + + outfile.write(' // Topic %s\n' % ( topic_name )) + outfile.write(' using %s = DDSGeneric::GenericDataReader<%s>;\n' % ( dro_name, topic_name )) + outfile.write(' using %s = GenericDtc<%s>;\n' % ( struct_name, dro_name )) + outfile.write(' using %sImpl = GenericDtcImpl<%s>;\n' % ( struct_name, dro_name )) + + outfile.write(' using %s = DDSGeneric::GenericDataReaderContentFilter<%s>;\n' % ( dro_name_cf, topic_name )) + outfile.write(' using %sCF = GenericDtc<%s>;\n' % ( struct_name, dro_name_cf )) + outfile.write(' using %sCFImpl = GenericDtcImpl<%s>;\n' % ( struct_name, dro_name_cf )) + + outfile.write(' namespace SampleInfo {\n') + outfile.write(' using %s = GenericDtcSampleInfo<%s>;\n' % ( struct_name, dro_name )) + outfile.write(' using %sImpl = GenericDtcImplSampleInfo<%s>;\n' % ( struct_name, dro_name )) + outfile.write(' }\n') + outfile.write('\n') + + outfile.write('} // namespace DTC\n') + outfile.write('\n') + +def printFtTopicTypeNames( outfile, ft_data ): + outfile.write(' template<> struct TopicNames<%s>' % ft_data.name) + outfile.write(' {\n') + outfile.write(' static constexpr const char *functionTopicName() { return "%s"; }\n' % ft_data.name) + outfile.write(' static constexpr const char *replyTopicName() { return "%s"; }\n' % ft_data.getReplyTopicName()) + outfile.write(' static constexpr const char *replyTopicTypeName() { return "%s"; }\n' % ft_data.getReplyTopicNameType( True )) + outfile.write(' static constexpr const char *requestTopicName() { return "%s"; }\n' % ft_data.getRequestTopicName()) + outfile.write(' static constexpr const char *requestTopicTypeName() { return "%s"; }\n' % ft_data.getRequestTopicNameType( True )) + outfile.write(' };\n') + +def printFtTypedefs( outfile, ft_data ): + if ft_data.needRequestTopicTypedef(): + outfile.write(' using %s = %s;\n' % ( ft_data.getRequestTopicNameType( False ), ft_data.getRequestTopicType() )) + if ft_data.needReplyTopicTypedef(): + outfile.write(' using %s = %s;\n' % ( ft_data.getReplyTopicNameType( False ), ft_data.getReplyTopicType() )) + +class FtsGenerator(object): + def generate( self, outfile, parser): + outputHeader( outfile, parser, 'FTS' ) + outfile.write('\n') + outfile.write('namespace FTS\n') + outfile.write('{\n') + outfile.write('template class GenericFtsImpl;\n') + outfile.write('template struct TopicNames;\n') + outfile.write('}\n') + outfile.write('namespace %s\n' % ( parser.module_name )) + outfile.write('{\n') + outfile.write(' // TypeDefinitions for Replies that are not implemented => using Core::FunctionReply\n') + for ft_name in sorted(parser.ft_enums): + printFtTypedefs( outfile, parser.ft_data[ft_name] ) + + outfile.write('} // namespace %s\n' % ( parser.module_name )) + outfile.write('\n') + + outfile.write('\n') + outfile.write('namespace FTS\n') + outfile.write('{\n') + outfile.write(' // TypeDefinitions for GenericFtsImpl\n') + for ft_name in sorted(parser.ft_enums): + ft_data = parser.ft_data[ft_name] + + dummy = '%sTemplateHelperDummy' % ft_data.name; + outfile.write(' class %s;\n' % dummy) + outfile.write(' using %s = GenericFtsImpl<%s, %s, %s>;\n' % (ft_data.name, ft_data.getRequestTopicType(), ft_data.getReplyTopicNameType(), dummy)) + printFtTopicTypeNames( outfile, ft_data ) + outfile.write('\n') + + outfile.write('} // namespace FTS\n') + outfile.write('\n') + +class FtcGenerator(object): + def generate( self, outfile, parser ): + outputHeader( outfile, parser, 'FTC' ) + outfile.write('\n') + outfile.write('namespace FTC\n') + outfile.write('{\n') + outfile.write('template class GenericFtcImpl;\n') + outfile.write('template struct TopicNames;\n') + outfile.write('}\n') + outfile.write('namespace %s\n' % ( parser.module_name )) + outfile.write('{\n') + outfile.write(' // TypeDefinitions for Replies that are not implemented => using Core::FunctionReply\n') + for ft_name in sorted(parser.ft_enums): + printFtTypedefs( outfile, parser.ft_data[ft_name] ) + + outfile.write('} // namespace %s\n' % ( parser.module_name )) + outfile.write('\n') + + outfile.write('\n') + outfile.write('namespace FTC\n') + outfile.write('{\n') + outfile.write(' // TypeDefinitions for GenericFtcImpl\n') + for ft_name in sorted(parser.ft_enums): + ft_data = parser.ft_data[ft_name] + + dummy = '%sTemplateHelperDummy' % ft_data.name; + outfile.write(' class %s;\n' % dummy) + outfile.write(' using %s = GenericFtcImpl<%s, %s, %s>;\n' % (ft_data.name, ft_data.getRequestTopicType(), ft_data.getReplyTopicNameType(), dummy)) + printFtTopicTypeNames( outfile, ft_data ) + outfile.write('\n') + + outfile.write('} // namespace FTC\n') + outfile.write('\n') + +class OperatorRights(object): + def generate( self, outfile, parser ): + outputHeader( outfile, parser, 'Operator Rights', False ) + outfile.write('namespace %s \n' % parser.module_name) + outfile.write('{\n') + outfile.write(' static const int Offset = %s; \n' % parser.op_right_offset) + outfile.write('}\n\n') + + outfile.write('namespace OperatorRights\n') + outfile.write('{\n') + globalOffset = parser.op_right_offset << 24 + for r in parser.op_rights: + outfile.write('enum %s\n' % r.name) + outfile.write('{\n') + offsetPlugin = globalOffset + (r.offset << 16) + groupCount = -1 + for group in r.groups: + groupCount = groupCount + 1 + groupOffset = offsetPlugin + (groupCount << 8) + for k,v,d in group.values: + v += groupOffset + outfile.write(' %s = %s,\n' % (k, hex(v))) + outfile.write('};\n') + + outfile.write('\n') + outfile.write('enum Groups_%s\n' % parser.module_name) + outfile.write('{\n') + pattern = re.compile('[\W]+') + for r in parser.op_rights: + offsetPlugin = globalOffset + (r.offset << 16) + for group in r.groups: + groupOffset = offsetPlugin + (groupCount << 8) + name = pattern.sub('', group.name) + outfile.write(' Group%s = %s,\n' % (name, hex(groupOffset))) + outfile.write('};\n') + + outfile.write('\n') + outfile.write('enum Plugins_%s\n' % parser.module_name) + outfile.write('{\n') + for r in parser.op_rights: + offsetPlugin = globalOffset + (r.offset << 16) + name = pattern.sub('', r.name) + outfile.write(' Plugin%s = %s,\n' % (name, hex(offsetPlugin))) + outfile.write('};\n') + + outfile.write('} // namespace OperatorRights\n') + +class OperatorRightsCpp(object): + def generate( self, outfile, parser ): + outputHeader( outfile, parser, 'Operator Rights (source)', False, False ) + outfile.write('#include "%s_operator_rights.h"\n' % parser.service_abbr) + outfile.write('#include \n\n\n') + if len(parser.op_rights) == 0: + return + outfile.write('\nvoid doNothing()\n{\n const char * txt[] {\n') + + disamb = 'OperatorRights' + globalOffset = parser.op_right_offset << 24 + for r in parser.op_rights: + offsetPlugin = globalOffset + (r.offset << 16) + groupCount = -1 + for group in r.groups: + groupCount = groupCount + 1 + groupOffset = offsetPlugin + (groupCount << 8) + if group.name != '': + outfile.write(' QT_TRANSLATE_NOOP("%s", "%s"),\n' % (disamb, group.name)) + if group.desc != '': + outfile.write(' QT_TRANSLATE_NOOP("%s", "%s"),\n' % (disamb, group.desc)) + for aclName, aclId, aclDescr in group.values: + outfile.write(' QT_TRANSLATE_NOOP("%s", "%s"),\n' % (disamb, aclDescr)) + outfile.write(' };\n Q_UNUSED(txt);\n}\n') + +class OperatorRightsSql(object): + def generate( self, outfile, parser ): + outfile.write('BEGIN;\n') + globalOffset = parser.op_right_offset << 24 + if globalOffset > 0: + outfile.write('--- Offset: %s = %s\n' % (globalOffset, hex(globalOffset))) + + groupId = globalOffset; + + for r in parser.op_rights: + offsetPlugin = globalOffset + (r.offset << 16) + groupCount = -1 + for group in r.groups: + groupCount = groupCount + 1 + groupOffset = offsetPlugin + (groupCount << 8) + + outfile.write('\n--- %s - %s = %s\n' % (group.name, hex(groupOffset), groupOffset)) + str = "INSERT INTO opm.opm_group (id, name, description, type_id, is_hidden) VALUES (%s, '%s', '%s', 4, false);\n" + outfile.write(str % (groupOffset, group.name, group.desc)) + for aclName, aclId, aclDescr in group.values: + aclId += groupOffset + outfile.write('--- Base: %s, %s - %s = %s\n' % (groupOffset, aclName, aclId, hex(aclId))) + str = "INSERT INTO opm.opm_access_control_level (id, name, description) VALUES (%s, '%s', '%s');\n"; + outfile.write(str % (aclId, aclName, aclDescr)); + str = "INSERT INTO opm.opm_group (id, name, description, type_id, is_hidden) VALUES (%s, '%s', '%s', 1, true);\n"; + outfile.write(str % (groupId, aclName, aclDescr)); + str = "INSERT INTO opm.opm_acl_allow (group_id, acl_id) VALUES (%s, %s); -- group_id is: %s = %s\n"; + outfile.write(str % (groupId, aclId, groupId, hex(groupId))); + str = "INSERT INTO opm.opm_group_group (group_id, is_member_of_group_id) VALUES (%s, %s); -- %s\n" + outfile.write(str % (groupOffset, groupId, group.name)) + groupId = groupId + 1 + outfile.write('\n') + outfile.write('END;\n') + +def generate( parser, generator, out_filename ): + try: + outfile = open(out_filename, 'w') + except: + print("Could not open %s for writing" % out_filename, file=sys.stderr) + sys.exit(4) + generator.generate(outfile, parser) + outfile.close() + +def usage( error ): + print(error, file=sys.stderr) + print("Usage: %s DTS|DTC|FTS|FTC|SQL " % sys.argv[0], file=sys.stderr) + print(" %s ALL " % sys.argv[0], file=sys.stderr) + print("i.e.: %s DTS exm_service.xml DtsTypeDefinitions.h" % sys.argv[0], file=sys.stderr) + print("call: '%s'" % " ".join(sys.argv), file=sys.stderr) + sys.exit(1) + +def main(): + if len(sys.argv) != 4: + usage("Invalid argument count") + + option_str = sys.argv[1] + xml_filename = sys.argv[2] + + parser = FileParser() + parser.build(xml_filename) + + if option_str == "DTS": + generate(parser, DtsGenerator(), sys.argv[3]) + elif option_str == "DTC": + generate(parser, DtcGenerator(), sys.argv[3]) + elif option_str == "FTS": + generate(parser, FtsGenerator(), sys.argv[3]) + elif option_str == "FTC": + generate(parser, FtcGenerator(), sys.argv[3]) + elif option_str == "SQL": + out_dir = sys.argv[3] + generate(parser, OperatorRightsSql(), os.path.join(out_dir, parser.service_abbr + '_operator_rights.sql')) + elif option_str == "ALL": + out_dir = sys.argv[3] + fn = parser.service_abbr + '_%s_types.h' + generate(parser, DtsGenerator(), fn % 'dts') + generate(parser, DtcGenerator(), fn % 'dtc') + generate(parser, FtsGenerator(), fn % 'fts') + generate(parser, FtcGenerator(), fn % 'ftc') + generate(parser, OperatorRights(), parser.service_abbr + '_operator_rights.h') + generate(parser, OperatorRightsCpp(), parser.service_abbr + '_operator_rights.cpp') + else: + usage("invalid option '%s'" % option_str) + sys.exit(0) + +if __name__ == "__main__": + main() diff --git a/Lts/Common/CodeGen/ea_export_script.txt b/Lts/Common/CodeGen/ea_export_script.txt new file mode 100755 index 000000000..2700b3ec5 --- /dev/null +++ b/Lts/Common/CodeGen/ea_export_script.txt @@ -0,0 +1,1465 @@ +// +// see http://www.sparxsystems.com/enterprise_architect_user_guide/12.0/automation_and_scripting/reference.html +// +// $Id$ +// $HeadURL$ +// $Author$ +// +// -> tabs to spaces +// sed -i "s/\t/ /g" ea_export_script.txt +// -> remove trailing spaces +// sed -i "s/[ ]*$//g" ea_export_script.txt +// + +!INC Local Scripts.EAConstants-JScript +!INC EAScriptLib.JScript-Database +!INC EAScriptLib.JScript-XML +!INC EAScriptLib.JScript-TaggedValue + +function warnOut( msg ) +{ + Session.Output( "WARN: " + msg ); +} + +function infoOut( msg ) +{ + Session.Output( "INFO: " + msg ); +} + +function TVGetElementTaggedValueEx( theElement /* : EA.Element */, taggedValueName /* : String */, defaultValue /* : String */ ) /* : variant */ +{ + var result = defaultValue; + + if ( theElement != null && taggedValueName.length > 0 ) + { + for ( var j = 0; j < theElement.TaggedValuesEx.Count; j++ ) + { + var taggedValue as EA.TaggedValue; + taggedValue = theElement.TaggedValuesEx.GetAt(j); + if (taggedValue.Name == taggedValueName) + { + result = taggedValue.Value; + break; + } + } + } + + return result; +} + + +function getFrameworkPackage( element_ ) +{ + if ( element_ == null ) + return null; + var element as EA.Element; + element = element_; + if ( element.Stereotype == "framework" ) + return element; + + var elementId = element.PackageID; + if ( element.ObjectType == otPackage ) + { + var package as EA.Package; + package = element; + var packageElement as EA.Element; + packageElement = package.Element; + if ( packageElement == null ) + return null; + if ( packageElement.Stereotype == "framework" ) + return package; + elementId = element.ParentID; + } + //Session.Output( "Name: " + element.Name + " PackageID: " + elementId ); + element = Repository.GetPackageByID( elementId ); + + return getFrameworkPackage( element ) +} + +function getFrameworkElement( element_ ) +{ + var element as EA.Element; + element = element_; + if ( element.Stereotype == "framework" ) + return element; + var package as EA.Package; + package = getFrameworkPackage( element_ ); + if ( package == null ) + return null; + return package.Element; +} + +function getConnectorFromElement( _element, connectorType ) +{ + var element as EA.Element; + element = _element; + + if ( element == null ) + return null; + + for( var j = 0; j < element.Connectors.Count; j++ ) + { + var connector as EA.Connector; + connector = element.Connectors.GetAt( j ); + if( connector == null ) + continue; + if( connector.ClientID != element.ElementID ) + continue; + if( connector.Type != connectorType ) + continue; + return connector; + } + return null; +} + + +function getMethodFromElement( _element, methodString ) +{ + var element as EA.Element; + element = _element; + + if( element == null || methodString == null ) + return null; + + var methods as EA.Collection; + methods = element.MethodsEx; + + return getCollectionAttribute(methods, methodString); +} + +function getCollectionAttribute(_collections, methodName) +{ + var collections as EA.Collection; + + //Session.Output( "Find methodName:" + methodName ); + collections = _collections; + // method.GetByName() does not work on non-existing attributes + for( var i = 0; i < collections.Count; ++i ) + { + var method as EA.Method; + method = collections.GetAt( i ); + //Session.Output( "MethodName: " + method.Name ); + if( method && method.Name == methodName ) + return method; + } + return null; +} + +function getKeyColumnFromMethod( element, methodString ) +{ + var method as EA.Method; + method = getMethodFromElement( element, methodString ); + if( method == null ) + { + Session.Output( "Can not find PK/FK '" + methodString + "' in Table " + element.Name ); + return null; + } + if( method.Parameters.Count != 1 ) + return null; + + return method.Parameters.GetAt(0).Name; +} + +function getTaggedValueGroup( element, groupName ) +{ + var groupsTaggedValue = TVGetElementTaggedValue( element, groupName ); + if ( groupsTaggedValue != null ) + { + tmp1 = DBGetFieldValueString( "Notes", "t_propertytypes", "Property = \'" + groupName + "\' " ); + + var startIndex = tmp1.lastIndexOf( "=" ) + 1; + var endIndex = tmp1.length - 1; + + var groupNames = tmp1.substring(startIndex, endIndex).split( "," ); + + var groupVals = groupsTaggedValue.split( "," ); + var validGroups = ""; + for ( var i = 0; i < groupVals.length; i++ ) + { + if ( groupVals[i] == 1 ) + { + if ( validGroups.length > 0 ) + { + validGroups += ","; + } + validGroups += groupNames[i]; + } + } + if ( validGroups.length > 0 ) + { + return validGroups; + } + } + return null; +} + +function exportTaggedValueGroup( xml, xmlAttribName, element, groupName ) +{ + var validGroups = getTaggedValueGroup( element, groupName ); + if ( validGroups != null ) + { + //Session.Output('valid groups: ' + validGroups ); + xml.setAttribute( xmlAttribName, validGroups ); + } +} + +function exportFunctionDeclaration( xmlDoc, xmlParent, _method ) +{ + var method as EA.Method; + method = _method; + + var methodXml = xmlDoc.createNode( 1, method.Stereotype, '' ); + xmlParent.appendChild( methodXml ); + methodXml.setAttribute( 'name', method.Name ); + methodXml.setAttribute( 'returns', method.ReturnType ); + if (method.IsConst) + methodXml.setAttribute( 'isConst', 1 ); + for( var k = 0; k < method.Parameters.Count; k++ ) + { + var param as EA.Parameter; + param = method.Parameters.GetAt( k ); + var paramXml = xmlDoc.createNode( 1, 'parameter', '' ); + paramXml.setAttribute( 'pos', k + 1 ); + paramXml.setAttribute( 'name', param.Name ); + paramXml.setAttribute( 'type', param.Type ); + if (param.Default != "") + paramXml.setAttribute( 'default', param.Default ); + methodXml.appendChild( paramXml ); + } + return methodXml; +} + +function exportDbStructBase( xmlDoc, dbStructsXml, dbStructXml, connector, exportedTopicTypes ) +{ + // only inheritance + if( connector.Type != "Generalization" ) + return; + + var base as EA.Element; + base = GetElementByID( connector.SupplierID ); + if( base == null ) + { + Session.Output( "got unknown element id " + connector.SupplierID ); + return; + } + + // now generate the base='foo' - part for inheritance + dbStructXml.setAttribute( 'base', base.Name ); + + // check if the base struct is already exported + for( var i = 0; i < exportedTopicTypes.length; ++i ) + { + if( exportedTopicTypes[i] == connector.SupplierID ) + return; + } + exportedTopicTypes.push( connector.SupplierID ); + + // not yet exported -> export + var dbStructXml = xmlDoc.createNode( 1, 'table', '' ); + dbStructsXml.appendChild( dbStructXml ); + dbStructXml.setAttribute( 'name', base.Name ); + + var baseConnectors as EA.Collection; + baseConnectors = base.Connectors; + for( var j = 0; j < baseConnectors.Count; j++ ) + { + var connector as EA.Connector; + baseConnector = baseConnectors.GetAt( j ); + if( baseConnector == null ) + continue; + if( baseConnector.ClientID != base.ElementID ) + continue; + + // check if base of base is already exported + exportDbStructBase( xmlDoc, dbStructsXml, dbStructXml, baseConnector, exportedTopicTypes ); + } +} + +function exportDDSBase( xmlDoc, topicTypesXml, topicTypeXml, connector, exportedTopicTypes ) +{ + // only inheritance + if( connector == null || connector.Type != "Generalization" ) + return; + + var supplier as EA.Element; + supplier = GetElementByID( connector.SupplierID ); + if( supplier == null ) + { + Session.Output( "got unknown element id " + connector.SupplierID ); + return; + } + + var supplierPackage as EA.Package; + var clientPackage as EA.Package; + supplierPackage = getFrameworkElement( supplier ); + clientPackage = getFrameworkElement( GetElementByID( connector.ClientID ) ); + + // now generate the base='foo' - part for inheritance + if ( supplierPackage.Name != clientPackage.Name ) + { + topicTypeXml.setAttribute( 'baseValueType', supplierPackage.Name + "::" + supplier.Name ); + } else { + topicTypeXml.setAttribute( 'baseValueType', supplier.Name ); + } + + if ( supplier.Attributes.Count == 0 ) + { + // special case - an empty idl struct - modelled for consistency, + // but rtiddsgen does not like it and it's also useless in codebase + var grandparentConnector = getConnectorFromElement( supplier, "Generalization" ) + exportDDSBase( xmlDoc, topicTypesXml, topicTypeXml, grandparentConnector, exportedTopicTypes ) + } + exportedTopicTypes.push( connector.SupplierID ); +} + + + +function getDBServiceStructs( _package, xmlDoc, xmlRootNode ) +{ + var dbServiceStructsXml = xmlDoc.createNode( 1, 'dbServiceStructs', '' ); + xmlRootNode.appendChild( dbServiceStructsXml ); + + var package as EA.Package; + package = _package; + var elements as EA.Collection; + elements = package.Elements; + + var dbstructs = [] + + if( elements.Count == 0 ) { + infoOut("'Database Service Structs' is empty."); + } + for( var i = 0; i < elements.Count; i++ ) + { + var element as EA.Element; + element = elements.GetAt( i ); + if( element.Stereotype == "implementationClass" ) + { + var dbClassXml = xmlDoc.createNode( 1, 'serviceStruct', '' ); + dbServiceStructsXml.appendChild( dbClassXml ); + dbClassXml.setAttribute( 'name', element.Name ); + var connectors as EA.Collection; + connectors = element.Connectors; + + var correspondingTableFound = false; + for( var j = 0; j < connectors.Count; j++ ) + { + var connector as EA.Connector; + connector = connectors.GetAt( j ); + if( connector == null ) + continue; + + if( connector.Type != "Dependency" && connector.Type != "InformationFlow" ) + continue; + + var sourceId = (( connector.Type == "Dependency") ? connector.ClientID : connector.SupplierID ); + var destId = (( connector.Type == "Dependency") ? connector.SupplierID : connector.ClientID ); + + if( sourceId != element.ElementID ) + continue; + + var supplier as EA.Element; + supplier = GetElementByID( destId ); + if( supplier == null ) + { + Session.Output( "got unknown element id " + destId ); + continue; + } + + dbClassXml.setAttribute( 'table', supplier.Name ); + correspondingTableFound = true; + + dbstructs.push( destId ); + + if( connector.Type == "InformationFlow" ) + { + dbClassXml.setAttribute( 'static', 1 ); + } + } + if ( !correspondingTableFound ) + { + warnOut( "Found no corresponding table for Database Service Struct '" + element.Name + "'" ); + } + var methods as EA.Collection; + methods = element.MethodsEx; + for( var j = 0; j < methods.Count; j++ ) + { + var method as EA.Method; + method = methods.GetAt( j ); + if( method == null ) + continue; + if( method.Stereotype == 'function' || method.Stereotype == 'notification' ) + { + var methodXml = exportFunctionDeclaration( xmlDoc, dbClassXml, method ); + if( method.Stereotype == 'notification' ) + { + exportTaggedValueGroup( methodXml, 'notificationTypes', method, 'Legatus::DatabaseNotificationType' ); + } + } + } + } + } + + var exportedTopicTypes = []; + for( var i = 0; i < dbstructs.length; i++ ) + { + var element as EA.Element; + element = GetElementByID( dbstructs[i] ); + + if( element == null ) + continue; + + var dbStructXml = xmlDoc.createNode( 1, 'table', '' ); + dbServiceStructsXml.appendChild( dbStructXml ); + dbStructXml.setAttribute( 'name', element.Name ); + + exportedTopicTypes.push( element.ElementID ); + + var connectors as EA.Collection; + connectors = element.Connectors; + + for( var j = 0; j < connectors.Count; j++ ) + { + var connector as EA.Connector; + connector = connectors.GetAt( j ); + if( connector == null ) + continue; + if( connector.ClientID != element.ElementID ) + continue; + + exportDbStructBase( xmlDoc, dbServiceStructsXml, dbStructXml, connector, exportedTopicTypes ); + } + } +} + +function exportReferentialIntegrity( _methods, srcFk, xmlOut) +{ + var methods as EA.Collection; + methods = _methods; + + var method as EA.Method; + method = null; + for (m = 0; m < methods.Count(); ++m) + { + method = methods.GetAt(m); + if (method.Name == srcFk) + break; + method = null; + } + if (method == null) + return; + + for (var t = 0; t < method.TaggedValues.Count(); ++t) + { + var methodTag as EA.MethodTag; + methodTag = method.TaggedValues.GetAt(t); + if (methodTag.Name != 'property') + continue; + if (methodTag.Value == 'Delete Set Null=1;') + xmlOut.setAttribute( 'referential_integrity', 'DeleteSetNull') + else if (methodTag.Value == 'Delete Restrict=1;') + xmlOut.setAttribute( 'referential_integrity', 'DeleteRestrict') + else if (methodTag.Value == 'Delete Cascade=1;') + xmlOut.setAttribute( 'referential_integrity', 'DeleteCascade') + else if (methodTag.Value == 'Delete No Action=1;') + xmlOut.setAttribute( 'referential_integrity', 'DeleteNoAction') + else if (methodTag.Value == 'Delete Set Default=1;') + xmlOut.setAttribute( 'referential_integrity', 'DeleteSetDefault') + break; + } + +} +function getDBModel( _package, xmlDoc, xmlRootNode ) +{ + var tablesXml = xmlDoc.createNode( 1, 'tables', '' ); + xmlRootNode.appendChild( tablesXml ); + + var package as EA.Package; + package = _package; + var elements as EA.Collection; + elements = package.Elements; + + if( elements.Count == 0 ) { + infoOut("'Database Model' is empty."); + } + + for( var i = 0; i < elements.Count; i++ ) + { + var element as EA.Element; + element = elements.GetAt( i ); + if( element.Stereotype == "table" ) + { + var tableXml = xmlDoc.createNode( 1, 'table', '' ); + tablesXml.appendChild( tableXml ); + tableXml.setAttribute( 'name', element.Name ); + if( element.Notes != "" ) + { + tableXml.setAttribute( 'comment', element.Notes ); + } + exportTaggedValueGroup( tableXml, 'exerciseDependency', element, 'Legatus::ExerciseDependency' ); + + var attributes as EA.Collection; + attributes = element.Attributes; + for( var j = 0; j < attributes.Count; j++ ) + { + var attribute as EA.Attribute; + attribute = attributes.GetAt( j ); + if( attribute == null ) + continue; + + var columnXml = xmlDoc.createNode( 1, 'column', '' ); + tableXml.appendChild( columnXml ); + + columnXml.setAttribute( 'name', attribute.Name ); + columnXml.setAttribute( 'datatype', attribute.Type ); + + if( attribute.Default != "" ) + if (attribute.Default == "Optional" | attribute.Default == "optional") + columnXml.setAttribute( 'isOptional', 1 ); + else + columnXml.setAttribute( 'default', attribute.Default ); + if( attribute.IsOrdered ) + columnXml.setAttribute( 'isPrimaryKey', 1 ); + if( attribute.IsCollection ) + columnXml.setAttribute( 'isForeignKey', 1 ); + + var attrTag as EA.AttributeTag; + attrTag = attribute.TaggedValues.GetByName( "AutoNum" ); + if( attrTag != null && attrTag.Value == "True" ) + { + var sequenceXml = xmlDoc.createNode( 1, 'sequence', '' ); + columnXml.appendChild( sequenceXml ); + + attrTag = attribute.TaggedValues.GetByName( "StartNum" ); + sequenceXml.setAttribute( 'start', (( attrTag == null ) ? "1" : attrTag.Value ) ); + + attrTag = attribute.TaggedValues.GetByName( "Increment" ); + sequenceXml.setAttribute( 'increment', (( attrTag == null ) ? "1" : attrTag.Value ) ); + } + if ( attrTag == null && attribute.IsOrdered ) + { + warnOut( 'Column ' + element.Name + '.' + attribute.Name + ' is a primary key, but has no sequence (AutoNum) value set!') + } + + if( attribute.Notes != "" ) + { + columnXml.setAttribute( 'comment', attribute.Notes ); + } + } + + var methods as EA.Collection; + methods = element.MethodsEx; + for( var j = 0; j < methods.Count; j++ ) + { + var method as EA.Method; + method = methods.GetAt( j ); + if( method == null ) + continue; + if( method.Stereotype == 'index' || method.Stereotype == 'unique' ) + { + var methodXml = xmlDoc.createNode( 1, 'constraint', '' ); + tableXml.appendChild( methodXml ); + methodXml.setAttribute( 'name', method.Name ); + methodXml.setAttribute( 'type', method.Stereotype ); + for( var k = 0; k < method.Parameters.Count; k++ ) + { + var param as EA.Parameter; + param = method.Parameters.GetAt( k ); + var paramXml = xmlDoc.createNode( 1, 'column', '' ); + paramXml.setAttribute( 'name', param.Name ); + methodXml.appendChild( paramXml ); + } + } + } + + var connectors as EA.Collection; + connectors = element.Connectors; + for( var j = 0; j < connectors.Count; j++ ) + { + var connector as EA.Connector; + connector = connectors.GetAt( j ); + if( connector == null ) + continue; + if( connector.ClientID != element.ElementID ) + continue; + if( connector.Direction != "Source -> Destination" ) + continue; + if( connector.Type != "Association" ) + continue; + + var supplier as EA.Element; + supplier = GetElementByID( connector.SupplierID ); + if( supplier == null ) + { + Session.Output( "got unknown element id " + connector.SupplierID ); + continue; + } + + // FKINFO=SRC=FK_pdm_equipment_system_event_filter_pdm_hgr_mine_search_filter_type:DST=PK_pdm_hgr_mine_search_filter_type:; + s = connector.StyleEx; + srcFk = s.split( "SRC=" )[1].split(":")[0]; + dstFk = s.split( "DST=" )[1].split(":")[0]; + // Session.Output("srcFk: " + srcFk); + // Session.Output("dstFk: " + dstFk); + + var foreignKeyXml = xmlDoc.createNode( 1, 'foreignKey', '' ); + tableXml.appendChild( foreignKeyXml ); + foreignKeyXml.setAttribute( 'name', srcFk ) + + // Default: onDeleteCascade = true + exportReferentialIntegrity(methods, srcFk, foreignKeyXml); + + var sourceInfo = xmlDoc.createNode( 1, 'source', '' ) + //Session.Output( "Source: " + sourceInfo ); + foreignKeyXml.appendChild( sourceInfo ); + sourceInfo.setAttribute( 'table', element.Name ) + //Session.Output( "table: " + element.Name ); + sourceInfo.setAttribute( 'column', getKeyColumnFromMethod(element, srcFk) ) + + var destInfo = xmlDoc.createNode( 1, 'destination', '' ) + foreignKeyXml.appendChild( destInfo ); + exportExternalFk(xmlDoc, destInfo, element, supplier); + destInfo.setAttribute( 'table', supplier.Name ) + destInfo.setAttribute( 'column', getKeyColumnFromMethod(supplier, dstFk) ) + //Session.Output( "Destination: " + destInfo ); + //Session.Output( "table: " + supplier.Name ); + + } + } + } +} + +function getDBViews( _package, xmlDoc, xmlRootNode ) +{ + var viewsXml = xmlDoc.createNode( 1, 'views', '' ); + xmlRootNode.appendChild( viewsXml ); + + var package as EA.Package; + package = _package; + var elements as EA.Collection; + elements = package.Elements; + + for( var i = 0; i < elements.Count; i++ ) + { + var element as EA.Element; + element = elements.GetAt( i ); + if( element.Stereotype == "view" ) + { + var viewXml = xmlDoc.createNode( 1, 'view', '' ); + viewsXml.appendChild( viewXml ); + viewXml.setAttribute( 'name', element.Name ); + if( element.Notes != "" ) + { + viewXml.setAttribute( 'comment', element.Notes ); + } + exportTaggedValueGroup( viewXml, 'exerciseDependency', element, 'Legatus::ExerciseDependency' ); + + var attributes as EA.Collection; + attributes = element.Attributes; + for( var j = 0; j < attributes.Count; j++ ) + { + var attribute as EA.Attribute; + attribute = attributes.GetAt( j ); + if( attribute == null ) + continue; + if( attribute.Stereotype != "filter") + continue; + viewXml.setAttribute( "filter", attribute.Name ) + } + + var connectors as EA.Collection; + connectors = element.Connectors; + for( var j = 0; j < connectors.Count; j++ ) + { + var connector as EA.Connector; + connector = connectors.GetAt( j ); + if( connector == null ) + continue; + if( connector.ClientID != element.ElementID ) + continue; + if( connector.Direction != "Source -> Destination" ) + continue; + if( connector.Type != "Dependency" ) + continue; + + var supplier as EA.Element; + supplier = GetElementByID( connector.SupplierID ); + if( supplier == null ) + { + Session.Output( "got unknown element id " + connector.SupplierID ); + continue; + } + viewXml.setAttribute( "base", supplier.Name ) + } + } + } +} + +function getEnumerations( _package, xmlDoc, xmlRootNode ) +{ + var rootXml = xmlDoc.createNode( 1, 'enumerations', '' ); + xmlRootNode.appendChild( rootXml ); + + var package as EA.Package; + package = _package; + var elements as EA.Collection; + elements = package.Elements; + + if( elements.Count == 0 ) { + warnOut("'Enumerations' is empty. At least one placeholder enum is needed for generating C++ code."); + } + + for( var i = 0; i < elements.Count; i++ ) + { + var element as EA.Element; + element = elements.GetAt( i ); + //Session.Output("Enum element: " + element.Name); + if( element.Stereotype == "enumeration" ) + { + var elementXml = xmlDoc.createNode( 1, 'enumeration', '' ); + rootXml.appendChild( elementXml ); + elementXml.setAttribute( 'name', element.Name ); + + var attributes as EA.Collection; + attributes = element.AttributesEx; + for( var j = 0; j < attributes.Count; j++ ) + { + var attribute as EA.Attribute; + attribute = attributes.GetAt( j ); + if( attribute == null ) + continue; + + var attr = xmlDoc.createNode( 1, 'attribute', '' ); + elementXml.appendChild( attr ); + + attr.setAttribute( 'key', attribute.Name ); + attr.setAttribute( 'value', attribute.Default ); + } + + var tableCount = 0; + var connectors as EA.Collection; + connectors = element.Connectors; + for( var j = 0; j < connectors.Count; j++ ) + { + var connector as EA.Connector; + connector = connectors.GetAt( j ); + if( connector == null ) + continue; + if( connector.ClientID != element.ElementID ) + continue; + if( connector.Direction != "Source -> Destination" ) + continue; + if( connector.Type != "Dependency" ) + continue; + var supplier as EA.Element; + supplier = GetElementByID( connector.SupplierID ); + if( supplier == null ) + { + Session.Output( "got unknown element id " + connector.SupplierID ); + continue; + } + if( supplier.Stereotype != "table" ) + continue; + + elementXml.setAttribute( 'table', supplier.Name ); + tableCount += 1; + } + if ( tableCount < 1 ) + { + Session.Output( "Got no information about associated database table for enumeration " + element.Name + " - not creating database entries for this enum" ); + } + else if ( tableCount > 1 ) + { + Session.Output( "Got more than one associated database table for enumeration " + element.Name + "!" ); + } + } + } +} + +function exportDtcCustomFunctions( xmlDoc, xmlParent, _element ) +{ + var element as EA.Element; + element = _element; + + var dtcFunctionsXml = xmlDoc.createNode( 1, 'customDtcFunctions', '' ); + xmlParent.appendChild( dtcFunctionsXml ); + + var dtcFunctionXml = xmlDoc.createNode( 1, 'customDtcFunction', '' ); + dtcFunctionsXml.appendChild( dtcFunctionXml ); + + var methods as EA.Collection; + methods = element.MethodsEx; + for( var j = 0; j < methods.Count; j++ ) + { + var method as EA.Method; + method = methods.GetAt( j ); + if( method == null ) + continue; + if( method.Stereotype == 'function' ) + { + // export DTC functions + exportFunctionDeclaration( xmlDoc, dtcFunctionXml, method ); + } else { + warnOut('Found method ' + method.Name + ' with unhandled Sterotype "' + method.Stereotype + '"'); + } + } +} + +function exportIdlFunctionTopic( xmlDoc, functionTopicsXml, _element ) +{ + var element as EA.Element; + element = _element; + + // we need two connectors: + // - Reply ( FunctionTopic -> idlStruct ) + // - Request ( idlStruct -> FunctionTopic ) + var replyConnector as EA.Connector; + var requestConnector as EA.Connector; + var replyElement as EA.Element; + var requestElement as EA.Element; + + for( var j = 0; j < element.Connectors.Count; j++ ) + { + var connector as EA.Connector; + connector = element.Connectors.GetAt( j ); + if( connector == null ) + continue; + if( connector.Type != "Dependency" ) + continue; + // Reply ( FunctionTopic -> idlStruct ) + if( connector.ClientID == element.ElementID ) + { + replyConnector = connector; + replyElement = GetElementByID( connector.SupplierID ) + } + else if( connector.SupplierID == element.ElementID ) + { + requestConnector = connector; + requestElement = GetElementByID( connector.ClientID ) + } + } + + if ( requestElement == null ) + { + Session.Output( "Could not find request TopicType for FunctionTopic " + element.Name ); + return; + } + if ( requestElement == null ) + { + Session.Output( "Could not find reply TopicType for FunctionTopic " + element.Name ); + return; + } + // Session.Output( "RequestTopicType: " + requestElement.Name + ", ReplyTopicType: " + replyElement.Name ); + var functionTopicXml = xmlDoc.createNode( 1, 'functionTopic', '' ); + functionTopicsXml.appendChild( functionTopicXml ); + functionTopicXml.setAttribute( 'name', element.Name ); + functionTopicXml.setAttribute( 'requestTopicType', requestElement.Name ); + functionTopicXml.setAttribute( 'replyTopicType', replyElement.Name ); + functionTopicXml.setAttribute( 'requestTopicName', element.Name + "Request" ); + functionTopicXml.setAttribute( 'replyTopicName', element.Name + "Reply" ); + // def value = False + var routeInReplay = TVGetElementTaggedValue(element, "Legatus::RouteTopicInReplay"); + functionTopicXml.setAttribute( 'routeInReplay', routeInReplay == 'true' ? 1 : 0 ); + + var replyPackage as EA.Package; + var requestPackage as EA.Package; + var clientPackage as EA.Package; + requestPackage = getFrameworkElement( GetElementByID( requestConnector.ClientID ) ); + replyPackage = getFrameworkElement( GetElementByID( replyConnector.SupplierID ) ); + currentPackage = getFrameworkElement( element ); + + if ( requestPackage.Name != currentPackage.Name ) + { + functionTopicXml.setAttribute( 'requestTopicType', requestPackage.Name + "::" + requestElement.Name ); + } + if ( replyPackage.Name != currentPackage.Name ) + { + functionTopicXml.setAttribute( 'replyTopicType', replyPackage.Name + "::" + replyElement.Name ); + } + +} + +function exportExternalFk( xmlDoc, fkXml, source, target ) +{ + var sourcePackage as EA.Package; + sourcePackage = getFrameworkElement( source ); + + var targetPackage as EA.Package; + targetPackage = getFrameworkElement( target ); + + if (targetPackage.Name != sourcePackage.Name) + { + fkXml.setAttribute( 'service', targetPackage.Name ); + fkXml.setAttribute( 'serviceAbbr', targetPackage.Alias.toLowerCase() ); + } + + //Session.Output("sourcePackage: " + sourcePackage.Alias); + //Session.Output("targetPackage: " + targetPackage.Alias); + + // try to get out the exercise/global dependency state + var edTgtTable = getTaggedValueGroup( target, "Legatus::ExerciseDependency" ); + var edSrcTable = getTaggedValueGroup( source, "Legatus::ExerciseDependency" ); + var edTgtPackage = getTaggedValueGroup( targetPackage, "Legatus::ExerciseDependency" ); + var edSrcPackage = getTaggedValueGroup( sourcePackage, "Legatus::ExerciseDependency" ); + // the four properties above can be inconsistent - we assume the best ... + + var tgtFqnName = targetPackage.Name + '.' + target.Name; + var srcFqnName = sourcePackage.Name + '.' + source.Name; + // table always wins, if availble + if (edTgtTable && edTgtTable != '') + edTgtPackage = edTgtTable; + if (edSrcTable && edSrcTable != '') + edSrcPackage = edSrcTable; + var isTgtExerciseDependent = (edTgtPackage.indexOf('Exercise') >= 0); + var isTgtGlobal = (edTgtPackage.indexOf('Global') >= 0); + var isSrcExerciseDependent = (edSrcPackage.indexOf('Exercise') >= 0); + var isSrcGlobal = (edSrcPackage.indexOf('Global') >= 0); + // since global and exercise can both be active we can get into trouble here... + if ( !isTgtExerciseDependent && !isTgtGlobal) + { + warnOut('Legatus::ExerciseDependency property for ' + tgtFqnName + + ' is neither set for Framework nor Table - this is not allowed!'); + return; + } + if ( !isSrcExerciseDependent && !isSrcGlobal) + { + warnOut('Legatus::ExerciseDependency property for ' + srcFqnName + + ' is neither set for Framework nor Table - this is not allowed!'); + return; + } + //Session.Output('Src: ' + srcFqnName + ' Global: ' + isSrcGlobal + ', Exercise: ' + isSrcExerciseDependent); + //Session.Output('Tgt: ' + tgtFqnName + ' Global: ' + isTgtGlobal + ', Exercise: ' + isTgtExerciseDependent); + // both are exercise dependent - do not export schema (so sqlgenerator generates fk to local table) + if (isTgtExerciseDependent && isSrcExerciseDependent) + return; + // both are global - do not export schema (so sqlgenerator generates fk to local table) + // but source must not be exerciseDependent because then we need the fqn + // when src is exercise and tgt is global + if (isTgtGlobal && isSrcGlobal && !isSrcExerciseDependent) + { + if (targetPackage.Name != sourcePackage.Name) + fkXml.setAttribute( 'schema', targetPackage.Alias.toLowerCase() ); + return; + } + if (isSrcGlobal && isTgtExerciseDependent) + { + warnOut('Legatus::ExerciseDependency property for ' + srcFqnName + + ' is global but destination (' + tgtFqnName + + ' is exercise dependent - this is not allowed / impossible'); + return; + } + fkXml.setAttribute( 'schema', targetPackage.Alias.toLowerCase() ); +} + +function getDDSModel( _package, xmlDoc, xmlRootNode ) +{ + var topicTypesXml = xmlDoc.createNode( 1, 'topicTypes', '' ); + xmlRootNode.appendChild( topicTypesXml ); + var functionTopicsXml = xmlDoc.createNode( 1, 'functionTopics', '' ); + xmlRootNode.appendChild( functionTopicsXml ); + + var package as EA.Package; + package = _package; + var basePackage as EA.Package; + basePackage = getFrameworkElement( package ); + var exportedTopicTypes = []; + + if( package.Elements.Count == 0 && package.Packages.Count == 0 ) { + infoOut("'DDS Model' is empty."); + } + + exportedTopicTypes = exportDDSModelPackage(package, + basePackage, + xmlDoc, + xmlRootNode, + topicTypesXml, + functionTopicsXml, + exportedTopicTypes ); +} + +function exportDDSModelPackage( _package, basePackage, xmlDoc, xmlRootNode, topicTypesXml, functionTopicsXml, exportedTopicTypes ) +{ + var package as EA.Package; + package = _package; + + // export all elements in this package + var elements as EA.Collection; + elements = package.Elements; + for( var i = 0; i < elements.Count; i++ ) + { + var element as EA.Element; + element = elements.GetAt( i ); + + //Session.Output("element.Stereotype = " + element.Stereotype + " Name = " + element.Name ); + if ( element.Stereotype == 'implementationClass' && element.Name == 'DTC') + { + exportDtcCustomFunctions( xmlDoc, xmlRootNode, element ); + } + else if ( element.Stereotype == 'FunctionTopic' ) + { + exportIdlFunctionTopic( xmlDoc, functionTopicsXml, element ); + } + else if ( element.Stereotype == 'idlStruct' || element.Stereotype == 'valueType' ) + { + // do not export empty structs (see exportDDSBase() ) + if ( element.Attributes.Count == 0 ) + { + infoOut('Not exporting dds struct ' + element.Name + ' - no attributes (not allowed by rtiddsgen)'); + continue; + } + + if (element.Stereotype == 'valueType') + { + warnOut('Stereotype "valueType" is deprecated, please replace with "idlStruct" in struct ' + element.Name); + } + + var topicTypeXml = xmlDoc.createNode( 1, 'topicType', '' ); + topicTypesXml.appendChild( topicTypeXml ); + topicTypeXml.setAttribute( 'name', element.Name ); + + var ddsExtensibility = TVGetElementTaggedValue(element, "IDL::Extensibility"); + if ( ddsExtensibility ) + topicTypeXml.setAttribute( 'extensibility', ddsExtensibility); + + var ddsIsDataObject = TVGetElementTaggedValueEx(element, "IDL::IsDataObject") == 'true'; + if ( ddsIsDataObject ) { + topicTypeXml.setAttribute( 'isDataObject', 1 ); + + // def value = True + var autosubscribe = TVGetElementTaggedValueEx(element, "Legatus::AutoSubscribe"); + topicTypeXml.setAttribute( 'autosubscribe', autosubscribe == 'false' ? 0 : 1 ); + + // def value = True + var routeToClient = TVGetElementTaggedValueEx(element, "Legatus::RouteTopicToClient"); + topicTypeXml.setAttribute( 'routeToClient', routeToClient == 'false' ? 0 : 1 ); + + // def value = False + var routeToServer = TVGetElementTaggedValueEx(element, "Legatus::RouteTopicToServer"); + topicTypeXml.setAttribute( 'routeToServer', routeToServer == 'true' ? 1 : 0 ); + + // def value = False + var routeInReplay = TVGetElementTaggedValueEx(element, "Legatus::RouteTopicInReplay"); + topicTypeXml.setAttribute( 'routeInReplay', routeInReplay == 'true' ? 1 : 0 ); + } + + var dtsAutogen = TVGetElementTaggedValue(element, "Legatus::AutogenDTS"); + if ( dtsAutogen == 'false' || dtsAutogen == 0 ) + topicTypeXml.setAttribute( 'autogenDTS', 0 ); + + var dtcAutogen = TVGetElementTaggedValue(element, "Legatus::AutogenDTC"); + if ( dtcAutogen == 'false' || dtcAutogen == 0 ) + topicTypeXml.setAttribute( 'autogenDTC', 0 ); + + // Attributes + var attributes as EA.Collection; + attributes = element.AttributesEx; // we need to investigate all, but only export our own + for( var j = 0; j < attributes.Count; j++ ) + { + var attribute as EA.Attribute; + attribute = attributes.GetAt( j ); + if( attribute == null ) + continue; + + if( ddsIsDataObject && attribute.Name == 'sampleTime' && attribute.Notes != 'no_record' ) + topicTypeXml.setAttribute( 'recordTopic', 1 ); + // only export our own attributes - isDerived does not work as expected + if( attribute.ParentID != element.ElementID ) + continue; + + var columnXml = xmlDoc.createNode( 1, 'column', '' ); + topicTypeXml.appendChild( columnXml ); + + columnXml.setAttribute( 'name', attribute.Name ); + columnXml.setAttribute( 'idlType', attribute.Type ); + //infoOut("attribute.Name: " + element.Name + ":" + attribute.Name); + if ( attribute.ClassifierID != 0 ) + { + var attributeTypePackage as EA.Package; + attributeTypePackage = getFrameworkElement( GetElementByID( attribute.ClassifierID ) ); + if ( attributeTypePackage != null && attributeTypePackage.Name != basePackage.Name ) + { + columnXml.setAttribute( 'idlType', attributeTypePackage.Name + "::" + attribute.Type ); + } + } + + //Session.Output("StereoType:" + attribute.Stereotype); + if( attribute.Stereotype == "key" ) + columnXml.setAttribute( 'isKey', 1 ); + if( attribute.Stereotype == "Optional" | attribute.Stereotype == "optional" ) + columnXml.setAttribute( 'isOptional', 1 ); + if ( attribute.Stereotype == "translatable" ) + columnXml.setAttribute( 'isTranslatable', 1); + + if( attribute.Notes != "" ) + columnXml.setAttribute( 'comment', attribute.Notes ); + } + + var supplierCon = getConnectorFromElement( element, "Generalization" ) + exportDDSBase( xmlDoc, topicTypesXml, topicTypeXml, supplierCon, exportedTopicTypes ); + } + } + // now handle subpackages + var packages as EA.Collection; + packages = package.Packages; + for( var i = 0; i < packages.Count; ++i ) + { + exportedTopicTypes = exportDDSModelPackage(packages.GetAt(i), + basePackage, + xmlDoc, + xmlRootNode, + topicTypesXml, + functionTopicsXml, + exportedTopicTypes ); + } + return exportedTopicTypes +} + +function ExportServiceInformation( _package, xmlDoc, xmlRootNode ) +{ + var servicePackage as EA.Package; + var serviceCollection as EA.Collection; + var hasDbServiceStructs = false; + var hasDataBaseModel = false; + var hasEnumerations = false; + var hasDdsModel = false; + + servicePackage = _package; + serviceCollection = servicePackage.Packages; + for( var i = 0; i < serviceCollection.Count; i++ ) + { + var obj = serviceCollection.GetAt( i ); + if( obj.ObjectType == otPackage ) + { + var package as EA.Package + package = obj + if( package.Name == "Database Service Structs") { + infoOut('Handling Database Service Structs ...'); + getDBServiceStructs( package, xmlDoc, xmlRootNode ) + hasDbServiceStructs = true; + infoOut('... ready'); + } else + if( package.Name == "Database Model") { + infoOut('Handling Database Model ...'); + getDBModel( package, xmlDoc, xmlRootNode ) + getDBViews( package, xmlDoc, xmlRootNode ) + hasDataBaseModel = true; + infoOut('... ready'); + } else + if( package.Name == "Enumerations") { + infoOut('Handling Enumerations ...'); + getEnumerations( package, xmlDoc, xmlRootNode ) + hasEnumerations = true; + infoOut('... ready'); + } else + if( package.Name == "DDS Model") { + infoOut('Handling DDS Model ...'); + getDDSModel( package, xmlDoc, xmlRootNode) + hasDdsModel = true; + infoOut('... ready'); + } + } + } + + if( !hasDbServiceStructs) { + warnOut("'Database Service Structs' not found!"); + } + if( !hasDataBaseModel) { + warnOut("'Database Model' not found!"); + } + if( !hasEnumerations) { + warnOut("'Enumerations' not found!"); + } + if( !hasDdsModel) { + warnOut("'DDS Model' not found!"); + } +} + +function ExportOperatorRights( _frameworkElement, _package, xmlDoc, xmlRootNode ) +{ + infoOut('Handling Operator Rights ...'); + var frameworkElement as EA.Element; + frameworkElement = _frameworkElement; + var package as EA.Package; // 'Plugin' + package = _package; + + var svcRightsOfs = TVGetElementTaggedValue( frameworkElement, "Legatus::OperatorRightsServiceOffset" ); + if( svcRightsOfs == null || svcRightsOfs == "" ) + { + warnOut( 'Legatus::OperatorRightsServiceOffset is not defined for framework ' + frameworkElement.Name ); + return; + } + var rightsRootNode = xmlDoc.createNode( 1, 'operatorRights', '' ); + xmlRootNode.appendChild( rightsRootNode ); + rightsRootNode.setAttribute( 'offset', svcRightsOfs); + if (!package) + return; + + var pluginPackages as EA.Collection; + pluginPackages = package.Packages; + for( var i = 0; i < pluginPackages.Count; i++ ) + { + var pluginPackage as EA.Package; + pluginPackage = pluginPackages.GetAt( i ); + var plgRightsOfs = TVGetElementTaggedValue( pluginPackage.Element, "Legatus::OperatorRightsGuiPluginOffset" ); + if( plgRightsOfs == null || plgRightsOfs == "" ) + { + warnOut( 'Legatus::OperatorRightsGuiPluginOffset is not defined for Gui Plugin1 ' + pluginPackage.Name ); + continue; + } + var pluginRightsNode = xmlDoc.createNode( 1, 'guiPlugin', '' ); + rightsRootNode.appendChild( pluginRightsNode ); + pluginRightsNode.setAttribute( 'name', pluginPackage.Name ); + pluginRightsNode.setAttribute( 'offset', plgRightsOfs ); + + //warnOut('name: ' + pluginPackage.Name) + var elements = pluginPackage.Elements; + for (var j = 0; j < elements.Count; j++) + { + var element as EA.Element; + var element = elements.GetAt( j ); + if (element.Stereotype != 'enumeration') + continue; + + var attributes as EA.Collection; + attributes = element.Attributes; + + if (attributes.Count == 0) + continue; + + var group = xmlDoc.createNode( 1, 'operatorRightGroup', '' ); + pluginRightsNode.appendChild( group ); + group.setAttribute( 'name', element.Name ) + if( element.Notes != "" ) + group.setAttribute( 'description', element.Notes ) + + + for (var k = 0; k < attributes.Count; k++) + { + var attribute as EA.Attribute; + attribute = attributes.GetAt( k ); + + var attributeNode = xmlDoc.createNode( 1, 'operatorRight', '' ); + group.appendChild( attributeNode ); + attributeNode.setAttribute( 'right', attribute.Name ); + attributeNode.setAttribute( 'value', attribute.Default ); + if( attribute.Notes != "" ) + attributeNode.setAttribute( 'description', attribute.Notes ); + } + } + } +} + +function DoSomething() +{ + // Show the script output window + Repository.EnsureOutputVisible( "Script" ); + + // Get the set of selected items as a collection + var selPkg = Repository.GetTreeSelectedPackage(); + + var frameworkPackage as EA.Package; + frameworkPackage = getFrameworkPackage( selPkg ); + if( frameworkPackage != null ) + { + Session.Output( "Export Service information data for " + frameworkPackage.Name ); + Session.Output( "===============================" ); + var xmlDoc = new ActiveXObject( 'Msxml2.DOMDocument' ); + var xmlRootNode = xmlDoc.createNode( 1, 'service', '' ); + xmlDoc.appendChild( xmlRootNode ); + var xmlSvnNode = xmlDoc.createNode( 1, 'rcs', '' ); + xmlRootNode.appendChild( xmlSvnNode ); + var xmlSvnNodeId = xmlDoc.createNode( 1, 'Id', '' ); + xmlSvnNode.appendChild( xmlSvnNodeId ); + xmlSvnNodeId.setAttribute( 'Id', '$Id$' ) + var xmlSvnNodeUrl = xmlDoc.createNode( 1, 'HeadURL', '' ); + xmlSvnNode.appendChild( xmlSvnNodeUrl ); + xmlSvnNodeUrl.setAttribute( 'HeadURL', '$HeadURL$' ) + var xmlSvnNodeAuthor = xmlDoc.createNode( 1, 'Author', '' ); + xmlSvnNode.appendChild( xmlSvnNodeAuthor ); + xmlSvnNodeAuthor.setAttribute( 'Author', '$Author$' ) + + var frameworkElement as EA.Element; + frameworkElement = frameworkPackage.Element; + + var frameworkCollection as EA.Collection; + frameworkCollection = frameworkPackage.Packages; + + // now search 'Service' Package inside frameworkCollection + var opRightsExported = false; + for( var i = 0; i < frameworkCollection.Count; i++ ) + { + var obj = frameworkCollection.GetAt( i ); + if( obj.ObjectType == otPackage ) + { + var package as EA.Package; + package = obj; + if( package.Name == "Service" || package.Name == "Proxy" ) { + ExportServiceInformation( package, xmlDoc, xmlRootNode ); + } + if( package.Name == "OperatorRights" ) { + ExportOperatorRights( frameworkElement, package, xmlDoc, xmlRootNode ); + opRightsExported = true; + } + } + } + + if (!opRightsExported) { + ExportOperatorRights( frameworkElement, null, xmlDoc, xmlRootNode ); + } + + xmlRootNode.setAttribute( 'namespace', frameworkPackage.Name ); + xmlRootNode.setAttribute( 'abbreviation', frameworkPackage.Alias.toUpperCase() ); + var procNames = TVGetElementTaggedValue( frameworkElement, "Legatus::ProcNames" ); + if( procNames == null || procNames == "" ) + procNames = frameworkPackage.Name; + xmlRootNode.setAttribute( 'procNames', procNames ); + var customRecordReplay = TVGetElementTaggedValue( frameworkElement, "Legatus::CustomRecordReplayPlugin" ); + if( customRecordReplay == 'true' ) + xmlRootNode.setAttribute( 'customRecordReplay', 1 ); + + exportTaggedValueGroup( xmlRootNode, 'exerciseDependency', frameworkElement, 'Legatus::ExerciseDependency' ); + exportTaggedValueGroup( xmlRootNode, 'groups', frameworkElement, 'Legatus::Groups' ); + + var writer = new ActiveXObject( "MSXML2.MXXMLWriter" ); + writer.indent = true; + writer.encoding = "UTF-8"; // for files only + writer.omitXMLDeclaration = false; + writer.standalone = true; + var reader = new ActiveXObject( "MSXML2.SAXXMLReader" ); + reader.contentHandler = writer; + reader.parse(xmlDoc); + + var fso = new ActiveXObject("Scripting.FileSystemObject"); + var outputFile = "C:\\TDS\\" + frameworkElement.Alias.toLowerCase() + "_service.xml"; + Session.Output("OutputFile: " + outputFile ); + var textStream = fso.CreateTextFile(outputFile, true); + // Writer always uses utf-16 as output ecoding - whyever + // also replace tab with 2 spaces + textStream.Write(writer.output.replace("encoding=\"UTF-16\"", "encoding=\"UTF-8\"").replace(/\t/g, " ")); + textStream.Write("\n"); + textStream.Close(); + + //Session.Prompt( "Wrote framework '" + frameworkPackage.Name + "' to " + outputFile , promptOk ); + } else { + Session.Prompt( "This script requires the current context item to be a framework package.\n" + + "Please select a framework package and try again.", promptOK ); + } +} + +DoSomething(); + + +function getPackageByName( element_, requestedName ) +{ + var element as EA.Element; + element = element_; + if ( element.Name == requestedName ) + return element; + + var elementId = element.PackageID; + if ( element.ObjectType == otPackage ) + { + var package as EA.Package; + package = element; + var packageElement as EA.Element; + packageElement = package.Element; + if ( packageElement == null ) + return null; + if ( packageElement.Name == requestedName ) + return package; + elementId = element.ParentID; + } + element = Repository.GetPackageByID( elementId ); + + return getPackageByName( element, requestedName ) +} + +function isFrameworkPackage( element ) +{ + if( element.ObjectType != otPackage ) + return false; + var package as EA.Package; + package = element; + return ( package.StereotypeEx == 'framework' ) +} + +function getOperatorRightsEnum( element ) +{ + var package as EA.Package; + package = element; + var elements = package.Elements; + + for (var i = 0; i < elements.Count; i++ ) + { + var element as EA.Element; + var element = elements.GetAt( i ); + if ( element.Stereotype == 'enumeration' && element.Name == 'OperatorRights' ) + return element; + } + return null; +} + +function ExportOperatorRightsTest() +{ + // Show the script output window + Repository.EnsureOutputVisible( "Script" ); + + Session.Output( "Export Operator Rights" ); + Session.Output( "======================" ); + // Get the set of selected items as a collection + var selPkg = Repository.GetTreeSelectedPackage(); + + var scPackage as EA.Package; + if( selPkg != null ) + { + scPackage = getPackageByName( selPkg, 'Software Components' ); + } + if ( scPackage == null ) + { + Session.Prompt( "This script requires the current context item to be inside the 'Software Components' package\n" + + "Please select this package and try again.", promptOK ); + return; + } + // now walk every leaf and search for an element named 'OperatorRights' + // with the stereotype 'enumeration' + // the path should be <> / Plugin / / <> OperatorRights + + var frameworkPackages as EA.Collection; + frameworkPackages = scPackage.Packages; + for( var i = 0; i < frameworkPackages.Count; i++ ) + { + var obj = frameworkPackages.GetAt( i ); + if ( !isFrameworkPackage( obj ) ) + continue; + var package as EA.Package; + package = obj; + var subPackages = package.Packages; + for( var j = 0; j < subPackages.Count; j++ ) + { + var subObj = subPackages.GetAt( j ); + if ( subObj.Name != 'Plugin' ) + continue; + var subPackage as EA.Package; + subPackage = subObj; + var pluginPackages = subPackage.Packages; + for ( var k = 0; k < pluginPackages.Count; k++ ) + { + var element as EA.Element; + element = getOperatorRightsEnum( pluginPackages.GetAt( k ) ); + if ( element == null ) + continue; + var attributes as EA.Collection; + attributes = element.Attributes; + for( var j = 0; j < attributes.Count; j++ ) + { + var attribute as EA.Attribute; + attribute = attributes.GetAt( j ); + Session.Output('found attribute: ' + attribute.Name + ' - ' + attribute.Default ); + } + } + } + } +} + +//ExportOperatorRightsTest(); + diff --git a/Lts/Common/CodeGen/main_grantlee.cpp b/Lts/Common/CodeGen/main_grantlee.cpp new file mode 100755 index 000000000..3ee571270 --- /dev/null +++ b/Lts/Common/CodeGen/main_grantlee.cpp @@ -0,0 +1,172 @@ + + + +#include "StructBuilder.h" +#include "XmlReader.h" +#include "DaoGeneratorGrantlee.h" +#include "DatabaseGeneratorGrantlee.h" +#include "CppTopicGeneratorGrantlee.h" +#include "SqlGenerator.h" + +#include +#include +#include +#include + + +class CmdLineParser : public LtsCommandLineParser::LtsCommandLineParser +{ +public: + CmdLineParser() + : LtsCommandLineParser(LtsCommandLineParser::LtsCommandLineParser::None) + { + addOption({ QStringList() << QStringLiteral("s") << QStringLiteral("servicefile"), + QStringLiteral("full path to the service xml-file"), + QStringLiteral("service xml-file"), + QStringLiteral("/home") + }); + + addOption({ QStringList() << QStringLiteral("a") << QStringLiteral("action"), + QStringLiteral("the action to execute"), + QStringLiteral("DATABASE|DATABASE_SERVER|IDL|DDS_TOPICS"), + QStringLiteral("/0") + }); + + addOption({ QStringList() << QStringLiteral("o") << QStringLiteral("outdir"), + QStringLiteral("the output directory where to put the generated files"), + QStringLiteral("output directory"), + QStringLiteral("/home") + }); + + addOption({ QStringList() << QStringLiteral("i") << QStringLiteral("idlinfo"), + QStringLiteral("the info file with all available idl service files"), + QStringLiteral("info file for idl"), + QStringLiteral("/0") + }); + } + + bool init() + { + if (!LtsCommandLineParser::init()) + return false; + + if (serviceFile().isEmpty() || action().isEmpty() || outDir().isEmpty()) + { + showHelp(); + return false; + } + return true; + } + + QString serviceFile() const { return value(QStringLiteral("s")); } + QString action() const { return value(QStringLiteral("a")); } + QString outDir() const { return value(QStringLiteral("o")); } + QString idlInfoFile() const { return value(QStringLiteral("i")); } +}; + +int main(int argc, char **argv) +{ + NGLCoreApplication app(argc, argv); + app.createLogger("CodeGen", "codegen_log4cxx.props", true ); + + NGL::INGLLogger &m_logger = app.baseLogger(); + + CmdLineParser options; + if (!options.init()) + return -1; + + QScopedPointer reader(new CodeGen::XmlReader); + + DEBUG_LOG(m_logger) << "ServiceFile: " << options.serviceFile(); + DEBUG_LOG(m_logger) << "IdlInfoFile: " << options.idlInfoFile(); + if (!reader->readServiceFile(options.serviceFile(), options.idlInfoFile())) + { + ERROR_LOG(m_logger) << "Error parsing service file '" << options.serviceFile() + << ": " << reader->errorString(); + return 2; + } + + QDir dir(options.outDir()); + DEBUG_LOG(m_logger) << "Directory: " << options.outDir(); + if (!dir.exists() && !dir.mkpath(options.outDir())) + { + ERROR_LOG(m_logger) << "Directory '" << options.outDir() + << "' does not exist and can not be created!"; + return 3; + } + + QScopedPointer structBuilder(new CodeGen::StructBuilder(reader.data())); + if (!structBuilder->buildStructures()) + { + ERROR_LOG(m_logger) << "Error building up structures"; + return 4; + } + + const QString action = options.action().toLower(); + + + QScopedPointer generator; + if (action == QStringLiteral("dao")) + generator.reset(new CodeGen::DaoGeneratorGrantlee); + else if (action == QStringLiteral("database")) + generator.reset(new CodeGen::DatabaseGeneratorGrantlee(false)); + else if (action == QStringLiteral("database_server")) + generator.reset(new CodeGen::DatabaseGeneratorGrantlee(true)); + else if (action == QStringLiteral("dds_topics")) + generator.reset(new CodeGen::CppTopicGeneratorGrantlee); + else if (action == QStringLiteral("sql")) + { + CodeGen::SqlGenerator sql(reader.data(), structBuilder.data()); + if (!sql.generateData(35)) + return 8; + + + if (!sql.writeSql(dir.absolutePath() + QLatin1Char('/'), CodeGen::ExerciseDependency::Exercise)) + { + ERROR_LOG(m_logger) << "Error while writing sql information for service " << sql.getServiceAbbr(); + return 10; + } + + if (!sql.generateData(42)) + return 8; + + + if (!sql.writeSql(dir.absolutePath() + QLatin1Char('/'), CodeGen::ExerciseDependency::Exercise)) + { + ERROR_LOG(m_logger) << "Error while writing sql information for service " << sql.getServiceAbbr(); + return 10; + } + + + sql.generateData(0); + if (!sql.writeSql(dir.absolutePath() + QLatin1Char('/'), CodeGen::ExerciseDependency::Global)) + { + ERROR_LOG(m_logger) << "Error while writing sql information for service " << sql.getServiceAbbr(); + return 10; + } + return 0; + } + else + { + ERROR_LOG(m_logger) << "Invalid action '" << options.action() << "' given!"; + return 5; + } + + if (!generator->init(reader.data(), structBuilder.data())) + { + ERROR_LOG(m_logger) << "Error initializing generator!"; + return 6; + } + + + + + const auto posArgs = options.positionalArguments(); + for (const QString &file : posArgs) + { + + if (!generator->generateData(file, dir.absolutePath())) + return 7; + } + return 0; +} diff --git a/Lts/Common/CodeGen/main_grantlee_gui.cpp b/Lts/Common/CodeGen/main_grantlee_gui.cpp new file mode 100755 index 000000000..5c4574133 --- /dev/null +++ b/Lts/Common/CodeGen/main_grantlee_gui.cpp @@ -0,0 +1,158 @@ + + + +#include "main_grantlee_gui.h" +#include "ui_main_grantlee_gui.h" +#include "StructBuilder.h" +#include "XmlReader.h" +#include "CppTopicGeneratorGrantlee.h" +#include "DatabaseGeneratorGrantlee.h" + +#include +#include +#include +#include +#include + + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , m_xmlReader(nullptr) + , m_structBuilder(nullptr) + , m_generator(new CodeGen::CppTopicGeneratorGrantlee) + , m_engine(new Grantlee::Engine) + , m_ui(new Ui_MainGrantleeGui) +{ + m_idlServiceInfoFile = QStringLiteral("/home/chehrlic/TDS/TDS-build-product-clang/service_idl_files.txt"); + m_ui->setupUi(this); + connect(m_ui->actionExit, &QAction::triggered, + this, &MainWindow::close); + connect(m_ui->actionSelect_Data_Source, &QAction::triggered, + this, &MainWindow::onSelectDataSource); + connect(m_ui->actionOpen_Template, &QAction::triggered, + this, &MainWindow::onOpenTemplate); + connect(m_ui->actionSave_Template, &QAction::triggered, + this, &MainWindow::onSaveTemplate); + connect(m_ui->pbRefresh, &QPushButton::clicked, + this, &MainWindow::onRefreshOutput); + connect(m_ui->actionDatabaseGenerator, &QAction::triggered, + this, &MainWindow::onGeneratorDb); + connect(m_ui->actionDDS_Generator, &QAction::triggered, + this, &MainWindow::onGeneratorDds); +} + +MainWindow::~MainWindow() +{ + delete m_xmlReader; + delete m_structBuilder; + delete m_generator; + delete m_engine; + delete m_ui; +} + +static QLatin1String s_startDir("/home/chehrlic/TDS/TDS-source-product/Lts/"); + +void MainWindow::onSelectDataSource() +{ + const QString dataSource = QFileDialog::getOpenFileName(this, tr("Select data source"), s_startDir, tr("Service Files (*.xml)")); + if (dataSource.isEmpty()) + return; + delete m_xmlReader; + m_xmlReader = new CodeGen::XmlReader; + if (!m_xmlReader->readServiceFile(dataSource, m_idlServiceInfoFile)) + { + QMessageBox::warning(this, tr("Invalid data source"), tr("The selected data source is not valid: %1").arg(m_xmlReader->errorString())); + return; + } + delete m_structBuilder; + m_structBuilder = new CodeGen::StructBuilder(m_xmlReader); + if (!m_structBuilder->buildStructures()) + { + QMessageBox::warning(this, tr("Invalid data source"), tr("Error building structures from data")); + return; + } + m_ui->teOuput->clear(); + m_generator->init(m_xmlReader, m_structBuilder); + QMessageBox::information(this, tr("Data source"), tr("Successfully loaded data source")); +} + +void MainWindow::onOpenTemplate() +{ + const QString templateFile = QFileDialog::getOpenFileName(this, tr("Select data source"), s_startDir, tr("Templates (*.grantlee)")); + if (templateFile.isEmpty()) + return; + QFile f(templateFile); + if (!f.open(QIODevice::ReadOnly)) + { + QMessageBox::warning(this, tr("Error reading template file"), tr("Can not read file: %1").arg(f.errorString())); + return; + } + const QString content = QString::fromUtf8(f.readAll()); + m_ui->teInput->setText(content); + m_templateFile = templateFile; +} + +void MainWindow::onSaveTemplate() +{ + QFile f(m_templateFile); + if (!f.open(QIODevice::WriteOnly)) + { + QMessageBox::warning(this, tr("Error saving template file"), tr("Can not save file: %1").arg(f.errorString())); + return; + } + f.write(m_ui->teInput->toPlainText().toUtf8()); + f.close(); + QMessageBox::information(this, tr("Saving template file"), tr("Successfully saved template to %1").arg(m_templateFile)); +} + +void MainWindow::onRefreshOutput() +{ + const QString in = m_ui->teInput->toPlainText(); + if (!m_generator->setTemplate(QString(), in)) + { + m_ui->teOuput->setText(m_generator->errorString()); + return; + } + m_ui->teOuput->setText(m_generator->render()); +} + +void MainWindow::onGeneratorDb() +{ + if (dynamic_cast(m_generator)) + return; + delete m_generator; + m_generator = new CodeGen::DatabaseGeneratorGrantlee(false); + if (m_xmlReader && m_structBuilder) + m_generator->init(m_xmlReader, m_structBuilder); +} + +void MainWindow::onGeneratorDds() +{ + if (dynamic_cast(m_generator)) + return; + delete m_generator; + m_generator = new CodeGen::CppTopicGeneratorGrantlee; + if (m_xmlReader && m_structBuilder) + m_generator->init(m_xmlReader, m_structBuilder); +} + + +int main(int argc, char **argv) +{ + NGLApplication app(argc, argv); + app.createLogger("CodeGen", "codegen_log4cxx.props"); + + NGL::INGLLogger &m_logger = app.baseLogger(); + STARTUP_LOG(m_logger); + + int iRet = 0; + { + QScopedPointer mw(new MainWindow); + mw->show(); + iRet = app.exec(); + } + + SHUTDOWN_LOG(m_logger); + return iRet; +} + diff --git a/Lts/Common/CodeGen/main_grantlee_gui.h b/Lts/Common/CodeGen/main_grantlee_gui.h new file mode 100755 index 000000000..02aa486d3 --- /dev/null +++ b/Lts/Common/CodeGen/main_grantlee_gui.h @@ -0,0 +1,39 @@ + + +#pragma once + +#include + +namespace CodeGen +{ +class XmlReader; +class StructBuilder; +class GeneratorGrantleeBase; +} +namespace Grantlee +{ +class Engine; +} +class Ui_MainGrantleeGui; +class MainWindow : public QMainWindow +{ + Q_OBJECT +public: + explicit MainWindow(QWidget *parent = nullptr); + ~MainWindow(); +private Q_SLOTS: + void onSelectDataSource(); + void onOpenTemplate(); + void onSaveTemplate(); + void onRefreshOutput(); + void onGeneratorDb(); + void onGeneratorDds(); +private: + CodeGen::XmlReader *m_xmlReader; + CodeGen::StructBuilder *m_structBuilder; + CodeGen::GeneratorGrantleeBase *m_generator; + Grantlee::Engine *m_engine; + QString m_idlServiceInfoFile; + QString m_templateFile; + Ui_MainGrantleeGui *m_ui; +}; diff --git a/Lts/Common/CodeGen/main_grantlee_gui.ui b/Lts/Common/CodeGen/main_grantlee_gui.ui new file mode 100755 index 000000000..c34b9fe73 --- /dev/null +++ b/Lts/Common/CodeGen/main_grantlee_gui.ui @@ -0,0 +1,140 @@ + + + MainGrantleeGui + + + + 0 + 0 + 838 + 532 + + + + MainWindow + + + + + + + Qt::Vertical + + + + + + + Input + + + + + + + + + + + + + + Output + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Refresh Output + + + Ctrl+R + + + + + + + + + + + + + + + + 0 + 0 + 838 + 22 + + + + + Fi&le + + + + + + + + + &Generator + + + + + + + + + + + &Exit + + + + + &Open Template + + + + + &Select Data Source + + + + + Sa&ve Template + + + + + &DatabaseGenerator + + + + + DDS &Generator + + + + + + diff --git a/Lts/Common/CodeGen/svc.idl.grantlee b/Lts/Common/CodeGen/svc.idl.grantlee new file mode 100755 index 000000000..bdcf031d6 --- /dev/null +++ b/Lts/Common/CodeGen/svc.idl.grantlee @@ -0,0 +1,39 @@ +/** + * @RDE_HEADER@ + * Template for idl struct definitions + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Hanna E. Rathje, SLSE, 2015-08-05 + */ + +{% autoescape off %} +// filename: {{ namespace_abbr }}.idl +#ifndef {{ namespace_abbr }}_idl_included +#define {{ namespace_abbr }}_idl_included + +//@copy #include <{{ include_prefix }}/{{ namespace_abbr }}_export.h> // #define NDDSUSERDllExport for linux + +//@copy #include "{{ namespace_abbr }}_enums.hpp" +#include "{{ namespace_abbr }}_enums.idl" + +{% for ext_service in ext_services.member %} +//@copy #include <{{ ext_service.baseFilePath }}.hpp> +#include <{{ ext_service.baseFilePath }}.idl> +{% endfor %} + +module {{ namespace }} +{ + {% for idl_struct in idl_structs %} + + {% if idl_struct.comment %} // {{ idl_struct.comment }} {% endif %} + struct {{ idl_struct.name }} {% if idl_struct.baseValueType %}: {{ idl_struct.baseValueType }} {% endif %} + { {% for idl_member in idl_struct.member %} + {{ idl_member.type }} {{ idl_member.name }}; {{ idl_member.isKey }} {{ idl_member.isOptional }} {% endfor %} + }; //@Extensibility {{ idl_struct.extensibility }} + + {% endfor %} +}; // namespace {{ namespace }} + +#endif +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_dao.cpp.grantlee b/Lts/Common/CodeGen/svc_dao.cpp.grantlee new file mode 100755 index 000000000..510fad917 --- /dev/null +++ b/Lts/Common/CodeGen/svc_dao.cpp.grantlee @@ -0,0 +1,204 @@ +/** + * @RDE_HEADER@ + * Template for 'real' structures including insert/update/delete functions for db + * $Id: svc_db_impl.cpp.grantlee 56901 2022-04-22 04:21:30Z karwat $ + * $HeadURL: svn://192.168.133.21/TDS/branches/product/features/25_02_refactoring/Lts/Common/CodeGen/svc_db_impl.cpp.grantlee $ + * $Author: karwat $ + */ + +{% autoescape off %} +// filename: {{ namespace_abbr }}_dao.cpp +#include "{{ namespace_abbr }}_dao.h" +{% if custom_db_procs %} +#include <{{ include_prefix }}/{{ namespace_abbr }}_db_impl_custom.h> +{% endif %} + +#include +#include +#include +#include +#include +#include +#include + +namespace {{ namespace }} +{ + +Dao::Dao(int exerciseId) + : m_query(nullptr) + , m_exerciseId(exerciseId) + , m_bEmitMessageOnError(true) + , m_bCanHandleSchemas(true) + , m_ownDatabaseConnection(true) + , m_logger(getLogger("Db")) +{} + +Dao::~Dao() +{ + m_query = nullptr; +{% if has_db_notifications %} + QSqlDriver *driver = m_dbConnection.driver(); + if (driver && m_dbConnection.isOpen()) + { +{% for cpp_struct in cpp_structs %}{% for db_notify in cpp_struct.dbNotifications %} driver->unsubscribeFromNotification(QStringLiteral("{{ db_notify }}")); +{% endfor %}{% endfor %} } +{% endif %} + if (m_ownDatabaseConnection && m_dbConnection.isOpen()) + m_dbConnection.close(); +} + +bool Dao::init(const QString &dbUser, + const QString &dbPasswd, + const QString &dbDriver, + const QString &dbHost, + const QString &dbName) +{ + m_ownDatabaseConnection = true; + + m_dbConnection = DBConnection::open(m_logger, dbUser, dbPasswd, dbDriver, dbHost, dbName, schemaName()); + + return initInternal(); +} + +bool Dao::initInternal() +{ + QSqlDriver *driver = m_dbConnection.driver(); + if (!driver) + { + WARNING_LOG(m_logger) << "No SQL driver loaded - no database available!"; + return false; + } + const QString driverName = m_dbConnection.driverName(); + DEBUG_LOG(m_logger) << "Connected to database " << m_dbConnection.databaseName() << "@" << m_dbConnection.hostName() + << ", schema: " << schemaName() << ", used driver: " << driverName; + if (!m_dbConnection.isOpen()) + { + WARNING_LOG(m_logger) << "Database connection is no open - can not initialize database interface"; + return false; + } + if (driverName == QLatin1String("QSQLITE")) + m_bCanHandleSchemas = false; + +{% if has_db_notifications %} + if (!driver->hasFeature(QSqlDriver::EventNotifications)) + { + WARNING_LOG(m_logger) << "Can not subscribe to notifications - sqldriver does not support this feature!"; + } + else + { + connect(driver, QOverload::of(&QSqlDriver::notification), + this, &Dao::onNotification); +{% for cpp_struct in cpp_structs %}{% for db_notify in cpp_struct.dbNotifications %}driver->subscribeToNotification(QStringLiteral("{{ db_notify }}"));{% endfor %}{% endfor %} + } +{% endif %} + m_query = std::unique_ptr(new NSqlQuery(m_dbConnection, m_logger)); + return true; +} + +QString Dao::schemaName() const +{ + if (!m_bCanHandleSchemas) + return QString(); + if (m_exerciseId <= 0) + return QStringLiteral("{{ namespace_abbr }}"); + return DBCommon::getExerciseSchemaName(m_exerciseId); +} + +QString Dao::dbUser() const +{ + if (m_exerciseId > 0) + return QStringLiteral("ets_ex_00"); + else + return QStringLiteral("ets_") + schemaName(); +} + +void Dao::setEmitMessageOnError(bool bEmit) +{ + m_bEmitMessageOnError = bEmit; +} + +QString Dao::getTableName(const QString &tableName) const +{ + if (!m_bCanHandleSchemas) + return tableName; + if (m_exerciseId > 0) + return DBCommon::getExerciseTableName(tableName, m_exerciseId); + return QStringLiteral("{{ namespace_abbr }}.") + tableName; +} + +QSqlError Dao::getLastError() const +{ + return m_lastError; +} + +{% if has_db_notifications %} +void Dao::onNotification(const QString &name, QSqlDriver::NotificationSource source, const QVariant &payload) +{ + DEBUG_LOG(m_logger) << "Notification source = " << source; + if (source == QSqlDriver::SelfSource) + return; + // search coresponding signal +{% for cpp_struct in cpp_structs %}{% for db_notify in cpp_struct.dbNotifications %} + if (name == QStringLiteral("{{ db_notify }}")) + { + emit signal{{ db_notify }}(payload.toInt()); + + return; + } + {% endfor %}{% endfor %} +} +{% endif %} + +{% for cpp_struct in cpp_structs %} +// +// --> Struct: Database::{{ cpp_struct.name }} +// + +bool Dao::select(QVector &dataVec, const QString &where) +{ + SqlQueryJanitor janitor(m_query); + const auto cb =[&dataVec] (const Database::{{ cpp_struct.name }} &data) + { + dataVec.push_back(data); + }; + return selectPrepareInternal{{ cpp_struct.name }}(where) && selectFetchInternal{{ cpp_struct.name }}(cb); +} + +bool Dao::selectPrepareInternal{{ cpp_struct.name }}(const QString &where) +{ + const QString tableName(getTableName(QStringLiteral("{{ cpp_struct.viewTableName }}"))); + const QString stmt(QStringLiteral("SELECT {% for row in cpp_struct.viewMember %}{{ row.dbColumnNameView }}{% if not forloop.last %}, {% endif %}{% endfor %}" + " FROM ") + tableName + where); + return m_query->prepare(stmt); +} + +bool Dao::selectFetchInternal{{ cpp_struct.name }}(const std::function &cb) +{ + if (!m_query->exec(m_bEmitMessageOnError)) + { + m_lastError = m_query->lastError(); + return false; + } + + Database::{{ cpp_struct.name }} data; + while (m_query->next()) + { + int i = 0; + {% for row in cpp_struct.viewMember %} + data.{{ row.cppMemberName }}{{ row.cppFromQvPre1 }} = {{ row.cppFromQvPre2 }}m_query->value(i++){{ row.cppFromQvPost }};{% endfor %} + cb(data); + } + + return true; +} + +// <-- Database::{{ cpp_struct.name }} end +{% endfor %} + +/*static*/ QString Dao::qVariantToJsonString(const QVariant &value) +{ + return value.isNull() ? QStringLiteral("{}") : value.toString(); +} + +} // namespace {{ namespace }} +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_dao.h.grantlee b/Lts/Common/CodeGen/svc_dao.h.grantlee new file mode 100755 index 000000000..49855abbd --- /dev/null +++ b/Lts/Common/CodeGen/svc_dao.h.grantlee @@ -0,0 +1,82 @@ +/** + * @RDE_HEADER@ + * Template for 'real' structures including insert/update/delete functions for db + * $Id: svc_db_impl.h.grantlee 53676 2021-06-10 10:21:06Z diers $ + * $HeadURL: svn://192.168.133.21/TDS/branches/product/features/25_02_refactoring/Lts/Common/CodeGen/svc_db_impl.h.grantlee $ + * $Author: diers $ + */ + +{% autoescape off %} +// filename: {{ namespace_abbr }}_dao.h +#pragma once + +#include +#include +#include +#include +#include +#include + +#include <{{ include_prefix }}/{{ namespace_abbr }}_enums.hpp> +#include <{{ include_prefix }}/{{ namespace_abbr }}_db.h> +#include <{{ include_prefix }}/logger.h> + +class NSqlQuery; +namespace {{ namespace }} +{ +class {{ namespace_abbr_upper }}_COMMON_EXPORT Dao {% if not custom_db_procs %}final {%endif %}: public QObject +{ + Q_OBJECT +public: + explicit Dao(int exerciseId); + ~Dao(); + + bool init(const QString &dbUser, + const QString &dbPasswd, + const QString &dbDriver, + const QString &dbHost, + const QString &dbName); + + QString schemaName() const; + QString dbUser() const; + const QSqlDatabase &database() const { return m_dbConnection; } + void setEmitMessageOnError(bool bEmit); + QSqlError getLastError() const; + + {% for cpp_struct in cpp_structs %} + // --> Database::{{ cpp_struct.name }} +public: + bool select(QVector &dataVec, const QString &where = QString()); + +private: + bool selectPrepareInternal{{ cpp_struct.name }}(const QString &where); + bool selectFetchInternal{{ cpp_struct.name }}(const std::function &cb); + // <-- Database::{{ cpp_struct.name }} + {% endfor %} + + static QString qVariantToJsonString(const QVariant &value); + +protected: + QString getTableName(const QString &tableName) const; +{% if has_db_notifications %} +private Q_SLOTS: + void onNotification(const QString &name, QSqlDriver::NotificationSource source, const QVariant &payload); + +Q_SIGNALS: +{% for cpp_struct in cpp_structs %}{% for db_notify in cpp_struct.dbNotifications %} void signal{{ db_notify }}(int id); +{% endfor %}{% endfor %}{% endif %} +protected: + QSqlDatabase m_dbConnection; + std::unique_ptr m_query; + QSqlError m_lastError; + int m_exerciseId; + bool m_bEmitMessageOnError; + bool m_bCanHandleSchemas; + bool m_ownDatabaseConnection; + LOGGER_T m_logger; + +private: + bool initInternal(); +};// class Dao +} // namespace {{ namespace }} +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_db.h.grantlee b/Lts/Common/CodeGen/svc_db.h.grantlee new file mode 100755 index 000000000..30ead9d31 --- /dev/null +++ b/Lts/Common/CodeGen/svc_db.h.grantlee @@ -0,0 +1,145 @@ +/** + * @RDE_HEADER@ + * Template for 'real' structures including insert/update/delete functions for db + * $Id$ + * $HeadURL$ + * $Author$ + */ + +{% autoescape off %} +// filename: {{ namespace_abbr }}_db.h +#pragma once + +#include +#include +#include + +#include <{{ include_prefix }}/{{ namespace_abbr }}_export.h> +#include <{{ include_prefix }}/{{ namespace_abbr }}_db_structs.h> +#include <{{ include_prefix }}/{{ namespace_abbr }}_enums.hpp> + +{% for ext_service in ext_services.member %} +#include <{{ ext_service.baseFilePath }}.hpp> +{% endfor %} + +namespace NGL +{ +class INGLLogger; +} +class QSqlDatabase; + +namespace {{ namespace }} +{ + +class {{ namespace_abbr_upper }}_COMMON_EXPORT DatabaseInterface : public QObject +{ + Q_OBJECT +public: + virtual ~DatabaseInterface() = default; + + /** + * Create a new instance for {{ namespace }}::DatabaseInterface. + * @param exerciseId if <= 0 -> global service + */ + static std::shared_ptr create(int exerciseId); + + /** + * @brief initialize the database interface (must be called after the db connection was opened!) + * @return true on succcess + */ + virtual bool init(const QSqlDatabase &dbConnection) = 0; + virtual bool init(const QString &dbUser, + const QString &dbPasswd, + const QString &dbDriver, + const QString &dbHost, + const QString &dbName) = 0; + + /** + * @brief return the current schema name depending if exercise id > 0 or not + */ + virtual QString schemaName() const = 0; + + /** + * @brief return the current database user dependening if exercise id > 0 or not + */ + virtual QString dbUser() const = 0; + + /** + * @brief return the database connection used in this DatabaseInterface + */ + virtual const QSqlDatabase &database() const = 0; + + /** + * emit an error message (ERROR_LOG) when a statement fails to execute (default: on) + */ + virtual void setEmitMessageOnError(bool bEmit) = 0; + + virtual QSqlError getLastError() const = 0; + + {% for cpp_struct in cpp_structs %} + // + // Database::{{ cpp_struct.name }} + // +public: + virtual bool insert(Database::{{ cpp_struct.name }} &data) = 0; + virtual bool upsert(Database::{{ cpp_struct.name }} &data) = 0; + virtual bool update(const Database::{{ cpp_struct.name }} &data) = 0; +{% if cpp_struct.insertStructures|length == 1 %} + virtual bool insert(std::vector &cont) = 0; + virtual bool upsert(std::vector &cont) = 0; + virtual bool update(const std::vector &cont) = 0; +{% endif %} + virtual bool select(QVector &dataVec) = 0; + virtual bool select(const std::function &cb) = 0; + {% for fk in cpp_struct.selectRemoveMember %} + virtual bool selectBy{{ fk.cppMemberNameCc }}(QVector &dataVec, const {{ fk.cppType }} &{{ fk.cppMemberName }}, const QString &orderBy = QString()) = 0; + virtual bool selectBy{{ fk.cppMemberNameCc }}(const std::function &cb, const {{ fk.cppType }} &{{ fk.cppMemberName }}, const QString &orderBy = QString()) = 0; + {% endfor %} + virtual bool select{{ cpp_struct.name }}({{ cpp_struct.primaryKey.cppType }} {{ cpp_struct.primaryKey.cppMemberName }}, Database::{{ cpp_struct.name }} &data) = 0; + virtual bool remove{{ cpp_struct.name }}({{ cpp_struct.primaryKey.cppType }} {{ cpp_struct.primaryKey.cppMemberName }}) = 0; + {% if cpp_struct.deleteWithoutPkAllowed %} {% for fk in cpp_struct.selectRemoveMember %} + virtual bool remove{{ cpp_struct.nameCc }}By{{ fk.cppMemberNameCc }}(const {{ fk.cppType }} &{{ fk.cppMemberName }}) = 0; + {% endfor %} {% endif %} + virtual bool fixUpSequence{{ cpp_struct.name }}() = 0; + virtual bool truncate{{ cpp_struct.name }}() = 0; +{% if cpp_struct.dbNotifications %} +Q_SIGNALS: + {% for dbnotify in cpp_struct.dbNotifications %} + // database notification on insert/update/delete + void {{ dbnotify }}(const QVariant &payload); + {% endfor %} + {% endif %} + // + // Database::{{ cpp_struct.name }} end + // + {% endfor %} +{% if data_structs|length > 0 %} +public:{% for data_struct in data_structs %} + // read only struct Database::{{ data_struct.name }} + virtual bool select(QVector &dataVec) = 0; +{% endfor %} {% endif %} +{% if custom_db_procs|length > 0 %} +public:{% for db_proc in custom_db_procs %} + // custom proc, body must be implemented by user + virtual {{ db_proc.signature }} = 0; +{% endfor %} {% endif %} + +public: + + // vector of database struct to/from JSON-array + // helper for BaseDownloader + template + static QJsonArray toJson(const QVector &dataVec) + { + return Database::toJson(dataVec); + } + + template + static std::vector fromJson(const QJsonArray &arr) + { + return Database::fromJson(arr); + } +};// class DatabaseInterface + +} // namespace {{ namespace }} +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_db_impl.cpp.grantlee b/Lts/Common/CodeGen/svc_db_impl.cpp.grantlee new file mode 100755 index 000000000..ee32ddbe2 --- /dev/null +++ b/Lts/Common/CodeGen/svc_db_impl.cpp.grantlee @@ -0,0 +1,732 @@ +/** + * @RDE_HEADER@ + * Template for 'real' structures including insert/update/delete functions for db + * $Id$ + * $HeadURL$ + * $Author$ + */ + +{% autoescape off %} +// filename: {{ namespace_abbr }}_db_impl.cpp +#include "{{ namespace_abbr }}_db_impl.h" +{% if custom_db_procs %} +#include <{{ include_prefix }}/{{ namespace_abbr }}_db_impl_custom.h> +{% endif %} + +#include +#include +#include +#include +#include +#include +#include + +namespace {{ namespace }} +{ + +QString qVariantToJsonString(const QVariant &value) +{ + return value.isNull() ? QStringLiteral("{}") : value.toString(); +} + +QString fixupEmptyJsonString(const QString &str) +{ + return str.isEmpty() ? QStringLiteral("{}") : str; +} + +// DatabaseInterface +std::shared_ptr DatabaseInterface::create(int exerciseId) +{ + {% if custom_db_procs %} + return std::make_shared(exerciseId); + {% else %} + return std::make_shared(exerciseId); + {% endif %} +} + +DatabaseInterfaceImpl::DatabaseInterfaceImpl(int exerciseId) + : m_query(nullptr) + , m_exerciseId(exerciseId) + , m_bEmitMessageOnError(true) + , m_bCanHandleSchemas(true) + , m_ownDatabaseConnection(true) + , m_logger(getLogger("Db")) +{} + +DatabaseInterfaceImpl::~DatabaseInterfaceImpl() +{ + m_query = nullptr; +{% if has_db_notifications %} + QSqlDriver *driver = m_dbConnection.driver(); + if (driver && m_dbConnection.isOpen()) + { +{% for cpp_struct in cpp_structs %}{% for db_notify in cpp_struct.dbNotifications %} + driver->unsubscribeFromNotification(QStringLiteral("{{ db_notify }}"));{% endfor %}{% endfor %} + } +{% endif %} + if (m_ownDatabaseConnection && m_dbConnection.isOpen()) + m_dbConnection.close(); +} + +bool DatabaseInterfaceImpl::init(const QSqlDatabase &dbConnection) +{ + m_dbConnection = dbConnection; + m_ownDatabaseConnection = false; + return initInternal(); +} + +bool DatabaseInterfaceImpl::init(const QString &dbUser, + const QString &dbPasswd, + const QString &dbDriver, + const QString &dbHost, + const QString &dbName) +{ + m_ownDatabaseConnection = true; + + m_dbConnection = DBConnection::open(m_logger, dbUser, dbPasswd, dbDriver, dbHost, dbName, schemaName()); + + return initInternal(); +} + +bool DatabaseInterfaceImpl::initInternal() +{ + QSqlDriver *driver = m_dbConnection.driver(); + if (!driver) + { + WARNING_LOG(m_logger) << "No SQL driver loaded - no database available!"; + return false; + } + const QString driverName = m_dbConnection.driverName(); + DEBUG_LOG(m_logger) << "Connected to database " << m_dbConnection.databaseName() << "@" << m_dbConnection.hostName() + << ", schema: " << schemaName() << ", used driver: " << driverName; + if (!m_dbConnection.isOpen()) + { + WARNING_LOG(m_logger) << "Database connection is no open - can not initialize database interface"; + return false; + } + if (driverName == QLatin1String("QSQLITE")) + m_bCanHandleSchemas = false; + +{% if has_db_notifications %} + if (!driver->hasFeature(QSqlDriver::EventNotifications)) + { + WARNING_LOG(m_logger) << "Can not subscribe to notifications - sqldriver does not support this feature!"; + } + else + { + connect(driver, QOverload::of(&QSqlDriver::notification), + this, &DatabaseInterfaceImpl::onNotification); +{% for cpp_struct in cpp_structs %}{% for db_notify in cpp_struct.dbNotifications %} + driver->subscribeToNotification(QStringLiteral("{{ db_notify }}"));{% endfor %}{% endfor %} + } +{% endif %} + m_query = std::unique_ptr(new NSqlQuery(m_dbConnection, m_logger)); + return true; +} + +QString DatabaseInterfaceImpl::schemaName() const +{ + if (!m_bCanHandleSchemas) + return QString(); + if (m_exerciseId <= 0) + return QStringLiteral("{{ namespace_abbr }}"); + return DBCommon::getExerciseSchemaName(m_exerciseId); +} + +QString DatabaseInterfaceImpl::dbUser() const +{ + if (m_exerciseId > 0) + return QStringLiteral("ets_ex_00"); + else + return QStringLiteral("ets_") + schemaName(); +} + +void DatabaseInterfaceImpl::setEmitMessageOnError(bool bEmit) +{ + m_bEmitMessageOnError = bEmit; +} + +QString DatabaseInterfaceImpl::getTableName(const QString &tableName) const +{ + if (!m_bCanHandleSchemas) + return tableName; + if (m_exerciseId > 0) + return DBCommon::getExerciseTableName(tableName, m_exerciseId); + return QStringLiteral("{{ namespace_abbr }}.") + tableName; +} + +QSqlError DatabaseInterfaceImpl::getLastError() const +{ + return m_lastError; +} + +{% if has_db_notifications %} +void DatabaseInterfaceImpl::onNotification(const QString &name, QSqlDriver::NotificationSource source, const QVariant &payload) +{ + DEBUG_LOG(m_logger) << "Notification source = " << source; + // no need to emit notification signals + if (source == QSqlDriver::SelfSource) + return; + // search coresponding signal +{% for cpp_struct in cpp_structs %}{% for db_notify in cpp_struct.dbNotifications %} + if (name == QLatin1String("{{ db_notify }}")) + { + emit {{ db_notify }}(payload); + }{% endfor %}{% endfor %} +} +{% endif %} + +{% for cpp_struct in cpp_structs %} +// +// Struct: Database::{{ cpp_struct.name }} +// +bool DatabaseInterfaceImpl::insert(Database::{{ cpp_struct.name }} &data) +{ + SqlQueryJanitor janitor(m_query); +{% if cpp_struct.insertStructures|length > 1 %} + DBTransaction transaction(m_dbConnection); +{% endif %} +{% for insertStruct in cpp_struct.insertStructures %} + { + const QString tableName(getTableName(QStringLiteral("{{ insertStruct.name }}"))); + + QString keyColumnInsertKey; + QString keyColumnInsertVal; + {% if forloop.first and cpp_struct.primaryKey.dbHasSequence %} + const bool bInsertWithKey = data.{{cpp_struct.primaryKey.cppMemberName}} != {{cpp_struct.primaryKey.cppDefault}}; + if (bInsertWithKey) + { + keyColumnInsertKey = QStringLiteral("{{ insertStruct.primaryKey.dbColumnName }}, "); + keyColumnInsertVal = QStringLiteral(" :{{ insertStruct.primaryKey.dbColumnName }}, "); + } + {% endif %} + const QString stmt(QStringLiteral("INSERT INTO ") + tableName + + QStringLiteral(" (") + keyColumnInsertKey + + QStringLiteral("{% for row in insertStruct.member %} {{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}" + " ) VALUES (") + keyColumnInsertVal + + QStringLiteral("{% for row in insertStruct.member %}:{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} )")); + + if (!m_query->prepare(stmt)) + { + m_lastError = m_query->lastError(); + return false; + } + + {% if forloop.first and cpp_struct.primaryKey.dbHasSequence %} + if (bInsertWithKey) + m_query->bindValue(QStringLiteral(":{{ insertStruct.primaryKey.dbColumnName }}"), data.{{ insertStruct.serialMember.cppMemberName }}); + {% endif %} + {% for row in insertStruct.member %} + m_query->bindValue(QStringLiteral(":{{ row.dbColumnName }}"), {{ row.cppToQvPre }}data.{{ row.cppMemberName }}{{ row.cppToQvPost }}); + {% endfor %} + + if (!m_query->exec(m_bEmitMessageOnError)) + { + m_lastError = m_query->lastError(); + return false; + } + + {% if insertStruct.serialMember.cppMemberName %} + {% if forloop.first and cpp_struct.primaryKey.dbHasSequence %} + if (data.{{cpp_struct.primaryKey.cppMemberName}} == {{cpp_struct.primaryKey.cppDefault}}) + data.{{ insertStruct.serialMember.cppMemberName }} = m_query->lastInsertId(){{ insertStruct.serialMember.cppFromQvPost }}; + {% else %} + data.{{ insertStruct.serialMember.cppMemberName }} = m_query->lastInsertId(){{ insertStruct.serialMember.cppFromQvPost }}; + {% endif %} + {% if insertStruct.baseId.cppMemberName %} + data.{{ insertStruct.baseId.cppMemberName }} = data.{{ insertStruct.serialMember.cppMemberName }}; + {% endif %} + {% endif %} + } +{% endfor %} +{% if cpp_struct.insertStructures|length > 1 %} + transaction.commit(); +{% endif %} + return true; +} + +bool DatabaseInterfaceImpl::upsert(Database::{{ cpp_struct.name }} &data) +{ + SqlQueryJanitor janitor(m_query); +{% if cpp_struct.insertStructures|length > 1 %} + DBTransaction transaction(m_dbConnection); +{% endif %} +{% for insertStruct in cpp_struct.insertStructures %} + { + const QString tableName(getTableName(QStringLiteral("{{ insertStruct.name }}"))); + + QString keyColumnInsertKey; + QString keyColumnInsertKeyConflict; + QString keyColumnInsertVal; + {% if forloop.first and cpp_struct.primaryKey.dbHasSequence %} + const bool bInsertWithKey = true; + keyColumnInsertVal = QStringLiteral(" :{{ insertStruct.primaryKey.dbColumnName }}, "); + keyColumnInsertKey = QStringLiteral("{{ insertStruct.primaryKey.dbColumnName }}, "); + {% endif %} + + keyColumnInsertKeyConflict = QStringLiteral("{{ insertStruct.primaryKey.dbColumnName }} "); + + const QString stmt(QStringLiteral("INSERT INTO ") + tableName + + QStringLiteral(" (") + keyColumnInsertKey + + QStringLiteral("{% for row in insertStruct.member %} {{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}" + " ) VALUES (") + keyColumnInsertVal + + QStringLiteral("{% for row in insertStruct.member %}:{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} )") + + QStringLiteral(" ON CONFLICT (") + keyColumnInsertKeyConflict + QStringLiteral(") DO UPDATE ") + + QStringLiteral(" SET" + " {% for row in insertStruct.member %}{{ row.dbColumnName }} = :{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}" + " WHERE excluded.{{ insertStruct.primaryKey.dbColumnName }} = :{{ insertStruct.primaryKey.dbColumnName }}")); + + if (!m_query->prepare(stmt)) + { + m_lastError = m_query->lastError(); + return false; + } + + {% if forloop.first and cpp_struct.primaryKey.dbHasSequence %} + if (bInsertWithKey) + m_query->bindValue(QStringLiteral(":{{ insertStruct.primaryKey.dbColumnName }}"), data.{{ insertStruct.serialMember.cppMemberName }}); + {% endif %} + {% for row in insertStruct.member %} + m_query->bindValue(QStringLiteral(":{{ row.dbColumnName }}"), {{ row.cppToQvPre }}data.{{ row.cppMemberName }}{{ row.cppToQvPost }}); + {% endfor %} + + if (!m_query->exec(m_bEmitMessageOnError)) + { + m_lastError = m_query->lastError(); + return false; + } + + {% if insertStruct.serialMember.cppMemberName %} + {% if forloop.first and cpp_struct.primaryKey.dbHasSequence %} + if (data.{{cpp_struct.primaryKey.cppMemberName}} == {{cpp_struct.primaryKey.cppDefault}}) + data.{{ insertStruct.serialMember.cppMemberName }} = m_query->lastInsertId(){{ insertStruct.serialMember.cppFromQvPost }}; + {% else %} + data.{{ insertStruct.serialMember.cppMemberName }} = m_query->lastInsertId(){{ insertStruct.serialMember.cppFromQvPost }}; + {% endif %} + {% if insertStruct.baseId.cppMemberName %} + data.{{ insertStruct.baseId.cppMemberName }} = data.{{ insertStruct.serialMember.cppMemberName }}; + {% endif %} + {% endif %} + } +{% endfor %} +{% if cpp_struct.insertStructures|length > 1 %} + transaction.commit(); +{% endif %} + return true; +} + +bool DatabaseInterfaceImpl::update(const Database::{{ cpp_struct.name }} &data) +{ + SqlQueryJanitor janitor(m_query); +{% if cpp_struct.insertStructures|length > 1 %} + DBTransaction transaction(m_dbConnection); +{% endif %} + {% for insertStruct in cpp_struct.insertStructures %} + { + const QString tableName(getTableName(QStringLiteral("{{ insertStruct.name }}"))); + const QString stmt(QStringLiteral("UPDATE ") + tableName + + QStringLiteral(" SET" + " {% for row in insertStruct.member %}{{ row.dbColumnName }} = :{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}" + " WHERE {{ insertStruct.primaryKey.dbColumnName }} = :{{ insertStruct.primaryKey.dbColumnName }}")); + + if (!m_query->prepare(stmt)) + { + m_lastError = m_query->lastError(); + return false; + } + + {% for row in insertStruct.member %} + m_query->bindValue(QStringLiteral(":{{ row.dbColumnName }}"), {{ row.cppToQvPre }}data.{{ row.cppMemberName }}{{ row.cppToQvPost }}); + {% endfor %} + {% if insertStruct.serialMember.cppMemberName %} + m_query->bindValue(QStringLiteral(":{{ insertStruct.primaryKey.dbColumnName }}"), data.{{ insertStruct.serialMember.cppMemberName }}); + {% endif %} + + if (!m_query->exec(m_bEmitMessageOnError)) + { + m_lastError = m_query->lastError(); + return false; + } + } + {% endfor %} +{% if cpp_struct.insertStructures|length > 1 %} + transaction.commit(); +{% endif %} + return true; +} + +{% if cpp_struct.insertStructures|length == 1 %} +// current only implemented for 'plain' structures with only one affected table +bool DatabaseInterfaceImpl::insert(std::vector &cont) +{ + if (cont.empty()) + return true; + + {% for insertStruct in cpp_struct.insertStructures %} + { + const QString tableName(getTableName(QStringLiteral("{{ insertStruct.name }}"))); + + QString keyColumnInsertKey; + QString keyColumnInsertVal; + {% if forloop.first and cpp_struct.primaryKey.dbHasSequence %} + const bool bInsertWithKey = cont[0].{{cpp_struct.primaryKey.cppMemberName}} != {{cpp_struct.primaryKey.cppDefault}}; + // the first element determines the stmt to be used + if (bInsertWithKey) + { + keyColumnInsertKey = QStringLiteral("{{ insertStruct.primaryKey.dbColumnName }}, "); + keyColumnInsertVal = QStringLiteral(" :{{ insertStruct.primaryKey.dbColumnName }}, "); + } + {% endif %} + const QString stmt(QStringLiteral("INSERT INTO ") + tableName + + QStringLiteral(" (") + keyColumnInsertKey + + QStringLiteral("{% for row in insertStruct.member %} {{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}" + " ) VALUES (") + keyColumnInsertVal + + QStringLiteral("{% for row in insertStruct.member %}:{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} )")); + + { + SqlQueryJanitor janitor(m_query); + if (!m_query->prepare(stmt)) + { + m_lastError = m_query->lastError(); + return false; + } + + for (auto &data : cont) + { + {% if forloop.first and cpp_struct.primaryKey.dbHasSequence %} + if (bInsertWithKey) + m_query->bindValue(QStringLiteral(":{{ insertStruct.primaryKey.dbColumnName }}"), data.{{ insertStruct.serialMember.cppMemberName }}); + {% endif %} + {% for row in insertStruct.member %} + m_query->bindValue(QStringLiteral(":{{ row.dbColumnName }}"), {{ row.cppToQvPre }}data.{{ row.cppMemberName }}{{ row.cppToQvPost }}); + {% endfor %} + + if (!m_query->exec(m_bEmitMessageOnError)) + { + m_lastError = m_query->lastError(); + return false; + } + + {% if insertStruct.serialMember.cppMemberName %} + if (data.{{cpp_struct.primaryKey.cppMemberName}} == {{cpp_struct.primaryKey.cppDefault}}) + data.{{ insertStruct.serialMember.cppMemberName }} = m_query->lastInsertId(){{ insertStruct.serialMember.cppFromQvPost }}; + {% endif %} + } + } + + {% if forloop.first and cpp_struct.primaryKey.dbHasSequence %} + if (bInsertWithKey) + fixUpSequence{{ cpp_struct.nameCc }}(); + {% endif %} + + } + {% endfor %} + return true; +} + +bool DatabaseInterfaceImpl::upsert(std::vector &cont) +{ + if (cont.empty()) + return true; + + {% for insertStruct in cpp_struct.insertStructures %} + { + const QString tableName(getTableName(QStringLiteral("{{ insertStruct.name }}"))); + + QString keyColumnInsertKey; + QString keyColumnInsertKeyConflict; + QString keyColumnInsertVal; + {% if forloop.first and cpp_struct.primaryKey.dbHasSequence %} + const bool bInsertWithKey = true; + keyColumnInsertVal = QStringLiteral(" :{{ insertStruct.primaryKey.dbColumnName }}, "); + keyColumnInsertKey = QStringLiteral("{{ insertStruct.primaryKey.dbColumnName }}, "); + {% endif %} + // the first element determines the stmt to be used + keyColumnInsertKeyConflict = QStringLiteral("{{ insertStruct.primaryKey.dbColumnName }} "); + + const QString stmt(QStringLiteral("INSERT INTO ") + tableName + + QStringLiteral(" (") + keyColumnInsertKey + + QStringLiteral("{% for row in insertStruct.member %} {{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}" + " ) VALUES (") + keyColumnInsertVal + + QStringLiteral("{% for row in insertStruct.member %}:{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} )") + + QStringLiteral(" ON CONFLICT (") + keyColumnInsertKeyConflict + QStringLiteral(") DO UPDATE ") + + QStringLiteral(" SET" + " {% for row in insertStruct.member %}{{ row.dbColumnName }} = :{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}" + " WHERE excluded.{{ insertStruct.primaryKey.dbColumnName }} = :{{ insertStruct.primaryKey.dbColumnName }}")); + + { + SqlQueryJanitor janitor(m_query); + if (!m_query->prepare(stmt)) + { + m_lastError = m_query->lastError(); + return false; + } + + for (auto &data : cont) + { + {% if forloop.first and cpp_struct.primaryKey.dbHasSequence %} + if (bInsertWithKey) + m_query->bindValue(QStringLiteral(":{{ insertStruct.primaryKey.dbColumnName }}"), data.{{ insertStruct.serialMember.cppMemberName }}); + {% endif %} + {% for row in insertStruct.member %} + m_query->bindValue(QStringLiteral(":{{ row.dbColumnName }}"), {{ row.cppToQvPre }}data.{{ row.cppMemberName }}{{ row.cppToQvPost }}); + {% endfor %} + + if (!m_query->exec(m_bEmitMessageOnError)) + { + m_lastError = m_query->lastError(); + return false; + } + + {% if insertStruct.serialMember.cppMemberName %} + if (data.{{cpp_struct.primaryKey.cppMemberName}} == {{cpp_struct.primaryKey.cppDefault}}) + data.{{ insertStruct.serialMember.cppMemberName }} = m_query->lastInsertId(){{ insertStruct.serialMember.cppFromQvPost }}; + {% endif %} + } + } + + {% if forloop.first and cpp_struct.primaryKey.dbHasSequence %} + if (bInsertWithKey) + fixUpSequence{{ cpp_struct.nameCc }}(); + {% endif %} + + } + {% endfor %} + return true; +} + +// current only implemented for 'plain' structures with only one affected table +bool DatabaseInterfaceImpl::update(const std::vector &cont) +{ + if (cont.empty()) + return true; + + {% for insertStruct in cpp_struct.insertStructures %} + { + const QString tableName(getTableName(QStringLiteral("{{ insertStruct.name }}"))); + const QString stmt(QStringLiteral("UPDATE ") + tableName + + QStringLiteral(" SET" + " {% for row in insertStruct.member %}{{ row.dbColumnName }} = :{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}" + " WHERE {{ insertStruct.primaryKey.dbColumnName }} = :{{ insertStruct.primaryKey.dbColumnName }}")); + + { + SqlQueryJanitor janitor(m_query); + if (!m_query->prepare(stmt)) + { + m_lastError = m_query->lastError(); + return false; + } + + for (const auto &data : cont) + { + {% for row in insertStruct.member %} + m_query->bindValue(QStringLiteral(":{{ row.dbColumnName }}"), {{ row.cppToQvPre }}data.{{ row.cppMemberName }}{{ row.cppToQvPost }}); + {% endfor %} + {% if insertStruct.serialMember.cppMemberName %} + m_query->bindValue(QStringLiteral(":{{ insertStruct.primaryKey.dbColumnName }}"), data.{{ insertStruct.serialMember.cppMemberName }}); + {% endif %} + + if (!m_query->exec(m_bEmitMessageOnError)) + { + m_lastError = m_query->lastError(); + return false; + } + } + } + } + {% endfor %} + return true; +} +{% endif %} + +bool DatabaseInterfaceImpl::select(QVector &dataVec) +{ + SqlQueryJanitor janitor(m_query); + const auto cb =[&dataVec] (const Database::{{ cpp_struct.name }} &data) + { + dataVec.push_back(data); + }; + return selectPrepareInternal{{ cpp_struct.name }}(QString()) && selectFetchInternal{{ cpp_struct.name }}(cb); +} + +bool DatabaseInterfaceImpl::select(const std::function &cb) +{ + SqlQueryJanitor janitor(m_query); + return selectPrepareInternal{{ cpp_struct.name }}(QString()) && selectFetchInternal{{ cpp_struct.name }}(cb); +} + +{% for fk in cpp_struct.selectRemoveMember %} +bool DatabaseInterfaceImpl::selectBy{{ fk.cppMemberNameCc }}(QVector &dataVec, const {{ fk.cppType }} &{{ fk.cppMemberName }}, const QString &orderBy) +{ + SqlQueryJanitor janitor(m_query); + if (!selectPrepareInternal{{ cpp_struct.name }}(QStringLiteral(" WHERE {{ fk.dbColumnName }} = :{{ fk.dbColumnName }}") + orderBy)) + { + ERROR_LOG(m_logger) << "Error preparing query: " << m_query->lastError().text(); + m_lastError = m_query->lastError(); + return false; + } + m_query->bindValue(QStringLiteral(":{{ fk.dbColumnName }}"), {{ fk.cppToQvPre }}{{ fk.cppMemberName }}{{ fk.cppToQvPost }}); + const auto cb =[&dataVec] (const Database::{{ cpp_struct.name }} &data) + { + dataVec.push_back(data); + }; + return selectFetchInternal{{ cpp_struct.name }}(cb); +} + +bool DatabaseInterfaceImpl::selectBy{{ fk.cppMemberNameCc }}(const std::function &cb, const {{ fk.cppType }} &{{ fk.cppMemberName }}, const QString &orderBy) +{ + SqlQueryJanitor janitor(m_query); + if (!selectPrepareInternal{{ cpp_struct.name }}(QStringLiteral(" WHERE {{ fk.dbColumnName }} = :{{ fk.dbColumnName }}") + orderBy)) + { + ERROR_LOG(m_logger) << "Error preparing query: " << m_query->lastError().text(); + m_lastError = m_query->lastError(); + return false; + } + m_query->bindValue(QStringLiteral(":{{ fk.dbColumnName }}"), {{ fk.cppToQvPre }}{{ fk.cppMemberName }}{{ fk.cppToQvPost }}); + return selectFetchInternal{{ cpp_struct.name }}(cb); +} +{% endfor %} + +bool DatabaseInterfaceImpl::selectPrepareInternal{{ cpp_struct.name }}(const QString &where) +{ + const QString tableName(getTableName(QStringLiteral("{{ cpp_struct.viewTableName }}"))); + const QString stmt(QStringLiteral("SELECT {% for row in cpp_struct.viewMember %}{{ row.dbColumnNameView }}{% if not forloop.last %}, {% endif %}{% endfor %}" + " FROM ") + tableName + where); + return m_query->prepare(stmt); +} + +bool DatabaseInterfaceImpl::selectFetchInternal{{ cpp_struct.name }}(const std::function &cb) +{ + if (!m_query->exec(m_bEmitMessageOnError)) + { + m_lastError = m_query->lastError(); + return false; + } + + Database::{{ cpp_struct.name }} data; + while (m_query->next()) + { + int i = 0; + {% for row in cpp_struct.viewMember %} + data.{{ row.cppMemberName }}{{ row.cppFromQvPre1 }} = {{ row.cppFromQvPre2 }}m_query->value(i++){{ row.cppFromQvPost }};{% endfor %} + cb(data); + } + + return true; +} + +bool DatabaseInterfaceImpl::select{{ cpp_struct.name }}({{ cpp_struct.primaryKey.cppType }} {{ cpp_struct.primaryKey.cppMemberName }}, Database::{{ cpp_struct.name }} &data) +{ + SqlQueryJanitor janitor(m_query); + int iCnt = 0; + const auto cb =[&data, &iCnt] (const Database::{{ cpp_struct.name }} &content) + { + data = content; + ++iCnt; + }; + const QString where = QStringLiteral(" WHERE {{ cpp_struct.primaryKey.dbColumnName }} = %1").arg({{ cpp_struct.primaryKey.cppMemberName }}); + if (!selectPrepareInternal{{ cpp_struct.name }}(where) || !selectFetchInternal{{ cpp_struct.name }}(cb)) + return false; + + return (iCnt == 1); +} + +bool DatabaseInterfaceImpl::remove{{ cpp_struct.name }}({{ cpp_struct.primaryKey.cppType }} {{ cpp_struct.primaryKey.cppMemberName }}) +{ + SqlQueryJanitor janitor(m_query); + const QString tableName(getTableName(QStringLiteral("{{ cpp_struct.primaryKey.dbTableName }}"))); + const QString stmt(QStringLiteral("DELETE FROM ") + tableName + + QStringLiteral(" WHERE {{ cpp_struct.primaryKey.dbColumnName }} = ") + + QString::number({{ cpp_struct.primaryKey.cppMemberName }})); + + if (!m_query->exec(stmt, m_bEmitMessageOnError)) + { + m_lastError = m_query->lastError(); + return false; + } + + return true; +} + +{% if cpp_struct.deleteWithoutPkAllowed %} {% for fk in cpp_struct.selectRemoveMember %} +bool DatabaseInterfaceImpl::remove{{ cpp_struct.nameCc }}By{{ fk.cppMemberNameCc }}(const {{ fk.cppType }} &{{ fk.cppMemberName }}) +{ + SqlQueryJanitor janitor(m_query); + const QString tableName(getTableName(QStringLiteral("{{ cpp_struct.primaryKey.dbTableName }}"))); + const QString stmt(QStringLiteral("DELETE FROM ") + tableName + + QStringLiteral(" WHERE {{ fk.dbColumnName }} = :{{ fk.cppMemberName }}")); + + if (!m_query->prepare(stmt)) + { + ERROR_LOG(m_logger) << "Error preparing query: " << m_query->lastError().text(); + m_lastError = m_query->lastError(); + return false; + } + + m_query->bindValue(QStringLiteral(":{{ fk.cppMemberName }}"), {{ fk.cppToQvPre }}{{ fk.cppMemberName }}{{ fk.cppToQvPost }}); + + + return m_query->exec(m_bEmitMessageOnError); +} +{% endfor %} {% endif %} +bool DatabaseInterfaceImpl::fixUpSequence{{ cpp_struct.nameCc }}() +{ + SqlQueryJanitor janitor(m_query); + const QString tableName(getTableName(QStringLiteral("{{ cpp_struct.primaryKey.dbTableName }}"))); + const QString stmt(QStringLiteral("SELECT setval('") + tableName + QStringLiteral("_{{ cpp_struct.primaryKey.dbColumnName }}_seq', COALESCE((SELECT MAX({{ cpp_struct.primaryKey.dbColumnName }})+1 FROM ") + + tableName + QStringLiteral("), 1),false)")); + + if (!m_query->exec(stmt, m_bEmitMessageOnError)) + { + m_lastError = m_query->lastError(); + return false; + } + + return true; +} + +bool DatabaseInterfaceImpl::truncate{{ cpp_struct.nameCc }}() +{ + SqlQueryJanitor janitor(m_query); + const QString tableName(getTableName(QStringLiteral("{{ cpp_struct.primaryKey.dbTableName }}"))); + const QString stmt(QStringLiteral("TRUNCATE TABLE ") + tableName + QStringLiteral(" CASCADE")); + + if (!m_query->exec(stmt, m_bEmitMessageOnError)) + { + m_lastError = m_query->lastError(); + return false; + } + + return true; +} +// Database::{{ cpp_struct.name }} end +{% endfor %} + +{% for ds in data_structs %} +bool DatabaseInterfaceImpl::select(QVector &dataVec) +{ + SqlQueryJanitor janitor(m_query); + const QString tableName(getTableName(QStringLiteral("{{ ds.viewTableName }}"))); + const QString stmt(QStringLiteral("SELECT {% for row in ds.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}" + " FROM ") + tableName); + + if (!m_query->exec(stmt, m_bEmitMessageOnError)) + { + m_lastError = m_query->lastError(); + return false; + } + + Database::{{ ds.name }} data; + while (m_query->next()) + { + int i = 0; + {% for row in ds.member %} + data.{{ row.cppMemberName }}{{ row.cppFromQvPre1 }} = {{ row.cppFromQvPre2 }}m_query->value(i++){{ row.cppFromQvPost }};{% endfor %} + dataVec.append(data); + } + + return true; +} +{% endfor %} +} // namespace {{ namespace }} +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_db_impl.h.grantlee b/Lts/Common/CodeGen/svc_db_impl.h.grantlee new file mode 100755 index 000000000..558c05dfc --- /dev/null +++ b/Lts/Common/CodeGen/svc_db_impl.h.grantlee @@ -0,0 +1,102 @@ +/** + * @RDE_HEADER@ + * Template for 'real' structures including insert/update/delete functions for db + * $Id$ + * $HeadURL$ + * $Author$ + */ + +{% autoescape off %} +// filename: {{ namespace_abbr }}_db_impl.h +#pragma once + +#include +#include +#include +#include +#include +#include + +#include <{{ include_prefix }}/{{ namespace_abbr }}_enums.hpp> +#include <{{ include_prefix }}/{{ namespace_abbr }}_db.h> +#include <{{ include_prefix }}/logger.h> + +class NSqlQuery; +namespace {{ namespace }} +{ +class {{ namespace_abbr_upper }}_COMMON_EXPORT DatabaseInterfaceImpl {% if not custom_db_procs %}final {%endif %}: public DatabaseInterface +{ + Q_OBJECT +public: + explicit DatabaseInterfaceImpl(int exerciseId); + ~DatabaseInterfaceImpl(); + + bool init(const QSqlDatabase &dbConnection) override final; + bool init(const QString &dbUser, + const QString &dbPasswd, + const QString &dbDriver, + const QString &dbHost, + const QString &dbName) override final; + + QString schemaName() const override final; + QString dbUser() const override final; + const QSqlDatabase &database() const override final { return m_dbConnection; } + void setEmitMessageOnError(bool bEmit) override final; + QSqlError getLastError() const override final; + + {% for cpp_struct in cpp_structs %} + + // Database::{{ cpp_struct.name }} + bool insert(Database::{{ cpp_struct.name }} &data) override final; + bool upsert(Database::{{ cpp_struct.name }} &data) override final; + bool update(const Database::{{ cpp_struct.name }} &data) override final; +{% if cpp_struct.insertStructures|length == 1 %} + bool insert(std::vector &cont) override final; + bool upsert(std::vector &cont) override final; + bool update(const std::vector &cont) override final; +{% endif %} + bool select(QVector &dataVec) override final; + bool select(const std::function &cb) override final; + {% for fk in cpp_struct.selectRemoveMember %} + bool selectBy{{ fk.cppMemberNameCc }}(QVector &dataVec, const {{ fk.cppType }} &{{ fk.cppMemberName }}, const QString &orderBy = QString()) override final; + bool selectBy{{ fk.cppMemberNameCc }}(const std::function &cb, const {{ fk.cppType }} &{{ fk.cppMemberName }}, const QString &orderBy = QString()) override final; + {% endfor %} +protected: + bool selectPrepareInternal{{ cpp_struct.name }}(const QString &where); + bool selectFetchInternal{{ cpp_struct.name }}(const std::function &cb); +public: + bool select{{ cpp_struct.name }}({{ cpp_struct.primaryKey.cppType }} {{ cpp_struct.primaryKey.cppMemberName }}, Database::{{ cpp_struct.name }} &data) override final; + bool remove{{ cpp_struct.name }}({{ cpp_struct.primaryKey.cppType }} {{ cpp_struct.primaryKey.cppMemberName }}) override final; + {% if cpp_struct.deleteWithoutPkAllowed %} {% for fk in cpp_struct.selectRemoveMember %} + bool remove{{ cpp_struct.nameCc }}By{{ fk.cppMemberNameCc }}(const {{ fk.cppType }} &{{ fk.cppMemberName }}) override final; + {% endfor %} {% endif %} + bool fixUpSequence{{ cpp_struct.name }}() override final; + bool truncate{{ cpp_struct.name }}() override final; + // Database::{{ cpp_struct.name }} + {% endfor %} + + {% for data_struct in data_structs %} + // read only struct{{ data_struct.name }} + bool select(QVector &dataVec) override final; + {% endfor %} + +protected: + QString getTableName(const QString &tableName) const; +{% if has_db_notifications %} +private Q_SLOTS: + void onNotification(const QString &name, QSqlDriver::NotificationSource source, const QVariant &payload); +{% endif %} +protected: + QSqlDatabase m_dbConnection; + std::unique_ptr m_query; + QSqlError m_lastError; + int m_exerciseId; + bool m_bEmitMessageOnError; + bool m_bCanHandleSchemas; + bool m_ownDatabaseConnection; + LOGGER_T m_logger; +private: + bool initInternal(); +};// class DatabaseInterfaceImpl +} // namespace {{ namespace }} +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_db_structs.cpp.grantlee b/Lts/Common/CodeGen/svc_db_structs.cpp.grantlee new file mode 100755 index 000000000..baa4ee475 --- /dev/null +++ b/Lts/Common/CodeGen/svc_db_structs.cpp.grantlee @@ -0,0 +1,95 @@ +/** + * @RDE_HEADER@ + * Template for 'real' structures including insert/update/delete functions for db + * $Id$ + * $HeadURL$ + * $Author$ + */ + +{% autoescape off %} +// filename: {{ namespace_abbr }}_db_structs.cpp +#include "{{ namespace_abbr }}_db_structs.h" +#include "{{ namespace_abbr }}_export.h" + +namespace {{ namespace }} +{ +namespace Database +{ +QString jsonValueToJsonString(const QJsonValue &value) +{ + return value.isNull() ? QStringLiteral("{}") : value.toString(); +} + +QString qVariantToJsonString(const QVariant &value) +{ + return value.isNull() ? QStringLiteral("{}") : value.toString(); +} + +QString fixupEmptyJsonString(const QString &str) +{ + return str.isEmpty() ? QStringLiteral("{}") : str; +} + + +// QPointF +QJsonValue toJson(const QPointF &element) +{ + QJsonObject obj; + obj.insert(QStringLiteral("x"), QJsonValue(element.x())); + obj.insert(QStringLiteral("y"), QJsonValue(element.y())); + return QJsonValue(obj); +} +template<> +QPointF fromJson(const QJsonValue &val) +{ + QJsonObject obj = val.toObject(); + return QPointF(obj.value(QStringLiteral("x")).toDouble(), obj.value(QStringLiteral("y")).toDouble()); +} +{% if need_point3d %} +// QPoint3D +QJsonValue toJson(const LtsUtils::QPoint3D &element) +{ + QJsonObject obj; + obj.insert(QStringLiteral("x"), QJsonValue(element.x())); + obj.insert(QStringLiteral("y"), QJsonValue(element.y())); + obj.insert(QStringLiteral("z"), QJsonValue(element.z())); + return QJsonValue(obj); +} +template<> +LtsUtils::QPoint3D fromJson(const QJsonValue &val) +{ + QJsonObject obj = val.toObject(); + return LtsUtils::QPoint3D(obj.value(QStringLiteral("x")).toDouble(), obj.value(QStringLiteral("y")).toDouble(), obj.value(QStringLiteral("z")).toDouble()); +} +{% endif %} + +{% for cpp_struct in cpp_structs %} +// +// Struct: {{ cpp_struct.name }} +// +QJsonObject toJson(const {{ cpp_struct.name }} &element) +{ + QJsonObject obj; + {% for row in cpp_struct.jsonMember %} + {{ row.dbToJsonElement }}; + {% endfor %} + return obj; +} + +template<> +{{ cpp_struct.name }} fromJson(const QJsonValue &value) +{ + const QJsonObject objElement(value.toObject()); + {{ cpp_struct.name }} element; + + {% for row in cpp_struct.jsonMember %} + { {{ row.jsonElementToDb }}; } {% endfor %} + + return element; +} +// {{ cpp_struct.name }} end +{% endfor %} + +} // namespace Database +} // namespace {{ namespace }} +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_db_structs.h.grantlee b/Lts/Common/CodeGen/svc_db_structs.h.grantlee new file mode 100755 index 000000000..1782c0bd8 --- /dev/null +++ b/Lts/Common/CodeGen/svc_db_structs.h.grantlee @@ -0,0 +1,87 @@ +/** + * @RDE_HEADER@ + * Template for 'real' structures including insert/update/delete functions for db + * $Id$ + * $HeadURL$ + * $Author$ + */ + +{% autoescape off %} +// filename: {{ namespace_abbr }}_db_structs.h +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +{% if need_point3d %} +#include +{% endif %} +{% if need_pgarray %} +#include +{% endif %} + +#include <{{ include_prefix }}/{{ namespace_abbr }}_export.h> +#include <{{ include_prefix }}/{{ namespace_abbr }}_enums.hpp> + +{% for ext_service in ext_services.member %} +#include <{{ ext_service.baseFilePath }}.hpp> +{% endfor %} + +namespace {{ namespace }} +{ +namespace Database +{ +{% for cpp_struct in cpp_structs %} + // {{ cpp_struct.name }} + struct {{ cpp_struct.name }} + { + {% for m in cpp_struct.cppMember %}{{ m.cppType }} {{ m.cppMemberName }}{% if m.cppDefault %} = {{ m.cppDefault }}{% endif %}; + {% endfor %} + };{% endfor %} + +{% for data_struct in data_structs %} + // read only struct {{ data_struct.name }} + struct {{ data_struct.name }} + { + {% for m in data_struct.cppMember %}{{ m.cppType }} {{ m.cppMemberName }}{% if m.cppDefault %} = {{ m.cppDefault }}{% endif %}; + {% endfor %} + };{% endfor %} + + // converting DbStructs to/from JSON + template + T fromJson(const QJsonValue &value); +{% for cpp_struct in cpp_structs %} + // {{ cpp_struct.name }} + template <> {{ namespace_abbr_upper }}_{{ library_target }}_EXPORT {{ cpp_struct.name }} fromJson(const QJsonValue &value); + {{ namespace_abbr_upper }}_{{ library_target }}_EXPORT QJsonObject toJson(const {{ cpp_struct.name }} &element);{% endfor %} + + // vector of database struct to/from JSON-array + template + QJsonArray toJson(const QVector &dataVec) + { + QJsonArray arr; + for (const T &data : dataVec) + arr.append(QJsonValue(toJson(data))); + + return arr; + } + + template + std::vector fromJson(const QJsonArray &arr) + { + std::vector dataVec; + dataVec.reserve(arr.size()); + for (const QJsonValue &val : arr) + dataVec.push_back(fromJson(val)); + + return dataVec; + } +};// namespace Database + +} // namespace {{ namespace }} +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_dtc.h.grantlee b/Lts/Common/CodeGen/svc_dtc.h.grantlee new file mode 100755 index 000000000..4e4f87c7b --- /dev/null +++ b/Lts/Common/CodeGen/svc_dtc.h.grantlee @@ -0,0 +1,88 @@ +/** + * {{ namespace_abbr_upper }} Data Topic Client Interface + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Hanna E. Rathje, SLSE, 2016-01-04 + */ + +{% autoescape off %} +// filename: {{ namespace_abbr }}_dtc.h +#pragma once + +#include +#include +#include +#include + +#include <{{ include_prefix }}/{{ namespace_abbr }}_export.h> +#include <{{ include_prefix }}/{{ namespace_abbr }}.hpp> + +namespace DDSConnector +{ +class DdsConnector; +} + +namespace {{ namespace }} +{ +namespace Interface +{ +class {{ namespace_abbr_upper }}_COMMON_EXPORT Dtc : public QObject +{ + Q_OBJECT +protected: + Dtc() = default; +public: + virtual ~Dtc() = default; + + /** + * Create a new instance for {{ namespace }}::Interface::Dtc. + */ + static std::shared_ptr create(const std::shared_ptr &ddsConnector); + + /** + * A unique identifier for this DTC + */ + static inline const char *identifier() { return "{{ namespace }}.Interface.Dtc"; } + + /** + * initialize this Dts instance with all data topic readers + */ + virtual bool enableAll(bool enable = true) = 0; + /** + * only initialize the specific reader (can be called instead enableAll()) + */ +{% for dt in dataTopics.member %} {% if dt.dtcImpl %} + virtual bool enable{{ dt.name }}(bool enable = true) = 0; + virtual bool is{{ dt.name }}Enabled() const = 0; + virtual bool waitForHistoricalData{{ dt.name }}(int timeoutMSec = -1) = 0; + {% endif %} {% endfor %} + + {% for cp in custom_dtc_procs %} + // custom procs, body must be implemented by user + virtual {{ cp.signature }} = 0; + {% endfor %} + + {% for dt in dataTopics.member %} +public: + // + // Data Topic '{{ dt.name }}' + // {% if dt.key.name %} + virtual QMap<{{ dt.key.cppType }}, {{ namespace }}::{{ dt.type }}> {{ dt.nameFlc }}s() = 0; + virtual bool {{ dt.nameFlc }}s(const std::function &cb) = 0; + // when the cb returns true, the loop is stopped (useful when searching for a specific topic) + virtual bool {{ dt.nameFlc }}s(const std::function &cb) = 0; + virtual {{ namespace }}::{{ dt.type }} {{ dt.nameFlc }}({{ dt.key.cppTypeCr }} {{ dt.key.nameFlc }}) = 0; + {% else %} + virtual {{ namespace }}::{{ dt.type }} {{ dt.nameFlc }}() = 0; + {% endif %} +Q_SIGNALS: + void {{ dt.nameFlc }}Changed(const {{ namespace }}::{{ dt.type }} &data, DataModification modType); + + {% endfor %} + +}; + +} // namespace Interface +} // namespace {{ namespace }} +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_dtc_impl.cpp.grantlee b/Lts/Common/CodeGen/svc_dtc_impl.cpp.grantlee new file mode 100755 index 000000000..4a7caff62 --- /dev/null +++ b/Lts/Common/CodeGen/svc_dtc_impl.cpp.grantlee @@ -0,0 +1,304 @@ +/** + * {{ namespace_abbr_upper }} Data Topic Client Interface + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Christian Ehrlicher, SLSE, 2016-04-07 + */ + +{% autoescape off %} +// filename: {{ namespace_abbr }}_dtc_impl.cpp + +#include "{{ namespace_abbr }}_dtc_impl.h" +{% if custom_dtc_procs %} +#include <{{ include_prefix }}/{{ namespace_abbr }}_dtc_impl_custom.h> +{% endif %} +#include +#include +#include +#include +#include +#include + +namespace {{ namespace }} +{ +namespace Interface +{ +// initializer function +std::shared_ptr Dtc::create(const std::shared_ptr &ddsConnector) +{ + // share only for main thread + if (qApp->thread() != QThread::currentThread()) + {% if custom_dtc_procs %} + return std::make_shared(ddsConnector); + {% else %} + return std::make_shared(ddsConnector); + {% endif %} + + static std::unordered_map> s_availableConnectors; + + auto it = s_availableConnectors.find(ddsConnector.get()); + if (it != s_availableConnectors.end()) + { + auto ret = it->second.lock(); + if (ret) + return ret; + s_availableConnectors.erase(it); + } + {% if custom_dtc_procs %} + auto ret = std::make_shared(ddsConnector); + {% else %} + auto ret = std::make_shared(ddsConnector); + {% endif %} + s_availableConnectors.insert(std::make_pair(ddsConnector.get(), ret)); + return std::move(ret); // c++11 defect +} + +// DtcImpl +DtcImpl::DtcImpl(const std::shared_ptr &ddsConnector) + : Dtc() + , m_ddsConnector(ddsConnector) + , m_logger(getLogger("Dtc")) +{} + +DtcImpl::~DtcImpl() +{ + enableAll(false); +} + +bool DtcImpl::enableAll(bool enable) +{ {% for dt in dataTopics.member %} {% if dt.dtcImpl %} {% if dt.autosubscribe %} + if (!enable{{ dt.name }}(enable)) + { + ERROR_LOG(m_logger) << "Error initializing data topic {{ dt.name }}"; + return false; + } + {% endif %} {% endif %} {% endfor %} + return true; +} + +{% for dt in dataTopics.member %} {% if dt.dtcImpl %} {% if dt.key.name %} +template <> +struct TopicConstraints<{{ dt.type }}> +{ + using KEY = {{ dt.key.cppType }}; +}; +{% endif %} {% endif %} {% endfor %} + + +template +bool DtcImpl::checkEnabled(const DTC &dtc) const +{ + if (!dtc) + { + using TOPIC = typename DtcConstraints::TOPIC ; + WARNING_LOG(m_logger) << "Error retrieving data topic " + << DDS::topic_type_name::value() << " - not enabled"; + Q_ASSERT(false); + return false; + } + return true; +} + +template +bool DtcImpl::waitForHistoricalData(DTC &dtc, int timeoutMSec) +{ + if (!checkEnabled(dtc)) + return false; + return dtc->waitForHistoricalData(timeoutMSec); +} + +template ::KEY> +K getKey(const T &val); + +template +QMap DtcImpl::getAll(const DTC &dtc) const +{ + if (!checkEnabled(dtc)) + return {}; + + QMap retMap; + for (const auto &sample : dtc->readAlive()) + retMap.insert(getKey(sample.data()), sample); + + return retMap; +} + +template +T DtcImpl::getOne(const DTC &dtc, const C &cont, const K &key) const +{ + if (!checkEnabled(dtc)) + return {}; + const auto it = cont.find(key); + return it == cont.end() ? T() : it.value(); +} + +template +bool DtcImpl::iterateAll(const DTC &dtc, const C &cont, const std::function &cb) const +{ + if (!checkEnabled(dtc)) + return false; + + for (const auto &sample : cont) + cb(sample); + + return true; +} + +template +bool DtcImpl::iterateUntil(const DTC &dtc, const C &cont, const std::function &cb) const +{ + if (!checkEnabled(dtc)) + return false; + + for (const auto &sample : cont) + { + if (cb(sample)) + return true; + } + return false; +} + +{% for dt in dataTopics.member %} {% if dt.dtcImpl %} +// +// Data Topic '{{ dt.name }}' +// +bool DtcImpl::enable{{ dt.name }}(bool enable) +{ + if (enable) + { + if (m_dtc{{ dt.name }}) + { + INFO_LOG(m_logger) << "{{ dt.name }} already enabled!"; + return true; + } + m_dtc{{ dt.name }}.reset(m_ddsConnector->createDTC()); + assert(m_dtc{{ dt.name }}.get()); + m_dtc{{ dt.name }}->setSubscriberCallback([this](const {{ namespace }}::{{ dt.name }} &data, DataModification modType) + { + {{ dt.nameFlc }}ChangedInternal(data, modType); + }); + } + else + { + m_dtc{{ dt.name }} = nullptr; + } + + return true; +} + +bool DtcImpl::waitForHistoricalData{{ dt.name }}(int timeoutMSec) +{ + + if (!is{{ dt.name }}Enabled()) + { + enable{{ dt.name }}(); + DEBUG_LOG(m_logger) << "NEED TO ENABLE ! {{ dt.name }}" ; + } + + if (!waitForHistoricalData(m_dtc{{ dt.name }}, timeoutMSec)) + return false; +{% if dt.key.name %} + m_{{ dt.nameFlc }}Cont = getAll(m_dtc{{ dt.name }}); +{% else %} + auto samples = m_dtc{{ dt.name }}->readAlive(); + if (samples.length() == 0) + return false; + m_{{ dt.nameFlc }} = samples[samples.length() - 1].data(); +{% endif %} + return true; +} + +{% if dt.key.name %} +template <> +{{ dt.key.cppType }} getKey(const {{ dt.type }} &val) +{ + return val.{{ dt.key.name }}(); +} + +QMap<{{ dt.key.cppType }},{{ namespace }}::{{ dt.type }}> DtcImpl::{{ dt.nameFlc }}s() +{ + if (!is{{ dt.name }}Enabled()) + { + enable{{ dt.name }}(); + DEBUG_LOG(m_logger) << "NEED TO ENABLE ! {{ dt.name }}" ; + waitForHistoricalData{{ dt.name }}(10); + } + return m_{{ dt.nameFlc }}Cont; +} + +bool DtcImpl::{{ dt.nameFlc }}s(const std::function &cb) +{ + if (!is{{ dt.name }}Enabled()) + { + enable{{ dt.name }}(); + DEBUG_LOG(m_logger) << "NEED TO ENABLE ! {{ dt.name }}" ; + waitForHistoricalData{{ dt.name }}(10); + } + return iterateAll(m_dtc{{ dt.name }}, m_{{ dt.nameFlc }}Cont, cb); +} + +bool DtcImpl::{{ dt.nameFlc }}s(const std::function &cb) +{ + if (!is{{ dt.name }}Enabled()) + { + enable{{ dt.name }}(); + DEBUG_LOG(m_logger) << "NEED TO ENABLE ! {{ dt.name }}" ; + waitForHistoricalData{{ dt.name }}(10); + } + return iterateUntil(m_dtc{{ dt.name }}, m_{{ dt.nameFlc }}Cont, cb); +} + +{{ namespace }}::{{ dt.type }} DtcImpl::{{ dt.nameFlc }}({{ dt.key.cppTypeCr }} {{ dt.key.nameFlc }}) +{ + if (!is{{ dt.name }}Enabled()) + { + enable{{ dt.name }}(); + DEBUG_LOG(m_logger) << "NEED TO ENABLE ! {{ dt.name }}" ; + waitForHistoricalData{{ dt.name }}(10); + } + return getOne(m_dtc{{ dt.name }}, m_{{ dt.nameFlc }}Cont, {{ dt.key.nameFlc }}); +} + +void DtcImpl::{{ dt.nameFlc }}ChangedInternal(const {{ namespace }}::{{ dt.type }} &data, DataModification modType) +{ + switch (modType) + { + case DataModification::eAdded: + case DataModification::eModified: + m_{{ dt.nameFlc }}Cont[getKey(data)] = data; + break; + case DataModification::eRemoved: + m_{{ dt.nameFlc }}Cont.remove(getKey(data)); + break; + } + emit {{ dt.nameFlc }}Changed(data, modType); +} + +{% else %} +{{ namespace }}::{{ dt.type }} DtcImpl::{{ dt.nameFlc }}() +{ + return m_{{ dt.nameFlc }}; +} +void DtcImpl::{{ dt.nameFlc }}ChangedInternal(const {{ namespace }}::{{ dt.type }} &data, DataModification modType) +{ + switch (modType) + { + case DataModification::eAdded: + case DataModification::eModified: + m_{{ dt.nameFlc }} = data; + break; + case DataModification::eRemoved: + m_{{ dt.nameFlc }} = {{ namespace }}::{{ dt.type }}(); + break; + } + emit {{ dt.nameFlc }}Changed(data, modType); +} + +{% endif %} {% endif %} {% endfor %} + +} // namespace Interface +} // namespace {{ namespace }} +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_dtc_impl.h.grantlee b/Lts/Common/CodeGen/svc_dtc_impl.h.grantlee new file mode 100755 index 000000000..73d6588ad --- /dev/null +++ b/Lts/Common/CodeGen/svc_dtc_impl.h.grantlee @@ -0,0 +1,99 @@ +/** + * {{ namespace_abbr_upper }} Data Topic Client Interface + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Christian Ehrlicher, SLSE, 2016-04-07 + */ + +{% autoescape off %} +// filename: {{ namespace_abbr }}_dtc_impl.h +#pragma once + +#include <{{ include_prefix }}/{{ namespace_abbr }}_dtc.h> +#include <{{ include_prefix }}/{{ namespace_abbr }}_dtc_types.h> +#include <{{ include_prefix }}/logger.h> + +#include + +#if !defined(MAKE_SVC{{ namespace_abbr_upper }}CLIENT_LIB) +#error "You are not allowed to include this file in your code!" +#endif + +namespace {{ namespace }} +{ +namespace Interface +{ +template +struct DtcConstraints +{ + using DTC = typename UQ_DTC::element_type; + using TOPIC = typename DTC::TopicType; +}; + +template +struct TopicConstraints +{}; + +class DtcImpl {% if not custom_dtc_procs %}final {%endif %}: public Dtc +{ + Q_OBJECT +public: + explicit DtcImpl(const std::shared_ptr &ddsConnector); + ~DtcImpl(); + + bool enableAll(bool enable = true) override; + {% for dt in dataTopics.member %} {% if dt.dtcImpl %} + // + // Data Topic '{{ dt.name }}' + // +public: + bool enable{{ dt.name }}(bool enable = true) override final; + bool is{{ dt.name }}Enabled() const override final { return m_dtc{{ dt.name }} != nullptr; } + bool waitForHistoricalData{{ dt.name }}(int timeoutMSec = -1) override final; +{% if dt.key.name %} + QMap<{{ dt.key.cppType }}, {{ namespace }}::{{ dt.type }}> {{ dt.nameFlc }}s() override final; + bool {{ dt.nameFlc }}s(const std::function &cb) override final; + bool {{ dt.nameFlc }}s(const std::function &cb) override final; + {{ namespace }}::{{ dt.type }} {{ dt.nameFlc }}({{ dt.key.cppTypeCr }} {{ dt.key.nameFlc }}) override final; +{% else %} + {{ namespace }}::{{ dt.type }} {{ dt.nameFlc }}() override final; +{% endif %} {% endif %} {% endfor %} + +private: + template + bool checkEnabled(const DTC &dtc) const; + template + bool waitForHistoricalData(DTC &dtc, int timeoutMSec); + template ::TOPIC, + typename K = typename TopicConstraints::KEY> + QMap getAll(const DTC &dtc) const; + template ::TOPIC, + typename K = typename TopicConstraints::KEY> + T getOne(const DTC &dtc, const C &cont, const K &key) const; + template ::TOPIC> + bool iterateAll(const DTC &dtc, const C &cont, const std::function &cb) const; + template ::TOPIC> + bool iterateUntil(const DTC &dtc, const C &cont, const std::function &cb) const; + +protected: + std::shared_ptr m_ddsConnector; + +{% for dt in dataTopics.member %} +{% if dt.dtcImpl %} + void {{ dt.nameFlc }}ChangedInternal(const {{ namespace }}::{{ dt.type }} &data, DataModification modType); + std::unique_ptr m_dtc{{ dt.name }}; {% if dt.key.name %} + QMap<{{ dt.key.cppType }}, {{ namespace }}::{{ dt.type }}> m_{{ dt.nameFlc }}Cont; {% else %} + {{ namespace }}::{{ dt.type }} m_{{ dt.nameFlc }}; +{% endif %} {% endif %} {% endfor %} + + LOGGER_T m_logger; +}; + +} // namespace Interface +} // namespace {{ namespace }} +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_dts.h.grantlee b/Lts/Common/CodeGen/svc_dts.h.grantlee new file mode 100755 index 000000000..224a680ea --- /dev/null +++ b/Lts/Common/CodeGen/svc_dts.h.grantlee @@ -0,0 +1,69 @@ +/** + * {{ namespace_abbr_upper }} Data Topic Server Interface + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Hanna E. Rathje, SLSE, 2016-01-05 + */ + +{% autoescape off %} +// filename: {{ namespace_abbr }}_dts.h +#pragma once + +#include <{{ include_prefix }}/{{ namespace_abbr }}_export.h> +#include <{{ include_prefix }}/{{ namespace_abbr }}.hpp> + + +namespace DDSConnector +{ +class DdsConnector; +} + +namespace {{ namespace }} +{ +namespace Interface +{ +class Dts +{ +protected: + Dts() = default; +public: + virtual ~Dts() = default; + + /** + * Create a new instance for {{ namespace }}::Interface::Dts. + * The caller takes ownership of the returned instance + */ + {{ namespace_abbr_upper }}_SERVER_EXPORT static std::shared_ptr create(const std::shared_ptr &ddsConnector); + + /** + * A unique identifier for this DTS + */ + {{ namespace_abbr_upper }}_SERVER_EXPORT static inline const char *identifier() { return "{{ namespace }}.Interface.Dts"; } + + /** + * initialize this Dts instance with all data topic writers + */ + virtual bool enableAll(bool enable = true) = 0; + /** + * only initialize the specific writer (can be called instead enableAll()) + */ + {% for dt in dataTopics.member %} {% if dt.dtsImpl %} + virtual bool enable{{ dt.name }}(bool enable = true) = 0; + virtual bool is{{ dt.name }}Enabled() const = 0; + {% endif %} {% endfor %} + +{% for dt in dataTopics.member %} + // + // Data Topic '{{ dt.name }}' + // + virtual bool publish(const {{ namespace }}::{{ dt.type }} &data) = 0; + {% if dt.key.name %} + virtual bool remove{{ dt.name }}ByKey({{ dt.key.cppTypeCr }} {{ dt.key.nameFlc }}, bool errorMsgIfSampleDoesNotExists = true) = 0; + {% endif %} + {% endfor %} +}; + +} // namespace Interface +} // namespace {{ namespace }} +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_dts_impl.cpp.grantlee b/Lts/Common/CodeGen/svc_dts_impl.cpp.grantlee new file mode 100755 index 000000000..251c037f7 --- /dev/null +++ b/Lts/Common/CodeGen/svc_dts_impl.cpp.grantlee @@ -0,0 +1,100 @@ +/** + * {{ namespace_abbr_upper }} Data Topic Server Interface + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Christian Ehrlicher, SLSE, 2016-04-07 + */ + +{% autoescape off %} +// filename: {{ namespace_abbr }}_dts_impl.cpp +#include "{{ namespace_abbr }}_dts_impl.h" +#include + +namespace {{ namespace }} +{ +namespace Interface +{ +// initializer function +std::shared_ptr Dts::create(const std::shared_ptr &ddsConnector) +{ + return std::make_shared(ddsConnector); +} + +// DtsImpl +DtsImpl::DtsImpl(const std::shared_ptr &ddsConnector) + : Dts() + , m_ddsConnector(ddsConnector) + , m_logger(getLogger("Dts")) +{} + +DtsImpl::~DtsImpl() +{ + enableAll(false); +} + +bool DtsImpl::enableAll(bool enable) +{ + {% for dt in dataTopics.member %} {% if dt.dtsImpl %} + if (!enable{{ dt.name }}(enable)) + { + WARNING_LOG(m_logger) << "Error initializing data topic {{ dt.name }}"; + return false; + } + {% endif %} {% endfor %} + return true; +} + +{% for dt in dataTopics.member %} {% if dt.dtsImpl %} +// +// Data Topic '{{ dt.name }}' +// +bool DtsImpl::enable{{ dt.name }}(bool enable) +{ + if (enable) + { + if (m_dts{{ dt.name }}) + { + INFO_LOG(m_logger) << "{{ ft.name }} already enabled!"; + return true; + } + m_dts{{ dt.name }}.reset(m_ddsConnector->createDTS()); + assert(m_dts{{ dt.name }}.get()); + } + else + { + m_dts{{ dt.name }} = nullptr; + } + return true; +} + +bool DtsImpl::publish(const {{ namespace }}::{{ dt.type }} &data) +{ + if (!m_dts{{ dt.name }}) + { + WARNING_LOG(m_logger) << "Error publishing data topic {{ dt.name }} - not enabled"; + Q_ASSERT(false); + return false; + } + return m_dts{{ dt.name }}->write(data); +} + +{% if dt.key.name %} +bool DtsImpl::remove{{ dt.name }}ByKey({{ dt.key.cppTypeCr }} {{ dt.key.nameFlc }}, bool errorMsgIfSampleDoesNotExists) +{ + if (!m_dts{{ dt.name }}) + { + WARNING_LOG(m_logger) << "Error removing data topic {{ dt.name }} - not enabled"; + Q_ASSERT(false); + return false; + } + {{ namespace }}::{{ dt.name }} data; + data.{{ dt.key.name }}({{ dt.key.name }}); + return m_dts{{ dt.name }}->remove(data, errorMsgIfSampleDoesNotExists); +} +{% endif %} +{% endif %} {% endfor %} + +} // namespace Interface +} // namespace {{ namespace }} +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_dts_impl.h.grantlee b/Lts/Common/CodeGen/svc_dts_impl.h.grantlee new file mode 100755 index 000000000..06852a4c7 --- /dev/null +++ b/Lts/Common/CodeGen/svc_dts_impl.h.grantlee @@ -0,0 +1,58 @@ +/** + * {{ namespace_abbr_upper }} Data Topic Server Interface + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Christian Ehrlicher, SLSE, 2016-04-07 + */ + +{% autoescape off %} +// filename: {{ namespace_abbr }}_dts_impl.h +#pragma once + +#include + +#include <{{ include_prefix }}/{{ namespace_abbr }}_dts.h> +#include <{{ include_prefix }}/{{ namespace_abbr }}_dts_types.h> +#include <{{ include_prefix }}/logger.h> + +#include + +#if !defined(MAKE_SVC{{ namespace_abbr_upper }}SERVER_LIB) +#error "You are not allowed to include this file in your code!" +#endif + +namespace {{ namespace }} +{ +namespace Interface +{ +class DtsImpl final : public Dts +{ +public: + explicit DtsImpl(const std::shared_ptr &ddsConnector); + ~DtsImpl(); + + bool enableAll(bool enable = true) override; + + {% for dt in dataTopics.member %} {% if dt.dtsImpl %} + // + // Data Topic '{{ dt.name }}' + // + bool enable{{ dt.name }}(bool enable = true) override; + bool is{{ dt.name }}Enabled() const override final { return m_dts{{ dt.name }} != nullptr; } + bool publish(const {{ namespace }}::{{ dt.type }} &data) override; + {% if dt.key.name %} + bool remove{{ dt.name }}ByKey({{ dt.key.cppTypeCr }} {{ dt.key.nameFlc }}, bool errorMsgIfSampleDoesNotExists = true) override; + {% endif %} {% endif %} {% endfor %} + +protected: + std::shared_ptr m_ddsConnector; + {% for dt in dataTopics.member %} {% if dt.dtsImpl %} + std::unique_ptr m_dts{{ dt.name }}; + {% endif %} {% endfor %} + LOGGER_T m_logger; +}; + +} // namespace Interface +} // namespace {{ namespace }} +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_enums.idl.grantlee b/Lts/Common/CodeGen/svc_enums.idl.grantlee new file mode 100755 index 000000000..2ea8976a5 --- /dev/null +++ b/Lts/Common/CodeGen/svc_enums.idl.grantlee @@ -0,0 +1,32 @@ +/** + * @RDE_HEADER@ + * Template for idl struct definitions + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Hanna E. Rathje, SLSE, 2015-08-19 + */ + +{% autoescape off %} +// filename: {{ namespace_abbr }}_enums.idl +#ifndef {{ namespace_abbr }}_enums_idl_included +#define {{ namespace_abbr }}_enums_idl_included + +//@copy #include <{{ include_prefix }}/{{ namespace_abbr }}_export.h> // #define NDDSUSERDllExport for linux + + +module {{ namespace }} +{ + {% for idl_enum in idl_enums %} + enum {{ idl_enum.name }} + { + {% for idl_member in idl_enum.member %} + {{ idl_member.key }} = {{ idl_member.value }}{% if not forloop.last %},{% endif %} {% endfor %} + }; + + {% endfor %} + +}; // module {{ namespace }} + +#endif +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_ftc.h.grantlee b/Lts/Common/CodeGen/svc_ftc.h.grantlee new file mode 100755 index 000000000..20c31ae53 --- /dev/null +++ b/Lts/Common/CodeGen/svc_ftc.h.grantlee @@ -0,0 +1,78 @@ +/** + * {{ namespace }} Function Topic Client interface + * $Id$ + * $HeadURL$ + * $Author$ + */ + +{% autoescape off %} +// filename: {{ namespace_abbr }}_ftc.h +#pragma once + +#include <{{ include_prefix }}/{{ namespace_abbr }}_export.h> +#include <{{ include_prefix }}/{{ namespace_abbr }}.hpp> +#include + + +namespace DDSConnector +{ +class DdsConnector; +} + +namespace {{ namespace }} +{ +namespace Interface +{ +class Ftc +{ +protected: + Ftc() = default; +public: + virtual ~Ftc() = default; + + /** + * Create a new instance for {{ namespace }}::Interface::Ftc. + * The caller takes ownership of the returned instance! + */ + {{ namespace_abbr_upper }}_CLIENT_EXPORT static std::shared_ptr create(const std::shared_ptr &ddsConnector, bool forceCreate = false); + + /** + * A unique identifier for this FTC + */ + {{ namespace_abbr_upper }}_CLIENT_EXPORT static inline const char *identifier() { return "{{ namespace }}.Interface.Ftc"; } + + /** + * initialize this Ftc instance with all function topic clients + */ + virtual bool enableAll(bool enable = true) = 0; + /** + * only initialize the specific function topic client (can be called instead enableAll()) + */ + {% for ft in functionTopics.member %} + virtual bool enable{{ ft.name }}(bool enable = true) = 0; + virtual bool is{{ ft.name }}Enabled() const = 0; + {% endfor %} + +{% for ft in functionTopics.member %} + // + // Function Topic '{{ ft.name }}' + // + class {{ ft.replyHandler }} + { + public: + {{ ft.replyHandler }}() = default; + virtual ~{{ ft.replyHandler }}() = default; + virtual void {{ ft.onReply }}(const {{ ft.replyTypeFro }} &reply) = 0; + }; + virtual int64_t {{ ft.requestFlc }}(const {{ ft.requestType }} &request, {{ ft.replyHandler }} *handler) = 0; + virtual int64_t {{ ft.requestFlc }}(const {{ ft.requestType }} &request, const std::function &cb) = 0; + virtual {% if ft.isCoreFt %}int32_t{% else %}{{ ft.replyTypeFro }}{% endif %} {{ ft.requestFlc }}(const {{ ft.requestType }} &request) noexcept(false) = 0; // await + virtual int64_t last{{ ft.request }}UniqueId() const = 0; + + {% endfor %} +}; + + +} // namespace Interface +} // namespace {{ namespace }} +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_ftc_impl.cpp.grantlee b/Lts/Common/CodeGen/svc_ftc_impl.cpp.grantlee new file mode 100755 index 000000000..c90871ccd --- /dev/null +++ b/Lts/Common/CodeGen/svc_ftc_impl.cpp.grantlee @@ -0,0 +1,254 @@ +/** + * {{ namespace }} Function Topic Client implementation + * $Id$ + * $HeadURL$ + * $Author$ + */ + +{% autoescape off %} +// filename: {{ namespace_abbr }}_ftc_impl.cpp +#include "{{ namespace_abbr }}_ftc_impl.h" + +#include +#include + +#include +#include + +#include +#include + + +//using namespace std::literals::chrono_literals; + + +namespace {{ namespace }} +{ +namespace Interface +{ +// initializer function +Q_DECL_EXPORT std::shared_ptr Ftc::create(const std::shared_ptr &ddsConnector, bool forceCreate) +{ + // share only for main thread + if (qApp->thread() != QThread::currentThread() || forceCreate) + return std::make_shared(ddsConnector); + + static std::unordered_map> s_availableConnectors; + + auto it = s_availableConnectors.find(ddsConnector.get()); + if (it != s_availableConnectors.end()) + { + auto ret = it->second.lock(); + if (ret) + return ret; + s_availableConnectors.erase(it); + } + + auto ret = std::make_shared(ddsConnector); + s_availableConnectors.insert(std::make_pair(ddsConnector.get(), ret)); + return std::move(ret); // c++11 defect +} + +// FtcImpl +FtcImpl::FtcImpl(const std::shared_ptr &ddsConnector) + : Ftc() + , m_ddsConnector(ddsConnector) + , m_logger(getLogger("Ftc")) +{} + +bool FtcImpl::enableAll(bool enable) +{ {% for ft in functionTopics.member %} + if (!enable{{ ft.name }}(enable)) + { + ERROR_LOG(m_logger) << "Error initializing function topic {{ ft.name }}"; + return false; + } + {% endfor %} + return true; +} + +FtcImpl::~FtcImpl() +{ + enableAll(false); +} + +{% for ft in functionTopics.member %} +// Function Topic '{{ ft.name }}' +bool FtcImpl::enable{{ ft.name }}(bool enable) +{ + if (enable) + { + if (m_ftc{{ ft.name }}) + { + INFO_LOG(m_logger) << "{{ ft.name }} already enabled!"; + return true; + } + m_ftc{{ ft.name }}.reset( + m_ddsConnector->createFTC(this)); + assert(m_ftc{{ ft.name }}.get()); + } + else + { + m_ftc{{ ft.name }} = nullptr; + } + + return true; +} +{% endfor %} + +template +static T buildErrorReason(int reason, int requestId) +{ + std::string errString; + std::vector errArgs; + switch (reason) + { + case 1: + errString = tr_lts("dds", "Service could not handle request"); + break; + case 3: + errString = tr_lts("dds", "Timeout while waiting for answer from server"); + break; + default: + errString = tr_lts("dds", "Unknown error reason: %1"); + errArgs.push_back(std::to_string(reason)); + break; + } + + T reply; + reply.id(requestId); + reply.errorString(errString); + reply.errorParams(errArgs); + return reply; +} + +{% for ft in functionTopics.member %} +// +// {{ ft.name }} +// +int64_t FtcImpl::{{ ft.requestFlc }}(const {{ ft.requestType }} &request, {{ ft.replyHandler }} *handler) +{ + return {{ ft.requestFlc }}(request, [ handler ](const {{ ft.replyTypeFro }} &reply) { if (handler) handler->{{ ft.onReply }}(reply); }); +} + +int64_t FtcImpl::{{ ft.requestFlc }}(const {{ ft.requestType }} &request, const std::function &cb) +{ + if (!m_ftc{{ ft.name }}) + { + WARNING_LOG(m_logger) << "Error requesting function topic {{ ft.name }} - not enabled"; + Q_ASSERT(false); + return 0; + } + + static const char * const slotReply = SLOT({{ ft.onReply }}({{ namespace }}::{{ ft.reply }},int,bool)); + static const char * const slotError = SLOT({{ ft.onError }}(int,int,int)); + int uniqueId = 0; + if (!m_ftc{{ ft.name }}->requestDataClassic(request, slotReply, slotError, uniqueId)) + return 0; + + m_{{ ft.replyHandlerFlc }}[uniqueId] = cb; + // first 16 hex bytes from Sha3_512 hash for '{{ ft.name }}' converted to an int64 + m_last{{ ft.request }}UniqueId = uniqueId + {{ ft.replyHandlerHash }}; + return m_last{{ ft.request }}UniqueId; +} + +// NOTE: do not use if you call an FTC which returns an id while also containing an error as "error doesn't mean that the service might not reply with an id - so always take the id" +{% if ft.isCoreFt %}int32_t{% else %}{{ ft.replyTypeFro }}{% endif %} FtcImpl::{{ ft.requestFlc }}(const {{ ft.requestType }} &request) noexcept(false) +{ + if (!m_ftc{{ ft.name }}) + { + Q_ASSERT(false); + throw std::runtime_error("Error requesting function topic {{ ft.name }} - not enabled"); + } + + auto *requestWatcher = new Request2(); + auto future = m_ftc{{ ft.name }}->requestDataModern(request, requestWatcher); + + m_currentlyOpen_{{ ft.requestFlc }}Requests++; + //fprintf(stderr, "{{ ft.requestFlc }}: waiting for reply... (currently open requests: %d)\n", m_currentlyOpen_{{ ft.requestFlc }}Requests); + LtsUtils::await(requestWatcher, &Request2::finished, true); // blocks until request has reply or failed + m_currentlyOpen_{{ ft.requestFlc }}Requests--; + //fprintf(stderr, "{{ ft.requestFlc }}: request done!\n"); + + // the generic ftc impl's 'requestDataModern' + 'exec()' guarantees that calling 'future.get()' will not result in a 'wait()' call by 'get()' - but we should still check! + switch (future.wait_for(std::chrono::seconds(1))) + { + case std::future_status::deferred: + { + Q_ASSERT(false); + // fprintf(stderr, "{{ ft.requestFlc }}: deferred...\n"); + break; + } + case std::future_status::timeout: + { + Q_ASSERT(false); + // fprintf(stderr, "{{ ft.requestFlc }}: future timeout!\n"); + throw std::runtime_error("Future timeout!"); + } + case std::future_status::ready: + { + // fprintf(stderr, "{{ ft.requestFlc }}: ready\n"); + break; + } + } + +{% if ft.isCoreFt %} + const auto _reply = future.get(); // throws if exception set + + Core::FunctionReplyObject fro( + {% for rp in ft.replyParams %} _reply.{{ rp.nameGetter }}(){% if not forloop.last %},{% endif %} {% endfor %} + ); + fro.setUniqueId(m_last{{ ft.request }}UniqueId); + + if (fro.error()) + { + ERROR_LOG(m_logger) << "FTC call failed: " << fro.translated(); + throw std::runtime_error(fro.translated().toStdString().c_str()); + } + + const auto reply = fro.id(); +{% else %} + const auto reply = future.get(); // throws if exception set +{% endif %} + + return reply; +} + +void FtcImpl::{{ ft.onReply }}(const {{ namespace }}::{{ ft.reply }} &reply, int uniqueId, bool bRequestFinished) +{ + auto it = m_{{ ft.replyHandlerFlc }}.find(uniqueId); + if (it == m_{{ ft.replyHandlerFlc }}.end()) + { + WARNING_LOG(m_logger) << "Error replying function topic {{ ft.name }} - no replyHandler"; + Q_ASSERT(false); + return; + } + + auto cb = it.value(); + if (bRequestFinished) + m_{{ ft.replyHandlerFlc }}.erase(it); + if (!cb) + return; + + m_last{{ ft.request }}UniqueId = uniqueId + {{ ft.replyHandlerHash }}; +{% if ft.isCoreFt %} + Core::FunctionReplyObject fro( + {% for rp in ft.replyParams %} + reply.{{ rp.nameGetter }}(){% if not forloop.last %},{% endif %} {% endfor %} + ); + fro.setUniqueId(m_last{{ ft.request }}UniqueId); + cb(fro); +{% else %} + cb(reply); +{% endif %} +} + +void FtcImpl::{{ ft.onError }}(int reason, int uniqueId, int requestId) +{ + {{ ft.onReply }}(buildErrorReason<{{ namespace }}::{{ ft.reply }}>(reason, requestId), uniqueId, true); +} +{% endfor %} + +} // namespace Interface +} // namespace {{ namespace }} +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_ftc_impl.h.grantlee b/Lts/Common/CodeGen/svc_ftc_impl.h.grantlee new file mode 100755 index 000000000..ce1afe2cf --- /dev/null +++ b/Lts/Common/CodeGen/svc_ftc_impl.h.grantlee @@ -0,0 +1,81 @@ +/** + * {{ namespace }} Function Topic Client implementation + * $Id$ + * $HeadURL$ + * $Author$ + */ + +{% autoescape off %} +// filename: {{ namespace_abbr }}_ftc_impl.h +#pragma once +#include <{{ include_prefix }}/{{ namespace_abbr }}_ftc.h> +#include <{{ include_prefix }}/{{ namespace_abbr }}_ftc_types.h> +#include <{{ include_prefix }}/logger.h> + +#include +#include +#include +#include + +#include + + +#if !defined(MAKE_SVC{{ namespace_abbr_upper }}CLIENT_LIB) +#error "You are not allowed to include this file in your code!" +#endif + +namespace {{ namespace }} +{ +namespace Interface +{ +class FtcImpl final : public QObject, public Ftc +{ + Q_OBJECT +public: + explicit FtcImpl(const std::shared_ptr &ddsConnector); + ~FtcImpl(); + + bool enableAll(bool enable = true) override; +{% for ft in functionTopics.member %} + bool enable{{ ft.name }}(bool enable = true) override; + bool is{{ ft.name }}Enabled() const override { return m_ftc{{ ft.name }} != nullptr; } +{% endfor %} + +{% for ft in functionTopics.member %} +// +// Function Topic '{{ ft.name }}' +// + +/// Classic request: +public: + int64_t {{ ft.requestFlc }}(const {{ ft.requestType }} &request, {{ ft.replyHandler }} *handler) override; // returns the uniqueId used for this request + int64_t {{ ft.requestFlc }}(const {{ ft.requestType }} &request, const std::function &cb) override; // returns the uniqueId used for this request + int64_t last{{ ft.request }}UniqueId() const override { return m_last{{ ft.request }}UniqueId; } + +private Q_SLOTS: + void {{ ft.onReply }}(const {{ namespace }}::{{ ft.reply }} &reply, int uniqueId, bool bRequestFinished); + void {{ ft.onError }}(int reason, int uniqueId, int requestId); + +private: + QHash> m_{{ ft.replyHandlerFlc }}; + int64_t m_last{{ ft.request }}UniqueId = -1; + +/// Modern request: +public: + /* Executes the request and blocks (enters Qt's event loop) until the reply has arrived. + * NOTE: If reply has a FunctionReplyWithState, this function only returns the first reply and returns. + {% if ft.isCoreFt %} * Returns the id of the FunctionReply.{% endif %}*/ + {% if ft.isCoreFt %}int32_t{% else %}{{ ft.replyTypeFro }}{% endif %} {{ ft.requestFlc }}(const {{ ft.requestType }} &request) noexcept(false) override; + +private: + int m_currentlyOpen_{{ ft.requestFlc }}Requests { 0 }; + std::unique_ptr m_ftc{{ ft.name }}; +{% endfor %} + +private: + std::shared_ptr m_ddsConnector; + LOGGER_T m_logger; +}; +} // namespace Interface +} // namespace {{ namespace }} +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_fts.h.grantlee b/Lts/Common/CodeGen/svc_fts.h.grantlee new file mode 100755 index 000000000..aa5752fe7 --- /dev/null +++ b/Lts/Common/CodeGen/svc_fts.h.grantlee @@ -0,0 +1,90 @@ +/** + * {{ namespace }} Function Topic Server interface + * $Id$ + * $HeadURL$ + * $Author$ + */ + +{% autoescape off %} +// filename: {{ namespace_abbr }}_fts.h +#pragma once + +#include <{{ include_prefix }}/{{ namespace_abbr }}_export.h> +#include <{{ include_prefix }}/{{ namespace_abbr }}.hpp> +#include + +namespace DDSConnector +{ +class DdsConnector; +} + +namespace {{ namespace }} +{ +namespace Interface +{ +class Fts +{ +protected: + Fts() = default; +public: + virtual ~Fts() = default; + + /** + * Create a new instance for {{ namespace }}::Interface::Dts. + * The caller takes ownership of the returned instance + */ + {{ namespace_abbr_upper }}_SERVER_EXPORT static std::shared_ptr create(const std::shared_ptr &ddsConnector); + + /** + * A unique identifier for this FTC + */ + {{ namespace_abbr_upper }}_SERVER_EXPORT static inline const char *identifier() { return "{{ namespace }}.Interface.Fts"; } + + /** + * initialize this Fts instance with all function topic servers + */ + virtual bool enableAll(bool enable = true) = 0; + /** + * only initialize the specific function topic writer (can be called instead enableAll()) + */ + {% for ft in functionTopics.member %} + virtual bool enable{{ ft.name }}(bool enable = true) = 0; + virtual bool is{{ ft.name }}Enabled() const = 0; + {% endfor %} + +{% for ft in functionTopics.member %} + // + // Function Topic '{{ ft.name }}' + // + class {{ ft.requestHandler }} + { + public: + explicit {{ ft.requestHandler }}(const std::shared_ptr &fts) + : m_fts(fts) + { + if (fts) + fts->set{{ ft.requestHandler }}(this); + } + virtual ~{{ ft.requestHandler }}() + { + auto sp = m_fts.lock(); + if (sp) + sp->set{{ ft.requestHandler }}(nullptr); + } + virtual void {{ ft.onRequest }}(const rti::core::SampleIdentity &cookie, + const {{ ft.requestType }} &request) = 0; + private: + std::weak_ptr m_fts; + }; + virtual void set{{ ft.requestHandler }}({{ ft.requestHandler }} *handler) = 0; + virtual bool {{ ft.replyFlc }}(const rti::core::SampleIdentity &cookie, const {{ ft.replyType }} &reply) = 0; + {% if ft.isCoreFt %} + virtual bool {{ ft.replyFlc }}(const rti::core::SampleIdentity &cookie, const Core::FunctionReplyObject &replyObject) = 0; + {% endif %} + +{% endfor %} +}; + +} // namespace Interface +} // namespace {{ namespace }} +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_fts_impl.cpp.grantlee b/Lts/Common/CodeGen/svc_fts_impl.cpp.grantlee new file mode 100755 index 000000000..884d063c9 --- /dev/null +++ b/Lts/Common/CodeGen/svc_fts_impl.cpp.grantlee @@ -0,0 +1,114 @@ +/** + * {{ namespace }} Function Topic Server implementation + * $Id$ + * $HeadURL$ + * $Author$ + */ + +{% autoescape off %} +// filename: {{ namespace_abbr }}_fts_impl.cpp +#include "{{ namespace_abbr }}_fts_impl.h" +#include + +namespace {{ namespace }} +{ +namespace Interface +{ +// initializer function +std::shared_ptr Fts::create(const std::shared_ptr &ddsConnector) +{ + return std::make_shared(ddsConnector); +} + +// FtsImpl +FtsImpl::FtsImpl(const std::shared_ptr &ddsConnector) + : Fts() + , m_ddsConnector(ddsConnector) + , m_logger(getLogger("Fts")) +{} + +FtsImpl::~FtsImpl() +{ + enableAll(false); +} + +bool FtsImpl::enableAll(bool enable) +{ + DEBUG_LOG(m_logger) << "FtsImpl::enableAll(" << (enable ? "true" : "false") << ")"; + {% for ft in functionTopics.member %} + if (!enable{{ ft.name }}(enable)) + { + ERROR_LOG(m_logger) << "Error initializing function topic {{ ft.name }}"; + return false; + } + {% endfor %} + return true; +} + +{% for ft in functionTopics.member %} +// +// Function Topic '{{ ft.name }}' +// +bool FtsImpl::enable{{ ft.name }}(bool enable) +{ + if (enable) + { + if (m_fts{{ ft.name }}) + { + INFO_LOG(m_logger) << "{{ ft.name }} already enabled!"; + return true; + } + m_fts{{ ft.name }}.reset(m_ddsConnector->createFTS()); + assert(m_fts{{ ft.name }}.get()); + m_fts{{ ft.name }}->setSubscriberCallback([this] (const rti::core::SampleIdentity &identity, const {{ namespace }}::{{ ft.request }} &request) { this->{{ ft.onRequest }}(identity, request); }); + } + else + { + m_fts{{ ft.name }}.reset(); + } + return true; +} + +bool FtsImpl::{{ ft.replyFlc }}(const rti::core::SampleIdentity &cookie, const {{ ft.replyType }} &reply) +{ + if (!m_fts{{ ft.name }}) + { + WARNING_LOG(m_logger) << "Error publishing function topic {{ dt.name }} - not enabled"; + Q_ASSERT(false); + return false; + } + + return m_fts{{ ft.name }}->sendReply(cookie, reply); +} +{% if ft.isCoreFt %} +bool FtsImpl::{{ ft.replyFlc }}(const rti::core::SampleIdentity &cookie, const Core::FunctionReplyObject &replyObject) +{ + return {{ ft.replyFlc }}(cookie, {{ ft.replyType }}(replyObject.id(), replyObject.str(), replyObject.args(), replyObject.additionalInfo())); +} +{% endif %} + + +void FtsImpl::set{{ ft.requestHandler }}({{ ft.requestHandler }} *handler) +{ + m_{{ ft.requestHandlerFlc }} = handler; +} + +void FtsImpl::{{ ft.onRequest }}(const rti::core::SampleIdentity &cookie, + const {{ namespace }}::{{ ft.request }} &request) +{ + DEBUG_LOG(m_logger) << "{{ ft.onRequest }}"; + if (!m_{{ ft.requestHandlerFlc }}) + { + WARNING_LOG(m_logger) << "Incoming {{ ft.request }} without registered handler!"; + Q_ASSERT(false); + return; + } + + m_{{ ft.requestHandlerFlc }}->{{ ft.onRequest }}(cookie, request); +} + +{% endfor %} + +} // namespace Interface +} // namespace {{ namespace }} +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_fts_impl.h.grantlee b/Lts/Common/CodeGen/svc_fts_impl.h.grantlee new file mode 100755 index 000000000..0b0adcb13 --- /dev/null +++ b/Lts/Common/CodeGen/svc_fts_impl.h.grantlee @@ -0,0 +1,66 @@ +/** + * {{ namespace }} Function Topic Server implementation + * $Id$ + * $HeadURL$ + * $Author$ + */ + +{% autoescape off %} +// filename: {{ namespace_abbr }}_fts_impl.h +#pragma once +#include <{{ include_prefix }}/{{ namespace_abbr }}_fts.h> +#include <{{ include_prefix }}/{{ namespace_abbr }}_fts_types.h> +#include <{{ include_prefix }}/logger.h> + +#include +#include + +#if !defined(MAKE_SVC{{ namespace_abbr_upper }}SERVER_LIB) +#error "You are not allowed to include this file in your code!" +#endif + +namespace {{ namespace }} +{ +namespace Interface +{ +class FtsImpl final : public QObject, public Fts +{ + Q_OBJECT +public: + explicit FtsImpl(const std::shared_ptr &ddsConnector); + ~FtsImpl(); + + bool enableAll(bool enable = true) override; + +{% for ft in functionTopics.member %} + // + // {{ ft.name }} + // +public: + bool enable{{ ft.name }}(bool enable = true) override; + bool is{{ ft.name }}Enabled() const override { return m_fts{{ ft.name }} != nullptr && m_{{ ft.requestHandlerFlc }} != nullptr; } + bool {{ ft.replyFlc }}(const rti::core::SampleIdentity &cookie, const {{ ft.replyType }} &reply) override; + {% if ft.isCoreFt %} + bool {{ ft.replyFlc }}(const rti::core::SampleIdentity &cookie, const Core::FunctionReplyObject &replyObject) override; + {% endif %} + + void set{{ ft.requestHandler }}({{ ft.requestHandler }} *handler) override; + +private Q_SLOTS: + void {{ ft.onRequest }}(const rti::core::SampleIdentity &cookie, + const {{ namespace }}::{{ ft.request }} &request); +private: + std::unique_ptr m_fts{{ ft.name }}; + {{ ft.requestHandler }} *m_{{ ft.requestHandlerFlc }} = nullptr; + +{% endfor %} + +private: + + std::shared_ptr m_ddsConnector; + LOGGER_T m_logger; +}; + +} // namespace Interface +} // namespace {{ namespace }} +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_json.cpp.grantlee b/Lts/Common/CodeGen/svc_json.cpp.grantlee new file mode 100755 index 000000000..7b13e8f3f --- /dev/null +++ b/Lts/Common/CodeGen/svc_json.cpp.grantlee @@ -0,0 +1,160 @@ +/** + * Json <-> DDS conversion routines + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Christian Ehrlicher, SLSE, 2017-11-30 + */ + +{% autoescape off %} +// filename: {{ namespace_abbr }}_json.cpp + +#include "{{ namespace_abbr }}_json.h" +#include +#include +#include +#include +#include + +namespace {{ namespace }} +{ +template<> +int fromJson(const QJsonValue &val, type) +{ + return val.toInt(); +} + +template<> +double fromJson(const QJsonValue &val, type) +{ + return val.toDouble(); +} + +template<> +qint64 fromJson(const QJsonValue &val, type) +{ + return val.toString().toLongLong(); +} + +QJsonValue toJson(int vec) +{ + return QJsonValue(vec); +} +QJsonValue toJson(double vec) +{ + return QJsonValue(vec); +} +QJsonValue toJson(qint64 vec) +{ + return QJsonValue(QString::number(vec)); +} + +// std::string +QJsonValue toJson(const std::string &element) +{ + return QJsonValue(QString::fromStdString(element)); +} +template<> +std::string fromJson(const QJsonValue &val, type) +{ + return val.toString().toStdString(); +} + +// std::vector +template +QJsonArray toJson(const std::vector &vec) +{ + QJsonArray arr; + for (const auto &element : vec) + arr.push_back(toJson(element)); + return arr; +} + +template +std::vector fromJson(const QJsonValue &val, type>) +{ + std::vector ret; + if (!val.isArray()) + return ret; + const QJsonArray array(val.toArray()); + ret.reserve(array.size()); + for(int i = 0; i < array.size(); ++i) + ret.push_back(fromJson(array.at(i), type{})); + return ret; +} + +// dds::core:vector (bytestream) +inline QJsonValue toJson(const std::vector &element) +{ + return QJsonValue(QString::fromLatin1(QByteArray::fromRawData(reinterpret_cast(&element.at(0)), + static_cast(element.size())).toBase64())); +} +template<> +std::vector fromJson(const QJsonValue &val, type>) +{ + const QByteArray ba(QByteArray::fromBase64(val.toString().toLatin1())); + return LtsUtils::convert>(ba); +} + +{% for dt in jsonTopics %} +// {{ dt.name }} +QJsonObject toJson(const {{ dt.name }} &element) +{ + QJsonObject obj; + {% for row in dt.cppMember %} + {{ row.ddsToJsonElement }}; + {% endfor %} + return obj; +} + +template<> +{{ dt.name }} fromJson(const QJsonValue &value, type<{{ dt.name }}>) +{ + const QJsonObject objElement(value.toObject()); + {{ dt.name }} element; + + {% for row in dt.cppMember %} + { {{ row.jsonElementToDds }}; } {% endfor %} + + return element; +} + +// +// extensions to support sequence of seqence of Non-POD : +// + +QJsonValue toJson(const std::vector <{{ dt.name }} > &element) +{ + + if (element.size() == 0) + return QJsonValue(); + QJsonArray array; + for(size_t i = 0; i < element.size(); ++i) + array.push_back(toJson(element.at(i))); + + return array; +} + +template<> +std::vector<{{ dt.name }}> fromJson(const QJsonValue &value, type< std::vector <{{ dt.name }} > >) +{ + std::vector < {{ dt.name }} > ret; + if (value.isNull()) + return ret; + if (!value.isArray()) + return ret; + + const QJsonArray array = value.toArray(); + ret.reserve(array.size()); + for(const auto &ele : array) + ret.push_back(fromJson(ele, type<{{ dt.name }}>{})); + return ret; +} + + + +{% endfor %} + +} // namespace {{ namespace }} + +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_json.h.grantlee b/Lts/Common/CodeGen/svc_json.h.grantlee new file mode 100755 index 000000000..b59958565 --- /dev/null +++ b/Lts/Common/CodeGen/svc_json.h.grantlee @@ -0,0 +1,50 @@ +/** + * Json <-> DDS conversion routines + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Christian Ehrlicher, SLSE, 2017-11-30 + */ + +{% autoescape off %} +// filename: {{ namespace_abbr }}_json.h +#pragma once + +#include "{{ namespace_abbr }}.hpp" +#include "{{ namespace_abbr }}_export.h" + +#include +#include + +namespace {{ namespace }} +{ +template +struct type{}; + +template +T fromJson(const QJsonValue &val, type); + +template +T fromJson(const QJsonValue &val) +{ + return fromJson(val, type{}); +} + +{% for dt in jsonTopics %} +// {{ dt.name }} +{{ namespace_abbr_upper }}_COMMON_EXPORT QJsonObject toJson(const {{ dt.name }} &element); +template<> {{ namespace_abbr_upper }}_COMMON_EXPORT {{ dt.name }} fromJson(const QJsonValue &val, type<{{ dt.name }}>); +{{ namespace_abbr_upper }}_COMMON_EXPORT QJsonValue toJson(const std::vector< {{ dt.name }} > &element); +template<> {{ namespace_abbr_upper }}_COMMON_EXPORT std::vector<{{ dt.name }}> fromJson(const QJsonValue &value, type< std::vector< {{ dt.name }} > >); + +{% endfor %} + + +// +// extensions to support sequence of seqence of Non-POD : +// + + +} // namespace {{ namespace }} + +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_record_replay_plugin.cpp.grantlee b/Lts/Common/CodeGen/svc_record_replay_plugin.cpp.grantlee new file mode 100755 index 000000000..42cb88a65 --- /dev/null +++ b/Lts/Common/CodeGen/svc_record_replay_plugin.cpp.grantlee @@ -0,0 +1,876 @@ +/** + * RecordReplay Plugin + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Christian Ehrlicher, SLSE, 2016-07-21 + */ + +{% autoescape off %} +// filename: {{ namespace_abbr }}_record_replay_plugin.cpp + +#include "{{ namespace_abbr }}_record_replay_plugin.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace {{ namespace }} +{ + +template +struct type{}; + +// conversion DDS -> QVariant, encapsulate complex members into json +template +QJsonValue toJson(const T &val); + +template +QVariant toQVariant(const T &val) +{ + return QVariant::fromValue(val); +} + +template<> +QJsonValue toJson(const int &vec) +{ + return QJsonValue(vec); +} +template<> +QJsonValue toJson(const double &vec) +{ + return QJsonValue(vec); +} +template<> +QJsonValue toJson(const qint64 &val) +{ + return QJsonValue(QString::number(val)); +} + +template<> +QJsonValue toJson(const std::vector &val) +{ + if (val.size() == 0) + return QJsonValue(); + QJsonArray array; + for(size_t i = 0; i < val.size(); ++i) + array.push_back(toJson(val.at(i))); + + return QJsonValue(array); +} + + +template +QVariant toQVariant(const dds::core::safe_enum &val) +{ + // do not use T here - otherwise the implicit ctor dds::core::safe_enum() is used which leads to infinite recursion + return toQVariant(val.underlying()); +} + +template<> +QJsonValue toJson(const std::string &element) +{ + return QJsonValue(QString::fromStdString(element)); +} + +template<> +QVariant toQVariant(const std::string &val) +{ + return QString::fromStdString(val); +} + +template +QVariant toQVariant(const std::vector &data) +{ + if (data.size() == 0) + return QString(); + QJsonArray array; + for(size_t i = 0; i < data.size(); ++i) + array.push_back(toJson(data.at(i))); + + return QString::fromUtf8(QJsonDocument(array).toJson()); +} + +template<> +QVariant toQVariant(const std::vector &data) +{ + if (data.size() == 0) + return QVariant(); + return QVariant(LtsUtils::convert(data)); +} + +template +static QVariant toQVariant(const dds::core::optional &val) +{ + if (val.is_set()) + return toQVariant(val.get()); + return QVariant(); +} + +// conversion QVariant -> DDS, decode complex members from json +template +T fromJson(const QJsonValue &val, type); + +template<> +int fromJson(const QJsonValue &val, type) +{ + return val.toInt(); +} +template<> +double fromJson(const QJsonValue &val, type) +{ + return val.toDouble(); +} +template<> +qint64 fromJson(const QJsonValue &val, type) +{ + return val.toString().toLongLong(); +} +/* +template<> +std::vector fromJson(const QJsonValue &val, type>) +{ + std::vector ret; + if (val.isNull()) + return ret; + const QJsonArray array(QJsonDocument::fromJson(val.toString().toUtf8()).array()); + ret.reserve(array.size()); + for(const auto &ele : array) + ret.push_back(fromJson(ele, type{} )); + return ret; +} +*/ + +template +std::vector fromJson(const QJsonValue &jsonData, type>) +{ + std::vector ret; + if (jsonData.isNull()) + return ret; + const QJsonArray array(jsonData.toArray()); + ret.reserve(array.size()); + for(const auto &ele : array) + ret.push_back(fromJson(ele, type{})); + return ret; +} + +template +std::vector fromJson(const QVariant &jsonData, type>) +{ + std::vector ret; + if (jsonData.isNull()) + return ret; + const QJsonArray array(QJsonDocument::fromJson(jsonData.toString().toUtf8()).array()); // warning: ugly and didn't work in the fromJson version above + ret.reserve(array.size()); + for(const auto &ele : array) + ret.push_back(fromJson(ele, type{})); + return ret; +} + +template +T fromQVariant(const QVariant &val, type); + +template<> +bool fromQVariant(const QVariant &val, type) +{ + return val.toBool(); +} + +template<> +short fromQVariant(const QVariant &val, type) +{ + return static_cast(val.toInt()); +} + +template<> +int fromQVariant(const QVariant &val, type) +{ + return val.toInt(); +} + +template<> +long long fromQVariant(const QVariant &val, type) +{ + return val.toLongLong(); +} + +template<> +double fromQVariant(const QVariant &val, type) +{ + return val.toDouble(); +} + +template<> +std::string fromJson(const QJsonValue &val, type) +{ + return val.toString().toStdString(); +} + +template<> +std::string fromQVariant(const QVariant &val, type) +{ + return val.toString().toStdString(); +} + +template +dds::core::safe_enum fromQVariant(const QVariant &val, type>) +{ + using inner = typename dds::core::safe_enum::inner_enum; + return static_cast(val.toInt()); +} + +template +std::vector fromQVariant(const QVariant &val, type> t) +{ + return fromJson(val, t); +} + +template<> +std::vector fromQVariant(const QVariant &in, type>) +{ + return LtsUtils::convert>(in.toByteArray()); +} + +template +static dds::core::optional fromQVariant(const QVariant &val, type>) +{ + if (val.isNull()) + return dds::core::optional(); + return fromQVariant(val, type{}); +} + +template +T fromJson(const QJsonValue &val) +{ + return fromJson(val, type{}); +} + +template +T fromQVariant(const QVariant &val) +{ + return fromQVariant(val, type{}); +} + +{% for dt in serializeTopics.member %} +// +// Begin {{ dt.typeFq }} +// +template<> +{{ dt.typeFq }} fromJson(const QJsonValue &val, type<{{ dt.typeFq }}>) +{ + const QJsonObject objElement(val.toObject()); + {{ dt.typeFq }} element; + {% for row in dt.recTable.cppMember %} + { {{ row.jsonElementToDds }}; } {% endfor %} + + return element; +} +template<> +QJsonValue toJson(const {{ dt.typeFq }} &element) +{ + QJsonObject obj; + {% for row in dt.recTable.cppMember %} + {{ row.ddsToJsonElement }}; {% endfor %} + + return obj; +} + +template <> +QVariant toQVariant(const {{ dt.typeFq }} &val) +{ + return QString::fromUtf8(QJsonDocument(toJson(val).toObject()).toJson()); +} + +template<> +{{ dt.typeFq }} fromQVariant(const QVariant &val, type<{{ dt.typeFq }}>) +{ + return fromJson(QJsonDocument::fromJson(val.toString().toUtf8()).object(), type<{{ dt.typeFq }}>{}); +} + +// +// extensions to support sequence of seqence of Non-POD : +// + +template<> +QJsonValue toJson(const std::vector < {{ dt.typeFq }} > &data) +{ + if (data.size() == 0) + return QJsonValue(); + QJsonArray array; + for(size_t i = 0; i < data.size(); ++i) + array.push_back(toJson(data.at(i))); + + return array; +} + +template<> +std::vector < {{ dt.typeFq }} > fromJson(const QJsonValue &val) +{ + std::vector < {{ dt.typeFq }} > ret; + if (val.isNull()) + return ret; + if (!val.isArray()) + return ret; + const QJsonArray array = val.toArray(); + ret.reserve(array.size()); + for(const auto &ele : array) + ret.push_back(fromJson(ele, type<{{ dt.typeFq }}>{})); + return ret; +} +// +// : extensions to support sequence of seqence of Non-POD +// + + +// +// End {{ dt.typeFq }} +// +{% endfor %} + + + + +RecordReplayPlugin::RecordReplayPlugin() + : m_exerciseId(0) + , m_bCanHandleSchemasRead(true) + , m_bCanHandleSchemasWrite(true) + , m_logger(getLogger("RecordReplay")) +{} + +RecordReplayPlugin::~RecordReplayPlugin() = default; + +QString RecordReplayPlugin::getTableName(const QString &tableName, bool bCanHandleSchemas) const +{ + if (!bCanHandleSchemas) + return tableName; + return DBCommon::getExerciseTableName(tableName, m_exerciseId); +} + +bool RecordReplayPlugin::initDtc(const std::shared_ptr &ddsConnector, + int exerciseId) +{ + m_ddsConnector = ddsConnector; + m_exerciseId = exerciseId; + if (!m_ddsConnector) + return false; +{% for dt in dataTopics.member %} {% if dt.recordTopic %} + m_dtc{{ dt.name }}.reset(m_ddsConnector->createDTC()); +{% endif %} {% endfor %} + + return true; +} + +bool RecordReplayPlugin::initDts(const std::shared_ptr &ddsConnector, + int exerciseId, + bool bCanHandleSchemas, + NSqlQuery &query) +{ + Q_UNUSED(query); + m_ddsConnector = ddsConnector; + m_exerciseId = exerciseId; + m_bCanHandleSchemasRead = bCanHandleSchemas; + m_bCanHandleSchemasWrite = bCanHandleSchemas; + if (!m_ddsConnector) + return false; +{% for dt in dataTopics.member %} {% if dt.recordTopic %} + m_dts{{ dt.name }}.reset(m_ddsConnector->createDTS()); +{% endif %} {% endfor %} + + return true; +} + +bool RecordReplayPlugin::initCopy(int exerciseId, bool bCanHandleSchemasRead, bool bCanHandleSchemasWrite) +{ + m_exerciseId = exerciseId; + m_bCanHandleSchemasRead = bCanHandleSchemasRead; + m_bCanHandleSchemasWrite = bCanHandleSchemasWrite; + return true; +} + +bool RecordReplayPlugin::copyData(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 fromTime, qint64 toTime) +{ + {% for dt in dataTopics.member %} {% if dt.recordTopic %} + // {{ dt.name }} + if (!copyInitialData{{ dt.name }}(queryFrom, queryTo, fromTime)) return false; + if (!copyData{{ dt.name }}(queryFrom, queryTo, fromTime, toTime)) return false; + {% endif %} {% endfor %} + + return true; +} + +bool RecordReplayPlugin::ddsTopicsToDatabase(NSqlQuery &q, QHash &minRecordSampleTimes) +{ +{% for dt in dataTopics.member %} {% if dt.recordTopic %} + if (!write{{ dt.name }}(q, minRecordSampleTimes)) return false; +{% endif %} {% endfor %} + + return true; +} + +bool RecordReplayPlugin::setReplayStartTime(NSqlQuery &q, qint64 startTime) +{ +{% for dt in dataTopics.member %} {% if dt.recordTopic %} + if (!read{{ dt.name }}At(q, startTime)) return false; +{% endif %} {% endfor %} + + return true; +} + +bool RecordReplayPlugin::ddsTopicsFromDatabase(NSqlQuery &q, qint64 fromTime, qint64 toTime) +{ +{% for dt in dataTopics.member %} {% if dt.recordTopic %} + if (!read{{ dt.name }}(q, fromTime, toTime)) return false; +{% endif %} {% endfor %} + + return true; +} + +bool RecordReplayPlugin::publishData(qint64 desiredTime) +{ +{% for dt in dataTopics.member %} {% if dt.recordTopic %} + if (!publish{{ dt.name }}(desiredTime)) return false; +{% endif %} {% endfor %} + + return true; +} + +void RecordReplayPlugin::clearData() +{ +{% for dt in dataTopics.member %} {% if dt.recordTopic %} + clearData{{ dt.name }}(); +{% endif %} {% endfor %} +} + +std::map> RecordReplayPlugin::getSnapshotTimes(NSqlQuery &q, + qint64 toTime, + qint64 interval, + const QHash &minRecordedSampleTimes) +{ + std::map> ret; +{% for dt in dataTopics.member %} {% if dt.recordTopic %} + { + const QString &topicName(QStringLiteral("{{ dt.name }}")); + std::vector vec; + auto it = minRecordedSampleTimes.find(topicName); + if (it != minRecordedSampleTimes.end()) + { + qint64 nextSampleTime = getNextSampleTimeFor{{ dt.name }}(q, it.value()); // is 0 or -1 if no samples are available + nextSampleTime -= (nextSampleTime % interval); // sync to interval - only needed for better debugging + + if (nextSampleTime != m_snapshot{{ dt.name }}MapTime) + { + // the current snapshot data does not match the starting time -> reread m_{{ dt.nameFlc }}Keys + readLastSnapshotKeysFor{{ dt.name }}(q, nextSampleTime); + //DEBUG_LOG(m_logger) << "fromTime: " << fromTime << ", m_snapshotParticipantMapTime: " << m_snapshotParticipantMapTime + // << " - rereading snapshot times! Map now contains " << m_snapshot{{ dt.name }}Map.size() << " entries"; + } + + while (nextSampleTime > 0 && nextSampleTime <= toTime) + { + const qint64 intervalStart = nextSampleTime; + const qint64 intervalEnd = intervalStart + interval; + updateSnapshotKeys{{ dt.name }}(q, intervalStart, intervalEnd); + + vec.emplace_back(RecSnapshot(intervalEnd, m_snapshot{{ dt.name }}Map)); + + nextSampleTime = getNextSampleTimeFor{{ dt.name }}(q, intervalEnd); + nextSampleTime -= (nextSampleTime % interval); // sync to interval - only needed for better debugging + } + } + if (!vec.empty()) + ret.insert({topicName, std::move(vec)}); + } +{% endif %} {% endfor %} + + return ret; +} + +QVector RecordReplayPlugin::getRecordTopicNames() const +{ + return {{% for dt in dataTopics.member %} {% if dt.recordTopic %} + QStringLiteral("{{ dt.name }}"){% if not forloop.last %}, {% endif %} {% endif %} {% endfor %} + }; +} + +QVector RecordReplayPlugin::getRecordTableNames() const +{ + return {{% for dt in dataTopics.member %} {% if dt.recordTopic %} + QStringLiteral("{{ dt.recTable.name }}"){% if not forloop.last %}, {% endif %} {% endif %} {% endfor %} + }; +} + +qint64 RecordReplayPlugin::getMinimumSampleTime(NSqlQuery &q, const QString &topicName, qint64 defaultValue) const +{ + QString tableName; + {% for dt in dataTopics.member %} {% if dt.recordTopic %} + if (topicName == QLatin1String("{{ dt.name }}")) + tableName = QStringLiteral("{{ dt.recTable.name }}"); + {% endif %} {% endfor %} + if (tableName.isEmpty()) + return defaultValue; + const QString stmt(QStringLiteral("SELECT MIN(sample_time) FROM ") + tableName); + if (!q.execAndFetch(stmt)) + return defaultValue; + const QVariant val = q.value(0); + return val.isNull() ? defaultValue : val.toLongLong(); +} + +{% for dt in dataTopics.member %} {% if dt.recordTopic %} + +bool RecordReplayPlugin::read{{ dt.name }}At(NSqlQuery &q, qint64 startTime) +{ + // read snapshot data for {{ dt.name }} + if (!readLastSnapshotFor{{ dt.name }}(q, startTime)) return false; + // read other samples between snapshot time and startTime + if (!read{{ dt.name }}(q, m_snapshot{{ dt.name }}MapTime, startTime)) return false; + return true; +} + +bool RecordReplayPlugin::write{{ dt.name }}(NSqlQuery &q, QHash &minRecordSampleTimes) +{ + using DATA = {{ namespace }}::{{ dt.name }}; + + if (!m_dtc{{ dt.name }}) + return false; + + qint64 minRecordSampleTime = std::numeric_limits::max(); + + if (m_dtc{{ dt.name }}->hasSamples()) + { + TRACE_LOG(m_logger) << "m_dtc{{ dt.name }}->hasSamples() == true "; + + const QString stmt(QStringLiteral("INSERT INTO ") + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasWrite) + + QStringLiteral(" ( dds_is_alive, {% for row in dt.recTable.cppMember %} {{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}) VALUES " + " (:dds_is_alive, {% for row in dt.recTable.cppMember %}:{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %});")); + + if (!q.prepare(stmt)) + return false; + + dds::sub::LoanedSamples samples = m_dtc{{ dt.name }}->takeAlive(); + TRACE_LOG(m_logger) << "samples count: " << samples.length(); + for (const auto &sample : samples) + { + const DATA &data = sample.data(); + INFO_LOG(m_logger) << "m_dtc{{ dt.name }} {{ dt.recTable.ddsKey.cppMemberName }}: " << data.{{ dt.recTable.ddsKey.cppMemberName }}(); + + {% for row in dt.recTable.cppMember %} + q.bindValue(QStringLiteral(":{{ row.dbColumnName }}"), {{ row.ddsToQVariant }}); + {% endfor %} + q.bindValue(QStringLiteral(":dds_is_alive"), true); + + if (!q.exec()) + return false; + + minRecordSampleTime = std::min(minRecordSampleTime, data.sampleTime()); + } + } + + dds::sub::LoanedSamples disposedSamples = m_dtc{{ dt.name }}->takeDisposed(); + if (disposedSamples.length() != 0) + { + const QString stmt(QStringLiteral("INSERT INTO ") + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasWrite) + + QStringLiteral(" ( dds_is_alive, sample_time, {{ dt.recTable.ddsKey.dbColumnName }}) VALUES " + " (:dds_is_alive,:sample_time,:{{ dt.recTable.ddsKey.dbColumnName }});")); + + if (!q.prepare(stmt)) + return false; + + DEBUG_LOG(m_logger) << "disposedSamples count: " << disposedSamples.length(); + auto dr = m_dtc{{ dt.name }}->dataReader()->dataReader(); + {{ dt.name }} data; + for (const auto &sample : disposedSamples) + { + const qint64 sampleTime = sample.info().source_timestamp().to_millisecs(); + dr.key_value(data, sample.info().instance_handle()); + q.bindValue(QStringLiteral(":dds_is_alive"), false); + q.bindValue(QStringLiteral(":sample_time"), sampleTime); + q.bindValue(QStringLiteral(":{{ dt.recTable.ddsKey.cppMemberName }}"), {{ dt.recTable.ddsKey.ddsToQVariant }}); + + if (!q.exec()) + return false; + + minRecordSampleTime = std::min(minRecordSampleTime, sampleTime); + } + } + + if (minRecordSampleTime != std::numeric_limits::max()) + minRecordSampleTimes[QStringLiteral("{{ dt.name }}")] = minRecordSampleTime; + + return true; +} + +// read the last snapshot which is <= startTime, set m_snapshot{{ dt.name }}MapTime to the snapshot time +bool RecordReplayPlugin::readLastSnapshotFor{{ dt.name }}(NSqlQuery &q, qint64 startTime) +{ + // rebuild m_{{ dt.nameFlc }}Map content for given startTime + clearData{{ dt.name }}(); + + const QString stmt(QStringLiteral("SELECT id, time_stamp FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) + + QStringLiteral(" WHERE svc_name = '{{ namespace_abbr}}' AND topic_name = '{{ dt.name }}' AND time_stamp <= %1 " + " ORDER BY time_stamp DESC LIMIT 1").arg(startTime)); + + if (!q.exec(stmt)) + { + ERROR_LOG(m_logger) << "Error while fetch max(time_stamp) for topic {{ dt.name }}"; + return false; + } + if (!q.next() || q.value(0).isNull()) + { + INFO_LOG(m_logger) << "No snapshot available for topic {{ dt.name }}"; + return true; + } + const qint64 idSnapshot = q.value(0).toLongLong(); + const qint64 timeSnapshot = q.value(1).toLongLong(); + + const QString stmt2(QStringLiteral("SELECT dds_is_alive, {% for row in dt.recTable.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} FROM ") + + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) + + QStringLiteral(" WHERE rec_id IN (SELECT reduce_dim2(rec_id_array) FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) + + QStringLiteral(" WHERE id = ") + QString::number(idSnapshot) + QLatin1Char(')')); + + if (!q.exec(stmt2)) + return false; + + {{ namespace }}::{{ dt.name }} data; + while (q.next()) + { + int i = 0; + + const bool bIsAlive = q.value(i++).toBool(); + {% for row in dt.recTable.viewMember %} + {{ row.qVariantToDds }}; + {% endfor %} + + if (bIsAlive) + m_{{ dt.nameFlc }}Map.insert(data.sampleTime(), data); + else + m_{{ dt.nameFlc }}DisposedMap.insert(data.sampleTime(), data); + m_{{ dt.nameFlc }}Keys.insert(data.{{ dt.key.name }}()); + } + m_snapshot{{ dt.name }}MapTime = timeSnapshot; + + return true; +} + + +bool RecordReplayPlugin::read{{ dt.name }}(NSqlQuery &q, qint64 fromTime, qint64 toTime) +{ + const QString stmt(QStringLiteral("SELECT dds_is_alive, {% for row in dt.recTable.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} FROM ") + + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) + + QStringLiteral(" WHERE sample_time > " ) + QString::number(fromTime) + + QStringLiteral(" AND sample_time <= ") + QString::number(toTime) + QLatin1Char(';')); + + if (!q.exec(stmt)) + return false; + + {{ namespace }}::{{ dt.name }} data; + while (q.next()) + { + int i = 0; + const bool bIsAlive = q.value(i++).toBool(); + + {% for row in dt.recTable.viewMember %} + {{ row.qVariantToDds }}; + {% endfor %} + + if (bIsAlive) + m_{{ dt.nameFlc }}Map.insert(data.sampleTime(), data); + else + m_{{ dt.nameFlc }}DisposedMap.insert(data.sampleTime(), data); + m_{{ dt.nameFlc }}Keys.insert(data.{{ dt.key.name }}()); + } + return true; +} + +bool RecordReplayPlugin::updateSnapshotKeys{{ dt.name }}(NSqlQuery &q, qint64 fromTime, qint64 toTime) +{ + const QString stmt(QStringLiteral("SELECT {{ dt.recTable.ddsKey.dbColumnName }}, {{ dt.recTable.primaryKey.dbColumnName }}, dds_is_alive FROM ") + + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) + + QStringLiteral(" WHERE sample_time > ") + QString::number(fromTime) + + QStringLiteral(" AND sample_time <= ") + QString::number(toTime) + + QStringLiteral(" ORDER BY sample_time ")); + + DEBUG_LOG(m_logger) << "stmt: " << stmt; + + if (!q.exec(stmt)) + return false; + + while (q.next()) + { + const qint64 key = q.value(0){{ dt.recTable.ddsKey.cppFromQvPost }}; + const qint64 pk = q.value(1){{ dt.recTable.primaryKey.cppFromQvPost }}; + const bool isAlive = q.value(2).toBool(); + if (isAlive) + { + DEBUG_LOG(m_logger) << "INSERT {{ dt.name }} key: " << key << ", pk: " << pk << ", isAlive: " << isAlive; + m_snapshot{{ dt.name }}Map[key] = pk; + m_{{ dt.nameFlc }}Keys.insert(key); + } + else + { + DEBUG_LOG(m_logger) << "REMOVE {{ dt.name }} key: " << key << ", pk: " << pk << ", isAlive: " << isAlive; + m_snapshot{{ dt.name }}Map.erase(key); + m_{{ dt.nameFlc }}Keys.erase(key); + } + } + + // new validity time for snapshot data + m_snapshot{{ dt.name }}MapTime = toTime; + + DEBUG_LOG(m_logger) << "updateSnapshotKeys{{ dt.name }}(" << fromTime << ", " << toTime << ") = " + << m_snapshot{{ dt.name }}Map.size(); + + return true; +} + +bool RecordReplayPlugin::readLastSnapshotKeysFor{{ dt.name }}(NSqlQuery &q, qint64 fromTime) +{ + const QString stmt(QStringLiteral("SELECT rec_id_array FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) + + QStringLiteral(" WHERE svc_name = '{{ namespace_abbr}}' AND topic_name = '{{ dt.name }}' AND time_stamp <= %1 " + " ORDER BY time_stamp DESC LIMIT 1").arg(fromTime)); + + // no previous snapshot found? + if (!q.exec(stmt)) + return false; + if (!q.next() || q.value(0).isNull()) + { + DEBUG_LOG(m_logger) << "no snapshot entry for {{ dt.name }} found"; + return true; + } + + m_snapshot{{ dt.name }}Map = LtsUtils::BigintArray2D::fromPgString(q.value(0).toString())(); + + DEBUG_LOG(m_logger) << "Map size {{ dt.name }}: " << m_snapshot{{ dt.name }}Map.size(); + + return true; +} + + +qint64 RecordReplayPlugin::getNextSampleTimeFor{{ dt.name }}(NSqlQuery &q, qint64 fromTime) +{ + const QString stmt(QStringLiteral("SELECT MIN(sample_time) FROM ") + + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) + + QStringLiteral(" WHERE sample_time > ") + QString::number(fromTime)); + qint64 ret = -1; + if (q.execAndFetch(stmt) && !q.value(0).isNull()) + ret = q.value(0).toLongLong(); + + DEBUG_LOG(m_logger) << "next min sampleTime for {{ dt.name }} is " << ret << " (> " << fromTime << ")"; + + return ret; +} + + +bool RecordReplayPlugin::publish{{ dt.name }}(qint64 desiredTime) +{ + if (!m_dts{{ dt.name }}) + return false; + { + auto it = m_{{ dt.nameFlc }}Map.begin(); + while (it != m_{{ dt.nameFlc }}Map.end() && it.key() <= desiredTime) + { + m_dts{{ dt.name }}->write(it.value()); + it = m_{{ dt.nameFlc }}Map.erase(it); + } + } + { + auto it = m_{{ dt.nameFlc }}DisposedMap.begin(); + while (it != m_{{ dt.nameFlc }}DisposedMap.end() && it.key() <= desiredTime) + { + m_dts{{ dt.name }}->remove(it.value()); + it = m_{{ dt.nameFlc }}DisposedMap.erase(it); + } + } + + return true; +} + +void RecordReplayPlugin::clearData{{ dt.name }}() +{ + if (m_dts{{ dt.name }}) + { + {{ dt.name }} tmpData; + for (auto key : m_{{ dt.nameFlc }}Keys) + { + tmpData.{{ dt.key.name }}(key); + m_dts{{ dt.name }}->remove(tmpData); + } + } + m_{{ dt.nameFlc }}Keys.clear(); + m_{{ dt.nameFlc }}Map.clear(); + m_{{ dt.nameFlc }}DisposedMap.clear(); +} + +bool RecordReplayPlugin::copyInitialData{{ dt.name }}(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 startTime) +{ + const QString stmt(QStringLiteral("SELECT id FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) + + QStringLiteral(" WHERE svc_name = '{{ namespace_abbr}}' AND topic_name = '{{ dt.name }}' AND time_stamp <= %1 " + " ORDER BY time_stamp DESC LIMIT 1").arg(startTime)); + + if (!queryFrom.exec(stmt)) + { + ERROR_LOG(m_logger) << "Error while fetch max(time_stamp) for topic {{ dt.name }}"; + return false; + } + if (!queryFrom.next() || queryFrom.value(0).isNull()) + { + INFO_LOG(m_logger) << "No snapshot available for topic {{ dt.name }}"; + return true; + } + const qint64 idSnapshot = queryFrom.value(0).toLongLong(); + + const QString stmtSelect(QStringLiteral("SELECT dds_is_alive, {% for row in dt.recTable.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} FROM ") + + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) + + QStringLiteral(" WHERE rec_id IN (SELECT reduce_dim2(rec_id_array) FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) + + QStringLiteral(" WHERE id = ") + QString::number(idSnapshot) + QLatin1Char(')')); + + if (!queryFrom.exec(stmtSelect)) + return false; + + return copyDataPrepared{{ dt.name }}(queryFrom, queryTo); +} + +bool RecordReplayPlugin::copyData{{ dt.name }}(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 fromTime, qint64 toTime) +{ + const QString stmt(QStringLiteral("SELECT dds_is_alive, {% for row in dt.recTable.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} FROM ") + + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) + + QStringLiteral(" WHERE sample_time > " ) + QString::number(fromTime) + + QStringLiteral(" AND sample_time <= ") + QString::number(toTime) + QLatin1Char(';')); + + if (!queryFrom.exec(stmt)) + return false; + + return copyDataPrepared{{ dt.name }}(queryFrom, queryTo); +} + +bool RecordReplayPlugin::copyDataPrepared{{ dt.name }}(NSqlQuery &queryFromPrepared, NSqlQuery &queryTo) +{ + const QString stmtInsert(QStringLiteral("INSERT INTO ") + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasWrite) + + QStringLiteral(" ( dds_is_alive, {% for row in dt.recTable.cppMember %} {{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}) VALUES " + " (:dds_is_alive, {% for row in dt.recTable.cppMember %}:{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %});")); + + if (!queryTo.prepare(stmtInsert)) + return false; + + while (queryFromPrepared.next()) + { + int i = 0; + + queryTo.bindValue(QStringLiteral(":dds_is_alive"), queryFromPrepared.value(i++)); + {% for row in dt.recTable.cppMember %} + queryTo.bindValue(QStringLiteral(":{{ row.dbColumnName }}"), queryFromPrepared.value(i++)); + {% endfor %} + if (!queryTo.exec()) + return false; + } + return true; +} + +{% endif %} {% endfor %} + +} // namespace {{ namespace }} + +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_record_replay_plugin.cpp.grantlee_possible_extensions b/Lts/Common/CodeGen/svc_record_replay_plugin.cpp.grantlee_possible_extensions new file mode 100755 index 000000000..97a6e9240 --- /dev/null +++ b/Lts/Common/CodeGen/svc_record_replay_plugin.cpp.grantlee_possible_extensions @@ -0,0 +1,637 @@ +/** + * RecordReplay Plugin + * $Id: svc_record_replay_plugin.cpp.grantlee 50268 2020-03-06 07:42:09Z ehrlicher $ + * $HeadURL: svn://slssvn/TDS/branches/product/iterations/21_07_assault_energy/Lts/Common/CodeGen/svc_record_replay_plugin.cpp.grantlee $ + * $Author: ehrlicher $ + * Created by Christian Ehrlicher, SLSE, 2016-07-21 + */ + + +Leider gibnt es Probleme, wenn man Sequenzen von Sequenzen von Non-PODs erstellt. +Es erbgaben sich unresolved symbols... (Compiler war zufrieden, Linker aber nicht - hmm) + +Mit dem folgenden Konstrukt konnten ich diese "beheben", doch je nach Konstellation in der XYZ_service.xml +ergaben sich Fehler a la "template specialication after instatiation" + +Daher haben wir (zumindest zunächst) Abstand von dieser Erweiterung genommen. + +... + +{% for dt in serializeTopics.member %} +// +// Begin {{ dt.typeFq }} +// +template<> +{{ dt.typeFq }} fromJson(const QJsonValue &val, type<{{ dt.typeFq }}>) +{ + const QJsonObject objElement(val.toObject()); + {{ dt.typeFq }} element; + {% for row in dt.recTable.cppMember %} + { {{ row.jsonElementToDds }}; } {% endfor %} + + return element; +} +template<> +QJsonValue toJson(const {{ dt.typeFq }} &element) +{ + QJsonObject obj; + {% for row in dt.recTable.cppMember %} + {{ row.ddsToJsonElement }}; {% endfor %} + + return obj; +} + +template <> +QVariant toQVariant(const {{ dt.typeFq }} &val) +{ + return QString::fromUtf8(QJsonDocument(toJson(val).toObject()).toJson()); +} + +template<> +{{ dt.typeFq }} fromQVariant(const QVariant &val, type<{{ dt.typeFq }}>) +{ + return fromJson(QJsonDocument::fromJson(val.toString().toUtf8()).object(), type<{{ dt.typeFq }}>{}); +} + +// +// extensions to support sequence of seqence of Non-POD : +// + +template<> +QJsonValue toJson(const std::vector < {{ dt.typeFq }} > &data) +{ + if (data.size() == 0) + return QJsonValue(); + QJsonArray array; + for(size_t i = 0; i < data.size(); ++i) + array.push_back(toJson(data.at(i))); + + return QJsonDocument(array).object(); +} + +template<> +std::vector < {{ dt.typeFq }} > fromJson(const QJsonValue &val) +{ + std::vector < {{ dt.typeFq }} > ret; + if (val.isNull() == 0) + return ret; + if (val.isArray() == 0) return ret; + const QJsonArray array = val.toArray(); + ret.reserve(array.size()); + for(const auto &ele : array) + ret.push_back(fromJson(ele, type<{{ dt.typeFq }}>{})); + return ret; +} +// +// : extensions to support sequence of seqence of Non-POD +// + + +// +// End {{ dt.typeFq }} +// +{% endfor %} + + + + +RecordReplayPlugin::RecordReplayPlugin() + : m_exerciseId(0) + , m_bCanHandleSchemasRead(true) + , m_bCanHandleSchemasWrite(true) + , m_logger(getLogger("RecordReplay")) +{} + +RecordReplayPlugin::~RecordReplayPlugin() = default; + +QString RecordReplayPlugin::getTableName(const QString &tableName, bool bCanHandleSchemas) const +{ + if (!bCanHandleSchemas) + return tableName; + return DBCommon::getExerciseTableName(tableName, m_exerciseId); +} + +bool RecordReplayPlugin::initDtc(const std::shared_ptr &ddsConnector, + int exerciseId) +{ + m_ddsConnector = ddsConnector; + m_exerciseId = exerciseId; + if (!m_ddsConnector) + return false; +{% for dt in dataTopics.member %} {% if dt.recordTopic %} + m_dtc{{ dt.name }}.reset(m_ddsConnector->createDTC()); +{% endif %} {% endfor %} + + return true; +} + +bool RecordReplayPlugin::initDts(const std::shared_ptr &ddsConnector, + int exerciseId, + bool bCanHandleSchemas, + NSqlQuery &query) +{ + Q_UNUSED(query); + m_ddsConnector = ddsConnector; + m_exerciseId = exerciseId; + m_bCanHandleSchemasRead = bCanHandleSchemas; + m_bCanHandleSchemasWrite = bCanHandleSchemas; + if (!m_ddsConnector) + return false; +{% for dt in dataTopics.member %} {% if dt.recordTopic %} + m_dts{{ dt.name }}.reset(m_ddsConnector->createDTS()); +{% endif %} {% endfor %} + + return true; +} + +bool RecordReplayPlugin::initCopy(int exerciseId, bool bCanHandleSchemasRead, bool bCanHandleSchemasWrite) +{ + m_exerciseId = exerciseId; + m_bCanHandleSchemasRead = bCanHandleSchemasRead; + m_bCanHandleSchemasWrite = bCanHandleSchemasWrite; + return true; +} + +bool RecordReplayPlugin::copyData(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 fromTime, qint64 toTime) +{ + {% for dt in dataTopics.member %} {% if dt.recordTopic %} + // {{ dt.name }} + if (!copyInitialData{{ dt.name }}(queryFrom, queryTo, fromTime)) return false; + if (!copyData{{ dt.name }}(queryFrom, queryTo, fromTime, toTime)) return false; + {% endif %} {% endfor %} + + return true; +} + +bool RecordReplayPlugin::ddsTopicsToDatabase(NSqlQuery &q, QHash &minRecordSampleTimes) +{ +{% for dt in dataTopics.member %} {% if dt.recordTopic %} + if (!write{{ dt.name }}(q, minRecordSampleTimes)) return false; +{% endif %} {% endfor %} + + return true; +} + +bool RecordReplayPlugin::setReplayStartTime(NSqlQuery &q, qint64 startTime) +{ +{% for dt in dataTopics.member %} {% if dt.recordTopic %} + if (!read{{ dt.name }}At(q, startTime)) return false; +{% endif %} {% endfor %} + + return true; +} + +bool RecordReplayPlugin::ddsTopicsFromDatabase(NSqlQuery &q, qint64 fromTime, qint64 toTime) +{ +{% for dt in dataTopics.member %} {% if dt.recordTopic %} + if (!read{{ dt.name }}(q, fromTime, toTime)) return false; +{% endif %} {% endfor %} + + return true; +} + +bool RecordReplayPlugin::publishData(qint64 desiredTime) +{ +{% for dt in dataTopics.member %} {% if dt.recordTopic %} + if (!publish{{ dt.name }}(desiredTime)) return false; +{% endif %} {% endfor %} + + return true; +} + +void RecordReplayPlugin::clearData() +{ +{% for dt in dataTopics.member %} {% if dt.recordTopic %} + clearData{{ dt.name }}(); +{% endif %} {% endfor %} +} + +std::map> RecordReplayPlugin::getSnapshotTimes(NSqlQuery &q, + qint64 toTime, + qint64 interval, + const QHash &minRecordedSampleTimes) +{ + std::map> ret; +{% for dt in dataTopics.member %} {% if dt.recordTopic %} + { + const QString &topicName(QStringLiteral("{{ dt.name }}")); + std::vector vec; + auto it = minRecordedSampleTimes.find(topicName); + if (it != minRecordedSampleTimes.end()) + { + qint64 nextSampleTime = getNextSampleTimeFor{{ dt.name }}(q, it.value()); // is 0 or -1 if no samples are available + nextSampleTime -= (nextSampleTime % interval); // sync to interval - only needed for better debugging + + if (nextSampleTime != m_snapshot{{ dt.name }}MapTime) + { + // the current snapshot data does not match the starting time -> reread m_{{ dt.nameFlc }}Keys + readLastSnapshotKeysFor{{ dt.name }}(q, nextSampleTime); + //DEBUG_LOG(m_logger) << "fromTime: " << fromTime << ", m_snapshotParticipantMapTime: " << m_snapshotParticipantMapTime + // << " - rereading snapshot times! Map now contains " << m_snapshot{{ dt.name }}Map.size() << " entries"; + } + + while (nextSampleTime > 0 && nextSampleTime <= toTime) + { + const qint64 intervalStart = nextSampleTime; + const qint64 intervalEnd = intervalStart + interval; + updateSnapshotKeys{{ dt.name }}(q, intervalStart, intervalEnd); + + vec.emplace_back(RecSnapshot(intervalEnd, m_snapshot{{ dt.name }}Map)); + + nextSampleTime = getNextSampleTimeFor{{ dt.name }}(q, intervalEnd); + nextSampleTime -= (nextSampleTime % interval); // sync to interval - only needed for better debugging + } + } + if (!vec.empty()) + ret.insert({topicName, std::move(vec)}); + } +{% endif %} {% endfor %} + + return ret; +} + +QVector RecordReplayPlugin::getRecordTopicNames() const +{ + return {{% for dt in dataTopics.member %} {% if dt.recordTopic %} + QStringLiteral("{{ dt.name }}"){% if not forloop.last %}, {% endif %} {% endif %} {% endfor %} + }; +} + +QVector RecordReplayPlugin::getRecordTableNames() const +{ + return {{% for dt in dataTopics.member %} {% if dt.recordTopic %} + QStringLiteral("{{ dt.recTable.name }}"){% if not forloop.last %}, {% endif %} {% endif %} {% endfor %} + }; +} + +qint64 RecordReplayPlugin::getMinimumSampleTime(NSqlQuery &q, const QString &topicName, qint64 defaultValue) const +{ + QString tableName; + {% for dt in dataTopics.member %} {% if dt.recordTopic %} + if (topicName == QLatin1String("{{ dt.name }}")) + tableName = QStringLiteral("{{ dt.recTable.name }}"); + {% endif %} {% endfor %} + if (tableName.isEmpty()) + return defaultValue; + const QString stmt(QStringLiteral("SELECT MIN(sample_time) FROM ") + tableName); + if (!q.execAndFetch(stmt)) + return defaultValue; + const QVariant val = q.value(0); + return val.isNull() ? defaultValue : val.toLongLong(); +} + +{% for dt in dataTopics.member %} {% if dt.recordTopic %} + +bool RecordReplayPlugin::read{{ dt.name }}At(NSqlQuery &q, qint64 startTime) +{ + // read snapshot data for {{ dt.name }} + if (!readLastSnapshotFor{{ dt.name }}(q, startTime)) return false; + // read other samples between snapshot time and startTime + if (!read{{ dt.name }}(q, m_snapshot{{ dt.name }}MapTime, startTime)) return false; + return true; +} + +bool RecordReplayPlugin::write{{ dt.name }}(NSqlQuery &q, QHash &minRecordSampleTimes) +{ + using DATA = {{ namespace }}::{{ dt.name }}; + + if (!m_dtc{{ dt.name }}) + return false; + + qint64 minRecordSampleTime = std::numeric_limits::max(); + + if (m_dtc{{ dt.name }}->hasSamples()) + { + TRACE_LOG(m_logger) << "m_dtc{{ dt.name }}->hasSamples() == true "; + + const QString stmt(QStringLiteral("INSERT INTO ") + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasWrite) + + QStringLiteral(" ( dds_is_alive, {% for row in dt.recTable.cppMember %} {{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}) VALUES " + " (:dds_is_alive, {% for row in dt.recTable.cppMember %}:{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %});")); + + if (!q.prepare(stmt)) + return false; + + dds::sub::LoanedSamples samples = m_dtc{{ dt.name }}->takeAlive(); + TRACE_LOG(m_logger) << "samples count: " << samples.length(); + for (const auto &sample : samples) + { + const DATA &data = sample.data(); + INFO_LOG(m_logger) << "m_dtc{{ dt.name }} {{ dt.recTable.ddsKey.cppMemberName }}: " << data.{{ dt.recTable.ddsKey.cppMemberName }}(); + + {% for row in dt.recTable.cppMember %} + q.bindValue(QStringLiteral(":{{ row.dbColumnName }}"), {{ row.ddsToQVariant }}); + {% endfor %} + q.bindValue(QStringLiteral(":dds_is_alive"), true); + + if (!q.exec()) + return false; + + minRecordSampleTime = std::min(minRecordSampleTime, data.sampleTime()); + } + } + + dds::sub::LoanedSamples disposedSamples = m_dtc{{ dt.name }}->takeDisposed(); + if (disposedSamples.length() != 0) + { + const QString stmt(QStringLiteral("INSERT INTO ") + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasWrite) + + QStringLiteral(" ( dds_is_alive, sample_time, {{ dt.recTable.ddsKey.dbColumnName }}) VALUES " + " (:dds_is_alive,:sample_time,:{{ dt.recTable.ddsKey.dbColumnName }});")); + + if (!q.prepare(stmt)) + return false; + + DEBUG_LOG(m_logger) << "disposedSamples count: " << disposedSamples.length(); + auto dr = m_dtc{{ dt.name }}->dataReader()->dataReader(); + {{ dt.name }} data; + for (const auto &sample : disposedSamples) + { + const qint64 sampleTime = sample.info().source_timestamp().to_millisecs(); + dr.key_value(data, sample.info().instance_handle()); + q.bindValue(QStringLiteral(":dds_is_alive"), false); + q.bindValue(QStringLiteral(":sample_time"), sampleTime); + q.bindValue(QStringLiteral(":{{ dt.recTable.ddsKey.cppMemberName }}"), {{ dt.recTable.ddsKey.ddsToQVariant }}); + + if (!q.exec()) + return false; + + minRecordSampleTime = std::min(minRecordSampleTime, sampleTime); + } + } + + if (minRecordSampleTime != std::numeric_limits::max()) + minRecordSampleTimes[QStringLiteral("{{ dt.name }}")] = minRecordSampleTime; + + return true; +} + +// read the last snapshot which is <= startTime, set m_snapshot{{ dt.name }}MapTime to the snapshot time +bool RecordReplayPlugin::readLastSnapshotFor{{ dt.name }}(NSqlQuery &q, qint64 startTime) +{ + // rebuild m_{{ dt.nameFlc }}Map content for given startTime + clearData{{ dt.name }}(); + + const QString stmt(QStringLiteral("SELECT id, time_stamp FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) + + QStringLiteral(" WHERE svc_name = '{{ namespace_abbr}}' AND topic_name = '{{ dt.name }}' AND time_stamp <= %1 " + " ORDER BY time_stamp DESC LIMIT 1").arg(startTime)); + + if (!q.exec(stmt)) + { + ERROR_LOG(m_logger) << "Error while fetch max(time_stamp) for topic {{ dt.name }}"; + return false; + } + if (!q.next() || q.value(0).isNull()) + { + INFO_LOG(m_logger) << "No snapshot available for topic {{ dt.name }}"; + return true; + } + const qint64 idSnapshot = q.value(0).toLongLong(); + const qint64 timeSnapshot = q.value(1).toLongLong(); + + const QString stmt2(QStringLiteral("SELECT dds_is_alive, {% for row in dt.recTable.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} FROM ") + + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) + + QStringLiteral(" WHERE rec_id IN (SELECT reduce_dim2(rec_id_array) FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) + + QStringLiteral(" WHERE id = ") + QString::number(idSnapshot) + QLatin1Char(')')); + + if (!q.exec(stmt2)) + return false; + + {{ namespace }}::{{ dt.name }} data; + while (q.next()) + { + int i = 0; + + const bool bIsAlive = q.value(i++).toBool(); + {% for row in dt.recTable.viewMember %} + {{ row.qVariantToDds }}; + {% endfor %} + + if (bIsAlive) + m_{{ dt.nameFlc }}Map.insert(data.sampleTime(), data); + else + m_{{ dt.nameFlc }}DisposedMap.insert(data.sampleTime(), data); + m_{{ dt.nameFlc }}Keys.insert(data.{{ dt.key.name }}()); + } + m_snapshot{{ dt.name }}MapTime = timeSnapshot; + + return true; +} + + +bool RecordReplayPlugin::read{{ dt.name }}(NSqlQuery &q, qint64 fromTime, qint64 toTime) +{ + const QString stmt(QStringLiteral("SELECT dds_is_alive, {% for row in dt.recTable.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} FROM ") + + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) + + QStringLiteral(" WHERE sample_time > " ) + QString::number(fromTime) + + QStringLiteral(" AND sample_time <= ") + QString::number(toTime) + QLatin1Char(';')); + + if (!q.exec(stmt)) + return false; + + {{ namespace }}::{{ dt.name }} data; + while (q.next()) + { + int i = 0; + const bool bIsAlive = q.value(i++).toBool(); + + {% for row in dt.recTable.viewMember %} + {{ row.qVariantToDds }}; + {% endfor %} + + if (bIsAlive) + m_{{ dt.nameFlc }}Map.insert(data.sampleTime(), data); + else + m_{{ dt.nameFlc }}DisposedMap.insert(data.sampleTime(), data); + m_{{ dt.nameFlc }}Keys.insert(data.{{ dt.key.name }}()); + } + return true; +} + +bool RecordReplayPlugin::updateSnapshotKeys{{ dt.name }}(NSqlQuery &q, qint64 fromTime, qint64 toTime) +{ + const QString stmt(QStringLiteral("SELECT {{ dt.recTable.ddsKey.dbColumnName }}, {{ dt.recTable.primaryKey.dbColumnName }}, dds_is_alive FROM ") + + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) + + QStringLiteral(" WHERE sample_time > ") + QString::number(fromTime) + + QStringLiteral(" AND sample_time <= ") + QString::number(toTime) + + QStringLiteral(" ORDER BY sample_time ")); + + DEBUG_LOG(m_logger) << "stmt: " << stmt; + + if (!q.exec(stmt)) + return false; + + while (q.next()) + { + const qint64 key = q.value(0){{ dt.recTable.ddsKey.cppFromQvPost }}; + const qint64 pk = q.value(1){{ dt.recTable.primaryKey.cppFromQvPost }}; + const bool isAlive = q.value(2).toBool(); + if (isAlive) + { + DEBUG_LOG(m_logger) << "INSERT {{ dt.name }} key: " << key << ", pk: " << pk << ", isAlive: " << isAlive; + m_snapshot{{ dt.name }}Map[key] = pk; + m_{{ dt.nameFlc }}Keys.insert(key); + } + else + { + DEBUG_LOG(m_logger) << "REMOVE {{ dt.name }} key: " << key << ", pk: " << pk << ", isAlive: " << isAlive; + m_snapshot{{ dt.name }}Map.erase(key); + m_{{ dt.nameFlc }}Keys.erase(key); + } + } + + // new validity time for snapshot data + m_snapshot{{ dt.name }}MapTime = toTime; + + DEBUG_LOG(m_logger) << "updateSnapshotKeys{{ dt.name }}(" << fromTime << ", " << toTime << ") = " + << m_snapshot{{ dt.name }}Map.size(); + + return true; +} + +bool RecordReplayPlugin::readLastSnapshotKeysFor{{ dt.name }}(NSqlQuery &q, qint64 fromTime) +{ + const QString stmt(QStringLiteral("SELECT rec_id_array FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) + + QStringLiteral(" WHERE svc_name = '{{ namespace_abbr}}' AND topic_name = '{{ dt.name }}' AND time_stamp <= %1 " + " ORDER BY time_stamp DESC LIMIT 1").arg(fromTime)); + + // no previous snapshot found? + if (!q.exec(stmt)) + return false; + if (!q.next() || q.value(0).isNull()) + { + DEBUG_LOG(m_logger) << "no snapshot entry for {{ dt.name }} found"; + return true; + } + + m_snapshot{{ dt.name }}Map = LtsUtils::BigintArray2D::fromPgString(q.value(0).toString())(); + + DEBUG_LOG(m_logger) << "Map size {{ dt.name }}: " << m_snapshot{{ dt.name }}Map.size(); + + return true; +} + + +qint64 RecordReplayPlugin::getNextSampleTimeFor{{ dt.name }}(NSqlQuery &q, qint64 fromTime) +{ + const QString stmt(QStringLiteral("SELECT MIN(sample_time) FROM ") + + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) + + QStringLiteral(" WHERE sample_time > ") + QString::number(fromTime)); + qint64 ret = -1; + if (q.execAndFetch(stmt) && !q.value(0).isNull()) + ret = q.value(0).toLongLong(); + + DEBUG_LOG(m_logger) << "next min sampleTime for {{ dt.name }} is " << ret << " (> " << fromTime << ")"; + + return ret; +} + + +bool RecordReplayPlugin::publish{{ dt.name }}(qint64 desiredTime) +{ + if (!m_dts{{ dt.name }}) + return false; + { + auto it = m_{{ dt.nameFlc }}Map.begin(); + while (it != m_{{ dt.nameFlc }}Map.end() && it.key() <= desiredTime) + { + m_dts{{ dt.name }}->write(it.value()); + it = m_{{ dt.nameFlc }}Map.erase(it); + } + } + { + auto it = m_{{ dt.nameFlc }}DisposedMap.begin(); + while (it != m_{{ dt.nameFlc }}DisposedMap.end() && it.key() <= desiredTime) + { + m_dts{{ dt.name }}->remove(it.value()); + it = m_{{ dt.nameFlc }}DisposedMap.erase(it); + } + } + + return true; +} + +void RecordReplayPlugin::clearData{{ dt.name }}() +{ + if (m_dts{{ dt.name }}) + { + {{ dt.name }} tmpData; + for (auto key : m_{{ dt.nameFlc }}Keys) + { + tmpData.{{ dt.key.name }}(key); + m_dts{{ dt.name }}->remove(tmpData); + } + } + m_{{ dt.nameFlc }}Keys.clear(); + m_{{ dt.nameFlc }}Map.clear(); + m_{{ dt.nameFlc }}DisposedMap.clear(); +} + +bool RecordReplayPlugin::copyInitialData{{ dt.name }}(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 startTime) +{ + const QString stmt(QStringLiteral("SELECT id FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) + + QStringLiteral(" WHERE svc_name = '{{ namespace_abbr}}' AND topic_name = '{{ dt.name }}' AND time_stamp <= %1 " + " ORDER BY time_stamp DESC LIMIT 1").arg(startTime)); + + if (!queryFrom.exec(stmt)) + { + ERROR_LOG(m_logger) << "Error while fetch max(time_stamp) for topic {{ dt.name }}"; + return false; + } + if (!queryFrom.next() || queryFrom.value(0).isNull()) + { + INFO_LOG(m_logger) << "No snapshot available for topic {{ dt.name }}"; + return true; + } + const qint64 idSnapshot = queryFrom.value(0).toLongLong(); + + const QString stmtSelect(QStringLiteral("SELECT dds_is_alive, {% for row in dt.recTable.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} FROM ") + + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) + + QStringLiteral(" WHERE rec_id IN (SELECT reduce_dim2(rec_id_array) FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) + + QStringLiteral(" WHERE id = ") + QString::number(idSnapshot) + QLatin1Char(')')); + + if (!queryFrom.exec(stmtSelect)) + return false; + + return copyDataPrepared{{ dt.name }}(queryFrom, queryTo); +} + +bool RecordReplayPlugin::copyData{{ dt.name }}(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 fromTime, qint64 toTime) +{ + const QString stmt(QStringLiteral("SELECT dds_is_alive, {% for row in dt.recTable.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} FROM ") + + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) + + QStringLiteral(" WHERE sample_time > " ) + QString::number(fromTime) + + QStringLiteral(" AND sample_time <= ") + QString::number(toTime) + QLatin1Char(';')); + + if (!queryFrom.exec(stmt)) + return false; + + return copyDataPrepared{{ dt.name }}(queryFrom, queryTo); +} + +bool RecordReplayPlugin::copyDataPrepared{{ dt.name }}(NSqlQuery &queryFromPrepared, NSqlQuery &queryTo) +{ + const QString stmtInsert(QStringLiteral("INSERT INTO ") + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasWrite) + + QStringLiteral(" ( dds_is_alive, {% for row in dt.recTable.cppMember %} {{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}) VALUES " + " (:dds_is_alive, {% for row in dt.recTable.cppMember %}:{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %});")); + + if (!queryTo.prepare(stmtInsert)) + return false; + + while (queryFromPrepared.next()) + { + int i = 0; + + queryTo.bindValue(QStringLiteral(":dds_is_alive"), queryFromPrepared.value(i++)); + {% for row in dt.recTable.cppMember %} + queryTo.bindValue(QStringLiteral(":{{ row.dbColumnName }}"), queryFromPrepared.value(i++)); + {% endfor %} + if (!queryTo.exec()) + return false; + } + return true; +} + +{% endif %} {% endfor %} + +} // namespace {{ namespace }} + +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_record_replay_plugin.h.grantlee b/Lts/Common/CodeGen/svc_record_replay_plugin.h.grantlee new file mode 100755 index 000000000..2aded1edd --- /dev/null +++ b/Lts/Common/CodeGen/svc_record_replay_plugin.h.grantlee @@ -0,0 +1,126 @@ +/** + * RecordReplay Plugin + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Christian Ehrlicher, SLSE, 2016-07-21 + */ + +{% autoescape off %} +// filename: {{ namespace_abbr }}_record_replay_plugin.h +#pragma once + +#include +#include +#include +#include +#include <{{ include_prefix }}/{{ namespace_abbr }}_dtc_types.h> +#include <{{ include_prefix }}/{{ namespace_abbr }}_dts_types.h> +#include <{{ include_prefix }}/logger.h> + +// TODO: Problem with QT5_WRAP_CPP ... +#include <../../Common/PluginInterfaces/record_replay_plugin.h> + +#include + +{% if custom_record_replay %} +#ifndef {{ namespace_abbr_upper }}RECORDREPLAYPLUGIN_EXPORT +# ifdef BUILD_STATIC_LIBRARIES +# define {{ namespace_abbr_upper }}RECORDREPLAYPLUGIN_EXPORT +# else +# if defined(MAKE_{{ namespace_abbr_upper }}RECORDREPLAYPLUGIN_LIB) +# define {{ namespace_abbr_upper }}RECORDREPLAYPLUGIN_EXPORT Q_DECL_EXPORT +# else +# define {{ namespace_abbr_upper }}RECORDREPLAYPLUGIN_EXPORT Q_DECL_IMPORT +# endif +# endif +#endif +{% endif %} + +namespace {{ namespace }} { + +class {% if custom_record_replay %}{{ namespace_abbr_upper }}RECORDREPLAYPLUGIN_EXPORT{% endif %} + RecordReplayPlugin {% if not custom_record_replay %}final{% endif %}: public QObject, public RecordReplay::RecordReplayPlugin +{ + Q_OBJECT +{% if not custom_record_replay %} + Q_PLUGIN_METADATA(IID "special:3:recordreplay.{{ namespace_abbr }}") +{% endif %} + Q_INTERFACES(RecordReplay::RecordReplayPlugin) +public: + RecordReplayPlugin(); + ~RecordReplayPlugin(); + QString pluginName() const override { return QStringLiteral("{{ namespace_abbr }}RecordReplayPlugin"); } + + bool initDtc(const std::shared_ptr &ddsConnector, + int exerciseId) override; + bool initDts(const std::shared_ptr &ddsConnector, + int exerciseId, + bool bCanHandleSchemas, + NSqlQuery &query) override; + bool initCopy(int exerciseId, bool bCanHandleSchemasRead, bool bCanHandleSchemasWrite) override; + bool copyData(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 fromTime, qint64 toTime) override; + QString svcShortName() const override { return QStringLiteral("{{ namespace_abbr }}"); } + // recording functions + bool ddsTopicsToDatabase(NSqlQuery &q, QHash &minRecordSampleTimes) override; + // publishing functions + bool setReplayStartTime(NSqlQuery &q, qint64 startTime) override; + bool ddsTopicsFromDatabase(NSqlQuery &q, qint64 fromTime, qint64 toTime) override; + bool publishData(qint64 desiredTime) override; + void clearData() override; + + // structure/functions for snapshot creation + std::map> getSnapshotTimes(NSqlQuery &query, + qint64 toTime, + qint64 interval, + const QHash &minRecordedSampleTimes) override; + QVector getRecordTopicNames() const override; + QVector getRecordTableNames() const override; + qint64 getMinimumSampleTime(NSqlQuery &q, const QString &topicName, qint64 defaultValue) const override; + + // getter for initial service startup +{% for dt in dataTopics.member %} {% if dt.recordTopic %} + // {{ dt.name }} + QMultiMap &get{{ dt.name }}Map() { return m_{{ dt.nameFlc }}Map; } + const QMultiMap &get{{ dt.name }}Map() const { return m_{{ dt.nameFlc }}Map; } + bool read{{ dt.name }}At(NSqlQuery &q, qint64 startTime); +{% endif %} {% endfor %} + +protected: +{% for dt in dataTopics.member %} {% if dt.recordTopic %} + // {{ dt.name }} + bool write{{ dt.name }}(NSqlQuery &q, QHash &minRecordSampleTimes); + bool read{{ dt.name }}(NSqlQuery &q, qint64 fromTime, qint64 toTime); + bool readLastSnapshotFor{{ dt.name }}(NSqlQuery &q, qint64 startTime); + bool updateSnapshotKeys{{ dt.name }}(NSqlQuery &q, qint64 fromTime, qint64 toTime); + bool readLastSnapshotKeysFor{{ dt.name }}(NSqlQuery &q, qint64 fromTime); + qint64 getNextSampleTimeFor{{ dt.name }}(NSqlQuery &q, qint64 fromTime); + bool publish{{ dt.name }}(qint64 desiredTime); + void clearData{{ dt.name }}(); + bool copyInitialData{{ dt.name }}(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 startTime); + bool copyData{{ dt.name }}(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 fromTime, qint64 toTime); + bool copyDataPrepared{{ dt.name }}(NSqlQuery &queryFromPrepared, NSqlQuery &queryTo); +{% endif %} {% endfor %} + + QString getTableName(const QString &tableName, bool bCanHandleSchemas) const; +protected: + std::shared_ptr m_ddsConnector; + int m_exerciseId; + bool m_bCanHandleSchemasRead; + bool m_bCanHandleSchemasWrite; +{% for dt in dataTopics.member %} {% if dt.recordTopic %} + // {{ dt.name }} + std::unique_ptr m_dtc{{ dt.name }}; + std::unique_ptr m_dts{{ dt.name }}; + QMultiMap m_{{ dt.nameFlc }}Map; + QMultiMap m_{{ dt.nameFlc }}DisposedMap; + std::set m_{{ dt.nameFlc }}Keys; + std::map m_snapshot{{ dt.name }}Map; + int64_t m_snapshot{{ dt.name }}MapTime = -1; // time for which the snapshot is valid +{% endif %} {% endfor %} + + LOGGER_T m_logger; +}; + +} +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_topic_type_plugin.cpp.grantlee b/Lts/Common/CodeGen/svc_topic_type_plugin.cpp.grantlee new file mode 100755 index 000000000..8f944ce20 --- /dev/null +++ b/Lts/Common/CodeGen/svc_topic_type_plugin.cpp.grantlee @@ -0,0 +1,102 @@ +/** + * DDS Type Plugin to provide custom types to other applications like ddsview + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Christian Ehrlicher, SLSE, 2016-07-08 + */ + +{% autoescape off %} +// filename: {{ namespace_abbr }}_topic_type_plugin.cpp + +#include "{{ namespace_abbr }}_topic_type_plugin.h" +#include <{{ namespace_abbr }}_dtc_types.h> +#include <{{ namespace_abbr }}_ftc_types.h> +#include <{{ namespace_abbr }}_fts_types.h> +#include +#include +#include +#include + +namespace {{ namespace }} +{ +QMap TopicTypePlugin::getDataTopics() const +{ + return + {{% for dt in dataTopics.member %} + { + dds::topic::topic_type_name<{{ dt.name }}>::value(), + { + DDS::Helper::getShortTopicName(dds::topic::topic_type_name<{{ dt.name }}>::value()), + dds::topic::topic_type_name<{{ dt.name }}>::value(), + rti::topic::dynamic_type<{{ dt.name }}>::get() + } + }{% if not forloop.last %}, {% endif %}{% endfor %} + }; +} + +QMap TopicTypePlugin::getFunctionTopics() const +{ + return + {{% for ft in functionTopics.member %} + { + FTC::TopicNames::requestTopicName(), + { + FTC::TopicNames::requestTopicName(), + dds::topic::topic_type_name::value(), + rti::topic::dynamic_type::get() + } + }, + { + FTC::TopicNames::replyTopicName(), + { + FTC::TopicNames::replyTopicName(), + dds::topic::topic_type_name::value(), + rti::topic::dynamic_type::get() + } + }{% if not forloop.last %}, {% endif %}{% endfor %} + }; +} + +inline NGL::INGLLogger &logger(const char *name) +{ + return NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger(name); +} + +std::vector TopicTypePlugin::topicsToRouteToWs(IDL::PartitionType partitionType) const +{ + if (partitionType == IDL::PartitionType::Replay) + return + {{% for dt in dataTopics.topicsToRouteInReplay %} + new RoutingService::DataTopicDispatcher<{{ dt.name }}>(logger("RSC.{{ namespace_abbr_upper }}.{{ dt.name }}")),{% endfor %} + {% for ft in functionTopics.topicsToRouteInReplay %} + new RoutingService::FunctionTopicReplyDispatcher(logger("RSC.{{ namespace_abbr_upper }}.{{ ft.name }}.Reply")){% if not forloop.last %},{% endif %}{% endfor %} + }; + + return + {{% for dt in dataTopics.topicsToRouteToClient %} + new RoutingService::DataTopicDispatcher<{{ dt.name }}>(logger("RSC.{{ namespace_abbr_upper }}.{{ dt.name }}")),{% endfor %} + {% for ft in functionTopics.member %} + new RoutingService::FunctionTopicReplyDispatcher(logger("RSC.{{ namespace_abbr_upper }}.{{ ft.name }}.Reply")){% if not forloop.last %},{% endif %}{% endfor %} + }; +} + +std::vector TopicTypePlugin::topicsToRouteToSv(IDL::PartitionType partitionType) const +{ + if (partitionType == IDL::PartitionType::Replay) + return + {{% for ft in functionTopics.topicsToRouteInReplay %} + new RoutingService::FunctionTopicRequestDispatcher(logger("RSC.{{ namespace_abbr_upper }}.{{ ft.name }}.Request")){% if not forloop.last %},{% endif %}{% endfor %} + }; + + return + {{% for dt in dataTopics.topicsToRouteToServer %} + new RoutingService::DataTopicDispatcher<{{ dt.name }}>(logger("RSC.{{ namespace_abbr_upper }}.{{ dt.name }}")),{% endfor %} + {% for ft in functionTopics.member %} + new RoutingService::FunctionTopicRequestDispatcher(logger("RSC.{{ namespace_abbr_upper }}.{{ ft.name }}.Request")){% if not forloop.last %},{% endif %}{% endfor %} + }; +} + +} // namespace {{ namespace }} + +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_topic_type_plugin.h.grantlee b/Lts/Common/CodeGen/svc_topic_type_plugin.h.grantlee new file mode 100755 index 000000000..ea0b10408 --- /dev/null +++ b/Lts/Common/CodeGen/svc_topic_type_plugin.h.grantlee @@ -0,0 +1,39 @@ +/** + * DDS Type Plugin to provide custom types to other applications like ddsview + * $Id$ + * $HeadURL$ + * $Author$ + * Created by Christian Ehrlicher, SLSE, 2016-07-08 + */ + +#pragma once + +{% autoescape off %} +// filename: {{ namespace_abbr }}_topic_type_plugin.h + +#include <{{ include_prefix }}/{{ namespace_abbr }}.hpp> +#include + +#include +// TODO: Problem with QT5_WRAP_CPP ... +#include <../../DDS/Generic/topic_type_plugin.h> + +namespace {{ namespace }} +{ +class TopicTypePlugin final : public QObject, public DDSGeneric::TopicTypePlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "topic_type:3:{{ namespace_abbr }}") + Q_INTERFACES(DDSGeneric::TopicTypePlugin) +public: + TopicTypePlugin() = default; + + QMap getDataTopics() const override final; + QMap getFunctionTopics() const override final; + std::vector topicsToRouteToWs(IDL::PartitionType partitionType) const override final; + std::vector topicsToRouteToSv(IDL::PartitionType partitionType) const override final; + QString getServiceShortName() const override final { return QStringLiteral("{{ namespace_abbr_upper }}"); } +}; +} + +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_translation.cpp.grantlee b/Lts/Common/CodeGen/svc_translation.cpp.grantlee new file mode 100755 index 000000000..ebf6bea72 --- /dev/null +++ b/Lts/Common/CodeGen/svc_translation.cpp.grantlee @@ -0,0 +1,43 @@ +/** +* @RDE_HEADER@ +* Convert DSS enumerations to translatable strings +* $Id$ +* $HeadURL$ +* $Author$ +*/ + +{% autoescape off %} +// filename: {{ namespace_abbr }}_translation.cpp + +#include "{{ namespace_abbr }}_translation.h" +#include + +namespace {{ namespace }} +{ +{% for idl_enum in idl_enums %} +// +// {{ idl_enum.name }} +// +QString toString({{ idl_enum.name }} state) +{ + switch(state.underlying()) + {{% for idl_member in idl_enum.member %} + case {{ idl_enum.name }}_def::{{ idl_member.key }}: return QCoreApplication::translate("{{ idl_enum.name }}", "{{ idl_member.key }}"); + {% endfor %} + default: break; + } + return QCoreApplication::translate("{{ idl_enum.name }}", "Unknown (%1)").arg(state.underlying()); +} +{% endfor %} + +{% for dt in dataTopics.member %}{% if dt.hasTrColumns %}{% for tr in dt.trColumns %} +// {{ dt.name }}::{{ tr.name }} +QString tr{{ dt.name }}{{ tr.nameCc }}(const std::string &text) +{ + return QCoreApplication::translate("{{ dt.name }}", text.c_str(), "{{ tr.name }}"); +} +{% endfor %}{% endif %}{% endfor %} + +} // namespace {{ namespace }} + +{% endautoescape %} diff --git a/Lts/Common/CodeGen/svc_translation.h.grantlee b/Lts/Common/CodeGen/svc_translation.h.grantlee new file mode 100755 index 000000000..5f7c33796 --- /dev/null +++ b/Lts/Common/CodeGen/svc_translation.h.grantlee @@ -0,0 +1,31 @@ +/** +* @RDE_HEADER@ +* Convert DSS enumerations to translatable strings +* $Id$ +* $HeadURL$ +* $Author$ +*/ + +{% autoescape off %} +#pragma once + +// filename: {{ namespace_abbr }}_translation.h + +#include "{{ namespace_abbr }}_enums.hpp" +#include <{{ include_prefix }}/{{ namespace_abbr }}_export.h> + +class QString; + +namespace {{ namespace }} +{ +// Translatable enumerations: +{% for idl_enum in idl_enums %} +{{ namespace_abbr_upper }}_COMMON_EXPORT QString toString({{ idl_enum.name }} state);{% endfor %} + +// Translatable struct members: +{% for dt in dataTopics.member %}{% if dt.hasTrColumns %}{% for tr in dt.trColumns %} +{{ namespace_abbr_upper }}_COMMON_EXPORT QString tr{{ dt.nameCc }}{{ tr.nameCc }}(const std::string &text); // {{ dt.nameCc }}::{{ tr.name }} {% endfor %}{% endif %}{% endfor %} + +} // namespace {{ namespace }} + +{% endautoescape %} diff --git a/Lts/Common/LtsAppDescriptor/CMakeLists.txt b/Lts/Common/LtsAppDescriptor/CMakeLists.txt new file mode 100755 index 000000000..1c9d87f41 --- /dev/null +++ b/Lts/Common/LtsAppDescriptor/CMakeLists.txt @@ -0,0 +1,78 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# +# Created by Mike Wille, SLSE, 2016-10-24 +# + + +# LtsAppDescriptor (lib) +SET(LtsAppDescriptor_SRCS + lts_app_descriptor.cpp + lts_app_descriptor.h +) + +NGL_ADD_LIBRARY(WS LtsAppDescriptor SHARED ${LtsAppDescriptor_SRCS}) + +NGL_TARGET_INCLUDE_DIRECTORIES(LtsAppDescriptor + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) + +NGL_TARGET_LINK_LIBRARIES(LtsAppDescriptor + PUBLIC + Qt5::Core +) + +NGL_INSTALL_TARGET(LtsAppDescriptor) + + + +# LtsAppDescriptorChecker +SET(LtsAppDescriptorChecker_SRCS + checker/main.cpp +) + +NGL_ADD_EXECUTABLE(WS LtsAppDescriptorChecker ${LtsAppDescriptorChecker_SRCS}) + +NGL_TARGET_LINK_LIBRARIES(LtsAppDescriptorChecker + PRIVATE + LtsAppDescriptor + PUBLIC + Qt5::Core +) + + + + + +# LtsAppDescriptorEditor => Hat niemand benutzt, wird keiner benutzen, also wird der bis auf Weiteres auch nicht an die neue Syntax angepasst. +#SET(LtsAppDescriptorEditor_SRCS +# lts_app_descriptor_editor.cpp +# lts_app_descriptor_editor.h +#) + +#NGL_WRAP_UI(LtsAppDescriptorEditor_SRCS +# lts_app_descriptor_editor.ui +#) + +#NGL_ADD_LIBRARY(WS NO_TRANSLATION LtsAppDescriptorEditor SHARED ${LtsAppDescriptorEditor_SRCS}) + +#NGL_TARGET_INCLUDE_DIRECTORIES(LtsAppDescriptorEditor +# PUBLIC +# ${CMAKE_CURRENT_SOURCE_DIR}/../ +#) + +#NGL_TARGET_LINK_LIBRARIES(LtsAppDescriptorEditor +# PUBLIC +# Qt5::Core +# Qt5::Widgets +# LtsAppDescriptor +#) + +#NGL_INSTALL_TARGET(LtsAppDescriptorEditor) #not visible for end-user + +## Executable: +#NGL_ADD_EXECUTABLE(WS NO_TRANSLATION LtsAppDescriptorEditorApp main.cpp) +#NGL_TARGET_LINK_LIBRARIES(LtsAppDescriptorEditorApp LtsAppDescriptorEditor) diff --git a/Lts/Common/LtsAppDescriptor/checker/main.cpp b/Lts/Common/LtsAppDescriptor/checker/main.cpp new file mode 100755 index 000000000..725bf2a71 --- /dev/null +++ b/Lts/Common/LtsAppDescriptor/checker/main.cpp @@ -0,0 +1,138 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + const QStringList args(a.arguments()); + + if (args.count() != 3) + { + printf("usage: %s \n", args.at(0).toStdString().c_str()); + return -1; + } + + const QString currentSrcDir(a.arguments().at(1)); + + const QString appsJsonFilePath(QStringLiteral("%1/%2").arg(currentSrcDir, a.arguments().at(2))); + QFile f(appsJsonFilePath); + + if (!f.open(QFile::ReadOnly)) + { + printf("LtsAppDescriptorChecker: cannot open apps.json at %s: %s\n", appsJsonFilePath.toStdString().c_str(), f.errorString().toStdString().c_str()); + return -2; + } + + printf("LtsAppDescriptorChecker: checking %s\n", appsJsonFilePath.toStdString().c_str()); + + QStringList lastErrors; + QStringList dummy1; + QString dummy2; + LtsAppDescriptor::AppDescriptionVector adv = LtsAppDescriptor::LtsAppDescriptor::getAppDescriptionVector(f.readAll(), lastErrors, dummy1, dummy2); + + if (!lastErrors.isEmpty()) + { + printf("LtsAppDescriptorChecker: apps.json at %s invalid:\n%s\n", appsJsonFilePath.toStdString().c_str(), lastErrors.join(QStringLiteral("\r\n\r\n")).toStdString().c_str()); + return 1; + } + + + + QFileInfoList guiPluginFiles; + QDirIterator it(QStringLiteral("%1/../../Plugins/").arg(currentSrcDir), QDirIterator::Subdirectories); + + while (it.hasNext()) + { + QString filename(it.next()); + QFileInfo file(filename); + + if (file.isDir()) + continue; + + if (file.fileName().endsWith(QStringLiteral("_gui_plugin.h"), Qt::CaseInsensitive)) + guiPluginFiles.append(file); + } + + QStringList filenames; + filenames.reserve(guiPluginFiles.size()); + for (const QFileInfo &fi : qAsConst(guiPluginFiles)) + { + filenames += fi.absoluteFilePath(); + } + + + QStringList guiPluginIIDs; + for (const QFileInfo &fi : qAsConst(guiPluginFiles)) + { + QFile f2(fi.absoluteFilePath()); + + if (!f2.open(QFile::ReadOnly)) + continue; + + QTextStream ts(&f2); + while (!ts.atEnd()) + { + QString line(ts.readLine().trimmed().simplified()); + static QString iidIdentifier(QStringLiteral("Q_PLUGIN_METADATA")); + + if (line.contains(iidIdentifier)) + { + QRegularExpression re(QStringLiteral("(?<=gui:3:).*(?=\"\\))")); + QRegularExpressionMatch match = re.match(line); + if (match.hasMatch()) + { + QString matched = match.captured(0); + + guiPluginIIDs += matched; + } + break; + } + } + } + + + + bool ok = true; + + for (const LtsAppDescriptor::AppDescription &appdesc : qAsConst(adv)) + { + for (const LtsAppDescriptor::PluginInfo &pi : qAsConst(appdesc.pluginInfos)) + { + if (!guiPluginIIDs.contains(pi.iid)) + { + printf("LtsAppDescriptorChecker: apps.json at '%s' uses unknown IID '%s'\n", appsJsonFilePath.toStdString().c_str(), pi.iid.toStdString().c_str()); + ok = false; + } + } + } + + + + return !ok; +} + + + + + + + + + + + + + + + diff --git a/Lts/Common/LtsAppDescriptor/lts_app_descriptor.cpp b/Lts/Common/LtsAppDescriptor/lts_app_descriptor.cpp new file mode 100755 index 000000000..047d0aab1 --- /dev/null +++ b/Lts/Common/LtsAppDescriptor/lts_app_descriptor.cpp @@ -0,0 +1,215 @@ + + + +#include "lts_app_descriptor.h" + +#include +#include +#include +#include +#include +#include + + + + +namespace LtsAppDescriptor +{ +static void addOptionToPlugin(PluginInfo &pluginInfo, const PluginInfo::Flag &flag) +{ + pluginInfo.flags |= flag; +} + + +static void addOptionToPlugin(QVector &pluginInfos, const PluginInfo::Flag &flag, const QString &pluginName) +{ + for (PluginInfo &pi : pluginInfos) + { + if (pi.iid == pluginName) + { + addOptionToPlugin(pi, flag); + return; + } + } + + PluginInfo pi; + pi.iid = pluginName; + pi.type = PluginInfo::PluginType::GUI; + addOptionToPlugin(pi, flag); + pluginInfos.append(pi); +} + + +static bool addOptionsToPlugin(AppDescription &appDesc, const QString &optionIdentifier, const QStringList &ignoredPlugins, + const QJsonObject &optionsObject, const PluginInfo::Flag flag, QStringList &lastErrors, + bool atLeastOnePluginNeeded) +{ + bool hasPlugins = false; + auto optionsObjectIt = optionsObject.constFind(optionIdentifier); + if (optionsObjectIt != optionsObject.constEnd()) + { + const QJsonArray plugins((*optionsObjectIt).toObject()[QStringLiteral("Plugins")].toArray()); + + for (const auto &pluginElem : plugins) + { + const QString pluginName = pluginElem.toString(); + if (!ignoredPlugins.contains(pluginName)) + { + addOptionToPlugin(appDesc.pluginInfos, flag, pluginName); + hasPlugins = true; + } + } + } + + if (atLeastOnePluginNeeded && !hasPlugins) + { + lastErrors.append(QCoreApplication::tr("No plugins specified for '%1' option for app '%2'").arg(optionIdentifier, appDesc.appName)); + return false; + } + + return true; +} + + +App LtsAppDescriptor::appFromInteger(int i) +{ + switch (i) + { + case int(App::Login): + case int(App::Situation): + case int(App::ResourceManager): + case int(App::PlanEditor): + case int(App::BuildingViewer): + case int(App::OC): + case int(App::OrgChart): + case int(App::Scripts): + + case int(App::Snapshot): + case int(App::SystemMonitor): + case int(App::Timeline): + case int(App::SymbolEditor): + case int(App::ScriptEditorEssentialsView): + case int(App::BaseDownloader): + case int(App::OnlineHelp): + case int(App::ThpSituationReplay): + case int(App::SituationTablet): + case int(App::SilhouetteEditor): + case int(App::Shootback): + case int(App::SAAs): + return static_cast(i); + } + Q_ASSERT_X(false, "LtsAppDescriptor::appFromInteger", qPrintable(QStringLiteral("Unknown app id: %1").arg(i))); + return App::Login; +} + +AppDescriptionVector LtsAppDescriptor::getAppDescriptionVector(const QByteArray &jsonDocument, + QStringList &lastErrors, + const QStringList &ignoredPlugins, + QString &loginPluginIid) +{ + + + AppDescriptionVector appDescriptions; + + QJsonParseError parseError; + QJsonDocument appsDoc(QJsonDocument::fromJson(jsonDocument, &parseError)); + + if (parseError.error != QJsonParseError::NoError) + { + + + int row = 0; + int column = 0; + QChar x = QLatin1Char('\n'); + + for (int i = 0; i < jsonDocument.count(); i++) + { + if (i == parseError.offset) + break; + + if (QChar::fromLatin1(jsonDocument[i]) == x) + { + row++; + column = 0; + } + else + { + column++; + } + } + + + lastErrors.append(QCoreApplication::tr("JSON: %1
Row: %2, Column: %3").arg(parseError.errorString()).arg(row+1).arg(column+1)); + return appDescriptions; + } + + const QJsonObject root(appsDoc.object()); + + loginPluginIid = root[QStringLiteral("LoginPlugin")].toString(); + + const QJsonArray appArray(root[QStringLiteral("Apps")].toArray()); + if (appArray.isEmpty()) + { + lastErrors.append(QCoreApplication::tr("Invalid json: Could not find 'Apps' array")); + return appDescriptions; + } + + + for (const auto &appElem : appArray) + { + AppDescription appDesc; + const QJsonObject appObject = appElem.toObject(); + + + + appDesc.id = appFromInteger(appObject.value(QStringLiteral("Id")).toInt(-1)); + if (appDesc.id == App::Login) + { + lastErrors.append(QCoreApplication::tr("App '%1': no App Id or invalid App Id given").arg(appDesc.appName)); + return appDescriptions; + } + + appDesc.appName = appObject.value(QStringLiteral("AppName")).toString(); + + const QJsonValue valueActive = appObject.value(QStringLiteral("Active")); + if (!valueActive.isUndefined()) + { + if (valueActive.type() != QJsonValue::Type::Bool) + { + lastErrors.append(QCoreApplication::tr("App '%1': value App.Active is not a bool (%2)").arg(appDesc.appName).arg((int)valueActive.type())); + return appDescriptions; + } + } + appDesc.active = valueActive.toBool(false); + + const QJsonValue valueVisible = appObject.value(QStringLiteral("Visible")); + if (!valueVisible.isUndefined() && valueVisible.type() != QJsonValue::Type::Bool) + { + lastErrors.append(QCoreApplication::tr("App '%1': value App.Visible is not a bool (%2)").arg(appDesc.appName).arg((int)valueVisible.type())); + return appDescriptions; + } + appDesc.visible = valueVisible.toBool(true); + + const QJsonArray array(appObject.value(QStringLiteral("ACLs")).toArray()); + for (const QJsonValue &value : array) + appDesc.requiredAcls.append(value.toInt()); + + + + const QJsonObject optionsObject(appObject.value(QStringLiteral("Options")).toObject()); + + addOptionsToPlugin(appDesc, QStringLiteral("Payloads"), ignoredPlugins, optionsObject, PluginInfo::Flag::PAYLOAD, lastErrors, false); + addOptionsToPlugin(appDesc, QStringLiteral("Actions"), ignoredPlugins, optionsObject, PluginInfo::Flag::ACTIONS, lastErrors, true ); + addOptionsToPlugin(appDesc, QStringLiteral("Main"), ignoredPlugins, optionsObject, PluginInfo::Flag::MAIN, lastErrors, false); + + + appDescriptions.append(appDesc); + + } + + + + return appDescriptions; +} + +} diff --git a/Lts/Common/LtsAppDescriptor/lts_app_descriptor.h b/Lts/Common/LtsAppDescriptor/lts_app_descriptor.h new file mode 100755 index 000000000..34d217f12 --- /dev/null +++ b/Lts/Common/LtsAppDescriptor/lts_app_descriptor.h @@ -0,0 +1,96 @@ + + + +#pragma once + +#include +#include + +#ifndef LTSAPPDESCRIPTOR_EXPORT +# ifdef BUILD_STATIC_LIBRARIES +# define LTSAPPDESCRIPTOR_EXPORT +# else +# if defined(MAKE_LTSAPPDESCRIPTOR_LIB) +# define LTSAPPDESCRIPTOR_EXPORT Q_DECL_EXPORT +# else +# define LTSAPPDESCRIPTOR_EXPORT Q_DECL_IMPORT +# endif +# endif +#endif + + +namespace LtsAppDescriptor +{ + +enum class App +{ + Login = -1, + Situation = 0, + ResourceManager = 1, + PlanEditor = 2, + BuildingViewer = 3, + OC = 4, + OrgChart = 5, + Scripts = 6, + + Snapshot = 8, + SystemMonitor = 9, + Timeline = 10, + SymbolEditor = 11, + ScriptEditorEssentialsView = 12, + BaseDownloader = 13, + OnlineHelp = 14, + ThpSituationReplay = 15, + SituationTablet = 17, + SilhouetteEditor = 18, + Shootback = 19, + SAAs = 20, +}; + +struct PluginInfo +{ + enum class PluginType + { + UNKNOWN, + DATA, + GUI, + }; + + enum class Flag + { + UNKNOWN = 1 << 0, + ACTIONS = 1 << 1, + MAIN = 1 << 2, + PAYLOAD = 1 << 3, + OPTION = 1 << 4, + }; + + PluginType type = PluginType::UNKNOWN; + QString iid; + QFlags flags; + QStringList options; +}; + +struct AppDescription +{ + App id = App::Login; + bool active = false; + bool visible = true; + QString appName; + QVector requiredAcls; + QVector pluginInfos; + + bool isValid() const { return !appName.isEmpty(); } +}; + +typedef QVector AppDescriptionVector; + + +namespace LtsAppDescriptor +{ +LTSAPPDESCRIPTOR_EXPORT App appFromInteger(int i); +LTSAPPDESCRIPTOR_EXPORT AppDescriptionVector getAppDescriptionVector(const QByteArray &jsonDocument, QStringList &lastErrors, + const QStringList &ignoredPlugins, QString &loginPluginIid); +} + +} diff --git a/Lts/Common/LtsAppDescriptor/lts_app_descriptor_editor.cpp b/Lts/Common/LtsAppDescriptor/lts_app_descriptor_editor.cpp new file mode 100755 index 000000000..d90fe9c04 --- /dev/null +++ b/Lts/Common/LtsAppDescriptor/lts_app_descriptor_editor.cpp @@ -0,0 +1,554 @@ +#include "lts_app_descriptor_editor.h" +#include "ui_lts_app_descriptor_editor.h" + +#include +#include +#include +#include +#include +#include + +#include + +LtsAppDescriptorEditor::LtsAppDescriptorEditor(QWidget *parent) + : QWidget(parent) + , m_ui(new Ui_LtsAppDescriptionEditor) +{ + m_ui->setupUi(this); + m_vector = LtsAppDescriptor::AppDescriptionVector(); + m_appIndex = QModelIndex(); + m_pluginIndex = QModelIndex(); + m_fileName = QString(); + m_changed = false; + + connect(m_ui->lvApps, SIGNAL(clicked(QModelIndex)), this, SLOT(onAppsClicked(QModelIndex))); + connect(m_ui->lvPlugins, SIGNAL(clicked(QModelIndex)), this, SLOT(onPluginsClicked(QModelIndex))); + connect(m_ui->lePluginType, SIGNAL(editingFinished()), this, SLOT(pluginTypeChanged())); + connect(m_ui->pbAddApp, SIGNAL(pressed()), this, SLOT(onAppAddClicked())); + connect(m_ui->pbAddPlugin, SIGNAL(pressed()), this, SLOT(onPluginAddClicked())); + connect(m_ui->pbDeleteApp, SIGNAL(pressed()), this, SLOT(onAppDeleteClicked())); + connect(m_ui->pbDeletePlugin, SIGNAL(pressed()), this, SLOT(onPluginDeletedClicked())); + connect(m_ui->pbUpdate, SIGNAL(pressed()), this, SLOT(onUpdateClicked())); + connect(m_ui->pbNewPlugin, SIGNAL(pressed()), this, SLOT(onNewpluginClicked())); + connect(m_ui->pbNewApp, SIGNAL(pressed()), this, SLOT(onNewAppClicked())); + connect(m_ui->lePluginName, SIGNAL(textChanged(QString)), this, SLOT(enableNewPlugin())); + connect(m_ui->lePluginType, SIGNAL(textChanged(QString)), this, SLOT(enableNewPlugin())); + + m_ui->gbPlugins->setEnabled(false); + m_ui->pbDeletePlugin->setEnabled(false); + m_ui->pbDeleteApp->setEnabled(false); + m_ui->pbAddPlugin->setEnabled(false); + m_ui->pbUpdate->setEnabled(false); + m_ui->pbNewPlugin->setEnabled(false); + m_ui->pbNewApp->setEnabled(false); + QTimer::singleShot(0, this, &LtsAppDescriptorEditor::getAppDescriptionVector); +} + +LtsAppDescriptorEditor::~LtsAppDescriptorEditor() +{ + checkSave(); + delete m_ui; +} + + +void LtsAppDescriptorEditor::getAppDescriptionVector() +{ + m_fileName = QFileDialog::getOpenFileName(this, tr("Open JSON"), QLatin1Literal("/shd/CTC/TOOLS/TDS/TDS-source/Lts/Projects/Legatus/resources/"), + tr("JSON File (*.json)")); + if (m_fileName.isEmpty()) + return; + + QFile file; + file.setFileName(m_fileName); + file.open(QIODevice::ReadOnly | QIODevice::Text); + QString val(QString::fromUtf8(file.readAll())); + file.close(); + + QStringList errors; + m_vector = LtsAppDescriptor::LtsAppDescriptor::getAppDescriptionVector(val.toUtf8(), errors); + + updateAppModel(); +} + +void LtsAppDescriptorEditor::onAppsClicked(const QModelIndex &index) +{ + m_ui->gbPlugins->setEnabled(true); + m_ui->pbDeleteApp->setEnabled(true); + m_ui->pbAddPlugin->setEnabled(true); + m_ui->pbNewApp->setEnabled(true); + m_appIndex = index; + + QStringList pluginList; + Q_FOREACH(const LtsAppDescriptor::PluginInfo &reqPlugin, m_vector.at(m_appIndex.row()).requiredPlugins) + { + LtsAppDescriptor::PluginInfo::PluginType pluginType = reqPlugin.type; + QString type; + if (pluginType == LtsAppDescriptor::PluginInfo::PluginType::DATA) + type = QLatin1String("data"); + else if(pluginType == LtsAppDescriptor::PluginInfo::PluginType::GUI) + type = QLatin1String("gui"); + else + type = QLatin1String("unknown"); + + if(type == QLatin1String("data")) + pluginList << QString(type + QLatin1String(": ") +reqPlugin.iid); + else + { + QString strFlags; + QFlags flags = reqPlugin.flags; + + bool actionsFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::ACTIONS); + if(actionsFlag) + strFlags.append(QLatin1String("actions ")); + bool mainFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::MAIN); + if(mainFlag) + strFlags.append(QLatin1String("main ")); + bool payloadFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::PAYLOAD); + if(payloadFlag) + strFlags.append(QLatin1String("payload ")); + bool optionalFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::OPTION); + if(optionalFlag) + strFlags.append(QLatin1String("optional ")); + + pluginList << QString(type + QLatin1String(": ") + reqPlugin.iid + QLatin1String(" [ ") + strFlags + QLatin1String("]")); + } + + } + updatePluginModel(pluginList); + + m_ui->chbAppActive->setChecked(m_vector.at(m_appIndex.row()).active); + m_ui->leAppName->setText(m_vector.at(m_appIndex.row()).appName); +} + +void LtsAppDescriptorEditor::onPluginsClicked(const QModelIndex &index) +{ + m_pluginIndex = index; + m_ui->pbDeletePlugin->setEnabled(true); + m_ui->pbUpdate->setEnabled(true); + m_ui->pbNewPlugin->setEnabled(true); + + const LtsAppDescriptor::PluginInfo plugin = m_vector.at(m_appIndex.row()).requiredPlugins.at(m_pluginIndex.row()); + QString pluginName = plugin.iid; + m_ui->lePluginName->setText(pluginName); + + LtsAppDescriptor::PluginInfo::PluginType pluginType = plugin.type; + QString type; + if (pluginType == LtsAppDescriptor::PluginInfo::PluginType::DATA) + type = QLatin1String("data"); + else if(pluginType == LtsAppDescriptor::PluginInfo::PluginType::GUI) + type = QLatin1String("gui"); + else + type = QLatin1String("unknown"); + m_ui->lePluginType->setText(type); + + if(type == QLatin1String("gui")) + { + m_ui->chbActions->setEnabled(true); + m_ui->chbMain->setEnabled(true); + m_ui->chbPayload->setEnabled(true); + + QFlags flags = plugin.flags; + bool actionsFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::ACTIONS); + m_ui->chbActions->setChecked(actionsFlag); + bool mainFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::MAIN); + m_ui->chbMain->setChecked(mainFlag); + bool payloadFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::PAYLOAD); + m_ui->chbPayload->setChecked(payloadFlag); + } + else + { + m_ui->chbActions->setChecked(false); + m_ui->chbActions->setEnabled(false); + m_ui->chbMain->setChecked(false); + m_ui->chbMain->setEnabled(false); + m_ui->chbPayload->setChecked(false); + m_ui->chbPayload->setEnabled(false); + } +} + +void LtsAppDescriptorEditor::pluginTypeChanged() +{ + if(m_ui->lePluginType->text().isEmpty()) + return; + + if(m_ui->lePluginType->text() == QLatin1String("gui")) + { + m_ui->chbActions->setEnabled(true); + m_ui->chbMain->setEnabled(true); + m_ui->chbPayload->setEnabled(true); + } + else if(m_ui->lePluginType->text() == QLatin1String("data")) + { + m_ui->chbActions->setChecked(false); + m_ui->chbActions->setEnabled(false); + m_ui->chbMain->setChecked(false); + m_ui->chbMain->setEnabled(false); + m_ui->chbPayload->setChecked(false); + m_ui->chbPayload->setEnabled(false); + } + else + { + QMessageBox msgBox(QMessageBox::Information, + tr("Error"), + tr("Plugin Type must either be \"gui\" or \"data\".")); + msgBox.exec(); + m_ui->lePluginType->setText(QString()); + } +} + +void LtsAppDescriptorEditor::onAppAddClicked() +{ + m_changed = true; + + if(m_ui->leAppName->text().isEmpty()) + { + QMessageBox msgBox(QMessageBox::Critical, + tr("Error"), + tr("The App needs to have a name.")); + msgBox.exec(); + return; + } + + QStringList appList; + Q_FOREACH(const LtsAppDescriptor::AppDescription &appDesc, m_vector) + { + appList << appDesc.appName; + } + + if(appList.contains(m_ui->leAppName->text(),Qt::CaseInsensitive)) + { + QMessageBox msgBox(QMessageBox::Critical, + tr("Error"), + tr("There already exists an app named \"%1\".").arg(m_ui->leAppName->text())); + msgBox.exec(); + return; + } + + LtsAppDescriptor::AppDescription newApp; + newApp.appName = m_ui->leAppName->text(); + newApp.active = m_ui->chbAppActive->isChecked(); + m_vector.append(newApp); + + updateAppModel(); + +} + +void LtsAppDescriptorEditor::onPluginAddClicked() +{ + m_changed = true; + + if(m_ui->lePluginType->text().isEmpty()) + { + QMessageBox msgBox(QMessageBox::Critical, + tr("Error"), + tr("The Plugin needs to contain a type.")); + msgBox.exec(); + return; + } + if(m_ui->lePluginName->text().isEmpty()) + { + QMessageBox msgBox(QMessageBox::Critical, + tr("Error"), + tr("The Plugin needs to have a name.")); + msgBox.exec(); + return; + } + + QStringList pluginList; + Q_FOREACH(const LtsAppDescriptor::PluginInfo &pluginDesc, m_vector.at(m_appIndex.row()).requiredPlugins) + { + pluginList << pluginDesc.iid; + } + + if(pluginList.contains(m_ui->lePluginName->text(),Qt::CaseInsensitive)) + { + QMessageBox msgBox(QMessageBox::Critical, + tr("Error"), + tr("There already exists an plugin named \"%1\".").arg(m_ui->lePluginName->text())); + msgBox.exec(); + return; + } + + + LtsAppDescriptor::PluginInfo newPlugin; + + newPlugin.iid = m_ui->lePluginName->text(); + + if(m_ui->lePluginType->text() == QLatin1String("gui")) + newPlugin.type = LtsAppDescriptor::PluginInfo::PluginType::GUI; + else if(m_ui->lePluginType->text() == QLatin1String("data")) + newPlugin.type = LtsAppDescriptor::PluginInfo::PluginType::DATA; + else + newPlugin.type = LtsAppDescriptor::PluginInfo::PluginType::UNKNOWN; + + if(m_ui->chbActions->isChecked()) + newPlugin.flags |= LtsAppDescriptor::PluginInfo::Flag::ACTIONS; + if(m_ui->chbMain->isChecked()) + newPlugin.flags |= LtsAppDescriptor::PluginInfo::Flag::MAIN; + if(m_ui->chbPayload->isChecked()) + newPlugin.flags |= LtsAppDescriptor::PluginInfo::Flag::PAYLOAD; + + m_vector[m_appIndex.row()].requiredPlugins.append(newPlugin); + + onAppsClicked(m_appIndex); + onNewpluginClicked(); + +} + +void LtsAppDescriptorEditor::onAppDeleteClicked() +{ + m_changed = true; + + if(!m_appIndex.isValid()) return; + + QMessageBox msgBox(tr("Warning"), + tr("The App \n\"%1\" \nwill be removed. Continue?").arg(m_appIndex.data().toString()), + QMessageBox::Warning, + QMessageBox::Yes | QMessageBox::Default, + QMessageBox::No | QMessageBox::Escape, + QMessageBox::NoButton); + + if (msgBox.exec() == QMessageBox::No) + return; + + m_vector.remove(m_appIndex.row()); + + updateAppModel(); + +} + +void LtsAppDescriptorEditor::onPluginDeletedClicked() +{ + m_changed = true; + + if(!m_pluginIndex.isValid()) return; + + QMessageBox msgBox(tr("Warning"), + tr("The Plugin \n\"%1\" \nwill be removed. Continue?").arg(m_pluginIndex.data().toString()), + QMessageBox::Warning, + QMessageBox::Yes | QMessageBox::Default, + QMessageBox::No | QMessageBox::Escape, + QMessageBox::NoButton); + + if (msgBox.exec() == QMessageBox::No) + return; + + m_vector[m_appIndex.row()].requiredPlugins.remove(m_pluginIndex.row()); + m_pluginIndex = QModelIndex(); + m_ui->lePluginName->setText(QString()); + m_ui->lePluginType->setText(QString()); + m_ui->chbActions->setChecked(false); + m_ui->chbMain->setChecked(false); + m_ui->chbPayload->setChecked(false); + + m_ui->pbDeletePlugin->setEnabled(false); + m_ui->pbUpdate->setEnabled(false); + m_ui->pbNewPlugin->setEnabled(false); + + onAppsClicked(m_appIndex); +} + +void LtsAppDescriptorEditor::onUpdateClicked() +{ + m_changed = true; + + if(m_ui->lePluginType->text().isEmpty()) + { + QMessageBox msgBox(QMessageBox::Critical, + tr("Error"), + tr("The Plugin needs to contain a type.")); + msgBox.exec(); + return; + } + if(m_ui->lePluginName->text().isEmpty()) + { + QMessageBox msgBox(QMessageBox::Critical, + tr("Error"), + tr("The Plugin needs to have a name.")); + msgBox.exec(); + return; + } + + m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].iid = m_ui->lePluginName->text(); + + if(m_ui->lePluginType->text() == QLatin1String("gui")) + m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].type = LtsAppDescriptor::PluginInfo::PluginType::GUI; + else if(m_ui->lePluginType->text() == QLatin1String("data")) + m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].type = LtsAppDescriptor::PluginInfo::PluginType::DATA; + else + m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].type = LtsAppDescriptor::PluginInfo::PluginType::UNKNOWN; + + m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].flags = QFlags(); + + if(m_ui->chbActions->isChecked()) + m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].flags |= LtsAppDescriptor::PluginInfo::Flag::ACTIONS; + if(m_ui->chbMain->isChecked()) + m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].flags |= LtsAppDescriptor::PluginInfo::Flag::MAIN; + if(m_ui->chbPayload->isChecked()) + m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].flags |= LtsAppDescriptor::PluginInfo::Flag::PAYLOAD; + + onAppsClicked(m_appIndex); + onNewpluginClicked(); +} + +void LtsAppDescriptorEditor::onNewpluginClicked() +{ + m_ui->lePluginName->setText(QString()); + m_ui->lePluginType->setText(QString()); + m_ui->chbActions->setEnabled(true); + m_ui->chbMain->setEnabled(true); + m_ui->chbPayload->setEnabled(true); + m_ui->chbActions->setChecked(false); + m_ui->chbMain->setChecked(false); + m_ui->chbPayload->setChecked(false); + m_ui->pbUpdate->setEnabled(false); + m_ui->pbNewPlugin->setEnabled(false); + m_ui->pbDeletePlugin->setEnabled(false); + + if(m_pluginIndex.isValid()) + m_ui->lvPlugins->selectionModel()->select(m_pluginIndex,QItemSelectionModel::Deselect); + m_pluginIndex = QModelIndex(); +} + +void LtsAppDescriptorEditor::onNewAppClicked() +{ + updatePluginModel(QStringList()); + + m_ui->gbPlugins->setEnabled(false); + m_ui->pbDeletePlugin->setEnabled(false); + m_ui->pbDeleteApp->setEnabled(false); + m_ui->pbAddPlugin->setEnabled(false); + m_ui->pbUpdate->setEnabled(false); + m_ui->pbNewPlugin->setEnabled(false); + m_ui->leAppName->setText(QString()); + m_ui->chbAppActive->setChecked(false); + m_ui->lePluginName->setText(QString()); + m_ui->lePluginType->setText(QString()); + m_ui->chbActions->setChecked(false); + m_ui->chbMain->setChecked(false); + m_ui->chbPayload->setChecked(false); + m_ui->pbNewApp->setEnabled(false); + + if(m_appIndex.isValid()) + m_ui->lvApps->selectionModel()->select(m_appIndex,QItemSelectionModel::Deselect); + m_appIndex = QModelIndex(); +} + +void LtsAppDescriptorEditor::updateAppModel() +{ + onNewAppClicked(); + QStringList appList; + Q_FOREACH(const LtsAppDescriptor::AppDescription &appDesc, m_vector) + { + appList << appDesc.appName; + } + + if (!m_ui->lvApps->model()) + m_ui->lvApps->setModel(new QStringListModel(this)); + QStringListModel *m = qobject_cast(m_ui->lvApps->model()); + if (!m) + return; + m->setStringList(appList); +} + +void LtsAppDescriptorEditor::updatePluginModel(const QStringList &list) +{ + if (!m_ui->lvPlugins->model()) + m_ui->lvPlugins->setModel(new QStringListModel(this)); + QStringListModel *m = qobject_cast(m_ui->lvPlugins->model()); + if (!m) + return; + m->setStringList(list); +} + +void LtsAppDescriptorEditor::checkSave() +{ + if(m_changed) + { + QMessageBox msgBox(tr("Warning"), + tr("The Data has been modified.\nDo you want to save your changes?"), + QMessageBox::Warning, + QMessageBox::Yes | QMessageBox::Default, + QMessageBox::No | QMessageBox::Escape, + QMessageBox::NoButton); + + if (msgBox.exec() == QMessageBox::Yes) + onSaveAsClicked(); + } +} + +void LtsAppDescriptorEditor::onSaveAsClicked() +{ + QString fileName = QFileDialog::getSaveFileName(this, tr("Save JSON"), QLatin1Literal("/shd/CTC/TOOLS/TDS/TDS-source/Lts/Projects/Legatus/resources/"), + tr("JSON File (*.json)")); + if(fileName.isEmpty()) + return; + + QByteArray jsonByteArray; + jsonByteArray = LtsAppDescriptor::LtsAppDescriptor::createJson(m_vector); + + QFile f(fileName); + f.open(QIODevice::WriteOnly); + f.write(jsonByteArray); + f.close(); + + m_changed = false; + + QMessageBox msgBox(QMessageBox::Information, + tr("Success"), + tr("Your file has been saved.")); + msgBox.exec(); +} + +void LtsAppDescriptorEditor::onSaveClicked() +{ + const QByteArray jsonByteArray(LtsAppDescriptor::LtsAppDescriptor::createJson(m_vector)); + + QFile f(m_fileName); + f.open(QIODevice::WriteOnly); + f.write(jsonByteArray); + f.close(); + + m_changed = false; + + QMessageBox msgBox(QMessageBox::Information, + tr("Success"), + tr("Your file has been saved.")); + msgBox.exec(); +} + +void LtsAppDescriptorEditor::onOpenClicked() +{ + checkSave(); + getAppDescriptionVector(); + m_changed = false; +} + +void LtsAppDescriptorEditor::onNewClicked() +{ + checkSave(); + m_ui->chbActions->setChecked(false); + m_ui->chbMain->setChecked(false); + m_ui->chbPayload->setChecked(false); + m_ui->chbAppActive->setChecked(false); + m_ui->leAppName->setText(QString()); + m_ui->lePluginName->setText(QString()); + m_ui->lePluginType->setText(QString()); + m_ui->gbPlugins->setEnabled(false); + + m_vector = LtsAppDescriptor::AppDescriptionVector(); + + updateAppModel(); + updatePluginModel(QStringList()); + +} + +void LtsAppDescriptorEditor::enableNewPlugin() +{ + m_ui->pbNewPlugin->setEnabled(true); +} + +void LtsAppDescriptorEditor::onQuitClicked() +{ + QApplication::quit(); +} diff --git a/Lts/Common/LtsAppDescriptor/lts_app_descriptor_editor.h b/Lts/Common/LtsAppDescriptor/lts_app_descriptor_editor.h new file mode 100755 index 000000000..97be5c4ff --- /dev/null +++ b/Lts/Common/LtsAppDescriptor/lts_app_descriptor_editor.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include + +#include "lts_app_descriptor.h" + +#ifndef LTSAPPDESCRIPTOREDITOR_EXPORT +# ifdef BUILD_STATIC_LIBRARIES +# define LTSAPPDESCRIPTOREDITOR_EXPORT +# else +# if defined(MAKE_LTSAPPDESCRIPTOREDITOR_LIB) +# define LTSAPPDESCRIPTOREDITOR_EXPORT Q_DECL_EXPORT +# else +# define LTSAPPDESCRIPTOREDITOR_EXPORT Q_DECL_IMPORT +# endif +# endif +#endif + +class Ui_LtsAppDescriptionEditor; + + +class LTSAPPDESCRIPTOREDITOR_EXPORT LtsAppDescriptorEditor : public QWidget +{ + Q_OBJECT + +public: + explicit LtsAppDescriptorEditor(QWidget *parent = 0); + ~LtsAppDescriptorEditor(); + +private: + Ui_LtsAppDescriptionEditor *m_ui; + LtsAppDescriptor::AppDescriptionVector m_vector; + QModelIndex m_appIndex; + QModelIndex m_pluginIndex; + + QString m_fileName; + + bool m_changed; + + void getAppDescriptionVector(); + void setupMainWindow(); + void updateAppModel(); + void updatePluginModel(const QStringList &list); + void checkSave(); + +private slots: + void onAppsClicked(const QModelIndex &index); + void onPluginsClicked(const QModelIndex &index); + void pluginTypeChanged(); + void onAppAddClicked(); + void onPluginAddClicked(); + void onAppDeleteClicked(); + void onPluginDeletedClicked(); + void onUpdateClicked(); + void onNewpluginClicked(); + void onNewAppClicked(); + void enableNewPlugin(); + +public slots: + void onSaveClicked(); + void onSaveAsClicked(); + void onOpenClicked(); + void onNewClicked(); + void onQuitClicked(); +}; diff --git a/Lts/Common/LtsAppDescriptor/lts_app_descriptor_editor.ui b/Lts/Common/LtsAppDescriptor/lts_app_descriptor_editor.ui new file mode 100755 index 000000000..a791a3218 --- /dev/null +++ b/Lts/Common/LtsAppDescriptor/lts_app_descriptor_editor.ui @@ -0,0 +1,434 @@ + + + LtsAppDescriptionEditor + + + + 0 + 0 + 865 + 337 + + + + + 0 + 0 + + + + + + + true + + + + + + Required Plugins: + + + + + + + App: + + + + + + + + 0 + 0 + + + + + 150 + 0 + + + + QAbstractItemView::NoEditTriggers + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + New + + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + Name: + + + Qt::AlignCenter + + + + + + + Delete + + + + + + + + 0 + 0 + + + + New Plugin Requirements + + + + + + Type: + + + + + + + + 0 + 0 + + + + + 210 + 0 + + + + + 200 + 16777215 + + + + gui/data + + + + + + + IID: + + + + + + + + 0 + 0 + + + + + 210 + 0 + + + + Name of the Plugin + + + + + + + Flags: + + + + + + + + 0 + 25 + + + + Actions + + + + + + + + 0 + 25 + + + + Main + + + + + + + + 0 + 25 + + + + Payload + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 0 + + + + + + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + Active: + + + Qt::AlignCenter + + + + + + + Delete + + + + + + + + 0 + 0 + + + + + 300 + 0 + + + + QAbstractItemView::NoEditTriggers + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Update + + + + + + + + 0 + 0 + + + + + 100 + 30 + + + + + 100 + 30 + + + + Name of the App + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Add + + + + + + + New + + + + + + + + 0 + 0 + + + + + 80 + 0 + + + + + + + + + + + Add + + + + + + + + 0 + 0 + + + + List of Apps + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + lvApps + pbNewApp + pbAddApp + pbDeleteApp + chbAppActive + leAppName + lvPlugins + pbDeletePlugin + pbNewPlugin + lePluginType + lePluginName + chbPayload + chbMain + chbActions + pbAddPlugin + pbUpdate + + + + diff --git a/Lts/Common/LtsAppDescriptor/main.cpp b/Lts/Common/LtsAppDescriptor/main.cpp new file mode 100755 index 000000000..1904bac5c --- /dev/null +++ b/Lts/Common/LtsAppDescriptor/main.cpp @@ -0,0 +1,56 @@ + + + +#include "lts_app_descriptor_editor.h" +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + QMainWindow *mainWindow = new QMainWindow(); + QMenu *fileMenu = mainWindow->menuBar()->addMenu(QObject::tr("&File")); + + QAction *openAct = new QAction(QObject::tr("&Open"), mainWindow); + openAct->setShortcuts(QKeySequence::Open); + openAct->setStatusTip(QObject::tr("Open an existing file")); + fileMenu->addAction(openAct); + + QAction *newAct = new QAction(QObject::tr("&New"), mainWindow); + newAct->setShortcuts(QKeySequence::New); + newAct->setStatusTip(QObject::tr("Create a new App")); + fileMenu->addAction(newAct); + + QAction *saveAct = new QAction(QObject::tr("&Save"), mainWindow); + saveAct->setShortcuts(QKeySequence::Save); + saveAct->setStatusTip(QObject::tr("Save modified file")); + fileMenu->addAction(saveAct); + + QAction *saveAsAct = new QAction(QObject::tr("&SaveAs"), mainWindow); + saveAsAct->setShortcuts(QKeySequence::SaveAs); + saveAsAct->setStatusTip(QObject::tr("Save modified file as")); + fileMenu->addAction(saveAsAct); + + QAction *quitAct = new QAction(QObject::tr("&Quit"), mainWindow); + quitAct->setShortcuts(QKeySequence::Quit); + quitAct->setStatusTip(QObject::tr("Quit the Application")); + fileMenu->addAction(quitAct); + + + LtsAppDescriptorEditor *editor = new LtsAppDescriptorEditor(mainWindow); + mainWindow->setCentralWidget(editor); + QObject::connect(openAct, &QAction::triggered, editor, &LtsAppDescriptorEditor::onOpenClicked); + QObject::connect(newAct, &QAction::triggered, editor, &LtsAppDescriptorEditor::onNewClicked); + QObject::connect(saveAct, &QAction::triggered, editor, &LtsAppDescriptorEditor::onSaveClicked); + QObject::connect(saveAsAct, &QAction::triggered, editor, &LtsAppDescriptorEditor::onSaveAsClicked); + QObject::connect(quitAct, &QAction::triggered, editor, &LtsAppDescriptorEditor::onQuitClicked); + + mainWindow->show(); + + int ret = a.exec(); + delete mainWindow; + return ret; +} diff --git a/Lts/Common/LtsAppManager/AppChooser/CustomViews/app.cpp b/Lts/Common/LtsAppManager/AppChooser/CustomViews/app.cpp new file mode 100755 index 000000000..280de0beb --- /dev/null +++ b/Lts/Common/LtsAppManager/AppChooser/CustomViews/app.cpp @@ -0,0 +1,35 @@ + + + +#include "app.h" +#include "ui_app.h" +#include "AppChooser/app_chooser_model.h" + + +namespace LtsAppManager +{ + +CustomViewApp::CustomViewApp(QWidget *parent) + : AbstractDisplayWidget(parent) + , m_ui(new Ui::CustomViewApp) +{ + m_ui->setupUi(this); + m_ui->image->setMenuEnabled(false); +} + +CustomViewApp::~CustomViewApp() +{ + delete m_ui; +} + +bool CustomViewApp::init(const QModelIndex &idx) +{ + if (!idx.isValid()) + return false; + + m_ui->lbAppName->setText(rowData(idx, AppChooserModel::Name).toString()); + + return true; +} + +} diff --git a/Lts/Common/LtsAppManager/AppChooser/CustomViews/app.h b/Lts/Common/LtsAppManager/AppChooser/CustomViews/app.h new file mode 100755 index 000000000..096e7ffef --- /dev/null +++ b/Lts/Common/LtsAppManager/AppChooser/CustomViews/app.h @@ -0,0 +1,30 @@ + + +#pragma once + +#include + + +namespace Ui +{ +class CustomViewApp; +} + +namespace LtsAppManager +{ + +class CustomViewApp final : public LtsDataView::AbstractDisplayWidget +{ + Q_OBJECT + +public: + explicit CustomViewApp(QWidget *parent = nullptr); + ~CustomViewApp(); + bool init(const QModelIndex &idx) override; + +private: + Ui::CustomViewApp* m_ui; + +}; + +} diff --git a/Lts/Common/LtsAppManager/AppChooser/CustomViews/app.ui b/Lts/Common/LtsAppManager/AppChooser/CustomViews/app.ui new file mode 100755 index 000000000..d4653b571 --- /dev/null +++ b/Lts/Common/LtsAppManager/AppChooser/CustomViews/app.ui @@ -0,0 +1,108 @@ + + + CustomViewApp + + + + 0 + 0 + 300 + 150 + + + + + 0 + 0 + + + + + + + + + + + 0 + 0 + + + + + 128 + 128 + + + + + 128 + 128 + + + + + + + + + + + + <App Name> + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + <html><head/><body><p>&lt;Description with wordwrap&gt;</p></body></html> + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + LtsWidgets::LtsImageEditor + QWidget +
LtsWidgets/lts_image_editor.h
+ 1 +
+
+ + +
diff --git a/Lts/Common/LtsAppManager/AppChooser/app_chooser.cpp b/Lts/Common/LtsAppManager/AppChooser/app_chooser.cpp new file mode 100755 index 000000000..aaf658738 --- /dev/null +++ b/Lts/Common/LtsAppManager/AppChooser/app_chooser.cpp @@ -0,0 +1,93 @@ + + +#include "AppChooser/app_chooser.h" +#include "ui_app_chooser.h" +#include "AppChooser/app_chooser_model.h" +#include "lts_app_manager.h" +#include "AppChooser/CustomViews/app.h" + +#include + +#include + + +namespace LtsAppManager +{ + +AppChooser::AppChooser() + : QWidget(nullptr) + , m_ui(new Ui::AppChooser) + , m_model() + , m_madeSelection(false) +{ + m_ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + + connect(m_ui->widget, &LtsDataView::LtsDataView::LtsDataView::signalDoubleClicked, + this, &AppChooser::slotAppSelected); + connect(m_ui->widget, &LtsDataView::LtsDataView::LtsDataView::signalEnterPressed, + this, &AppChooser::slotAppSelected); + connect(m_ui->widget, &LtsDataView::LtsDataView::LtsDataView::signalSelectionChanged, + this, &AppChooser::enaDisa); + connect(m_ui->pbQuit, &QPushButton::clicked, + this, &QWidget::close); + connect(m_ui->pbSelect, &QPushButton::clicked, + this, &AppChooser::slotAppSelected); + + m_model = new AppChooserModel(this); + m_model->initModel(LtsAppManager::getInstance()->getAvailableApps()); + + m_ui->widget->seamless(); + + m_ui->widget->setModel(m_model); + m_ui->widget->hideColumn(AppChooserModel::Columns::Visible); +} + + +AppChooser::~AppChooser() +{ + + delete m_ui; +} + + +void AppChooser::slotQuit() +{ + if (m_alreadyCloseEmitted) + return; + m_alreadyCloseEmitted = true; + emit signalLogout(); +} + + +void AppChooser::closeEvent(QCloseEvent *) +{ + if (!m_madeSelection) + slotQuit(); +} + + +void AppChooser::slotAppSelected() +{ + const QModelIndex idx = m_ui->widget->currentIndex(); + const LtsAppDescriptor::AppDescription app = m_model->getRowData(idx.row()); + + if (!app.active) + return; + + hide(); + m_madeSelection = true; + emit signalAppSelected(app.id); + close(); +} + + +void AppChooser::enaDisa() +{ + const QModelIndexList selectedRows = m_ui->widget->selectedRows(); + const int cntSelectedRows = selectedRows.size(); + + m_ui->pbSelect->setEnabled(cntSelectedRows == 1); +} + +} diff --git a/Lts/Common/LtsAppManager/AppChooser/app_chooser.h b/Lts/Common/LtsAppManager/AppChooser/app_chooser.h new file mode 100755 index 000000000..27fdad5ba --- /dev/null +++ b/Lts/Common/LtsAppManager/AppChooser/app_chooser.h @@ -0,0 +1,47 @@ + +#pragma once + +#include +#include + +namespace Ui +{ +class AppChooser; +} + + +namespace LtsAppManager +{ + +class AppChooserModel; + +class AppChooser : public QWidget +{ + Q_OBJECT + +public: + AppChooser(); + ~AppChooser(); + +private: + Ui::AppChooser *m_ui; + AppChooserModel *m_model; + bool m_madeSelection; + bool m_alreadyCloseEmitted { false }; + + void enaDisa(); + +protected: + void closeEvent(QCloseEvent *); + +signals: + void signalAppSelected(LtsAppDescriptor::App appId); + void signalLogout(); + +private slots: + void slotAppSelected(); + void slotQuit(); + +}; + +} diff --git a/Lts/Common/LtsAppManager/AppChooser/app_chooser.ui b/Lts/Common/LtsAppManager/AppChooser/app_chooser.ui new file mode 100755 index 000000000..841d60178 --- /dev/null +++ b/Lts/Common/LtsAppManager/AppChooser/app_chooser.ui @@ -0,0 +1,92 @@ + + + AppChooser + + + + 0 + 0 + 800 + 400 + + + + + 0 + 0 + + + + App Chooser + + + + :/rde_icon_256.png:/rde_icon_256.png + + + + + + Select App: + + + + + + + + 0 + 0 + + + + + + + + + + Quit + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + Select + + + + + + + + + + LtsDataView::LtsDataView + QWidget +
Viewer/LtsDataView/lts_data_view.h
+ 1 +
+
+ + + + +
diff --git a/Lts/Common/LtsAppManager/AppChooser/app_chooser_model.cpp b/Lts/Common/LtsAppManager/AppChooser/app_chooser_model.cpp new file mode 100755 index 000000000..505c50c0a --- /dev/null +++ b/Lts/Common/LtsAppManager/AppChooser/app_chooser_model.cpp @@ -0,0 +1,50 @@ + + +#include "app_chooser_model.h" + +#include + + +namespace LtsAppManager +{ + +AppChooserModel::AppChooserModel(QObject *parent) + : LtsWidgets::LtsAbstractTableModel({tr("Name")}, parent) + +{} + + +bool AppChooserModel::initModel(const LtsAppDescriptor::AppDescriptionVector &availableApps) +{ + for (auto i = availableApps.constBegin(); i != availableApps.constEnd(); ++i) + { + const LtsAppDescriptor::AppDescription& appDesc = *i; + + if (!appDesc.visible) + continue; + + insertRow(appDesc); + } + + return true; +} + + +QVariant AppChooserModel::data(const QModelIndex &index, int role) const +{ + if (role != Qt::DisplayRole || !indexIsValid(index)) + return QVariant(); + + const LtsAppDescriptor::AppDescription &ad = m_vecData.at(index.row()); + + switch (index.column()) + { + case Name: + return LtsSession::LtsSession::appToString(ad.id); + case Visible: + return ad.visible; + } + + return QVariant(); +} +} diff --git a/Lts/Common/LtsAppManager/AppChooser/app_chooser_model.h b/Lts/Common/LtsAppManager/AppChooser/app_chooser_model.h new file mode 100755 index 000000000..e83b87d46 --- /dev/null +++ b/Lts/Common/LtsAppManager/AppChooser/app_chooser_model.h @@ -0,0 +1,29 @@ + +#pragma once + + +#include +#include + + + +namespace LtsAppManager +{ +class AppChooserModel final : public LtsWidgets::LtsAbstractTableModel +{ + Q_OBJECT + +public: + enum Columns + { + Name = 0, + Visible, + }; + + explicit AppChooserModel(QObject *parent = nullptr); + + bool initModel(const LtsAppDescriptor::AppDescriptionVector &availableApps); + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override final; +}; +} diff --git a/Lts/Common/LtsAppManager/CMakeLists.txt b/Lts/Common/LtsAppManager/CMakeLists.txt new file mode 100755 index 000000000..422572abf --- /dev/null +++ b/Lts/Common/LtsAppManager/CMakeLists.txt @@ -0,0 +1,52 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# Created by Mike Wille, SLSE, 2016-10-27 +# + + +# LtsAppManager + +set(LtsAppManager_SRCS + lts_app_manager.cpp + AppChooser/app_chooser.cpp + AppChooser/app_chooser_model.cpp + AppChooser/CustomViews/app.cpp +) + +NGL_WRAP_UI(LtsAppManager_SRCS + AppChooser/app_chooser.ui + AppChooser/CustomViews/app.ui +) + +NGL_ADD_LIBRARY(WS LtsAppManager SHARED ${LtsAppManager_SRCS}) +NGL_TARGET_INCLUDE_DIRECTORIES(LtsAppManager + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ + ${CMAKE_CURRENT_SOURCE_DIR}/AppChooser/ +) + +NGL_TARGET_LINK_LIBRARIES(LtsAppManager + PRIVATE + NGLLogger + LtsDataView + LtsWidgets + LtsLogin + LtsPluginManager + LtsSession + LtsSessionData + LtsUtilsSettings + LtsMultiMonitor + ProjectConfig + Qt5::Gui + Qt5::Widgets + Qt5::Network # debug server + OpmSettingsGuiPluginInterface + LtsPluginLoader + PUBLIC + Qt5::Core + LtsAppDescriptor +) + +NGL_INSTALL_TARGET(LtsAppManager) diff --git a/Lts/Common/LtsAppManager/lts_app_manager.cpp b/Lts/Common/LtsAppManager/lts_app_manager.cpp new file mode 100755 index 000000000..66b7fcfb3 --- /dev/null +++ b/Lts/Common/LtsAppManager/lts_app_manager.cpp @@ -0,0 +1,1151 @@ +#include "lts_app_manager.h" +#include "AppChooser/app_chooser.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + + +#ifndef LTS_VERSION_MAJOR + #define LTS_VERSION_MAJOR "undefined" +#endif +#ifndef LTS_VERSION_MINOR + #define LTS_VERSION_MINOR "undefined" +#endif +#ifndef LTS_VERSION_PATCH + #define LTS_VERSION_PATCH "undefined" +#endif +#ifndef JENKINS_BUILD_NUMBER + #define JENKINS_BUILD_NUMBER "unknown" +#endif +static const char ver_major[] = LTS_VERSION_MAJOR; +static const char ver_minor[] = LTS_VERSION_MINOR; +static const char ver_patch[] = LTS_VERSION_PATCH; +static const char ver_jenkins[] = JENKINS_BUILD_NUMBER; + + +namespace LtsAppManager +{ + +struct LtsAppManagerHandler +{ + LtsAppManager ltsAppManager; +}; + +Q_GLOBAL_STATIC(LtsAppManagerHandler, s_ltsAppManager) + + +LtsAppManager *LtsAppManager::getInstance() +{ + return &s_ltsAppManager->ltsAppManager; +} + + +LtsAppManager::LtsAppManager() + : m_login(nullptr) +{ + + qRegisterMetaType("LtsAppDescriptor::App"); + slotStartDebugServer(); + connect(qApp, &QCoreApplication::aboutToQuit, this, [this] { delete m_login; m_login = nullptr; }); +} + + +LtsAppManager::~LtsAppManager() +{ + + delete m_login; + m_login = nullptr; +} + + +void LtsAppManager::setAvailableApps(const LtsAppDescriptor::AppDescriptionVector &availableApps) +{ + m_availableApps = availableApps; + + + + + + +#ifndef LTS_DEBUG + if (LtsSessionData::LtsSessionData::getInstance()->debugFlagValue(QStringLiteral("PluginManager.CheckAppRequirements")) == QVariant(true)) + { + QMessageBox::information(nullptr, QStringLiteral("LtsAppManager"), QStringLiteral("Checking App Requirements now")); +#else + { +#endif + QStringList errors; + LtsPluginManager::LtsPluginLoader::getInstance()->checkPlugins(availableApps, errors); + + if (!errors.isEmpty()) + QMessageBox::critical(nullptr, QStringLiteral("LtsAppManager"), QStringLiteral("App Requirements check failed!
- %1").arg(errors.join(QStringLiteral("
- ")))); + } + + + if (m_availableApps.isEmpty()) + { + QMessageBox::critical(nullptr, tr("App Manager"), tr("There are no apps available for this project.\nPlease contact Support.")); + abort(); + } +} + + +void LtsAppManager::slotStartDebugServer() +{ + QTcpServer *debugServer = new QTcpServer(this); + + if (!debugServer->listen(QHostAddress::Any, 20000)) + { + debugServer->deleteLater(); + const int waitTryAgain = 5; +#ifdef LTS_DEBUG + +#endif + QTimer::singleShot(waitTryAgain * 1000, this, &LtsAppManager::slotStartDebugServer); + return; + } + + connect(debugServer, &QTcpServer::newConnection, this, [this]() + { + QTcpServer *server = qobject_cast(sender()); + + if (server == nullptr) + return; + + QTcpSocket *client = server->nextPendingConnection(); + + if (client == nullptr) + return; + + connect(client, &QTcpSocket::readyRead, this, [this]() + { + QTcpSocket *client = qobject_cast(sender()); + + if (client == nullptr) + return; + + QString commandStr(QString::fromUtf8(client->readLine(100)).remove(QStringLiteral("\r")).remove(QStringLiteral("\n"))); + + QStringList argList = commandStr.split(QLatin1Char(' '), QString::SplitBehavior::SkipEmptyParts); + + if (argList.count() < 1) + return; + + const QString &command = argList.at(0); + + LtsSessionData::LtsSessionData *sessionData = LtsSessionData::LtsSessionData::getInstance(); + static QStringList commands; + commands << QStringLiteral("showSessions") + << QStringLiteral("operator") + << QStringLiteral("help") + << QStringLiteral("logout") + << QStringLiteral("flag") + << QStringLiteral("setFlag") + << QStringLiteral("start") + << QStringLiteral("voiceCommand") + << QStringLiteral("ftcmock") + ; + + switch (commands.indexOf(command)) + { + case 0: + { + sessionData->setDebugFlagValue(QStringLiteral("DeveloperTools.ShowSessionsOnce"), true); + client->write("K"); + break; + } + + case 1: + { + QString operatorName(sessionData->getOperatorName()); + + if (operatorName.isEmpty()) + operatorName = QStringLiteral("(none)"); + + QString result(QStringLiteral("%1").arg(operatorName)); + client->write(result.toUtf8()); + break; + } + + case 2: + { + QString result(QStringLiteral("Available Commands: %1").arg(commands.join(QStringLiteral(", ")))); + client->write(result.toUtf8()); + break; + } + + case 3: + { + client->write("..."); + slotLogout(); + client->write("\r\nK"); + break; + } + + case 4: + { + if (argList.count() != 2) + { + client->write("Usage: flag "); + break; + } + + const QString &debugFlagName = argList.at(1); + if (!sessionData->hasDebugFlag(debugFlagName)) + { + client->write("(unknown)"); + break; + } + + client->write(sessionData->debugFlagValue(argList.at(1)).toString().toUtf8()); + break; + } + + case 5: + { + if (argList.count() < 3) + { + client->write("Usage: setFlag \r\nFlag types: int, string (no spaces allowed yet), boolean, double"); + break; + } + + static QStringList flagTypes; + flagTypes << QStringLiteral("int") + << QStringLiteral("string") + << QStringLiteral("boolean") + << QStringLiteral("double"); + + const QString &flagType = argList.at(2); + bool k = false; + + const QString &flagName = argList.at(1); + + switch (flagTypes.indexOf(flagType)) + { + case 0: + { + int value = argList.at(3).toInt(&k); + if (k) + sessionData->setDebugFlagValue(flagName, value); + else + client->write("cannot convert value to integer"); + + break; + } + + case 1: + { + sessionData->setDebugFlagValue(flagName, argList.at(3)); + break; + } + + case 2: + { + QString value(argList.at(3).toLower().simplified()); + if (value == QStringLiteral("true")) + sessionData->setDebugFlagValue(flagName, true); + else if (value == QStringLiteral("false")) + sessionData->setDebugFlagValue(flagName, false); + else + client->write("cannot convert value to boolean"); + + break; + } + + case 3: + { + double value = argList.at(3).toDouble(&k); + if (k) + sessionData->setDebugFlagValue(flagName, value); + else + client->write("cannot convert value to double"); + + break; + } + + + default: + { + client->write("unknown flag type"); + break; + } + } + + break; + } + + case 6: + { + if (argList.count() < 3) + { + client->write("Usage: start "); + break; + } + + + + + + + + + + + client->write("not implemented yet"); + break; + } + + case 7: + { + if (argList.count() < 2) + { + client->write("Usage: voiceCommand "); + break; + } + + static QStringList keywords; + keywords << QStringLiteral("AddMarker") + << QStringLiteral("SetMarker") + << QStringLiteral("AddSnapshot") + << QStringLiteral("SaveSnapshot") ; + + + const QString &keyword = argList.at(1); + + switch (keywords.indexOf(keyword)) + { + case 0: + { + auto ev = new LtsWidgets::AddMarkerEvent; + ev->setAccepted(false); + sessionData->setDebugFlagValue(command, keyword); + QCoreApplication::postEvent(qApp, ev); + break; + } + case 1: + { + auto ev = new LtsWidgets::SetMarkerEvent(QString()); + if (argList.size() > 2) + ev->setDescription(argList.at(2)); + ev->setAccepted(false); + sessionData->setDebugFlagValue(command, keyword); + QCoreApplication::postEvent(qApp, ev); + break; + } + case 2: + { + auto ev = new LtsWidgets::AddSnapshotEvent; + ev->setAccepted(false); + sessionData->setDebugFlagValue(command, keyword); + QCoreApplication::postEvent(qApp, ev); + break; + } + case 3: + { + auto ev = new LtsWidgets::SaveSnapshotEvent(QString()); + if (argList.size() > 2) + ev->setDescription(argList.at(2)); + ev->setAccepted(false); + sessionData->setDebugFlagValue(command, keyword); + QCoreApplication::postEvent(qApp, ev); + break; + } + default: + break; + } + + if (!sessionData->hasDebugFlag(command)) + { + client->write("(unknown)"); + break; + } + + client->write(sessionData->debugFlagValue(argList.at(1)).toString().toUtf8()); + break; + } + case 8: + { + auto usage = [client]() + { + client->write("Usage: ftcmock start - See 'Session id' in DevDialog " + "\nftcmock stop "); + }; + + if (argList.count() < 3) + { + usage(); + break; + } + + bool k = false; + switch (QStringList({QStringLiteral("start"), QStringLiteral("stop")}).indexOf(argList.at(1))) + { + case 0: + { + if (argList.count() < 6) + { + usage(); + return; + } + + const int min = argList.at(3).toInt(&k); + if (!k || min < 0) + { + client->write("min must be a positive integer!"); + return; + } + + const int max = argList.at(4).toInt(&k); + if (!k || max < 0) + { + client->write("max must be a positive integer!"); + return; + } + + const int timers = argList.at(5).toInt(&k); + if (!k || timers < 1) + { + client->write("'timer count' must be a positive integer and greater than 0!"); + return; + } + + break; + } + + case 1: + { + break; + } + + default: + { + usage(); + return; + } + } + + const int sessionId = argList.at(2).toInt(&k); + if (!k || sessionId < 0) + { + client->write("sessionId must be a positive integer!"); + return; + } + + emit signalCommand(client, argList); + break; + } + + default: + { + QString result(QStringLiteral("Unknown command '%1'").arg(command)); + client->write(result.toUtf8()); + break; + } + } + + client->write("\r\n> "); + }); + + + QString versionString = QStringLiteral("%1.%2-%3 (rev: %4)") + .arg(QString::fromLatin1(ver_major), + QString::fromLatin1(ver_minor), + QString::fromLatin1(ver_jenkins), + QString::fromLatin1(ver_patch)); + + + QString motd(QStringLiteral("\r\nOperatorApplication - Version %1").arg(versionString)); + client->write(motd.toUtf8()); + client->write("\r\n> "); + }); +} + + +void LtsAppManager::showLogin() +{ + + QTimer::singleShot(0, this, &LtsAppManager::slotShowLogin); +} + + +void LtsAppManager::slotShowLogin() +{ + + + + + + + if (m_login == nullptr) + { + m_login = new LtsLogin::LtsLogin; + connect(m_login, &LtsLogin::LtsLogin::signalLoginDone, + this, [&] + { + + +#ifdef LTS_DEBUG + +#endif + if (m_appList.isEmpty()) + if (!startAppOrLetUserChoose(m_app, {})) + QTimer::singleShot(100, this, &LtsAppManager::slotLogout); + }); + + connect(m_login, &LtsLogin::LtsLogin::signalLogoffDone, + this, &LtsAppManager::signalLogoffDone); + + + connect(m_login, &LtsLogin::LtsLogin::signalLogoffDone, + this, &LtsAppManager::slotShowLogin, Qt::QueuedConnection); + + connect(m_login, &LtsLogin::LtsLogin::signalLogout, + this, &LtsAppManager::slotLogout); + + m_login->setLoginPlugin(m_loginPluginIid); + } + + m_login->login(); +} + + +bool LtsAppManager::hasRightForApp(const LtsAppDescriptor::AppDescription &appDescription) const +{ + const QSet &acls = LtsSessionData::LtsSessionData::getInstance()->getOperatorRights(); + for (int reqRight : appDescription.requiredAcls) + { + if (!acls.contains(reqRight)) + { + QMessageBox::critical(nullptr, tr("App Manager"), tr("The minimal required operator rights are not met. Therefore, the app cannot be started.")); + return false; + } + } + + return true; +} + + +void LtsAppManager::taskSwitch(QObject *_caller) +{ + LtsSession::LtsSession *caller = qobject_cast(_caller); + + if (caller == nullptr) + { + Q_ASSERT(false); + return; + } + + QVector sessions; + + for (const LtsPluginManager::LtsPluginManager *app : m_appList) + sessions += app->sessions(); + + + + + + + if (sessions.isEmpty()) + return; + + int sessionCount = sessions.count(); + + + if (sessionCount < 2) + return; + + + + + + auto sessionIt = std::find_if(sessions.begin(), sessions.end(), [caller](const LtsSession::LtsSession *session) + { + return (session == caller); + }); + + if (sessionIt == sessions.end()) + { + + return; + } + + + auto it = ++sessionIt; + + + + + + + + + + + + + int x = 0; + do + { + + + if (x > 100) + { + + return; + } + x++; + + + if (it == sessions.end()) + { + + if (sessions.begin() == sessionIt) + { + + return; + } + it = sessions.begin(); + + } + + LtsSession::LtsSession *session = *it; + + + + QWidget *blw = session->baseLayoutWidget(); + + if (blw == nullptr || session->isForeverInvisible() || session == caller) + { + + continue; + } + + + caller->baseLayoutWidget()->hide(); + blw->showFullScreen(); + + return; + } + + while (++it != sessionIt); +} + + +void LtsAppManager::hideAllOtherBLWs(QObject *_caller) +{ + + + + + + if (!LtsSessionData::LtsSessionData::getInstance()->isTablet()) + return; + + + + + + LtsSession::LtsSession *caller = qobject_cast(_caller); + + QVector sessions; + for (const LtsPluginManager::LtsPluginManager *app : m_appList) + sessions += app->sessions(); + + for (LtsSession::LtsSession *session : sessions) + { + + QWidget *blw = session->baseLayoutWidget(); + + if (blw != nullptr && !session->isForeverInvisible() && session != caller ) + { + + blw->hide(); + } + } + + + + + + if (caller != nullptr) + return; + + + + + + + for (LtsSession::LtsSession *session : sessions) + { + + QWidget *blw = session->baseLayoutWidget(); + + if (blw == nullptr || session->isForeverInvisible()) + { + + continue; + } + + + blw->showFullScreen(); + return; + } + + +} + + +bool LtsAppManager::startAppOrLetUserChoose(LtsAppDescriptor::App appId, const LtsSession::DdsConnectionParams &connParams, bool force) +{ + return startAppOrLetUserChoose(getApp(appId), connParams, force); +} + + +bool LtsAppManager::startAppOrLetUserChoose(const LtsAppDescriptor::AppDescription &appDescription, + const LtsSession::DdsConnectionParams &connParams, + bool force) +{ + if (!appDescription.active && !force) + { + + + AppChooser *appChooser = new AppChooser(); + appChooser->setAttribute(Qt::WA_DeleteOnClose); + + connect(appChooser, &AppChooser::signalLogout, + this, &LtsAppManager::slotLogout); + + connect(appChooser, &AppChooser::signalAppSelected, + this, [this, connParams](LtsAppDescriptor::App appId) + { + if (createApp(appId, connParams) == nullptr) + QTimer::singleShot(100, this, &LtsAppManager::slotLogout); + }, Qt::QueuedConnection); + + appChooser->show(); + return true; + } + + + + + if (createApp(appDescription, connParams, {}, false, force) == nullptr) + { + QMessageBox::critical(nullptr, tr("App Manager"), tr("Could not create app.\nPlease contact support.")); + return false; + } + + return true; +} + + +LtsPluginManager::LtsPluginManager *LtsAppManager::createApp(LtsAppDescriptor::App appId, + const LtsSession::DdsConnectionParams &connParams, + const QString &workspaceId, + bool hidden, bool force) +{ + return createApp(getApp(appId), connParams, workspaceId, hidden, force); +} + + +LtsPluginManager::LtsPluginManager *LtsAppManager::createApp(const LtsAppDescriptor::AppDescription &appDescription, + const LtsSession::DdsConnectionParams &connParams, + const QString &workspaceId, + bool hidden, bool force) +{ + if (!appDescription.isValid()) + { + + return nullptr; + } + + if (!hasRightForApp(appDescription) && !force) + return nullptr; + + + const bool bDesiredAppHasSituationMapInstance = LtsPluginManager::LtsPluginManager::hasSituationMapInstance(appDescription, true); + if (bDesiredAppHasSituationMapInstance) + { + const int curInstances = getNumberOfSituationMapInstances(); + + if (curInstances >= 3 && !force) + { + if (QMessageBox::Yes != QMessageBox::question(nullptr, tr("App Manager"), + tr("Are you sure you want to launch another resource-intensive app?
" + "Please note that you are currently running three apps containing the resource-intensive map componment.
" + "It may lead to potential performance issues."))) + { + return nullptr; + } + } + } + + + auto *pluginManager = new LtsPluginManager::LtsPluginManager(); + + connect(pluginManager, &LtsPluginManager::LtsPluginManager::signalQuit, + this, &LtsAppManager::slotQuit); + connect(pluginManager, &LtsPluginManager::LtsPluginManager::signalSoftQuit, + this, &LtsAppManager::slotSoftQuit); + connect(pluginManager, &LtsPluginManager::LtsPluginManager::signalLogout, + this, &LtsAppManager::slotLogout); + connect(pluginManager, &LtsPluginManager::LtsPluginManager::signalInitializationFinished, + this, &LtsAppManager::signalInitializationFinished); + connect(pluginManager, &LtsPluginManager::LtsPluginManager::destroyed, + this, &LtsAppManager::slotAppDestroyed); + connect(pluginManager, &LtsPluginManager::LtsPluginManager::hideAllOtherBLWs, + this, &LtsAppManager::hideAllOtherBLWs); + connect(pluginManager, &LtsPluginManager::LtsPluginManager::taskSwitch, + this, &LtsAppManager::taskSwitch); +#ifdef LTS_DEBUG + connect(pluginManager, &LtsPluginManager::LtsPluginManager::signalStillAlive, + this, [this]() + { + auto *app = qobject_cast(sender()); + if (!app) + return; + + for (auto i = m_appList.constBegin(); i != m_appList.constEnd(); ++i) + { + if (*i == app) + return; + } + + + }); +#endif + + + + + + + + + + + + + + + + + + if (!pluginManager->startApp(appDescription, false, connParams, workspaceId, hidden)) + { + QMessageBox::critical(nullptr, tr("App Manager"), tr("The Plugin Manager failed to start the app due to plugin errors.\nPlease contact support.")); + pluginManager->deleteLater(); + + return nullptr; + } + + m_appList.append(pluginManager); + + return pluginManager; +} + + +bool LtsAppManager::hasApp(LtsAppDescriptor::App appId) const +{ + return (std::find_if(m_availableApps.constBegin(), m_availableApps.constEnd(), [appId](const LtsAppDescriptor::AppDescription &app) + { + bool ret = (app.id == appId); + + return ret; + }) != m_availableApps.constEnd()); +} + +int LtsAppManager::getNumberOfSituationMapInstances() const +{ + int numberOfSituationMapInstances = 0; + for (const LtsPluginManager::LtsPluginManager *app : qAsConst(m_appList)) + { + if (!app) + continue; + + if (app->hasSituationMapInstance()) + ++numberOfSituationMapInstances; + } + + return numberOfSituationMapInstances; +} + +LtsAppDescriptor::AppDescription LtsAppManager::getApp(LtsAppDescriptor::App appId) const +{ + for (const LtsAppDescriptor::AppDescription &app : m_availableApps) + { + + + if (app.id == appId) + return app; + } + + + return LtsAppDescriptor::AppDescription(); +} + + +void LtsAppManager::setMainApp(LtsAppDescriptor::App appId) +{ + m_app = getApp(appId); +} + + +void LtsAppManager::saveState() +{ + + + if (LtsSessionData::LtsSessionData::getInstance()->getOperatorId() == 0) + return; + + + QStringList unsavedWorkspaces = LtsUtils::OperatorSettings::getInstance()->identifiers(QStringLiteral("UnsavedWorkspace")); + for (auto i = unsavedWorkspaces.constBegin(); i != unsavedWorkspaces.constEnd(); ++i) + LtsUtils::OperatorSettings::getInstance()->removeValue(QStringLiteral("UnsavedWorkspace"), *i); + + QStringList unsavedFilterPreset = LtsUtils::OperatorSettings::getInstance()->identifiers(QStringLiteral("UnsavedFilterPreset")); + for (auto i = unsavedFilterPreset.constBegin(); i != unsavedFilterPreset.constEnd(); ++i) + LtsUtils::OperatorSettings::getInstance()->removeValue(QStringLiteral("UnsavedFilterPreset"), *i); + + + QJsonArray root; + for (const LtsPluginManager::LtsPluginManager *app : qAsConst(m_appList)) + { + const QVector sessions = app->sessions(); + + const auto appId = app->appDescription().id; + + if (appId == LtsAppDescriptor::App::Login || sessions.count() < 1) + continue; + + + QJsonObject appState; + appState[QStringLiteral("id")] = static_cast(appId); + + QJsonArray sessionStates; + + for (LtsSession::LtsSession *session : sessions) + { + if (session->baseLayout() != nullptr && session->baseLayout()->isSecondaryWidget()) + continue; + + QJsonObject sessionState; + + int screenId = session->screenId(); + + + + int screenContainerId = session->screenContainerId(); + + + + sessionState[QStringLiteral("screenId")] = screenId; + sessionState[QStringLiteral("screenContainerId")] = screenContainerId; + sessionState[QStringLiteral("exerciseId")] = session->exerciseId(); + sessionState[QStringLiteral("partitionId")] = session->partitionId(); + sessionState[QStringLiteral("partitionType")] = int(session->partitionType()); + sessionState[QStringLiteral("savegame")] = session->getSavegame(); + + + const QString newId(QUuid::createUuid().toString()); + sessionState[QStringLiteral("workspaceId")] = newId; + + std::shared_ptr ifcOpm; + if (session->getForeignGuiInterface(ifcOpm)) + { + + ifcOpm->callWorkspaceSaveUnsaved(newId); + } + + + + sessionStates.append(sessionState); + } + + if (!sessionStates.isEmpty()) + { + appState[QStringLiteral("sessions")] = sessionStates; + root.append(appState); + } + } + + QJsonDocument doc(root); + + + LtsUtils::OperatorSettings::getInstance()->setValue(QStringLiteral("app"), QStringLiteral("LastAppStates"), doc.toJson(QJsonDocument::Compact)); +} + + +void LtsAppManager::slotLogout() +{ + for (LtsPluginManager::LtsPluginManager *app : qAsConst(m_appList)) + { + const QVector sessions = app->sessions(); + for (LtsSession::LtsSession *session : sessions) + { + if (session->notifyUnsavedChanges() == false ) + return; + } + } + + + saveState(); + + const QVector apps = m_appList; + + + if (apps.isEmpty() && m_login != nullptr) + m_login->logoff(); + + + for (LtsPluginManager::LtsPluginManager *app : apps) + app->deleteLater(); + + + +} + + +void LtsAppManager::slotAppDestroyed() +{ + LtsPluginManager::LtsPluginManager *app = static_cast(sender()); + + m_appList.removeOne(app); + + + if (m_appList.isEmpty() && m_login != nullptr) + { + + m_login->logoff(); + } +} + + +void LtsAppManager::slotQuit() +{ + + + QObject *object = sender(); + + if (!object) + qCritical() << "ERROR: LtsAppManager: slot called without object - so it cannot be deleted from the app assignments!"; + + LtsPluginManager::LtsPluginManager *app = qobject_cast(object); + + if (app == nullptr) + { + qCritical() << "ERROR: LtsAppManager: quit requested for invalid app"; + return; + } + + if (m_appList.count() == 1) + { + m_appList.removeAll(app); + saveState(); + } + + app->deleteLater(); + +} + + +void LtsAppManager::slotSoftQuit() +{ + + + QObject *object = sender(); + + + + + LtsPluginManager::LtsPluginManager *app = qobject_cast(object); + + if (app == nullptr) + { + qCritical() << "ERROR: LtsAppManager: quit requested for invalid app"; + return; + } + + app->_softQuit(); + + if (m_appList.count() == 1) + { + m_appList.removeAll(app); + saveState(); + } + + + +} + + +bool LtsAppManager::otherSessionsResponsibility(LtsSession::LtsSession *thisSession, const QVector &requiredAppTypes, + const QVector &requiredPartitionTypes, const QString &menuGroup, + const QString &identifier, LtsWidgets::LayoutArea area) +{ + const QVector apps = LtsAppManager::LtsAppManager::getInstance()->getAppList(); + QVector worthyApps; + + for (LtsPluginManager::LtsPluginManager *app : apps) + { + if (!requiredAppTypes.contains(static_cast(app->appDescription().id))) + continue; + + worthyApps.append(app); + } + + + + for (const LtsPluginManager::LtsPluginManager *app : qAsConst(worthyApps)) + { + const QVector sessions = app->sessions(); + for (const LtsSession::LtsSession *session : sessions) + { + if (session->widgetExists(menuGroup, identifier, area)) + return true; + } + } + + + + LtsMultiMonitor::LtsMultiMonitor *mm = LtsMultiMonitor::LtsMultiMonitor::getInstance(); + LtsMultiMonitor::LtsScreenBase *mainScreen = mm->mainScreen(); + + for (const LtsPluginManager::LtsPluginManager *app : qAsConst(worthyApps)) + { + const QVector sessions = app->sessions(); + for (LtsSession::LtsSession *session : sessions) + { + if (session != thisSession + && mm->widgetsScreen(session->baseLayoutWidget()) == mainScreen + && requiredPartitionTypes.contains(session->partitionType())) + return true; + } + } + + return false; +} + +} diff --git a/Lts/Common/LtsAppManager/lts_app_manager.h b/Lts/Common/LtsAppManager/lts_app_manager.h new file mode 100755 index 000000000..3b068345f --- /dev/null +++ b/Lts/Common/LtsAppManager/lts_app_manager.h @@ -0,0 +1,123 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + + +#ifndef LTSAPPMANAGER_EXPORT +# ifdef BUILD_STATIC_LIBRARIES +# define LTSAPPMANAGER_EXPORT +# else +# if defined(MAKE_LTSAPPMANAGER_LIB) +# define LTSAPPMANAGER_EXPORT Q_DECL_EXPORT +# else +# define LTSAPPMANAGER_EXPORT Q_DECL_IMPORT +# endif +# endif +#endif + + +namespace LtsPluginManager +{ +class LtsPluginManager; +} + +namespace LtsLogin +{ +class LtsLogin; +} + +namespace LtsSession +{ +class LtsSession; +struct DdsConnectionParams; +} + +namespace DeveloperTools +{ +class DevDialog; +} + +class QTcpSocket; + + +namespace LtsAppManager +{ + +class LTSAPPMANAGER_EXPORT LtsAppManager : public QObject +{ + Q_OBJECT + +public: + static LtsAppManager* getInstance(); + + void showLogin(); + + + LtsPluginManager::LtsPluginManager *createApp(LtsAppDescriptor::App appId, const LtsSession::DdsConnectionParams &connParams, + const QString &workspaceId = QString(), bool hidden = false, bool force = false); + bool startAppOrLetUserChoose(LtsAppDescriptor::App appId, const LtsSession::DdsConnectionParams &connParams, bool force = false); + void setLoginPluginIid(const QString &loginPluginIid) { m_loginPluginIid = loginPluginIid; } + void setMainApp(LtsAppDescriptor::App appId = LtsAppDescriptor::App::Login); + QVector getAppList() const { return m_appList ;} + LtsAppDescriptor::AppDescriptionVector getAvailableApps() const { return m_availableApps; } + void setAvailableApps(const LtsAppDescriptor::AppDescriptionVector &availableApps); + bool hasRightForApp(const LtsAppDescriptor::AppDescription &appDescription) const; + LtsAppDescriptor::AppDescription getApp(LtsAppDescriptor::App appId) const; + bool hasApp(LtsAppDescriptor::App appId) const; + int getNumberOfSituationMapInstances() const; + void debugSendMessage(const QString &msg); + + + + bool otherSessionsResponsibility(LtsSession::LtsSession *thisSession, const QVector &requiredAppTypes, + const QVector &requiredPartitionTypes, const QString &menuGroup, + const QString &identifier, LtsWidgets::LayoutArea area); + +private: + friend struct LtsAppManagerHandler; + friend class DeveloperTools::DevDialog; + + LtsAppManager(); + ~LtsAppManager(); + + bool startAppOrLetUserChoose(const LtsAppDescriptor::AppDescription &appDescription, const LtsSession::DdsConnectionParams &connParams, bool force = false); + LtsPluginManager::LtsPluginManager *createApp(const LtsAppDescriptor::AppDescription &appDescription, + const LtsSession::DdsConnectionParams &connParams, + const QString &workspaceId = QString(), + bool hidden = false, bool force = false); + void saveState(); + + LtsAppDescriptor::AppDescriptionVector m_availableApps; + QVector m_appList; + LtsLogin::LtsLogin *m_login; + LtsAppDescriptor::AppDescription m_app; + QString m_loginPluginIid; + +public slots: + void slotLogout(); + +private slots: + void slotQuit(); + void slotSoftQuit(); + void slotShowLogin(); + void slotAppDestroyed(); + void slotStartDebugServer(); + void hideAllOtherBLWs(QObject *_caller); + void taskSwitch(QObject *_caller); + +signals: + void signalLoginDone(); + void signalLogoffDone(); + void signalShowLogin(); + void signalInitializationFinished(); + void signalCommand(QTcpSocket *client, const QStringList argList); +}; + +} diff --git a/Lts/Common/LtsBotan/CMakeLists.txt b/Lts/Common/LtsBotan/CMakeLists.txt new file mode 100755 index 000000000..ff9f098c7 --- /dev/null +++ b/Lts/Common/LtsBotan/CMakeLists.txt @@ -0,0 +1,12 @@ +find_package(Botan2) + +if (BOTAN_FOUND AND CMAKE_VERSION VERSION_GREATER 3.15.9) # botan doesn't use cmake, make it a cmake target | imported targets don't work correctly with 3.10 + add_library(LtsBotan STATIC IMPORTED GLOBAL) + set_target_properties(LtsBotan PROPERTIES IMPORTED_LOCATION ${BOTAN_LIBRARIES}) + set_target_properties(LtsBotan PROPERTIES INCLUDE_DIRECTORIES ${BOTAN_INCLUDE_DIRS}) + target_compile_definitions(LtsBotan INTERFACE BOTAN_FOUND) + target_include_directories(LtsBotan SYSTEM INTERFACE ${BOTAN_INCLUDE_DIRS}) + set(LTS_BOTAN LtsBotan) + + message(STATUS "BOTAN_INCLUDE_DIRS = ${BOTAN_INCLUDE_DIRS}, BOTAN_LIBRARIES = ${BOTAN_LIBRARIES}, LTS_BOTAN = ${LTS_BOTAN}") +endif() diff --git a/Lts/Common/LtsCommandLineParser/CMakeLists.txt b/Lts/Common/LtsCommandLineParser/CMakeLists.txt new file mode 100755 index 000000000..8e927d952 --- /dev/null +++ b/Lts/Common/LtsCommandLineParser/CMakeLists.txt @@ -0,0 +1,21 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +NGL_ADD_LIBRARY2(ALL LtsCommandLineParser SHARED + LtsCommandLineParser.cpp + LtsCommandLineParser.h +) +NGL_TARGET_INCLUDE_DIRECTORIES(LtsCommandLineParser + INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) +NGL_TARGET_LINK_LIBRARIES(LtsCommandLineParser + PRIVATE + ngltools + Qt5::Network + PUBLIC + Qt5::Core +) diff --git a/Lts/Common/LtsCommandLineParser/LtsCommandLineParser.cpp b/Lts/Common/LtsCommandLineParser/LtsCommandLineParser.cpp new file mode 100755 index 000000000..05b334738 --- /dev/null +++ b/Lts/Common/LtsCommandLineParser/LtsCommandLineParser.cpp @@ -0,0 +1,183 @@ + + + +#include "LtsCommandLineParser.h" +#include +#include +#include +#include +#include + +namespace LtsCommandLineParser +{ +static constexpr int s_maxDomainId = 232; + +LtsCommandLineParser::LtsCommandLineParser(ProvideCommandLineParameterOptions opts) + : QCommandLineParser() + , m_enabledOptions(opts) +{ + if (NGL::isAppInBuildBasePath() || m_enabledOptions.testFlag(CreateDomainIdFromIp)) + { + const QList addresses = QNetworkInterface::allAddresses(); + for (const QHostAddress &address : addresses) + { + if (address.isLoopback() || address.isNull()) + continue; + if (address.protocol() != QAbstractSocket::IPv4Protocol) + continue; + + + m_generatedDomainId = (address.toIPv4Address() & 0x000000ff) % 232; + if (m_generatedDomainId == 0) + m_generatedDomainId = s_maxDomainId; + break; + } + } + + if (m_enabledOptions.testFlag(ExerciseId)) + addOption({{QStringLiteral("e"), QStringLiteral("exerciseid")}, + QStringLiteral("Default: -1 (invalid, get from lts-cc.conf)"), + QStringLiteral("exerciseId"), + QStringLiteral("-1") + }); + if (m_enabledOptions.testFlag(DomainId)) + addOption({{QStringLiteral("d"), QStringLiteral("domainid")}, + QStringLiteral("Default: ") + QString::number(m_generatedDomainId), + QStringLiteral("domainId"), + QString::number(m_generatedDomainId) + }); + if (m_enabledOptions.testFlag(CustomConfigFile)) + { + const QString cf(NGL::findProjectConfigFile()); + addOption({{QStringLiteral("i"), QStringLiteral("ini")}, + QStringLiteral("Default: ") + cf, + QStringLiteral("filename"), + cf + }); + } + if (m_enabledOptions.testFlag(TcpConnectionUrl)) + { + const QString defaultTcpUrl(QStringLiteral("EXCTDS01:7400")); + addOption({{QStringLiteral("t"), QStringLiteral("tcpurl")}, + QStringLiteral("Default: ") + defaultTcpUrl, + QStringLiteral("hostname:port"), + defaultTcpUrl + }); + } + addHelpOption(); + + if (!m_enabledOptions.testFlag(NoVersion)) + addVersionOption(); +} + +int LtsCommandLineParser::domainId() const +{ + if (m_domainId == 0 && m_generatedDomainId != 0 && !m_domainIdSetByFunction) + { +#ifdef WIN32 + + fprintf(stdout, "\n\nAttention: using a default domain id != 0 because executable is run inside builddir - default domainid is %d\n\n", m_generatedDomainId); +#else + fprintf(stdout, "\n\n\e[31mAttention: using a default domain id != 0 because executable is run inside builddir - default domainid is %d\e[0m\n\n", m_generatedDomainId); +#endif + return m_generatedDomainId; + } + return m_domainId; +} + +void LtsCommandLineParser::setDomainId(int id) +{ + m_domainIdSetByFunction = true; + m_domainId = id; +} + +bool LtsCommandLineParser::iniFileSet() const +{ + if (!m_enabledOptions.testFlag(CustomConfigFile)) + return false; + return isSet(QStringLiteral("i")); +} + +bool LtsCommandLineParser::useTcpConnection() const +{ + if (!m_enabledOptions.testFlag(TcpConnectionUrl)) + return false; + return isSet(QStringLiteral("t")); +} + +QUrl LtsCommandLineParser::tcpConnectionUrl() const +{ + if (!m_enabledOptions.testFlag(TcpConnectionUrl)) + return QUrl(); + if (!useTcpConnection()) + return QUrl(); + QString str = value(QStringLiteral("t")); + + str.replace(QLatin1String("tcpv4_lan"), QLatin1String("tcpv4lan")); + QUrl url = QUrl::fromUserInput(str); + if (url.scheme() == QLatin1String("http") || + url.scheme() == QLatin1String("file")) + url.setScheme(QStringLiteral("tcpv4lan")); + return url; +} + +bool LtsCommandLineParser::init(bool bParseHelp) +{ + if (!parse(QCoreApplication::arguments())) + { + fprintf(stdout, "\nBad option: %s \n", qPrintable(errorText())); + if (!m_unitModuleTestMode) + return false; + } + + if (m_enabledOptions.testFlag(ExerciseId)) + m_exerciseId = value(QStringLiteral("e")).toInt(); + if (m_enabledOptions.testFlag(DomainId)) + m_domainId = value(QStringLiteral("d")).toInt(); + if (m_enabledOptions.testFlag(CustomConfigFile)) + m_iniFile = value(QStringLiteral("i")); + + if (bParseHelp && isSet(QStringLiteral("h"))) + showHelp(); + if (!m_unitModuleTestMode && isSet(QStringLiteral("v"))) + showVersion(); + if (!domainIdProvided()) + { + const QString iniFile = m_iniFile.isEmpty() ? NGL::findProjectConfigFile() : m_iniFile; + if (!iniFile.isEmpty()) + { + QSettings s(iniFile, QSettings::IniFormat); + bool bOk; + int domainId = s.value(QStringLiteral("Global/DomainId"), m_domainId).toInt(&bOk); + if (bOk && domainId > 0 && domainId <= s_maxDomainId) + m_domainId = domainId; + } + } + return true; +} + +void LtsCommandLineParser::showHelp(int exitCode) +{ + if (!m_additionalHelpStr.isEmpty()) + fprintf(stdout, "%s", qPrintable(m_additionalHelpStr)); + + + QCommandLineParser::showHelp(exitCode); +} + +bool LtsCommandLineParser::domainIdProvided() const +{ + if (!m_enabledOptions.testFlag(DomainId)) + return false; + return isSet(QStringLiteral("d")); +} + +bool LtsCommandLineParser::exerciseIdProvided() const +{ + if (!m_enabledOptions.testFlag(ExerciseId)) + return false; + return isSet(QStringLiteral("e")); +} + + +} diff --git a/Lts/Common/LtsCommandLineParser/LtsCommandLineParser.h b/Lts/Common/LtsCommandLineParser/LtsCommandLineParser.h new file mode 100755 index 000000000..69d80a1c3 --- /dev/null +++ b/Lts/Common/LtsCommandLineParser/LtsCommandLineParser.h @@ -0,0 +1,56 @@ + + + +#pragma once + +#include +#include + +class QUrl; +namespace LtsCommandLineParser +{ +class LTSCOMMANDLINEPARSER_EXPORT LtsCommandLineParser : public QCommandLineParser +{ +public: + enum ProvideCommandLineParameterOptions + { + None = 0x0000, + CreateDomainIdFromIp = 0x0001, + DomainId = 0x0002, + ExerciseId = 0x0004, + CustomConfigFile = 0x0008, + TcpConnectionUrl = 0x0010, + NoVersion = 0x0020 + }; + + explicit LtsCommandLineParser(ProvideCommandLineParameterOptions opts = ProvideCommandLineParameterOptions(CreateDomainIdFromIp|DomainId|ExerciseId|CustomConfigFile)); + virtual ~LtsCommandLineParser() = default; + int domainId() const; + void setDomainId(int id); + int exerciseId() const { return m_exerciseId; } + QString iniFile() const { return m_iniFile; } + bool iniFileSet() const; + bool useTcpConnection() const; + QUrl tcpConnectionUrl() const; + virtual bool init(bool bParseHelp = true); + + void showHelp(int exitCode = 0); + bool domainIdProvided() const; + bool exerciseIdProvided() const; + + void setUnitModuleTestMode(bool onOff) { m_unitModuleTestMode = onOff; } + +protected: + QString m_additionalHelpStr; + +private: + QFlags m_enabledOptions; + int m_exerciseId{0}; + int m_domainId{0}; + int m_generatedDomainId{0}; + bool m_domainIdSetByFunction{false}; + bool m_unitModuleTestMode{false}; + QString m_iniFile; +}; + +} diff --git a/Lts/Common/LtsCoordTrans/CMakeLists.txt b/Lts/Common/LtsCoordTrans/CMakeLists.txt new file mode 100755 index 000000000..e7caa41b5 --- /dev/null +++ b/Lts/Common/LtsCoordTrans/CMakeLists.txt @@ -0,0 +1,78 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# Created by Christian Ehrlicher, SLSE, 2016-08-31 +# + +FIND_PACKAGE(GeographicLib REQUIRED) +NGL_ADD_LIBRARY2(LtsCoordTrans WS AAR SV HLA SHARED + lts_coord_trans.h + lts_coord_trans.cpp +) +NGL_TARGET_LINK_LIBRARIES(LtsCoordTrans + PRIVATE + GeographicLib + PUBLIC + Qt5::Core +) +NGL_TARGET_INCLUDE_DIRECTORIES(LtsCoordTrans + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) + +SET(ltsCoordWidget_SRCS + lts_coord_widget.cpp + lts_coord_widget.h + lts_coord_widget_p.cpp + lts_coord_widget_p.h +) +NGL_WRAP_UI(ltsCoordWidget_SRCS + lts_coord_widget_mgrs.ui + lts_coord_widget_wgs84.ui +) +NGL_ADD_LIBRARY2(ltsCoordWidget WS AAR SV SHARED ${ltsCoordWidget_SRCS}) +NGL_TARGET_LINK_LIBRARIES(ltsCoordWidget + PRIVATE + LtsCoordTrans + PUBLIC + Qt5::Core + Qt5::Widgets +) +NGL_TARGET_INCLUDE_DIRECTORIES(ltsCoordWidget + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) + +IF(TARGET LtsCoordTrans) + SET(_tools lonlat2mgrs mgrs2lonlat) + FOREACH(_tool ${_tools}) + NGL_ADD_EXECUTABLE(ALL ${_tool} ${_tool}.cpp) + NGL_TARGET_LINK_LIBRARIES(${_tool} + Qt5::Core + GeographicLib + ) + NGL_INSTALL_TARGET(${_tool}) + ENDFOREACH() +ENDIF() + +IF(BUILD_TESTING AND TARGET ltsCoordWidget) + ADD_EXECUTABLE(lts_coord_widget_test lts_coord_widget_test.cpp) + TARGET_LINK_LIBRARIES(lts_coord_widget_test + ltsCoordWidget + LtsCoordTrans + ) +ENDIF() + +IF(BUILD_TESTING AND TARGET LtsCoordTrans) + ADD_EXECUTABLE(lts_coord_trans_unit_test + lts_coord_trans_unit_test.cpp + lts_coord_trans_unit_test.h + ) + TARGET_LINK_LIBRARIES(lts_coord_trans_unit_test + LtsCoordTrans + Qt5::Test + GeographicLib + ) + ADD_TEST(NAME lts_coord_trans_unit_test COMMAND $) +ENDIF() diff --git a/Lts/Common/LtsCoordTrans/lonlat2mgrs.cpp b/Lts/Common/LtsCoordTrans/lonlat2mgrs.cpp new file mode 100755 index 000000000..37c15c46a --- /dev/null +++ b/Lts/Common/LtsCoordTrans/lonlat2mgrs.cpp @@ -0,0 +1,67 @@ + + + +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + if (argc < 3 || argc > 5) + { + fprintf(stderr, "Usage: %s []\n", argv[0]); + fprintf(stderr, "Usage: %s -r []\n", argv[0]); + return -1; + } + + bool lonLatInRad = false; + double lon = atof(argv[1]); + double lat = atof(argv[2]); + int prec = 5; + if (argc >= 4) + { + int precArgId = -1; + if (strcmp(argv[3], "-r") == 0) + { + lonLatInRad = true; + if (argc == 5) + precArgId = 4; + } + else + precArgId = 3; + if (precArgId > 0) + prec = atoi(argv[precArgId]); + } + + std::string mgrs; + + int zone; + bool northp; + double x, y; + try + { + const double latDeg = lonLatInRad ? lat * 180 / M_PI : lat; + const double lonDeg = lonLatInRad ? lon * 180 / M_PI : lon; + GeographicLib::UTMUPS::Forward(latDeg, lonDeg, zone, northp, x, y); + GeographicLib::MGRS::Forward(zone, northp, x, y, prec, mgrs); + } + catch (...) + { + fprintf(stderr, "Error converting %9.6f, %9.6f to MGRS coords\n", lat, lon); + return -2; + } + std::string part1, part2, part3, part4; + part1 = mgrs.substr(0, 3); + part2 = mgrs.substr(3, 2); + if (prec > 0) + { + part3 = mgrs.substr(5, prec); + part4 = mgrs.substr(5 + prec, prec); + } + fprintf(stdout, "%13.10f, %13.10f = %s %s %s %s (UTM: %d %.2f, %.2f %c)\n", + lon, lat, + part1.c_str(), part2.c_str(), part3.c_str(), part4.c_str(), + zone, x, y, northp ? 'N' : 'S'); + return 0; +} diff --git a/Lts/Common/LtsCoordTrans/lts_coord_trans.cpp b/Lts/Common/LtsCoordTrans/lts_coord_trans.cpp new file mode 100755 index 000000000..517f1fe79 --- /dev/null +++ b/Lts/Common/LtsCoordTrans/lts_coord_trans.cpp @@ -0,0 +1,445 @@ + + + +#include "lts_coord_trans.h" + +#include +#include +#include +#include +#include +#include +#include + + +#include + +namespace LtsCoordTrans +{ + +static long s_conversion_counter_MGRS_Forward = 0; +static long s_conversion_counter_MGRS_Reverse = 0; + +static long s_conversion_counter_WGS84_Forward = 0; +static long s_conversion_counter_WGS84_Reverse = 0; + +static long s_conversion_counter_UTMUPS_Forward = 0; +static long s_conversion_counter_UTMUPS_Reverse = 0; + + +inline void incrementCounter(long &counterRef) +{ +#ifdef LTS_DEBUG + ++counterRef; +#endif +} + + +static inline std::string cleanupMgrsString(const QString &in) +{ + const QByteArray ba(in.toLatin1()); + std::string ret; + ret.reserve(ba.size()); + for (const char c : ba) + { + if (c != ' ') + ret += c; + } + return ret; +} + +QString UtmCoordinate::mgrs(int prec, bool withSpaces) const +{ + if (prec < 0) + prec = 0; + if (prec > 11) + prec = 11; + + std::string mgrs; + try + { + GeographicLib::MGRS::Forward(zone, northp, x, y, prec, mgrs); + incrementCounter(s_conversion_counter_MGRS_Forward); + } + catch (const std::exception &e) + { + fprintf(stderr, "Error while calculating mgrs from utm %d, %9.6f, %9.6f: %s\n", zone, x, y, e.what()); + mgrs.clear(); + } + + QString ret; + if (withSpaces && mgrs.size() == static_cast(5 + 2 * prec)) + { + + const char *ptr = mgrs.c_str(); + + ret = QString::fromLatin1(ptr, 3) + QLatin1Char(' ') + + QString::fromLatin1(ptr + 3, 2); + if (prec > 0) + { + ret += QLatin1Char(' ') + QString::fromLatin1(ptr + 5, prec) + + QLatin1Char(' ') + QString::fromLatin1(ptr + 5 + prec, prec); + } + } + else + ret = QString::fromLatin1(mgrs.c_str(), mgrs.size()); + return ret; +} + +QPointF UtmCoordinate::toPoint() const +{ + return {x,y}; +} + +QPointF UtmCoordinate::geographic(double offsetX, double offsetY) const +{ + QPointF ret; + try + { + GeographicLib::UTMUPS::Reverse(zone, northp, x + offsetX, y + offsetY, ret.ry(), ret.rx()); + incrementCounter(s_conversion_counter_UTMUPS_Reverse); + } + catch (const std::exception &e) + { + fprintf(stderr, "Error while calculating geographic from utm %d, %9.6f, %9.6f: %s\n", zone, x + offsetX, y + offsetY, e.what()); + ret = QPointF(); + } + return ret * deg2rad(); +} + +QPointF UtmCoordinate::geographic(const QPointF &offset) const +{ + return geographic(offset.x(), offset.y()); +} + +QPointF UtmCoordinate::relativeTo(const UtmCoordinate &other) const +{ + if (zone == other.zone) + return QPointF(x - other.x, y - other.y); + + UtmCoordinate otherInMyZone = fromGeographic(other.geographic(), zone); + return QPointF(x - otherInMyZone.x, y - otherInMyZone.y); +} + +UtmCoordinate UtmCoordinate::fromGeographic(double lon, double lat, int setZone) +{ + + lon *= rad2deg(); + lat *= rad2deg(); + + UtmCoordinate ret; + try + { + + GeographicLib::UTMUPS::Forward(lat, lon, ret.zone, ret.northp, ret.x, ret.y, setZone); + incrementCounter(s_conversion_counter_UTMUPS_Forward); + } + catch (const std::exception &e) + { + fprintf(stderr, "Error while calculating mgrs base point from %9.6f, %9.6f: %s\n", lat, lon, e.what()); + ret = UtmCoordinate(); + } + return ret; +} + +UtmCoordinate UtmCoordinate::fromGeographic(const QPointF &lonLat, int setZone) +{ + return fromGeographic(lonLat.x(), lonLat.y(), setZone); +} + +UtmCoordinate UtmCoordinate::fromMgrs(const QString &mgrs) +{ + UtmCoordinate utm; + try + { + int prec; + + GeographicLib::MGRS::Reverse(cleanupMgrsString(mgrs), utm.zone, utm.northp, utm.x, utm.y, prec, true); + incrementCounter(s_conversion_counter_MGRS_Reverse); + + } + catch (const std::exception &e) + { + fprintf(stderr, "Error while converting mgrs coordinate %s to utm: %s\n", qPrintable(mgrs), e.what()); + utm = UtmCoordinate(); + } + return utm; +} + +QString mgrsFromGeographic(double lon, double lat, int prec, bool withSpaces) +{ + return UtmCoordinate::fromGeographic(lon, lat, -1).mgrs(prec, withSpaces); +} + +QPointF geographicFromMgrs(const QString &mgrs) +{ + return UtmCoordinate::fromMgrs(mgrs).geographic(); +} + +QVector &staticDefaultSquaresHelper() +{ + static QVector s_defaultMgrsSquares; + return s_defaultMgrsSquares; +} +UtmCoordinate &staticPreferedMgrsSquareHelper() +{ + static UtmCoordinate s_preferedMgrsSquare; + return s_preferedMgrsSquare; +} +QVector &staticMgrsSquaresWithOffsetsHelper() +{ + static QVector s_mgrsSquaresWithOffsets; + return s_mgrsSquaresWithOffsets; +} + + +QRectF addMarginToArea(const QRectF &rect_LatLon_radiant, const QSize &marginMeter) +{ + double lonMin, lonMax, latMin, latMmax; + rect_LatLon_radiant.normalized().getCoords(&lonMin, &latMin, &lonMax, &latMmax); + + + + + + LtsCoordTrans::UtmCoordinate basePoint; + { + + const QPointF centerRad = rect_LatLon_radiant.center(); + QString mgrs = LtsCoordTrans::mgrsFromGeographic(centerRad.x(), centerRad.y(), -1); + mgrs += QStringLiteral("00000000000000"); + + + basePoint = LtsCoordTrans::UtmCoordinate::fromMgrs(mgrs); + } + + const UtmCoordinate lowerLeft(UtmCoordinate::fromGeographic(lonMin, latMin, basePoint.zone)); + const UtmCoordinate upperRight(UtmCoordinate::fromGeographic(lonMax, latMmax, basePoint.zone)); + + int offsetX = marginMeter.width(); + int offsetY = marginMeter.height(); + + const QPointF topLeft(lowerLeft.geographic(-offsetX, -offsetY)); + const QPointF bottomRight(upperRight.geographic(offsetX, offsetY)); + + QRectF res = QRectF(topLeft, bottomRight); + return res; +} + + +QVector getMgrsSquares(const QRectF &rect, bool setAsDefaultSquares, bool setAsPreferedSquare, bool setAsSquareWithMargin) +{ + QVector ret = getMgrsSquaresInternal(rect); + + if (setAsDefaultSquares) + staticDefaultSquaresHelper() = ret; + + if (setAsPreferedSquare) + staticPreferedMgrsSquareHelper() = LtsCoordTrans::UtmCoordinate::fromGeographic(rect.center()); + + if (setAsSquareWithMargin) + staticMgrsSquaresWithOffsetsHelper() = ret; + + + return ret; +} + +QVector getMgrsSquaresInternal(const QRectF &rect) +{ + QVector ret; + QVector tmpMgrsSquares; + + + double lonMin, lonMax, latMin, latMmax; + rect.normalized().getCoords(&lonMin, &latMin, &lonMax, &latMmax); + + + bool withInSingleUtmSquare = true; + { + const UtmCoordinate upperRight(UtmCoordinate::fromGeographic(lonMax, latMmax)); + const UtmCoordinate lowerLeft(UtmCoordinate::fromGeographic(lonMin, latMin)); + withInSingleUtmSquare = upperRight.zone == lowerLeft.zone; + } + + + + + + LtsCoordTrans::UtmCoordinate basePoint; + { + + QPointF centerRad = rect.center(); + QString mgrs = LtsCoordTrans::mgrsFromGeographic(centerRad.x(), centerRad.y(), -1); + mgrs += QStringLiteral("00000000000000"); + + + basePoint = LtsCoordTrans::UtmCoordinate::fromMgrs(mgrs); + } + + const UtmCoordinate upperRight(UtmCoordinate::fromGeographic(lonMax, latMmax, basePoint.zone)); + const UtmCoordinate lowerLeft(UtmCoordinate::fromGeographic(lonMin, latMin, basePoint.zone)); + + const double maxX(upperRight.x - lowerLeft.x); + const double maxY(upperRight.y - lowerLeft.y); + + + + const int increment = withInSingleUtmSquare ? 10000 : 1000; + for (double x = 0; x <= maxX + increment; x += increment) + { + const double xOfs = std::min(x, maxX); + for (double y = 0; y <= maxY + increment; y += increment) + { + const double yOfs = std::min(y, maxY); + + const UtmCoordinate utm(UtmCoordinate::fromGeographic(lowerLeft.geographic(xOfs, yOfs))); + const QString mgrs(utm.mgrs(0)); + + if (!tmpMgrsSquares.contains(mgrs)) + { + tmpMgrsSquares += mgrs; + ret += utm; + } + } + } + return ret; +} + +QStringList getMgrsSurroundingSquares(const QRectF &geoRect, const QSize &offsetMeter) +{ + + const QVector mainUtms = getMgrsSquaresInternal(geoRect); + QStringList mainSquares; + mainSquares.reserve(mainUtms.count()); + for (const auto &utm : mainUtms) + mainSquares.append(utm.mgrs(0)); + + + const QRectF rectWithOffset = addMarginToArea(geoRect,offsetMeter); + + QVector mainUtmsWithOffset = getMgrsSquaresInternal(rectWithOffset); + QStringList mainSquaresWithOffset; + mainSquaresWithOffset.reserve(mainUtmsWithOffset.count()); + for (const auto &utm : qAsConst(mainUtmsWithOffset)) + mainSquaresWithOffset.append(utm.mgrs(0)); + + + QStringList surroundingSquares; + surroundingSquares.reserve(mainSquaresWithOffset.count()); + for (const auto & mgrs0 : mainSquaresWithOffset) + { + if (!mainSquares.contains(mgrs0)) + surroundingSquares.append(mgrs0); + } + + return surroundingSquares; +} + +QVector defaultMgrsSquareWithOffsets() +{ + return staticMgrsSquaresWithOffsetsHelper(); +} + + +void setDefaultMgrsSquares(const QVector &squares) +{ + QVector coords; + coords.reserve(squares.size()); + for (const QString &s : squares) + coords += UtmCoordinate::fromMgrs(s + QStringLiteral("00000000000000")); + staticDefaultSquaresHelper() = coords; +} + +QVector defaultMgrsSquares() +{ + return staticDefaultSquaresHelper(); +} + +UtmCoordinate preferedMgrsSquare() +{ + return staticPreferedMgrsSquareHelper(); +} + + +bool lonLatAltToGeocentric(double lon, double lat, double altitude, double &xOut, double &yOut, double &zOut) +{ + + lon *= rad2deg(); + lat *= rad2deg(); + + try + { + GeographicLib::Geocentric::WGS84().Forward(lat, lon, altitude, xOut, yOut, zOut); + incrementCounter(s_conversion_counter_WGS84_Forward); + } + catch (...) + { + xOut = 0; + yOut = 0; + zOut = 0; + return false; + } + return true; +} + +bool geocentric2LonLatAlt(double x, double y, double z, + double &lon, double &lat, double &altitude) +{ + + + try + { + GeographicLib::Geocentric::WGS84().Reverse(x, y, z, lat, lon, altitude); + incrementCounter(s_conversion_counter_WGS84_Reverse); + } + catch (...) + { + lon = 0; + lat = 0; + altitude = 0; + return false; + } + + lon *= deg2rad(); + lat *= deg2rad(); + return true; +} + +double distance(const QPointF &p1, const QPointF &p2) +{ + return distance(p1.x(), p1.y(), p2.x(), p2.y()); +} + +double squareDistance(const QPointF &p1, const QPointF &p2) +{ + return squareDistance(p1.x(), p1.y(), p2.x(), p2.y()); +} + +std::ostream &operator<< (std::ostream &stream, + const UtmCoordinate &coord) +{ + + stream << "UtmCoordinate(" << std::setprecision(8) << coord.x + << "#" << std::setprecision(8) << coord.y + << " zone=" << coord.zone << ")"; + return stream; +} + +void dumpConversionCounter() +{ + fprintf(stderr, "LtsCoordTrans: s_conversion_counter_MGRS_Forward : - mgrs : %ld\n" , s_conversion_counter_MGRS_Forward); + fprintf(stderr, "LtsCoordTrans: s_conversion_counter_MGRS_Reverse - fromMgrs : %ld\n" , s_conversion_counter_MGRS_Reverse); + fprintf(stderr, "LtsCoordTrans: s_conversion_counter_WGS84_Forward - lonLatAltToGeocentric : %ld\n" , s_conversion_counter_WGS84_Forward); + fprintf(stderr, "LtsCoordTrans: s_conversion_counter_WGS84_Reverse - geocentric2LonLatAlt : %ld\n" , s_conversion_counter_WGS84_Reverse); + fprintf(stderr, "LtsCoordTrans: s_conversion_counter_UTMUPS_Forward - fromGeographic : %ld\n" , s_conversion_counter_UTMUPS_Forward); + fprintf(stderr, "LtsCoordTrans: s_conversion_counter_UTMUPS_Reverse - geographic : %ld\n" , s_conversion_counter_UTMUPS_Reverse); +} + +long getConversionCounter_MGRS_Forward() { return s_conversion_counter_MGRS_Forward; } +long getConversionCounter_MGRS_Reverse() { return s_conversion_counter_MGRS_Reverse; } +long getConversionCounter_WGS84_Forward() { return s_conversion_counter_WGS84_Forward; } +long getConversionCounter_WGS84_Reverse() { return s_conversion_counter_WGS84_Reverse; } +long getConversionCounter_UTMUPS_Forward() { return s_conversion_counter_UTMUPS_Forward; } +long getConversionCounter_UTMUPS_Reverse() { return s_conversion_counter_UTMUPS_Reverse; } + +} diff --git a/Lts/Common/LtsCoordTrans/lts_coord_trans.h b/Lts/Common/LtsCoordTrans/lts_coord_trans.h new file mode 100755 index 000000000..8589a0ea8 --- /dev/null +++ b/Lts/Common/LtsCoordTrans/lts_coord_trans.h @@ -0,0 +1,249 @@ + + + +#pragma once + +#include + +#include +#include +#include +class QPointF; +class QRectF; +class QSize; +class QString; + +namespace LtsCoordTrans +{ + + + + +inline Q_CONSTEXPR double pi() { return M_PI; } +inline Q_CONSTEXPR double deg2rad() { return pi() / 180.; } +inline Q_CONSTEXPR double rad2deg() { return 1. / deg2rad(); } + +struct LTSCOORDTRANS_EXPORT UtmCoordinate +{ + int zone = -1; + bool northp = true; + double x = 0.; + double y = 0.; + + UtmCoordinate() = default; + UtmCoordinate(int zone_, double x_, double y_, bool northp_ = true) + : zone(zone_) + , northp(northp_) + , x(x_) + , y(y_) + {} + + + + bool isValid() const { return zone > 0 && zone <= 60; } + + + + QString mgrs(int prec = 5, bool withSpaces = true) const; + + QPointF toPoint() const; + + + + QPointF geographic(double offsetX = 0., double offsetY = 0.) const; + + + + QPointF geographic(const QPointF &offset) const; + + + + QPointF relativeTo(const UtmCoordinate &other) const; + + + + static UtmCoordinate fromGeographic(double lon, double lat, int setZone = -1); + + + + static UtmCoordinate fromGeographic(const QPointF &lonLat, int setZone = -1); + + + + static UtmCoordinate fromMgrs(const QString &mgrs); +}; + + + +LTSCOORDTRANS_EXPORT QString mgrsFromGeographic(double lon, double lat, int prec = 5, bool withSpaces = true); + + + +LTSCOORDTRANS_EXPORT QPointF geographicFromMgrs(const QString &mgrs); + + + +LTSCOORDTRANS_EXPORT QRectF addMarginToArea(const QRectF &rect_LatLon_radiant, const QSize &marginMeter); + + + +LTSCOORDTRANS_EXPORT QVector getMgrsSquares(const QRectF &rect, bool setAsDefaultSquares, bool setAsPreferedSquare, bool setAsSquareWithMargin = false); + + + +QVector getMgrsSquaresInternal(const QRectF &rect); + + + +QStringList getMgrsSurroundingSquares(const QRectF &geoRect, const QSize &offsetMeter); + + + +LTSCOORDTRANS_EXPORT QVector defaultMgrsSquareWithOffsets(); + + + +LTSCOORDTRANS_EXPORT void setDefaultMgrsSquares(const QVector &squares); + + + +LTSCOORDTRANS_EXPORT QVector defaultMgrsSquares(); + + + +LTSCOORDTRANS_EXPORT UtmCoordinate preferedMgrsSquare(); + + + +LTSCOORDTRANS_EXPORT bool lonLatAltToGeocentric(double lon, double lat, double altitude, double &xOut, double &yOut, double &zOut); + + + +LTSCOORDTRANS_EXPORT bool geocentric2LonLatAlt(double x, double y, double z, + double &lon, double &lat, double &altitude); + + + + +inline double squareDistance(const UtmCoordinate &p1, const UtmCoordinate &p2) +{ + if (p1.zone != p2.zone) + { + Q_ASSERT(false); + return std::nan(""); + } + return std::pow(p1.x - p2.x, 2) + std::pow(p1.y - p2.y, 2); +} + + + +LTSCOORDTRANS_EXPORT double squareDistance(const QPointF &p1, const QPointF &p2); + + + +inline double squareDistance(const double lon1, const double lat1, const double lon2, const double lat2) +{ + const auto p1(UtmCoordinate::fromGeographic(lon1, lat1)); + const auto p2(UtmCoordinate::fromGeographic(lon2, lat2, p1.zone)); + return squareDistance(p1, p2); +} + + + +inline double squareDistance(const UtmCoordinate &p1, const double lon2, const double lat2) +{ + const auto p2(UtmCoordinate::fromGeographic(lon2, lat2, p1.zone)); + return squareDistance(p1, p2); +} + + + +inline double distance(const UtmCoordinate &p1, const UtmCoordinate &p2) +{ + if (p1.zone != p2.zone) + { + Q_ASSERT(false); + return std::nan(""); + } + return sqrt(squareDistance(p1, p2)); +} + + + +LTSCOORDTRANS_EXPORT double distance(const QPointF &p1, const QPointF &p2); + + + +inline double distance(const double lon1, const double lat1, const double lon2, const double lat2) +{ + return sqrt(squareDistance(lon1, lat1, lon2, lat2)); +} + +inline double distance(const UtmCoordinate &p1, const double lon2, const double lat2) +{ + return sqrt(squareDistance(p1, lon2, lat2)); +} + + +LTSCOORDTRANS_EXPORT void dumpConversionCounter(); + +LTSCOORDTRANS_EXPORT long getConversionCounter_MGRS_Forward(); +LTSCOORDTRANS_EXPORT long getConversionCounter_MGRS_Reverse(); +LTSCOORDTRANS_EXPORT long getConversionCounter_WGS84_Forward(); +LTSCOORDTRANS_EXPORT long getConversionCounter_WGS84_Reverse(); +LTSCOORDTRANS_EXPORT long getConversionCounter_UTMUPS_Forward(); +LTSCOORDTRANS_EXPORT long getConversionCounter_UTMUPS_Reverse(); + + +LTSCOORDTRANS_EXPORT std::ostream &operator<< (std::ostream &stream, + const UtmCoordinate &coord); + + + + + + + +class LTSCOORDTRANS_EXPORT UtmCoordinate3D : public LtsCoordTrans::UtmCoordinate +{ + using BaseClass = LtsCoordTrans::UtmCoordinate; + +public: + UtmCoordinate3D() + { + reset(); + } + + explicit UtmCoordinate3D(const LtsCoordTrans::UtmCoordinate &utm2d, double _zValue = std::nan("")) + :BaseClass() + { + zone = utm2d.zone; + northp = utm2d.northp; + x = utm2d.x; + y = utm2d.y; + zValue = _zValue; + } + + void reset() + { + zone = -1; + northp = true; + + + + + + + x = 0.; + y = 0.; + zValue = std::nan(""); + } + + double z() const { return zValue; } + +protected: + double zValue{std::nan("")}; +}; + + +} diff --git a/Lts/Common/LtsCoordTrans/lts_coord_trans_unit_test.cpp b/Lts/Common/LtsCoordTrans/lts_coord_trans_unit_test.cpp new file mode 100755 index 000000000..90061f05b --- /dev/null +++ b/Lts/Common/LtsCoordTrans/lts_coord_trans_unit_test.cpp @@ -0,0 +1,469 @@ + + + +#include "lts_coord_trans_unit_test.h" +#include "lts_coord_trans.h" +#include +#include +#include + +QTEST_MAIN(coordtrans_test) + +void coordtrans_test::initTestCase() +{ + qDebug("sizeof(UtmCoordinate) = %lu", sizeof(LtsCoordTrans::UtmCoordinate)); +} + +void coordtrans_test::cleanupTestCase() +{} + +void coordtrans_test::testFromGeographic() +{ + + + + LtsCoordTrans::UtmCoordinate utm; + QPointF lonlat; + + lonlat = QPointF(8.6264954126, 53.1138437183) * LtsCoordTrans::deg2rad(); + utm = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y(), -1); + QCOMPARE(utm.mgrs(0), QStringLiteral("32U MD")); + utm = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y()); + QCOMPARE(utm.mgrs(8), QStringLiteral("32U MD 74999999 84999999")); + + lonlat = QPointF(11.3003696521, 52.8224535191) * LtsCoordTrans::deg2rad(); + utm = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y(), -1); + QCOMPARE(utm.mgrs(0), QStringLiteral("32U PD")); + utm = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y()); + QCOMPARE(utm.mgrs(5), QStringLiteral("32U PD 55000 55000")); + + lonlat = QPointF(54.8821607021, 24.2631973752) * LtsCoordTrans::deg2rad(); + utm = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y(), -1); + QCOMPARE(utm.mgrs(0), QStringLiteral("40R BM")); + utm = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y()); + QCOMPARE(utm.mgrs(5), QStringLiteral("40R BM 85000 85000")); + + + + utm = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("40R BM 00000 00000")); + QCOMPARE(utm.mgrs(0), QStringLiteral("40Q BM")); +} + +void coordtrans_test::testMgrsFromGeographic() +{ + + QString mgrs; + QPointF lonlat; + + lonlat = QPointF(8.6191049344, 53.1048309844) * LtsCoordTrans::deg2rad(); + mgrs = LtsCoordTrans::mgrsFromGeographic(lonlat.x(), lonlat.y(), 9); + QCOMPARE(mgrs, QStringLiteral("32U MD 745000000 840000000")); + + mgrs = LtsCoordTrans::mgrsFromGeographic(lonlat.x(), lonlat.y(), 8, false); + QCOMPARE(mgrs, QStringLiteral("32UMD7450000084000000")); +} + +void coordtrans_test::testRelativeMetricalOffet_data() +{ + + QTest::addColumn("offset"); + QTest::addColumn("correctMgrs"); + + + QTest::newRow("set 1") << QPoint( 5, 5) << QStringLiteral("32U PD 99995 99995"); + + + QTest::newRow("set 2") << QPoint(100, 100) << QStringLiteral("32U QE 00090 00090"); + QTest::newRow("set 3") << QPoint(500, 500) << QStringLiteral("32U QE 00490 00490"); +} + +void coordtrans_test::testRelativeMetricalOffet() +{ + QFETCH(QPoint, offset); + QFETCH(QString, correctMgrs); + + const LtsCoordTrans::UtmCoordinate &utmRef = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32U PD 99990 99990")); + qDebug() << "utmRef = " << utmRef.mgrs(); + + { + LtsCoordTrans::UtmCoordinate utmRaw; + utmRaw.x = utmRef.x + offset.x(); + utmRaw.y = utmRef.y + offset.y(); + utmRaw.zone = utmRef.zone; + qDebug() << "offset = " << offset << " - utmRaw = " << utmRaw.mgrs(); + + QCOMPARE(utmRaw.mgrs(), correctMgrs); + } + + + + + { + const LtsCoordTrans::UtmCoordinate utmSimple(utmRef.zone, + utmRef.x + offset.x(), + utmRef.y + offset.y()); + + qDebug() << "offset = " << offset << " - utmSimple = " << utmSimple.mgrs(); + QCOMPARE(utmSimple.mgrs(), correctMgrs); + } + +} + +QRectF createArea(const QString &base, + int xCenter, int yCenter, int width, int height) +{ + + QRectF areaRect = QRectF(xCenter - width / 2, yCenter - height / 2, width, height); + LtsCoordTrans::UtmCoordinate utmBase(LtsCoordTrans::UtmCoordinate::fromGeographic(LtsCoordTrans::geographicFromMgrs(base))); + const QPointF topLeft(utmBase.geographic(areaRect.left(), areaRect.top())); + const QPointF bottomRight(utmBase.geographic(areaRect.right(), areaRect.bottom())); + + + + QRectF rect (topLeft, bottomRight); + return rect; +} + + +void coordtrans_test::testAddMarginToArea() +{ + + { + qDebug() << "Altmark"; + QRectF area = createArea(QStringLiteral("32U PD 0000000000"), 50000, 10000, 30000, 30000); + QRectF area1Increased = LtsCoordTrans::addMarginToArea(area, QSize(5000, 20000)); + + qDebug() << "topLeft : " << qSetRealNumberPrecision(15) << area.topLeft() << " - degree:" << area.topLeft()*LtsCoordTrans::rad2deg(); + qDebug() << "topLeftInc : " << qSetRealNumberPrecision(15) << area1Increased.topLeft() << " - degree:" << area1Increased.topLeft()*LtsCoordTrans::rad2deg(); + + qDebug() << "bottomRight : " << qSetRealNumberPrecision(15) << area.bottomRight() << " - degree:" << area.bottomRight()*LtsCoordTrans::rad2deg(); + qDebug() << "bottomRightInc : " << qSetRealNumberPrecision(15) << area1Increased.bottomRight() << " - degree:" << area1Increased.bottomRight()*LtsCoordTrans::rad2deg(); + + QVERIFY2( area1Increased.topLeft().x() < area.topLeft().x(), "addMarginToArea topLeft X failed"); + QVERIFY2( area1Increased.topLeft().y() < area.topLeft().y(), "addMarginToArea topLeft Y failed"); + + QVERIFY2( area1Increased.bottomRight().x() > area.bottomRight().x(), "addMarginToArea bottomRight X failed"); + QVERIFY2( area1Increased.bottomRight().y() > area.bottomRight().y(), "addMarginToArea bottomRight Y failed"); + } + + + { + qDebug() << "Luxemburg"; + QRectF area = createArea(QStringLiteral("32U LA 0000000000"), 100, 100, 5000, 5000); + QRectF area1Increased = LtsCoordTrans::addMarginToArea(area, QSize(50000, 20000)); + + qDebug() << "topLeft : " << qSetRealNumberPrecision(15) << area.topLeft() << " - degree:" << area.topLeft()*LtsCoordTrans::rad2deg(); + qDebug() << "topLeftInc : " << qSetRealNumberPrecision(15) << area1Increased.topLeft() << " - degree:" << area1Increased.topLeft()*LtsCoordTrans::rad2deg(); + + qDebug() << "bottomRight : " << qSetRealNumberPrecision(15) << area.bottomRight() << " - degree:" << area.bottomRight()*LtsCoordTrans::rad2deg(); + qDebug() << "bottomRightInc : " << qSetRealNumberPrecision(15) << area1Increased.bottomRight() << " - degree:" << area1Increased.bottomRight()*LtsCoordTrans::rad2deg(); + + QVERIFY2( area1Increased.topLeft().x() < area.topLeft().x(), "addMarginToArea topLeft X failed"); + QVERIFY2( area1Increased.topLeft().y() < area.topLeft().y(), "addMarginToArea topLeft Y failed"); + + QVERIFY2( area1Increased.bottomRight().x() > area.bottomRight().x(), "addMarginToArea bottomRight X failed"); + QVERIFY2( area1Increased.bottomRight().y() > area.bottomRight().y(), "addMarginToArea bottomRight Y failed"); + } +} + +void coordtrans_test::testIncreaseRect() +{ + + QPointF lonlat = QPointF(8.6191049344, 53.1048309844) * LtsCoordTrans::deg2rad(); + QString mgrs = LtsCoordTrans::mgrsFromGeographic(lonlat.x(), lonlat.y(), 9); + QCOMPARE(mgrs, QStringLiteral("32U MD 745000000 840000000")); + + const LtsCoordTrans::UtmCoordinate centerPoint = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y()); + + LtsCoordTrans::UtmCoordinate topLeftUtm; + LtsCoordTrans::UtmCoordinate topLeftUtm2; + + + { + QPointF topLeftGeo = centerPoint.geographic(-1111, 2222); + QPointF bottomRightGeo = centerPoint.geographic( 3000, -4000); + + topLeftUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(topLeftGeo); + const LtsCoordTrans::UtmCoordinate bottomRightUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(bottomRightGeo); + qDebug() << "topLeftUtm: " << topLeftUtm.x << " - " << qSetRealNumberPrecision(15) << topLeftUtm.y; + qDebug() << "bottomRightUtm: " << bottomRightUtm.x << " - " << qSetRealNumberPrecision(15) << bottomRightUtm.y; + } + + + { + topLeftUtm2.x = centerPoint.x - 1111; + topLeftUtm2.y = centerPoint.y + 2222; + + LtsCoordTrans::UtmCoordinate bottomRightUtm2; + bottomRightUtm2.x = centerPoint.x + 3000; + bottomRightUtm2.y = centerPoint.y - 4000; + + qDebug() << "topLeftUtm2: " << topLeftUtm2.x << " - " << qSetRealNumberPrecision(15) << topLeftUtm2.y; + qDebug() << "bottomRightUtm2: " << bottomRightUtm2.x << " - " << qSetRealNumberPrecision(15) << bottomRightUtm2.y; + } + + QVERIFY2(qFuzzyCompare(topLeftUtm.x, topLeftUtm2.x), "testIncreaseRect x failed"); + QVERIFY2(qFuzzyCompare(topLeftUtm.y, topLeftUtm2.y), "testIncreaseRect y failed"); +} + +void coordtrans_test::testIncreaseRect2() +{ + + QPointF lonlat = QPointF(8.6191049344, 53.1048309844) * LtsCoordTrans::deg2rad(); + QString mgrs = LtsCoordTrans::mgrsFromGeographic(lonlat.x(), lonlat.y(), 9); + QCOMPARE(mgrs, QStringLiteral("32U MD 745000000 840000000")); + + QRectF myRect; + QRectF myRectIncreased; + + { + const LtsCoordTrans::UtmCoordinate centerPoint = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y()); + + QPointF topLeftGeo = centerPoint.geographic(-3000, 2000); + QPointF bottomRightGeo = centerPoint.geographic( 3000, -2000); + myRect = QRectF(topLeftGeo, bottomRightGeo).normalized(); + + qDebug() << "myRect : " << myRect; + qDebug() << "myRect center : " << myRect.center(); + qDebug() << "myRect height : " << myRect.height(); + } + + + { + const QPointF centerGeo = myRect.center(); + const LtsCoordTrans::UtmCoordinate centerUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(centerGeo); + + QPointF topLeftGeo = centerUtm.geographic(-6000, 4000); + QPointF bottomRightGeo = centerUtm.geographic( 6000, -4000); + + myRectIncreased = QRectF(topLeftGeo, bottomRightGeo).normalized(); + + qDebug() << "myRectIncreased : " << myRectIncreased; + qDebug() << "myRectIncreased center: " << myRectIncreased.center(); + qDebug() << "myRectIncreased height: " << myRectIncreased.height(); + + } + + + QVERIFY2( (int)(myRectIncreased.center().x()*10000) == (int)(myRect.center().x()*10000), "testIncreaseRect2 centerX failed"); + QVERIFY2( (int)(myRectIncreased.center().y()*10000) == (int)(myRect.center().y()*10000), "testIncreaseRect2 centerY failed"); + + QVERIFY2(myRectIncreased.width() > myRect.width(), "testIncreaseRect2 width failed"); + QVERIFY2(myRectIncreased.height() > myRect.height(), "testIncreaseRect2 height failed"); + +} + + + +void coordtrans_test::testGeographicFromMgrs() +{ + + QString mgrs(QStringLiteral("32UPD7654398765")); + + + QPointF geographic = LtsCoordTrans::geographicFromMgrs(mgrs); + QCOMPARE(geographic, QPointF(11.6436815550, 53.2088411454) * LtsCoordTrans::deg2rad()); +} + +void coordtrans_test::testMgrsSquares() +{ + + + + + checkArea(QStringLiteral("32U PD 0000000000"), 50000, 10000, 30000, 30000, { QStringLiteral("32U PC"), + QStringLiteral("32U PD") + }); + checkArea(QStringLiteral("32U MD 0000000000"), 50000, 10000, 30000, 30000, { QStringLiteral("32U MC"), + QStringLiteral("32U MD") + }); + checkArea(QStringLiteral("32U MD 0000000000"), 89999, 89999, 21000, 21000, { QStringLiteral("32U MD"), + QStringLiteral("32U ME"), + QStringLiteral("32U ND"), + QStringLiteral("32U NE") + }); + + checkArea(QStringLiteral("39Q XG 0000000000"), 40000, 35000, 60000, 70000, { QStringLiteral("39Q XG"), + QStringLiteral("39R XG") + }); + checkArea(QStringLiteral("40R CP 0000000000"), 94000, 9000, 7000, 16000, { QStringLiteral("40R CP") }); + checkArea(QStringLiteral("40R DN 0000000000"), 500, 95500, 12000, 7000, { QStringLiteral("40R CN"), + QStringLiteral("40R DN") + }); + checkArea(QStringLiteral("40R DN 0000000000"), 500, 96760, 30000, 30000, { QStringLiteral("40R CN"), + QStringLiteral("40R CP"), + QStringLiteral("40R DN"), + QStringLiteral("40R DP") + }); + checkArea(QStringLiteral("40R BM 0000000000"), 59000, 59500, 46000, 40000, { QStringLiteral("40Q BM"), + QStringLiteral("40R BM") + }); + + + + checkArea(QStringLiteral("32U LA 0000000000"), 10000, 10000, 5000, 5000, { QStringLiteral("32U LA"), + }); + + + checkArea(QStringLiteral("32U LA 0000000000"), 100, 100, 60000, 60000, { QStringLiteral("31U GQ"), + QStringLiteral("31U GR"), + QStringLiteral("32U KA"), + QStringLiteral("32U KV"), + QStringLiteral("32U LA"), + QStringLiteral("32U LV") + }); + + checkArea(QStringLiteral("32U LA 0000000000"), 100, 100,150000,150000, { QStringLiteral("31U FQ"), + QStringLiteral("31U FR"), + QStringLiteral("31U GQ"), + QStringLiteral("31U GR"), + QStringLiteral("32U KA"), + QStringLiteral("32U KV"), + QStringLiteral("32U LA"), + QStringLiteral("32U LV") + }); + + checkArea(QStringLiteral("32U LA 0000000000"), 100, 100,200000,200000, { QStringLiteral("31U FP"), + QStringLiteral("31U FQ"), + QStringLiteral("31U FR"), + QStringLiteral("31U GP"), + QStringLiteral("31U GQ"), + QStringLiteral("31U GR"), + QStringLiteral("31U GS"), + QStringLiteral("32U KA"), + QStringLiteral("32U KB"), + QStringLiteral("32U KV"), + QStringLiteral("32U LA"), + QStringLiteral("32U LB"), + QStringLiteral("32U LV"), + QStringLiteral("32U MA"), + QStringLiteral("32U MB"), + QStringLiteral("32U MV") + }); + +} + + +QVector checkAreaGetMgrsSquares(const QString &base, + int xCenter, int yCenter, int width, int height) +{ + + QRectF areaRect = QRectF(xCenter - width / 2, yCenter - height / 2, width, height); + LtsCoordTrans::UtmCoordinate utmBase(LtsCoordTrans::UtmCoordinate::fromGeographic(LtsCoordTrans::geographicFromMgrs(base))); + const QPointF topLeft(utmBase.geographic(areaRect.left(), areaRect.top())); + const QPointF bottomRight(utmBase.geographic(areaRect.right(), areaRect.bottom())); + + + + const auto squares = LtsCoordTrans::getMgrsSquares(QRectF(topLeft, bottomRight), false, false); + + QStringList src; + src.reserve(squares.size()); + for (const auto &utm : squares) + src += utm.mgrs(0); + src.sort(); + + + return squares; +} + +void coordtrans_test::testMgrsSquaresWithSurrounding() +{ + + + + + + + + + + + + + + + + + int initialSize = 5000; + + + QVector squares1 = checkAreaGetMgrsSquares(QStringLiteral("32U NC 0000000000"), 50000, 50000, initialSize, initialSize); + + QVERIFY2(squares1.size() == 1, "checkAreaGetMgrsSquares failed"); + + + + + int biggerSize = initialSize + (1*110000); + QVector squares9 = checkAreaGetMgrsSquares(QStringLiteral("32U NC 0000000000"), 50000, 50000, biggerSize, biggerSize); + + QVERIFY2(squares9.size() == 9, "checkAreaGetMgrsSquares biggerSize failed"); + + int muchBiggerSize = initialSize + (3*110000); + QVector square33 = checkAreaGetMgrsSquares(QStringLiteral("32U NC 0000000000"), 50000, 50000, muchBiggerSize, muchBiggerSize); + + QVERIFY2(square33.size() == 33, "checkAreaGetMgrsSquares muchBiggerSize failed"); + + + QStringList string1; + string1.reserve(squares1.size()); + for (const LtsCoordTrans::UtmCoordinate &item : qAsConst(squares1)) + string1.append(item.mgrs(0)); + + QStringList string9; + string9.reserve(squares9.size()); + for (const LtsCoordTrans::UtmCoordinate &item : qAsConst(squares9)) + string9.append(item.mgrs(0)); + + QStringList string33; + string33.reserve(square33.size()); + for (const LtsCoordTrans::UtmCoordinate &item : qAsConst(square33)) + string33.append(item.mgrs(0)); + + { + QStringList surroundingStrings; + surroundingStrings.reserve(squares9.size()); + for (const QString &str : qAsConst(string9)) + if (!string1.contains(str)) + surroundingStrings.append(str); + + QVERIFY2(surroundingStrings.size() == 8, "checkAreaGetMgrsSquares squaresDiff failed"); + } + + { + QStringList surroundingStrings; + surroundingStrings.reserve(string33.size()); + for (const QString &str : qAsConst(string33)) + if (!string1.contains(str)) + surroundingStrings.append(str); + + QVERIFY2(surroundingStrings.size() == 32, "checkAreaGetMgrsSquares squaresDiff failed"); + } + +} + +void coordtrans_test::checkArea(const QString &base, + int xCenter, int yCenter, int width, int height, + const QStringList &coords) +{ + qDebug() << "checkArea : base: " << base; + QRectF areaRect = QRectF(xCenter - width / 2, yCenter - height / 2, width, height); + LtsCoordTrans::UtmCoordinate utmBase(LtsCoordTrans::UtmCoordinate::fromGeographic(LtsCoordTrans::geographicFromMgrs(base))); + const QPointF topLeft(utmBase.geographic(areaRect.left(), areaRect.top())); + const QPointF bottomRight(utmBase.geographic(areaRect.right(), areaRect.bottom())); + qDebug() << "TopLeft: " << topLeft * LtsCoordTrans::rad2deg(); + qDebug() << "BottomRight: " << bottomRight * LtsCoordTrans::rad2deg(); + + const auto squares = LtsCoordTrans::getMgrsSquares(QRectF(topLeft, bottomRight), false, false); + + QStringList src; + src.reserve(squares.size()); + for (const auto &utm : squares) + src += utm.mgrs(0); + src.sort(); + qDebug() << "src: '" << src << "' expected: '" << coords << "'"; + QCOMPARE(src, coords); +} + + diff --git a/Lts/Common/LtsCoordTrans/lts_coord_trans_unit_test.h b/Lts/Common/LtsCoordTrans/lts_coord_trans_unit_test.h new file mode 100755 index 000000000..9bc7183d2 --- /dev/null +++ b/Lts/Common/LtsCoordTrans/lts_coord_trans_unit_test.h @@ -0,0 +1,34 @@ + + +#pragma once + +#include +#include + +class coordtrans_test : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void testFromGeographic(); + void testMgrsFromGeographic(); + + void testRelativeMetricalOffet_data(); + void testRelativeMetricalOffet(); + + void testAddMarginToArea(); + void testIncreaseRect(); + void testIncreaseRect2(); + + void testGeographicFromMgrs(); + void testMgrsSquares(); + + void testMgrsSquaresWithSurrounding(); + +private: + void checkArea(const QString &base, + int xCenter, int yCenter, int width, int height, + const QStringList &coords); + +}; diff --git a/Lts/Common/LtsCoordTrans/lts_coord_widget.cpp b/Lts/Common/LtsCoordTrans/lts_coord_widget.cpp new file mode 100755 index 000000000..c64012c0f --- /dev/null +++ b/Lts/Common/LtsCoordTrans/lts_coord_widget.cpp @@ -0,0 +1,196 @@ + + + +#include "lts_coord_widget.h" +#include "lts_coord_widget_p.h" + +#include +#include + +namespace LtsCoordWidget +{ + +LtsCoordWidget::LtsCoordWidget(DisplayType dt, QWidget *parent) + : QWidget(parent) + , m_displayType(dt) +{ + setFocusPolicy(Qt::StrongFocus); + rebuildUi(); +} + +void LtsCoordWidget::setCoordinate(const QPointF &geoCoord, bool bUseAsDefaultValue) +{ + if (m_coordinate != geoCoord) + emit coordinateChanged(geoCoord); + if (bUseAsDefaultValue) + m_defaultCoordinate = geoCoord; + if (m_inputWidgetHandler) + m_inputWidgetHandler->setCoordinate(geoCoord); +} + +void LtsCoordWidget::clear() +{ + if (m_inputWidgetHandler) + m_inputWidgetHandler->clear(); +} + +void LtsCoordWidget::setCoordinate(double lon, double lat, bool bUseAsDefaultValue) +{ + setCoordinate(QPointF(lon, lat), bUseAsDefaultValue); +} + +QPointF LtsCoordWidget::coordinate() const +{ + if (!m_inputWidgetHandler) + return QPointF(); + return m_inputWidgetHandler->coordinate(); +} + + +LtsCoordWidget::DisplayType LtsCoordWidget::displayType() const +{ + return m_displayType; +} + +void LtsCoordWidget::setDisplayType(LtsCoordWidget::DisplayType dt) +{ + if (m_displayType == dt) + return; + m_displayType = dt; + rebuildUi(); +} + +bool LtsCoordWidget::withResetButton() const +{ + return m_bWithResetButton; +} + +void LtsCoordWidget::setWithResetButton(bool bWith) +{ + if (m_bWithResetButton == bWith) + return; + m_bWithResetButton = bWith; + if (m_inputWidgetHandler) + m_inputWidgetHandler->resetButton()->setVisible(m_bWithResetButton); +} + +bool LtsCoordWidget::showToolTip() const +{ + return m_bShowToolTip; +} + +void LtsCoordWidget::setShowToolTip(bool bShow) +{ + if (m_bShowToolTip == bShow) + return; + m_bShowToolTip = bShow; + + updateToolTip(); +} + +bool LtsCoordWidget::mgrsSquareEditable() const +{ + return m_bMgrsSquareEditable; +} + +void LtsCoordWidget::setMgrsSquareEditable(bool bEditable) +{ + if (m_bMgrsSquareEditable == bEditable) + return; + m_bMgrsSquareEditable = bEditable; + if (m_inputWidgetHandler) + m_inputWidgetHandler->setSquaresEditable(bEditable); +} + +void LtsCoordWidget::setWithSurroundingMgrsSquares(bool bEnable) +{ + if (m_inputWidgetHandler) + m_inputWidgetHandler->setWithSurroundingSquare(bEnable); +} + +void LtsCoordWidget::setMgrsSquares(const QVector &geoCoords) +{ + if (m_inputWidgetHandler) + m_inputWidgetHandler->setMgrsSquares(geoCoords); +} + + +bool LtsCoordWidget::event(QEvent *event) +{ + if (event->type() == QEvent::ToolTip) + updateToolTip(); + return QWidget::event(event); +} + + +void LtsCoordWidget::rebuildUi() +{ + + + + + if (m_inputWidgetHandler) + { + m_inputWidgetHandler->cleanup(); + delete m_inputWidgetHandler; + delete layout(); + } + + switch (m_displayType) + { + case DisplayType::MGRS5: + m_inputWidgetHandler = new MgrsEdit(5, this); + break; + case DisplayType::MGRS7: + m_inputWidgetHandler = new MgrsEdit(7, this); + break; + case DisplayType::UTM: + + qt_assert("DisplayType::UTM not yet implemented", __FILE__, __LINE__); + break; + case DisplayType::WGS84: + m_inputWidgetHandler = new Wgs84Edit(this); + break; + } + if (!m_inputWidgetHandler) + return; + + m_inputWidgetHandler->setSquaresEditable(m_bMgrsSquareEditable); + m_inputWidgetHandler->resetButton()->setVisible(m_bWithResetButton); + connect(m_inputWidgetHandler->resetButton(), &QToolButton::clicked, + this, &LtsCoordWidget::onResetToDefaultCoordinate); + connect(m_inputWidgetHandler, &LtsCoordInputWidgetHandler::coordinateChanged, + this, &LtsCoordWidget::coordinateChanged); + connect(m_inputWidgetHandler, &LtsCoordInputWidgetHandler::invalidCoordinate, + this, &LtsCoordWidget::invalidCoordinate); + if (!m_defaultCoordinate.isNull()) + m_inputWidgetHandler->setCoordinate(m_defaultCoordinate); +} + +void LtsCoordWidget::updateToolTip() +{ + QString tt; + if (m_bShowToolTip) + { + const QPointF p(coordinate()); + if (!p.isNull()) + { + const QString mgrs = LtsCoordTrans::mgrsFromGeographic(p.x(), p.y()); + const QPointF p2(p * LtsCoordTrans::rad2deg()); + tt = tr("MGRS: %1\nlat/lon: %2/%3").arg(mgrs).arg(p2.y(), 0, 'f', 10).arg(p2.x(), 0, 'f', 10); + } + } + setToolTip(tt); +} + + +void LtsCoordWidget::onResetToDefaultCoordinate() +{ + if (!m_inputWidgetHandler) + return; + if (m_defaultCoordinate.isNull()) + m_inputWidgetHandler->clear(); + m_inputWidgetHandler->setCoordinate(m_defaultCoordinate); +} + +} diff --git a/Lts/Common/LtsCoordTrans/lts_coord_widget.h b/Lts/Common/LtsCoordTrans/lts_coord_widget.h new file mode 100755 index 000000000..92c2f1331 --- /dev/null +++ b/Lts/Common/LtsCoordTrans/lts_coord_widget.h @@ -0,0 +1,78 @@ + + +#pragma once + +#include +#include +#include + +class QToolButton; + +namespace LtsCoordWidget +{ +class LtsCoordInputWidgetHandler; +class LTSCOORDWIDGET_EXPORT LtsCoordWidget : public QWidget +{ + Q_OBJECT +public: + enum class DisplayType + { + MGRS5, + MGRS7, + UTM, + WGS84, + MGRS = DisplayType::MGRS5 + }; + Q_ENUM(DisplayType) + Q_PROPERTY(DisplayType displayType READ displayType WRITE setDisplayType) + Q_PROPERTY(bool withResetButton READ withResetButton WRITE setWithResetButton) + Q_PROPERTY(bool showToolTip READ showToolTip WRITE setShowToolTip) + Q_PROPERTY(bool mgrsSquareEditable READ mgrsSquareEditable WRITE setMgrsSquareEditable) + + + + explicit LtsCoordWidget(QWidget *parent = nullptr) + : LtsCoordWidget(LtsCoordWidget::DisplayType::MGRS, parent) + {} + + + + explicit LtsCoordWidget(DisplayType dt, QWidget *parent = nullptr); + + QPointF coordinate() const; + DisplayType displayType() const; + bool withResetButton() const; + bool showToolTip() const; + bool mgrsSquareEditable() const; + bool event(QEvent *event) override; +public Q_SLOTS: + + + void setCoordinate(const QPointF &geoCoord, bool bUseAsDefaultValue = true); + void setCoordinate(double lon, double lat, bool bUseAsDefaultValue = true); + void setDisplayType(LtsCoordWidget::DisplayType dt); + void setWithResetButton(bool bWith); + void setShowToolTip(bool bShow); + void setMgrsSquareEditable(bool bEditable); + void setWithSurroundingMgrsSquares(bool bEnable); + void setMgrsSquares(const QVector &geoCoords); + void clear(); + +Q_SIGNALS: + void coordinateChanged(const QPointF &newGeoCoord); + void invalidCoordinate(); +private: + void rebuildUi(); + void updateToolTip(); +private Q_SLOTS: + void onResetToDefaultCoordinate(); +private: + bool m_bWithResetButton = false; + bool m_bShowToolTip = true; + bool m_bMgrsSquareEditable = false; + LtsCoordWidget::DisplayType m_displayType = LtsCoordWidget::DisplayType::MGRS; + LtsCoordInputWidgetHandler *m_inputWidgetHandler = nullptr; + QPointF m_coordinate; + QPointF m_defaultCoordinate; +}; +} diff --git a/Lts/Common/LtsCoordTrans/lts_coord_widget_mgrs.ui b/Lts/Common/LtsCoordTrans/lts_coord_widget_mgrs.ui new file mode 100755 index 000000000..1ad1ddc85 --- /dev/null +++ b/Lts/Common/LtsCoordTrans/lts_coord_widget_mgrs.ui @@ -0,0 +1,55 @@ + + + Mgrs + + + + 0 + 0 + 322 + 32 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Pos X + + + + + + + Pos Y + + + + + + + + :/style/arrow_back.svg:/style/arrow_back.svg + + + + + + + + diff --git a/Lts/Common/LtsCoordTrans/lts_coord_widget_p.cpp b/Lts/Common/LtsCoordTrans/lts_coord_widget_p.cpp new file mode 100755 index 000000000..57011cfc5 --- /dev/null +++ b/Lts/Common/LtsCoordTrans/lts_coord_widget_p.cpp @@ -0,0 +1,325 @@ + + + +#include "lts_coord_widget_p.h" + + +namespace LtsCoordWidget +{ + + + +void LtsCoordInputWidgetHandler::onCoordinateDataChanged() +{ + const QPointF coord(coordinate()); + if (coord.isNull()) + { + emit invalidCoordinate(); + return; + } + emit coordinateChanged(coord); +} + + + + +MgrsEdit::MgrsEdit(int precision, QWidget *widget) + : LtsCoordInputWidgetHandler(widget) + , m_precision(precision) + , m_ui(new Ui::Mgrs) +{ + m_ui->setupUi(widget); + + if (m_precision < 0) + m_precision = 0; + if (m_precision > 11) + m_precision = 11; + const int upperLimit = pow(10, m_precision) - 1; + m_ui->leEast->setValidator(new QIntValidator(0, upperLimit, m_ui->leEast)); + m_ui->leNorth->setValidator(new QIntValidator(0, upperLimit, m_ui->leNorth)); + setDefaultMgrsSquares(); + + connect(m_ui->cbxZone, QOverload::of(&QComboBox::currentIndexChanged), + this, &MgrsEdit::onCoordinateDataChanged); + connect(m_ui->leEast, &QLineEdit::editingFinished, + this, &MgrsEdit::onCoordinateDataChanged); + connect(m_ui->leNorth, &QLineEdit::editingFinished, + this, &MgrsEdit::onCoordinateDataChanged); +} + +MgrsEdit::~MgrsEdit() +{ + delete m_ui; +} + +bool MgrsEdit::isValid() +{ + + const QString &east = m_ui->leEast->text(); + if (east.size() != m_precision) + return false; + + const QString &north = m_ui->leNorth->text(); + if (north.size() != m_precision) + return false; + + if (m_ui->cbxZone->currentIndex() < 0) + return false; + + if (m_ui->cbxZone->currentText().isEmpty()) + return false; + + return true; +} + +void MgrsEdit::setCoordinate(const QPointF &geoCoord) +{ + const QString mgrsString = LtsCoordTrans::mgrsFromGeographic(geoCoord.x(), geoCoord.y(), m_precision, false); + if (mgrsString.isEmpty()) + { + m_ui->leEast->clear(); + m_ui->leNorth->clear(); + return; + } + + m_ui->leEast->setText(QString()); + m_ui->leNorth->setText(QString()); + + + const QString mgrsSquare = mgrsString.left(3) + QLatin1Char(' ') + mgrsString.mid(3, 2); + QString east = mgrsString.mid(5, m_precision); + QString north = mgrsString.mid(5 + m_precision, m_precision); + int idx = m_ui->cbxZone->findText(mgrsSquare); + if (idx < 0) + { + addMgrsSquare(mgrsSquare); + idx = m_ui->cbxZone->findText(mgrsSquare); + } + m_ui->cbxZone->setCurrentIndex(idx); + m_ui->leEast->setText(east); + m_ui->leNorth->setText(north); +} + +static inline QString getMgrsCoord(QLineEdit *le, int prec) +{ + QString ret = le->text(); + if (ret.isEmpty()) + return ret; + while (ret.size() < prec) + ret += QLatin1Char('0'); + le->setText(ret); + return ret; +} + +QPointF MgrsEdit::coordinate() const +{ + const QString east = getMgrsCoord(m_ui->leEast, m_precision); + const QString north = getMgrsCoord(m_ui->leNorth, m_precision); + if (east.isEmpty() || north.isEmpty() || m_ui->cbxZone->currentIndex() < 0) + return QPointF(); + + const QString mgrsString(m_ui->cbxZone->currentText() + east + north); + return LtsCoordTrans::geographicFromMgrs(mgrsString); +} + +QToolButton *MgrsEdit::resetButton() +{ + return m_ui->tbReset; +} + +void MgrsEdit::cleanup() +{ + + + delete m_ui->horizontalLayout; + delete m_ui->cbxZone; + delete m_ui->leEast; + delete m_ui->leNorth; + delete m_ui->tbReset; +} + +void MgrsEdit::clear() +{ + m_ui->cbxZone->clear(); + m_ui->leEast->clear(); + m_ui->leNorth->clear(); + setDefaultMgrsSquares(); +} + +void MgrsEdit::setSquaresEditable(bool bEditable) +{ + m_ui->cbxZone->setEditable(bEditable); +} + +void MgrsEdit::setWithSurroundingSquare(bool bEnabled) +{ + m_withMgrsSquareOffset = bEnabled; +} + +static QStringList defaultMgrsSquares() +{ + const QVector squares(LtsCoordTrans::defaultMgrsSquares()); + QStringList sl; + sl.reserve(squares.size()); + for (const LtsCoordTrans::UtmCoordinate &c : squares) + sl += c.mgrs(0); + std::sort(sl.begin(), sl.end()); + return sl; +} + +static QString preferedMgrsSquare() +{ + const LtsCoordTrans::UtmCoordinate c(LtsCoordTrans::preferedMgrsSquare()); + return c.mgrs(0); +} + + +void MgrsEdit::setDefaultMgrsSquares() +{ + m_ui->cbxZone->addItems(defaultMgrsSquares()); + m_ui->cbxZone->setCurrentIndex(m_ui->cbxZone->findText(preferedMgrsSquare())); +} + +void MgrsEdit::addMgrsSquare(const QString &newMgrsSquare) +{ + QStringList sl = defaultMgrsSquares(); + sl += newMgrsSquare; + if (m_withMgrsSquareOffset) + { + QStringList squares = utmCoordsToList(LtsCoordTrans::defaultMgrsSquareWithOffsets()); + + for (const auto &sq : qAsConst(squares)) + if (!sl.contains(sq)) + sl.append(sq); + } + std::sort(sl.begin(), sl.end()); + m_ui->cbxZone->clear(); + m_ui->cbxZone->addItems(sl); +} + +QStringList MgrsEdit::utmCoordsToList(const QVector &coords) +{ + QStringList list; + list.reserve(coords.count()); + for (const auto &utm : coords) + list.append(utm.mgrs(0)); + + return list; +} + +void MgrsEdit::setMgrsSquares(const QVector &geoCoords) +{ + QStringList sl = utmCoordsToList(geoCoords); + if (sl.isEmpty()) + return; + + std::sort(sl.begin(), sl.end()); + m_ui->cbxZone->clear(); + m_ui->cbxZone->addItems(sl); +} + + + + +Wgs84Edit::Wgs84Edit(QWidget *widget) + : LtsCoordInputWidgetHandler(widget) + , m_ui(new Ui::Wgs84) +{ + m_ui->setupUi(widget); + + DoubleValidator *v = new DoubleValidator(-180, 180, 9, m_ui->leLon); + v->setNotation(QDoubleValidator::StandardNotation); + m_ui->leLon->setValidator(v); + + v = new DoubleValidator(-180, 180, 9, m_ui->leLat); + v->setNotation(QDoubleValidator::StandardNotation); + m_ui->leLat->setValidator(v); + + connect(m_ui->leLon, &QLineEdit::editingFinished, + this, &Wgs84Edit::onCoordinateDataChanged); + connect(m_ui->leLat, &QLineEdit::editingFinished, + this, &Wgs84Edit::onCoordinateDataChanged); +} + +Wgs84Edit::~Wgs84Edit() +{ + delete m_ui; +} + +bool Wgs84Edit::isValid() +{ + + return (!m_ui->leLon->text().isEmpty()) && (!m_ui->leLat->text().isEmpty()); +} + +void Wgs84Edit::setCoordinate(const QPointF &geoCoord) +{ + const QPointF coord(geoCoord * LtsCoordTrans::rad2deg()); + const QLocale l; + m_ui->leLon->setText(l.toString(coord.x(), 'f', 9)); + m_ui->leLat->setText(l.toString(coord.y(), 'f', 9)); +} + +QPointF Wgs84Edit::coordinate() const +{ + QPointF ret; + const QLocale l; + + + const QString lonStr = m_ui->leLon->text().remove(l.groupSeparator()); + const QString latStr = m_ui->leLat->text().remove(l.groupSeparator()); + if (lonStr.isEmpty() || latStr.isEmpty()) + return ret; + + ret.rx() = l.toDouble(lonStr); + ret.ry() = l.toDouble(latStr); + + return ret * LtsCoordTrans::deg2rad(); +} + +QToolButton *Wgs84Edit::resetButton() +{ + return m_ui->tbReset; +} + +void Wgs84Edit::cleanup() +{ + + + delete m_ui->horizontalLayout; + delete m_ui->leLon; + delete m_ui->leLat; + delete m_ui->tbReset; + delete m_ui->lblLon; + delete m_ui->lblLat; +} + +void Wgs84Edit::clear() +{ + m_ui->leLon->clear(); + m_ui->leLat->clear(); +} + + + + +DoubleValidator::DoubleValidator(double bottom, double top, int decimals, QObject *parent) + : QDoubleValidator(bottom, top, decimals, parent) +{} + +QValidator::State DoubleValidator::validate(QString &input, int &pos) const +{ + const QValidator::State ret = QDoubleValidator::validate(input, pos); + if (ret != QValidator::Acceptable) + return ret; + QLocale l; + const QChar gs(l.groupSeparator()); + if (gs.isNull()) + return ret; + if (input.contains(gs)) + return QValidator::Invalid; + return ret; +} + +} + diff --git a/Lts/Common/LtsCoordTrans/lts_coord_widget_p.h b/Lts/Common/LtsCoordTrans/lts_coord_widget_p.h new file mode 100755 index 000000000..93553568a --- /dev/null +++ b/Lts/Common/LtsCoordTrans/lts_coord_widget_p.h @@ -0,0 +1,93 @@ + + +#pragma once + +#include +#include +#include +#include + +#include "ui_lts_coord_widget_mgrs.h" +#include "ui_lts_coord_widget_wgs84.h" + +namespace LtsCoordWidget +{ +class LtsCoordInputWidgetHandler : public QObject +{ + Q_OBJECT +public: + explicit LtsCoordInputWidgetHandler(QObject *parent) + : QObject(parent) + {} + + virtual bool isValid() = 0; + virtual void setCoordinate(const QPointF &geoCoord) = 0; + virtual QPointF coordinate() const = 0; + virtual QToolButton *resetButton() = 0; + virtual void cleanup() = 0; + virtual void clear() = 0; + virtual void setSquaresEditable(bool bEditable) = 0; + virtual void setWithSurroundingSquare(bool bEnabled) = 0; + virtual void setMgrsSquares(const QVector &geoCoords) = 0; +protected Q_SLOTS: + void onCoordinateDataChanged(); +Q_SIGNALS: + void coordinateChanged(const QPointF &newGeoCoord); + void invalidCoordinate(); +}; + +class MgrsEdit final : public LtsCoordInputWidgetHandler +{ + Q_OBJECT +public: + explicit MgrsEdit(int precision, QWidget *widget); + ~MgrsEdit(); + + bool isValid() override; + void setCoordinate(const QPointF &geoCoord) override; + QPointF coordinate() const override; + QToolButton *resetButton() override; + void cleanup() override; + void clear() override; + void setSquaresEditable(bool bEditable) override; + void setWithSurroundingSquare(bool bEnabled) override; + void setMgrsSquares(const QVector &geoCoords) override; +private: + void setDefaultMgrsSquares(); + void addMgrsSquare(const QString &newMgrsSquare); + QStringList utmCoordsToList(const QVector &coords); +private: + int m_precision; + bool m_withMgrsSquareOffset=false; + Ui::Mgrs *m_ui; +}; + +class Wgs84Edit final : public LtsCoordInputWidgetHandler +{ + Q_OBJECT +public: + explicit Wgs84Edit(QWidget *widget); + ~Wgs84Edit(); + + bool isValid() override; + void setCoordinate(const QPointF &geoCoord) override; + QPointF coordinate() const override; + QToolButton *resetButton() override; + void cleanup() override; + void clear() override; + void setSquaresEditable(bool bEditable) override { Q_UNUSED(bEditable); } + void setWithSurroundingSquare(bool bEnabled) override { Q_UNUSED(bEnabled); } + void setMgrsSquares(const QVector &geoCoords) override { Q_UNUSED(geoCoords); } +private: + Ui::Wgs84 *m_ui; +}; + +class DoubleValidator final : public QDoubleValidator +{ + Q_OBJECT +public: + DoubleValidator(double bottom, double top, int decimals, QObject *parent = nullptr); + + QValidator::State validate(QString &input, int &pos) const override; +}; +} diff --git a/Lts/Common/LtsCoordTrans/lts_coord_widget_test.cpp b/Lts/Common/LtsCoordTrans/lts_coord_widget_test.cpp new file mode 100755 index 000000000..e9ae4075f --- /dev/null +++ b/Lts/Common/LtsCoordTrans/lts_coord_widget_test.cpp @@ -0,0 +1,115 @@ + + + +#include +#include +#include +#include +#include +#include +#include "lts_coord_widget.h" + +static void updateLabel(const QPointF &coord, QLabel *lbl, int prec) +{ + QString mgrs = LtsCoordTrans::mgrsFromGeographic(coord.x(), coord.y(), prec); + QString str = QStringLiteral("mgrs: %1, lon: %2, lat: %3") + .arg(mgrs) + .arg(coord.x() * LtsCoordTrans::rad2deg(), 0, 'f', 10) + .arg(coord.y() * LtsCoordTrans::rad2deg(), 0, 'f', 10); + lbl->setText(str); +} + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + + LtsCoordTrans::UtmCoordinate base(LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32UMD00"))); + QPointF topLeft(base.geographic(+50000, -50000)); + QPointF bottomRight(base.geographic(-50000, 50000)); + LtsCoordTrans::getMgrsSquares(QRectF(topLeft, bottomRight), true, true); + + QDialog *dialog(new QDialog); + QVBoxLayout *lay(new QVBoxLayout(dialog)); + + + const QPointF defCoord(QPointF(53.0, 8.0) * LtsCoordTrans::deg2rad()); + + LtsCoordWidget::LtsCoordWidget *wMgrs5(new LtsCoordWidget::LtsCoordWidget(LtsCoordWidget::LtsCoordWidget::DisplayType::MGRS, dialog)); + wMgrs5->setWithResetButton(true); + wMgrs5->setCoordinate(defCoord, true); + lay->addWidget(wMgrs5); + QLabel *lbl5(new QLabel(dialog)); + lay->addWidget(lbl5); + QObject::connect(wMgrs5, &LtsCoordWidget::LtsCoordWidget::coordinateChanged, + lbl5, [lbl5](const QPointF & coord) + { + updateLabel(coord, lbl5, 5); + }); + + + LtsCoordWidget::LtsCoordWidget *wMgrs7(new LtsCoordWidget::LtsCoordWidget(LtsCoordWidget::LtsCoordWidget::DisplayType::MGRS7, dialog)); + wMgrs7->setWithResetButton(true); + wMgrs7->setCoordinate(defCoord, true); + lay->addWidget(wMgrs7); + QLabel *lbl7(new QLabel(dialog)); + lay->addWidget(lbl7); + QObject::connect(wMgrs7, &LtsCoordWidget::LtsCoordWidget::coordinateChanged, + wMgrs7, [lbl7](const QPointF & coord) + { + updateLabel(coord, lbl7, 7); + }); + + + LtsCoordWidget::LtsCoordWidget *w84(new LtsCoordWidget::LtsCoordWidget(LtsCoordWidget::LtsCoordWidget::DisplayType::WGS84, dialog)); + w84->setWithResetButton(true); + w84->setCoordinate(defCoord, true); + lay->addWidget(w84); + QLabel *lbl84(new QLabel(dialog)); + lay->addWidget(lbl84); + QObject::connect(w84, &LtsCoordWidget::LtsCoordWidget::coordinateChanged, + w84, [lbl84](const QPointF & coord) + { + updateLabel(coord, lbl84, 11); + }); + + + QHBoxLayout *hLay = new QHBoxLayout; + lay->addLayout(hLay); + LtsCoordWidget::LtsCoordWidget *wChange(new LtsCoordWidget::LtsCoordWidget(LtsCoordWidget::LtsCoordWidget::DisplayType::WGS84, dialog)); + wChange->setWithResetButton(true); + wChange->setCoordinate(defCoord, true); + hLay->addWidget(wChange); + QPushButton *pbReset(new QPushButton(QStringLiteral("With Reset"), dialog)); + hLay->addWidget(pbReset); + QObject::connect(pbReset, &QPushButton::clicked, + wChange, [wChange]() { wChange->setWithResetButton(!wChange->withResetButton());}); + QPushButton *pbNext(new QPushButton(QStringLiteral("Next Type"), dialog)); + hLay->addWidget(pbNext); + QObject::connect(pbNext, &QPushButton::clicked, + wChange, [wChange]() + { + LtsCoordWidget::LtsCoordWidget::DisplayType dt = wChange->displayType(); + switch (dt) + { + case LtsCoordWidget::LtsCoordWidget::DisplayType::MGRS5: + dt = LtsCoordWidget::LtsCoordWidget::DisplayType::MGRS7; break; + case LtsCoordWidget::LtsCoordWidget::DisplayType::MGRS7: + dt = LtsCoordWidget::LtsCoordWidget::DisplayType::WGS84; break; + case LtsCoordWidget::LtsCoordWidget::DisplayType::WGS84: + dt = LtsCoordWidget::LtsCoordWidget::DisplayType::MGRS5; break; + default: + break; + } + wChange->setDisplayType(dt); + }); + + + QPushButton *pb(new QPushButton(QStringLiteral("Quit"), dialog)); + lay->addWidget(pb); + QObject::connect(pb, &QPushButton::clicked, qApp, QCoreApplication::quit); + + dialog->show(); + int ret = app.exec(); + delete dialog; + return ret; +} diff --git a/Lts/Common/LtsCoordTrans/lts_coord_widget_wgs84.ui b/Lts/Common/LtsCoordTrans/lts_coord_widget_wgs84.ui new file mode 100755 index 000000000..eaf643260 --- /dev/null +++ b/Lts/Common/LtsCoordTrans/lts_coord_widget_wgs84.ui @@ -0,0 +1,58 @@ + + + Wgs84 + + + + 0 + 0 + 293 + 32 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Lat: + + + + + + + + + + Lon: + + + + + + + + + + + icon:arrow_back.svgicon:arrow_back.svg + + + + + + + + diff --git a/Lts/Common/LtsCoordTrans/mgrs2lonlat.cpp b/Lts/Common/LtsCoordTrans/mgrs2lonlat.cpp new file mode 100755 index 000000000..b74e2b10f --- /dev/null +++ b/Lts/Common/LtsCoordTrans/mgrs2lonlat.cpp @@ -0,0 +1,45 @@ + + + +#include +#include +#include + +int main(int argc, char **argv) +{ + if (argc != 2) + { + fprintf(stderr, "Usage: %s \n", argv[0]); + return -1; + } + + std::string mgrs = argv[1]; + mgrs.erase(remove_if(mgrs.begin(), mgrs.end(), isspace), mgrs.end()); + + int prec; + double lonDeg, latDeg; + try + { + int zone; + bool northp; + double x, y; + GeographicLib::MGRS::Reverse(mgrs, zone, northp, x, y, prec, true); + GeographicLib::UTMUPS::Reverse(zone, northp, x, y, latDeg, lonDeg); + + GeographicLib::UTMUPS::Forward(latDeg, lonDeg, zone, northp, x, y); + GeographicLib::MGRS::Forward(zone, northp, x, y, prec, mgrs); + } + catch (...) + { + fprintf(stderr, "Error converting %s to Lon/Lat coords\n", mgrs.c_str()); + return -2; + } + std::string part1, part2, part3, part4; + part1 = mgrs.substr(0, 3); + part2 = mgrs.substr(3, 2); + part3 = mgrs.substr(5, prec); + part4 = mgrs.substr(5 + prec, prec); + fprintf(stdout, "%s %s %s %s = %13.10f, %13.10f ( %13.10f, %13.10f )\n", + part1.c_str(), part2.c_str(), part3.c_str(), part4.c_str(), lonDeg, latDeg, lonDeg / 180. * M_PI, latDeg / 180. * M_PI); + return 0; +} diff --git a/Lts/Common/LtsCoordinate/CMakeLists.txt b/Lts/Common/LtsCoordinate/CMakeLists.txt new file mode 100755 index 000000000..b2455253d --- /dev/null +++ b/Lts/Common/LtsCoordinate/CMakeLists.txt @@ -0,0 +1,26 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +NGL_ADD_LIBRARY2(WS SV HLA LtsCoordinate SHARED + lts_coordinate.h + lts_coordinate.cpp + lts_coord_vector.h + lts_coord_vector.cpp +) +NGL_TARGET_INCLUDE_DIRECTORIES(LtsCoordinate + INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) +NGL_TARGET_LINK_LIBRARIES(LtsCoordinate + PRIVATE + LtsCoordTrans + PUBLIC + ddscore + LtsUtilsCoreHeaderOnly + Qt5::Core +) + +ADD_SUBDIRECTORY(UnitTest_Coordinate) diff --git a/Lts/Common/LtsCoordinate/UnitTest_Coordinate/CMakeLists.txt b/Lts/Common/LtsCoordinate/UnitTest_Coordinate/CMakeLists.txt new file mode 100755 index 000000000..dc6f85433 --- /dev/null +++ b/Lts/Common/LtsCoordinate/UnitTest_Coordinate/CMakeLists.txt @@ -0,0 +1,30 @@ +# +# @RDE_HEADER@ +# $Id$ +# $HeadURL$ +# $Author$ +# + +SET(UnitTestSources + lts_coordinate_unit_test.cpp + lts_coordinate_unit_test.h +) + +IF(BUILD_TESTING) +ADD_EXECUTABLE(commonUnitTestCoordinate ${LinkedSources} ${UnitTestSources} ) + +TARGET_LINK_LIBRARIES(commonUnitTestCoordinate + NGLLogger + nglcoreapplication + LtsCoordinate + LtsElevation + LtsCoordTrans + situationMapCommon + situationMapElevationMap + Qt5::Core + Qt5::Gui + Qt5::Test) + +ADD_TEST(NAME commonUnitTestCoordinate COMMAND $) + +ENDIF() diff --git a/Lts/Common/LtsCoordinate/UnitTest_Coordinate/lts_coordinate_unit_test.cpp b/Lts/Common/LtsCoordinate/UnitTest_Coordinate/lts_coordinate_unit_test.cpp new file mode 100755 index 000000000..e5dc118ab --- /dev/null +++ b/Lts/Common/LtsCoordinate/UnitTest_Coordinate/lts_coordinate_unit_test.cpp @@ -0,0 +1,971 @@ + + + +#include "lts_coordinate_unit_test.h" + +#include +#include +#include +#include + +#include +#include +#include + + +Q_DECLARE_METATYPE(LtsUtils::QPoint3D); + + +#include +#include + +static const int s_id{4711}; +static LtsElevation::LtsElevationManager *s_eleMgr{nullptr}; + + + + + + + +class MyLtsElevationManagerBootstrap : public LtsElevation::LtsElevationManager +{ + using BaseClass = LtsElevation::LtsElevationManager; + +public: + static LtsElevationManager *createInstanceUnitTest(int id, const QStringList &filePaths_rough_to_fine) + { + return BaseClass::createInstanceUnitTest(id, filePaths_rough_to_fine); + } + +}; + + + + +UnitTestCoordinate::UnitTestCoordinate() + : m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("UnitTestCoordinate")) +{} + + +void UnitTestCoordinate::initTestCase() +{ + + + QString filePath(QStringLiteral("/usr/share/arcgis/resources/elevation/Altmark_dt1.tif")); + + s_eleMgr = MyLtsElevationManagerBootstrap::createInstanceUnitTest(s_id, {filePath}); + + const bool bIsValid = s_eleMgr->isValid(); + std::cerr << "myBootstrapMgr->isValid() 1 = " << bIsValid << std::endl; + + + { + s_eleMgr->waitForInitialization(); + const bool bIsValid = s_eleMgr->isValid(); + std::cerr << "myBootstrapMgr->isValid() 2 = " << bIsValid << std::endl; + } + + QVERIFY2(s_eleMgr , "static LtsElevationManager does not exist"); + + + + + using namespace std::placeholders; + LtsCoordTrans::LtsCoordinate::elevationCbFunc elevationCB = std::bind(&LtsElevation::LtsElevationManager::elevation, + s_eleMgr, _1, _2, _3, _4); + LtsCoordTrans::LtsCoordinate::setElevationCallback(elevationCB); + + QVERIFY2(LtsCoordTrans::LtsCoordinate::hasElevationCallback() , "setElevationCallback failed"); +} + +void UnitTestCoordinate::init() +{ + QVERIFY2(s_eleMgr, "static LtsElevationManager does not exist"); + QVERIFY2(LtsCoordTrans::LtsCoordinate::hasElevationCallback() , "setElevationCallback failed"); +} + +void UnitTestCoordinate::test_NaN() +{ + bool equal = qFuzzyCompare(std::nan(""), std::nan("")); + NOTICE_LOG(m_logger) << " qFuzzyCompare(NaN, NaN) = " << equal; + + + QVERIFY2(equal == false, "qFuzzyCompare(NaN, NaN) failed"); +} + +void UnitTestCoordinate::test_coordinate_simple1() +{ + LtsCoordTrans::LtsCoordinate ltsCoord; + + QVERIFY2(ltsCoord.isValid() == false, "isValid failed"); + + const Core::GeoPosition &geoPos = ltsCoord.geoPosition(); + NOTICE_LOG(m_logger) << " geoPosition = " << geoPos; + QVERIFY2(qFuzzyIsNull(geoPos.lon()) , "geoPosition failed"); + QVERIFY2(qFuzzyIsNull(geoPos.lat()) , "geoPosition failed"); + QVERIFY2(std::isnan( geoPos.z() ) , "geoPosition failed"); + + const QPointF &utm = ltsCoord.toUtmPointF(); + const QPointF &utm2 = ltsCoord.toUtm(); + const LtsCoordTrans::UtmCoordinate3D &utm3D = ltsCoord.toUtmCoordinate3D(); + NOTICE_LOG(m_logger) << " utmCoordinateAsPointF = " << utm; + NOTICE_LOG(m_logger) << " utmCoordinateAsPointF = " << utm2; + NOTICE_LOG(m_logger) << " UtmCoordinate3D = " << utm3D; + + QVERIFY2(qFuzzyCompare(utm3D.x, 166021.44308053952), "data no equal"); + QVERIFY2(qFuzzyCompare(utm3D.y, 0.0 ), "data no equal"); + QVERIFY2( utm3D.zone == 31 , "data no equal"); + QVERIFY2(utm == utm2 , "data no equal"); + + const Core::GeoPosition &geoPos2 = ltsCoord.geoPosition(); + NOTICE_LOG(m_logger) << " geoPosition2 = " << geoPos2; + QVERIFY2(qFuzzyIsNull(geoPos2.lon()) , "geoPosition2 failed"); + QVERIFY2(qFuzzyIsNull(geoPos2.lat()) , "geoPosition2 failed"); + QVERIFY2(std::isnan( geoPos2.z() ) , "geoPosition2 failed"); +} + +void UnitTestCoordinate::test_coordinate_simple2() +{ + Core::GeoPosition defaultGeoPos; + LtsCoordTrans::LtsCoordinate ltsCoord(defaultGeoPos); + + QVERIFY2(ltsCoord.isValid() == false, "isValid failed"); + + const Core::GeoPosition &geoPos = ltsCoord.geoPosition(); + NOTICE_LOG(m_logger) << " geoPosition = " << geoPos; + QVERIFY2(qFuzzyIsNull(geoPos.lon()) , "geoPosition failed"); + QVERIFY2(qFuzzyIsNull(geoPos.lat()) , "geoPosition failed"); + QVERIFY2(qFuzzyIsNull(geoPos.z() ) , "geoPosition failed"); + + const QPointF &utm = ltsCoord.toUtm(); + NOTICE_LOG(m_logger) << " utmCoordinateAsPointF = " << utm; + + + const Core::GeoPosition &geoPos2 = ltsCoord.geoPosition(); + NOTICE_LOG(m_logger) << " geoPosition2 = " << geoPos2; + QVERIFY2(qFuzzyIsNull(geoPos2.lon()) , "geoPosition2 failed"); + QVERIFY2(qFuzzyIsNull(geoPos2.lat()) , "geoPosition2 failed"); + QVERIFY2(qFuzzyIsNull(geoPos2.z() ) , "geoPosition2 failed"); +} + +void UnitTestCoordinate::test_coordinate_simple3() +{ + static const Core::GeoPosition s_invalidPos(std::nan(""), std::nan(""), std::nan("")); + + + LtsCoordTrans::LtsCoordinate ltsCoord(s_invalidPos); + + QVERIFY2(ltsCoord.isValid() == false, "isValid failed"); + + const Core::GeoPosition &geoPos = ltsCoord.geoPosition(); + NOTICE_LOG(m_logger) << " geoPosition = " << geoPos; + QVERIFY2(std::isnan(geoPos.lon()) , "geoPosition failed"); + QVERIFY2(std::isnan(geoPos.lat()) , "geoPosition failed"); + QVERIFY2(std::isnan(geoPos.z()) , "geoPosition failed"); + + const QPointF &utm = ltsCoord.toUtm(); + NOTICE_LOG(m_logger) << " utmCoordinateAsPointF = " << utm; + QVERIFY2(utm.isNull() , "utm failed"); +} + +void UnitTestCoordinate::test_coordinate_simple4() +{ + LtsCoordTrans::LtsCoordinate::resetConversionCounter(); + QVERIFY2(LtsCoordTrans::LtsCoordinate::getConversionCounter() == 0, "getConversionCounter failed"); + + const LtsCoordTrans::UtmCoordinate &utmIn = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000")); + const double zValue = 2.3; + + const LtsCoordTrans::LtsCoordinate ltsCoord(utmIn.geographic(), zValue); + + + + const LtsCoordTrans::UtmCoordinate &utmCoord_FirstTry = ltsCoord.utmCoordinate(); + Q_UNUSED(utmCoord_FirstTry); + QVERIFY2(LtsCoordTrans::LtsCoordinate::getConversionCounter() == 1, "getConversionCounter failed"); + + const LtsCoordTrans::UtmCoordinate &utmCoord_SecondTry = ltsCoord.utmCoordinate(); + Q_UNUSED(utmCoord_SecondTry); + + QVERIFY2(LtsCoordTrans::LtsCoordinate::getConversionCounter() == 1, "getConversionCounter failed"); +} + + + +void UnitTestCoordinate::test_coordinate_fromUtm() +{ + const LtsCoordTrans::UtmCoordinate &utmIn = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000")); + const double zValue = 2.3; + NOTICE_LOG(m_logger) << " utmIn = " << utmIn; + + + LtsCoordTrans::LtsCoordinate ltsCoord(utmIn.geographic(), zValue); + + const LtsCoordTrans::UtmCoordinate &utmCoord = ltsCoord.utmCoordinate(); + NOTICE_LOG(m_logger) << " utmCoord = " << utmCoord; + NOTICE_LOG(m_logger) << " utmCoord = " << utmCoord.mgrs(); + QVERIFY2(utmCoord.mgrs() == QStringLiteral("32U PD 70000 10000") , "utmCoordinate failed"); + + + const Core::GeoPosition &geoPos = ltsCoord.geoPosition(); + NOTICE_LOG(m_logger) << " geoPosition = " << geoPos; + + QVERIFY2(qFuzzyCompare(geoPos.lon(), 0.200706392814992), "geoPosition failed"); + QVERIFY2(qFuzzyCompare(geoPos.lat(), 0.914792952478694), "geoPosition failed"); + QVERIFY2(qFuzzyCompare(geoPos.z() , zValue ) , "geoPosition failed"); + + + const QPointF &utmP2D = ltsCoord.toUtm(); + NOTICE_LOG(m_logger) << " utmP2D = " << utmP2D; + QVERIFY2(qFuzzyCompare(utmP2D.x(), 670000.5), "utm1 failed"); + QVERIFY2(qFuzzyCompare(utmP2D.y(), 5810000.5), "utm2 failed"); + + const LtsUtils::QPoint3D &utmP3D = ltsCoord.toUtm(); + NOTICE_LOG(m_logger) << " utmP3D = " << utmP3D; + QVERIFY2(qFuzzyCompare(utmP3D.x(), 670000.5), "utm1 failed"); + QVERIFY2(qFuzzyCompare(utmP3D.y(), 5810000.5), "utm2 failed"); + QVERIFY2(qFuzzyCompare(utmP3D.z() , zValue ), "utm3 failed"); + + const LtsCoordTrans::UtmCoordinate3D &utmCoord3D = ltsCoord.toUtmCoordinate3D(); + NOTICE_LOG(m_logger) << " UtmCoordinate3D = " << utmCoord3D; + QVERIFY2(qFuzzyCompare(utmCoord3D.x, 670000.5), "utmCoord3D failed"); + QVERIFY2(qFuzzyCompare(utmCoord3D.y, 5810000.5), "utmCoord3D failed"); + QVERIFY2(qFuzzyCompare(utmCoord3D.z(), zValue ), "utmCoord3D failed"); + + + const LtsCoordTrans::UtmCoordinate &utmCoord2D = utmCoord3D; + NOTICE_LOG(m_logger) << " utmCoord2D = " << utmCoord2D; + QVERIFY2(qFuzzyCompare(utmCoord2D.x, 670000.5), "utmCoord2D failed"); + QVERIFY2(qFuzzyCompare(utmCoord2D.y, 5810000.5), "utmCoord2D failed"); + + + const LtsCoordTrans::UtmCoordinate &utm = ltsCoord.utmCoordinate(); + QVERIFY2(utm.mgrs() == QStringLiteral("32U PD 70000 10000") , "utmCoordinate failed"); +} + +void UnitTestCoordinate::test_coordinate_fromUtm2() +{ + const LtsCoordTrans::UtmCoordinate &utmInZone32 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U LA 10000 00000")); + const double zValue = 2.3; + NOTICE_LOG(m_logger) << " utmInZone32 = " << utmInZone32; + + const QPointF &utmInDefQPointF = utmInZone32.geographic(); + + const int zone31 = 31; + const LtsCoordTrans::UtmCoordinate &utmInZone31 = LtsCoordTrans::UtmCoordinate::fromGeographic(utmInDefQPointF, zone31); + NOTICE_LOG(m_logger) << " utmInZone31 = " << utmInZone31; + + NOTICE_LOG(m_logger) << " utmInZone31.geographic() = " << utmInZone31.geographic(); + NOTICE_LOG(m_logger) << " utmInZone32.geographic() = " << utmInZone32.geographic(); + + QVERIFY2(utmInZone31.geographic() == utmInZone32.geographic(), "geographic failed"); + + { + + LtsCoordTrans::LtsCoordinate ltsFromUtm31(utmInZone31.geographic(), zValue); + LtsCoordTrans::LtsCoordinate ltsFromUtm32(utmInZone32.geographic(), zValue); + + double dist = ltsFromUtm31.distanceUtm3D(ltsFromUtm32); + NOTICE_LOG(m_logger) << " dist1 = " << dist; + QVERIFY2(qFuzzyCompare(dist, 0), "distance3D"); + } + { + + LtsCoordTrans::LtsCoordinate ltsFromUtm31(utmInZone31.geographic() , zValue); + LtsCoordTrans::LtsCoordinate ltsFromUtm32(utmInZone32.geographic(1, 0), zValue); + + double dist = ltsFromUtm31.distanceUtm3D(ltsFromUtm32); + NOTICE_LOG(m_logger) << " dist2 = " << dist; + QVERIFY2(fabs(dist-1) < 0.00001, "distance3D"); + } + + { + + + + LtsCoordTrans::LtsCoordinate ltsFromUtm31 = LtsCoordTrans::LtsCoordinate::fromUtmCoordinate(utmInZone31, zValue); + LtsCoordTrans::LtsCoordinate ltsFromUtm32 = LtsCoordTrans::LtsCoordinate::fromUtmCoordinate(utmInZone32, zValue); + + double dist = ltsFromUtm31.distanceUtm3D(ltsFromUtm32); + NOTICE_LOG(m_logger) << " dist3 = " << dist; + QVERIFY2(qFuzzyCompare(dist, 0), "distance3D"); + } + + { + LtsCoordTrans::LtsCoordinate ltsFromUtm32(utmInZone32.geographic(), zValue); + + const QPointF &utmP2D_def = ltsFromUtm32.toUtm(); + const QPointF &utmP2D_31 = ltsFromUtm32.toUtm(31); + NOTICE_LOG(m_logger) << " utmP2D_def = " << utmP2D_def; + NOTICE_LOG(m_logger) << " utmP2D_31 = " << utmP2D_31; + + LtsCoordTrans::LtsCoordinate lts1 = LtsCoordTrans::LtsCoordinate::fromUtm(utmP2D_def, 32, 0.0); + LtsCoordTrans::LtsCoordinate lts2 = LtsCoordTrans::LtsCoordinate::fromUtm(utmP2D_31, 31, 0.0); + NOTICE_LOG(m_logger) << " lts1 = " << lts1; + NOTICE_LOG(m_logger) << " lts2 = " << lts2; + + double dist = lts1.distanceUtm3D(lts2); + NOTICE_LOG(m_logger) << " dist4 = " << dist; + QVERIFY2(qFuzzyCompare(dist, 0), "distance3D"); + } +} + + +void UnitTestCoordinate::test_coordinate_elevationAt() +{ + const double zValue = 1.23; + + const LtsCoordTrans::UtmCoordinate &utmIn = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000")); + NOTICE_LOG(m_logger) << " utmIn = " << utmIn; + + LtsCoordTrans::LtsCoordinate ltsFromUtm(utmIn.geographic(), zValue); + NOTICE_LOG(m_logger) << " ltsFromUtm1 = " << ltsFromUtm; + QVERIFY2(qFuzzyCompare(ltsFromUtm.geoPosition().z(), zValue) , "geoPosition failed"); + + + const double ele = ltsFromUtm.elevationAt(); + QVERIFY2(qFuzzyCompare(ele, 75), "elevationAt failed"); + + NOTICE_LOG(m_logger) << " ltsFromUtm2 = " << ltsFromUtm; + QVERIFY2(qFuzzyCompare(ltsFromUtm.geoPosition().z(), zValue) , "geoPosition failed"); + + + ltsFromUtm.setZValueAccordingElevation(); + NOTICE_LOG(m_logger) << " ltsFromUtm3 = " << ltsFromUtm; + QVERIFY2(qFuzzyCompare(ltsFromUtm.geoPosition().z(), 75) , "setZValueAccordingElevation failed"); + + + const LtsUtils::QPoint3D &utmP3D = ltsFromUtm.toUtm(); + NOTICE_LOG(m_logger) << " utmP3D_a = " << utmP3D; + QVERIFY2(qFuzzyCompare(utmP3D.z(), 75 ) , "utmCoordinateAsPoint3D failed"); + + { + + const double ele3 = LtsCoordTrans::LtsCoordinate::elevationAt(ltsFromUtm); + NOTICE_LOG(m_logger) << " ele3 = " << ele3; + + QVERIFY2(qFuzzyCompare(ele, ele3 ), "LtsCoordinate::elevationAt failed"); + } + + + + + + + LtsCoordTrans::UtmCoordinate utmMoved = ltsFromUtm.utmCoordinate(); + utmMoved.x += 11; + utmMoved.y += 22; + const int zValueNew = 123; + ltsFromUtm = LtsCoordTrans::LtsCoordinate(utmMoved.geographic(), zValueNew); + NOTICE_LOG(m_logger) << " ltsFromUtm4 = " << ltsFromUtm; + + + const double ele2 = ltsFromUtm.elevationAt(); + NOTICE_LOG(m_logger) << " ltsFromUtm5 = " << ltsFromUtm; + QVERIFY2(ele2 != 75 , "setZValueAccordingElevation failed"); +} + +void UnitTestCoordinate::test_coordinate_elevationAt_isCovered() +{ + const LtsCoordTrans::UtmCoordinate &utmIn1 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000")); + const LtsCoordTrans::UtmCoordinate &utmIn2 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U MD 10000 10000")); + + LtsCoordTrans::LtsCoordinate ltsFromUtm1(utmIn1.geographic(), 11); + LtsCoordTrans::LtsCoordinate ltsFromUtm2(utmIn2.geographic(), 11); + + { + bool bIsCovered = false; + double ele = ltsFromUtm1.elevationAt(-123, &bIsCovered); + Q_UNUSED(ele); + QVERIFY2(bIsCovered == true , "elevationAt1a failed"); + + + bIsCovered = false; + ele = ltsFromUtm1.elevationAt(-123, &bIsCovered); + Q_UNUSED(ele); + QVERIFY2(bIsCovered == true , "elevationAt1b failed"); + + + + bIsCovered = false; + ele = LtsCoordTrans::LtsCoordinate::elevationAt(ltsFromUtm1, -123, &bIsCovered); + Q_UNUSED(ele); + QVERIFY2(bIsCovered == true , "elevationAt2a failed"); + + + bIsCovered = false; + ele = LtsCoordTrans::LtsCoordinate::elevationAt(ltsFromUtm1, -123, &bIsCovered); + Q_UNUSED(ele); + QVERIFY2(bIsCovered == true , "elevationAt2b failed"); + } + + { + bool bIsCovered = true; + double ele = ltsFromUtm2.elevationAt(-123, &bIsCovered); + Q_UNUSED(ele); + QVERIFY2(bIsCovered == false , "elevationAt2 failed"); + + + bIsCovered = true; + ele = LtsCoordTrans::LtsCoordinate::elevationAt(ltsFromUtm2, -123, &bIsCovered); + Q_UNUSED(ele); + QVERIFY2(bIsCovered == false , "elevationAt1 failed"); + } +} + +void UnitTestCoordinate::test_coordinate_elevation_data() +{ + QTest::addColumn("mgrs"); + QTest::addColumn("elevationIn"); + QTest::addColumn("elevationRef"); + + + QTest::newRow("elevation 10 ") << QStringLiteral("32 U PD 70000 10000") << std::nan("") << 75.0; + QTest::newRow("elevation 11 ") << QStringLiteral("32 U PD 70000 10000") << 0.0 << 75.0; + QTest::newRow("elevation 20 ") << QStringLiteral("32 U PD 72000 13000") << std::nan("") << 80.0; + QTest::newRow("elevation 21 ") << QStringLiteral("32 U PD 72000 13000") << 0.340 << 80.0; +} +void UnitTestCoordinate::test_coordinate_elevation() +{ + QFETCH(QString, mgrs); + QFETCH(double, elevationIn); + QFETCH(double, elevationRef); + + const LtsCoordTrans::UtmCoordinate &utmIn = LtsCoordTrans::UtmCoordinate::fromMgrs(mgrs); + NOTICE_LOG(m_logger) << " utmIn = " << utmIn; + + LtsCoordTrans::LtsCoordinate ltsFromUtm(utmIn.geographic(), elevationIn); + NOTICE_LOG(m_logger) << " ltsFromUtm = " << ltsFromUtm; + + + NOTICE_LOG(m_logger) << " geoPosition = " << ltsFromUtm.geoPosition(); + ltsFromUtm.replaceZValueByElevationIfValue(elevationIn); + + + NOTICE_LOG(m_logger) << " geoPosition = " << ltsFromUtm.geoPosition(); + QVERIFY2(qFuzzyCompare(ltsFromUtm.geoPosition().z(), elevationRef), "elevation failed"); + + + const LtsUtils::QPoint3D &utmP3D = ltsFromUtm.toUtm(); + NOTICE_LOG(m_logger) << " utmP3D = " << utmP3D; + QVERIFY2(qFuzzyCompare(utmP3D.z(), elevationRef) , "utmCoordinateAsPoint3D failed"); +} + +void UnitTestCoordinate::test_coordinate_mgrs() +{ + const LtsCoordTrans::UtmCoordinate &utmIn = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000")); + NOTICE_LOG(m_logger) << " utmIn = " << utmIn.mgrs(); + + LtsCoordTrans::LtsCoordinate ltsFromUtm(utmIn.geographic()); + + { + const QString asMgrs = ltsFromUtm.mgrs(); + NOTICE_LOG(m_logger) << " asMgrs = " << asMgrs; + QVERIFY2(asMgrs.compare(QStringLiteral("32U PD 70000 10000")) == 0, "UtmCoordinate::mgrs failed"); + } + { + const QString asMgrs = ltsFromUtm.mgrsWithZ(); + NOTICE_LOG(m_logger) << " asMgrs = " << asMgrs; + QVERIFY2(asMgrs.compare(QStringLiteral("32U PD 70000 10000 z=75.00")) == 0, "UtmCoordinate::mgrs failed"); + } + + { + const LtsCoordTrans::LtsCoordinate coord(0.200706392814992, 0.914792952478694, 123); + + + const QString str1 = coord.toString(); + NOTICE_LOG(m_logger) << " str1 cheap = " << str1; + QVERIFY2(str1.compare(QStringLiteral("LtsCoordinate(m_geo=[[lon: 0.200706392814992, lat: 0.914792952478694]z: 123], m_utm2D.x 0, m_utm2D.y 0, m_utm2D.zone -1, elevation nan)")) == 0, "toString failed"); + + + + + + const bool fullAndExpensive = true; + const QString str2 = coord.toString(fullAndExpensive); + NOTICE_LOG(m_logger) << " str2 expensive = " << str2; + QVERIFY2(str2.compare(QStringLiteral("LtsCoordinate(m_geo=[[lon: 0.200706392814992, lat: 0.914792952478694]z: 123], m_utm2D.x 670000.499999998, m_utm2D.y 5810000.5, m_utm2D.zone 32, m_utm2D.mgrs '32U PD 70000 10000', elevation 75.000)")) == 0, "toString failed"); + } + +} + +void UnitTestCoordinate::test_coordinate_distance() +{ + const LtsCoordTrans::UtmCoordinate &utmIn1 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000")); + const LtsCoordTrans::UtmCoordinate &utmIn2 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70010 10100")); + NOTICE_LOG(m_logger) << " XXutmIn1 = " << utmIn1; + NOTICE_LOG(m_logger) << " XXutmIn2 = " << utmIn2; + + QVERIFY2(qFuzzyCompare((utmIn2.x - utmIn1.x), 10) , "coord failed"); + QVERIFY2(qFuzzyCompare((utmIn2.y - utmIn1.y), 100) , "coord failed"); + + const LtsCoordTrans::LtsCoordinate lts1(utmIn1.geographic(), 11); + const LtsCoordTrans::LtsCoordinate lts2(utmIn2.geographic(), 111); + + { + double dist2D = lts1.distanceUtm2D(lts2); + NOTICE_LOG(m_logger) << " dist2D = " << dist2D; + QVERIFY2(std::fabs(dist2D-100.499) < 0.01, "distanceUtm2D failed"); + } + + { + double dist3D = lts1.distanceUtm3D(lts2); + NOTICE_LOG(m_logger) << " dist3D = " << dist3D; + QVERIFY2(std::fabs(dist3D-141.774) < 0.01, "distanceUtm3D failed"); + } +} + + +void UnitTestCoordinate::test_coordinate_distanceDiffZones() +{ + const LtsCoordTrans::UtmCoordinate &utmIn1 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("31 U FR 99999 00000")); + const LtsCoordTrans::UtmCoordinate &utmIn2 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U LA 00000 00000")); + NOTICE_LOG(m_logger) << " XXutmIn1 = " << utmIn1; + NOTICE_LOG(m_logger) << " XXutmIn2 = " << utmIn2; + + + + LtsCoordTrans::LtsCoordinate lts1(utmIn1.geographic(), 0); + LtsCoordTrans::LtsCoordinate lts2(utmIn2.geographic(), 0); + NOTICE_LOG(m_logger) << " lts1 = " << lts1; + NOTICE_LOG(m_logger) << " lts2 = " << lts2; + + double dist = lts1.distanceUtm3D(lts2); + + NOTICE_LOG(m_logger) << " dist = " << dist; + QVERIFY2(std::fabs(dist-33382.7) < 0.1, "distance3D failed"); +} + +void UnitTestCoordinate::test_squareInDifferentZone() +{ + const LtsCoordTrans::UtmCoordinate &utm_bl = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000")); + const LtsCoordTrans::UtmCoordinate &utm_br = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 11000")); + const LtsCoordTrans::UtmCoordinate &utm_tl = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 71000 10000")); + const LtsCoordTrans::UtmCoordinate &utm_tr = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 71000 11000")); + + const LtsCoordTrans::LtsCoordinate lts_bl(utm_bl.geographic(), 0); + const LtsCoordTrans::LtsCoordinate lts_br(utm_br.geographic(), 0); + const LtsCoordTrans::LtsCoordinate lts_tl(utm_tl.geographic(), 0); + const LtsCoordTrans::LtsCoordinate lts_tr(utm_tr.geographic(), 0); + + { + + QVERIFY2(qFuzzyCompare(lts_bl.distanceUtm2D(lts_br), 1000.0), "distance3D 1 failed"); + QVERIFY2(qFuzzyCompare(lts_tl.distanceUtm2D(lts_tr), 1000.0), "distance3D 2 failed"); + QVERIFY2(qFuzzyCompare(lts_br.distanceUtm2D(lts_tr), 1000.0), "distance3D 3 failed"); + QVERIFY2(qFuzzyCompare(lts_bl.distanceUtm2D(lts_tl), 1000.0), "distance3D 4 failed"); + + QVERIFY2(qFuzzyCompare(lts_bl.distanceUtm2D(lts_tr), 1414.213562373177), "distance3D 5 failed"); + QVERIFY2(qFuzzyCompare(lts_br.distanceUtm2D(lts_tl), 1414.213562373177), "distance3D 6 failed"); + } + + + + { + const LtsCoordTrans::UtmCoordinate &utm31_bl = LtsCoordTrans::UtmCoordinate(31, utm_bl.x, utm_bl.y); + const LtsCoordTrans::UtmCoordinate &utm31_br = LtsCoordTrans::UtmCoordinate(31, utm_br.x, utm_br.y); + const LtsCoordTrans::UtmCoordinate &utm31_tl = LtsCoordTrans::UtmCoordinate(31, utm_tl.x, utm_tl.y); + const LtsCoordTrans::UtmCoordinate &utm31_tr = LtsCoordTrans::UtmCoordinate(31, utm_tr.x, utm_tr.y); + + + QVERIFY2(qFuzzyCompare(LtsCoordTrans::distance(utm31_bl, utm31_br), 1000.0), "LtsCoordTrans::distance 1 failed"); + QVERIFY2(qFuzzyCompare(LtsCoordTrans::distance(utm31_tl, utm31_tr), 1000.0), "LtsCoordTrans::distance 2 failed"); + QVERIFY2(qFuzzyCompare(LtsCoordTrans::distance(utm31_br, utm31_tr), 1000.0), "LtsCoordTrans::distance 3 failed"); + QVERIFY2(qFuzzyCompare(LtsCoordTrans::distance(utm31_bl, utm31_tl), 1000.0), "LtsCoordTrans::distance 4 failed"); + + QVERIFY2(qFuzzyCompare(LtsCoordTrans::distance(utm31_bl, utm31_tr), 1414.213562373095), "LtsCoordTrans::distance 5 failed"); + QVERIFY2(qFuzzyCompare(LtsCoordTrans::distance(utm31_br, utm31_tl), 1414.213562373095), "LtsCoordTrans::distance 6 failed"); + } + + + + + + + + +} + +void UnitTestCoordinate::test_coordinate_angle() +{ + + + const LtsCoordTrans::UtmCoordinate &utm_ul = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000")); + const LtsCoordTrans::LtsCoordinate lts_ul(utm_ul.geographic(), 0); + + { + + const LtsCoordTrans::UtmCoordinate &utm_2 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 11000")); + const LtsCoordTrans::LtsCoordinate lts_2(utm_2.geographic(), 0); + double angle = LtsCoordTrans::LtsCoordinate::angleDegreeUtm2D(lts_ul, lts_2); + QVERIFY2(qFuzzyCompare(angle, 0.0), "angleUtm2D 1 failed"); + } + + { + + const LtsCoordTrans::UtmCoordinate &utm_2 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 71000 10000")); + const LtsCoordTrans::LtsCoordinate lts_2(utm_2.geographic(), 0); + double angle = LtsCoordTrans::LtsCoordinate::angleDegreeUtm2D(lts_ul, lts_2); + QVERIFY2(qFuzzyCompare(angle, 90.0), "angleUtm2D 1 failed"); + } + + { + + const LtsCoordTrans::UtmCoordinate &utm_2 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 09000")); + const LtsCoordTrans::LtsCoordinate lts_2(utm_2.geographic(), 0); + double angle = LtsCoordTrans::LtsCoordinate::angleDegreeUtm2D(lts_ul, lts_2); + QVERIFY2(qFuzzyCompare(angle, 180.0), "angleUtm2D 1 failed"); + } + + { + + const LtsCoordTrans::UtmCoordinate &utm_2 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 69000 10000")); + const LtsCoordTrans::LtsCoordinate lts_2(utm_2.geographic(), 0); + double angle = LtsCoordTrans::LtsCoordinate::angleDegreeUtm2D(lts_ul, lts_2); + QVERIFY2(qFuzzyCompare(angle, 270.0), "angleUtm2D 1 failed"); + } + + + { + const LtsCoordTrans::UtmCoordinate &utm_or = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 71000 12000")); + const LtsCoordTrans::LtsCoordinate lts_or(utm_or.geographic(), 0); + double angle = LtsCoordTrans::LtsCoordinate::angleDegreeUtm2D(lts_ul, lts_or); + + QVERIFY2(qFuzzyCompare(angle, 26.565051177056659526), "angleUtm2D 1 failed"); + QVERIFY2((angle-26.5651 < 0.001), "angleUtm2D 1 failed"); + } +} + +void UnitTestCoordinate::test_coordinate_cont() +{ + std::vector stdVec; + stdVec.push_back(Core::GeoPosition(0.1, 0.11, 1)); + stdVec.push_back(Core::GeoPosition(0.2, 0.22, 2)); + stdVec.push_back(Core::GeoPosition(0.3, 0.33, 3)); + + const QVector &newCont = LtsCoordTrans::convert>(stdVec); + + NOTICE_LOG(m_logger) << " newCont.size() = " << newCont.size(); + QVERIFY2(stdVec.size() == static_cast(newCont.size()), "convert failed"); +} + +using namespace LtsCoordTrans; +class ParticpantPositionStore +{ +public: + ParticpantPositionStore() = default; + ~ParticpantPositionStore() = default; + + void setGeoPosition(const Core::GeoPosition &newGeoPos) + { + m_ltsCoord = LtsCoordinate(newGeoPos); + m_ltsCoord_NoImplicitCopy.updateGeoPosition(newGeoPos); + } + + + + + + LtsCoordinate getLtsCoord_copy() + { + return m_ltsCoord; + } + + + + const LtsCoordinateNoImplicitCopy& getLtsCoord_noCopy() + { + return m_ltsCoord_NoImplicitCopy; + } + + + + + + + + + + LtsCoordinate m_ltsCoord; + LtsCoordinateNoImplicitCopy m_ltsCoord_NoImplicitCopy; +}; + + +void UnitTestCoordinate::test_coordinate_avoid_copy() +{ + LtsCoordTrans::LtsCoordinate::resetConversionCounter(); + QVERIFY2(LtsCoordTrans::LtsCoordinate::getConversionCounter() == 0, "getConversionCounter failed"); + + ParticpantPositionStore store; + + + + + const UtmCoordinate &utmIn = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000")); + const LtsCoordinate ltsIn(utmIn.geographic(), 11); + + store.getLtsCoord_noCopy().updateGeoPosition(ltsIn.geoPosition()); + + + + + + + const LtsCoordinateNoImplicitCopy &justReference = store.getLtsCoord_noCopy(); + NOTICE_LOG(m_logger) << " justReference = " << justReference.mgrs(); + + const LtsCoordTrans::UtmCoordinate &convertedToUtm = justReference.utmCoordinate(); + NOTICE_LOG(m_logger) << " convertedToUtm = " << convertedToUtm; + QVERIFY2(qFuzzyCompare(convertedToUtm.x, 670000.5 ), "convert failed"); + QVERIFY2(qFuzzyCompare(convertedToUtm.y, 5810000.5), "convert failed"); + QVERIFY2(LtsCoordTrans::LtsCoordinate::getConversionCounter() == 1, "getConversionCounter failed"); + + + + + + const LtsCoordTrans::UtmCoordinate &convertedToUtmSecondTry = justReference.utmCoordinate(); + Q_UNUSED(convertedToUtmSecondTry) + QVERIFY2(LtsCoordTrans::LtsCoordinate::getConversionCounter() == 1, "getConversionCounter failed"); + + + std::vector myVec; + myVec.push_back(justReference); + + + const LtsCoordTrans::LtsCoordinateNoImplicitCopy &retrievedUtmRef = myVec.front(); + NOTICE_LOG(m_logger) << " retrievedUtm = " << retrievedUtmRef.mgrs(); + + + QVERIFY2(justReference.mgrs() == retrievedUtmRef.mgrs(), "getConversionCounter failed"); + + + + + + QVERIFY2(LtsCoordTrans::LtsCoordinate::getConversionCounter() == 1, "getConversionCounter failed"); + + + + + +} + + + + +template +void removeItem(T& map, int key) { map.erase(key); } + + +template <> +void removeItem(QMap& map, int key) { map.remove(key); } + + +template <> +void removeItem(QHash& map, int key) { map.remove(key); } + + +template +void test_references(T& map) +{ + for (int i=2; i<10; ++i) + map[i] = i; + + const int &ref2 = map[2]; + const int &ref3 = map[3]; + const int &ref4 = map[4]; + const int &ref5 = map[5]; + const int &ref6 = map[6]; + const int &ref7 = map[7]; + const int &ref8 = map[8]; + + QVERIFY2(ref2 == 2, "ref failed"); + QVERIFY2(ref3 == 3, "ref failed"); + QVERIFY2(ref4 == 4, "ref failed"); + QVERIFY2(ref5 == 5, "ref failed"); + QVERIFY2(ref6 == 6, "ref failed"); + QVERIFY2(ref7 == 7, "ref failed"); + QVERIFY2(ref8 == 8, "ref failed"); + + + removeItem(map, 1); + removeItem(map, 3); + removeItem(map, 5); + + { + QVERIFY2(ref2 == 2, "ref failed"); + + QVERIFY2(ref4 == 4, "ref failed"); + + QVERIFY2(ref6 == 6, "ref failed"); + QVERIFY2(ref7 == 7, "ref failed"); + QVERIFY2(ref8 == 8, "ref failed"); + } + + + map[0] = 0; + map[1] = 1; + const int &ref0 = map[0]; + const int &ref1 = map[1]; + + { + QVERIFY2(ref0 == 0, "ref failed"); + QVERIFY2(ref1 == 1, "ref failed"); + + QVERIFY2(ref2 == 2, "ref failed"); + + QVERIFY2(ref4 == 4, "ref failed"); + + QVERIFY2(ref6 == 6, "ref failed"); + QVERIFY2(ref7 == 7, "ref failed"); + QVERIFY2(ref8 == 8, "ref failed"); + } + +} + +void UnitTestCoordinate::test_container_references() +{ + + + + + + + + + + { + std::unordered_map std_unordered_map; + test_references(std_unordered_map); + } + { + std::map std_map; + test_references(std_map); + } + { + QMap Qmap; + test_references(Qmap); + } + { + QHash Qhash; + test_references(Qhash); + } +} + + + + + +void UnitTestCoordinate::test_coordVector() +{ + std::vector geoVec; + + { + + QPointF utmPnt = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U LB 00001 50001")).geographic(); + geoVec.push_back( Core::GeoPosition(utmPnt.x(), utmPnt.y(), 1.1) ); + + utmPnt = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U LB 00001 50001")).geographic(1, 0); + geoVec.push_back( Core::GeoPosition(utmPnt.x(), utmPnt.y(), 2.2) ); + + utmPnt = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U LB 00001 50001")).geographic(2, 0); + geoVec.push_back( Core::GeoPosition(utmPnt.x(), utmPnt.y(), 3.3) ); + } + + const LtsCoordVector coordVec(geoVec); + QVERIFY2(coordVec.size() == 3, "size failed"); + + int cnt = LtsCoordTrans::getConversionCounter_UTMUPS_Forward(); + + { + const std::vector &utmVec = coordVec.toStdUtmCoord(32); + + + + + + QVERIFY2(utmVec.size() == 3, "size failed"); + QVERIFY2(utmVec.front().mgrs() == QStringLiteral("32U LB 00001 50001"), "mgrs failed"); + QVERIFY2(utmVec.back().mgrs() == QStringLiteral("32U LB 00003 50001"), "mgrs failed"); + QVERIFY2(LtsCoordTrans::getConversionCounter_UTMUPS_Forward() - cnt == 3, "getConversionCounterUTMUPS_Forward failed"); + } + { + const std::vector &utmVec = coordVec.toStdUtmCoord(32); + QVERIFY2(utmVec.size() == 3, "size failed"); + QVERIFY2(utmVec.front().mgrs() == QStringLiteral("32U LB 00001 50001"), "mgrs failed"); + QVERIFY2(utmVec.back().mgrs() == QStringLiteral("32U LB 00003 50001"), "mgrs failed"); + QVERIFY2(LtsCoordTrans::getConversionCounter_UTMUPS_Forward() - cnt == 3, "getConversionCounterUTMUPS_Forward failed"); + } + + { + const std::vector &utmVec = coordVec.toStdUtmCoord(31); + QVERIFY2(utmVec.size() == 3, "size failed"); + QVERIFY2(LtsCoordTrans::getConversionCounter_UTMUPS_Forward() - cnt == 6, "getConversionCounterUTMUPS_Forward failed"); + } + { + const std::vector &utmVec = coordVec.toStdUtmCoord(31); + QVERIFY2(utmVec.size() == 3, "size failed"); + QVERIFY2(LtsCoordTrans::getConversionCounter_UTMUPS_Forward() - cnt == 6, "getConversionCounterUTMUPS_Forward failed"); + } + + + + + + { + const QVector &utmVec = coordVec.toQVectorUtmCoord(32); + QVERIFY2(utmVec.size() == 3, "size failed"); + QVERIFY2(utmVec.front().mgrs() == QStringLiteral("32U LB 00001 50001"), "mgrs failed"); + QVERIFY2(utmVec.back().mgrs() == QStringLiteral("32U LB 00003 50001"), "mgrs failed"); + QVERIFY2(LtsCoordTrans::getConversionCounter_UTMUPS_Forward() - cnt == 6, "getConversionCounterUTMUPS_Forward failed"); + } + { + const QVector &utmVec = coordVec.toQVectorUtmCoord(32); + QVERIFY2(utmVec.size() == 3, "size failed"); + QVERIFY2(utmVec.front().mgrs() == QStringLiteral("32U LB 00001 50001"), "mgrs failed"); + QVERIFY2(utmVec.back().mgrs() == QStringLiteral("32U LB 00003 50001"), "mgrs failed"); + QVERIFY2(LtsCoordTrans::getConversionCounter_UTMUPS_Forward() - cnt == 6, "getConversionCounterUTMUPS_Forward failed"); + } + + + + + + { + const QVector &utmVec = coordVec.toQVectorQPointF(32); + QVERIFY2(utmVec.size() == 3, "size failed"); + QVERIFY2(LtsCoordTrans::getConversionCounter_UTMUPS_Forward() - cnt == 6, "getConversionCounterUTMUPS_Forward failed"); + } + { + const QVector &utmVec = coordVec.toQVectorUtmCoord(32); + QVERIFY2(utmVec.size() == 3, "size failed"); + QVERIFY2(LtsCoordTrans::getConversionCounter_UTMUPS_Forward() - cnt == 6, "getConversionCounterUTMUPS_Forward failed"); + } + +} + + + + + +int main(int argc, char *argv[]) +{ + NGLCoreApplication app(argc, argv); + app.createLogger("UnitTestCoordinate", "log4cxxPositionUnitTest.props"); + + UnitTestCoordinate tc; + + int ret = QTest::qExec(&tc, argc, argv); + + + if (s_eleMgr) + { + s_eleMgr->cleanupAll(); + } + + return ret; +} + + diff --git a/Lts/Common/LtsCoordinate/UnitTest_Coordinate/lts_coordinate_unit_test.h b/Lts/Common/LtsCoordinate/UnitTest_Coordinate/lts_coordinate_unit_test.h new file mode 100755 index 000000000..033a25221 --- /dev/null +++ b/Lts/Common/LtsCoordinate/UnitTest_Coordinate/lts_coordinate_unit_test.h @@ -0,0 +1,80 @@ + + + +#pragma once + +#include + +#include + +#include +#include +#include + + +using LOGGER_T = NGL::INGLLogger&; + + + + +class UnitTestCoordinate : public QObject +{ + Q_OBJECT + +public: + UnitTestCoordinate(); + + +private Q_SLOTS: + + + + + + + void initTestCase(); + + void init(); + + + + void test_NaN(); + + void test_coordinate_simple1(); + void test_coordinate_simple2(); + void test_coordinate_simple3(); + void test_coordinate_simple4(); + + void test_coordinate_fromUtm(); + void test_coordinate_fromUtm2(); + + void test_coordinate_elevationAt(); + void test_coordinate_elevationAt_isCovered(); + + void test_coordinate_elevation_data(); + void test_coordinate_elevation(); + + void test_coordinate_mgrs(); + + void test_coordinate_distance(); + void test_coordinate_distanceDiffZones(); + + void test_squareInDifferentZone(); + + void test_coordinate_angle(); + + void test_coordinate_cont(); + + void test_coordinate_avoid_copy(); + + + + void test_container_references(); + void test_coordVector(); + + + + +private: + LOGGER_T m_logger; +}; diff --git a/Lts/Common/LtsCoordinate/lts_coord_vector.cpp b/Lts/Common/LtsCoordinate/lts_coord_vector.cpp new file mode 100755 index 000000000..f386c915d --- /dev/null +++ b/Lts/Common/LtsCoordinate/lts_coord_vector.cpp @@ -0,0 +1,90 @@ + + + + +#include "lts_coord_vector.h" + +namespace LtsCoordTrans +{ + +LtsCoordVector::LtsCoordVector(const std::vector &geo_coordinates) + : m_geo_coordinates(geo_coordinates) +{ + +} + +const std::vector &LtsCoordVector::toStdUtmCoord(int zone) const +{ + convertGeoToStdUtm(zone); + return m_utmMapStd[zone]; +} + +const QVector &LtsCoordVector::toQVectorUtmCoord(int zone) const +{ + convertGeoToQVectorUtm(zone); + return m_utmMapQVector[zone]; +} + +const QVector &LtsCoordVector::toQVectorQPointF(int zone) const +{ + convertGeoToQVectorQPointF(zone); + return m_utmMapQVectorQPointF[zone]; +} + +void LtsCoordVector::convertGeoToStdUtm(int zone) const +{ + const auto it = m_utmMapStd.find(zone); + if (it != m_utmMapStd.end()) + return; + convertGeoToUtmInternal(zone, m_utmMapStd[zone]); +} + +void LtsCoordVector::convertGeoToQVectorUtm(int zone) const +{ + const auto it = m_utmMapQVector.find(zone); + if (it != m_utmMapQVector.end()) + return; + + + + + + convertGeoToStdUtm(zone); + QVector &utmVectorRef = m_utmMapQVector[zone]; + utmVectorRef.clear(); + utmVectorRef.reserve(m_geo_coordinates.size()); + + for (const auto &utmCoord : m_utmMapStd[zone]) + utmVectorRef.push_back(utmCoord); +} + +void LtsCoordVector::convertGeoToQVectorQPointF(int zone) const +{ + const auto it = m_utmMapQVectorQPointF.find(zone); + if (it != m_utmMapQVectorQPointF.end()) + return; + + convertGeoToStdUtm(zone); + QVector &QVectorQPointFRef = m_utmMapQVectorQPointF[zone]; + QVectorQPointFRef.clear(); + QVectorQPointFRef.reserve(m_geo_coordinates.size()); + + for (const auto &utmCoord : m_utmMapStd[zone]) + QVectorQPointFRef.push_back(QPointF(utmCoord.x, utmCoord.y)); +} + + + + + + + + + + + + + +} + + diff --git a/Lts/Common/LtsCoordinate/lts_coord_vector.h b/Lts/Common/LtsCoordinate/lts_coord_vector.h new file mode 100755 index 000000000..a16e12d5a --- /dev/null +++ b/Lts/Common/LtsCoordinate/lts_coord_vector.h @@ -0,0 +1,81 @@ + + + +#pragma once + +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include + + +namespace LtsCoordTrans +{ + + + +class LTSCOORDINATE_EXPORT LtsCoordVector +{ +public: + + + + explicit LtsCoordVector(const std::vector &geo_coordinates); + + + + int size() const { return m_geo_coordinates.size() ; } + const std::vector &geoPosition() const { return m_geo_coordinates; } + + + + + const std::vector &toStdUtmCoord(int zone) const; + const QVector &toQVectorUtmCoord(int zone) const; + const QVector &toQVectorQPointF(int zone) const; + + + +protected: + void convertGeoToStdUtm(int zone) const; + void convertGeoToQVectorUtm(int zone) const; + void convertGeoToQVectorQPointF(int zone) const; + + template + void convertGeoToUtmInternal(int zone, T &utmVectorRef) const + { + + utmVectorRef.clear(); + utmVectorRef.reserve(m_geo_coordinates.size()); + + for (const auto &geoCoord : qAsConst(m_geo_coordinates)) + utmVectorRef.push_back(LtsCoordTrans::UtmCoordinate::fromGeographic(geoCoord.lon(), geoCoord.lat(), zone)); + } + +protected: + std::vector m_geo_coordinates; + + + + + mutable std::unordered_map> m_utmMapStd; + mutable std::unordered_map> m_utmMapQVector; + mutable std::unordered_map> m_utmMapQVectorQPointF; +}; + + + + + + + +} + diff --git a/Lts/Common/LtsCoordinate/lts_coordinate.cpp b/Lts/Common/LtsCoordinate/lts_coordinate.cpp new file mode 100755 index 000000000..5074715bf --- /dev/null +++ b/Lts/Common/LtsCoordinate/lts_coordinate.cpp @@ -0,0 +1,178 @@ + + + + +#include "lts_coordinate.h" +#include + +namespace LtsCoordTrans +{ + +static long s_conversion_counter = 0; + + +std::function LtsCoordinate::m_getElevationCB; + + + + +void LtsCoordinate::setElevationCallback(const elevationCbFunc &elevationCB) +{ + m_getElevationCB = elevationCB; +} + +bool LtsCoordinate::hasElevationCallback() +{ + return m_getElevationCB != nullptr; +} + +double LtsCoordinate::elevationAt(double defaultElevation, bool *bIsCovered) const +{ + + + + if (!bIsCovered) + if (!std::isnan(m_elevation)) + return m_elevation; + if (m_getElevationCB) + m_elevation = m_getElevationCB(m_geo.lon(), m_geo.lat(), defaultElevation, bIsCovered); + return m_elevation; +} + +void LtsCoordinate::setZValueAccordingElevation(double defaultElevation) +{ + m_geo.z(elevationAt(defaultElevation)); +} + +double LtsCoordinate::elevationAt(const LtsCoordinate &coordinate, double defaultElevation, bool *bIsCovered) +{ + return coordinate.elevationAt(defaultElevation, bIsCovered); +} + +void LtsCoordinate::replaceZValueByElevationIfValue(double zValueToBeReplaced, double defaultElevation) +{ + + + + + + if ((std::isnan(zValueToBeReplaced) && std::isnan(m_geo.z())) || + qFuzzyCompare(m_geo.z(), zValueToBeReplaced)) + setZValueAccordingElevation(defaultElevation); +} + +double LtsCoordinate::angleDegreeUtm2D(const LtsCoordinate &coord1, const LtsCoordinate &coord2) +{ + const LtsCoordTrans::UtmCoordinate &utm1 = coord1.utmCoordinate(); + const LtsCoordTrans::UtmCoordinate &utm2 = coord2.utmCoordinateDiffZone(utm1.zone); + + const QPointF& offsetUtm = utm2.toPoint() - utm1.toPoint(); + QLineF poly(QPointF(), offsetUtm); + const double angle = poly.angle(); + + + + + + + + + + + + + + + + + + + + + return fmod(angle+90.0, 360); +} + +const UtmCoordinate &LtsCoordinate::utmCoordinateInternal(int zone) const +{ + if (zone == -1) + { + if (!hasUtm()) + convertGeoToUtm(); + return m_utm2D; + } + utmCoordinateDiffZone(zone); + return m_utm2D_differentZone; +} + +void LtsCoordinate::convertGeoToUtm() const +{ + + int zone = -1; + + if (std::isnan(m_geo.lon()) || std::isnan(m_geo.lat())) + { + fprintf(stderr, "Error while fromGeographic, value is NaN\n"); + m_utm2D = LtsCoordTrans::UtmCoordinate(); + return; + } + +#ifdef LTS_DEBUG + ++s_conversion_counter; +#endif + + m_utm2D = LtsCoordTrans::UtmCoordinate::fromGeographic(m_geo.lon(), m_geo.lat(), zone); + Q_ASSERT_X(m_utm2D.zone > 0, "LtsCoordinate", "zone has to be set"); +} + +QString LtsCoordinate::mgrs(int prec, bool withSpaces) const +{ + utmCoordinateInternal(); + QString tmp = m_utm2D.mgrs(prec, withSpaces); + return tmp; +} + +QString LtsCoordinate::mgrsWithZ(int prec, bool withSpaces) const +{ + double ele = elevationAt(); + QString tmp = mgrs(prec, withSpaces) + QStringLiteral(" z=") + QString::number(ele, 'f', 2); + return tmp; +} + + +QString LtsCoordinate::toString(bool fullAndExpensive) const +{ + if (fullAndExpensive) + { + utmCoordinateInternal(); + elevationAt(); + } + std::ostringstream stream; + stream << "LtsCoordinate(m_geo=" << m_geo + << ", m_utm2D.x " << m_utm2D.x << ", m_utm2D.y " << m_utm2D.y << ", m_utm2D.zone " << m_utm2D.zone; + if (!qFuzzyIsNull(m_utm2D.x) || !qFuzzyIsNull(m_utm2D.y)) + stream << ", m_utm2D.mgrs '" << m_utm2D.mgrs().toUtf8().constData() << "'"; + stream << ", elevation " << std::fixed << std::setprecision(3) << m_elevation + << ")"; + return QString::fromStdString(stream.str()); +} + +void LtsCoordinate::resetConversionCounter() +{ + s_conversion_counter = 0; +} +long LtsCoordinate::getConversionCounter() +{ + return s_conversion_counter; +} + + + +std::ostream &operator<<(std::ostream &stream, const LtsCoordinate &value) +{ + stream << value.toString().toStdString(); + return stream; +} + +} + + diff --git a/Lts/Common/LtsCoordinate/lts_coordinate.h b/Lts/Common/LtsCoordinate/lts_coordinate.h new file mode 100755 index 000000000..2f98e4d72 --- /dev/null +++ b/Lts/Common/LtsCoordinate/lts_coordinate.h @@ -0,0 +1,367 @@ + + + +#pragma once + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + + +namespace LtsCoordTrans +{ + +using UtmCoordinate2D = LtsCoordTrans::UtmCoordinate; +using QPoint3D = LtsUtils::QPoint3D; + + + + + +class LTSCOORDINATE_EXPORT LtsCoordinate +{ +public: + + + + + + + + explicit LtsCoordinate(double lon = 0, double lat = 0, double z = std::nan("")) + : m_geo(Core::GeoPosition(lon, lat, z)) + {} + + explicit LtsCoordinate(const QPointF &lonLat, double z = std::nan("")) + : m_geo(Core::GeoPosition(lonLat.x(), lonLat.y(), z)) + {} + + explicit LtsCoordinate(const Core::GeoPosition &geoPos) + : m_geo(geoPos) + {} + + explicit LtsCoordinate(const Core::GeoPosition2D &geoPos2D) + : m_geo(geoPos2D.lon(), geoPos2D.lat(), std::nan("")) + {} + + + + LtsCoordinate(const LtsCoordTrans::UtmCoordinate &utm2D, double zValue) = delete; + + static LtsCoordinate fromGeoPosition(double lon, double lat, double z = std::nan("")) + { + return LtsCoordinate(lon, lat, z); + } + + + static LtsCoordinate fromUtmCoordinate(const LtsCoordTrans::UtmCoordinate &utm2D, double zValue) + { + Q_ASSERT_X(utm2D.zone > 0, "LtsCoordinate", "zone has to be set"); + + + + const QPointF &geoPnt = utm2D.geographic(); + + return LtsCoordinate(geoPnt.x(), geoPnt.y(), zValue); + } + + static LtsCoordinate fromUtm(const QPointF &utm2d, int zone, double zValue) + { + const LtsCoordTrans::UtmCoordinate utmCoord(zone, utm2d.x(), utm2d.y()); + const QPointF &geoPnt = utmCoord.geographic(); + return LtsCoordinate(geoPnt.x(), geoPnt.y(), zValue); + } + + + + + + + bool isValid() const { return hasGeo() || hasUtm(); } + bool hasGeo() const { return !(valueIsNullOrNan(m_geo.lon()) || + valueIsNullOrNan(m_geo.lat())); } + bool hasUtm() const { return m_utm2D.isValid(); } + + static bool valueIsNullOrNan(double value) { return qFuzzyIsNull(value) || std::isnan(value); } + + QString mgrs(int prec = 5, bool withSpaces = true) const; + QString mgrsWithZ(int prec = 5, bool withSpaces = true) const; + + QString toString(bool fullAndExpensive = false) const; + + + + static void resetConversionCounter(); + static long getConversionCounter(); + + + + + + const Core::GeoPosition &geoPosition() const + { + return m_geo; + } + const Core::GeoPosition2D &geoPosition2D() const + { + return m_geo; + } + + const LtsCoordTrans::UtmCoordinate &utmCoordinate() const + { + if (!hasUtm()) + convertGeoToUtm(); + return m_utm2D; + } + + + double &rz() { return m_geo.z(); } + double z() const { return m_geo.z(); } + void setZ(double value) { m_geo.z(value); } + + + LtsCoordTrans::UtmCoordinate utmCoordinateDiffZone(int zone) const + { + + + m_utm2D_differentZone = LtsCoordTrans::UtmCoordinate::fromGeographic(m_geo.lon(), m_geo.lat(), zone); + return m_utm2D_differentZone; + } + + + + + + template + T toUtm(int zone = -1) const + { + T obj; + helperToUtm(obj, zone); + return obj; + + + + } + + QPointF toUtmPointF() const { return toUtm(); } + QPoint3D toUtmPoint3D() const { return toUtm(); } + + QPointF toGeoPointF() const { return QPointF (m_geo.lon(), m_geo.lat()); } + QPoint3D toGeoPoint3D() const { return QPoint3D(m_geo.lon(), m_geo.lat(), m_geo.z() ); } + + + UtmCoordinate3D toUtmCoordinate3D() const + { + return UtmCoordinate3D(utmCoordinate(), m_geo.z()); + } + + + + + + using elevationCbFunc = std::function; + + + + + static void setElevationCallback(const elevationCbFunc &elevationCB); + static bool hasElevationCallback(); + + + + + double elevationAt(double defaultElevation = 0.0, bool *bIsCovered = nullptr) const; + void setZValueAccordingElevation(double defaultElevation = 0.0); + + static double elevationAt(const LtsCoordinate &coordinate, double defaultElevation = 0.0, bool *bIsCovered = nullptr); + + + + + + + + + + + + + + void replaceZValueByElevationIfValue(double zValueToBeReplaced = std::nan(""), double defaultElevation = 0.0); + + + + + + + + + double heightOverElevation() const { return m_geo.z() - elevationAt(); } + + + double distanceUtm2D(const LtsCoordinate &other) const + { + + + const LtsCoordTrans::UtmCoordinate &thisUtm2D = utmCoordinate(); + const LtsCoordTrans::UtmCoordinate &otherUtm2D = other.utmCoordinateDiffZone(m_utm2D.zone); + return LtsCoordTrans::distance(thisUtm2D, otherUtm2D); + } + + double distanceUtm3D(const LtsCoordinate &other) const + { + + + const QPoint3D &thisUtm3D = toUtm(); + const QPoint3D &otherUtm3D = other.toUtm(m_utm2D.zone); + const double dist3D = (thisUtm3D - otherUtm3D).length(); + + Q_ASSERT_X(!std::isnan(dist3D), "LtsCoordinate", "ERROR, distance3D is NaN"); + + return dist3D; + } + + + + + + + + + + static double angleDegreeUtm2D(const LtsCoordinate &coord1, const LtsCoordinate &coord2); + + + + + +protected: + + void clearGeo() const { clearGeoLonLat(); m_geo.z(std::nan("")); } + void clearGeoLonLat() const { m_geo.lon(0); m_geo.lat(0); } + void clearUtm() const { m_utm2D = m_utm2D_differentZone = UtmCoordinate2D(); } + void clearElevation() const { m_elevation = std::nan(""); } + void clear() const { clearGeo(); clearUtm(); clearElevation(); } + + const LtsCoordTrans::UtmCoordinate &utmCoordinateInternal(int zone = -1) const; + void convertGeoToUtm() const; + +protected: + + void helperToUtm(QPointF &pntRef, int zone = -1) const + { + const auto &utmTemp = utmCoordinateInternal(zone); + pntRef.rx() = utmTemp.x; + pntRef.ry() = utmTemp.y; + } + void helperToUtm(QPoint3D &pntRef, int zone = -1) const + { + const auto &utmTemp = utmCoordinateInternal(zone); + pntRef.rx() = utmTemp.x; + pntRef.ry() = utmTemp.y; + pntRef.rz() = m_geo.z(); + } + +protected: + mutable Core::GeoPosition m_geo; + mutable UtmCoordinate2D m_utm2D; + mutable UtmCoordinate2D m_utm2D_differentZone; + mutable double m_elevation{std::nan("")}; + + static elevationCbFunc m_getElevationCB; +}; + + + + +class LTSCOORDINATE_EXPORT LtsCoordinateNoImplicitCopy : public LtsCoordinate +{ +public: + using BaseClass = LtsCoordinate; + + + + LtsCoordinateNoImplicitCopy() = default; + explicit LtsCoordinateNoImplicitCopy (const LtsCoordinateNoImplicitCopy &) = default; + + + + + + + + + ~LtsCoordinateNoImplicitCopy() = default; + + + + + + + + explicit LtsCoordinateNoImplicitCopy(const QPointF &lonLat, double z = std::nan("")) + : BaseClass(Core::GeoPosition(lonLat.x(), lonLat.y(), z)) + {} + + explicit LtsCoordinateNoImplicitCopy(const Core::GeoPosition &geoPos) + : BaseClass(geoPos) + {} + + explicit LtsCoordinateNoImplicitCopy(const Core::GeoPosition2D &geoPos2D) + : BaseClass(geoPos2D) + {} + + + + + void updateGeoPosition(const Core::GeoPosition &geoPos) const + { + clear(); + m_geo = geoPos; + } +}; + + + + + + + + + + + + + + + + + + +template +T_OUT convert(const T_IN& contIn) +{ + T_OUT out; + out.reserve(contIn.size()); + for (const Core::GeoPosition &posGeo : qAsConst(contIn)) + out.push_back(LtsCoordinate(posGeo)); + return out; +} + + + +LTSCOORDINATE_EXPORT std::ostream &operator<< (std::ostream &stream, + const LtsCoordinate &value); + + +} + diff --git a/Lts/Common/LtsElevation/CMakeLists.txt b/Lts/Common/LtsElevation/CMakeLists.txt new file mode 100755 index 000000000..73eaec362 --- /dev/null +++ b/Lts/Common/LtsElevation/CMakeLists.txt @@ -0,0 +1,30 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +NGL_ADD_LIBRARY2(WS SV HLA LtsElevation SHARED + lts_elevation_manager.cpp + lts_elevation_manager.h + logger.cpp + logger.h +) +NGL_TARGET_INCLUDE_DIRECTORIES(LtsElevation + INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) +NGL_TARGET_LINK_LIBRARIES(LtsElevation + PRIVATE + NGLLogger +## LtsSessionDataDds + AsmDataPluginInterface + ExmDataPluginInterface + situationMapCommon + situationMapElevationMap + PUBLIC + LtsUtilsCoreHeaderOnly + Qt5::Core +) + +ADD_SUBDIRECTORY(UnitTest_Elevation) diff --git a/Lts/Common/LtsElevation/UnitTest_Elevation/CMakeLists.txt b/Lts/Common/LtsElevation/UnitTest_Elevation/CMakeLists.txt new file mode 100755 index 000000000..765499abc --- /dev/null +++ b/Lts/Common/LtsElevation/UnitTest_Elevation/CMakeLists.txt @@ -0,0 +1,29 @@ +# +# @RDE_HEADER@ +# $Id$ +# $HeadURL$ +# $Author$ +# + +SET(UnitTestSources + elevation_unit_test.cpp + elevation_unit_test.h +) + +IF(BUILD_TESTING) +ADD_EXECUTABLE(commenUnitTestElevation ${LinkedSources} ${UnitTestSources} ) + +TARGET_LINK_LIBRARIES(commenUnitTestElevation + NGLLogger + nglcoreapplication + LtsElevation + LtsCoordTrans + situationMapCommon + situationMapElevationMap + Qt5::Core + Qt5::Gui + Qt5::Test) + +ADD_TEST(NAME commenUnitTestElevation COMMAND $) + +ENDIF() diff --git a/Lts/Common/LtsElevation/UnitTest_Elevation/elevation_unit_test.cpp b/Lts/Common/LtsElevation/UnitTest_Elevation/elevation_unit_test.cpp new file mode 100755 index 000000000..f4c67e4c3 --- /dev/null +++ b/Lts/Common/LtsElevation/UnitTest_Elevation/elevation_unit_test.cpp @@ -0,0 +1,230 @@ + + + +#include "elevation_unit_test.h" + +#include +#include + +#include +#include +#include + + +Q_DECLARE_METATYPE(LtsUtils::QPoint3D); +Q_DECLARE_METATYPE(LtsElevation::LineOfSight); + +#include + +static const int s_id{4711}; +static LtsElevation::LtsElevationManager *s_eleMgr{nullptr}; + + + + + + + +class MyLtsElevationManagerBootstrap : public LtsElevation::LtsElevationManager +{ + using BaseClass = LtsElevation::LtsElevationManager; + +public: + static LtsElevationManager *createInstanceUnitTest(int id, const QStringList &filePaths_rough_to_fine) + { + return BaseClass::createInstanceUnitTest(id, filePaths_rough_to_fine); + } + +}; + + + + +UnitTestElevation::UnitTestElevation() + : m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("UnitTestElevation")) +{} + + +void UnitTestElevation::initTestCase() +{ + + + QString filePath(QStringLiteral("/usr/share/arcgis/resources/elevation/Altmark_dt1.tif")); + + s_eleMgr = MyLtsElevationManagerBootstrap::createInstanceUnitTest(s_id, {filePath}); + + const bool bIsValid = s_eleMgr->isValid(); + std::cerr << "myBootstrapMgr->isValid() 1 = " << bIsValid << std::endl; + + + { + s_eleMgr->waitForInitialization(); + const bool bIsValid = s_eleMgr->isValid(); + std::cerr << "myBootstrapMgr->isValid() 2 = " << bIsValid << std::endl; + } +} + +void UnitTestElevation::init() +{ + QVERIFY2(s_eleMgr , "static LtsElevationManager does not exist"); +} + + + + +void UnitTestElevation::test_elevation_data() +{ + QTest::addColumn("lonLat"); + QTest::addColumn("elevationRef"); + + + QTest::newRow("elevation 1 ") << LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32U PD 70000 10000")).geographic() << 75.0; + QTest::newRow("elevation 2 ") << LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32U PD 72000 13000")).geographic() << 80.0; + + + + + QTest::newRow("elevation 3 ") << LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32U PD 76925 18625")).geographic() << 92.0; +} + +void UnitTestElevation::test_elevation() +{ + QFETCH(QPointF, lonLat); + QFETCH(double, elevationRef); + + double elevation = s_eleMgr->elevation(lonLat.x(), lonLat.y()); + + QVERIFY2(qFuzzyCompare(elevationRef, elevation), " got wrong elevation result"); +} + +void UnitTestElevation::test_elevation_isCovered() +{ + const QPointF &lonLat1 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32U PD 70000 10000")).geographic(); + const QPointF &lonLat2 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32U MD 60000 10000")).geographic(); + + const double defaultElevationValue = -123; + { + bool bIsCovered = false; + s_eleMgr->elevation(lonLat1.x(), lonLat1.y(), defaultElevationValue, &bIsCovered); + QVERIFY2(bIsCovered, " error on bIsCovered"); + } + { + bool bIsCovered = true; + s_eleMgr->elevation(lonLat2.x(), lonLat2.y(), defaultElevationValue, &bIsCovered); + QVERIFY2(!bIsCovered, " error on bIsCovered"); + } +} + +void UnitTestElevation::test_elevation_defaultValue() +{ + const QPointF &lonLat = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32U PD 60000 10000")).geographic(); + + double elevation = s_eleMgr->elevation(lonLat.x(), lonLat.y()); + QVERIFY2(qFuzzyCompare(elevation, std::numeric_limits::min()), " error on defaultValue"); + + { + const double defaultValue = 100; + elevation = s_eleMgr->elevation(lonLat.x(), lonLat.y(), defaultValue); + QVERIFY2(qFuzzyCompare(elevation, defaultValue), " error on defaultValue"); + } + + { + const double defaultValue = std::numeric_limits::max(); + elevation = s_eleMgr->elevation(lonLat.x(), lonLat.y(), defaultValue); + QVERIFY2(qFuzzyCompare(elevation, defaultValue), " error on defaultValue"); + } +} + + + + +void UnitTestElevation::test_lineOfSightCalculation_data() +{ + QTest::addColumn("startGeo2D"); + QTest::addColumn("startObjH"); + QTest::addColumn("endGeo2D"); + QTest::addColumn("endObjH"); + QTest::addColumn("expectedResult"); + + + + + QTest::newRow("losDataset 1 ") << LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32U PD 60000 10000")).geographic() << 1.0 + << LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32U PD 60000 60000")).geographic() << 1.0 + << LtsElevation::LineOfSight::LOS_ERROR; + + + QTest::newRow("losDataset 2 ") << LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32U PD 77200 17600")).geographic() << 1.0 + << LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32U PD 77600 17600")).geographic() << 1.0 + << LtsElevation::LineOfSight::LOS_INVISIBLE; + + + QTest::newRow("losDataset 3 ") << LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32U PD 77200 17600")).geographic() << 20.0 + << LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32U PD 77600 17600")).geographic() << 20.0 + << LtsElevation::LineOfSight::LOS_VISIBLE; + + + QTest::newRow("losDataset 4 ") << LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32U PD 77200 17600")).geographic() << -1.0 + << LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32U PD 77600 17600")).geographic() << 1.0 + << LtsElevation::LineOfSight::LOS_ERROR; + QTest::newRow("losDataset 5 ") << LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32U PD 77200 17600")).geographic() << 1.0 + << LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32U PD 77600 17600")).geographic() << -1.0 + << LtsElevation::LineOfSight::LOS_ERROR; + QTest::newRow("losDataset 6 ") << LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32U PD 77200 17600")).geographic() << 0.0 + << LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32U PD 77600 17600")).geographic() << 0.0 + << LtsElevation::LineOfSight::LOS_INVISIBLE; + +} +void UnitTestElevation::test_lineOfSightCalculation() +{ + QFETCH(QPointF, startGeo2D); + QFETCH(double, startObjH); + QFETCH(QPointF, endGeo2D); + QFETCH(double, endObjH); + QFETCH(LtsElevation::LineOfSight, expectedResult); + + LtsUtils::QPoint3D start3D = LtsUtils::QPoint3D::fromQPointF(startGeo2D); + start3D.rz() = startObjH; + start3D.rz() += s_eleMgr->elevation(start3D.x(), start3D.y()); + + LtsUtils::QPoint3D end3D = LtsUtils::QPoint3D::fromQPointF(endGeo2D); + end3D.rz() = endObjH; + end3D.rz() += s_eleMgr->elevation(end3D.x(), end3D.y()); + + LtsElevation::LineOfSight los = s_eleMgr->lineOfSightCalculation(start3D, end3D); + NOTICE_LOG(m_logger) << "los = " << los; + + { + LtsElevation::LineOfSight los2 = s_eleMgr->lineOfSightCalculation(startGeo2D, startObjH, + endGeo2D, endObjH); + QVERIFY2(los == los2, " got different lineOfSightCalculation result"); + } + + + + QVERIFY2(los == expectedResult, " got lineOfSightCalculation result"); +} + + + + + +int main(int argc, char *argv[]) +{ + NGLCoreApplication app(argc, argv); + app.createLogger("UnitTestElevation", "log4cxxElevationUnitTest.props"); + + UnitTestElevation tc; + + int ret = QTest::qExec(&tc, argc, argv); + + + if (s_eleMgr) + { + s_eleMgr->cleanupAll(); + } + + return ret; +} + + diff --git a/Lts/Common/LtsElevation/UnitTest_Elevation/elevation_unit_test.h b/Lts/Common/LtsElevation/UnitTest_Elevation/elevation_unit_test.h new file mode 100755 index 000000000..6268babf1 --- /dev/null +++ b/Lts/Common/LtsElevation/UnitTest_Elevation/elevation_unit_test.h @@ -0,0 +1,56 @@ + + + +#pragma once + +#include + +#include + +#include +#include +#include + + +using LOGGER_T = NGL::INGLLogger&; + + + + +class UnitTestElevation : public QObject +{ + Q_OBJECT + +public: + UnitTestElevation(); + + +private Q_SLOTS: + + + + + + + void initTestCase(); + + void init(); + + + + + void test_elevation_data(); + void test_elevation(); + + void test_elevation_isCovered(); + void test_elevation_defaultValue(); + + void test_lineOfSightCalculation_data(); + void test_lineOfSightCalculation(); + + + + +private: + LOGGER_T m_logger; +}; diff --git a/Lts/Common/LtsElevation/logger.cpp b/Lts/Common/LtsElevation/logger.cpp new file mode 100755 index 000000000..c5b10f2fc --- /dev/null +++ b/Lts/Common/LtsElevation/logger.cpp @@ -0,0 +1,32 @@ + + + +#include "logger.h" + +#include + +namespace LtsElevation +{ + +LOGGER_T getLogger(const char *name) +{ + static const char ns[] = "Lts.Elevation."; + std::string loggerName; + const size_t len = strlen(name); + loggerName.reserve(sizeof(ns) + len); + loggerName.append(ns, sizeof(ns) - 1); + loggerName.append(name, len); + + try + { + return NGL::NGLLoggerBootstrap::getNGLRootLogger(). + getNGLLogger(loggerName); + } + catch (const std::exception &ex) + { + fprintf(stderr, "Error retrieving logger: %s", ex.what()); + abort(); + } +} + +} diff --git a/Lts/Common/LtsElevation/logger.h b/Lts/Common/LtsElevation/logger.h new file mode 100755 index 000000000..fa2ec1baa --- /dev/null +++ b/Lts/Common/LtsElevation/logger.h @@ -0,0 +1,18 @@ + + + + +#pragma once + +#include + +namespace LtsElevation +{ +typedef NGL::INGLLogger &LOGGER_T; + +LOGGER_T getLogger(const char *name); +inline LOGGER_T getLogger(const QString &loggerName) +{ + return getLogger(loggerName.toUtf8().constData()); +} +} diff --git a/Lts/Common/LtsElevation/lts_elevation_manager.cpp b/Lts/Common/LtsElevation/lts_elevation_manager.cpp new file mode 100755 index 000000000..f69fe8d90 --- /dev/null +++ b/Lts/Common/LtsElevation/lts_elevation_manager.cpp @@ -0,0 +1,510 @@ + + + +#include "lts_elevation_manager.h" +#include +#include + +#include +#include + +#include +#include +#include +#include + + + + +static_assert((int)SituationMap::ElevationMapManager::LineOfSight::LOS_UNDEFINED == (int)LtsElevation::LineOfSight::LOS_UNDEFINED, "LineOfSight mismatch"); +static_assert((int)SituationMap::ElevationMapManager::LineOfSight::LOS_ERROR == (int)LtsElevation::LineOfSight::LOS_ERROR, "LineOfSight mismatch"); +static_assert((int)SituationMap::ElevationMapManager::LineOfSight::LOS_VISIBLE == (int)LtsElevation::LineOfSight::LOS_VISIBLE, "LineOfSight mismatch"); +static_assert((int)SituationMap::ElevationMapManager::LineOfSight::LOS_INVISIBLE == (int)LtsElevation::LineOfSight::LOS_INVISIBLE, "LineOfSight mismatch"); + +namespace LtsElevation +{ +class LtsElevationManager; + + + +using LtsElevatioMgrMapTrainingAreaId = QMap; + + +static LtsElevatioMgrMapTrainingAreaId <sElevatioMgrMapTrainingAreaId() +{ + static LtsElevatioMgrMapTrainingAreaId s_ltsElevatioMgrMapTrainingAreaId; + return s_ltsElevatioMgrMapTrainingAreaId; +} + +static void cleanupLtsElevatioMgrMapTrainingAreaId() +{ + qDeleteAll(ltsElevatioMgrMapTrainingAreaId()); + ltsElevatioMgrMapTrainingAreaId().clear(); +} + +LtsElevatioMgrMapTrainingAreaId &getLtsElevationMgrMapTrainingAreaId() +{ + if (ltsElevatioMgrMapTrainingAreaId().isEmpty()) + qAddPostRoutine(cleanupLtsElevatioMgrMapTrainingAreaId); + + return ltsElevatioMgrMapTrainingAreaId(); +} + +static long s_counter{0}; + + + + +class LtsElevationManager::Private +{ +public: + Private() = default; + ~Private() + { + delete m_elevationManager; + } + std::shared_ptr m_exmDtc; + std::shared_ptr m_asmDtc; + + QStringList m_filePaths_rough_to_fine; + SituationMap::ElevationMapManager *m_elevationManager{nullptr}; +}; + + + + + +LtsElevationManager::LtsElevationManager() + : QObject(nullptr) + , d(new Private) + , m_logger(getLogger("LtsElevationManager")) +{ + ++s_counter; + TRACE_LOG(m_logger) << LOGFUNCTION << " START, s_counter=" << s_counter << " this=" << this; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, s_counter=" << s_counter << " this=" << this; +} + +LtsElevationManager::~LtsElevationManager() +{ + --s_counter; + TRACE_LOG(m_logger) << LOGFUNCTION << " START, s_counter=" << s_counter << " this=" << this; + delete d; + TRACE_LOG(m_logger) << LOGFUNCTION << " END, s_counter=" << s_counter << " this=" << this; +} + +LTS_ELEVATION_MANAGER_TYPE LtsElevationManager::createInstance(int exerciseId, + const std::shared_ptr &exmDtc, + const std::shared_ptr &asmDtc) +{ + LOGGER_T logger = getLogger("LtsElevationManager"); + TRACE_LOG(logger) << LOGFUNCTION << " START, exerciseId=" << exerciseId; + + if (exerciseId < 0) + { + ERROR_LOG(logger) << LOGFUNCTION << " exerciseId invalid, exerciseId=" << exerciseId; + return nullptr; + } + + int trainingAreaId = -1; + + + + + + static QHash s_exIdToTaIdStore; + + const auto exTaIt = s_exIdToTaIdStore.find(exerciseId); + if (exTaIt != s_exIdToTaIdStore.end()) + { + + + trainingAreaId = *exTaIt; + + } else { + + + + if (!exmDtc || !asmDtc) + { + ERROR_LOG(logger) << LOGFUNCTION << " no dtcs for exerciseId=" << exerciseId + << ", asmDtc=" << asmDtc + << ", exmDtc=" << exmDtc; + return nullptr; + } + + + trainingAreaId = exerciseIdToTrainingAreaId(exerciseId, exmDtc); + if (trainingAreaId <= 0) + { + ERROR_LOG(logger) << LOGFUNCTION << " Error getting trainingAreaId" + << ", exerciseId=" << exerciseId + << ", trainingAreaId=" << trainingAreaId; + return nullptr; + } + + + s_exIdToTaIdStore[exerciseId] = trainingAreaId; + + } + + + + + LTS_ELEVATION_MANAGER_TYPE temp = createInstanceForTrainingAreaId(trainingAreaId, asmDtc); + if (!temp) + { + ERROR_LOG(logger) << LOGFUNCTION << " Error on createInstanceForTrainingAreaId" + << ", exerciseId=" << exerciseId + << ", trainingAreaId=" << trainingAreaId; + return nullptr; + } + + + temp->initDDS(exmDtc, asmDtc); + + TRACE_LOG(logger) << LOGFUNCTION << " END, exerciseId=" << exerciseId; + return temp; +} + +LTS_ELEVATION_MANAGER_TYPE LtsElevationManager::getInstance(int exerciseId) +{ + return LtsElevationManager::createInstance(exerciseId); +} + +LTS_ELEVATION_MANAGER_TYPE LtsElevationManager::createInstanceForTrainingAreaId(int trainingAreaId, + const std::shared_ptr &asmDtc) +{ + LOGGER_T logger = getLogger("LtsElevationManager"); + TRACE_LOG(logger) << LOGFUNCTION << " START, trainingAreaId=" << trainingAreaId; + + if (trainingAreaId < 0) + { + ERROR_LOG(logger) << LOGFUNCTION << " trainingAreaId invalid, trainingAreaId=" << trainingAreaId; + return nullptr; + } + + + LtsElevatioMgrMapTrainingAreaId &map = getLtsElevationMgrMapTrainingAreaId(); + QMap::iterator it = map.find(trainingAreaId); + + + if (it != map.end()) + { + DEBUG_LOG(logger) << " LtsElevationMgrMap contains instances: " << map.size(); + return it.value(); + } + + + if (!asmDtc) + { + ERROR_LOG(logger) << LOGFUNCTION << " no dtcs for trainingAreaId=" << trainingAreaId + << ", asmDtc=" << asmDtc; + return nullptr; + } + + LTS_ELEVATION_MANAGER_TYPE temp = new LtsElevationManager(); + + + temp->initDDS(nullptr, asmDtc); + + const QStringList &filenames = temp->getFilenamesForTrainingArea(trainingAreaId); + if (filenames.isEmpty()) + { + ERROR_LOG(logger) << LOGFUNCTION << " no filenames for trainingAreaId=" << trainingAreaId; + delete temp; + return nullptr; + } + + + bool bInitFilenames = temp->init(filenames); + if (!bInitFilenames) + { + ERROR_LOG(logger) << LOGFUNCTION << " bInitFilenames failed for trainingAreaId=" << trainingAreaId; + delete temp; + return nullptr; + } + + map.insert(trainingAreaId, temp); + + DEBUG_LOG(logger) << " LtsElevationMgrMap contains instances now: " << map.size(); + + TRACE_LOG(logger) << LOGFUNCTION << " END, trainingAreaId=" << trainingAreaId; + return map[trainingAreaId]; +} + + + + +LTS_ELEVATION_MANAGER_TYPE LtsElevationManager::createInstanceUnitTest(int id, const QStringList &filePaths_rough_to_fine) +{ + LOGGER_T logger = getLogger("LtsElevationManager"); + + LtsElevatioMgrMapTrainingAreaId &map = getLtsElevationMgrMapTrainingAreaId(); + QMap::iterator it = map.find(id); + + + if (it != map.end()) + { + DEBUG_LOG(logger) << " LtsElevationMgrMap contains instances: " << map.size(); + return it.value(); + } + + + LTS_ELEVATION_MANAGER_TYPE temp = new LtsElevationManager(); + + + bool bInitFilenames = temp->init(filePaths_rough_to_fine); + if (!bInitFilenames) + { + ERROR_LOG(logger) << LOGFUNCTION << " bInitFilenames failed for id=" << id; + delete temp; + return nullptr; + } + + map.insert(id, temp); + + DEBUG_LOG(logger) << " LtsElevationMgrMap contains instances now: " << map.size(); + + TRACE_LOG(logger) << LOGFUNCTION << " END, trainingAreaId=" << id; + return map[id]; + + + +} + + +void LtsElevationManager::cleanupAll() +{ + LOGGER_T logger = getLogger("LtsElevationManager"); + TRACE_LOG(logger) << LOGFUNCTION << " START"; + cleanupLtsElevatioMgrMapTrainingAreaId(); + TRACE_LOG(logger) << LOGFUNCTION << " END"; +} + +void LtsElevationManager::initDDS(const std::shared_ptr &exmDtc, const std::shared_ptr &asmDtc) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + d->m_exmDtc = exmDtc; + if (d->m_exmDtc) + d->m_exmDtc->waitForHistoricalDataExercise(1000); + + d->m_asmDtc = asmDtc; + if (d->m_asmDtc) + d->m_asmDtc->waitForHistoricalDataTrainingArea(1000); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +QStringList LtsElevationManager::getFilenamesForExercise(int exerciseId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!d->m_exmDtc || !d->m_asmDtc) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no dtcs" + << ", exerciseId=" << exerciseId; + return {}; + } + + + int trainingAreaId = exerciseIdToTrainingAreaId(exerciseId, d->m_exmDtc); + if (trainingAreaId <= 0) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Error getting trainingAreaId" + << ", exerciseId=" << exerciseId + << ", trainingAreaId=" << trainingAreaId; + return {}; + } + + QStringList filePaths_rough_to_fine = getFilenamesForTrainingArea(trainingAreaId); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END, filePaths_rough_to_fine=" << filePaths_rough_to_fine.join(QStringLiteral(";")); + return filePaths_rough_to_fine; +} + +QStringList LtsElevationManager::getFilenamesForTrainingArea(int trainingAreaId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + + if (!d->m_asmDtc) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " no dtcs" + << ", trainingAreaId=" << trainingAreaId; + return {}; + } + + + QStringList filePaths_rough_to_fine; + { + d->m_asmDtc->waitForHistoricalDataTrainingArea(1000); + const AssetManagement::TrainingArea &ta = d->m_asmDtc->trainingArea(trainingAreaId); + if (ta.id() != trainingAreaId) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Error on s_asmDtc->trainingArea" + << ", trainingAreaId=" << trainingAreaId; + return {}; + } + const std::vector &mdVec = ta.elevation(); + filePaths_rough_to_fine.reserve(mdVec.size()); + + for (const AssetManagement::MapDescription &md : qAsConst(mdVec)) + { + + QUrl url(QString::fromStdString(md.url())); + QString filePathAbs = url.toLocalFile(); + if (QDir::isRelativePath(filePathAbs)) + filePathAbs = SituationMap::getResourcePath() + QStringLiteral("/elevation/") + filePathAbs; + + filePaths_rough_to_fine.append(filePathAbs); + } + } + TRACE_LOG(m_logger) << LOGFUNCTION << " END, filePaths_rough_to_fine=" << filePaths_rough_to_fine.join(QStringLiteral(";")); + return filePaths_rough_to_fine; +} + +bool LtsElevationManager::init(const QStringList &filePaths_rough_to_fine) +{ + delete d->m_elevationManager; + + d->m_elevationManager = new SituationMap::ElevationMapManager(); + int cnt = d->m_elevationManager->init(filePaths_rough_to_fine); + + if (cnt != filePaths_rough_to_fine.size()) + { + FATAL_LOG(m_logger) << LOGFUNCTION << " ERROR, not all files could be loaded !!!"; + FATAL_LOG(m_logger) << LOGFUNCTION << " ERROR, not all files could be loaded !!!"; + FATAL_LOG(m_logger) << LOGFUNCTION << " ERROR, not all files could be loaded !!!"; + FATAL_LOG(m_logger) << LOGFUNCTION << " ERROR, not all files could be loaded !!!"; + FATAL_LOG(m_logger) << LOGFUNCTION << " ERROR, not all files could be loaded !!!"; + FATAL_LOG(m_logger) << LOGFUNCTION << " ERROR, not all files could be loaded !!!"; + FATAL_LOG(m_logger) << LOGFUNCTION << " ERROR, not all files could be loaded !!!"; + return false; + } + return true; +} + +int LtsElevationManager::exerciseIdToTrainingAreaId(int exerciseId, const std::shared_ptr &exmDtc) +{ + LOGGER_T logger = getLogger("LtsElevationManager"); + + if (!exmDtc) + { + ERROR_LOG(logger) << LOGFUNCTION << " no dtcs" + << ", exerciseId=" << exerciseId; + return {}; + } + + + int trainingAreaId = -1; + { + exmDtc->waitForHistoricalDataExercise(1000); + + const ExerciseManagement::Exercise &exm = exmDtc->exercise(exerciseId); + if (exm.id() != exerciseId) + { + ERROR_LOG(logger) << LOGFUNCTION << " Error on m_exmDtc->exercise" + << ", exerciseId=" << exerciseId; + return {}; + } + trainingAreaId = exm.trainingAreaId(); + + if (trainingAreaId <= 0) + { + ERROR_LOG(logger) << LOGFUNCTION << " Error getting trainingAreaId" + << ", exerciseId=" << exerciseId + << ", trainingAreaId=" << trainingAreaId; + return -1; + } + } + return trainingAreaId; +} + +bool LtsElevationManager::waitForInitialization() +{ + if (!d->m_elevationManager) + return false; + return d->m_elevationManager->waitForInitialization(); +} + +bool LtsElevationManager::isValid() const +{ + if (!d->m_elevationManager) + return false; + return d->m_elevationManager->isValid(); +} + +double LtsElevationManager::elevation(double lon_rad, double lat_rad, double defaultValue, bool *bIsCovered) const +{ + if (!d->m_elevationManager) + return defaultValue; + return d->m_elevationManager->elevation(lon_rad, lat_rad, defaultValue, bIsCovered); +} + +LineOfSight LtsElevationManager::lineOfSightCalculation(const LtsUtils::QPoint3D &startPointGeo_absZ, + const LtsUtils::QPoint3D &endPointGeo_absZ) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << ", startPointGeo_absZ = " << startPointGeo_absZ << ", endPointGeo_absZ = " << endPointGeo_absZ; + + if (!isValid()) + { + ERROR_LOG(m_logger) << LOGFUNCTION << " Error, LtsElevationManager not valid"; + return LineOfSight::LOS_ERROR; + } + + + SituationMap::ElevationMapManager::LineOfSight eleMapMgrResult = d->m_elevationManager->lineOfSightCalculation(startPointGeo_absZ,endPointGeo_absZ); + + + LtsElevation::LineOfSight result = static_cast(eleMapMgrResult); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" << ", startPointGeo = " << startPointGeo_absZ << ", endPointGeo = " << endPointGeo_absZ + << " -> LineOfSight " << result; + return result; +} + +LineOfSight LtsElevationManager::lineOfSightCalculation(const QPointF &startPointGeo, double startHat, const QPointF &endPointGeo, double endHat) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << ", startPointGeo = " << startPointGeo << ", startHat = " << startHat + << ", endPointGeo = " << endPointGeo << ", endHat = " << endHat; + + + SituationMap::ElevationMapManager::LineOfSight eleMapMgrResult = d->m_elevationManager->lineOfSightCalculation(startPointGeo, startHat, + endPointGeo, endHat); + + + LtsElevation::LineOfSight result = static_cast(eleMapMgrResult); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << ", startPointGeo = " << startPointGeo << ", startHat = " << startHat + << ", endPointGeo = " << endPointGeo << ", endHat = " << endHat + << " -> LineOfSight " << static_cast(eleMapMgrResult); + return result; +} + + +const QString toString(const LineOfSight &value) +{ + switch (value) + { + case LineOfSight::LOS_UNDEFINED: + return QStringLiteral("LOS_UNDEFINED"); + case LineOfSight::LOS_ERROR: + return QStringLiteral("LOS_ERROR"); + case LineOfSight::LOS_VISIBLE: + return QStringLiteral("LOS_VISIBLE"); + case LineOfSight::LOS_INVISIBLE: + return QStringLiteral("LOS_INVISIBLE"); + } + Q_ASSERT(false); + return QStringLiteral("LineOfSightERROR"); +} + +std::ostream &operator<<(std::ostream &stream, const LineOfSight value) +{ + stream << toString(value).toStdString(); + return stream; +} + +} diff --git a/Lts/Common/LtsElevation/lts_elevation_manager.h b/Lts/Common/LtsElevation/lts_elevation_manager.h new file mode 100755 index 000000000..85c73fbe8 --- /dev/null +++ b/Lts/Common/LtsElevation/lts_elevation_manager.h @@ -0,0 +1,129 @@ + + + +#pragma once + +#include "logger.h" +#include +#include + + + +#include +#include +#include +#include + +#include +#include + + + +namespace LtsSession +{ +class LtsSessionDataDds; +} + +namespace ExerciseManagement +{ +namespace Interface +{ +class Dtc; +} +} + +namespace AssetManagement +{ +namespace Interface +{ +class Dtc; +} +} + + +namespace LtsElevation +{ + +enum class LineOfSight { LOS_UNDEFINED, + LOS_ERROR, + LOS_VISIBLE, + LOS_INVISIBLE }; + +class LtsElevationManager; +using LTS_ELEVATION_MANAGER_TYPE = QPointer; + + + + +class LTSELEVATION_EXPORT LtsElevationManager : public QObject +{ +public: + + + + + + static LTS_ELEVATION_MANAGER_TYPE createInstance(int exerciseId, + const std::shared_ptr &exmDtc = {}, + const std::shared_ptr &asmDtc = {}); + + static LTS_ELEVATION_MANAGER_TYPE getInstance(int exerciseId); + + + static LTS_ELEVATION_MANAGER_TYPE createInstanceForTrainingAreaId(int trainingAreaId, + const std::shared_ptr &asmDtc = nullptr); + + + + + static void cleanupAll(); + virtual ~LtsElevationManager(); + + + bool waitForInitialization(); + bool isValid() const; + + double elevation(double lon_rad, double lat_rad, double defaultValue = std::numeric_limits::min(), bool *bIsCovered = nullptr) const; + + LineOfSight lineOfSightCalculation(const LtsUtils::QPoint3D &startPointGeo_absZ, + const LtsUtils::QPoint3D &endPointGeo_absZ); + + LineOfSight lineOfSightCalculation(const QPointF &startPointGeo, double startHat, + const QPointF &endPointGeo, double endHat); + +protected: + explicit LtsElevationManager(); + + + + void initDDS(const std::shared_ptr &exmDtc, + const std::shared_ptr &asmDtc); + bool init(const QStringList &filePaths_rough_to_fine); + static int exerciseIdToTrainingAreaId(int exerciseId, const std::shared_ptr &exmDtc); + + + QStringList getFilenamesForExercise(int exerciseId); + QStringList getFilenamesForTrainingArea(int trainingAreaId); + + + + + static LTS_ELEVATION_MANAGER_TYPE createInstanceUnitTest(int id, const QStringList &filePaths_rough_to_fine); + +private: + + + class Private; + + Private *d; + LOGGER_T m_logger; +}; + +LTSELEVATION_EXPORT const QString toString(const LtsElevation::LineOfSight &value); + +LTSELEVATION_EXPORT std::ostream &operator<< (std::ostream &stream, + const LtsElevation::LineOfSight value); + + + +} diff --git a/Lts/Common/LtsLicence/CMakeLists.txt b/Lts/Common/LtsLicence/CMakeLists.txt new file mode 100755 index 000000000..7b0002bb2 --- /dev/null +++ b/Lts/Common/LtsLicence/CMakeLists.txt @@ -0,0 +1,41 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# Created by hjl, SLSE, 2016-05-31 +# + +FIND_PACKAGE(OpenSSL REQUIRED) + +SET(ltslicence_SRCS + aes_crypt.cpp + aes_crypt.h + lts_licence.cpp + lts_licence.h +) + +NGL_ADD_LIBRARY2(AAR SV WS LtsLicence SHARED ${ltslicence_SRCS}) + +NGL_TARGET_INCLUDE_DIRECTORIES(LtsLicence + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) +NGL_TARGET_LINK_LIBRARIES(LtsLicence + PRIVATE + ${OPENSSL_LIBRARIES} + Qt5::Xml + NGLLogger + PUBLIC + Qt5::Core +) + +IF (BUILD_TESTING AND TARGET LtsLicence) + ADD_EXECUTABLE(aes_crypt_test aes_crypt_test.cpp aes_crypt_test.h) + TARGET_LINK_LIBRARIES(aes_crypt_test + LtsLicence + NGLLogger + Qt5::Core + Qt5::Test + ) + ADD_TEST(NAME aes_crypt_test COMMAND $) +ENDIF() diff --git a/Lts/Common/LtsLicence/aes_crypt.cpp b/Lts/Common/LtsLicence/aes_crypt.cpp new file mode 100755 index 000000000..59e117898 --- /dev/null +++ b/Lts/Common/LtsLicence/aes_crypt.cpp @@ -0,0 +1,232 @@ + + + +#include "aes_crypt.h" +#include +#include +#include +#include + +namespace LtsLicence +{ +struct AesCryptPrivate +{ + AesCryptPrivate() + : passPhrase(QLatin1String("LTS " LTS_VERSION_MAJOR "." LTS_VERSION_MINOR)) + , logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("LtsLicenceLib.AesCrypt")) + { + qsrand(time(nullptr)); +#if OPENSSL_VERSION_NUMBER < 0x1010000fL + ERR_load_crypto_strings(); +#endif + } + ~AesCryptPrivate() + { +#if OPENSSL_VERSION_NUMBER < 0x1010000fL + ERR_free_strings(); +#endif + } + + QString passPhrase; + NGL::INGLLogger &logger; +}; +Q_GLOBAL_STATIC(AesCryptPrivate, s_Private) + +void setPassPhrase(const QString &passPhrase) +{ + s_Private->passPhrase = passPhrase; +} + +QString passPhrase() +{ + return s_Private->passPhrase; +} + +QByteArray decryptData(const QByteArray &dataToDecrypt, + const QString &pw, + bool *bOk) +{ + if (bOk) + *bOk = false; + + int ret; + int outSize1 = 0; + int outSize2 = 0; + unsigned char key[32]; + + + const QByteArray baPassword = pw.toUtf8(); + const unsigned char *dataPw = reinterpret_cast(baPassword.data()); + int i = EVP_BytesToKey(EVP_aes_256_cbc(), + EVP_sha1(), + nullptr, + dataPw, + baPassword.size(), + 5, + key, + nullptr); + if (i != 32) + { + ERROR_LOG(s_Private->logger) << "Wrong key size - should be 256 bits" ; + return QByteArray(); + } + + const QByteArray baSalt = dataToDecrypt.left(EVP_MAX_IV_LENGTH); + const unsigned char *salt = reinterpret_cast(baSalt.data()); + + const QByteArray bufEncrypted = dataToDecrypt.mid(EVP_MAX_IV_LENGTH); + + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + ret = EVP_DecryptInit_ex(ctx, + EVP_aes_256_cbc(), + nullptr, + key, + salt); + if (ret == 0) + { + EVP_CIPHER_CTX_free(ctx); + char errbuf[256]; + ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf)); + ERROR_LOG(s_Private->logger) << "EVP_DecryptInit_ex failed" ; + return QByteArray(); + } + + + QByteArray bufDecrypted; + bufDecrypted.resize(bufEncrypted.size() + EVP_CIPHER_CTX_block_size(ctx)); + const unsigned char *dataIn = reinterpret_cast(bufEncrypted.data()); + unsigned char *dataOut = reinterpret_cast(bufDecrypted.data()); + + ret = EVP_DecryptUpdate(ctx, + dataOut, + &outSize1, + dataIn, + bufEncrypted.size()); + if (ret == 0) + { + EVP_CIPHER_CTX_free(ctx); + char errbuf[256]; + ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf)); + ERROR_LOG(s_Private->logger) << "EVP_DecryptUpdate failed" ; + return QByteArray(); + } + + ret = EVP_DecryptFinal_ex(ctx, + dataOut + outSize1, + &outSize2); + if (ret == 0) + { + EVP_CIPHER_CTX_free(ctx); + char errbuf[256]; + ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf)); + ERROR_LOG(s_Private->logger) << "EVP_DecryptFinal_ex failed" ; + return QByteArray(); + } + + EVP_CIPHER_CTX_free(ctx); + + bufDecrypted.resize(outSize1 + outSize2); + + if (bOk) + *bOk = true; + return bufDecrypted; +} + + +QByteArray encryptData(const QByteArray &dataToEncrypt, + const QString &pw, + bool *bOk) +{ + if (bOk) + *bOk = false; + + int ret; + int outSize1 = 0; + int outSize2 = 0; + unsigned char key[32]; + QByteArray bufEncrypted; + + + const QByteArray baPassword = pw.toUtf8(); + const unsigned char *dataPw = reinterpret_cast(baPassword.data()); + int i = EVP_BytesToKey(EVP_aes_256_cbc(), + EVP_sha1(), + nullptr, + dataPw, + baPassword.size(), + 5, + key, + nullptr); + if (i != 32) + { + ERROR_LOG(s_Private->logger) << "Wrong key size - should be 256 bits" ; + return QByteArray(); + } + + + QByteArray baSalt(EVP_MAX_IV_LENGTH, 0); + for (int i = 0; i < EVP_MAX_IV_LENGTH; ++i) + baSalt[i] = qrand(); + const unsigned char *salt = reinterpret_cast(baSalt.data()); + + + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + ret = EVP_EncryptInit_ex(ctx, + EVP_aes_256_cbc(), + nullptr, + key, + salt); + if (ret == 0) + { + EVP_CIPHER_CTX_free(ctx); + char errbuf[256]; + ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf)); + ERROR_LOG(s_Private->logger) << "EVP_EncryptInit_ex failed" ; + return QByteArray(); + } + + + bufEncrypted = QByteArray::fromRawData(reinterpret_cast(salt), EVP_MAX_IV_LENGTH); + bufEncrypted.resize(bufEncrypted.size() + + dataToEncrypt.size() + + EVP_CIPHER_CTX_block_size(ctx)); + const unsigned char *dataIn = reinterpret_cast(dataToEncrypt.data()); + unsigned char *dataOut = reinterpret_cast(bufEncrypted.data()); + dataOut += baSalt.size(); + ret = EVP_EncryptUpdate(ctx, + dataOut, + &outSize1, + dataIn, + dataToEncrypt.size()); + if (ret == 0) + { + EVP_CIPHER_CTX_free(ctx); + char errbuf[256]; + ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf)); + ERROR_LOG(s_Private->logger) << "EVP_EncryptUpdate failed" ; + return QByteArray(); + } + + + ret = EVP_EncryptFinal_ex(ctx, + dataOut + outSize1, + &outSize2); + if (ret == 0) + { + EVP_CIPHER_CTX_free(ctx); + char errbuf[256]; + ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf)); + ERROR_LOG(s_Private->logger) << "EVP_EncryptFinal_ex failed" ; + return QByteArray(); + } + + EVP_CIPHER_CTX_free(ctx); + + bufEncrypted.resize(outSize1 + outSize2 + baSalt.size()); + + if (bOk) + *bOk = true; + return bufEncrypted; +} + +} diff --git a/Lts/Common/LtsLicence/aes_crypt.h b/Lts/Common/LtsLicence/aes_crypt.h new file mode 100755 index 000000000..45fd7dc90 --- /dev/null +++ b/Lts/Common/LtsLicence/aes_crypt.h @@ -0,0 +1,22 @@ + + + +#pragma once + +#include +#include + +namespace LtsLicence +{ + +LTSLICENCE_EXPORT void setPassPhrase(const QString &passPhrase); +LTSLICENCE_EXPORT QString passPhrase(); + +LTSLICENCE_EXPORT QByteArray decryptData(const QByteArray &dataToDecrypt, + const QString &pw = passPhrase(), + bool *bOk = nullptr); + +LTSLICENCE_EXPORT QByteArray encryptData(const QByteArray &dataToEncrypt, + const QString &pw = passPhrase(), + bool *bOk = nullptr); +} diff --git a/Lts/Common/LtsLicence/aes_crypt_test.cpp b/Lts/Common/LtsLicence/aes_crypt_test.cpp new file mode 100755 index 000000000..1c68bc31d --- /dev/null +++ b/Lts/Common/LtsLicence/aes_crypt_test.cpp @@ -0,0 +1,54 @@ + + + +#include +#include +#include +#include + +#include "aes_crypt.h" +#include "aes_crypt_test.h" + +QTEST_MAIN(aes_crypt_test) + +void aes_crypt_test::initTestCase() +{ + NGL::NGLLoggerBootstrap::createNGLRootLogger("log4cxx.props", false); +} + +void aes_crypt_test::truePassTest() +{ + qDebug("Simple TestCase"); + bool bOk; + const QByteArray content("30.06.2013"); + const QString pw(QStringLiteral("aes_crypt_test")); + const QByteArray enc = LtsLicence::encryptData(content, pw, &bOk); + QVERIFY2(bOk, "Error encrypting data..."); + + QCOMPARE(((content.size() + 15 + EVP_MAX_IV_LENGTH) / 18) * 16, enc.size()); + const QByteArray dec = LtsLicence::decryptData(enc, pw, &bOk); + QVERIFY2(bOk, "Error encrypting data..."); + QCOMPARE(dec, content); + + + const QByteArray bigContent(12345, '1'); + const QByteArray bigEnc = LtsLicence::encryptData(bigContent, pw, &bOk); + QVERIFY2(bOk, "Error encrypting data..."); + + QCOMPARE(((bigContent.size() + 15 + EVP_MAX_IV_LENGTH) / 16) * 16, bigEnc.size()); +} + +void aes_crypt_test::falsePassTest() +{ + qDebug("False Password TestCase"); + bool bOk; + const QByteArray content("30.06.2013"); + const QString pw(QStringLiteral("aes_crypt_test")); + const QString pw2(QStringLiteral("aes_crypt_test2")); + const QByteArray enc = LtsLicence::encryptData(content, pw, &bOk); + QVERIFY2(bOk, "Error encrypting data..."); + QVERIFY2(enc.size() != 20, "Error encrypting data..."); + const QByteArray dec = LtsLicence::decryptData(enc, pw2, &bOk); + QVERIFY2(!bOk, "Error encrypting data..."); + QVERIFY2(dec.isEmpty(), "decrypted data is not empty..."); +} diff --git a/Lts/Common/LtsLicence/aes_crypt_test.h b/Lts/Common/LtsLicence/aes_crypt_test.h new file mode 100755 index 000000000..49234c473 --- /dev/null +++ b/Lts/Common/LtsLicence/aes_crypt_test.h @@ -0,0 +1,15 @@ + + + +#pragma once + +#include + +class aes_crypt_test : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void truePassTest(); + void falsePassTest(); +}; diff --git a/Lts/Common/LtsLicence/lts_licence.cpp b/Lts/Common/LtsLicence/lts_licence.cpp new file mode 100755 index 000000000..575eb2031 --- /dev/null +++ b/Lts/Common/LtsLicence/lts_licence.cpp @@ -0,0 +1,173 @@ + + + + +#include "lts_licence.h" +#include +#include +#include + +namespace LtsLicence +{ +static bool extractLicence(const QDomElement &licenceElement, QVector &licenceVec); +static NGL::INGLLogger &getLogger() +{ + return NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("LtsLicence"); +} + +QVector readLicences(const QByteArray &unencryptedXmlData) +{ + QVector licenceVec; + + QDomDocument xmlDoc; + QString errorMsg; + int errorLine = 0; + int errorColumn = 0; + + if (!xmlDoc.setContent(unencryptedXmlData, &errorMsg, &errorLine, &errorColumn)) + { + ERROR_LOG(getLogger()) << "Error reading xml data, " << "error in line " << errorLine + << ", column " << errorColumn + << ", message: " << errorMsg; + return licenceVec; + } + + INFO_LOG(getLogger()) << "Try to extract licences"; + + QDomElement licenceElement = xmlDoc.documentElement().firstChildElement(QLatin1String("licence")); + + while (!licenceElement.isNull()) + { + if (!extractLicence(licenceElement, licenceVec)) + { + licenceVec.clear(); + return licenceVec; + } + + licenceElement = licenceElement.nextSiblingElement(QLatin1String("licence")); + } + return licenceVec; +} + + +QByteArray writeUnencryptedLicences(const QVector &licences) +{ + QDomDocument xmlDoc; + + QDomProcessingInstruction instr = xmlDoc.createProcessingInstruction(QLatin1String("xml"), + QLatin1String("version='1.0' encoding='UTF-8' standalone='yes'")); + xmlDoc.appendChild(instr); + + QDomElement root = xmlDoc.createElement(QLatin1String("licences")); + xmlDoc.appendChild(root); + + for (int i = 0; i < licences.size(); ++i) + { + QDomElement lic = xmlDoc.createElement(QLatin1String("licence")); + root.appendChild(lic); + + + QDomElement element = xmlDoc.createElement(QLatin1String("name")); + QDomText text = xmlDoc.createTextNode(licences[i].name); + element.appendChild(text); + lic.appendChild(element); + + + element = xmlDoc.createElement(QLatin1String("ignoreMaxCount")); + text = xmlDoc.createTextNode(QString::number(licences[i].ignoreMaxCount)); + element.appendChild(text); + lic.appendChild(element); + + + element = xmlDoc.createElement(QLatin1String("ignoreExpiration")); + text = xmlDoc.createTextNode(QString::number(licences[i].ignoreExpiration)); + element.appendChild(text); + lic.appendChild(element); + + + element = xmlDoc.createElement(QLatin1String("maxCount")); + text = xmlDoc.createTextNode(QString::number(licences[i].maxCount)); + element.appendChild(text); + lic.appendChild(element); + + + const QLatin1String format("dd.MM.yyyy hh:mm:ss"); + const QString qs = licences[i].expiration.toString(format); + + + element = xmlDoc.createElement(QLatin1String("expiration")); + text = xmlDoc.createTextNode(qs); + element.appendChild(text); + lic.appendChild(element); + } + INFO_LOG(getLogger()) << "Generated XML Data...\n" << xmlDoc.toByteArray(); + return xmlDoc.toByteArray(); +} + + +bool extractLicence(const QDomElement &licenceElement, QVector &licenceVec) +{ + Licence licence; + + + QDomElement tag = licenceElement.firstChildElement(QLatin1String("name")); + licence.name = tag.text(); + + + tag = licenceElement.firstChildElement(QLatin1String("ignoreMaxCount")); + licence.ignoreMaxCount = tag.text().toInt(); + + + tag = licenceElement.firstChildElement(QLatin1String("ignoreExpiration")); + licence.ignoreExpiration = tag.text().toInt(); + + + tag = licenceElement.firstChildElement(QLatin1String("maxCount")); + licence.maxCount = tag.text().toInt(); + + + tag = licenceElement.firstChildElement(QLatin1String("expiration")); + + const QString qs(tag.text()); + + + const QLatin1String format("dd.MM.yyyy hh:mm:ss"); + const QDateTime qdt = QDateTime::fromString(qs, format); + + if (!qdt.isValid()) + { + ERROR_LOG(getLogger()) << "Invalid expiration format! " << qs + << " used format: " << format + ; + return false; + } + + licence.expiration = qdt; + INFO_LOG(getLogger()) << "Read licence for:'" << licence.name << "'" + << " IgnoreMaxCount: " << licence.ignoreMaxCount + << " IgnoreExpiration: " << licence.ignoreExpiration + << " MaxCount: " << licence.maxCount + << " Expiration: " << licence.expiration.date().toString() + << " " << licence.expiration.time().toString() + << " RefCount: " << licence.licenceRefCount + ; + + licenceVec.append(licence); + return true; +} + + + +QString Licence::toString() const +{ + return QObject::tr("Name: %1, Count: %2, ExpireDate: %3, Expires: %4, UseMaxCount: %5") + .arg(name) + .arg(maxCount) + .arg(expiration.toString()) + .arg(ignoreExpiration) + .arg(ignoreMaxCount); +} + + + +} diff --git a/Lts/Common/LtsLicence/lts_licence.h b/Lts/Common/LtsLicence/lts_licence.h new file mode 100755 index 000000000..d996756f2 --- /dev/null +++ b/Lts/Common/LtsLicence/lts_licence.h @@ -0,0 +1,33 @@ + + + +#pragma once + +#include +#include +#include +#include + +namespace LtsLicence +{ + +struct LTSLICENCE_EXPORT Licence +{ + QString name; + bool ignoreMaxCount = false; + bool ignoreExpiration = false; + int maxCount = 0; + QDateTime expiration; + int licenceRefCount = 0; + + QString toString() const; +}; + + + +LTSLICENCE_EXPORT QVector readLicences(const QByteArray &unencryptedXmlData); + + + +LTSLICENCE_EXPORT QByteArray writeUnencryptedLicences(const QVector &licences); +} diff --git a/Lts/Common/LtsLogin/CMakeLists.txt b/Lts/Common/LtsLogin/CMakeLists.txt new file mode 100755 index 000000000..d91928f35 --- /dev/null +++ b/Lts/Common/LtsLogin/CMakeLists.txt @@ -0,0 +1,42 @@ +# +# Library which automatically provides Login/Logoff so that user data (rights for apps, etc.) is available before any app is started +# +# $Id$ +# $HeadURL$ +# $Author$ +# Created by Mike Wille, SLSE, 2016-10-27 +# + + +# LtsLogin + +set(LtsLogin_SRCS + lts_login.cpp + logout_info_dialog.cpp +) + +NGL_WRAP_UI(LtsLogin_SRCS + logout_info_dialog.ui +) + +NGL_ADD_LIBRARY2(WS LtsLogin SHARED ${LtsLogin_SRCS}) +NGL_TARGET_INCLUDE_DIRECTORIES(LtsLogin + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) + +NGL_TARGET_LINK_LIBRARIES(LtsLogin + PRIVATE + LtsAppDescriptor + LtsPluginManager + LtsSession + LtsSessionData + LtsWidgets + LtsUtilsSettings + LtsUtilsCoreHeaderOnly + PUBLIC + OpmDataPluginInterface + SrmDataPluginInterface + AsmDataPluginInterface + Qt5::Core +) diff --git a/Lts/Common/LtsLogin/logout_info_dialog.cpp b/Lts/Common/LtsLogin/logout_info_dialog.cpp new file mode 100755 index 000000000..4861822d4 --- /dev/null +++ b/Lts/Common/LtsLogin/logout_info_dialog.cpp @@ -0,0 +1,94 @@ + + + +#include "logout_info_dialog.h" +#include "ui_logout_info_dialog.h" + +#include + + +namespace LtsLogin +{ + +LogoutInformationDialog::LogoutInformationDialog(int timeOut, AssetManagement::ShutdownAction shutdownAction, QWidget *parent) + : LtsWidgets::LtsFramelessDialog(parent, LtsWidgets::Behaviour::Modal) + , m_ui(new Ui::LogoutInformationDialog) + , m_timer(new QTimer(this)) + , m_timeOut(timeOut) + , m_secsLeft(timeOut) +{ + m_ui->setupUi(this); + QString actionText; + switch(shutdownAction.underlying()) + { + case AssetManagement::ShutdownAction::NoAction: + case AssetManagement::ShutdownAction::Logout: + actionText = tr("logout"); + break; + case AssetManagement::ShutdownAction::Shutdown: + actionText = tr("shutdown"); + break; + case AssetManagement::ShutdownAction::Reboot: + actionText = tr("reboot"); + break; + } + + setText(tr("A %1 was triggered by a remote workstation.").arg(actionText)); + + connect(m_ui->buttonBox->getButton(LtsWidgets::DialogButton::Ok), &QPushButton::clicked, this, &LogoutInformationDialog::acceptClicked); + connect(m_ui->buttonBox->getButton(LtsWidgets::DialogButton::Close), &QPushButton::clicked, this, &LogoutInformationDialog::closeClicked); + connect(m_timer, &QTimer::timeout, this, &LogoutInformationDialog::onTimerTimeout); + + m_timer->setInterval(1000); +} + +LogoutInformationDialog::~LogoutInformationDialog() +{ + delete m_ui; +} + +void LogoutInformationDialog::setText(const QString &text) +{ + m_ui->lblLogoutText->setText(text); +} + +void LogoutInformationDialog::onTimerTimeout() +{ + if (m_secsLeft > 0) + { + m_secsLeft--; + setCloseButtonText(m_secsLeft); + } + else + { + acceptClicked(); + } +} + +void LogoutInformationDialog::acceptClicked() +{ + m_timer->stop(); + accept(); +} + +void LogoutInformationDialog::closeClicked() +{ + m_timer->stop(); + reject(); + +} + +void LogoutInformationDialog::setCloseButtonText(int secsLeft) +{ + m_ui->buttonBox->getButton(LtsWidgets::DialogButton::Close)->setText(tr("Abort [%1]").arg(secsLeft)); +} + +void LogoutInformationDialog::showEvent(QShowEvent *event) +{ + m_secsLeft = m_timeOut; + setCloseButtonText(m_secsLeft); + m_timer->start(); + LtsWidgets::LtsFramelessDialog::showEvent(event); +} + +} diff --git a/Lts/Common/LtsLogin/logout_info_dialog.h b/Lts/Common/LtsLogin/logout_info_dialog.h new file mode 100755 index 000000000..c1938baaf --- /dev/null +++ b/Lts/Common/LtsLogin/logout_info_dialog.h @@ -0,0 +1,43 @@ + + + +#pragma once + +#include +#include + +class QTimer; + +namespace Ui { class LogoutInformationDialog; } + +namespace LtsLogin +{ + +class LogoutInformationDialog : public LtsWidgets::LtsFramelessDialog +{ + Q_OBJECT + +public: + LogoutInformationDialog(int timeOut, AssetManagement::ShutdownAction shutdownAction, QWidget *parent = nullptr); + ~LogoutInformationDialog(); + +private slots: + void onTimerTimeout(); + void acceptClicked(); + void closeClicked(); + +private: + void setText(const QString &text); + void setCloseButtonText(int secsLeft); + + Ui::LogoutInformationDialog *m_ui; + QTimer *m_timer; + int m_timeOut; + int m_secsLeft; + + +protected: + void showEvent(QShowEvent *event) override final; +}; + +} diff --git a/Lts/Common/LtsLogin/logout_info_dialog.ui b/Lts/Common/LtsLogin/logout_info_dialog.ui new file mode 100755 index 000000000..dbd122e6e --- /dev/null +++ b/Lts/Common/LtsLogin/logout_info_dialog.ui @@ -0,0 +1,76 @@ + + + LogoutInformationDialog + + + + 0 + 0 + 400 + 165 + + + + Form + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + + + <logout information text> + + + Qt::AutoText + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + + + + + + + + + + + LtsWidgets::LtsDialogButtonBox + QWidget +
LtsWidgets/lts_dialog_button_box.h
+ 1 +
+
+ + +
diff --git a/Lts/Common/LtsLogin/lts_login.cpp b/Lts/Common/LtsLogin/lts_login.cpp new file mode 100755 index 000000000..ac04484f0 --- /dev/null +++ b/Lts/Common/LtsLogin/lts_login.cpp @@ -0,0 +1,312 @@ + + +#include "lts_login.h" +#include "logout_info_dialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +namespace LtsLogin +{ +const int logoutShutdownTimeout = 2 * 60; + +LtsLogin::LtsLogin() + : m_loginApp(nullptr) + , m_shutdownAction(AssetManagement::ShutdownAction::NoAction) + , m_isInLogoutDialog(false) +{} + + +LtsLogin::~LtsLogin() +{ + delete m_loginApp; + m_loginApp = nullptr; +} + + +void LtsLogin::login() +{ + LtsAppDescriptor::PluginInfo opmDataPlugin; + opmDataPlugin.iid = QStringLiteral("opm"); + opmDataPlugin.type = LtsAppDescriptor::PluginInfo::PluginType::DATA; + + LtsAppDescriptor::PluginInfo pdmDataPlugin; + pdmDataPlugin.iid = QStringLiteral("pdm"); + pdmDataPlugin.type = LtsAppDescriptor::PluginInfo::PluginType::DATA; + + LtsAppDescriptor::PluginInfo asmDataPlugin; + asmDataPlugin.iid = QStringLiteral("asm"); + asmDataPlugin.type = LtsAppDescriptor::PluginInfo::PluginType::DATA; + + LtsAppDescriptor::PluginInfo srmDataPlugin; + srmDataPlugin.iid = QStringLiteral("srm"); + srmDataPlugin.type = LtsAppDescriptor::PluginInfo::PluginType::DATA; + + LtsAppDescriptor::PluginInfo guiPlugin; + guiPlugin.iid = m_loginPluginIid; + guiPlugin.type = LtsAppDescriptor::PluginInfo::PluginType::GUI; + guiPlugin.flags |= LtsAppDescriptor::PluginInfo::Flag::PAYLOAD; + + QVector plugins; + plugins.append(opmDataPlugin); + plugins.append(pdmDataPlugin); + plugins.append(asmDataPlugin); + plugins.append(srmDataPlugin); + plugins.append(guiPlugin); + + LtsAppDescriptor::AppDescription app; + app.active = true; + app.appName = QStringLiteral("Login"); + app.pluginInfos = plugins; + + LtsSession::DdsConnectionParams connParams; + connParams.domainId = LtsSessionData::LtsSessionData::getInstance()->getDomainIdPreset(); + connParams.tcpConnectionUrl = LtsSessionData::LtsSessionData::getInstance()->getTcpConnectionUrl(); + connParams.partitionType = IDL::PartitionType::Global; + connParams.partitionId = 0; + + m_loginApp = new LtsPluginManager::LtsPluginManager(); + bool result = m_loginApp->startApp(app, true, connParams); + if (!result) + { + qWarning() << "no login plugin - probably an offline project"; + emit signalLoginDone(); + return; + } + + + + connect(m_loginApp, &LtsPluginManager::LtsPluginManager::signalInitializationFinished, + this, [this]() + { + disconnect(m_loginApp, &LtsPluginManager::LtsPluginManager::signalInitializationFinished, + this, nullptr); + + LtsSession::LtsSession *session = getAdminSession(); + + if (!session) + { + qCritical() << "I cannot find the Admin Session :( - abort!"; + QMessageBox::critical(nullptr, tr("Error loading login app"), tr("Could not load login app - abort.")); + abort(); + } + + if (session->isInitializationDone()) + QMetaObject::invokeMethod(this, &LtsLogin::slotLoginDone); + else + connect(session, &LtsSession::LtsSession::signalDoneInitializingSession, + this, &LtsLogin::slotLoginDone, Qt::UniqueConnection); + }); +} + + +void LtsLogin::slotLoginDone() +{ + LtsSession::LtsSession *session = getAdminSession(); + + if (session != nullptr) + { + disconnect(session, &LtsSession::LtsSession::signalDoneInitializingSession, + this, &LtsLogin::slotLoginDone); + + if (session->getDtc(m_asmDtc)) + { + m_asmDtc->enableWorkstationLogout(); + connect(m_asmDtc.get(), &AssetManagement::Interface::Dtc::workstationLogoutChanged, + this, &LtsLogin::slotLogoutShutdown); + } + else + qWarning() << "LtsLogin: no asm dtc, no remote logout/shutdown!"; + } + else + { + qCritical() << "LtsLogin: no admin session for login!!"; + QMessageBox::critical(nullptr, tr("Login error"), tr("Could not finalize login - abort.")); + abort(); + } + + emit signalLoginDone(); +} + + +void LtsLogin::showLogoutInformationDialog(AssetManagement::ShutdownAction action) +{ + LogoutInformationDialog *lid = new LogoutInformationDialog(logoutShutdownTimeout, action, nullptr); + connect(lid, &LogoutInformationDialog::accepted, + this, [this, action]() + { + m_shutdownAction = action; + m_isInLogoutDialog = false; + emit signalLogout(); + }); + + connect(lid, &LogoutInformationDialog::rejected, + this, [this]() + { + m_isInLogoutDialog = false; + }); + + lid->show(); +} + + +void LtsLogin::slotLogoutShutdown(const AssetManagement::WorkstationLogout &data, DataModification modType) +{ + if (m_isInLogoutDialog) + return; + + if (m_asmDtc == nullptr || modType != DataModification::eAdded) + return; + + if (!m_asmDtc->isAssetEnabled()) + m_asmDtc->enableAsset(); + for (int id : data.ids()) + { + if (QString::fromStdString(m_asmDtc->asset(id).name()).compare(LtsSession::LtsSession::workstationName(), Qt::CaseInsensitive) == 0) + { + m_isInLogoutDialog = true; + showLogoutInformationDialog(data.shutdownAction()); + break; + } + } +} + + +LtsSession::LtsSession *LtsLogin::getAdminSession() +{ + return m_loginApp->m_sessionManager->getSession(IDL::PartitionType::Global, 0); +} + + +void LtsLogin::logoff() +{ + if (m_loginApp == nullptr) + { + qCritical() << "LtsLogin::logoff: missing login app!"; + return; + } + + + + LtsSession::LtsSession *datSession = getAdminSession(); + + if (!datSession) + { + qCritical() << "LtsLogin::logoff: no admin session for logout!"; + abort(); + } + + int operatorId = LtsSessionData::LtsSessionData::getInstance()->getOperatorId(); + int workstationId = LtsSessionData::LtsSessionData::getInstance()->getWorkstationId(); + + LtsSessionData::LtsSessionData::getInstance()->clear(); + + std::shared_ptr opmFtc; + if (operatorId == -1 || + !datSession->getFtc(opmFtc) + ) + { + + } + else + { + try + { + + LtsUtils::OperatorSettings *os = LtsUtils::OperatorSettings::getInstance(); + + const QByteArray settings = os->exportSettings(); + + + const auto vec(LtsUtils::convert>(settings)); + os->clear(); + opmFtc->operatorGetSetSettingsRequest(OperatorManagement::OperatorGetSetSettingsRequest(operatorId, vec)); + } + catch (const std::runtime_error &err) + { + qCritical() << "Saving operator settings failed!" << err.what(); + } + + + std::shared_ptr srmIfc; + if (datSession->getForeignDataInterface(srmIfc)) + { + try + { + srmIfc->removeReplayPartitionsWorkstation(workstationId); + } + catch (const std::runtime_error &err) + { + qCritical() << "Removing reply partitions from this workstation failed!" << err.what(); + } + + try + { + srmIfc->removeReplayPartitionsOperator(operatorId); + } + catch (const std::runtime_error &err) + { + qCritical() << "Removing reply partitions from this operator failed!" << err.what(); + } + } + + + try + { + opmFtc->operatorLogoutRequest(OperatorManagement::OperatorLogoutRequest(operatorId)); + } + catch (const std::runtime_error &err) + { + qCritical() << "Logout failed!" << err.what(); + } + + + } + + m_loginApp->deleteLater(); + m_loginApp = nullptr; + + QString cmd; + switch (m_shutdownAction.underlying()) + { + case AssetManagement::ShutdownAction_def::Shutdown: +#ifdef LTS_DEBUG + cmd = QStringLiteral("xeyes"); +#else + cmd = QStringLiteral("systemctl poweroff"); +#endif + break; + case AssetManagement::ShutdownAction_def::Reboot: +#ifdef LTS_DEBUG + cmd = QStringLiteral("kcalc"); +#else + cmd = QStringLiteral("systemctl reboot"); +#endif + break; + case AssetManagement::ShutdownAction_def::Logout: + case AssetManagement::ShutdownAction_def::NoAction: + emit signalLogoffDone(); + break; + } + + if (!cmd.isEmpty()) + { + QProcess::startDetached(cmd); + QCoreApplication::quit(); + } +} + +} diff --git a/Lts/Common/LtsLogin/lts_login.h b/Lts/Common/LtsLogin/lts_login.h new file mode 100755 index 000000000..d22b7e1c9 --- /dev/null +++ b/Lts/Common/LtsLogin/lts_login.h @@ -0,0 +1,69 @@ +#pragma once + + + +#include +#include +#include +#include + +#include + + +namespace LtsPluginManager +{ +class LtsPluginManager; +} +namespace LtsUtils +{ +class RequestHelper; +} +namespace LtsSession +{ +class LtsSession; +} +namespace DeveloperTools +{ +class DevDialog; +} + +namespace LtsLogin +{ + +class LTSLOGIN_EXPORT LtsLogin : public QObject +{ + Q_OBJECT + +public: + LtsLogin(); + ~LtsLogin(); + + void login(); + void logoff(); + void setLoginPlugin(const QString &loginPluginIid) { m_loginPluginIid = loginPluginIid; } + +private: + friend class DeveloperTools::DevDialog; + + LtsSession::LtsSession *getAdminSession(); + void showLogoutInformationDialog(AssetManagement::ShutdownAction action); + + LtsPluginManager::LtsPluginManager *m_loginApp; + AssetManagement::ShutdownAction m_shutdownAction; + std::shared_ptr m_asmDtc; + QString m_loginPluginIid; + bool m_isInLogoutDialog; + +private slots: + void slotLoginDone(); + void slotLogoutShutdown(const AssetManagement::WorkstationLogout &data, DataModification modType); + +signals: + void signalLogout(); + void signalStopBlock(); + void signalLoginDone(); + void signalLogoffDone(); + +}; + +} diff --git a/Lts/Common/LtsMultiMonitor/CMakeLists.txt b/Lts/Common/LtsMultiMonitor/CMakeLists.txt new file mode 100755 index 000000000..bcb988452 --- /dev/null +++ b/Lts/Common/LtsMultiMonitor/CMakeLists.txt @@ -0,0 +1,49 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# +# Created by Mike Wille, SLSE, 2016-11-01 +# + + +# LtsMultiMonitor +SET(LtsMultiMonitor_SRCS + lts_multi_monitor_export.h + lts_multi_monitor.cpp + lts_dummy_widget.cpp + lts_screen_container.cpp + lts_multi_monitor_selector_base.cpp + lts_screen_base.cpp + lts_magic_corner.h + lts_magic_corner.cpp + + # normal screen + lts_multi_monitor_selector_normal.cpp + lts_screen_normal.cpp + + # ultra wide screen + lts_multi_monitor_selector_wide.cpp + lts_screen_wide.cpp +) + +NGL_ADD_LIBRARY(WS LtsMultiMonitor SHARED ${LtsMultiMonitor_SRCS}) + +NGL_TARGET_INCLUDE_DIRECTORIES(LtsMultiMonitor + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) + +NGL_TARGET_LINK_LIBRARIES(LtsMultiMonitor + PUBLIC + Qt5::Core + Qt5::Widgets + PRIVATE + Qt5::Gui + LtsWidgets + LtsSessionData + LtsUtilsCore + LtsUtilsGuiDds +) + +NGL_INSTALL_TARGET(LtsMultiMonitor) diff --git a/Lts/Common/LtsMultiMonitor/lts_dummy_widget.cpp b/Lts/Common/LtsMultiMonitor/lts_dummy_widget.cpp new file mode 100755 index 000000000..e98341a60 --- /dev/null +++ b/Lts/Common/LtsMultiMonitor/lts_dummy_widget.cpp @@ -0,0 +1,44 @@ +#include "lts_dummy_widget.h" + +#include + +#include + +#include +#include + + +namespace LtsMultiMonitor +{ + +FullscreenVideoDummyWidget::FullscreenVideoDummyWidget() +{ + setWindowIcon(LtsUtils::iconFromSvg(QStringLiteral(":/style/mobile_video.svg"))); + setWindowTitle(tr("Fullscreen Video Helper")); + + + + +} + + +void FullscreenVideoDummyWidget::hideVideo() +{ + qWarning() << "FullscreenVideoDummyWidget::hideVideo()"; + emit signalHideVideo(); +} + +void FullscreenVideoDummyWidget::showVideo() +{ + emit signalShowVideo(); +} + + +bool FullscreenVideoDummyWidget::event(QEvent *event) +{ + if (event->type() == QEvent::Type::Show) + assert(false); + return QWidget::event(event); +} + +} diff --git a/Lts/Common/LtsMultiMonitor/lts_dummy_widget.h b/Lts/Common/LtsMultiMonitor/lts_dummy_widget.h new file mode 100755 index 000000000..a5030a84b --- /dev/null +++ b/Lts/Common/LtsMultiMonitor/lts_dummy_widget.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include + + +class QEvent; + + +namespace LtsMultiMonitor +{ + +class LTSMULTIMONITOR_EXPORT SecondaryWidget : public QWidget +{ + Q_OBJECT +public: + SecondaryWidget() = default; + virtual ~SecondaryWidget() = default; +}; + + + +class LTSMULTIMONITOR_EXPORT FullscreenVideoDummyWidget : public SecondaryWidget +{ + Q_OBJECT + +public: + FullscreenVideoDummyWidget(); + + void hideVideo(); + void showVideo(); + +protected: + bool event(QEvent *); + +signals: + void signalWidgetPlaced(const QRect &globalGeometry); + void signalHideVideo(); + void signalShowVideo(); +}; + +} diff --git a/Lts/Common/LtsMultiMonitor/lts_magic_corner.cpp b/Lts/Common/LtsMultiMonitor/lts_magic_corner.cpp new file mode 100755 index 000000000..b31212ab8 --- /dev/null +++ b/Lts/Common/LtsMultiMonitor/lts_magic_corner.cpp @@ -0,0 +1,214 @@ +#include "lts_magic_corner.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include + + +#define SMOL 48 +#define SWOLE 100 + + +using namespace std::chrono_literals; + + +namespace LtsMultiMonitor +{ +LtsMagicCorner::LtsMagicCorner(const QRect &neogeo) + : m_neogeo(neogeo) + , m_shrinkTimer(new QTimer(this)) + , m_label(new QLabel(this)) +{ + + + m_shrinkTimer->setInterval(1500ms); + m_shrinkTimer->setSingleShot(true); + + connect(m_shrinkTimer, &QTimer::timeout, + this, &LtsMagicCorner::makeSmol); + + makeSmol(); + + + setWindowFlags(windowFlags() | Qt::FramelessWindowHint); + setMouseTracking(true); + setContentsMargins(0, 0, 0, 0); + setWindowFlag(Qt::WindowType::WindowStaysOnTopHint); + setWindowFlag(Qt::WindowType::X11BypassWindowManagerHint); + activateWindow(); + + + + auto *t = new QTimer(this); + connect(t, &QTimer::timeout, + this, [this]() + { + if (!isVisible()) + return; + + raise(); + + + + }); + t->setInterval(750ms); + t->start(); + + + m_label->setMinimumSize(SWOLE, SWOLE); + m_label->setMaximumSize(SWOLE, SWOLE); + m_label->setGeometry(0, 0, SWOLE, SWOLE); + m_label->setText(QStringLiteral("")); + m_label->setAlignment(Qt::AlignmentFlag::AlignCenter); + + + + + + +} + + +void LtsMagicCorner::setActiveWidgetType(LtsScreenBase::WidgetType widgetType) +{ + m_activeWidgetType = widgetType; + if (widgetType == LtsScreenBase::WidgetType::MainWidget) + m_label->setPixmap(m_mainPixmap); + else + m_label->setPixmap(m_secondaryPixmap); +} + +void LtsMagicCorner::setFreezed(bool freezed) +{ + setVisible(!freezed); +} + + +void LtsMagicCorner::setIcon(const QIcon &icon, LtsScreenBase::WidgetType widgetType) +{ + QPixmap p(LtsUtils::pixmapFromIcon(icon)); + + if (widgetType == LtsScreenBase::WidgetType::MainWidget) + m_mainPixmap = p; + else + m_secondaryPixmap = p; + + if (widgetType != m_activeWidgetType) + m_label->setPixmap(p); + + + + + + + + + + + + +} + + +void LtsMagicCorner::mousePressEvent(QMouseEvent *event) +{ + m_mousePressed = (event->button() == Qt::MouseButton::LeftButton); +} + + +void LtsMagicCorner::mouseReleaseEvent(QMouseEvent *event) +{ + if (!m_mousePressed) + return; + + m_mousePressed = false; + + if (event->button() != Qt::MouseButton::LeftButton) + return; + + m_shrinkTimer->start(); + emit toggle(); +} + + +void LtsMagicCorner::makeSmol() +{ + setAttribute(Qt::WA_TranslucentBackground); + setGeometry(m_neogeo.x(), m_neogeo.y(), SMOL, SMOL); + setMinimumSize(SMOL, SMOL); + setMaximumSize(SMOL, SMOL); + m_label->hide(); +} + + +void LtsMagicCorner::makeSwole() +{ + if (!isVisible()) + return; + + setAttribute(Qt::WA_TranslucentBackground, false); + setAttribute(Qt::WA_NoSystemBackground, false); + setGeometry(m_neogeo.x(), m_neogeo.y(), SWOLE, SWOLE); + setMinimumSize(SWOLE, SWOLE); + setMaximumSize(SWOLE, SWOLE); + m_label->show(); + m_shrinkTimer->start(); + update(); +} + + +void LtsMagicCorner::mouseMoveEvent(QMouseEvent *) +{ + + emit signalMakeAllSwole(); +} + +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Lts/Common/LtsMultiMonitor/lts_magic_corner.h b/Lts/Common/LtsMultiMonitor/lts_magic_corner.h new file mode 100755 index 000000000..8af1b17b5 --- /dev/null +++ b/Lts/Common/LtsMultiMonitor/lts_magic_corner.h @@ -0,0 +1,55 @@ +#pragma once + +#include "lts_screen_base.h" + +#include +#include + + +class QMouseEvent; +class QPaintEvent; +class QLabel; +class QTimer; + + +namespace LtsMultiMonitor +{ +class LtsMagicCorner : public QDialog +{ + Q_OBJECT + +public: + explicit LtsMagicCorner(const QRect &neogeo); + + void setIcon(const QIcon &icon, LtsScreenBase::WidgetType widgetType); + void setActiveWidgetType(LtsScreenBase::WidgetType widgetType); + + + void setFreezed(bool freezed); + +protected: + void mouseMoveEvent(QMouseEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + +signals: + void toggle(); + void signalMakeAllSwole(); + +private slots: + void makeSmol(); + +public slots: + void makeSwole(); + +private: + QRect m_neogeo; + QTimer *m_shrinkTimer; + bool m_mousePressed { false }; + bool m_freezed = false; + QLabel *m_label; + QPixmap m_mainPixmap; + QPixmap m_secondaryPixmap; + LtsScreenBase::WidgetType m_activeWidgetType; +}; +} diff --git a/Lts/Common/LtsMultiMonitor/lts_multi_monitor.cpp b/Lts/Common/LtsMultiMonitor/lts_multi_monitor.cpp new file mode 100755 index 000000000..934cdc05d --- /dev/null +++ b/Lts/Common/LtsMultiMonitor/lts_multi_monitor.cpp @@ -0,0 +1,696 @@ + + + +#include "lts_multi_monitor.h" +#include "lts_multi_monitor_selector_normal.h" +#include "lts_multi_monitor_selector_wide.h" +#include "lts_screen_normal.h" +#include "lts_screen_wide.h" +#include "lts_dummy_widget.h" +#include "lts_screen_container.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#ifdef Q_OS_WIN +#include +#endif + + +namespace LtsMultiMonitor +{ +static LtsMultiMonitor *s_ltsMultiMonitor = nullptr; + + +LtsMultiMonitor *LtsMultiMonitor::getInstance() +{ + if (!s_ltsMultiMonitor) + { + s_ltsMultiMonitor = new LtsMultiMonitor; + qAddPostRoutine(&cleanupLtsMultiMonitor); + } + return s_ltsMultiMonitor; +} + +void LtsMultiMonitor::cleanupLtsMultiMonitor() +{ + delete s_ltsMultiMonitor; + s_ltsMultiMonitor = nullptr; +} + + +LtsMultiMonitor::LtsMultiMonitor() + : QObject(nullptr) + , m_mainScreen(nullptr) + , m_active(false) + , m_initialized(false) + , m_skipScreenId(-1) + , m_subSkipScreenId(-1) + + , m_overlapBaseLayouts(false) + , m_debugMargins(false) +{} + + +LtsMultiMonitor::~LtsMultiMonitor() +{ + + + + for (auto i = m_screens.begin(); i != m_screens.end(); ++i) + { + LtsScreenBase *screen = *i; + disconnect(screen, &QObject::destroyed, this, &LtsMultiMonitor::screenDied); + screen->deleteLater(); + } + + + + +} + + +void LtsMultiMonitor::screenDied() +{ + + Q_ASSERT(false); +} + + +void LtsMultiMonitor::init() +{ + + if (LtsSessionData::LtsSessionData::getInstance()->isTablet()) + { + m_overlapBaseLayouts = true; + m_initialized = true; + return; + } + + if (m_initialized || !m_active) + return; + + QList screenList = QGuiApplication::screens(); + const QScreen &mainScreen = *QGuiApplication::primaryScreen(); + + + + + for (auto i = screenList.constBegin(); i != screenList.constEnd(); ++i) + { + const QScreen &screen = **i; + + + bool found = false; + + for (LtsScreenBase *ltsScreen : qAsConst(m_screens)) + { + if (ltsScreen->serialNumber() == screen.name()) + { + found = true; + break; + } + } + + if (found) + continue; + + + int screenId = m_nextScreenId++; + + + QRect screenGeo(screen.geometry()); + + if (screenId == m_skipScreenId) + { + if (m_subSkipScreenId == 0) + { + + QRect newScreenGeo(screenGeo); + + newScreenGeo.moveLeft(screenGeo.x() + (screenGeo.width() / 2)); + newScreenGeo.setWidth(screenGeo.width() / 2); + + screenGeo = newScreenGeo; + } + else if (m_subSkipScreenId == 1) + { + + QRect newScreenGeo(screenGeo); + + newScreenGeo.setWidth(screenGeo.width() / 2); + + screenGeo = newScreenGeo; + } + else + { + + continue; + } + } + + + + bool isUltraWideView = (screenGeo.width() > 3000); + + LtsScreenBase *newScreen; + if (isUltraWideView) + { + newScreen = new LtsScreenWide(screenGeo, screenId, m_debugMargins, screen.name()); + } + else + { + auto *newScreenNormal = new LtsScreenNormal(std::move(screenGeo), screenId, m_debugMargins, screen.name()); + newScreen = newScreenNormal; + + + connect(newScreenNormal, &LtsScreenNormal::signalMakeAllSwole, + this, &LtsMultiMonitor::signalMakeAllSwole); + connect(this, &LtsMultiMonitor::signalMakeAllSwole, + newScreenNormal, &LtsScreenNormal::signalMakeSwole); + } + + connect(newScreen, &QObject::destroyed, this, &LtsMultiMonitor::screenDied); + + if ((&mainScreen) == (&screen)) + m_mainScreen = newScreen; + + m_screens.append(newScreen); + } + if (m_screens.isEmpty()) + { + QMessageBox::critical(nullptr, QString(), tr("No screens found - cannot start application.")); + abort(); + } + + if (m_mainScreen == nullptr) + m_mainScreen = m_screens.first(); + + m_initialized = true; + emit signalScreenCountChanged(screenCount()); +} + + +int LtsMultiMonitor::screenCount() const +{ + return m_screens.count(); +} + + +bool LtsMultiMonitor::cancelAndProcessIfInactive(QWidget *widget) const +{ + if (m_active) + return false; + + widget->activateWindow(); + widget->show(); + + place(widget); + + return true; +} + + +void LtsMultiMonitor::place(QWidget *widget) +{ + + + auto *blw = qobject_cast(widget); + if (blw != nullptr) + emit blw->signalSuccessfullyPlaced(); +} + + +bool LtsMultiMonitor::putScreen(QWidget *widget, int screenId, int screenPart) +{ + Q_ASSERT(widget != nullptr); + if (widget == nullptr) + return false; + + if (cancelAndProcessIfInactive(widget)) + return true; + + init(); + + if (overlapBaseLayouts(widget)) + return true; + + + bool isSecondary = isWidgetSecondary(widget); + qWarning() << "putScreen: isSecondary" << isSecondary; + + for (LtsScreenBase *screen : qAsConst(m_screens)) + { + if (screen->screenId() != screenId) + continue; + + screen->switchToTemp(isSecondary ? LtsScreenBase::WidgetType::SecondaryWidget : LtsScreenBase::WidgetType::MainWidget); + + try + { + screen->putWidget(widget, screenPart); + } + catch (...) + { + screen->revertSwitch(); + + return selectScreen(widget); + } + + screen->check(); + + place(widget); + screen->finalizeSwitch(); + return true; + } + + + return selectScreen(widget); +} + +bool LtsMultiMonitor::putScreenAutomatically(QWidget *widget) +{ + if (widget == nullptr) + return false; + + if (cancelAndProcessIfInactive(widget)) + return true; + + init(); + + if (overlapBaseLayouts(widget)) + return true; + + for (LtsScreenBase *screen : qAsConst(m_screens)) + { + if (screen->screenId() == m_skipScreenId || !screen->isEmpty()) + continue; + + screen->switchToTemp(LtsScreenBase::WidgetType::SecondaryWidget); + + try + { + screen->putWidget(widget, 1); + } + catch (...) + { + try + { + screen->putWidget(widget, 2); + } + catch (...) + { + screen->revertSwitch(); + continue; + } + } + + screen->check(); + place(widget); + screen->finalizeSwitch(); + + return true; + } + + return false; +} + + +bool LtsMultiMonitor::isOverlapBaseLayouts() const +{ + return (m_overlapBaseLayouts ); +} + + +void LtsMultiMonitor::freezeMagicCorner(int screenId) +{ + + for (auto *screen : qAsConst(m_screens)) + { + + if (screen->screenId() == screenId) + { + LtsScreenNormal* normalScreen = qobject_cast(screen); + if(normalScreen->child()->hasMainWidget() && normalScreen->child()->hasSecondaryWidget()) + { + normalScreen->hideMagicCorner(true); + if(normalScreen->child()->activeWidgetType() == LtsScreenBase::WidgetType::MainWidget) + normalScreen->child()->toggle(); + } + } + } +} + +void LtsMultiMonitor::unfreezeMagicCorner(int screenId) +{ + + for (auto *screen : qAsConst(m_screens)) + { + + if (screen->screenId() == screenId) + { + LtsScreenNormal* normalScreen = qobject_cast(screen); + if(normalScreen->child()->hasMainWidget() && normalScreen->child()->hasSecondaryWidget()) + normalScreen->hideMagicCorner(false); + } + } +} + + +bool LtsMultiMonitor::overlapBaseLayouts(QWidget *widget) +{ + if (!isOverlapBaseLayouts()) + return false; + + + + + Q_ASSERT(qobject_cast(widget) == nullptr); + if (qobject_cast(widget) != nullptr) + { + + return false; + } + + + + + + + + + + + + + + + +#ifdef Q_OS_WIN + widget->winId(); + QWindowsWindowFunctions::setHasBorderInFullScreen(widget->windowHandle(), true); +#endif + + widget->activateWindow(); + widget->showFullScreen(); + + place(widget); + + return true; +} + + +bool LtsMultiMonitor::selectAndSwap(QWidget *widget) +{ + if (!m_active) + return false; + + Q_ASSERT(!isOverlapBaseLayouts()); + if (isOverlapBaseLayouts()) + return false; + + + + LtsScreenContainer *oldContainer = nullptr; + for (LtsScreenBase *_screen : qAsConst(m_screens)) + { + try + { + oldContainer = _screen->containerOfWidget(widget); + } + catch (...){} + } + + if (oldContainer == nullptr) + { + + LtsWidgets::LtsMessageDialog::showMessage(widget, tr("Could not determine screen id for this swap!
Swapping not possible, please try again later.")); + return false; + } + + + + + return selectScreen(widget, oldContainer); +} + + +LtsScreenBase *LtsMultiMonitor::widgetsScreen(QWidget *widget) +{ + for (LtsScreenBase *screen : qAsConst(m_screens)) + if (screen->hasWidget(widget)) + return screen; + return nullptr; +} + + +bool LtsMultiMonitor::isWidgetSecondary(QWidget *widget) +{ +#ifndef GUEZ_MODE + return false; +#endif + + auto *blw = qobject_cast(widget); + auto *secondaryWidget = qobject_cast(widget); + + bool isSecondaryWidget = ((blw != nullptr && blw->isSecondaryWidget()) || secondaryWidget != nullptr); + + + + return isSecondaryWidget; +} + + +class StupidDeleter +{ +public: + explicit StupidDeleter(LtsScreenBase *screen) + : m_screen(screen) + {} + + ~StupidDeleter() + { + + if (m_screen != nullptr) + { + + m_screen->revertSwitch(); + } + + } + + void reset() + { + + m_screen = nullptr; + } + + +private: + LtsScreenBase *m_screen; +}; + + +bool LtsMultiMonitor::selectScreen(QWidget *widget, LtsScreenContainer *swap, bool putOnMainIfFree, bool ltsScreenNormalOnly, const QVector &irreplacableWidgets) +{ + Q_ASSERT(widget != nullptr); + if (widget == nullptr) + return false; + + if (cancelAndProcessIfInactive(widget)) + return true; + + init(); + + if (overlapBaseLayouts(widget)) + return true; + + bool isSecondary = isWidgetSecondary(widget); + + + if (!isSecondary) + { + bool allEmpty = putOnMainIfFree; + for (LtsScreenBase *screen : qAsConst(m_screens)) + { + screen->switchToTemp(LtsScreenBase::WidgetType::MainWidget); + bool isEmpty = screen->isEmpty(); + screen->revertSwitch(); + + if (isEmpty) + continue; + + allEmpty = false; + break; + } + + + + if (allEmpty) + { + + + + m_mainScreen->switchToTemp(LtsScreenBase::WidgetType::MainWidget); + + try + { + m_mainScreen->putWidget(widget); + } + catch (...) + { + qWarning() << "failed - reverting"; + m_mainScreen->revertSwitch(); + + Q_ASSERT_X(false, __FUNCTION__, "screen promised to be empty but 'putWidgetSomewhere' method failed regardless"); + + + auto *blw = qobject_cast(widget); + if (blw != nullptr) + emit blw->signalClose(); + else + widget->deleteLater(); + + return false; + } + + m_mainScreen->check(); + place(widget); + m_mainScreen->finalizeSwitch(); + return true; + } + } + + + auto *selectionProcessWatcher = new QObject; + + + bool minOnce = false; + + for (auto i = m_screens.begin(); i != m_screens.end(); ++i) + { + LtsScreenBase *screen = *i; + + screen->switchToTemp(isSecondary ? LtsScreenBase::WidgetType::SecondaryWidget : LtsScreenBase::WidgetType::MainWidget); + + StupidDeleter _(screen); + + if (screen->isIrreplaceable()) + { + + continue; + } + + bool skip = false; + for (QWidget *widget : qAsConst(irreplacableWidgets)) + { + + if (screen->hasWidget(widget)) + { + + skip = true; + continue; + } + + + } + + if (skip) + continue; + + MultiMonitorSelectorBase *mms; + if (qobject_cast(screen) != nullptr) + { + auto *screenNormal = static_cast(screen); + mms = new MultiMonitorSelectorNormal(screenNormal, isSecondary, irreplacableWidgets); + } + else if (qobject_cast(screen) != nullptr && !ltsScreenNormalOnly) + { + auto *screenWide = static_cast(screen); + mms = new MultiMonitorSelectorWide(screenWide, nullptr, irreplacableWidgets); + } + else + continue; + + + QPointer widgetPtr(widget); + + + connect(widget, &QWidget::destroyed, + mms, &MultiMonitorSelectorBase::close); + connect(mms, &MultiMonitorSelectorBase::rejected, + this, [widgetPtr, swap]() + { + + + if (!widgetPtr || swap != nullptr) + return; + + + + + + auto *blw = qobject_cast(widgetPtr.data()); + if (blw != nullptr) + emit blw->signalClose(); + else + widgetPtr->deleteLater(); + }); + connect(mms, &MultiMonitorSelectorBase::signalSelected, + this, [this, mms, widgetPtr]() + { + + + for (LtsScreenBase *screen : qAsConst(m_screens)) + screen->check(); + + + + if (widgetPtr) + place(widgetPtr.data()); + + + + mms->deleteLater(); + emit signalSelected(); + + + }); + connect(mms, &MultiMonitorSelectorBase::destroyed, + selectionProcessWatcher, &QObject::deleteLater); + connect(selectionProcessWatcher, &QObject::destroyed, + mms, &MultiMonitorSelectorBase::close); + + mms->setSwap(swap); + mms->setWidget(widget); + mms->show(); + _.reset(); + minOnce = true; + } + + if (!minOnce) + { + selectionProcessWatcher->deleteLater(); + auto *blw = qobject_cast(widget); + if (blw != nullptr) + emit blw->signalClose(); + else + widget->deleteLater(); + + LtsWidgets::LtsMessageDialog::showMessage(m_mainScreen->screenDialog(), tr("There are no free screens available to select from.")); + return false; + } + + return true; +} + + +} diff --git a/Lts/Common/LtsMultiMonitor/lts_multi_monitor.h b/Lts/Common/LtsMultiMonitor/lts_multi_monitor.h new file mode 100755 index 000000000..88c0cbb6b --- /dev/null +++ b/Lts/Common/LtsMultiMonitor/lts_multi_monitor.h @@ -0,0 +1,85 @@ +#pragma once + +#include "lts_multi_monitor_export.h" + +#include +#include + +#include + + +namespace LtsMultiMonitor +{ +class LtsScreenBase; +class MultiMonitorSelectorBase; +class LtsScreenContainer; + + +class LTSMULTIMONITOR_EXPORT LtsMultiMonitor : public QObject +{ + Q_OBJECT + +public: + static LtsMultiMonitor *getInstance(); + + + + bool selectScreen(QWidget *widget, LtsScreenContainer *swap = nullptr, bool putOnMainIfFree = true, bool ltsScreenNormalOnly = false, const QVector &irreplacableWidgets = {}); + bool selectAndSwap(QWidget *widget); + + + void setEnableMultiMonitorMode() { m_active = true; } + void setDebugMultiMonitorMode() { m_debugMargins = true; } + bool isActive() const { return m_active; } + + LtsScreenBase *widgetsScreen(QWidget *widget); + + + + + void freezeMagicCorner(int screenId); + void unfreezeMagicCorner(int screenId); + + bool putScreen(QWidget *widget, int screenId, int screenPart); + bool putScreenAutomatically(QWidget *widget); + static void place(QWidget *widget); + void setSkipScreen(int screenId, int subScreenId = -1) { m_skipScreenId = screenId; m_subSkipScreenId = subScreenId; } + LtsScreenBase *mainScreen() const { return m_mainScreen; } + int screenCount() const; + +private slots: + void screenDied(); + +private: + LtsMultiMonitor(); + ~LtsMultiMonitor(); + static void cleanupLtsMultiMonitor(); + void init(); + bool cancelAndProcessIfInactive(QWidget *widget) const; + static bool isWidgetSecondary(QWidget *widget) ; + + + + bool overlapBaseLayouts(QWidget *widget); + bool isOverlapBaseLayouts() const; + + QVector m_screens; + int m_nextScreenId { 0 }; + LtsScreenBase *m_mainScreen; + bool m_active; + bool m_initialized; + int m_skipScreenId; + int m_subSkipScreenId; + + bool m_overlapBaseLayouts; + bool m_debugMargins; + +signals: + void signalQuitLoop(); + void signalSelected(); + void signalScreenCountChanged(int); + void signalMakeAllSwole(); + +}; + +} diff --git a/Lts/Common/LtsMultiMonitor/lts_multi_monitor_export.h b/Lts/Common/LtsMultiMonitor/lts_multi_monitor_export.h new file mode 100755 index 000000000..973af8a09 --- /dev/null +++ b/Lts/Common/LtsMultiMonitor/lts_multi_monitor_export.h @@ -0,0 +1,17 @@ + + +#pragma once + +#include + +#ifndef LTSMULTIMONITOR_EXPORT +# ifdef BUILD_STATIC_LIBRARIES +# define LTSMULTIMONITOR_EXPORT +# else +# if defined(MAKE_LTSMULTIMONITOR_LIB) +# define LTSMULTIMONITOR_EXPORT Q_DECL_EXPORT +# else +# define LTSMULTIMONITOR_EXPORT Q_DECL_IMPORT +# endif +# endif +#endif diff --git a/Lts/Common/LtsMultiMonitor/lts_multi_monitor_selector_base.cpp b/Lts/Common/LtsMultiMonitor/lts_multi_monitor_selector_base.cpp new file mode 100755 index 000000000..382ade6f8 --- /dev/null +++ b/Lts/Common/LtsMultiMonitor/lts_multi_monitor_selector_base.cpp @@ -0,0 +1,419 @@ + + +#include "lts_multi_monitor_selector_base.h" +#include "lts_multi_monitor.h" +#include "lts_screen_base.h" +#include "lts_screen_container.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +namespace LtsMultiMonitor +{ +int MultiMonitorSelectorBase::s_margin = 10; +int MultiMonitorSelectorBase::s_offset = 6; +int MultiMonitorSelectorBase::s_buttonSize = 20; + + +MultiMonitorSelectorBase::MultiMonitorSelectorBase(LtsScreenBase *screen, const QVector &irreplacableWidgets, QWidget *) + : QDialog(nullptr) + , m_irreplacableWidgets(irreplacableWidgets) + , m_screen(screen) + , m_screenIsIrreplaceable(screen->isIrreplaceable()) +{ + setGeometry(screen->screenRect()); + setMouseTracking(true); + setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint ); + setAttribute(Qt::WA_TranslucentBackground); + setAttribute(Qt::WA_DeleteOnClose); +} + + +bool MultiMonitorSelectorBase::isNullSwap(LtsScreenContainer *container) +{ + if (container == m_swap) + { + + return true; + } + + + return false; +} + + +void MultiMonitorSelectorBase::closeEvent(QCloseEvent *event) +{ + + + qWarning() << "closeEvent"; + + if (!m_selected && !m_reverted) + { + qWarning() << "closeEvent: not selected and not reverted! reverting!"; + m_screen->revertSwitch(); + } + + + + + event->accept(); +} + + +bool MultiMonitorSelectorBase::killOrSwapOldBLW(LtsScreenContainer *container ) noexcept(false) +{ + + + + + + + + + if (isNullSwap(container)) + return true; + + + + + if (m_swap != nullptr) + { + QWidget *oldSwapWidget; + try + { + + oldSwapWidget = m_swap->stealWidget(); + + + } + catch (...) + { + + throw; + } + + + + + + if (!container->isEmpty()) + { + + QWidget *oldContainerWidget; + try + { + + oldContainerWidget = container->stealWidget(); + + + } + catch (...) + { + + throw; + } + + + m_swap->setWidget(oldContainerWidget); + } + + + + container->setWidget(oldSwapWidget); + + + if(m_swap->activeWidgetType() == LtsScreenBase::WidgetType::SecondaryWidget && !m_swap->hasSecondaryWidget() && m_swap->hasMainWidget()) + m_swap->toggle(); + else if(m_swap->activeWidgetType() == LtsScreenBase::WidgetType::MainWidget && !m_swap->hasMainWidget() && m_swap->hasSecondaryWidget()) + m_swap->toggle(); + + + return true; + } + + + + QWidget *oldWidget; + try + { + + oldWidget = container->widget(); + + } + catch (...) + { + + return false; + } + + + + auto *oldBLW = qobject_cast(oldWidget); + + if (oldBLW == nullptr) + { + container->stealWidget()->deleteLater(); + + return false; + } + + + + if (!oldBLW->slotClose()) + { + + throw "MultiMonitorSelectorBase::killOrSwapOldBLW: failed to close old blw"; + } + + container->stealWidget(); + return false; + +} + + +void MultiMonitorSelectorBase::setWidget(QWidget *widget) +{ + m_widget = widget; +} + + +void MultiMonitorSelectorBase::mouseMoveEvent(QMouseEvent *event) +{ + m_cursorPos = event->pos(); + update(); +} + + +void MultiMonitorSelectorBase::mousePressEvent(QMouseEvent *event) +{ + const QRect abortButtonRect(QRect(centeredRect().topRight() + QPoint(s_buttonSize, s_buttonSize), QSize(s_buttonSize, s_buttonSize))); + auto pos = event->pos(); + + Q_ASSERT(m_widget != nullptr); + + + if (abortButtonRect.contains(pos) || m_widget == nullptr) + { + qWarning() << "X pressed!"; + m_reverted = true; + m_screen->revertSwitch(); + reject(); + return; + } + + if (processSelection(pos)) + { + qWarning() << "process!"; + m_screen->finalizeSwitch(); + selected(); + return; + } +} + + +void MultiMonitorSelectorBase::selected() +{ + m_widget = nullptr; + m_selected = true; + emit signalSelected(); +} + + +QRect MultiMonitorSelectorBase::centeredRect() const +{ + + if (m_cachedCenteredRect.isValid()) + return m_cachedCenteredRect; + + + const QSize thisSize(size()); + const QSize newSize(thisSize.width() * 0.20, thisSize.height() * 0.20); + m_cachedCenteredRect = QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, newSize, rect()); + + return m_cachedCenteredRect; +} + + +void MultiMonitorSelectorBase::setColors(QPainter &p, const QRect checkSelection, bool occupied, bool countEdge) +{ + if (occupied) + { + p.setBrush(QColor("silver")); + p.setPen(QPen(QColor("white"), 2)); + return; + } + + + if (checkSelection.contains(m_cursorPos, countEdge)) + { + + p.setBrush(QColor("grey")); + p.setPen(QPen(QColor("white"), 2)); + return; + } + + + + p.setBrush(QColor("black")); + p.setPen(QPen(p.brush(), 1)); +} + + +void MultiMonitorSelectorBase::drawBase(QPainter &p, bool drawSelector, bool occupied, bool isSecondary, bool isInactiveWidgetOccupied) +{ + bool showReplacementIndicator = true; +#ifndef GUEZ_MODE + isSecondary = false; + showReplacementIndicator = false; +#endif + + + p.save(); + p.setRenderHint(QPainter::Antialiasing, true); + p.setRenderHint(QPainter::HighQualityAntialiasing, true); + + + + p.setBrush(QColor(53, 53, 53, 150)); + p.drawRect(geometry()); + + + + const QRect centered(centeredRect()); + p.setBrush(QColor("silver")); + p.setPen(QPen(QColor("black"), 0.5)); + + + + if (isSecondary && isInactiveWidgetOccupied) + { + const QRect r(centered.adjusted(12, 12, 12, 12)); + setColors(p, r, true); + p.drawRoundedRect(r, 7, 7); + } + + + + if (drawSelector) + { + if (!m_screenIsIrreplaceable) + { + + setColors(p, centered, occupied); + } + + p.drawRoundedRect(centered, 7, 7); + } + + + const int cX = centered.x(); + const int cW = centered.width(); + const int cY = centered.y(); + const int cH = centered.height(); + + + + if (isSecondary) + { + QPen pen(p.pen()); + QColor pc(pen.color()); + const QColor c(255 - pc.red(), 255 - pc.green(), 255 - pc.blue()); + pen.setColor(c); + p.setPen(pen); + + QFont f(p.font()); + f.setBold(true); + f.setPixelSize(cH / 2); + p.setFont(f); + + const QString text(tr("S", "Secondary (German: 'H' for 'Hilfsansicht')")); + + p.drawText(centered, Qt::AlignCenter, text); + } + + + + const QRect abortButtonRect(QRect(centered.topRight() + QPoint(s_buttonSize, s_buttonSize), QSize(s_buttonSize, s_buttonSize))); + const QPoint topRight(abortButtonRect.topLeft() ); + setColors(p, abortButtonRect, false, false); + + QPen pen(p.pen()); + pen.setWidth(5.0); + p.setPen(pen); + + p.drawLines(QVector() + << QLine(topRight , topRight + QPoint(s_buttonSize, s_buttonSize)) + << QLine(topRight + QPoint(0, s_buttonSize), topRight + QPoint(s_buttonSize, 0)) + ); + + + + if (showReplacementIndicator && occupied) + { + p.setBrush(QColor("silver")); + p.setPen(QPen(QColor("white"), 2)); + + const QRect secondRect(centered.adjusted(cW + 70, 0, cW + 70, 0)); + p.drawRoundedRect(secondRect, 7, 7); + + + p.drawLine(cX + cW + 15, cY + (cH / 2), + cX + cW + 55, cY + (cH / 2)); + + + p.drawPolygon(QPolygon( + { + QPoint(cX + cW + 56, cY + (cH / 2)), + QPoint(cX + cW + 46, cY + (cH / 2) - 10), + QPoint(cX + cW + 46, cY + (cH / 2) + 10), + })); + + const int srX = secondRect.x(); + const int srY = secondRect.y(); + const int srH = secondRect.height(); + const int diameter = srH - 50; + const int widthByTwo = (secondRect.width() / 2); + const int diameterByTwo = (diameter / 2); + const int lineX1 = srX + widthByTwo - diameterByTwo; + const int lineY1 = srY + 25; + + p.drawEllipse(lineX1, lineY1, diameter, diameter); + + + if (isSecondary) + { + QFont f(p.font()); + f.setBold(true); + f.setPixelSize(srH / 2); + p.setFont(f); + + const QString text(tr("S", "Secondary (German: 'H' for 'Hilfsansicht')")); + + p.drawText(secondRect, Qt::AlignCenter, text); + } + + + const int lineX2 = lineX1 + diameter; + const int lineY2 = lineY1 + diameter; + p.drawLine(lineX1, lineY1, lineX2, lineY2); + p.drawLine(lineX2, lineY1, lineX1, lineY2); + } + + + p.restore(); +} + +} diff --git a/Lts/Common/LtsMultiMonitor/lts_multi_monitor_selector_base.h b/Lts/Common/LtsMultiMonitor/lts_multi_monitor_selector_base.h new file mode 100755 index 000000000..c04cf457e --- /dev/null +++ b/Lts/Common/LtsMultiMonitor/lts_multi_monitor_selector_base.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include + + +class QScreen; +class QPainter; +class QPaintEvent; +class QMouseEvent; +class QCloseEvent; + + +namespace LtsMultiMonitor +{ +class LtsScreenBase; +class LtsScreenContainer; + +class MultiMonitorSelectorBase : public QDialog +{ + Q_OBJECT + +public: + explicit MultiMonitorSelectorBase(LtsScreenBase *screen, const QVector &irreplacableWidgets, QWidget *); + virtual ~MultiMonitorSelectorBase() {} + + void setSwap(LtsScreenContainer *swap) { m_swap = swap; } + void setWidget(QWidget *widget); + LtsScreenBase *screen() const { return m_screen; } + +signals: + void signalSelected(); + +protected: + static int s_offset; + static int s_margin; + static int s_buttonSize; + + LtsScreenContainer *m_swap { nullptr }; + QWidget *m_widget { nullptr }; + mutable QRect m_cachedCenteredRect; + QVector m_irreplacableWidgets; + + void drawBase(QPainter &p, bool drawSelector = true, bool occupied = false, bool isSecondary = false, bool isInactiveWidgetOccupied = false); + bool killOrSwapOldBLW(LtsScreenContainer *container) noexcept(false); + bool isNullSwap(LtsScreenContainer *container); + QRect centeredRect() const; + void setColors(QPainter &p, const QRect checkSelection, bool occupied = false, bool countEdge = false); + void selected(); + virtual bool processSelection(const QPoint &selection) = 0; + +private: + QPoint m_cursorPos; + bool m_selected { false }; + LtsScreenBase *m_screen; + bool m_screenIsIrreplaceable; + bool m_reverted { false }; + + void closeEvent(QCloseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; +}; + +} diff --git a/Lts/Common/LtsMultiMonitor/lts_multi_monitor_selector_normal.cpp b/Lts/Common/LtsMultiMonitor/lts_multi_monitor_selector_normal.cpp new file mode 100755 index 000000000..dc5908d4b --- /dev/null +++ b/Lts/Common/LtsMultiMonitor/lts_multi_monitor_selector_normal.cpp @@ -0,0 +1,100 @@ + + +#include "lts_multi_monitor.h" +#include "lts_multi_monitor_selector_normal.h" +#include "lts_screen_container.h" +#include "lts_screen_normal.h" +#include "lts_dummy_widget.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + + +namespace LtsMultiMonitor +{ + +MultiMonitorSelectorNormal::MultiMonitorSelectorNormal(LtsScreenNormal *screen, bool isSecondary, const QVector &irreplacableWidgets) + : MultiMonitorSelectorBase(screen, irreplacableWidgets, nullptr) + , m_screen(screen) + , m_screenIsEmpty(screen->isEmpty()) + , m_isSecondary(isSecondary) + , m_isInactiveWidgetOccupied(screen->child()->isInactiveWidgetOccupied()) +{ + +} + + +MultiMonitorSelectorNormal::~MultiMonitorSelectorNormal() +{ + +} + + +bool MultiMonitorSelectorNormal::processSelection(const QPoint &selection) +{ + + + + if (!centeredRect().contains(selection)) + { + + return false; + } + + if (m_screen->isIrreplaceable()) + { + + + return false; + } + + try + { + if (m_irreplacableWidgets.contains(m_screen->child()->widget())) + return false; + } + catch(...){} + + + + + + try + { + if (killOrSwapOldBLW(m_screen->child())) + { + + return true; + } + } + catch (...) + { + return false; + } + + + + + m_screen->putWidgetUnmanaged(m_widget); + + + return true; +} + + +void MultiMonitorSelectorNormal::paintEvent(QPaintEvent *) +{ + QPainter p(this); + drawBase(p, true, !m_screenIsEmpty, m_isSecondary, m_isInactiveWidgetOccupied); +} + +} diff --git a/Lts/Common/LtsMultiMonitor/lts_multi_monitor_selector_normal.h b/Lts/Common/LtsMultiMonitor/lts_multi_monitor_selector_normal.h new file mode 100755 index 000000000..b793341ac --- /dev/null +++ b/Lts/Common/LtsMultiMonitor/lts_multi_monitor_selector_normal.h @@ -0,0 +1,37 @@ +#pragma once + +#include "lts_multi_monitor_selector_base.h" + +#include +#include + + +class QScreen; +class QPaintEvent; +class QMouseEvent; + + +namespace LtsMultiMonitor +{ +class LtsScreenBase; +class LtsScreenNormal; + +class MultiMonitorSelectorNormal : public MultiMonitorSelectorBase +{ + Q_OBJECT + +public: + explicit MultiMonitorSelectorNormal(LtsScreenNormal *screen, bool isSecondary, const QVector &irreplacableWidgets = {}); + ~MultiMonitorSelectorNormal(); + +private: + LtsScreenNormal *m_screen; + bool m_screenIsEmpty; + bool m_isSecondary; + bool m_isInactiveWidgetOccupied; + + bool processSelection(const QPoint &selection) override; + void paintEvent(QPaintEvent *event) override; +}; + +} diff --git a/Lts/Common/LtsMultiMonitor/lts_multi_monitor_selector_wide.cpp b/Lts/Common/LtsMultiMonitor/lts_multi_monitor_selector_wide.cpp new file mode 100755 index 000000000..3f514d624 --- /dev/null +++ b/Lts/Common/LtsMultiMonitor/lts_multi_monitor_selector_wide.cpp @@ -0,0 +1,324 @@ + + +#include "lts_multi_monitor.h" +#include "lts_multi_monitor_selector_wide.h" +#include "lts_screen_container.h" +#include "lts_screen_wide.h" + +#include +#include +#include +#include + +#include +#include +#include + + + + +namespace LtsMultiMonitor +{ + +MultiMonitorSelectorWide::MultiMonitorSelectorWide(LtsScreenWide *screen, QWidget *, const QVector &irreplacableWidgets) + : MultiMonitorSelectorBase(screen, irreplacableWidgets, nullptr ) + , m_screen(screen) +{} + + +bool MultiMonitorSelectorWide::isDrawOnlyOneSelector() +{ + + + + bool res = ((m_screen->childMain()->isEmpty() && m_screen->childLeft()->isEmpty() && m_screen->childRight()->isEmpty()) + || (!m_screen->childMain()->isEmpty() && m_swap != nullptr && m_screen->childMain() == m_swap)); + + + + + return res; +} + + +bool MultiMonitorSelectorWide::processSelection(const QPoint &selection) +{ + + + + + + LtsScreenContainer *main = m_screen->childMain(); + LtsScreenContainer *left = m_screen->childLeft(); + LtsScreenContainer *right = m_screen->childRight(); + + bool singleSelector = isDrawOnlyOneSelector(); + + if (singleSelector) + { + if (main->isIrreplaceable()) + { + + return false; + } + + try + { + if (m_irreplacableWidgets.contains(main->widget())) + return false; + } + catch(...){} + + + + if (isNullSwap(m_screen->childMain())) + { + + return true; + } + + if (centeredRect().contains(selection)) + { + try + { + if (killOrSwapOldBLW(m_screen->childMain())) + { + + return true; + } + } + catch (...) + { + + return false; + } + + + m_screen->putWidgetUnmanagedMain(m_widget); + + return true; + } + + + return false; + } + + + bool leftContains = leftSide().contains(selection); + bool rightContains = rightSide().contains(selection); + + + + + + if (leftContains) + { + if (left->isIrreplaceable()) + { + + return false; + } + + try + { + if (m_irreplacableWidgets.contains(left->widget())) + return false; + } + catch(...){} + + + + try + { + + QWidget *oldWidget = main->stealWidget(); + + + m_screen->putWidgetUnmanagedRight(oldWidget); + } + catch (...) {} + + + + + try + { + if (killOrSwapOldBLW(left)) + { + + return true; + } + } + catch (...) + { + + return false; + } + + + + m_screen->putWidgetUnmanagedLeft(m_widget); + return true; + } + else if (rightContains) + { + if (right->isIrreplaceable()) + { + + return false; + } + + try + { + if (m_irreplacableWidgets.contains(right->widget())) + return false; + } + catch(...){} + + + + try + { + + QWidget *oldWidget = main->stealWidget(); + + + m_screen->putWidgetUnmanagedLeft(oldWidget); + } + catch (...) {} + + + + + + try + { + if (killOrSwapOldBLW(right)) + { + + return true; + } + } + catch (...) + { + + return false; + } + + + m_screen->putWidgetUnmanagedRight(m_widget); + return true; + } + + return false; +} + + +QRect MultiMonitorSelectorWide::leftSide() const +{ + QRect centered = centeredRect(); + + return QRect(centered.x() + s_margin, + centered.y() + s_margin, + (centered.width() / 2) - (s_margin*2), + centered.height() - (s_margin*2)); +} + + +QRect MultiMonitorSelectorWide::rightSide() const +{ + QRect centered = centeredRect(); + + return QRect(centered.x() + (centered.width() / 2) + s_margin, + centered.y() + s_margin, + (centered.width() / 2) - (s_margin*2), + centered.height() - (s_margin*2)); +} + + +void MultiMonitorSelectorWide::paintEvent(QPaintEvent *) +{ + QPainter p(this); + p.save(); + + p.setRenderHint(QPainter::Antialiasing, true); + p.setRenderHint(QPainter::HighQualityAntialiasing, true); + + const QRect centered(centeredRect()); + + bool drawOnlyOneSelector = isDrawOnlyOneSelector(); + drawBase(p, drawOnlyOneSelector, false ); + + + + if (!drawOnlyOneSelector) + { + p.setBrush(QColor("silver")); + p.setPen(QPen(QColor("black"), 0.5)); + p.drawRoundedRect(centered, 7, 7); + + + bool leftIrreplacable = false; + try + { + leftIrreplacable = m_irreplacableWidgets.contains(m_screen->childLeft()->widget()); + } + catch(...){} + + if (!m_screen->childLeft()->isIrreplaceable() && !leftIrreplacable) + { + + const QRect leftSide(centered.x() + s_margin, + centered.y() + s_margin, + (centered.width() / 2) - (s_margin*2), + centered.height() - (s_margin*2)); + + setColors(p, leftSide, !m_screen->childLeft()->isEmpty()); + + p.drawRoundedRect(leftSide, 6, 6); + } + + + bool rightIrreplacable = false; + try + { + rightIrreplacable = m_irreplacableWidgets.contains(m_screen->childRight()->widget()); + } + catch(...){} + + if (!m_screen->childRight()->isIrreplaceable() && !rightIrreplacable) + { + + const QRect rightSide(centered.x() + (centered.width() / 2) + s_margin, + centered.y() + s_margin, + (centered.width() / 2) - (s_margin*2), + centered.height() - (s_margin*2)); + + setColors(p, rightSide, !m_screen->childRight()->isEmpty()); + + p.drawRoundedRect(rightSide, 6, 6); + } + } + + + + static int buttonSize = 20; + const QRect abortButtonRect(QRect(centered.topRight() + QPoint(buttonSize, buttonSize), QSize(buttonSize, buttonSize))); + const QPoint topRight(abortButtonRect.topLeft() ); + setColors(p, abortButtonRect, false, false); + + + QPen pen(p.pen()); + pen.setWidth(5.0); + p.setPen(pen); + + p.drawLines(QVector() + << QLine(topRight , topRight + QPoint(buttonSize, buttonSize)) + << QLine(topRight + QPoint(0, buttonSize), topRight + QPoint(buttonSize, 0)) + ); + + + p.restore(); +} + +} diff --git a/Lts/Common/LtsMultiMonitor/lts_multi_monitor_selector_wide.h b/Lts/Common/LtsMultiMonitor/lts_multi_monitor_selector_wide.h new file mode 100755 index 000000000..483401af7 --- /dev/null +++ b/Lts/Common/LtsMultiMonitor/lts_multi_monitor_selector_wide.h @@ -0,0 +1,36 @@ +#pragma once + +#include "LtsMultiMonitor/lts_multi_monitor_selector_base.h" + +#include +#include + + +class QScreen; +class QPaintEvent; +class QMouseEvent; + + +namespace LtsMultiMonitor +{ +class LtsScreenWide; +class LtsScreenBase; + +class MultiMonitorSelectorWide : public MultiMonitorSelectorBase +{ + Q_OBJECT + +public: + explicit MultiMonitorSelectorWide(LtsScreenWide *screen, QWidget *superParent, const QVector &irreplacableWidgets); + +private: + LtsScreenWide *m_screen; + + bool isDrawOnlyOneSelector(); + QRect leftSide() const; + QRect rightSide() const; + bool processSelection(const QPoint &selection) override; + void paintEvent(QPaintEvent *event) override; +}; + +} diff --git a/Lts/Common/LtsMultiMonitor/lts_screen_base.cpp b/Lts/Common/LtsMultiMonitor/lts_screen_base.cpp new file mode 100755 index 000000000..1772c1899 --- /dev/null +++ b/Lts/Common/LtsMultiMonitor/lts_screen_base.cpp @@ -0,0 +1,70 @@ +#include "lts_screen_base.h" +#include "lts_dummy_widget.h" + +#include +#include + +#include +#include +#include +#include +#ifdef Q_OS_WIN +#include +#endif + + +class NoCloseWidget : public QWidget +{ + void closeEvent(QCloseEvent *event) override + { + event->ignore(); + } +}; + + +namespace LtsMultiMonitor +{ +LtsScreenBase::LtsScreenBase(QRect screenGeo, int screenId, bool debugMargins, const QString &serial) + : m_screenDialog(new NoCloseWidget()) + , m_screenId(screenId) + , m_serial(serial) + , m_screenRect(screenGeo) +{ + connect(this, &LtsScreenBase::destroyed, + m_screenDialog, &QWidget::deleteLater); + + if (debugMargins) + { + qWarning() << "LtsScreenBase: screen rect adjusted for dev machines!"; + m_screenRect.adjust(+50, +50, -100, -100); + } + + + m_screenDialog->setWindowFlags(Qt::FramelessWindowHint); + m_screenDialog->setGeometry(m_screenRect); + m_screenDialog->setMinimumSize(m_screenRect.width(), m_screenRect.height()); + m_screenDialog->setMaximumSize(m_screenRect.width(), m_screenRect.height()); + m_screenDialog->setContentsMargins(0, 0, 0, 0); + m_screenDialog->setWindowFlag(Qt::WindowType::WindowStaysOnBottomHint); + +#ifdef Q_OS_WIN + m_screenDialog->winId(); + QWindowsWindowFunctions::setHasBorderInFullScreen(m_screenDialog->windowHandle(), true); +#endif + + + + + + + + + + + + m_screenDialog->activateWindow(); + m_screenDialog->show(); +} + + +} diff --git a/Lts/Common/LtsMultiMonitor/lts_screen_base.h b/Lts/Common/LtsMultiMonitor/lts_screen_base.h new file mode 100755 index 000000000..06a9bcde2 --- /dev/null +++ b/Lts/Common/LtsMultiMonitor/lts_screen_base.h @@ -0,0 +1,59 @@ +#pragma once + +#include "lts_multi_monitor_export.h" + +#include +#include + +class QSplitter; +class QHBoxLayout; + +namespace LtsUtils { class Promise; } + +namespace LtsMultiMonitor +{ +class LtsScreenContainer; + + + +class LTSMULTIMONITOR_EXPORT LtsScreenBase : public QObject +{ + Q_OBJECT + +public: + enum WidgetType + { + MainWidget, + SecondaryWidget, + None, + }; + + LtsScreenBase(QRect screenGeo, int screenId, bool debugMargins, const QString &serial); + + const QRect &screenRect() const { return m_screenRect; } + int screenId() const { return m_screenId; } + const QString serialNumber() const { return m_serial; } + QWidget *screenDialog() const { return m_screenDialog; } + + virtual int screenType() const = 0; + virtual int containerIdOfWidget(QWidget *widget) const noexcept(false) = 0; + virtual LtsScreenContainer *putWidget(QWidget *widget, int screenPart = -1 ) noexcept(false) = 0; + virtual LtsScreenContainer *containerOfWidget(QWidget *widget) noexcept(false) = 0; + virtual bool isEmpty() const = 0; + virtual bool isIrreplaceable() const = 0; + virtual void check() {} + virtual bool hasWidget(QWidget *widget) const = 0; + virtual void switchToTemp(WidgetType ) {} + virtual void finalizeSwitch() {} + virtual void revertSwitch() {} + +protected: + QWidget *m_screenDialog; + +private: + int m_screenId; + QString m_serial; + QRect m_screenRect; +}; + +} diff --git a/Lts/Common/LtsMultiMonitor/lts_screen_container.cpp b/Lts/Common/LtsMultiMonitor/lts_screen_container.cpp new file mode 100755 index 000000000..35ce51804 --- /dev/null +++ b/Lts/Common/LtsMultiMonitor/lts_screen_container.cpp @@ -0,0 +1,555 @@ +#include "lts_screen_container.h" +#include "lts_dummy_widget.h" + +#include + +#include +#include +#include +#include + + + +#include + + + +namespace LtsMultiMonitor +{ +QString LtsScreenContainer::geoStr() const +{ + return QStringLiteral("%1:%2:") + .arg(QString::number(m_screenGeo.x()).rightJustified(4, QLatin1Char(' '))) + .arg(QString::number(m_screenGeo.y()).rightJustified(4, QLatin1Char(' '))) + ; +} + +LtsScreenContainer::LtsScreenContainer(QRect screenGeo) + : m_layout(new QHBoxLayout) + , m_screenGeo(screenGeo) +{ + + m_layout->setMargin(0); + m_layout->setSpacing(0); + m_layout->setStretch(0, 999); + + setContentsMargins(0, 0, 0, 0); + vanish(); + setLayout(m_layout); + + + + QTimer *t = new QTimer(this); + t->setInterval(3000); +#ifdef LTS_DEBUG + t->start(); +#endif + connect(t, &QTimer::timeout, + this, [this, screenGeo]() + { + QString old(QStringLiteral("<#!UNKNOWN!#>")); + + if (m_oldActiveWidget == nullptr) + old = QStringLiteral("null"); + else if (m_oldActiveWidget == &m_mainWidget) + old = QStringLiteral("MAIN"); + else if (m_oldActiveWidget == &m_secondaryWidget) + old = QStringLiteral("SECONDARY"); + + + }); +} + + +bool LtsScreenContainer::isInactiveWidgetOccupied() const +{ + if (m_activeWidget == &m_mainWidget && m_secondaryWidget != nullptr) + return true; + if (m_activeWidget == &m_secondaryWidget && m_mainWidget != nullptr) + return true; + return false; +} + + +QIcon LtsScreenContainer::mainWindowIcon() const +{ + if (m_mainWidget != nullptr) + return m_mainWidget->windowIcon(); + return {}; +} + + +QIcon LtsScreenContainer::secondaryWindowIcon() const +{ + if (m_secondaryWidget != nullptr) + return m_secondaryWidget->windowIcon(); + return {}; +} + + +bool LtsScreenContainer::mainAndSecondaryOccupied() const +{ + return (m_mainWidget != nullptr && m_secondaryWidget != nullptr); +} + + +LtsScreenBase::WidgetType LtsScreenContainer::activeWidgetType() const +{ + if (m_activeWidget == &m_mainWidget) + return LtsScreenBase::WidgetType::MainWidget; + if (m_activeWidget == &m_secondaryWidget) + return LtsScreenBase::WidgetType::SecondaryWidget; + + assert(false); + return LtsScreenBase::WidgetType::None; +} + + +void LtsScreenContainer::toggle() +{ + + switchToTemp((*m_activeWidget == m_mainWidget) ? LtsScreenBase::WidgetType::SecondaryWidget : LtsScreenBase::WidgetType::MainWidget); + + QWidget *w = activeWidget(); + + assert(w != nullptr); + if (w == nullptr) + { + revertSwitch(); + return; + } + + if (!tryPutDummyUnmanaged(w)) + w->show(); + + finalizeSwitch(); +} + + +bool LtsScreenContainer::isIrreplaceable() const +{ + if (m_activeWidget == nullptr) + return false; + + auto *blw = qobject_cast(activeWidget()); + if (blw == nullptr) + return false; + + return blw->isIrreplaceable(); +} + + +QWidget *LtsScreenContainer::activeWidget() const +{ + assert(m_activeWidget != nullptr); + + + + + + + return *m_activeWidget; +} + + +QWidget **LtsScreenContainer::activeWidgetPtr() +{ + assert(m_activeWidget != nullptr); + + + + + + + return m_activeWidget; +} + + +void LtsScreenContainer::vanish() +{ + return; + + + + + + + + + + + m_layout->setParent(nullptr); +} + + +void LtsScreenContainer::reappear() +{ + return; + + + + + setAttribute(Qt::WA_TranslucentBackground, false); + + + + + + if (m_layout->parent() == nullptr) + setLayout(m_layout); +} + + + + + + + + + + + + + +QWidget *LtsScreenContainer::stealWidget() noexcept(false) +{ + + + QWidget **widgetPtr = activeWidgetPtr(); + QWidget *widget = *widgetPtr; + + if (widget == nullptr) + { + + throw "stealWidget: no widget to steal"; + } + + + + + widget->setParent(nullptr); + vanish(); + + + disconnect(widget, &QWidget::destroyed, + this, &LtsScreenContainer::slotChildDestroyed); + + + + + + + + + QWidget *oldWidget = widget; + *widgetPtr = nullptr; + + return oldWidget; +} + + +bool LtsScreenContainer::hasWidget(QWidget *widget) const +{ + if (m_mainWidget != nullptr && m_mainWidget == widget) + return true; + if (m_secondaryWidget != nullptr && m_secondaryWidget == widget) + return true; + return false; +} + + +bool LtsScreenContainer::isEmpty() const +{ + QWidget **currentWidgetPtr = m_activeWidget; + if (currentWidgetPtr == nullptr) + return true; + return (*currentWidgetPtr == nullptr); +} + +bool LtsScreenContainer::hasMainWidget() +{ + return m_mainWidget != nullptr; +} + +bool LtsScreenContainer::hasSecondaryWidget() +{ + return m_secondaryWidget != nullptr; +} + +bool LtsScreenContainer::tryPutDummyUnmanaged(QWidget *widget) +{ + auto *dummy = qobject_cast(widget); + + if (dummy == nullptr) + return false; + + + + + + + QRect r(mapToGlobal(rect().topLeft()), rect().size()); + + emit dummy->signalWidgetPlaced(r); + + + + + + + + + + emit signalSetCornerIcons(); + + return true; +} + + +void LtsScreenContainer::switchToTemp(LtsScreenBase::WidgetType widgetType) +{ + + + Q_ASSERT(widgetType != LtsScreenBase::WidgetType::None); + + if ((m_activeWidget == &m_mainWidget && widgetType == LtsScreenBase::WidgetType::MainWidget) || + (m_activeWidget == &m_secondaryWidget && widgetType == LtsScreenBase::WidgetType::SecondaryWidget)) + { + + + m_nullSwitchActive = true; + return; + } + + if (widgetType == LtsScreenBase::WidgetType::MainWidget) + m_oldActiveWidget = &m_secondaryWidget; + else if (widgetType == LtsScreenBase::WidgetType::SecondaryWidget) + m_oldActiveWidget = &m_mainWidget; + else + Q_ASSERT(false); + + + + + m_activeWidget = (widgetType == LtsScreenBase::WidgetType::SecondaryWidget ? &m_secondaryWidget : &m_mainWidget); + + +} + + +void LtsScreenContainer::finalizeSwitch() +{ + + + QWidget **oldActiveWidget = m_oldActiveWidget; + m_oldActiveWidget = nullptr; + + + if (m_nullSwitchActive) + { + + m_nullSwitchActive = false; + return; + } + + if (oldActiveWidget == nullptr) + { + + return; + } + + if (*oldActiveWidget == nullptr) + { + + return; + } + + + + + + + + + + + + QWidget *widget = *oldActiveWidget; + auto *dummy = qobject_cast(widget); + if (dummy != nullptr) + { + + dummy->hideVideo(); + } + else + { + + widget->hide(); + } + + QWidget *widget2 = *m_activeWidget; + auto *dummy2 = qobject_cast(widget2); + if (dummy2 != nullptr) + { + dummy2->showVideo(); + } + + emit signalSetCornerIcons(); +} + + +void LtsScreenContainer::revertSwitch() +{ + + + QWidget **oldActiveWidgetBackup = m_oldActiveWidget; + m_oldActiveWidget = nullptr; + + + if (m_nullSwitchActive) + { + + m_nullSwitchActive = false; + return; + } + + + + if (oldActiveWidgetBackup == nullptr) + return; + + m_activeWidget = oldActiveWidgetBackup; + +} + + +void LtsScreenContainer::setWidget(QWidget *widget) +{ + + + + + QWidget **widgetPtr = activeWidgetPtr(); + + qWarning() << geoStr() << "setWidget: m_activeWidget" << m_activeWidget; + + QWidget *currentWidget = *widgetPtr; + + Q_ASSERT(currentWidget == nullptr); + + + + + + *widgetPtr = widget; + reappear(); + + connect(widget, &QWidget::destroyed, + this, &LtsScreenContainer::slotChildDestroyed, + Qt::UniqueConnection); + + if (tryPutDummyUnmanaged(widget)) + return; + + + + + + + m_layout->addWidget(widget); + + + + + + + + + widget->show(); + + emit signalSetCornerIcons(); +} + + +void LtsScreenContainer::slotChildDestroyed() +{ + if (sender() == static_cast(m_mainWidget)) + { + m_mainWidget = nullptr; + qWarning() << geoStr() << "m_mainWidget set to nullptr!"; + + if (*m_activeWidget == m_mainWidget) + { + qWarning() << "slotChildDestroyed: switching to sec"; + switchToTemp(LtsScreenBase::WidgetType::SecondaryWidget); + + QWidget *widget = activeWidget(); + + if (widget != nullptr) + { + auto *dummy = qobject_cast(widget); + if (dummy != nullptr) + { + QRect r(mapToGlobal(rect().topLeft()), rect().size()); + qWarning() << geoStr() << "LtsScreenContainer::slotChildDestroyed => (main) dummy->signalWidgetPlaced"; + emit dummy->signalWidgetPlaced(r); + } + else + { + widget->show(); + } + } + + finalizeSwitch(); + } + } + else if (sender() == static_cast(m_secondaryWidget)) + { + m_secondaryWidget = nullptr; + qWarning() << geoStr() << "m_secondaryWidget set to nullptr!"; + + if (*m_activeWidget == m_secondaryWidget) + { + qWarning() << "slotChildDestroyed: switching to main"; + switchToTemp(LtsScreenBase::WidgetType::MainWidget); + + QWidget *widget = activeWidget(); + + if (widget != nullptr) + { + auto *dummy = qobject_cast(widget); + if (dummy != nullptr) + { + QRect r(mapToGlobal(rect().topLeft()), rect().size()); + qWarning() << geoStr() << "LtsScreenContainer::slotChildDestroyed => (sec) dummy->signalWidgetPlaced"; + emit dummy->signalWidgetPlaced(r); + } + else + { + widget->show(); + } + } + + finalizeSwitch(); + } + } + else + { + qWarning() << geoStr() << "slotChildDestroyed NOT received from any known widget !!"; + Q_ASSERT(false); + } + + vanish(); + emit check(); +} + + +QWidget *LtsScreenContainer::widget() noexcept(false) +{ + QWidget **widgetPtr = m_activeWidget; + if (widgetPtr != nullptr && *widgetPtr != nullptr) + return *widgetPtr; + throw "no widget in container"; +} + +} diff --git a/Lts/Common/LtsMultiMonitor/lts_screen_container.h b/Lts/Common/LtsMultiMonitor/lts_screen_container.h new file mode 100755 index 000000000..acd91a7a6 --- /dev/null +++ b/Lts/Common/LtsMultiMonitor/lts_screen_container.h @@ -0,0 +1,68 @@ +#pragma once + +#include "lts_screen_base.h" + +#include + +#include +#include + + +class QHBoxLayout; + + +namespace LtsMultiMonitor +{ +class LTSMULTIMONITOR_EXPORT LtsScreenContainer : public QWidget +{ + Q_OBJECT + +public: + LtsScreenContainer(QRect screenGeo); + + bool isIrreplaceable() const; + bool isEmpty() const; + bool hasMainWidget(); + bool hasSecondaryWidget(); + void setWidget(QWidget *widget); + bool hasWidget(QWidget *widget) const; + QWidget *widget() noexcept(false); + QWidget *stealWidget() noexcept(false); + void switchToTemp(LtsScreenBase::WidgetType widgetType); + void finalizeSwitch(); + void revertSwitch(); + bool mainAndSecondaryOccupied() const; + QIcon mainWindowIcon() const; + QIcon secondaryWindowIcon() const; + LtsScreenBase::WidgetType activeWidgetType() const; + bool isInactiveWidgetOccupied() const; + +signals: + void check(); + void signalSetCornerIcons(); + +private: + QHBoxLayout *m_layout { nullptr }; + QWidget *m_mainWidget { nullptr }; + QWidget *m_secondaryWidget { nullptr }; + QWidget **m_activeWidget { nullptr }; + QWidget **m_oldActiveWidget { nullptr }; + bool m_nullSwitchActive { false }; + QRect m_screenGeo; + + QString geoStr() const; + bool tryPutDummyUnmanaged(QWidget *widget); + void vanish(); + void reappear(); + QWidget *activeWidget() const; + QWidget **activeWidgetPtr(); + +public slots: + void toggle(); + +private slots: + + void slotChildDestroyed(); +}; + +} diff --git a/Lts/Common/LtsMultiMonitor/lts_screen_normal.cpp b/Lts/Common/LtsMultiMonitor/lts_screen_normal.cpp new file mode 100755 index 000000000..39b25f7cf --- /dev/null +++ b/Lts/Common/LtsMultiMonitor/lts_screen_normal.cpp @@ -0,0 +1,201 @@ +#include "lts_screen_normal.h" +#include "lts_dummy_widget.h" +#include "lts_screen_container.h" +#include "lts_magic_corner.h" + +#include + +#include +#include +#include + + +#ifdef Q_OS_WIN +#include +#endif + + +namespace LtsMultiMonitor +{ + +LtsScreenNormal::LtsScreenNormal(QRect screenGeo, int screenId, bool debugMargins, const QString &serial) + : LtsScreenBase(screenGeo, screenId, debugMargins, serial) + , m_childContainer(new LtsScreenContainer(screenGeo)) + , m_corner(new LtsMagicCorner(screenGeo)) +{ + + connect(m_childContainer, &LtsScreenContainer::check, + this, &LtsScreenNormal::check); + + auto *theLayout = new QHBoxLayout; + + theLayout->setContentsMargins(0, 0, 0, 0); + theLayout->setMargin(0); + theLayout->setSpacing(0); + theLayout->setStretch(0, 999); + theLayout->addWidget(m_childContainer); + + m_screenDialog->setLayout(theLayout); + + m_corner->hide(); + connect(m_corner, &LtsMagicCorner::toggle, + this, [this]() + { + m_childContainer->toggle(); + m_corner->setActiveWidgetType(m_childContainer->activeWidgetType()); + setCornerIcons(); + }); + + connect(m_corner, &LtsMagicCorner::signalMakeAllSwole, + this, &LtsScreenNormal::signalMakeAllSwole); + connect(this, &LtsScreenNormal::signalMakeSwole, + m_corner, &LtsMagicCorner::makeSwole); + + connect(m_childContainer, &LtsScreenContainer::signalSetCornerIcons, + this, &LtsScreenNormal::setCornerIcons); +} + + +LtsScreenNormal::~LtsScreenNormal() +{ + m_corner->deleteLater(); +} + + +void LtsScreenNormal::setCornerIcons() +{ + m_corner->setActiveWidgetType(m_childContainer->activeWidgetType()); + m_corner->setIcon(m_childContainer->mainWindowIcon(), LtsScreenBase::WidgetType::MainWidget); + m_corner->setIcon(m_childContainer->secondaryWindowIcon(), LtsScreenBase::WidgetType::SecondaryWidget); + +} + + +void LtsScreenNormal::check() +{ + m_childContainer->mainAndSecondaryOccupied() ? m_corner->show() : m_corner->hide(); +} + + +LtsScreenContainer *LtsScreenNormal::containerOfWidget(QWidget *widget) +{ + if (m_childContainer->hasWidget(widget)) + return m_childContainer; + throw "widget is not present on this container"; +} + +void LtsScreenNormal::hideMagicCorner(bool hide) +{ + m_corner->setFreezed(hide); +} + + +bool LtsScreenNormal::isIrreplaceable() const +{ + return m_childContainer->isIrreplaceable(); +} + + +int LtsScreenNormal::containerIdOfWidget(QWidget *widget) const noexcept(false) +{ + if (m_childContainer->hasWidget(widget)) + return 0; + throw "widget not on this screen"; +} + + +LtsScreenContainer *LtsScreenNormal::putWidget(QWidget *widget, int ) noexcept(false) +{ + if (!isEmpty()) + throw "cannot put widget as container is non-empty"; + putWidgetUnmanaged(widget); + return m_childContainer; +} + + +bool LtsScreenNormal::hasWidget(QWidget *widget) const +{ + return (m_childContainer->hasWidget(widget)); +} + + +void LtsScreenNormal::putWidgetUnmanaged(QWidget *widget) +{ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + m_childContainer->setWidget(widget); + + + + + setCornerIcons(); + + + + + + +} + + +void LtsScreenNormal::slotOverlayDestroyed() +{ + m_screenDialog->show(); +} + + +bool LtsScreenNormal::isEmpty() const +{ + return m_childContainer->isEmpty(); +} + + +void LtsScreenNormal::switchToTemp(LtsScreenBase::WidgetType widgetType) +{ + m_childContainer->switchToTemp(widgetType); +} + + +void LtsScreenNormal::finalizeSwitch() +{ + m_childContainer->finalizeSwitch(); +} + + +void LtsScreenNormal::revertSwitch() +{ + m_childContainer->revertSwitch(); +} + +} diff --git a/Lts/Common/LtsMultiMonitor/lts_screen_normal.h b/Lts/Common/LtsMultiMonitor/lts_screen_normal.h new file mode 100755 index 000000000..cae9373aa --- /dev/null +++ b/Lts/Common/LtsMultiMonitor/lts_screen_normal.h @@ -0,0 +1,58 @@ +#pragma once + +#include "lts_screen_base.h" + +#include + +#include +#include + + +class QSplitter; +class QHBoxLayout; + + +namespace LtsMultiMonitor +{ +class LtsMagicCorner; + + +class LTSMULTIMONITOR_EXPORT LtsScreenNormal : public LtsScreenBase +{ + Q_OBJECT + +public: + LtsScreenNormal(QRect screenGeo, int screenId, bool debugMargins, const QString &serial); + ~LtsScreenNormal(); + int screenType() const override { return 0; } + int containerIdOfWidget(QWidget *widget) const noexcept(false) override; + LtsScreenContainer *child() const { return m_childContainer; } + LtsScreenContainer *putWidget(QWidget *widget, int) noexcept(false) override; + LtsScreenContainer *containerOfWidget(QWidget *widget) noexcept(false) override; + void hideMagicCorner(bool hide); + bool isEmpty() const override; + bool isIrreplaceable() const override; + void putWidgetUnmanaged(QWidget *widget); + bool hasWidget(QWidget *widget) const override; + void switchToTemp(WidgetType widgetType) override; + void finalizeSwitch() override; + void revertSwitch() override; + void check() override; + +private: + LtsScreenContainer *m_childContainer { nullptr }; + LtsMagicCorner *m_corner; + + + void swapLeftRight(); + bool tryPutDummyUnmanaged(QWidget *widget); + +signals: + void signalMakeAllSwole(); + void signalMakeSwole(); + +private slots: + void slotOverlayDestroyed(); + void setCornerIcons(); +}; +} diff --git a/Lts/Common/LtsMultiMonitor/lts_screen_wide.cpp b/Lts/Common/LtsMultiMonitor/lts_screen_wide.cpp new file mode 100755 index 000000000..9116442c1 --- /dev/null +++ b/Lts/Common/LtsMultiMonitor/lts_screen_wide.cpp @@ -0,0 +1,236 @@ +#include "lts_screen_wide.h" +#include "lts_dummy_widget.h" +#include "lts_screen_container.h" + +#include + +#include +#include +#include +#include +#ifdef Q_OS_WIN +#include +#endif + +namespace LtsMultiMonitor +{ + +LtsScreenWide::LtsScreenWide(const QRect &screenGeo, int screenId, bool debugMargins, const QString &serial) + : LtsScreenBase(screenGeo, screenId, debugMargins, serial) + , m_childContainerMain(new LtsScreenContainer(screenGeo)) + , m_childContainerLeft(new LtsScreenContainer(screenGeo)) + , m_childContainerRight(new LtsScreenContainer(screenGeo)) +{ + + connect(m_childContainerMain, &LtsScreenContainer::check, + this, &LtsScreenWide::check); + connect(m_childContainerLeft, &LtsScreenContainer::check, + this, &LtsScreenWide::check); + connect(m_childContainerRight, &LtsScreenContainer::check, + this, &LtsScreenWide::check); + + auto *theLayout = new QHBoxLayout; + + theLayout->setContentsMargins(0, 0, 0, 0); + theLayout->setMargin(0); + theLayout->setSpacing(0); + theLayout->setStretch(0, 9999); + + m_splitter = new QSplitter; + m_splitter->setContentsMargins(0, 0, 0, 0); + + m_splitter->addWidget(m_childContainerLeft); + m_splitter->addWidget(m_childContainerRight); + + theLayout->addWidget(m_splitter); + theLayout->addWidget(m_childContainerMain); + + m_screenDialog->setLayout(theLayout); + + m_splitter->hide(); +} + + +void LtsScreenWide::putWidgetUnmanagedMain(QWidget *widget) +{ + + m_childContainerMain->setWidget(widget); +} + + +void LtsScreenWide::putWidgetUnmanagedRight(QWidget *widget) +{ + + m_childContainerRight->setWidget(widget); +} + + +void LtsScreenWide::putWidgetUnmanagedLeft(QWidget *widget) +{ + + m_childContainerLeft->setWidget(widget); +} + + +bool LtsScreenWide::hasWidget(QWidget *widget) const +{ + return (m_childContainerMain->hasWidget(widget) || + m_childContainerLeft->hasWidget(widget) || + m_childContainerRight->hasWidget(widget)); +} + + +bool LtsScreenWide::isIrreplaceable() const +{ + return (childLeft()->isIrreplaceable() && childRight()->isIrreplaceable() && childMain()->isIrreplaceable()); +} + + +bool LtsScreenWide::isEmpty() const +{ + return (m_childContainerLeft->isEmpty() && m_childContainerRight->isEmpty() && m_childContainerMain->isEmpty()); +} + + +int LtsScreenWide::containerIdOfWidget(QWidget *widget) const noexcept(false) +{ + if (m_childContainerMain->hasWidget(widget)) + return 0; + if (m_childContainerLeft->hasWidget(widget)) + return 1; + if (m_childContainerRight->hasWidget(widget)) + return 2; + throw "widget not on this screen"; +} + + +LtsScreenContainer *LtsScreenWide::containerOfWidget(QWidget *widget) noexcept(false) +{ + if (m_childContainerMain->hasWidget(widget)) + return m_childContainerMain; + if (m_childContainerLeft->hasWidget(widget)) + return m_childContainerLeft; + if (m_childContainerRight->hasWidget(widget)) + return m_childContainerRight; + throw "widget is not present on this container"; +} + + +LtsScreenContainer *LtsScreenWide::putWidget(QWidget *widget, int screenPart) noexcept(false) +{ + if (screenPart == -1) + { + if (!isEmpty()) + throw "cannot put widget as at least one container is occupied and screen part was not specified"; + + putWidgetUnmanagedMain(widget); + return m_childContainerMain; + } + + switch (screenPart) + { + case 0: + { + if (!m_childContainerMain->isEmpty()) + throw "cannot put widget as main container is already occupied"; + + putWidgetUnmanagedMain(widget); + return m_childContainerMain; + } + + case 1: + { + if (!m_childContainerLeft->isEmpty()) + throw "cannot put widget as left container is already occupied"; + + + + if (!m_childContainerMain->isEmpty()) + putWidgetUnmanagedRight(m_childContainerMain->stealWidget()); + + putWidgetUnmanagedLeft(widget); + return m_childContainerLeft; + } + + case 2: + { + if (!m_childContainerRight->isEmpty()) + throw "cannot put widget as right container is already occupied"; + + + + if (!m_childContainerMain->isEmpty()) + putWidgetUnmanagedLeft(m_childContainerMain->stealWidget()); + + putWidgetUnmanagedRight(widget); + return m_childContainerRight; + } + + default: + { + throw "unknown screen part id"; + } + } +} + + +void LtsScreenWide::check() +{ + + + + + + Q_ASSERT( (!m_childContainerMain->isEmpty() && m_childContainerLeft->isEmpty() && m_childContainerRight->isEmpty()) || m_childContainerMain->isEmpty() ); + + + + if (m_childContainerMain->isEmpty()) + { + if (m_childContainerLeft->isEmpty() && !m_childContainerRight->isEmpty()) + { + try + { + QWidget *widget = m_childContainerRight->stealWidget(); + m_childContainerMain->setWidget(widget); + } + catch(...) + { + + + } + } + else if (!m_childContainerLeft->isEmpty() && m_childContainerRight->isEmpty()) + { + try + { + QWidget *widget = m_childContainerLeft->stealWidget(); + m_childContainerMain->setWidget(widget); + } + catch(...) + { + + + } + } + } + + + + if (!m_childContainerMain->isEmpty()) + { + + m_childContainerMain->show(); + m_splitter->hide(); + return; + } + + + + m_childContainerMain->hide(); + m_splitter->setVisible(!m_childContainerLeft->isEmpty() || !m_childContainerRight->isEmpty()); + m_childContainerLeft->setVisible(!m_childContainerLeft->isEmpty()); + m_childContainerRight->setVisible(!m_childContainerRight->isEmpty()); +} + +} diff --git a/Lts/Common/LtsMultiMonitor/lts_screen_wide.h b/Lts/Common/LtsMultiMonitor/lts_screen_wide.h new file mode 100755 index 000000000..dfe91d89a --- /dev/null +++ b/Lts/Common/LtsMultiMonitor/lts_screen_wide.h @@ -0,0 +1,51 @@ +#pragma once + +#include "lts_screen_base.h" + +#include + +#include +#include + + +class QSplitter; +class QHBoxLayout; + + +namespace LtsMultiMonitor +{ + +class LTSMULTIMONITOR_EXPORT LtsScreenWide : public LtsScreenBase +{ + Q_OBJECT + +public: + LtsScreenWide(const QRect &screenGeo, int screenId, bool debugMargins, const QString &serial); + + int screenType() const override { return 1; } + LtsScreenContainer *containerOfWidget(QWidget *widget) noexcept(false) override; + bool isEmpty() const override; + LtsScreenContainer *childLeft() const { return m_childContainerLeft; } + LtsScreenContainer *childRight() const { return m_childContainerRight; } + LtsScreenContainer *childMain() const { return m_childContainerMain; } + void putWidgetUnmanagedMain(QWidget *widget); + void putWidgetUnmanagedLeft(QWidget *widget); + void putWidgetUnmanagedRight(QWidget *widget); + bool isIrreplaceable() const override; + bool hasWidget(QWidget *widget) const override; + LtsScreenContainer *putWidget(QWidget *widget, int screenPart) noexcept(false) override; + int containerIdOfWidget(QWidget *widget) const noexcept(false) override; + +public slots: + void check() override; + +private: + LtsScreenContainer *m_childContainerMain; + LtsScreenContainer *m_childContainerLeft; + LtsScreenContainer *m_childContainerRight; + QSplitter *m_splitter; + + void swapLeftRight(); +}; + +} diff --git a/Lts/Common/LtsNotification/CMakeLists.txt b/Lts/Common/LtsNotification/CMakeLists.txt new file mode 100755 index 000000000..e995fa32c --- /dev/null +++ b/Lts/Common/LtsNotification/CMakeLists.txt @@ -0,0 +1,29 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# Created by Mike Wille, SLSE, 2016-01-13 +# + + +SET(LtsNoti_SRCS + lts_notification.h + lts_notification.cpp + lts_notification_ctl.h + lts_notification_ctl.cpp + + lts_notification_export.h +) + +NGL_ADD_LIBRARY(WS LtsNotification SHARED ${LtsNoti_SRCS}) + +NGL_TARGET_INCLUDE_DIRECTORIES(LtsNotification + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) + +NGL_TARGET_LINK_LIBRARIES(LtsNotification + Qt5::Widgets +) + +NGL_INSTALL_TARGET(LtsNotification) diff --git a/Lts/Common/LtsNotification/lts_notification.cpp b/Lts/Common/LtsNotification/lts_notification.cpp new file mode 100755 index 000000000..aae0c0465 --- /dev/null +++ b/Lts/Common/LtsNotification/lts_notification.cpp @@ -0,0 +1,92 @@ + + +#include "lts_notification.h" +#include "lts_notification_ctl.h" + +#include + + +namespace LtsNotification +{ + +LtsNotification::LtsNotification(LtsNotificationController *ctl) + : QObject() + , m_controller(ctl) +{ + connect(ctl, &LtsNotificationController::destroyed, + this, &LtsNotification::slotCtlDestroyed); +} + + +LtsNotification::~LtsNotification() +{ + delete m_controller; +} + + +void LtsNotification::setIcon(const QIcon& icon) +{ + m_controller->setIcon(icon); +} + + +void LtsNotification::setAction(const std::function &action) +{ + m_controller->setAction(action); +} + + +void LtsNotification::setToolTip(const QString &toolTip) +{ + m_controller->setToolTip(toolTip); +} + + +const QString &LtsNotification::tooltip() const +{ + return m_controller->tooltip(); +} + + +void LtsNotification::setCount(const int count) +{ + m_controller->setCount(count); +} + +void LtsNotification::setEnabled(bool enable) +{ + m_controller->setEnabled(enable); +} + +void LtsNotification::setBackground(const int r, const int g, const int b) +{ + m_controller->setBackground(r, g, b); +} + + + + + + + + + + + + + + + + + + + + +void LtsNotification::slotCtlDestroyed(QObject *obj) +{ + (void)obj; + + m_controller = nullptr; +} + +} diff --git a/Lts/Common/LtsNotification/lts_notification.h b/Lts/Common/LtsNotification/lts_notification.h new file mode 100755 index 000000000..05be741ca --- /dev/null +++ b/Lts/Common/LtsNotification/lts_notification.h @@ -0,0 +1,57 @@ + + +#pragma once + +#include "lts_notification_export.h" + +#include +#include +#include +#include + + +namespace LtsSession +{ +class LtsSession; +} + + +namespace LtsNotification +{ +class LtsNotificationController; + + + +class LTSNOTIFICATION_EXPORT LtsNotification : public QObject +{ + Q_OBJECT + +public: + + explicit LtsNotification(LtsNotificationController *ctl); + ~LtsNotification(); + + void setIcon(const QIcon &icon); + void setAction(const std::function &action); + void setToolTip(const QString &toolTip); + void setCount(const int count); + void setEnabled(bool enable); + void setBackground(const int r, const int g, const int b); + + const QString &tooltip() const; + + + + + +private: + LtsNotificationController *m_controller; + + + +private slots: + void slotCtlDestroyed(QObject *obj); + +}; + +} diff --git a/Lts/Common/LtsNotification/lts_notification_ctl.cpp b/Lts/Common/LtsNotification/lts_notification_ctl.cpp new file mode 100755 index 000000000..3a35ed5a9 --- /dev/null +++ b/Lts/Common/LtsNotification/lts_notification_ctl.cpp @@ -0,0 +1,172 @@ + + +#include "lts_notification_ctl.h" + +#include +#include +#include +#include +#include +#include + + +namespace LtsNotification +{ + +LtsNotificationController::LtsNotificationController(const QString &identifier) + : QObject() + , m_count(0) + , m_identifier(QStringLiteral("LtsNotification%1").arg(identifier)) +{} + + +LtsNotificationController::~LtsNotificationController() +{ + qDeleteAll(m_notiList); +} + + +void LtsNotificationController::setIcon(const QIcon& icon) +{ + m_icon = icon; + + setAllIcons(); +} + + +void LtsNotificationController::setCount(const int count) +{ + m_count = count; + + setAllIcons(); +} + + + +void LtsNotificationController::setBackground(const int r, const int g, const int b) +{ + + for (QToolButton *tb : qAsConst(m_notiList)) + { + + QString stylesheet; + + if(r == -1 && g == -1 && b == -1) + stylesheet = QStringLiteral(""); + else + stylesheet = QStringLiteral("QToolButton {background-color: rgb(") + + QString::number(r) + QStringLiteral(",") + + QString::number(g) + QStringLiteral(",") + + QString::number(b) + QStringLiteral(");}"); + + tb->setStyleSheet(stylesheet); + } +} + +void LtsNotificationController::setEnabled(bool enable) +{ + m_enable = enable; + + setAllIcons(); +} + + +void LtsNotificationController::setAction(const std::function &action) +{ + m_action = action; +} + + +void LtsNotificationController::setToolTip(const QString &toolTip) +{ + m_toolTip = toolTip; + + for (QToolButton *tb : qAsConst(m_notiList)) + { + tb->setToolTip(toolTip); + tb->setIconSize(QSize(30,30)); + } +} + + +void LtsNotificationController::setAllIcons() +{ + if (m_icon.isNull()) + return; + + + QIcon icon = modifyIcon(m_icon); + + for (QToolButton *tb : qAsConst(m_notiList)) + { + tb->setIcon(icon); + tb->setIconSize(QSize(30,30)); + tb->setEnabled(m_enable); + } +} + + +#define SIZE 40 + +QIcon LtsNotificationController::modifyIcon(const QIcon &icon) +{ + + + + if (m_count < 1) + return icon; + + QPixmap iconImage(icon.pixmap(SIZE * 8 / 10, SIZE * 8 / 10)); + QPainter painter(&iconImage); + + painter.save(); + painter.setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing | QPainter::HighQualityAntialiasing | QPainter::SmoothPixmapTransform); + + QFont f; + f.setFamily(QStringLiteral("Monospace")); + f.setPointSize(16); + f.setBold(true); + painter.setFont(f); + + QColor color(Qt::red); + painter.setBrush(color); + painter.setPen(QPen(color, 3.1)); + painter.drawText(QRect(0, 0, 30, 30), QString::number(m_count)); + painter.restore(); + + return iconImage; +} + + +QToolButton *LtsNotificationController::createWidget() +{ + QToolButton *b = new QToolButton(); + b->setToolButtonStyle(Qt::ToolButtonIconOnly); + b->setObjectName(m_identifier); + + + + b->setMinimumSize(SIZE, SIZE); + b->setMaximumSize(SIZE, SIZE); + + connect(b, &QToolButton::destroyed, + this, &LtsNotificationController::slotNotiWidgetDestroyed); + connect(b, &QToolButton::clicked, + this, [this](){ if (m_action) m_action(); }); + + if (!m_icon.isNull()) + b->setIcon(modifyIcon(m_icon)); + + b->setToolTip(m_toolTip); + + m_notiList.append(b); + return b; +} + + +void LtsNotificationController::slotNotiWidgetDestroyed(QObject *obj) +{ + m_notiList.removeAll(static_cast(obj)); +} + +} diff --git a/Lts/Common/LtsNotification/lts_notification_ctl.h b/Lts/Common/LtsNotification/lts_notification_ctl.h new file mode 100755 index 000000000..a179afccf --- /dev/null +++ b/Lts/Common/LtsNotification/lts_notification_ctl.h @@ -0,0 +1,67 @@ + + +#pragma once + +#include "lts_notification_export.h" + +#include +#include +#include +#include +#include + + +class QToolButton; + +namespace LtsSession +{ +class LtsSession; +} + + +namespace LtsNotification +{ +class LtsNotificationWidget; + + + +class LTSNOTIFICATION_EXPORT LtsNotificationController : public QObject +{ + Q_OBJECT + +public: + explicit LtsNotificationController(const QString &identifier); + ~LtsNotificationController(); + + QToolButton *createWidget(); + + void setIcon(const QIcon& icon); + void setAction(const std::function &action); + void setToolTip(const QString &toolTip); + void setCount(const int count); + void setBackground(const int r, const int g, const int b); + const QString &identifier() const { return m_identifier; } + const QString &tooltip() const { return m_toolTip; } + + void setEnabled(bool enable); + +private: + QList m_notiList; + + + QIcon m_icon; + std::function m_action; + QString m_toolTip; + int m_count; + bool m_enable{true}; + QString m_identifier; + + void setAllIcons(); + QIcon modifyIcon(const QIcon &icon); + +private slots: + void slotNotiWidgetDestroyed(QObject *obj); + +}; + +} diff --git a/Lts/Common/LtsNotification/lts_notification_export.h b/Lts/Common/LtsNotification/lts_notification_export.h new file mode 100755 index 000000000..92853b0c3 --- /dev/null +++ b/Lts/Common/LtsNotification/lts_notification_export.h @@ -0,0 +1,19 @@ + + + +#pragma once + +#include + +#ifndef LTSNOTIFICATION_EXPORT +# ifdef BUILD_STATIC_LIBRARIES +# define LTSNOTIFICATION_EXPORT +# else +# if defined(MAKE_LTSNOTIFICATION_LIB) +# define LTSNOTIFICATION_EXPORT Q_DECL_EXPORT +# else +# define LTSNOTIFICATION_EXPORT Q_DECL_IMPORT +# endif +# endif +#endif + diff --git a/Lts/Common/LtsPluginManager/CMakeLists.txt b/Lts/Common/LtsPluginManager/CMakeLists.txt new file mode 100755 index 000000000..4cbbc180f --- /dev/null +++ b/Lts/Common/LtsPluginManager/CMakeLists.txt @@ -0,0 +1,65 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# Created by Mike Wille, SLSE, 2016-05-20 +# + + +# LtsPluginLoader +NGL_ADD_LIBRARY(ALL LtsPluginLoader SHARED lts_plugin_loader.cpp lts_plugin_loader.h) +NGL_TARGET_INCLUDE_DIRECTORIES(LtsPluginLoader + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) + +NGL_TARGET_LINK_LIBRARIES(LtsPluginLoader + PRIVATE + NGLLogger + PUBLIC + Qt5::Core +) + +# LtsPluginManager +SET(ltspluginmanager_SRCS + lts_plugin_manager.h + lts_plugin_manager.cpp + Ui/lts_plugin_manager_dialog.h + Ui/lts_plugin_manager_dialog.cpp + Ui/lts_plugin_manager_widget.h + Ui/lts_plugin_manager_widget.cpp + + lts_plugin_manager_export.h +) + +NGL_WRAP_UI(ltspluginmanager_SRCS + Ui/lts_plugin_manager_dialog.ui + Ui/lts_plugin_manager_widget.ui +) + +NGL_ADD_LIBRARY(WS LtsPluginManager SHARED ${ltspluginmanager_SRCS}) + +NGL_TARGET_INCLUDE_DIRECTORIES(LtsPluginManager + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) + +NGL_TARGET_LINK_LIBRARIES(LtsPluginManager + PRIVATE + NGLLogger + LtsPluginLoader + GuiPluginInterface + DataPluginInterface + LtsWidgets + LtsSession + LtsSessionData + Qt5::Widgets + PUBLIC + ProjectConfig + LtsAppDescriptor + Qt5::Core +) + +NGL_INSTALL_TARGET(LtsPluginManager) + +NGL_INSTALL_TARGET(LtsPluginLoader) diff --git a/Lts/Common/LtsPluginManager/Ui/lts_plugin_manager_dialog.cpp b/Lts/Common/LtsPluginManager/Ui/lts_plugin_manager_dialog.cpp new file mode 100755 index 000000000..def518bb4 --- /dev/null +++ b/Lts/Common/LtsPluginManager/Ui/lts_plugin_manager_dialog.cpp @@ -0,0 +1,102 @@ + + + +#include "lts_plugin_manager_dialog.h" +#include "lts_plugin_manager_widget.h" +#include "ui_lts_plugin_manager_dialog.h" +#include "../lts_plugin_manager.h" + +#include +#include +#include + + +namespace LtsPluginManager +{ + +LtsPluginManagerDialog::LtsPluginManagerDialog(QWidget *parent) + : QDialog(parent) + , m_ui(new Ui::LtsPluginManagerDialog) +{ + m_ui->setupUi(this); + + + m_ui->masterSplitter->setStretchFactor(m_ui->masterSplitter->indexOf(m_ui->pluginsWidget), 20); + m_ui->masterSplitter->setStretchFactor(m_ui->masterSplitter->indexOf(m_ui->lwErrorLog), 0); +} + + +LtsPluginManagerDialog::~LtsPluginManagerDialog() +{ + delete m_ui; +} + + +void LtsPluginManagerDialog::setServiceMissing(const QString &newPluginFilename, const QString &missingPluginName) +{ + m_ui->lwErrorLog->addItem(tr("ERROR: Plugin '%1' needs the plugin '%2', which is not available!").arg(newPluginFilename, missingPluginName)); + getPluginWidget(newPluginFilename)->setStatus(LtsPluginWidget::MISSING_SERVICE); + + if (!isVisible()) + show(); + + QCoreApplication::processEvents(); +} + + +void LtsPluginManagerDialog::setOptionalServiceMissing(const QString &newPluginFilename, const QString &missingPluginName) +{ + m_ui->lwErrorLog->addItem(tr("INFO: Plugin %1 asked for plugin %2, which is not available!").arg(newPluginFilename, missingPluginName)); + + if (!isVisible()) + show(); + + QCoreApplication::processEvents(); +} + + +void LtsPluginManagerDialog::setInvalidPlugin(const QString &newPluginFilename) +{ + m_ui->lwErrorLog->addItem(tr("INFO: Invalid plugin '%1'").arg(newPluginFilename)); +} + + +LtsPluginWidget *LtsPluginManagerDialog::getPluginWidget(const QString &filename) +{ + LtsPluginWidget *pluginWidget = findChild(filename); + + if (!pluginWidget) + { + pluginWidget = new LtsPluginWidget(filename); + pluginWidget->setObjectName(filename); + qobject_cast(m_ui->pluginsWidget->layout())->insertWidget(0, pluginWidget); + } + + return pluginWidget; +} + + +void LtsPluginManagerDialog::setPluginLogStatus(const QString &filename, const LtsPluginWidget::Status &status) +{ + getPluginWidget(filename)->setStatus(status); + + QCoreApplication::processEvents(); +} + + +void LtsPluginManagerDialog::setPluginLogType(const QString &filename, const QString &type) +{ + getPluginWidget(filename)->setType(type); + + QCoreApplication::processEvents(); +} + + +void LtsPluginManagerDialog::setPluginLogIdent(const QString &filename, const QString &ident) +{ + getPluginWidget(filename)->setIdent(ident); + + QCoreApplication::processEvents(); +} + +} diff --git a/Lts/Common/LtsPluginManager/Ui/lts_plugin_manager_dialog.h b/Lts/Common/LtsPluginManager/Ui/lts_plugin_manager_dialog.h new file mode 100755 index 000000000..41c187195 --- /dev/null +++ b/Lts/Common/LtsPluginManager/Ui/lts_plugin_manager_dialog.h @@ -0,0 +1,38 @@ + + +#pragma once + +#include +#include "lts_plugin_manager_widget.h" + +#include + + +namespace Ui +{ +class LtsPluginManagerDialog; +} + +namespace LtsPluginManager +{ +class LTSPLUGINMANAGER_EXPORT LtsPluginManagerDialog : public QDialog +{ + Q_OBJECT +public: + explicit LtsPluginManagerDialog(QWidget *parent = nullptr); + ~LtsPluginManagerDialog(); + + void setServiceMissing(const QString &newPluginFilename, const QString &missingPluginName); + void setOptionalServiceMissing(const QString &newPluginFilename, const QString &missingPluginName); + void setInvalidPlugin(const QString &newPluginFilename); + + void setPluginLogStatus(const QString &filename, const LtsPluginWidget::Status &status); + void setPluginLogType(const QString &filename, const QString &type); + void setPluginLogIdent(const QString &filename, const QString &ident); + +private: + Ui::LtsPluginManagerDialog *m_ui; + + LtsPluginWidget *getPluginWidget(const QString &filename); +}; +} diff --git a/Lts/Common/LtsPluginManager/Ui/lts_plugin_manager_dialog.ui b/Lts/Common/LtsPluginManager/Ui/lts_plugin_manager_dialog.ui new file mode 100755 index 000000000..1afb9c845 --- /dev/null +++ b/Lts/Common/LtsPluginManager/Ui/lts_plugin_manager_dialog.ui @@ -0,0 +1,71 @@ + + + LtsPluginManagerDialog + + + + 0 + 0 + 492 + 342 + + + + + 0 + 0 + + + + true + + + + + + Qt::Vertical + + + + + 0 + 0 + + + + + 0 + 250 + + + + + + + Qt::Horizontal + + + + 493 + 20 + + + + + + + + + + 0 + 0 + + + + + + + + + + diff --git a/Lts/Common/LtsPluginManager/Ui/lts_plugin_manager_widget.cpp b/Lts/Common/LtsPluginManager/Ui/lts_plugin_manager_widget.cpp new file mode 100755 index 000000000..f575f4d89 --- /dev/null +++ b/Lts/Common/LtsPluginManager/Ui/lts_plugin_manager_widget.cpp @@ -0,0 +1,130 @@ + + + +#include "lts_plugin_manager_widget.h" +#include "ui_lts_plugin_manager_widget.h" + +using namespace LtsPluginManager; + +LtsPluginWidget::LtsPluginWidget(const QString &filename, QWidget *parent) + : QWidget(parent) + , m_ui(new Ui::LtsPluginWidget) +{ + m_ui->setupUi(this); + + m_ui->leFilename->setText(filename); + setStatus(UNKNOWN); + setType(); + setIdent(); + + + m_ui->lbGuiPlugins->hide(); + + m_ui->lbDtc->hide(); + m_ui->lwDtcs->hide(); + + m_ui->lbFtc->hide(); + m_ui->lwFtcs->hide(); +} + +LtsPluginWidget::~LtsPluginWidget() +{ + delete m_ui; +} + +void LtsPluginWidget::setStatus(Status status) +{ + QString statusText; + + switch (status) + { + case INVALID: + { + m_ui->frStatus->setStyleSheet(QLatin1String("background-color: rgb(189, 106, 98);")); + statusText = tr("Invalid"); + break; + } + case LOADED: + { + m_ui->frStatus->setStyleSheet(QLatin1String("background-color: rgb(124, 189, 124);")); + statusText = tr("Loaded"); + break; + } + case CONFLICT: + { + m_ui->frStatus->setStyleSheet(QLatin1String("background-color: rgb(189, 106, 98);")); + statusText = tr("CONFLICT!"); + break; + } + case UNLOADED: + { + m_ui->frStatus->setStyleSheet(QLatin1String("background-color: rgb(100, 100, 100);")); + statusText = tr("Unloaded"); + break; + } + case INIT_FAILED: + { + m_ui->frStatus->setStyleSheet(QLatin1String("background-color: rgb(189, 106, 98);")); + statusText = tr("INIT FAILED!"); + break; + } + case MISSING_SERVICE: + { + m_ui->frStatus->setStyleSheet(QLatin1String("background-color: rgb(189, 106, 98);")); + statusText = tr("MISSING SERVICE!"); + break; + } + case OBLIBATORY_INTERFACES_MISSING: + { + m_ui->frStatus->setStyleSheet(QLatin1String("background-color: rgb(189, 106, 98);")); + statusText = tr("OBLIGATORY INTERFACES MISSING!"); + break; + } + case INVALID_PLUGIN_VERSION: + { + m_ui->frStatus->setStyleSheet(QLatin1String("background-color: rgb(189, 106, 98);")); + statusText = tr("INVALID PLUGIN VERSION!"); + break; + } + default: + { + m_ui->frStatus->setStyleSheet(QLatin1String("background-color: rgb(100, 100, 100);")); + statusText = tr("Unknown (%1)").arg(status); + break; + } + } + + m_ui->leStatus->setText(statusText); +} + + +void LtsPluginWidget::setType(const QString &type) +{ + if (type.isEmpty()) + { + m_ui->leType->hide(); + m_ui->lbType->hide(); + } + else + { + m_ui->leType->show(); + m_ui->lbType->show(); + m_ui->leType->setText(type); + } +} + + +void LtsPluginWidget::setIdent(const QString &ident) +{ + if (ident.isEmpty()) + { + m_ui->leIdent->hide(); + m_ui->lbIdent->hide(); + } + else + { + m_ui->leIdent->show(); + m_ui->lbIdent->show(); + m_ui->leIdent->setText(ident); + } +} diff --git a/Lts/Common/LtsPluginManager/Ui/lts_plugin_manager_widget.h b/Lts/Common/LtsPluginManager/Ui/lts_plugin_manager_widget.h new file mode 100755 index 000000000..cf0826776 --- /dev/null +++ b/Lts/Common/LtsPluginManager/Ui/lts_plugin_manager_widget.h @@ -0,0 +1,45 @@ + + + +#pragma once + + +#include +#include + + +namespace Ui +{ +class LtsPluginWidget; +} + +namespace LtsPluginManager +{ +class LtsPluginWidget : public QWidget +{ + Q_OBJECT +public: + enum Status + { + UNKNOWN = 0, + INVALID, + LOADED, + CONFLICT, + UNLOADED, + INIT_FAILED, + MISSING_SERVICE, + OBLIBATORY_INTERFACES_MISSING, + INVALID_PLUGIN_VERSION, + }; + + explicit LtsPluginWidget(const QString &filename, QWidget *parent = nullptr); + ~LtsPluginWidget(); + + void setStatus(Status status); + void setType(const QString &type = QString()); + void setIdent(const QString &ident = QString()); + +private: + Ui::LtsPluginWidget *m_ui; +}; +} diff --git a/Lts/Common/LtsPluginManager/Ui/lts_plugin_manager_widget.ui b/Lts/Common/LtsPluginManager/Ui/lts_plugin_manager_widget.ui new file mode 100755 index 000000000..2c93dac33 --- /dev/null +++ b/Lts/Common/LtsPluginManager/Ui/lts_plugin_manager_widget.ui @@ -0,0 +1,196 @@ + + + LtsPluginWidget + + + + 0 + 0 + 481 + 580 + + + + + 0 + 0 + + + + true + + + + + + + + + 15 + 60 + + + + + 15 + 60 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + 0 + 0 + + + + + 200 + 0 + + + + + + + + Name: + + + + + + + Type: + + + + + + + Status: + + + + + + + Ident: + + + + + + + Gui Plugins: + + + + + + + + 0 + 0 + + + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + Foreign Data Topic Clients: + + + + + + + Foreign Function Topic Clients: + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + + + + diff --git a/Lts/Common/LtsPluginManager/lts_plugin_loader.cpp b/Lts/Common/LtsPluginManager/lts_plugin_loader.cpp new file mode 100755 index 000000000..4a8cc5c1d --- /dev/null +++ b/Lts/Common/LtsPluginManager/lts_plugin_loader.cpp @@ -0,0 +1,449 @@ + + + +#include "lts_plugin_loader.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + + +namespace LtsPluginManager +{ + +struct LtsPluginLoaderHandler +{ + LtsPluginLoader ltsPluginLoader; + static NGL::INGLLogger &logger() { return NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("LtsPluginLoader"); } + static void cleanupPluginLoader(); +}; +Q_GLOBAL_STATIC(LtsPluginLoaderHandler, s_ltsPluginLoader) + +void LtsPluginLoaderHandler::cleanupPluginLoader() +{ + if (s_ltsPluginLoader.exists()) + s_ltsPluginLoader->ltsPluginLoader.unloadAllPlugins(); +} + + +LtsPluginLoader *LtsPluginLoader::getInstance() +{ + if (!s_ltsPluginLoader.exists()) + qAddPostRoutine(&LtsPluginLoaderHandler::cleanupPluginLoader); + return &s_ltsPluginLoader->ltsPluginLoader; +} + + +LtsPluginLoader::~LtsPluginLoader() +{ + unloadAllPlugins(); +} + +void LtsPluginLoader::unloadAllPlugins() +{ + for (auto &pi : qAsConst(m_loadedPlugins)) + { + QPluginLoader *pluginLoader = pi.pluginLoader; + + delete pluginLoader->instance(); + + if (!pluginLoader->unload()) + qWarning() << "~LtsPluginLoader: could not unload" << pi.pluginName << pluginLoader->errorString(); + + delete pluginLoader; + } + m_loadedPlugins.clear(); +} + + +void LtsPluginLoader::checkPlugins(const QVector &appVector, + QStringList &errors ) +{ + NGL::INGLLogger &logger = LtsPluginLoaderHandler::logger(); + + if (appVector.isEmpty()) + { + errors += QStringLiteral("No Apps available"); + return; + } + + + QVector dataPlugins; + QVector guiPlugins; + auto addDataPluginFn = [&](const PluginInstance &pi) + { + dataPlugins.append(pi); + return true; + }; + auto addGuiPluginFn = [&](const PluginInstance &pi) + { + guiPlugins.append(pi); + return true; + }; + + if (!loadPlugins(PluginType::DATA, addDataPluginFn)) + { + errors += QStringLiteral("failed to load data plugins"); + return; + } + + if (!loadPlugins(PluginType::GUI, addGuiPluginFn)) + { + errors += QStringLiteral("failed to load gui plugins"); + return; + } + + + + + auto checkDuplicateIidsFn = [&](const QVector &plugins, const QString &pluginType) + { + typedef QMap IidMap; + IidMap iids; + + for (auto i3 = plugins.constBegin(); i3 != plugins.constEnd(); ++i3) + { + const PluginInstance &pin = *i3; + auto iidFound = iids.constFind(pin.pluginName); + + if (iidFound != iids.constEnd()) + errors += QStringLiteral("Iid duplicate for %1 plugin '%2'").arg(pluginType, pin.pluginName); + else + iids.insert(pin.pluginName, pin.fullFileName); + } + }; + + checkDuplicateIidsFn(dataPlugins, QStringLiteral("data")); + checkDuplicateIidsFn(guiPlugins, QStringLiteral("gui")); + + + + QVector plugins(dataPlugins + guiPlugins); + + + + for (auto i1 = appVector.constBegin(); i1 != appVector.constEnd(); ++i1) + { + const LtsAppDescriptor::AppDescription &app = *i1; + + + + const QVector &piVector = app.pluginInfos; + + for (auto i2 = piVector.constBegin(); i2 != piVector.constEnd(); i2++) + { + const LtsAppDescriptor::PluginInfo &pi = *i2; + bool found = false; + + for (auto i3 = plugins.constBegin(); i3 != plugins.constEnd(); ++i3) + { + const PluginInstance &pin = *i3; + + if (pi.iid == pin.pluginName) + { + found = true; + break; + } + } + + if (!found) + { + ERROR_LOG(logger) << "Plugin with iid '" << pi.iid << "' was requested but was not loaded!"; + errors += QStringLiteral("App '%1' requires plugin '%2' that could not be found.").arg(app.appName, pi.iid); + } + } + } + + return; +} + + +bool LtsPluginLoader::loadPlugins(PluginType requestedPluginType, + const std::function &loadPlugin, + const QStringList &requiredPlugins, + const QString &specialTopicTypeFilter, + const QStringList &optionalPlugins) +{ + NGL::INGLLogger &logger = LtsPluginLoaderHandler::logger(); + Q_ASSERT(requestedPluginType != PluginType::UNKNOWN); + + QStringList requiredPluginsList = requiredPlugins; + QStringList optionalPluginsList = optionalPlugins; + bool checkPluginsLists = (!requiredPluginsList.isEmpty()) || (!optionalPluginsList.isEmpty()); + + if (checkPluginsLists && !m_loadedPlugins.isEmpty()) + { + { + QMutableListIterator i1(requiredPluginsList); + + while (i1.hasNext()) + { + const QString pluginName = i1.next().toLower(); + + QMap::iterator i2 = m_loadedPlugins.find(PluginKey(requestedPluginType, pluginName)); + + if (i2 != m_loadedPlugins.end()) + { + DEBUG_LOG(logger) << "Plugin " << pluginName << " already loaded, using that instance"; + + if (loadPlugin) + loadPlugin(i2.value()); + + i1.remove(); + } + } + } + + + { + QMutableListIterator i1(optionalPluginsList); + + while (i1.hasNext()) + { + const QString pluginName = i1.next().toLower(); + + QMap::iterator i2 = m_loadedPlugins.find(PluginKey(requestedPluginType, pluginName)); + + if (i2 != m_loadedPlugins.end()) + { + DEBUG_LOG(logger) << "Plugin " << pluginName << " already loaded, using that instance"; + + if (loadPlugin) + loadPlugin(i2.value()); + + i1.remove(); + } + } + } + } + + + QFileInfo libPathInfo(QCoreApplication::applicationDirPath() + QStringLiteral("/../lib/")); + QString libPath(libPathInfo.absolutePath()); + if (!libPathInfo.exists()) + { + + + libPathInfo.setFile(QCoreApplication::applicationFilePath()); + libPath = libPathInfo.absolutePath(); + } + +#ifdef Q_OS_WIN + const QStringList nameFilter = QStringList(QStringLiteral("*Plugin*.dll")); +#else + const QStringList nameFilter = QStringList(QStringLiteral("*Plugin*.so")); +#endif + + static const QStringList pluginTypes({QStringLiteral("data"), + QStringLiteral("gui"), + QStringLiteral("topic_type"), + QStringLiteral("project"), + QStringLiteral("style"), + QStringLiteral("special")}); + + QDirIterator pluginDir(libPath, nameFilter, QDir::Files); + while (pluginDir.hasNext()) + { + pluginDir.next(); + const QString filename = pluginDir.fileName(); + if (filename.contains(QStringLiteral("PluginInterface"), Qt::CaseSensitive)) + continue; + + const QString absoluteFilePath = pluginDir.filePath(); + + DEBUG_LOG(logger) << "File Path: " << absoluteFilePath; + + + + + + + + + QScopedPointer pluginLoader(new QPluginLoader); + pluginLoader->setFileName(absoluteFilePath); + + const QJsonObject jObj = pluginLoader->metaData(); + QJsonObject::const_iterator iid = jObj.find(QStringLiteral("IID")); + + if (iid == jObj.end()) + { + DEBUG_LOG(logger) << "Plugin '" << filename << "' has no IID !"; + continue; + } + + const QString jsonIid = iid.value().toString().toLower(); + const QVector iidParameters = jsonIid.splitRef(QLatin1Char(':')); + + DEBUG_LOG(logger) << "Plugin " << absoluteFilePath << " provides iid '" << jsonIid; + + if (iidParameters.count() != 3) + { + WARNING_LOG(logger) << "Invalid iid "< i (requiredPluginsList); + + while (i.hasNext()) + { + const QString requiredPlugin = i.next().toLower(); + + if (pluginName == requiredPlugin) + { + found = true; + i.remove(); + break; + } + } + } + + + if (!found) + { + for (auto i = optionalPluginsList.constBegin(); i != optionalPluginsList.constEnd(); i++) + { + const QString optionalPlugin = (*i).toLower(); + + if (pluginName == optionalPlugin) + { + found = true; + break; + } + } + } + + + if (!found) + { + DEBUG_LOG(logger) << "Plugin " << pluginName << " was not requested"; + continue; + } + } + + + + + if (loadPlugin && !m_loadedPlugins.isEmpty()) + { + QMap::iterator i = m_loadedPlugins.find(PluginKey(requestedPluginType, pluginName)); + + if (i != m_loadedPlugins.end()) + { + DEBUG_LOG(logger) << "Plugin " << pluginName << " already loaded, using that instance ("<metaObject()->className()<<")"; + + loadPlugin(i.value()); + continue; + } + } + + + QObject *plugin = pluginLoader->instance(); + + if (!plugin) + { + ERROR_LOG(logger) << "Failed to load " << filename << " - " << pluginLoader->errorString(); + continue; + } + + PluginInstance pi; + pi.pluginName = pluginName; + pi.fileName = filename; + pi.fullFileName = absoluteFilePath; + pi.plugin = plugin; + pi.pluginLoader = pluginLoader.data(); + + if (loadPlugin && !loadPlugin(pi)) + { + DEBUG_LOG(logger) << "Plugin " << pluginName << " unloaded"; + delete pluginLoader->instance(); + pluginLoader->unload(); + continue; + } + pi.pluginLoader = pluginLoader.take(); + + + m_loadedPlugins.insert(PluginKey(requestedPluginType, pluginName), pi); + + + + + DEBUG_LOG(logger) << "Loaded " << jsonIid << " - File: " << filename; + } + + + if (checkPluginsLists && !requiredPluginsList.isEmpty()) + { + WARNING_LOG(logger) << "These plugins could not be found for Plugin Type " << (int)requestedPluginType << ": " << requiredPluginsList.join(QStringLiteral(", ")); + return false; + } + + return true; +} + +} diff --git a/Lts/Common/LtsPluginManager/lts_plugin_loader.h b/Lts/Common/LtsPluginManager/lts_plugin_loader.h new file mode 100755 index 000000000..9881bcefd --- /dev/null +++ b/Lts/Common/LtsPluginManager/lts_plugin_loader.h @@ -0,0 +1,85 @@ + + + +#pragma once + +#define PLUGIN_LOADER_VERSION 3 + +#include +#include +#include +#include + +#ifndef LTSPLUGINLOADER_EXPORT +# ifdef BUILD_STATIC_LIBRARIES +# define LTSPLUGINLOADER_EXPORT +# else +# if defined(MAKE_LTSPLUGINLOADER_LIB) +# define LTSPLUGINLOADER_EXPORT Q_DECL_EXPORT +# else +# define LTSPLUGINLOADER_EXPORT Q_DECL_IMPORT +# endif +# endif +#endif + +class QObject; +class QPluginLoader; + +namespace LtsAppDescriptor +{ +struct AppDescription; +} + +namespace LtsPluginManager +{ + +enum class PluginType +{ + UNKNOWN = 0, + DATA, + GUI, + TOPIC_TYPE, + PROJECT, + STYLE, + SPECIAL, +}; + + +struct PluginInstance +{ + QString pluginName; + QString fullFileName; + QString fileName; + QObject *plugin = nullptr; + QPluginLoader *pluginLoader = nullptr; +}; + + +class LTSPLUGINLOADER_EXPORT LtsPluginLoader +{ +public: + static LtsPluginLoader *getInstance(); + + void unloadAllPlugins(); + + void checkPlugins(const QVector &appVector, + QStringList &errors ); + + bool loadPlugins(PluginType requestedPluginType, + const std::function &loadPlugin, + const QStringList &requiredPlugins = QStringList(), + const QString &specialTopicTypeFilter = QString(), + const QStringList &optionalPlugins = QStringList()); + + typedef QPair PluginKey; + +private: + friend struct LtsPluginLoaderHandler; + + LtsPluginLoader() = default; + ~LtsPluginLoader(); + + QMap m_loadedPlugins; +}; + +} diff --git a/Lts/Common/LtsPluginManager/lts_plugin_manager.cpp b/Lts/Common/LtsPluginManager/lts_plugin_manager.cpp new file mode 100755 index 000000000..0b2e3703e --- /dev/null +++ b/Lts/Common/LtsPluginManager/lts_plugin_manager.cpp @@ -0,0 +1,286 @@ + + + +#include "lts_plugin_manager.h" +#include "Ui/lts_plugin_manager_dialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +namespace LtsPluginManager +{ + +LtsPluginManager::LtsPluginManager() + : m_sessionManager(new LtsSession::LtsSessionManager) + , m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("LtsPluginManager")) + , m_markedForDeletion(false) +{ + connect(m_sessionManager, &LtsSession::LtsSessionManager::signalQuit, + this, &LtsPluginManager::signalQuit); + connect(m_sessionManager, &LtsSession::LtsSessionManager::signalLogout, + this, &LtsPluginManager::signalLogout); + connect(m_sessionManager, &LtsSession::LtsSessionManager::signalInitializationFinished, + this, &LtsPluginManager::signalInitializationFinished); + connect(m_sessionManager, &LtsSession::LtsSessionManager::signalInitializationAborted, + this, &LtsPluginManager::signalInitializationAborted); + connect(m_sessionManager, &LtsSession::LtsSessionManager::signalInitializationFinished, + this, &LtsPluginManager::slotInitializationFinished); + connect(m_sessionManager, &LtsSession::LtsSessionManager::taskSwitch, + this, &LtsPluginManager::taskSwitch); + connect(m_sessionManager, &LtsSession::LtsSessionManager::hideAllOtherBLWs, + this, &LtsPluginManager::hideAllOtherBLWs); + + QTimer *t = new QTimer(this); + t->setInterval(1000); + connect(t, &QTimer::timeout, + this, &LtsPluginManager::signalStillAlive); + t->start(); +} + + +LtsPluginManager::~LtsPluginManager() +{ + + + delete m_sessionManager; +} + + +void LtsPluginManager::softQuit() +{ + emit signalSoftQuit(); +} + + +void LtsPluginManager::_softQuit() +{ + m_sessionManager->softQuit(); +} + + +QVector LtsPluginManager::sessions() const +{ + return m_sessionManager->sessions(); +} + +bool LtsPluginManager::hasSituationMapInstance(const LtsAppDescriptor::AppDescription &appDescription, bool alsoTestForScriptApp) +{ + if (alsoTestForScriptApp && appDescription.id == LtsAppDescriptor::App::Scripts) + return true; + + for (const LtsAppDescriptor::PluginInfo &pluginInfo : qAsConst(appDescription.pluginInfos)) + { + if (pluginInfo.flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::ACTIONS) && + (pluginInfo.iid == QStringLiteral("sitmap"))) + return true; + } + return false; +} + +bool LtsPluginManager::hasSituationMapInstance() const +{ + return hasSituationMapInstance(m_app); +} + +LtsSession::LtsSession *LtsPluginManager::getSession(IDL::PartitionType partitionType, int partitionId) +{ + return m_sessionManager->getSession(partitionType, partitionId); +} + + +bool LtsPluginManager::startApp(const LtsAppDescriptor::AppDescription &app, bool headless, + const LtsSession::DdsConnectionParams &connParams, const QString &workspaceId, + bool hidden) +{ + + + + LtsWidgets::LtsWaitDialogContainer wait; + + m_app = app; + LtsPluginLoader *pluginLoader = LtsPluginLoader::getInstance(); + + + + QVector dataPlugins; + auto cb = [&](const PluginInstance &pi) + { + dataPlugins.append(pi); + return true; + }; + + if (!pluginLoader->loadPlugins(PluginType::DATA, cb)) + { + WARNING_LOG(m_logger) << "PluginLoader failed to load data plugins - abort!"; + return false; + } + + + + QVector guiPlugins; + auto cbLoadPlugins = [&](const PluginInstance &pi) + { + guiPlugins.append(pi); + return true; + }; + + if (!pluginLoader->loadPlugins(PluginType::GUI, cbLoadPlugins)) + { + WARNING_LOG(m_logger) << "PluginLoader failed to load gui plugins - abort!"; + return false; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + QVector dataApiList; + for (auto i = dataPlugins.constBegin(); i != dataPlugins.constEnd(); ++i) + { + const PluginInstance &pi = *i; + + AppPluginInstance api; + api.pluginInstance = pi; + api.m_appName = m_app.appName; + api.m_appId = m_app.id; + + + dataApiList.append(api); + } + + + + + QVector guiApiList; + for (auto i = guiPlugins.constBegin(); i != guiPlugins.constEnd(); ++i) + { + const PluginInstance &pi = *i; + + AppPluginInstance api; + api.pluginInstance = pi; + api.m_appName = m_app.appName; + api.m_appId = m_app.id; + + + + for (auto i2 = app.pluginInfos.constBegin(); i2 != app.pluginInfos.constEnd(); ++i2) + { + const LtsAppDescriptor::PluginInfo &rp = *i2; + + + if (rp.type == LtsAppDescriptor::PluginInfo::PluginType::GUI + && rp.iid == pi.pluginName) + { + + api.m_flags = rp.flags; + api.m_options = rp.options; + + break; + } + } + + + + + + + + guiApiList.append(api); + } + + + + + + + + + + LtsSession::DdsConnectionParams connParamsNew(connParams); + if (!connParamsNew.isValid()) + { + INFO_LOG(m_logger) << "Invalid connection params given - using default parameter"; + + connParamsNew.domainId = LtsSessionData::LtsSessionData::getInstance()->getDomainIdPreset(); + connParamsNew.partitionId = 0; + connParamsNew.exerciseId = 0; + connParamsNew.partitionType = IDL::PartitionType::Global; + + + connParamsNew.tcpConnectionUrl = LtsSessionData::LtsSessionData::getInstance()->getTcpConnectionUrl(); + } + if (LtsSessionData::LtsSessionData::getInstance()->getQosFile().size() > 0 && LtsSessionData::LtsSessionData::getInstance()->getQosFile().at(0).size() > 0) + connParamsNew.qos_file = LtsSessionData::LtsSessionData::getInstance()->getQosFile(); + else + connParamsNew.qos_file = QStringList(); + if (!m_sessionManager->init(connParamsNew, + dataApiList, + guiApiList, + m_app.id, + workspaceId, + headless, + hidden)) + { + ERROR_LOG(m_logger) << "Could not create Global Session - abort."; + return false; + } + + return true; +} + +} diff --git a/Lts/Common/LtsPluginManager/lts_plugin_manager.h b/Lts/Common/LtsPluginManager/lts_plugin_manager.h new file mode 100755 index 000000000..bcb09d776 --- /dev/null +++ b/Lts/Common/LtsPluginManager/lts_plugin_manager.h @@ -0,0 +1,110 @@ + + + +#pragma once + +#include "lts_plugin_manager_export.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + + +namespace NGL +{ +class INGLLogger; +} +namespace DataPluginBase +{ +class Plugin; +} +namespace GuiPluginBase +{ +class Plugin; +} +namespace ProjectPlugin +{ +class Plugin; +} +namespace LtsSession +{ +class LtsSession; +class LtsSessionManager; +struct DdsConnectionParams; +} +namespace DeveloperTools +{ +class DevDialog; +} + + +namespace LtsPluginManager +{ +struct AppPluginInstance +{ + PluginInstance pluginInstance; + + QFlags m_flags; + QStringList m_options; + QString m_appName; + LtsAppDescriptor::App m_appId = LtsAppDescriptor::App::Login; +}; + +class LTSPLUGINMANAGER_EXPORT LtsPluginManager : public QObject +{ + Q_OBJECT + +public: + LtsPluginManager(); + ~LtsPluginManager(); + + bool startApp(const LtsAppDescriptor::AppDescription &app, bool headless, + const LtsSession::DdsConnectionParams &connParams, + const QString &workspaceId = QString(), bool hidden = false); + LtsSession::LtsSession *getSession(IDL::PartitionType partitionType, int partitionId); + bool isInitialized() const { return m_initialized; } + const LtsAppDescriptor::AppDescription &appDescription() const { return m_app; } + QVector sessions() const; + Q_DECL_DEPRECATED void setMarkedForDeletion() { m_markedForDeletion = true; } + Q_DECL_DEPRECATED bool markedForDeletion() const { return m_markedForDeletion; } + const QString &description() const { return m_description; } + void setDescription(const QString &description) { m_description = description; } + + static bool hasSituationMapInstance(const LtsAppDescriptor::AppDescription &appDescription, bool alsoTestFOrScriptApp = false); + bool hasSituationMapInstance() const; + void softQuit(); + void _softQuit(); + + + + LtsSession::LtsSessionManager *m_sessionManager; +private: + friend class DeveloperTools::DevDialog; + + LtsAppDescriptor::AppDescription m_app; + NGL::INGLLogger &m_logger; + bool m_initialized = false; + bool m_markedForDeletion; + QString m_description; + +private slots: + void slotInitializationFinished() { m_initialized = true; } + +signals: + void signalQuit(); + void signalSoftQuit(); + void signalLogout(); + void signalInitializationFinished(); + void signalInitializationAborted(); + void signalStillAlive(); + void hideAllOtherBLWs(QObject *caller); + void taskSwitch(QObject *caller); +}; + +} diff --git a/Lts/Common/LtsPluginManager/lts_plugin_manager_export.h b/Lts/Common/LtsPluginManager/lts_plugin_manager_export.h new file mode 100755 index 000000000..5d563576f --- /dev/null +++ b/Lts/Common/LtsPluginManager/lts_plugin_manager_export.h @@ -0,0 +1,17 @@ + + +#pragma once + +#include + +#ifndef LTSPLUGINMANAGER_EXPORT +# ifdef BUILD_STATIC_LIBRARIES +# define LTSPLUGINMANAGER_EXPORT +# else +# if defined(MAKE_LTSPLUGINMANAGER_LIB) +# define LTSPLUGINMANAGER_EXPORT Q_DECL_EXPORT +# else +# define LTSPLUGINMANAGER_EXPORT Q_DECL_IMPORT +# endif +# endif +#endif diff --git a/Lts/Common/LtsPrinting/CMakeLists.txt b/Lts/Common/LtsPrinting/CMakeLists.txt new file mode 100755 index 000000000..98c01a587 --- /dev/null +++ b/Lts/Common/LtsPrinting/CMakeLists.txt @@ -0,0 +1,35 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# +# Created by Christian Ehrlicher, SLSE, 2017-12-05 +# + +FIND_PACKAGE(KDReports REQUIRED) + +SET(SRCS + printing_base.cpp + printing_base.h + printing.cpp + printing.h +) + + +NGL_ADD_LIBRARY(WS LtsPrinting SHARED ${SRCS}) +NGL_TARGET_INCLUDE_DIRECTORIES(LtsPrinting + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) +NGL_TARGET_LINK_LIBRARIES(LtsPrinting + PRIVATE + KDReports::KDReports + LtsSessionData + LtsSession + nglsettings + ngltools + PUBLIC + Qt5::PrintSupport + Qt5::Core +) +NGL_INSTALL_TARGET(LtsPrinting) diff --git a/Lts/Common/LtsPrinting/printing.cpp b/Lts/Common/LtsPrinting/printing.cpp new file mode 100755 index 000000000..fbb841b57 --- /dev/null +++ b/Lts/Common/LtsPrinting/printing.cpp @@ -0,0 +1,430 @@ + + + +#include "printing.h" +#include "printing_base.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace LtsPrinting +{ + + + + +bool itemsHaveNoChildren(const QTreeView *view) +{ + const QAbstractItemModel *itemModel = view->model(); + int rowCount = itemModel->rowCount(); + for (int row = 0; row < rowCount; ++row) + { + if (itemModel->hasChildren(itemModel->index(row, 0))) + return false; + } + return true; +} + + + + +void setNameOfExerciseAndOperator(LtsSession::LtsSession *session, LtsPrinting::Base &nglPrinter) +{ + if ( session->exerciseId() != 0 ) + nglPrinter.setCurrentExerciseName(QObject::tr("Exercise: %1 %2").arg(session->exerciseData()->exName, + session->exerciseData()->phName)); + else + nglPrinter.setCurrentExerciseName(QObject::tr("Exercise: Administration") ); + + LtsSessionData::LtsSessionData *sessionData = LtsSessionData::LtsSessionData::getInstance(); + nglPrinter.setOperatorName(sessionData->getOperatorName()); +} + + +void printView(LtsSession::LtsSession *session, + QWidget *dialogParent, + const QAbstractItemView *view, + const QString& headingHtml) +{ + const QTableView *tableView = dynamic_cast(view); + const QTreeView *treeView = dynamic_cast(view); + QSet hiddenCols; + + int switchNum = 0; + if (tableView != nullptr) + { + switchNum = 1; + hiddenCols = LtsPrinting::Base::getHiddenCols(tableView); + } + else if (treeView != nullptr) + { + switchNum = 2; + hiddenCols = LtsPrinting::Base::getHiddenCols(treeView); + if (itemsHaveNoChildren(treeView)) switchNum = 1; + } + else if (dynamic_cast(view) != nullptr) + switchNum = 3; + else + { + return; + } + + QTextDocument doc; + QTextCursor cursor(&doc); + + doc.setMetaInformation(QTextDocument::DocumentTitle, dialogParent->windowTitle()); + LtsPrinting::Base nglPrinter(doc, dialogParent, + QObject::tr("Print %1").arg(dialogParent->windowTitle()), + dialogParent->windowTitle(), + QPrinter::A4, QPrinter::Landscape); + if (!nglPrinter.printer()) + return; + + setNameOfExerciseAndOperator(session, nglPrinter); + + cursor.movePosition(QTextCursor::End); + QString htmlStr; + + switch (switchNum) + { + case 1: + nglPrinter.printTableData(htmlStr, view, QString(), hiddenCols); + break; + case 2: + nglPrinter.printTreeData(htmlStr, view, QString(), hiddenCols); + break; + case 3: + nglPrinter.printListData(htmlStr, view, QString()); + break; + } + cursor.insertHtml(headingHtml); + cursor.insertHtml(htmlStr); + nglPrinter.print(&doc); + +} + + +void printView(LtsSession::LtsSession *session, + QWidget *dialogParent, + QAbstractItemView *view, + const QString& headingHtml) +{ + const QAbstractItemView *itemView = static_cast(view); + printView(session, dialogParent, itemView, headingHtml); +} + + +void printImage(LtsSession::LtsSession *session, QWidget *dialogParent, + const QImage &image, const QString &imageTitle, const QString &description, + const QPrinter::PageSize &pageSize, const QPrinter::Orientation &pageOrientation) +{ + QTextDocument doc; + QTextCursor cursor(&doc); + + QString windowTitle = dialogParent->windowTitle(); + doc.setMetaInformation(QTextDocument::DocumentTitle, dialogParent->windowTitle()); + LtsPrinting::Base nglPrinter(doc, dialogParent, + QObject::tr("Print %1").arg(windowTitle), + windowTitle, + pageSize, pageOrientation); + if (!nglPrinter.printer()) + return; + + setNameOfExerciseAndOperator(session, nglPrinter); + + cursor.movePosition(QTextCursor::End); + + nglPrinter.printDocImage(doc, image, imageTitle, description); + nglPrinter.print(&doc); +} + + +void printImages(LtsSession::LtsSession *session, QWidget *dialogParent, + const QString &docName, QVector &images, + QVector &imageTitles, QVector &descriptions, + const QPrinter::PageSize &pageSize, const QPrinter::Orientation &pageOrientation) + +{ + QTextDocument doc; + QTextCursor cursor(&doc); + + doc.setMetaInformation(QTextDocument::DocumentTitle, docName); + LtsPrinting::Base nglPrinter(doc, dialogParent, + QObject::tr("Print %1").arg(docName), + docName, pageSize, pageOrientation); + if (!nglPrinter.printer()) + return; + + setNameOfExerciseAndOperator(session, nglPrinter); + + cursor.movePosition(QTextCursor::End); + + for ( int i = 0; i < images.size(); i++) + { + QString imageTitle, imageDescription; + if (imageTitles.size() > i && imageTitles[i].size() > 0) + imageTitle = imageTitles[i]; + if (descriptions.size() > i && descriptions[i].size() > 0) + imageDescription = descriptions[i]; + nglPrinter.printDocImage(doc, images[i], imageTitle, imageDescription); + nglPrinter.printSinglePage(&doc); + doc.clear(); + + if (i != images.size() -1) + nglPrinter.newPage(); + + } + + nglPrinter.printDone(); + + +} + + +void printPDFImages(QWidget *dialogParent, const QString &docName, const QString &printPath, QVector &images) + +{ + QPrinter printer; + + printer.setColorMode(QPrinter::ColorMode::Color); + printer.setOrientation(QPrinter::Orientation::Portrait); + printer.setPageSize(QPrinter::PageSize::A4); + QString fn = docName; + fn = fn.remove(QLatin1Char(' ')); + fn = fn.remove(QLatin1String("..")); + fn = fn.remove(QLatin1Char('/')); + fn = printPath + fn; + if (!fn.endsWith(QLatin1String(".pdf"), Qt::CaseInsensitive)) + fn += QLatin1String(".pdf"); +#ifndef WIN32 + printer.setOutputFileName(fn); +#endif + printer.setOutputFormat(QPrinter::NativeFormat); + + QPrintDialog *dialog = new QPrintDialog(&printer, dialogParent); + dialog->setWindowTitle(QObject::tr("Print %1").arg(docName)); + if (dialog->exec() != QDialog::Accepted) + return; + + QPainter painter; + painter.begin(&printer); + int lastItem = images.size() - 1; + for (int i = 0; i < images.size(); ++i) + { + QRect rect = painter.viewport(); + QSize size = images.at(i).size(); + size.scale(rect.size(), Qt::KeepAspectRatio); + painter.setViewport(rect.x(), rect.y(), + size.width(), size.height()); + painter.setWindow(images.at(i).rect()); + painter.drawImage(0, 0, images.at(i)); + if (i != lastItem) + printer.newPage(); + } + painter.end(); + +} + + + + + +void printReport( LtsSession::LtsSession *session, QWidget *dialogParent, KDReports::Report& report ) +{ + QTextDocument doc; + QTextCursor cursor(&doc); + + doc.setMetaInformation(QTextDocument::DocumentTitle, dialogParent->windowTitle()); + LtsPrinting::Base nglPrinter(doc, dialogParent, + QObject::tr("Print %1").arg(dialogParent->windowTitle()), + dialogParent->windowTitle(), + QPrinter::A3, QPrinter::Landscape); + if (!nglPrinter.printer()) + return; + + setNameOfExerciseAndOperator( session, nglPrinter ); + + cursor.movePosition(QTextCursor::End); + + report.setWidthForEndlessPrinter(0); + report.setPageSize(QPrinter::A3); + report.setOrientation(QPrinter::Landscape); + + LtsPrinting::Base::printReportData(doc, report); + nglPrinter.print(&doc); +} + + +void printWidget(LtsSession::LtsSession *session, QWidget *dialogParent, QWidget *widget, const QString &title) +{ + QTextDocument doc; + QTextCursor cursor(&doc); + QImage image(widget->size(), QImage::Format_ARGB32); + widget->render(&image); + + QString windowTitle = dialogParent->windowTitle(); + doc.setMetaInformation(QTextDocument::DocumentTitle, dialogParent->windowTitle()); + LtsPrinting::Base nglPrinter(doc, dialogParent, + QObject::tr("Print %1").arg(windowTitle), + windowTitle, + QPrinter::A3, QPrinter::Landscape); + if (!nglPrinter.printer()) + return; + + setNameOfExerciseAndOperator(session, nglPrinter); + + cursor.movePosition(QTextCursor::End); + + nglPrinter.printDocImage(doc, image, title); + nglPrinter.print(&doc); +} + +void printMessage(LtsSession::LtsSession *session, + QWidget *dialogParent, + const QString &subject, const QString &subjectContent, + const QString &from, const QString &fromContent, + const QString &to, const QString &toContent, + const QString &msgTextContent, + const QString &time, const QString &timeContent) +{ + QTextDocument doc; + QString windowTitle = subject; + + doc.setMetaInformation(QTextDocument::DocumentTitle, subject); + LtsPrinting::Base nglPrinter(doc, dialogParent, + QObject::tr("Print %1").arg(windowTitle), + windowTitle, + QPrinter::A4, QPrinter::Portrait); + + QString str = QString(QStringLiteral("

%1 %2

")).arg(subject).arg(subjectContent); + str.append( QString(QStringLiteral("%1 %2
")).arg(time).arg(timeContent)); + str.append( QString(QStringLiteral("%1 %2
")).arg(from).arg(fromContent)); + str.append( QString(QStringLiteral("%1 %2
")).arg(to).arg(toContent)); + str.append( QString(QStringLiteral("%1")).arg(msgTextContent)); + + doc.setHtml(str); + if (!nglPrinter.printer()) + return; + + setNameOfExerciseAndOperator(session, nglPrinter); + + nglPrinter.print(&doc); +} + + +void printStoryboard(LtsSession::LtsSession *session, QWidget *dialogParent, QAbstractItemView *view, const QString &headingHtml) +{ + QTableWidget *tbl = dynamic_cast< QTableWidget *>(view); + QTextDocument doc; + QTextCursor cursor(&doc); + + QTextBlockFormat tbfFormat; + tbfFormat.setPageBreakPolicy(QTextFormat::PageBreakFlag::PageBreak_Auto); + + QString windowTitle = dialogParent->windowTitle(); + doc.setMetaInformation(QTextDocument::DocumentTitle, dialogParent->windowTitle()); + + LtsPrinting::Base nglPrinter(doc, dialogParent, + QObject::tr("Print %1").arg(windowTitle), + windowTitle, + QPrinter::A4, QPrinter::Portrait); + + if (!nglPrinter.printer()) + return; + + const int rows = tbl->rowCount(); + cursor.insertHtml(headingHtml); + + double currentHeight = 0; + double previousPagesHeight = 0; + QString htmlStr; + for (int rowCounter = 0; rowCounter < rows; rowCounter++) + { + + htmlStr.append(QStringLiteral("")); + QTextEdit *labelForFirstCell = (QTextEdit*)tbl->cellWidget(rowCounter, 0); + QString stringFirstColumn = labelForFirstCell->toHtml(); + + QTextEdit *labelSecondColumn = (QTextEdit*)tbl->cellWidget(rowCounter, 1); + QString stringSecondColumn = labelSecondColumn->toHtml(); + + QTextEdit *labelThirdColumn = (QTextEdit*)tbl->cellWidget(rowCounter, 2); + QString stringThirdColumn = labelThirdColumn->toHtml(); + + for (int rowInCellCounter = 0; rowInCellCounter < stringSecondColumn.split(QStringLiteral("
")).size(); ++rowInCellCounter) + { + htmlStr.append(QStringLiteral("")); + + if (rowInCellCounter < stringFirstColumn.split(QStringLiteral("
")).size()) + { + htmlStr.append(QStringLiteral("")); + } + else + { + htmlStr.append(QStringLiteral("")); + } + QString currentTextSecond = stringSecondColumn.split(QStringLiteral("
"))[rowInCellCounter]; + htmlStr.append(QStringLiteral("")); + + QString currentTextThird = stringThirdColumn.split(QStringLiteral("
"))[rowInCellCounter]; + htmlStr.append(QStringLiteral("")); + htmlStr.append(QStringLiteral("")); + } + QTextDocument tempDoc; + tempDoc.setPageSize(doc.pageSize()); + tempDoc.setHtml(htmlStr); + currentHeight = doc.size().height() - previousPagesHeight; + + if (currentHeight + tempDoc.size().height() > nglPrinter.pageSizeInMM().height()) + { + tbfFormat.setPageBreakPolicy(QTextFormat::PageBreakFlag::PageBreak_AlwaysBefore); + cursor.insertBlock(tbfFormat); + previousPagesHeight = doc.size().height(); + currentHeight = doc.size().height() - previousPagesHeight; + } + else + { + tbfFormat.setPageBreakPolicy(QTextFormat::PageBreakFlag::PageBreak_Auto); + currentHeight = doc.size().height() - previousPagesHeight; + } + cursor.insertHtml(htmlStr); + htmlStr.clear(); + } + htmlStr.append(QStringLiteral("
")); + htmlStr.append(stringFirstColumn.split(QStringLiteral("
"))[rowInCellCounter]); + htmlStr.append(QStringLiteral("
")); + htmlStr.append(QStringLiteral("")); + htmlStr.append(currentTextSecond); + htmlStr.append(QStringLiteral("")); + htmlStr.append(currentTextThird); + htmlStr.append(QStringLiteral("
")); + cursor.insertHtml(htmlStr); + + cursor.movePosition(QTextCursor::End); + + setNameOfExerciseAndOperator(session, nglPrinter); + nglPrinter.print(&doc); +} + + +} diff --git a/Lts/Common/LtsPrinting/printing.h b/Lts/Common/LtsPrinting/printing.h new file mode 100755 index 000000000..cfa985002 --- /dev/null +++ b/Lts/Common/LtsPrinting/printing.h @@ -0,0 +1,94 @@ + + + +#pragma once + +#include + +#ifndef LTSPRINTING_EXPORT +# ifdef BUILD_STATIC_LIBRARIES +# define LTSPRINTING_EXPORT +# else +# if defined(MAKE_LTSPRINTING_LIB) +# define LTSPRINTING_EXPORT Q_DECL_EXPORT +# else +# define LTSPRINTING_EXPORT Q_DECL_IMPORT +# endif +# endif +#endif + +class QWidget; +class QAbstractItemView; +class QImage; +namespace LtsSession +{ +class LtsSession; +} +namespace KDReports +{ +class Report; +} +namespace LtsPrinting +{ + + +LTSPRINTING_EXPORT void printView(LtsSession::LtsSession *session, + QWidget *dialogParent, + const QAbstractItemView *view, + const QString &headingHtml = QString()); +LTSPRINTING_EXPORT void printView(LtsSession::LtsSession *session, + QWidget *dialogParent, + QAbstractItemView *view, + const QString &headingHtml = QString()); +LTSPRINTING_EXPORT void printStoryboard(LtsSession::LtsSession *session, + QWidget *dialogParent, + QAbstractItemView *view, + const QString &headingHtml = QString()); + + +LTSPRINTING_EXPORT void printImage(LtsSession::LtsSession *session, + QWidget *dialogParent, + const QImage &image, + const QString &imageTitle, + const QString &description, + const QPrinter::PageSize &pageSize = QPrinter::A3, + const QPrinter::Orientation &pageOrientation = QPrinter::Landscape); + + + +LTSPRINTING_EXPORT void printImages(LtsSession::LtsSession *session, + QWidget *dialogParent, + const QString &docName, + QVector < QImage> &images, + QVector < QString> &imageTitles, + QVector < QString> &descriptions, + const QPrinter::PageSize &pageSize = QPrinter::A3, + const QPrinter::Orientation &pageOrientation = QPrinter::Landscape); + + + +LTSPRINTING_EXPORT void printPDFImages(QWidget *dialogParent, + const QString &docName, + const QString &printPath, + QVector &images); + + + +LTSPRINTING_EXPORT void printReport( LtsSession::LtsSession *session, QWidget *dialogParent, KDReports::Report& report ); + + + +LTSPRINTING_EXPORT void printWidget(LtsSession::LtsSession *session, QWidget *dialogParent, QWidget *widget, const QString &title); + + + +LTSPRINTING_EXPORT void printMessage(LtsSession::LtsSession *session, + QWidget *dialogParent, + const QString &subject, const QString &subjectContent, + const QString &from, const QString &fromContent, + const QString &to, const QString &toContent, + const QString &msgTextContent, + const QString &time, const QString &timeContent); + +} + diff --git a/Lts/Common/LtsPrinting/printing_base.cpp b/Lts/Common/LtsPrinting/printing_base.cpp new file mode 100755 index 000000000..a1a0c71f2 --- /dev/null +++ b/Lts/Common/LtsPrinting/printing_base.cpp @@ -0,0 +1,1305 @@ + + + + + + + + +#include "printing_base.h" +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + + +namespace LtsPrinting +{ +static inline double mmToInches(double mm) { return mm * 0.039370147; } +static inline double inchesToMM(double inches) { return inches * 25.4; } + +bool LtsPrinting::Base::m_printToFile = false; + +static int TitleDataHeight = 50; +static int DescriptionDataHeight = 50; + +static void spanSizes(const QAbstractItemView *view, + const QModelIndex &ms, + int &rowSpanSize, + int &columnSpanSize, + QVector > &spannedL); + + +Base::Base(QTextDocument &doc, + QWidget *dialogParent, + const QString &dialogTitle, + const QString &_fn, + QPrinter::PaperSize _ps, + QPrinter::Orientation _or, + QPrinter::ColorMode _cm) + : m_printer(new QPrinter), + m_painter(new QPainter), + m_iconFilenameNr(0), + m_tempdoc(nullptr), + leftmargin_(15.0), rightmargin_(15.0), topmargin_(15.0), + bottommargin_(15.0), spacing_(5.0), + headersize_(10), headerrule_(0.5), headertext_(HeaderText), + footersize_(10), footerrule_(0.5), footertext_(FooterText), + itemFontSize_(2), + m_settings(new NGL::Settings(NGL::findProjectConfigFile())), + m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("NGLPrinting")), + m_currentPage(1) +{ + QString fn = _fn; + fn = fn.remove(QLatin1Char(' ')); + fn = fn.remove(QLatin1String("..")); + fn = fn.remove(QLatin1Char('/')); + fn = m_settings->printPath() + fn; + if (!fn.endsWith(QLatin1String(".pdf"), Qt::CaseInsensitive)) + fn += QLatin1String(".pdf"); +#ifndef WIN32 + m_printer->setOutputFileName(fn); +#endif + m_printer->setOutputFormat(QPrinter::NativeFormat); + + + + + m_printer->setPaperSize(_ps); + m_printer->setOrientation(_or); + m_printer->setColorMode(_cm); + + m_printer->setFullPage(true); + + + + QTextOption tOption = doc.defaultTextOption(); + tOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + + doc.setDefaultTextOption(tOption); + + + + doc.setPageSize(QSizeF(printerPageSize())); + doc.setDefaultStyleSheet(styleSheet()); + + if (m_printToFile) + return; + QPrintDialog *dialog = new QPrintDialog(m_printer, dialogParent); + + + QAbstractPrintDialog::PrintDialogOptions op = + QAbstractPrintDialog::PrintToFile | + QAbstractPrintDialog::PrintSelection | + QAbstractPrintDialog::PrintPageRange | + QAbstractPrintDialog::PrintShowPageSize | + QAbstractPrintDialog::PrintCollateCopies; + dialog->setOptions(op); + QString title = dialogTitle; + if (_ps == QPrinter::A2) title += QLatin1String(" (A2)"); + if (_ps == QPrinter::A3) title += QLatin1String(" (A3)"); + if (_ps == QPrinter::A4) title += QLatin1String(" (A4)"); + if (_ps == QPrinter::A5) title += QLatin1String(" (A5)"); + dialog->setWindowTitle(title); + if (dialog->exec() != QDialog::Accepted) + { + + + delete m_printer; + m_printer = nullptr; + return; + } + + m_painter->begin(m_printer); +} + + +Base::~Base() +{ + delete m_printer; + delete m_painter; + delete m_tempdoc; + + for (int nr = 0; nr < m_iconFilenameNr; ++nr) + QFile::remove(iconFilename(nr)); + delete m_settings; +} + + +void Base::setPrintToFile(bool printToFile) +{ + m_printToFile = printToFile; +} + + +QPrinter *Base::printer() +{ + DEBUG_LOG(m_logger) << "Base::printer" ; + + return m_printer; +} + + +QString Base::styleSheet() +{ + QString ret; + + return ret; +} + + +QSize Base::printerPageSize() const +{ + return QSize(m_printer->pageRect().width() - 2 * DOC_MARGIN, + m_printer->pageRect().height() - 2 * DOC_MARGIN); +} + +bool Base::newPage() +{ + return m_printer->newPage(); +} + + +QSize Base::contentPageSize() const +{ + QSize ps(printerPageSize()); + + ps.setHeight(ps.height() - 50 - 50); + return ps; +} + + +void Base::printTitleData(QString &htmlStr, const QString &title, const QString &heading) +{ + if (!title.isEmpty()) + { + htmlStr += QLatin1String("
") + + QLatin1Char('<') + heading + QLatin1Char('>') + title + QLatin1String("') + + QLatin1String("
"); + } +} + + +bool Base::printListData(QString &str, + const QAbstractItemView *view, + const QString &title, + bool asIcons, + int iconExtent) +{ + if (!view) + return false; + printTitleData(str, title); + QAbstractItemModel *vm = view->model(); + if (!vm) + return false; + QItemSelectionModel *sm = view->selectionModel(); + QModelIndexList mil; + if (m_printer->printRange() == QPrinter::Selection) + mil = sm->selectedRows(); + else + { + int row = 0; + while (1) + { + QModelIndex mi = vm->index(row, 0, QModelIndex()); + if (mi.isValid()) + { + mil.append(mi); + row++; + continue; + } + break; + } + } + if (mil.count() == 0) + return false; + if (asIcons) + { + + int elemWidth = iconExtent + 40; + int tableWidth = contentPageSize().width(); + int nColums = tableWidth / elemWidth; + str += QLatin1String("") + + QLatin1String("") + + QLatin1String("") + + QLatin1String("") + + QLatin1String(""); + + int columIdx = 0; + for (const QModelIndex &mi :qAsConst(mil)) + { + + if (columIdx >= nColums) + { + + columIdx = 0; + str += QLatin1String(""); + } + + str += QLatin1String(""); + ++columIdx; + + } + str += QLatin1String("
"); + printDecorationData(str, vm, mi, iconExtent / 2); + + QVariant vdata = vm->data(mi, Qt::DisplayRole); + str += QLatin1String("
"); + QString displayText; + if (vdata.isValid()) + displayText = vdata.toString(); + else + displayText = QLatin1Char(' '); + str += QLatin1String("") + displayText + QLatin1String("") + + QLatin1String("
"); + } + else + { + + for (const QModelIndex &mi :qAsConst(mil)) + { + if (printItemData(str, vm, mi, iconExtent)) + str += QLatin1String("
"); + } + } + return true; +} + +static QModelIndexList getItemChildren(const QAbstractItemModel *model, + const QTreeView *tv, + const QModelIndex &root) +{ + QModelIndexList ret; + const int rc = model->rowCount(root); + ret.reserve(rc); + + for (int row = 0; row < rc; ++row) + { + const QModelIndex mi = model->index(row, 0, root); + ret += mi; + if (tv->isExpanded(mi)) + ret += getItemChildren(model, tv, mi); + } + + return ret; +} + + +bool Base::printTableData(QString &str, + const QAbstractItemView *view, + const QString &title_, + const QSet &suppressCols, + int iconExtent) +{ + if (!view) + return false; + QString title = title_; + printTitleData(str, title.remove(QLatin1String("[*]"))); + const QAbstractItemModel *imodel = view->model(); + QModelIndexList mil; + if (m_printer->printRange() == QPrinter::Selection) + { + const QItemSelectionModel *sm = view->selectionModel(); + mil += sm->selectedRows(); + } + else + { + const QTreeView *tv = qobject_cast(view); + if (tv) + { + const int rc = imodel->rowCount(); + mil.reserve(rc); + for (int row = 0; row < rc; ++row) + { + QModelIndex mi = imodel->index(row, 0); + mil += mi; + if (tv->isExpanded(mi)) + mil += getItemChildren(imodel, tv, mi); + } + } + else + { + const int rc = imodel->rowCount(); + mil.reserve(rc); + for (int row = 0; row < rc; ++row) + mil.append(imodel->index(row, 0)); + } + } + int colCount = imodel->columnCount(); + QStringList tableHeader; + for (int i = 0; i < colCount; i++) + { + if (suppressCols.contains(i)) + continue; + QVariant v = imodel->headerData(i, Qt::Horizontal); + if (v.isValid()) + tableHeader.append(v.toString()); + } + str += QLatin1String("") + + QLatin1String("") + + QLatin1String(""); + for (const QString &s : qAsConst(tableHeader)) + str += QLatin1String(""); + str += QLatin1String(""); + + + + + QVector > spannedL; + for (const QModelIndex &mi :qAsConst(mil)) + { + str += QLatin1String(""); + for (int ii = 0; ii < colCount; ii++) + { + if (suppressCols.contains(ii)) + continue; + const QModelIndex ms = mi.sibling(mi.row(), ii); + if (spannedL.contains(qMakePair(ms.row(), ms.column()))) + continue; + int rowSpanSize; + int columnSpanSize; + spanSizes(view, ms, rowSpanSize, columnSpanSize, spannedL); + printTableItemData(str, imodel, ms, rowSpanSize, columnSpanSize, + iconExtent); + } + str += QLatin1String(""); + } + str += QLatin1String("
") + s + QLatin1String("
"); + + DEBUG_LOG(m_logger) << qPrintable(str) ; + return true; +} + + +void spanSizes(const QAbstractItemView *view, + const QModelIndex &ms, + int &rowSpanSize, + int &columnSpanSize, + QVector > &spannedL) +{ + rowSpanSize = 1; + columnSpanSize = 1; + const QTableView *tview = qobject_cast(view); + if (tview) + { + rowSpanSize = tview->rowSpan(ms.row(), ms.column()); + columnSpanSize = tview->columnSpan(ms.row(), ms.column()); + for (int row = ms.row(); row < ms.row() + rowSpanSize; ++row) + spannedL << qMakePair(row, ms.column()); + for (int column = ms.column(); column < ms.column() + columnSpanSize; ++column) + spannedL << qMakePair(ms.row(), column); + } +} + + +bool Base::printTableItemData(QString &str, + const QAbstractItemModel *model, + const QModelIndex &mi, + int rowSpanSize, + int columnSpanSize, + int iconExtent) +{ + QVariant vdata; + QString htmlString; + + + vdata = model->data(mi, Qt::BackgroundRole); + if (vdata.isValid()) + { + QString bgColorString = vdata.value().color().name(); + htmlString += QLatin1String(" 1) + htmlString += QLatin1String(" rowspan=\"") + QString::number(rowSpanSize) + QLatin1Char('\"'); + if (columnSpanSize > 1) + htmlString += QLatin1String(" colspan=\"") + QString::number(columnSpanSize) + QLatin1Char('\"'); + + htmlString += QLatin1Char('>'); + + printItemData(htmlString, model, mi, iconExtent); + htmlString += QLatin1String(""); + str += htmlString; + return true; +} + + + + +bool Base::printTreeData(QString &str, + const QAbstractItemView *view, + const QString &title, + const QSet &suppressCols, + int iconExtent, + const QList &tabtexts) +{ + if (!view) + return false; + printTitleData(str, title); + QAbstractItemModel *vm = view->model(); + if (!vm) + return false; + QItemSelectionModel *sm = view->selectionModel(); + QModelIndexList milColumn0; + if (m_printer->printRange() == QPrinter::Selection) + milColumn0 = sm->selectedRows(); + else + { + int row = 0; + while (1) + { + QModelIndex mi = vm->index(row, 0); + if (mi.isValid()) + { + milColumn0.append(mi); + row++; + continue; + } + break; + } + } + if (milColumn0.count() == 0) + return false; + + str += QLatin1String("") + + QLatin1String(""); + + QStringList tableHeader; + if (tabtexts.size() > 0) + tableHeader = tabtexts; + else + { + for (int i = 0; i < vm->columnCount(); i++) + { + if (suppressCols.contains(i)) + continue; + QVariant v = vm->headerData(i, Qt::Horizontal); + if (v.isValid()) + tableHeader.append(v.toString()); + else + tableHeader.append(QString()); + } + } + + for (const QString &s : qAsConst(tableHeader)) + str += QLatin1String(""); + str += QLatin1String(""); + for (const QModelIndex &miColumn0 : qAsConst(milColumn0)) + printListPoint(str, view, vm, miColumn0, suppressCols, iconExtent); + + str += QLatin1String("

") + s + QLatin1String("



"); + + + + + + return true; +} + + +void Base::printListPoint(QString &str, + const QAbstractItemView *view, + const QAbstractItemModel *vm, + const QModelIndex &miColumn0, + const QSet &suppressCols, + int iconExtent) +{ + if (!miColumn0.isValid()) + return; + + str += QLatin1String(""); + for (int column = 0; column < vm->columnCount(); column++) + { + if (suppressCols.contains(column)) + continue; + QModelIndex ms = miColumn0.sibling(miColumn0.row(), column); + if (!ms.isValid()) + continue; + str += QLatin1String("
"); + if (column == 0) + { + + int hierCount = hierarchyCount(ms); + for (int genIdx = 0; genIdx < hierCount; ++genIdx) + str += QLatin1String("
"); + str += QLatin1String("
"); + printItemData(str, vm, ms, iconExtent); + str += QLatin1String("
"); + for (int genIdx = 0; genIdx < hierCount; ++genIdx) + str += QLatin1String("
"); + } + else + { + str += QLatin1String("
"); + printItemData(str, vm, ms, iconExtent); + str += QLatin1String("
"); + } + str += QLatin1String("
"); + } + str += QLatin1String(""); + + + const QTreeView *tv = qobject_cast(view); + const bool expanded = tv ? tv->isExpanded(miColumn0) : true; + if (expanded) + { + const int rc = vm->rowCount(miColumn0); + for (int row = 0; row < rc; row++) + { + QModelIndex cmi = vm->index(row, 0, miColumn0); + if (cmi.isValid()) + printListPoint(str, view, vm, cmi, suppressCols, iconExtent); + } + } +} + + +int Base::hierarchyCount(const QModelIndex &child) +{ + int count = 0; + const auto parent = child.parent(); + if (parent.isValid()) + count = hierarchyCount(parent) + 1; + return count; +} + + +bool Base::printItemData(QString &str, + const QAbstractItemModel *model, + const QModelIndex &mi, + int iconExtent) +{ + QVariant vdata; + QString displayString; + QString htmlString; + + + vdata = model->data(mi, Qt::CheckStateRole); + if (vdata.isValid()) + { + displayString += QLatin1String(""); + if (static_cast(vdata.toInt())) + displayString += QLatin1String("⊗ "); + else + displayString += QLatin1String("ο "); + displayString += QLatin1String(""); + } + + printDecorationData(displayString, model, mi, iconExtent / 2); + + displayString += QLatin1Char(' '); + vdata = model->data(mi, Qt::DisplayRole); + if (vdata.isValid()) + displayString += vdata.toString(); + + QString fgColorString; + vdata = model->data(mi, Qt::TextColorRole); + if (vdata.isValid()) + fgColorString = vdata.value().name(); + + vdata = model->data(mi, Qt::ForegroundRole); + if (vdata.isValid()) + fgColorString = vdata.value().color().name(); + + htmlString += QLatin1String("') + displayString + QLatin1String(""); + + str += htmlString; + return true; +} + + +void Base::printDecorationData(QString &str, + const QAbstractItemModel *model, + const QModelIndex &mi, + int iconExtent) +{ + QVariant vdata = model->data(mi, Qt::DecorationRole); + if (vdata.isNull() || !vdata.isValid()) + return; + QIcon icon = vdata.value(); + if (icon.isNull()) + return; + + QString fn = iconFilename(m_iconFilenameNr++); + QPixmap pm = icon.pixmap(1000); + pm.save(fn); + + str += QLatin1String("\"icon\""); +} + + +QString Base::iconFilename(int nr) const +{ + return m_settings->printPath() + QLatin1String("icon_") + QString::number(nr) + QLatin1String(".png"); +} + + +bool Base::printDocTextBlock(QTextDocument &doc, + const QString &text, + const QString &title) +{ + QTextCursor cursor(&doc); + cursor.movePosition(QTextCursor::End); + + if (!title.isEmpty()) + { + QString htmlStr; + printTitleData(htmlStr, title); + cursor.insertHtml(htmlStr); + } + cursor.insertBlock(); + + QTextBlockFormat bf = cursor.blockFormat(); + bf.setNonBreakableLines(false); + cursor.setBlockFormat(bf); + + QTextCharFormat cf = cursor.blockCharFormat(); + cf.setFontWeight(QFont::Normal); + cursor.setBlockCharFormat(cf); + + cursor.insertText(text); + return true; +} + + +bool Base::printDocImage(QTextDocument &doc, const QImage &image, + const QString &title, const QString &description, const qreal alreadyUsedHeight) const +{ + if (image.isNull()) + return false; + QTextCursor cursor(&doc); + + static int iImageCounter = 0; + QString name = QStringLiteral("printing://image_%1.png").arg(iImageCounter++); + doc.addResource(QTextDocument::ImageResource, + QUrl(name), + QVariant(image)); + + QString htmlStr; + if (!title.isEmpty()) + printTitleData(htmlStr, title); + if (!description.isEmpty()) + printTitleData(htmlStr, description, QLatin1String("h4")); + double rel = (double)image.height() / (double)image.width(); + + double dwMax = contentPageSize().width(); + double dhMax = contentPageSize().height() - alreadyUsedHeight; + if (!title.isEmpty()) + dhMax -= TitleDataHeight; + if (!description.isEmpty()) + dhMax -= DescriptionDataHeight; + double dh = dhMax; + double dw = dwMax; + + if (rel >= 1) + { + + + double new_dw = dhMax / rel; + if (new_dw > dwMax) + { + + dh = dwMax * rel; + } + else + dw = new_dw; + } + else + { + + + double new_dh = dw * rel; + if (new_dh > dhMax) + { + + dw = dhMax / rel; + } + else + dh = new_dh; + } + + QTextImageFormat tif; + tif.setName(name); + tif.setHeight(dh); + tif.setWidth(dw); + + cursor.insertHtml(htmlStr); + cursor.insertImage(tif); + return true; +} + + +bool Base::printReportData(QTextDocument &doc, KDReports::Report &report ) +{ + QTextCursor cursor(&doc); + cursor.movePosition(QTextCursor::End); + + QTemporaryFile tempFile; + if (!tempFile.open()) + return false; + if (!report.exportToHtml(tempFile.fileName())) + return false; + cursor.insertHtml(QString::fromUtf8(tempFile.readAll())); + return true; +} + + +QPrinter::PageSize Base::pageSize() const +{ + return m_printer->pageSize(); +} + + +void Base::setPageSize(QPrinter::PageSize size) +{ + m_printer->setPageSize(size); +} + + +QPrinter::Orientation Base::orientation() const +{ + return m_printer->orientation(); +} + + +void Base::setOrientation(QPrinter::Orientation orientation) +{ + m_printer->setOrientation(orientation); +} + + +double Base::leftMargin() const +{ + return leftmargin_; +} + + +void Base::setLeftMargin(double margin) +{ + if ((margin > 0) && (margin < m_printer->paperRect().width() / 2)) + leftmargin_ = margin; + else + leftmargin_ = 0; +} + + +double Base::rightMargin() const +{ + return rightmargin_; +} + + +void Base::setRightMargin(double margin) +{ + if ((margin > 0) && (margin < m_printer->paperRect().width() / 2)) + rightmargin_ = margin; + else + rightmargin_ = 0; +} + + +double Base::topMargin() const +{ + return topmargin_; +} + + +void Base::setTopMargin(double margin) +{ + if ((margin > 0) && (margin < m_printer->paperRect().height() / 4)) + topmargin_ = margin; + else + topmargin_ = 0; +} + + +double Base::bottomMargin() const +{ + return bottommargin_; +} + + +void Base::setBottomMargin(double margin) +{ + if ((margin > 0) && (margin < m_printer->paperRect().height() / 4)) + bottommargin_ = margin; + else + bottommargin_ = 0; +} + + +void Base::setMargins(double margin) +{ + if ((margin > 0) + && (margin < m_printer->paperRect().height() / 2) + && (margin < m_printer->paperRect().width() / 2)) + leftmargin_ = rightmargin_ = topmargin_ = bottommargin_ = margin; + else + leftmargin_ = rightmargin_ = topmargin_ = bottommargin_ = 0; +} + + +double Base::spacing() const +{ + return spacing_; +} + + +void Base::setSpacing(double spacing) +{ + if ((spacing > 0) && (spacing <= m_printer->paperRect().height() / 8)) + spacing_ = spacing; + else + spacing_ = 0; +} + + +double Base::headerSize() const +{ + return headersize_; +} + + +void Base::setHeaderSize(double size) +{ + if ((size > 0) && (size <= m_printer->paperRect().height() / 8)) + headersize_ = size; + else + headersize_ = 0; +} + + +double Base::headerRule() const +{ + return headerrule_; +} + + +void Base::setHeaderRule(double pointsize) +{ + headerrule_ = qMax(0.0, pointsize); +} + + +const QString &Base::headerText() const +{ + return headertext_; +} + + + + + + + + + + +void Base::setHeaderText(const QString &text) +{ + headertext_ = text; +} + + +double Base::footerSize() const +{ + return footersize_; +} + + +void Base::setFooterSize(double size) +{ + if ((size > 0) && (size <= m_printer->paperRect().height() / 8)) + footersize_ = size; + else + footersize_ = 0; +} + + +double Base::footerRule() const +{ + return footerrule_; +} + + + +void Base::setFooterRule(double pointsize) +{ + footerrule_ = qMax(0.0, pointsize); +} + + +const QString &Base::footerText() const +{ + return footertext_; +} + + + + + + + + + +void Base::setFooterText(const QString &text) +{ + footertext_ = text; +} + + +void Base:: setItemFontSize(int itemFontSize) +{ + itemFontSize_ = itemFontSize; +} + + +QRectF Base::paperRect(QPaintDevice *device) +{ + + QRectF rect = m_printer->paperRect(); + + rect.setWidth(rect.width() * + device->logicalDpiX() / m_printer->logicalDpiX()); + rect.setHeight(rect.height() * + device->logicalDpiY() / m_printer->logicalDpiY()); + + return rect; +} + + +QRectF Base::contentRect(QPaintDevice *device) +{ + + QRectF rect = paperRect(device); + + rect.adjust(mmToInches(leftmargin_) * device->logicalDpiX(), + mmToInches(topmargin_) * device->logicalDpiY(), + -mmToInches(rightmargin_) * device->logicalDpiX(), + -mmToInches(bottommargin_) * device->logicalDpiY()); + + + if (headersize_ > 0) + { + rect.adjust(0, mmToInches(headersize_) * device->logicalDpiY(), 0, 0); + rect.adjust(0, mmToInches(spacing_) * device->logicalDpiY(), 0, 0); + } + + if (footersize_ > 0) + { + rect.adjust(0, 0, 0, -mmToInches(footersize_) * device->logicalDpiY()); + rect.adjust(0, 0, 0, -mmToInches(spacing_) * device->logicalDpiY()); + } + + return rect; +} + + +QRectF Base::headerRect(QPaintDevice *device) +{ + QRectF rect = paperRect(device); + rect.adjust(mmToInches(leftmargin_) * device->logicalDpiX(), + mmToInches(topmargin_) * device->logicalDpiY(), + -mmToInches(rightmargin_) * device->logicalDpiX(), 0); + + rect.setBottom(rect.top() + + mmToInches(headersize_) * device->logicalDpiY()); + + return rect; +} + + +QRectF Base::footerRect(QPaintDevice *device) +{ + QRectF rect = paperRect(device); + rect.adjust(mmToInches(leftmargin_) * device->logicalDpiX(), 0, + -mmToInches(rightmargin_) * device->logicalDpiX(), + -mmToInches(bottommargin_) * device->logicalDpiY()); + + rect.setTop(rect.bottom() - + mmToInches(footersize_) * device->logicalDpiY()); + + return rect; +} + + +void Base::print(QTextDocument *doc) +{ + m_tempdoc = doc->clone(); + print(); + delete m_tempdoc; + m_tempdoc = nullptr; +} + + + +void Base::printSinglePage(QTextDocument *doc) +{ + m_tempdoc = doc->clone(); + printSinglePage(); + delete m_tempdoc; + m_tempdoc = nullptr; +} + + +void Base::printSinglePage() +{ + if (!m_printer || !m_tempdoc) return; + + m_tempdoc->setUseDesignMetrics(true); + m_tempdoc->documentLayout()->setPaintDevice(m_printer); + m_tempdoc->setPageSize(contentRect(m_printer).size()); + + QTextFrameFormat fmt = m_tempdoc->rootFrame()->frameFormat(); + fmt.setMargin(0); + m_tempdoc->rootFrame()->setFrameFormat(fmt); + + + + + + int doccopies; + int pagecopies; + if (m_printer->collateCopies()) + { + doccopies = 1; + pagecopies = m_printer->numCopies(); + } + else + { + doccopies = m_printer->numCopies(); + pagecopies = 1; + } + + + int firstpage = m_printer->fromPage(); + int lastpage = m_printer->toPage(); + if (firstpage == 0 && lastpage == 0) + { + firstpage = 1; + lastpage = m_tempdoc->pageCount(); + } + + + bool ascending = true; + if (m_printer->pageOrder() == QPrinter::LastPageFirst) + { + int tmp = firstpage; + firstpage = lastpage; + lastpage = tmp; + ascending = false; + } + + + m_painter->setRenderHints(QPainter::Antialiasing | + QPainter::TextAntialiasing | + QPainter::SmoothPixmapTransform, true); + for (int dc = 0; dc < doccopies; dc++) + { + int pagenum = firstpage; + + while (true) + { + for (int pc = 0; pc < pagecopies; pc++) + { + if (m_printer->printerState() == QPrinter::Aborted || + m_printer->printerState() == QPrinter::Error) + return; + + paintPage( m_tempdoc, pagenum); + if (pc < pagecopies - 1) + m_printer->newPage(); + } + if (pagenum == lastpage) + break; + if (ascending) + pagenum++; + else + pagenum--; + + { + m_printer->newPage(); + } + } + + if (dc < doccopies - 1) + { + m_printer->newPage(); + } + } +} + + +void Base::printDone() +{ + m_painter->end(); + m_currentPage = 1; +} + + + +void Base::print() +{ + m_currentPage = 1; + printSinglePage(); + m_painter->end(); + m_currentPage = 1; +} + + + +void Base::paintPage(QTextDocument *document, + int pagenum) +{ + QRectF rect; + double onepoint = m_painter->device()->logicalDpiY() / 72.0; + + + if (headersize_ > 0) + { + rect = headerRect(m_painter->device()); + if (headerrule_ > 0.0) + { + m_painter->save(); + + m_painter->translate(0, onepoint + (headerrule_ * onepoint / 2.0)); + m_painter->setPen(QPen(Qt::black, headerrule_ * onepoint)); + m_painter->drawLine(rect.bottomLeft(), rect.bottomRight()); + m_painter->restore(); + } + + + QString header = headertext_; + QString title = document->metaInformation(QTextDocument::DocumentTitle).remove(QLatin1String("[*]")); + header.replace(QLatin1String("&exercise;"), currentExerciseName()); + header.replace(QLatin1String("&title;"), title); + header.replace(QLatin1String("&logo;"), m_settings->logoProject()); + header.replace(QLatin1String("&logoHeight;"), QString::number(ProjectIconHeight)); + + + m_painter->save(); + m_painter->translate(rect.left(), rect.top()); + QRectF clip(0, 0, rect.width(), rect.height()); + QTextDocument doc; + doc.setUseDesignMetrics(true); + doc.setHtml(header); + doc.documentLayout()->setPaintDevice(m_painter->device()); + doc.setPageSize(rect.size()); + + + double newtop = clip.bottom() - doc.size().height(); + clip.setHeight(doc.size().height()); + m_painter->translate(0, newtop); + + doc.drawContents(m_painter, clip); + m_painter->restore(); + } + + + if (footersize_ > 0) + { + rect = footerRect(m_painter->device()); + if (footerrule_ > 0.0) + { + m_painter->save(); + + m_painter->translate(0, -onepoint + (-footerrule_ * onepoint / 2.0)); + m_painter->setPen(QPen(Qt::black, footerrule_ * onepoint)); + m_painter->drawLine(rect.topLeft(), rect.topRight()); + m_painter->restore(); + } + + + QString footer = footertext_; + footer.replace(QLatin1String("&date;"), QObject::tr("Printed on: ") + DTG::toString(QDateTime::currentDateTime()) ); + footer.replace(QLatin1String("&user;"), operatorName()); + footer.replace(QLatin1String("&footertitle;"), footertitle_); + footer.replace(QLatin1String("&page;"), QString::number(m_currentPage++)); + + m_painter->save(); + m_painter->translate(rect.left(), rect.top()); + QRectF clip(0, 0, rect.width(), rect.height()); + QTextDocument doc; + doc.setUseDesignMetrics(true); + doc.setHtml(footer); + doc.documentLayout()->setPaintDevice(m_painter->device()); + doc.setPageSize(rect.size()); + doc.drawContents(m_painter, clip); + m_painter->restore(); + } + + + m_painter->save(); + + rect = contentRect(m_painter->device()); + m_painter->translate(rect.left(), rect.top() - (pagenum - 1) * rect.height()); + QRectF clip(0, (pagenum - 1) * rect.height(), rect.width(), rect.height()); + + document->drawContents(m_painter, clip); + + m_painter->restore(); +} + +void Base::setCurrentExerciseName( const QString& exerciseName ) +{ + m_exerciseName = exerciseName; +} + + +QString Base::currentExerciseName() const +{ + return m_exerciseName; +} + +void Base::setOperatorName( const QString& operatorName ) +{ + m_operatorName = operatorName; +} + + +QString Base::operatorName() const +{ + return m_operatorName; +} + + + +QSize Base::pageSizeInMM() +{ + QRectF rect = contentRect(m_printer); + return rect.size().toSize(); +} +} diff --git a/Lts/Common/LtsPrinting/printing_base.h b/Lts/Common/LtsPrinting/printing_base.h new file mode 100755 index 000000000..b7e59bb7d --- /dev/null +++ b/Lts/Common/LtsPrinting/printing_base.h @@ -0,0 +1,299 @@ + + +#pragma once + +#include +#include + +namespace NGL +{ +class INGLLogger; +class Settings; +} +namespace KDReports +{ +class Report; +} + +class QPainter; +class QAbstractItemView; +class QAbstractItemModel; +class QModelIndex; +class QTextDocument; + +namespace LtsPrinting +{ + +static constexpr int DECORATION_EXTENT = 50; +static constexpr int DOC_MARGIN = 60; +static constexpr int ProjectIconHeight = 40; + + +static const QLatin1String HeaderText( + "" + " " + " " + " " + " " + " " + "
&exercise;&title;\"Logo\"
"); + +static const QLatin1String FooterText( + "" + " " + " " + " " + " " + " " + "
&date; -- &user;&footertitle;&page;
"); + + +class Base +{ +public: + + + Base(QTextDocument &doc, + QWidget *dialogParent, + const QString &dialogTitle, + const QString &_fn, + QPrinter::PaperSize _ps = QPrinter::A4, + QPrinter::Orientation _or = QPrinter::Portrait, + QPrinter::ColorMode _cm = QPrinter::Color); + + + + virtual ~Base(); + + + + QPrinter *printer(); + + + + static void setPrintToFile(bool printToFile); + + + + static QString styleSheet(); + + + QPrinter::PageSize pageSize() const; + + void setPageSize(QPrinter::PageSize size); + + QPrinter::Orientation orientation() const; + + void setOrientation(QPrinter::Orientation orientation); + + bool newPage(); + + + double leftMargin() const; + + void setLeftMargin(double margin); + + double rightMargin() const; + + void setRightMargin(double margin); + + double topMargin() const; + + void setTopMargin(double margin); + + double bottomMargin() const; + + void setBottomMargin(double margin); + + void setMargins(double margin); + + + double spacing() const; + + void setSpacing(double spacing); + + + double headerSize() const; + + void setHeaderSize(double size); + + double headerRule() const; + + void setHeaderRule(double pointsize); + + const QString &headerText() const; + + void setHeaderText(const QString &text); + + + double footerSize() const; + + void setFooterSize(double size); + + double footerRule() const; + + void setFooterRule(double pointsize); + + const QString &footerText() const; + + void setFooterText(const QString &text); + + void setFooterTitle(const QString &footertitle) { footertitle_ = footertitle; } + + + void setItemFontSize(int itemFontSize); + + + + static void printTitleData(QString &htmlStr, const QString &title, const QString &heading = QStringLiteral("h3")); + + + + bool printListData(QString &str, + const QAbstractItemView *view, + const QString &title, + bool asIcons = false, + int iconExtent = DECORATION_EXTENT); + + + bool printTableData(QString &str, + const QAbstractItemView *view, + const QString &title = QString(), + const QSet &suppressCols = QSet(), + int iconExtent = DECORATION_EXTENT); + + + bool printTreeData(QString &str, + const QAbstractItemView *view, + const QString &title = QString(), + const QSet &suppressCols = QSet(), + int iconExtent = DECORATION_EXTENT, + const QList &tabtexts = QList()); + + + + static bool printDocTextBlock(QTextDocument &doc, + const QString &text, + const QString &title = QString()); + + + + bool printDocImage(QTextDocument &doc, const QImage &image, + const QString &title = QString(), const QString &description = QString(), const qreal alreadyUsedHeight = 0) const; + + + + static bool printReportData(QTextDocument &doc, KDReports::Report &report); + + + + virtual void print(QTextDocument *doc); + + + virtual void printSinglePage(QTextDocument *doc); + + + virtual void printDone(); + + + + + template + static QSet getHiddenCols(T *view) + { + QSet hiddenCols; + int colCount = view->model()->columnCount(); + for (int i = 0; i < colCount; ++i) + { + if (view->isColumnHidden(i)) + hiddenCols += i; + } + return hiddenCols; + } + + + + void setCurrentExerciseName( const QString& exerciseName ); + + + + void setOperatorName( const QString& operatorName ); + + + void paintPage(QTextDocument *document, + int pagenum); + + QSize pageSizeInMM(); + +protected: + bool printTableItemData(QString &str, + const QAbstractItemModel *model, + const QModelIndex &mi, + int rowSpanSize, + int columnSpanSize, + int iconExtent = DECORATION_EXTENT); + bool printItemData(QString &str, + const QAbstractItemModel *model, + const QModelIndex &mi, + int iconExtent = DECORATION_EXTENT); + void printDecorationData(QString &str, + const QAbstractItemModel *model, + const QModelIndex &mi, + int iconExtent = DECORATION_EXTENT); + void printListPoint(QString &str, + const QAbstractItemView *view, + const QAbstractItemModel *vm, + const QModelIndex &miColumn0, + const QSet &suppressCols = QSet(), + int iconExtent = DECORATION_EXTENT); + static int hierarchyCount(const QModelIndex &child); + QString iconFilename(int nr) const; + QSize printerPageSize() const; + QSize contentPageSize() const; + + QString currentExerciseName() const; + QString operatorName() const; + + + QRectF paperRect(QPaintDevice *device); + + QRectF contentRect(QPaintDevice *device); + QRectF headerRect(QPaintDevice *device); + QRectF footerRect(QPaintDevice *device); + + + + void printSinglePage(); + void print(); + +private: + QPrinter *m_printer; + QPainter *m_painter; + static bool m_printToFile; + int m_iconFilenameNr; + + QTextDocument *m_tempdoc; + + double leftmargin_; + double rightmargin_; + double topmargin_; + double bottommargin_; + double spacing_; + + double headersize_; + double headerrule_; + QString headertext_; + double footersize_; + double footerrule_; + QString footertext_; + QString footertitle_; + int itemFontSize_; + +private: + NGL::Settings *m_settings; + NGL::INGLLogger &m_logger; + QString m_exerciseName; + QString m_operatorName; + int m_currentPage; +}; +} diff --git a/Lts/Common/LtsProjectionWorkstation/CMakeLists.txt b/Lts/Common/LtsProjectionWorkstation/CMakeLists.txt new file mode 100755 index 000000000..8fa649ca9 --- /dev/null +++ b/Lts/Common/LtsProjectionWorkstation/CMakeLists.txt @@ -0,0 +1,47 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# Created by Christian Roeskens, NLSE, 2018-03-02 + + +SET(LtsProjectionWorkstation_SRCS + + lts_projection_workstation.h + lts_projection_workstation.cpp + + lts_projection_workstation_export.h + + vnc_server_manager.h + vnc_server_manager.cpp + + logger.cpp + logger.h + + WebSocket/json_keys.h + WebSocket/messages.h + WebSocket/messages.cpp + WebSocket/message_factory.h + WebSocket/message_factory.cpp +) + +NGL_ADD_LIBRARY(WS LtsProjectionWorkstation SHARED ${LtsProjectionWorkstation_SRCS}) + +NGL_TARGET_INCLUDE_DIRECTORIES(LtsProjectionWorkstation + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) + +NGL_TARGET_LINK_LIBRARIES(LtsProjectionWorkstation + PUBLIC + Qt5::Core + PRIVATE + Qt5::Gui + NGLLogger + nglsettings + LtsWebSocketServer + LtsSession + LtsSessionData +) + +NGL_INSTALL_TARGET(LtsProjectionWorkstation) diff --git a/Lts/Common/LtsProjectionWorkstation/WebSocket/client_message.cpp b/Lts/Common/LtsProjectionWorkstation/WebSocket/client_message.cpp new file mode 100755 index 000000000..7678fdc5a --- /dev/null +++ b/Lts/Common/LtsProjectionWorkstation/WebSocket/client_message.cpp @@ -0,0 +1,63 @@ + + + +#include "client_message.h" +#include "json_keys.h" +#include "message_handler.h" + +namespace LtsProjectionWorkstation +{ + + + +LtsWebSocket::AbstractClientMessage *ClientMessageFactory::createMessage(LtsWebSocket::ReplyHandler *client, const QString &message) +{ + LtsWebSocket::AbstractClientMessage *result = checkAttributes(client, message); + + if (result) + return result; + + if (m_type == JsonKeys::LOGIN_REQUEST()) + result = new LoginRequest(m_messageHandler, client, m_type, m_uuid, m_data); + + if (m_type == JsonKeys::LOGOUT_REQUEST()) + result = new LoginRequest(m_messageHandler, client, m_type, m_uuid, m_data); + + return checkResult(client, message, result); +} + + + +void LoginRequest::process() +{ + if (hasValidData()) + { + int userId = m_data.toObject().value(JsonKeys::USER_ID()).toInt(-1), + userPriority = m_data.toObject().value(JsonKeys::USER_PRIORITY()).toInt(-1), + exerciseId = m_data.toObject().value(JsonKeys::EXERCISE_ID()).toInt(-1); + m_client->addReply(m_messageHandler->handleLoginRequest(userId, userPriority, exerciseId)); + } +} + +bool LoginRequest::hasValidData() const +{ + return m_data.isObject() + && m_data.toObject().value(JsonKeys::USER_ID()).toInt(-1) > -1 + && m_data.toObject().value(JsonKeys::USER_PRIORITY()).toInt(-1) > -1 + && m_data.toObject().value(JsonKeys::EXERCISE_ID()).toInt(-1) > -1; +} + + + +void LogoutRequest::process() +{ + m_client->addReply(m_messageHandler->handleLogoutRequest()); +} + +bool LogoutRequest::hasValidData() const +{ + return true; +} + + +} diff --git a/Lts/Common/LtsProjectionWorkstation/WebSocket/client_message.h b/Lts/Common/LtsProjectionWorkstation/WebSocket/client_message.h new file mode 100755 index 000000000..1ab4fe499 --- /dev/null +++ b/Lts/Common/LtsProjectionWorkstation/WebSocket/client_message.h @@ -0,0 +1,65 @@ + + + +#pragma once + +#include + +#include "logger.h" + + +namespace LtsProjectionWorkstation +{ +class MessageHandler; + +class ClientMessageFactory : public LtsWebSocket::AbstractClientMessageFactory +{ +public: + explicit ClientMessageFactory(MessageHandler *messageHandler) + : LtsWebSocket::AbstractClientMessageFactory(getLogger("ClientMessageFactory")) + , m_messageHandler(messageHandler) + {} + + + LtsWebSocket::AbstractClientMessage *createMessage(LtsWebSocket::ReplyHandler *client, const QString &message) override final; + +private: + MessageHandler *m_messageHandler; +}; + +class ClientMessage : public LtsWebSocket::AbstractClientMessage +{ +public: + explicit ClientMessage(MessageHandler *messageHandler, LtsWebSocket::ReplyHandler *client, const QString &type, const QString &uuid, const QJsonValue &data) + : AbstractClientMessage(client, type, uuid, data) + , m_messageHandler(messageHandler) + {} + +protected: + MessageHandler *m_messageHandler; + +}; + +class LoginRequest : public ClientMessage +{ +public: + explicit LoginRequest(MessageHandler *messageHandler, LtsWebSocket::ReplyHandler *client, const QString &type, const QString &uuid, const QJsonValue &data) + : ClientMessage(messageHandler, client, type, uuid, data) + {} + + void process(); + bool hasValidData() const; +}; + +class LogoutRequest : public ClientMessage +{ +public: + explicit LogoutRequest(MessageHandler *messageHandler, LtsWebSocket::ReplyHandler *client, const QString &type, const QString &uuid, const QJsonValue &data) + : ClientMessage(messageHandler, client, type, uuid, data) + {} + + void process(); + bool hasValidData() const; +}; + +} diff --git a/Lts/Common/LtsProjectionWorkstation/WebSocket/json_keys.h b/Lts/Common/LtsProjectionWorkstation/WebSocket/json_keys.h new file mode 100755 index 000000000..fcfcbfa1e --- /dev/null +++ b/Lts/Common/LtsProjectionWorkstation/WebSocket/json_keys.h @@ -0,0 +1,40 @@ + + +#pragma once + +#include + +namespace LtsProjectionWorkstation +{ +namespace JsonKeys +{ + +inline QString ERROR_MESSAGE() { return QStringLiteral("errorMessage"); } + + +inline QString LOGIN_REQUEST() { return QStringLiteral("loginRequest"); } +inline QString LOGOUT_REQUEST() { return QStringLiteral("logoutRequest"); } +inline QString DISCONNECT_REQUEST() { return QStringLiteral("disconnectRequest"); } + + + +inline QString LOGIN_REPLY() { return QStringLiteral("loginReply"); } +inline QString LOGOUT_REPLY() { return QStringLiteral("logoutReply"); } +inline QString DEDICAED_DISCONNECT() { return QStringLiteral("dedicatedDisconnect"); } + + +inline QString USER_ID() { return QStringLiteral("userId"); } +inline QString USER_PRIORITY() { return QStringLiteral("userPriority"); } +inline QString EXERCISE_ID() { return QStringLiteral("exerciseId"); } +inline QString NAME() { return QStringLiteral("name"); } +inline QString PORT() { return QStringLiteral("port"); } +inline QString WIDTH() { return QStringLiteral("width"); } +inline QString HEIGHT() { return QStringLiteral("height"); } +inline QString REASON() { return QStringLiteral("reason"); } +inline QString SUCCESS() { return QStringLiteral("success"); } +inline QString SCREENS() { return QStringLiteral("screens"); } +inline QString SETTINGS_DATA() { return QStringLiteral("settingsData"); } + +} + +} diff --git a/Lts/Common/LtsProjectionWorkstation/WebSocket/message_factory.cpp b/Lts/Common/LtsProjectionWorkstation/WebSocket/message_factory.cpp new file mode 100755 index 000000000..e89573346 --- /dev/null +++ b/Lts/Common/LtsProjectionWorkstation/WebSocket/message_factory.cpp @@ -0,0 +1,53 @@ +#include "message_factory.h" +#include "messages.h" +#include "json_keys.h" + +#include +#include + +#include + +namespace LtsProjectionWorkstation +{ + +LtsWebSocket::AbstractClientMessage *MessageFactory::createMessage(LtsWebSocket::ReplyHandler *client, const QString &message) +{ + + + if (m_uuid.isEmpty()) + { + WARNING_LOG(m_logger) << "Got message without uuid: " << message << "'"; + WARNING_LOG(m_logger) << "Created UUID for error message!"; + return new ErrorMessage(QUuid::createUuid().toString(), QStringLiteral("Got message without uuid")); + } + + LtsWebSocket::AbstractClientMessage *result = checkAttributes(client, message); + + if (result) + return result; + + if (m_type == JsonKeys::DEDICAED_DISCONNECT()) + result = new STC::DedicatedDisconnect(m_handler, client, m_type, m_uuid, m_data); + + if (m_type == JsonKeys::LOGIN_REPLY()) + result = new STC::LoginRequestReply(m_handler, client, m_type, m_uuid, m_data); + + if (m_type == JsonKeys::LOGOUT_REPLY()) + result = new STC::LoginRequestReply(m_handler, client, m_type, m_uuid, m_data); + + if (m_type == JsonKeys::LOGIN_REQUEST()) + result = new CTS::LoginRequest(m_handler, client, m_type, m_uuid, m_data); + + if (m_type == JsonKeys::LOGOUT_REQUEST()) + result = new CTS::LogoutRequest(m_handler, client, m_type, m_uuid, m_data); + + if (m_type == JsonKeys::DISCONNECT_REQUEST()) + result = new CTS::DisconnectRequest(m_handler, client, m_type, m_uuid, m_data); + + if (m_type == LtsWebSocket::JsonKeys::ERROR_()) + return nullptr; + + return checkResult(client, message, result); +} + +} diff --git a/Lts/Common/LtsProjectionWorkstation/WebSocket/message_factory.h b/Lts/Common/LtsProjectionWorkstation/WebSocket/message_factory.h new file mode 100755 index 000000000..9155b5e8f --- /dev/null +++ b/Lts/Common/LtsProjectionWorkstation/WebSocket/message_factory.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include + +namespace LtsProjectionWorkstation +{ + +class AbstractMessageHandler; + +class LTSPROJECTIONWORKSTATION_EXPORT MessageFactory + : public LtsWebSocket::AbstractClientMessageFactory +{ + +public: + MessageFactory(AbstractMessageHandler *handler, LOGGER_T &logger) + : LtsWebSocket::AbstractClientMessageFactory(logger) + , m_handler(handler) + {} + + + LtsWebSocket::AbstractClientMessage *createMessage(LtsWebSocket::ReplyHandler *client, const QString &message) override final; + +private: + AbstractMessageHandler *m_handler; +}; + +} diff --git a/Lts/Common/LtsProjectionWorkstation/WebSocket/messages.cpp b/Lts/Common/LtsProjectionWorkstation/WebSocket/messages.cpp new file mode 100755 index 000000000..01d47524a --- /dev/null +++ b/Lts/Common/LtsProjectionWorkstation/WebSocket/messages.cpp @@ -0,0 +1,210 @@ + + + +#include "messages.h" + +#include +#include + +namespace LtsProjectionWorkstation +{ + + +STC::LoginRequestReply::LoginRequestReply(const QString &uuid, Reason reason, int reasonUserId, const QVector &screens) + : Message(nullptr, nullptr, JsonKeys::LOGIN_REPLY(), uuid, QJsonValue()) + , m_screens(screens) + , m_reasonUserId(reasonUserId) + , m_reason(reason) +{ + QJsonObject result; + QJsonArray arr; + for (const VncScreen &vncScreen : screens) + { + QJsonObject screen; + screen.insert(JsonKeys::NAME(), vncScreen.name); + screen.insert(JsonKeys::PORT(), vncScreen.port); + screen.insert(JsonKeys::WIDTH(), vncScreen.geometry.width()); + screen.insert(JsonKeys::HEIGHT(), vncScreen.geometry.height()); + arr.append(screen); + } + result.insert(JsonKeys::REASON(), reason); + result.insert(JsonKeys::USER_ID(), reasonUserId); + result.insert(JsonKeys::SCREENS(), arr); + m_replyData = QJsonValue(result); +} + + +void STC::LoginRequestReply::process() +{ + if (!m_handler || !m_client) return; + QJsonObject dataObj = m_data.toObject(); + m_reason = Reason(dataObj.value(JsonKeys::REASON()).toInt()); + m_reasonUserId = dataObj.value(JsonKeys::USER_ID()).toInt(); + const auto arr = dataObj.value(JsonKeys::SCREENS()).toArray(); + for (const QJsonValue &val : arr) + { + const QJsonObject obj(val.toObject()); + VncScreen screen; + screen.name = obj.value(JsonKeys::NAME()).toString(QStringLiteral("err")); + screen.port = obj.value(JsonKeys::PORT()).toInt(-1); + screen.geometry = QRect(0, 0, + obj.value(JsonKeys::WIDTH()).toInt(-1), + obj.value(JsonKeys::HEIGHT()).toInt(-1)); + m_screens.append(std::move(screen)); + } + m_handler->handleMessage(this); +} + + +bool STC::LoginRequestReply::hasValidData() const +{ + if (!m_handler || !m_client) return false; + QJsonObject obj = m_data.toObject(); + if (obj.value(JsonKeys::REASON()).isUndefined()) return false; + if (obj.value(JsonKeys::USER_ID()).isUndefined()) return false; + if (obj.value(JsonKeys::SCREENS()).isUndefined()) return false; + return true; +} + + + +STC::DedicatedDisconnect::DedicatedDisconnect(const QString &uuid, int userId) + : Message(nullptr, nullptr, JsonKeys::DEDICAED_DISCONNECT(), uuid, QJsonValue()) + , m_userId(userId) +{ + QJsonObject result; + result.insert(JsonKeys::USER_ID(), m_userId); + m_replyData = result; +} + + +void STC::DedicatedDisconnect::process() +{ + if (!m_handler || !m_client) return; + QJsonObject obj = m_data.toObject(); + m_userId = obj.value(JsonKeys::USER_ID()).toInt(-1); + m_handler->handleMessage(this); +} + + +bool STC::DedicatedDisconnect::hasValidData() const +{ + if (!m_handler || !m_client) return false; + QJsonObject obj = m_data.toObject(); + if (obj.value(JsonKeys::USER_ID()).isUndefined()) return false; + return true; +} + + + +CTS::LoginRequest::LoginRequest(const QString &uuid, int userId, int priority, int exerciseId, const QByteArray &settingsData) + : Message(nullptr, nullptr, JsonKeys::LOGIN_REQUEST(), uuid, QJsonValue()) + , m_settingsData(settingsData), m_userId(userId), m_priority(priority), m_exerciseId(exerciseId) +{ + QJsonObject result; + result.insert(JsonKeys::USER_ID(), m_userId); + result.insert(JsonKeys::USER_PRIORITY(), m_priority); + result.insert(JsonKeys::EXERCISE_ID(), m_exerciseId); + result.insert(JsonKeys::SETTINGS_DATA(), QString::fromLatin1(m_settingsData.toBase64())); + m_replyData = result; +} + + +void CTS::LoginRequest::process() +{ + if (!m_handler || !m_client) return; + QJsonObject obj = m_data.toObject(); + m_userId = obj.value(JsonKeys::USER_ID()).toInt(-1); + m_priority = obj.value(JsonKeys::USER_PRIORITY()).toInt(-1); + m_exerciseId = obj.value(JsonKeys::EXERCISE_ID()).toInt(-1); + m_settingsData = QByteArray::fromBase64(obj.value(JsonKeys::SETTINGS_DATA()).toString().toLatin1()); + m_handler->handleMessage(this); +} + + +bool CTS::LoginRequest::hasValidData() const +{ + if (!m_handler || !m_client) return false; + QJsonObject obj = m_data.toObject(); + if (obj.value(JsonKeys::USER_ID()).isUndefined()) return false; + if (obj.value(JsonKeys::EXERCISE_ID()).isUndefined()) return false; + if (obj.value(JsonKeys::USER_PRIORITY()).isUndefined()) return false; + if (obj.value(JsonKeys::SETTINGS_DATA()).isUndefined()) return false; + return true; +} + + + +void CTS::LogoutRequest::process() +{ + if (!m_handler || !m_client) return; + m_handler->handleMessage(this); +} + + +bool CTS::LogoutRequest::hasValidData() const +{ + if (!m_handler || !m_client) return false; + return true; +} + + + +CTS::DisconnectRequest::DisconnectRequest(const QString &uuid, int userId) +: Message(nullptr, nullptr, JsonKeys::DISCONNECT_REQUEST(), uuid, QJsonValue()) +, m_userId(userId) +{ + qDebug() << " 1 m_userId = " << m_userId; + QJsonObject result; + result.insert(JsonKeys::USER_ID(), m_userId); + m_replyData = result; +} + +void CTS::DisconnectRequest::process() +{ + if (!m_handler || !m_client) return; + QJsonObject obj = m_data.toObject(); + m_userId = obj.value(JsonKeys::USER_ID()).toInt(-1); + qDebug() << " 2 m_userId = " << m_userId; + m_handler->handleMessage(this); +} + +bool CTS::DisconnectRequest::hasValidData() const +{ + if (!m_handler || !m_client) return false; + QJsonObject obj = m_data.toObject(); + if (obj.value(JsonKeys::USER_ID()).isUndefined()) return false; + return true; +} + + + +ErrorMessage::ErrorMessage(const QString &uuid, const QString &reason) + : Message(nullptr, nullptr, JsonKeys::ERROR_MESSAGE(), uuid, QJsonValue()) + , m_reason(reason) +{ + QJsonObject result; + result.insert(JsonKeys::REASON(), m_reason); + m_replyData = result; +} + + +void ErrorMessage::process() +{ + if (!m_handler || !m_client) return; + QJsonObject obj = m_data.toObject(); + m_reason = obj.value(JsonKeys::REASON()).toString(QStringLiteral("err")); + m_handler->handleMessage(this); +} + + +bool ErrorMessage::hasValidData() const +{ + if (!m_handler || !m_client) return false; + QJsonObject obj = m_data.toObject(); + if (obj.value(JsonKeys::REASON()).isUndefined()) return false; + return true; +} + + +} diff --git a/Lts/Common/LtsProjectionWorkstation/WebSocket/messages.h b/Lts/Common/LtsProjectionWorkstation/WebSocket/messages.h new file mode 100755 index 000000000..84b793157 --- /dev/null +++ b/Lts/Common/LtsProjectionWorkstation/WebSocket/messages.h @@ -0,0 +1,192 @@ + + + +#pragma once + +#include "json_keys.h" + +#include +#include + +#include +#include + +#include + +namespace LtsWebSocket { class ReplyHandler; } + +namespace LtsProjectionWorkstation +{ + +class AbstractMessageHandler +{ +public: + virtual ~AbstractMessageHandler() = default; + virtual void handleMessage(LtsWebSocket::AbstractClientMessage *message) = 0; +}; + + +class Reply : public LtsWebSocket::ReplyMessage +{ +public: + Reply(const QString &type, const int typeId, const QString &uuid, const QJsonObject &data) + : LtsWebSocket::ReplyMessage(type, typeId, uuid) + { + m_jsonRepresentation.insert(LtsWebSocket::JsonKeys::DATA(), data); + } +}; + + +class Message : public LtsWebSocket::AbstractClientMessage +{ +public: + Message(AbstractMessageHandler *handler, LtsWebSocket::ReplyHandler *client, const QString &type, const QString &uuid, const QJsonValue &data = QJsonValue()) + : LtsWebSocket::AbstractClientMessage(client, type, 1, uuid, data) + , m_handler(handler) + {} + + LtsWebSocket::ReplyMessage *toReplyMessage() const + { + if (!m_handler || !m_client) + return new Reply(m_type, m_typeId, m_uuid, m_replyData.toObject()); + return nullptr; + } + + void addReply(LtsWebSocket::ReplyMessage *reply) + { + if (m_client) + m_client->addReply(reply); + } + +protected: + AbstractMessageHandler *m_handler; + QJsonValue m_replyData; + +}; + + +class LTSPROJECTIONWORKSTATION_EXPORT ErrorMessage : public Message +{ +public: + using Message::Message; + ErrorMessage(const QString &uuid, const QString &reason); + + + void process() override final; + bool hasValidData() const override final; + +private: + QString m_reason; +}; + +namespace STC +{ + +class LTSPROJECTIONWORKSTATION_EXPORT LoginRequestReply : public Message +{ +public: + enum Reason + { + LoginSuccessful, + OtherUser, + }; + + using Message::Message; + LoginRequestReply(const QString &uuid, Reason reason, int reasonUserId, const QVector &screens); + + + void process() override final; + bool hasValidData() const override final; + + int reasonUserId() const { return m_reasonUserId; } + Reason reason() const { return m_reason; } + const QVector &screens() const { return m_screens; } + +private: + QVector m_screens; + int m_reasonUserId = -1; + Reason m_reason = LoginSuccessful; +}; + +class LTSPROJECTIONWORKSTATION_EXPORT DedicatedDisconnect : public Message +{ +public: + using Message::Message; + DedicatedDisconnect(const QString &uuid, int userId); + + + void process() override final; + bool hasValidData() const override final; + + int userid() const { return m_userId; } + +private: + int m_userId = -1; + +}; + + +} + + +namespace CTS +{ + +class LTSPROJECTIONWORKSTATION_EXPORT LoginRequest : public Message +{ +public: + using Message::Message; + LoginRequest(const QString &uuid, int userId, int priority, int exerciseId, const QByteArray &settingsData); + + + void process() override final; + bool hasValidData() const override final; + + int userId() const { return m_userId; } + int priority() const { return m_priority; } + int exerciseId() const { return m_exerciseId; } + const QByteArray &settingsData() const { return m_settingsData; } + +private: + QByteArray m_settingsData; + int m_userId = 0; + int m_priority = 0; + int m_exerciseId = 0; + +}; + + +class LTSPROJECTIONWORKSTATION_EXPORT LogoutRequest : public Message +{ +public: + using Message::Message; + explicit LogoutRequest(const QString &uuid) + : Message(nullptr, nullptr, JsonKeys::LOGOUT_REQUEST(), uuid, QJsonValue()) + {} + + + void process() override final; + bool hasValidData() const override final; + +}; + + +class LTSPROJECTIONWORKSTATION_EXPORT DisconnectRequest : public Message +{ +public: + using Message::Message; + explicit DisconnectRequest(const QString &uuid, int userId); + + + void process() override final; + bool hasValidData() const override final; + + int userId() const { return m_userId; } +private: + int m_userId{0}; + +}; + +} + +} diff --git a/Lts/Common/LtsProjectionWorkstation/WebSocket/reply_message.cpp b/Lts/Common/LtsProjectionWorkstation/WebSocket/reply_message.cpp new file mode 100755 index 000000000..6ca832899 --- /dev/null +++ b/Lts/Common/LtsProjectionWorkstation/WebSocket/reply_message.cpp @@ -0,0 +1,21 @@ + + + +#include "reply_message.h" + +namespace LtsProjectionWorkstation +{ + +LoginReply::LoginReply(const QJsonObject &data, const QString &uuid) + : LtsWebSocket::ReplyMessage(JsonKeys::LOGIN_REPLY(), uuid) +{ + m_jsonRepresentation.insert(LtsWebSocket::JsonKeys::DATA(), data); +} + +LogoutReply::LogoutReply(const QJsonObject &data, const QString &uuid) + : LtsWebSocket::ReplyMessage(JsonKeys::LOGOUT_REPLY(), uuid) +{ + m_jsonRepresentation.insert(LtsWebSocket::JsonKeys::DATA(), data); +} + +} diff --git a/Lts/Common/LtsProjectionWorkstation/WebSocket/reply_message.h b/Lts/Common/LtsProjectionWorkstation/WebSocket/reply_message.h new file mode 100755 index 000000000..e3f4235e7 --- /dev/null +++ b/Lts/Common/LtsProjectionWorkstation/WebSocket/reply_message.h @@ -0,0 +1,24 @@ + + + +#include "json_keys.h" + +#include +#include + +namespace LtsProjectionWorkstation +{ + +class LoginReply : public LtsWebSocket::ReplyMessage +{ +public: + explicit LoginReply(const QJsonObject &data, const QString &uuid); +}; + +class LogoutReply : public LtsWebSocket::ReplyMessage +{ +public: + explicit LogoutReply(const QJsonObject &data, const QString &uuid); +}; + +} diff --git a/Lts/Common/LtsProjectionWorkstation/logger.cpp b/Lts/Common/LtsProjectionWorkstation/logger.cpp new file mode 100755 index 000000000..07b63f904 --- /dev/null +++ b/Lts/Common/LtsProjectionWorkstation/logger.cpp @@ -0,0 +1,32 @@ + + + +#include "logger.h" + +#include + +namespace LtsProjectionWorkstation +{ + +LOGGER_T getLogger(const char *name) +{ + static const char ns[] = "PWS."; + std::string loggerName; + const size_t len = strlen(name); + loggerName.reserve(sizeof(ns) + len); + loggerName.append(ns, sizeof(ns) - 1); + loggerName.append(name, len); + + try + { + return NGL::NGLLoggerBootstrap::getNGLRootLogger(). + getNGLLogger(loggerName); + } + catch (const std::exception &ex) + { + fprintf(stderr, "Error retrieving logger: %s", ex.what()); + abort(); + } +} + +} diff --git a/Lts/Common/LtsProjectionWorkstation/logger.h b/Lts/Common/LtsProjectionWorkstation/logger.h new file mode 100755 index 000000000..5c34eddf4 --- /dev/null +++ b/Lts/Common/LtsProjectionWorkstation/logger.h @@ -0,0 +1,18 @@ + + + +#pragma once + +#include +#include "lts_projection_workstation_export.h" + +namespace LtsProjectionWorkstation +{ +using LOGGER_T = NGL::INGLLogger&; + +LTSPROJECTIONWORKSTATION_EXPORT LOGGER_T getLogger(const char *name); +inline LOGGER_T getLogger(const QString &loggerName) +{ + return getLogger(loggerName.toUtf8().constData()); +} +} diff --git a/Lts/Common/LtsProjectionWorkstation/lts_projection_workstation.cpp b/Lts/Common/LtsProjectionWorkstation/lts_projection_workstation.cpp new file mode 100755 index 000000000..d826cf6af --- /dev/null +++ b/Lts/Common/LtsProjectionWorkstation/lts_projection_workstation.cpp @@ -0,0 +1,247 @@ + + + +#include "lts_projection_workstation.h" +#include "WebSocket/message_factory.h" +#include "WebSocket/json_keys.h" + +#include +#include + +#include + +#include +#include +#include +#include + +namespace LtsProjectionWorkstation +{ + +static LtsProjectionWorkstation *s_ltsProjectionWorkstation = nullptr; + +LtsProjectionWorkstation *LtsProjectionWorkstation::getInstance() +{ + if (!s_ltsProjectionWorkstation) + { + s_ltsProjectionWorkstation = new LtsProjectionWorkstation; + qAddPostRoutine(&cleanupLtsProjectionWorkstation); + } + return s_ltsProjectionWorkstation; +} + +void LtsProjectionWorkstation::cleanupLtsProjectionWorkstation() +{ + delete s_ltsProjectionWorkstation; + s_ltsProjectionWorkstation = nullptr; +} + +bool LtsProjectionWorkstation::hasInstance() +{ + return !!s_ltsProjectionWorkstation; +} + +LtsProjectionWorkstation::LtsProjectionWorkstation(QObject *parent) + : QObject(parent) + , m_logger(getLogger("LtsProjetionWorkstation")) + , m_messageFactory(new MessageFactory(this, m_logger)) + , m_webSocketServer(new LtsWebSocket::LtsWebSocketServer(m_messageFactory, this)) + , m_pendingLoginRequest(nullptr) + , m_vncManager(new VncServerManager(m_logger, this)) +{ + connect(m_webSocketServer, &LtsWebSocket::LtsWebSocketServer::clientDisconnected, + this, &LtsProjectionWorkstation::clientDisconnected); + connect(m_webSocketServer, &LtsWebSocket::LtsWebSocketServer::clientConnected, + this, &LtsProjectionWorkstation::clientConnected); + + int port = NGL::Connections::port(NGL::Connections::eConnection::PWS_WEBSOCKET); + + if (m_webSocketServer->init(QStringLiteral("LtsProjectionWorkstation"), port)) + { + if (m_vncManager->init(qApp->screens())) + m_vncManager->startScreens(); + } +} + +LtsProjectionWorkstation::~LtsProjectionWorkstation() +{ + delete m_vncManager; + delete m_messageFactory; + delete m_webSocketServer; +} + +LoginInformation LtsProjectionWorkstation::pendingLoginRequest() +{ + if (!m_pendingLoginRequest) + return {}; + + LoginInformation result = *m_pendingLoginRequest; + + INFO_LOG(m_logger) << " reset pendingLoginRequest.."; + + delete m_pendingLoginRequest; + m_pendingLoginRequest = nullptr; + + return result; +} + +void LtsProjectionWorkstation::handleMessage(LtsWebSocket::AbstractClientMessage *message) +{ + CTS::LoginRequest *login = dynamic_cast(message); + if (login) + { + INFO_LOG(m_logger) << "Login request received!" + << ", login->userId()=" << login->userId() + << " with login->priority()=" << login->priority() + << " ;;; m_lastLoginRequest.m_userId=" << m_lastLoginRequest.m_userId + << ", m_priority=" << m_lastLoginRequest.m_priority; + + if (login->userId() > 0 && login->priority() > 0 && login->exerciseId() > 0) + { + + if ( login->userId() == m_lastLoginRequest.m_userId || + (login->priority() > m_lastLoginRequest.m_priority) + ) + { + + m_webSocketServer->sendToAllClients( (new STC::DedicatedDisconnect(QUuid::createUuid().toString(), login->userId()))->toReplyMessage() ); + + + + DEBUG_LOG(m_logger) << "Login request received and DedicatedDisconnect distributed, wait some seconds..."; + int i=0; + while(i<20) + { + DEBUG_LOG(m_logger) << "Login request received and DedicatedDisconnect distributed, wait some seconds...i=" << i; + qApp->processEvents(QEventLoop::AllEvents, 50); + QThread::msleep(50); + ++i; + } + INFO_LOG(m_logger) << "Login request received and DedicatedDisconnect distributed, wait some seconds... done"; + + + login->addReply((new STC::LoginRequestReply(QUuid::createUuid().toString(), + STC::LoginRequestReply::LoginSuccessful, + login->userId(), + m_vncManager->screens()) + )->toReplyMessage()); + + m_lastLoginRequest.m_userId = login->userId(); + m_lastLoginRequest.m_priority = login->priority(); + m_lastLoginRequest.m_exerciseId = login->exerciseId(); + m_lastLoginRequest.m_settingsData = login->settingsData(); + + slotLoginRequestReceived(login->userId(), login->exerciseId()); + + INFO_LOG(m_logger) << "Valid login request received -> userId: " << login->userId() + << " priority: " << login->priority() + << " exerciseId: " << login->exerciseId(); + DEBUG_LOG(m_logger) << "Logging in now!"; + + } + else + { + login->addReply((new STC::LoginRequestReply(QUuid::createUuid().toString(), + STC::LoginRequestReply::OtherUser, + m_lastLoginRequest.m_userId, + QVector()) + )->toReplyMessage()); + + DEBUG_LOG(m_logger) << "Higher prioritized login received -> userId: " << login->userId() + << " priority: " << login->priority() + << " exerciseId: " << login->exerciseId(); + + DEBUG_LOG(m_logger) << "Last login data was -> userId: " << m_lastLoginRequest.m_userId + << " priority: " << m_lastLoginRequest.m_priority + << " exerciseId: " << m_lastLoginRequest.m_exerciseId; + } + } + else + ERROR_LOG(m_logger) << "invalid login request received -> userId: " << login->userId() + << " priority: " << login->priority() + << " exerciseId: " << login->exerciseId(); + + return; + } + + CTS::LogoutRequest *logout = dynamic_cast(message); + if (logout) + { + DEBUG_LOG(m_logger) << "Logging out now!"; + slotLogoutRequestReceived(); + return; + } + + CTS::DisconnectRequest *discon = dynamic_cast(message); + if (discon) + { + + + + + + + + + int disconnectingUserId = discon->userId(); + INFO_LOG(m_logger) << "Disconnecting now! " + << ", disconnectingUserId=" << disconnectingUserId + << ", m_pendingLoginRequest=" << m_pendingLoginRequest; + m_lastLoginRequest = LoginInformation(); + return; + } +} + +void LtsProjectionWorkstation::slotLoginRequestReceived(int userId, int exerciseId) +{ + INFO_LOG(m_logger) << "slotLoginRequestReceived.." + << ", userId=" << userId + << ", exerciseId=" << exerciseId; + delete m_pendingLoginRequest; + m_pendingLoginRequest = new LoginInformation(m_lastLoginRequest); + emit signalLoginRequestReceived(userId, exerciseId); +} + +void LtsProjectionWorkstation::slotLogoutRequestReceived() +{ + emit signalLogoutRequestReceived(); +} + + +void LtsProjectionWorkstation::slotLogoffDone() +{ + if (hasPendingLoginRequest()) + return; + for (LtsWebSocket::ClientConnection *con : qAsConst(m_connections)) + con->webSocket()->close(); +} + + +void LtsProjectionWorkstation::clientDisconnected(LtsWebSocket::ClientConnection *client) +{ + INFO_LOG(m_logger) << LOGFUNCTION << " START" + << ", m_connections.size = " << m_connections.size(); + + m_connections.removeOne(client); + + if ( m_connections.size() == 0 && (m_lastLoginRequest.m_userId != 0 || m_lastLoginRequest.m_priority != 0)) + { + INFO_LOG(m_logger) << LOGFUNCTION << " reset m_lastLoginRequest"; + m_lastLoginRequest = LoginInformation(); + } + INFO_LOG(m_logger) << LOGFUNCTION << " END" + << ", m_connections.size = " << m_connections.size(); +} + +void LtsProjectionWorkstation::clientConnected(LtsWebSocket::ClientConnection *client) +{ + m_connections.append(client); +} + +void LtsProjectionWorkstation::setProjectionOperatorPreset(const QString &operatorName, const QString &operatorPassword) +{ + m_projectionOperatorPreset.first = operatorName; + m_projectionOperatorPreset.second = operatorPassword; +} + +} diff --git a/Lts/Common/LtsProjectionWorkstation/lts_projection_workstation.h b/Lts/Common/LtsProjectionWorkstation/lts_projection_workstation.h new file mode 100755 index 000000000..aedeb160a --- /dev/null +++ b/Lts/Common/LtsProjectionWorkstation/lts_projection_workstation.h @@ -0,0 +1,84 @@ + + + +#pragma once + +#include "lts_projection_workstation_export.h" +#include "vnc_server_manager.h" +#include "WebSocket/messages.h" +#include "logger.h" + +#include + +#include +#include + +namespace LtsWebSocket { class LtsWebSocketServer; } +namespace LtsProjectionWorkstation +{ + +struct LoginInformation +{ + LoginInformation() = default; + LoginInformation(int userId, int priority, int exerciseId, const QByteArray &settingsData) + : m_settingsData(settingsData), m_userId(userId), m_priority(priority), m_exerciseId(exerciseId) {} + + QByteArray m_settingsData; + int m_userId = 0; + int m_priority = 0; + int m_exerciseId = 0; +}; + +class MessageFactory; +class LTSPROJECTIONWORKSTATION_EXPORT LtsProjectionWorkstation : public QObject, public AbstractMessageHandler +{ + Q_OBJECT + +public: + static LtsProjectionWorkstation *getInstance(); + static bool hasInstance(); + + const LoginInformation &lastLoginRequest() const { return m_lastLoginRequest; } + LoginInformation pendingLoginRequest(); + bool hasPendingLoginRequest() const { return !!m_pendingLoginRequest; } + void setProjectionOperatorPreset(const QString &operatorName, const QString &operatorPassword); + const QPair &projectionOperatorPreset() const { return m_projectionOperatorPreset; } + + + void handleMessage(LtsWebSocket::AbstractClientMessage *message) override final; + +public slots: + void slotLoginRequestReceived(int userId, int exerciseId); + void slotLogoutRequestReceived(); + + void slotLogoffDone(); + +signals: + void signalLoginRequestReceived(int userId, int exerciseId); + void signalLogoutRequestReceived(); + +private slots: + void clientDisconnected(LtsWebSocket::ClientConnection* client); + void clientConnected(LtsWebSocket::ClientConnection* client); + +private: + explicit LtsProjectionWorkstation(QObject *parent = nullptr); + ~LtsProjectionWorkstation(); + static void cleanupLtsProjectionWorkstation(); + + LOGGER_T m_logger; + MessageFactory *m_messageFactory; + LtsWebSocket::LtsWebSocketServer *m_webSocketServer; + + + LoginInformation m_lastLoginRequest; + + LoginInformation *m_pendingLoginRequest; + QPair m_projectionOperatorPreset; + + QVector m_connections; + + VncServerManager *m_vncManager; +}; + +} diff --git a/Lts/Common/LtsProjectionWorkstation/lts_projection_workstation_export.h b/Lts/Common/LtsProjectionWorkstation/lts_projection_workstation_export.h new file mode 100755 index 000000000..a65af195b --- /dev/null +++ b/Lts/Common/LtsProjectionWorkstation/lts_projection_workstation_export.h @@ -0,0 +1,17 @@ + + +#pragma once + +#include + +#ifndef LTSPROJECTIONWORKSTATION_EXPORT +# ifdef BUILD_STATIC_LIBRARIES +# define LTSPROJECTIONWORKSTATION_EXPORT +# else +# if defined(MAKE_LTSPROJECTIONWORKSTATION_LIB) +# define LTSPROJECTIONWORKSTATION_EXPORT Q_DECL_EXPORT +# else +# define LTSPROJECTIONWORKSTATION_EXPORT Q_DECL_IMPORT +# endif +# endif +#endif diff --git a/Lts/Common/LtsProjectionWorkstation/vnc_server_manager.cpp b/Lts/Common/LtsProjectionWorkstation/vnc_server_manager.cpp new file mode 100755 index 000000000..ce89f931a --- /dev/null +++ b/Lts/Common/LtsProjectionWorkstation/vnc_server_manager.cpp @@ -0,0 +1,194 @@ + + + +#include "vnc_server_manager.h" + +#include +#include +#include + +namespace LtsProjectionWorkstation +{ + +VncServerManager::VncServerManager(LOGGER_T &logger, QObject *parent) + : QObject(parent) + , m_logger(logger) +{ +} + + +VncServerManager::~VncServerManager() +{ + stopScreens(); +} + +QList sortScreen(const QList &screens) +{ + auto ret(screens); + + auto doSortScreens = [](const QScreen *s1, const QScreen *s2) -> bool + { + const auto tl1 = s1->geometry().topLeft(); + const auto tl2 = s2->geometry().topLeft(); + if (tl1.y() > tl2.y()) + return true; + if (tl1.y() < tl2.y()) + return false; + return tl1.x() < tl2.x(); + }; + std::sort(ret.begin(), ret.end(), doSortScreens); + return ret; +} + +bool VncServerManager::init(const QList &screens) +{ + killScreens(); + createPwdFile(); + + const auto sortedScreens = sortScreen(screens); + + DEBUG_LOG(m_logger) << "Setup VNCScreens with " << screens.size() << " screens"; + + int count = 1; + + static constexpr char VNC_START_ARGS[] = "-rfbauth,%1/.vnc/passwd,-clip,%2x%3+%4+%5,-loop100,-forever,-display,:0,-threads,-noxdamage"; + + + for (QScreen *screen : sortedScreens) + { + VncScreen vncScreen; + + vncScreen.geometry = screen->geometry(); + vncScreen.name = QStringLiteral("Display%1").arg(count); + vncScreen.proc = new QProcess(this); + QString startArgs = QString::fromLatin1(VNC_START_ARGS).arg(QDir::home().absolutePath()) + .arg(screen->geometry().width()).arg(screen->geometry().height()) + .arg(screen->geometry().x()).arg(screen->geometry().y()); + + vncScreen.proc->setProgram(QStringLiteral("x11vnc")); + vncScreen.proc->setArguments(startArgs.split(QLatin1Char(','))); + + NOTICE_LOG(m_logger) << "Started VNC for Screen " << count << " with geometry " + << screen->geometry().width() << "," << screen->geometry().height() << "+" + << screen->geometry().x() << "+" << screen->geometry().y(); + + m_vncScreens.push_back(vncScreen); + count++; + } + + DEBUG_LOG(m_logger) << m_vncScreens.size() << " VNCScreen(s) created - Setup completed."; + return true; +} + +void VncServerManager::startScreens() +{ + DEBUG_LOG(m_logger) << "Starting " << m_vncScreens.size() << " VNCScreen(s) ... "; + for (const VncScreen &s : qAsConst(m_vncScreens)) + { + connect(s.proc, &QProcess::readyReadStandardOutput, + this, &VncServerManager::slotReadStandardOutput, Qt::UniqueConnection); + connect(s.proc, &QProcess::errorOccurred, + this, &VncServerManager::slotErrorOccured, Qt::UniqueConnection); + s.proc->start(); + } + for (const VncScreen &s : qAsConst(m_vncScreens)) + { + if (!s.proc->waitForStarted(2000)) + ERROR_LOG(m_logger) << "Can not start process '" << s.proc->program() << "': " + << s.proc->errorString(); + } + DEBUG_LOG(m_logger) << "Done"; +} + + +void VncServerManager::stopScreens() +{ + DEBUG_LOG(m_logger) << "Stopping " << m_vncScreens.size() << " VNCScreen(s) ..."; + + for (const VncScreen &s : qAsConst(m_vncScreens)) + { + s.proc->disconnect(this); + s.proc->terminate(); + } + for (const VncScreen &s : qAsConst(m_vncScreens)) + { + if (!s.proc->waitForFinished(2000)) + s.proc->kill(); + } + DEBUG_LOG(m_logger) << "Done"; +} + + +void VncServerManager::killScreens() +{ + DEBUG_LOG(m_logger) << "Killing all exisiting x11vnc processes ..."; + + SingleShotProcess ssp(QStringLiteral("killall x11vnc")); + ssp.execute(); + DEBUG_LOG(m_logger) << "Done"; +} + + +void VncServerManager::createPwdFile() +{ + DEBUG_LOG(m_logger) << "(Re)creating x11vnc password-file ..."; + + SingleShotProcess ssp(QStringLiteral("x11vnc -storepasswd ctc11de %1/.vnc/passwd") + .arg(QDir::home().absolutePath())); + ssp.execute(); + DEBUG_LOG(m_logger) << "Done"; +} + + +void VncServerManager::slotReadStandardOutput() +{ + QProcess *p = qobject_cast(QObject::sender()); + if (!p) + return; + + QString stdOut = QString::fromLocal8Bit(p->readAllStandardOutput()); + QRegularExpressionMatch match = QRegularExpression(QStringLiteral("PORT=(.*?)\\s")).match(stdOut); + if (!match.hasMatch()) + { + WARNING_LOG(m_logger) << "Could not retrieve port information from stdout of " << p->program(); + return; + } + const quint16 port = match.capturedRef(1).toInt(); + if (port == 0) + return; + for (VncScreen &screen : m_vncScreens) + { + if (screen.proc != p) + continue; + screen.port = port; + disconnect(screen.proc, &QProcess::readyReadStandardOutput, nullptr, nullptr); + break; + } +} + + +void VncServerManager::slotErrorOccured() +{ + QProcess *p = qobject_cast(QObject::sender()); + if (p) + ERROR_LOG(m_logger) << p->errorString(); +} + + + + +SingleShotProcess::SingleShotProcess(const QString &cmd, bool startImmediatly) + : m_cmd(cmd) +{ + if (startImmediatly) execute(); +} + +bool SingleShotProcess::execute() +{ + m_process.start(m_cmd); + return m_process.waitForFinished(); +} + + + +} diff --git a/Lts/Common/LtsProjectionWorkstation/vnc_server_manager.h b/Lts/Common/LtsProjectionWorkstation/vnc_server_manager.h new file mode 100755 index 000000000..3e1f01441 --- /dev/null +++ b/Lts/Common/LtsProjectionWorkstation/vnc_server_manager.h @@ -0,0 +1,69 @@ + + + +#pragma once + +#include +#include +#include +#include +#include + +#include "logger.h" + +class QScreen; + +namespace LtsProjectionWorkstation +{ + +class SingleShotProcess +{ +public: + explicit SingleShotProcess(const QString &cmd, bool startImmediatly = false); + + bool execute(); + +private: + QProcess m_process; + QString m_cmd; +}; + + +struct VncScreen +{ + QString name; + QProcess *proc = nullptr; + QRect geometry; + quint16 port = 0; +}; + + +class VncServerManager : public QObject +{ + Q_OBJECT +public: + explicit VncServerManager(LOGGER_T &logger, QObject *parent = nullptr); + + ~VncServerManager(); + + bool init(const QList &screens); + + const QVector &screens() const { return m_vncScreens; } + + + + void startScreens(); + void stopScreens(); + void killScreens(); + void createPwdFile(); + +private slots: + void slotReadStandardOutput(); + void slotErrorOccured(); + +private: + QVector m_vncScreens; + LOGGER_T &m_logger; +}; + +} diff --git a/Lts/Common/LtsScriptTimeline/CMakeLists.txt b/Lts/Common/LtsScriptTimeline/CMakeLists.txt new file mode 100755 index 000000000..738b921a6 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/CMakeLists.txt @@ -0,0 +1,70 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# +# Created by Mike Wille, SLSE, 2017-03-15 +# + + +# Test +#ADD_SUBDIRECTORY(Test) Not needed anymore + old 'api' + + +# Library +SET(LtsScriptTimeline_SRCS + tools.h + tools.cpp + indicator.h + indicator.cpp + script_timeline.h + script_timeline.cpp + track_content_widget.h + track_content_widget.cpp + track_entry.h + track_entry_painters.h + track_entry_painters.cpp + track_entry_previewer.h + track_entry_previewer.cpp + track_entry_preview_widget.h + track_entry_preview_widget.cpp + track_select_handler.h + track_select_handler.cpp + track_resize_handler.cpp + track_resize_handler.h + track_drag_handler.h + track_drag_handler.cpp + track_widget.h + track_widget.cpp + track_header.h + track_header.cpp + filter_track_widget.h + filter_track_widget.cpp + filter_background_widget.h + filter_background_widget.cpp + temporary_pointer_vector.h +) + +SET(LtsScriptTimeline_UICS + Ui/indicator.ui + Ui/script_timeline.ui + Ui/track_widget.ui + Ui/filter_track_widget.ui + Ui/track_entry_preview_widget.ui +) + +NGL_WRAP_UI(LtsScriptTimeline_SRCS ${LtsScriptTimeline_UICS}) + +NGL_ADD_LIBRARY2(WS LtsScriptTimeline SHARED ${LtsScriptTimeline_SRCS} ${LtsScriptTimeline_UICS}) + +NGL_TARGET_INCLUDE_DIRECTORIES(LtsScriptTimeline + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) + +NGL_TARGET_LINK_LIBRARIES(LtsScriptTimeline + Qt5::Core + Qt5::Widgets + LtsWidgets + LtsSessionData +) diff --git a/Lts/Common/LtsScriptTimeline/Test/CMakeLists.txt b/Lts/Common/LtsScriptTimeline/Test/CMakeLists.txt new file mode 100755 index 000000000..76ec481d5 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/Test/CMakeLists.txt @@ -0,0 +1,24 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# +# Created by Mike Wille, SLSE, 2017-03-15 +# + +# Test-Executable + +SET(LtsScriptTimelineTest_SRCS + main.cpp + mainwindow.h + mainwindow.cpp +) + +SET(LtsScriptTimelineTest_UICS + mainwindow.ui +) + +NGL_WRAP_UI(LtsScriptTimelineTest_SRCS ${LtsScriptTimelineTest_UICS}) + +NGL_ADD_EXECUTABLE(TOOLSWS LtsScriptTimelineTest ${LtsScriptTimelineTest_SRCS}) +NGL_TARGET_LINK_LIBRARIES(LtsScriptTimelineTest LtsScriptTimeline) diff --git a/Lts/Common/LtsScriptTimeline/Test/main.cpp b/Lts/Common/LtsScriptTimeline/Test/main.cpp new file mode 100755 index 000000000..30c682cea --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/Test/main.cpp @@ -0,0 +1,12 @@ +#include "mainwindow.h" +#include + + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/Lts/Common/LtsScriptTimeline/Test/mainwindow.cpp b/Lts/Common/LtsScriptTimeline/Test/mainwindow.cpp new file mode 100755 index 000000000..f5acea730 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/Test/mainwindow.cpp @@ -0,0 +1,104 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include + +#include +#include + + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) + , m_selectedTe(nullptr) + , m_currentNewTopicEditor(nullptr) + , m_currentEditTopicEditor(nullptr) +{ + ui->setupUi(this); + + ui->masterSplitter->setStretchFactor(ui->masterSplitter->indexOf(ui->masterSplitter), 20); + ui->masterSplitter->setStretchFactor(ui->masterSplitter->indexOf(ui->toolBox), 0); + + ui->detailSplitter->setStretchFactor(ui->masterSplitter->indexOf(ui->scriptTimeline), 20); + ui->detailSplitter->setStretchFactor(ui->masterSplitter->indexOf(ui->detailLayout), 0); + + + connect(ui->scriptTimeline, &LtsScriptTimeline::ScriptTimeline::signalTrackEntrySelectionChanged, this, &MainWindow::slotTrackEntrySelectionChanged); + + connect(ui->pbTopicAddAudio, &QPushButton::clicked, + this, [this]() + { + LtsScriptTimeline::TrackEntry *te = new LtsScriptTimeline::TrackEntry(LtsScriptTimeline::TrackEntryType::Audio); + te->time = -1; + te->length = 30; + te->topicId = 3; + + if (m_currentNewTopicEditor) + delete m_currentNewTopicEditor; + + LtsScriptTimeline::AudioTopicEditor *ate = new LtsScriptTimeline::AudioTopicEditor(te); + connect(ate, &LtsScriptTimeline::AudioTopicEditor::destroyed, this, [this]() { m_currentNewTopicEditor = nullptr; }); + m_currentNewTopicEditor = ate; + ui->newTopicTab->layout()->addWidget(ate); + ui->detailLayout->setCurrentWidget(ui->newTopicTab); + }); + + + ui->scriptTimeline->addTrack(ui->scriptTimeline->newTrackName()); + + + + + qCritical() << "THIS EXAMPLE NO LONGER WORKS!"; + + + +} + + +void MainWindow::disconnectTrackEntry() +{ + if (!m_selectedTe) + return; + + m_selectedTe = nullptr; + + if (m_currentEditTopicEditor) + { + + delete m_currentEditTopicEditor; + m_currentEditTopicEditor = nullptr; + ui->lblNoTopicSelected->setVisible(true); + } + + ui->lblNoTopicSelected->setVisible(true); +} + + +void MainWindow::slotTrackEntrySelectionChanged(LtsScriptTimeline::TrackEntry *te) +{ + qDeleteAll(ui->topicEditorTab->layout()->children()); + disconnectTrackEntry(); + + if (!te) + return; + + ui->lblNoTopicSelected->setVisible(false); + + m_selectedTe = te; + + + + + LtsScriptTimeline::AudioTopicEditor *ate = new LtsScriptTimeline::AudioTopicEditor(te); + connect(ate, &LtsScriptTimeline::AudioTopicEditor::destroyed, + this, [this]() { m_currentEditTopicEditor = nullptr; }); + m_currentEditTopicEditor = ate; + ui->topicEditorTab->layout()->addWidget(ate); +} + + +MainWindow::~MainWindow() +{ + delete ui; +} diff --git a/Lts/Common/LtsScriptTimeline/Test/mainwindow.h b/Lts/Common/LtsScriptTimeline/Test/mainwindow.h new file mode 100755 index 000000000..60c094c8a --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/Test/mainwindow.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +namespace Ui +{ +class MainWindow; +} + +namespace LtsScriptTimeline +{ +class TrackEntry; +} + + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + +private: + Ui::MainWindow *ui; + LtsScriptTimeline::TrackEntry *m_selectedTe; + QWidget *m_currentNewTopicEditor; + QWidget *m_currentEditTopicEditor; + + void disconnectTrackEntry(); + +private slots: + void slotTrackEntrySelectionChanged(LtsScriptTimeline::TrackEntry *te); +}; diff --git a/Lts/Common/LtsScriptTimeline/Test/mainwindow.ui b/Lts/Common/LtsScriptTimeline/Test/mainwindow.ui new file mode 100755 index 000000000..7057f2935 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/Test/mainwindow.ui @@ -0,0 +1,564 @@ + + + MainWindow + + + + 0 + 0 + 1084 + 834 + + + + MainWindow + + + + + + + Qt::Horizontal + + + + Qt::Vertical + + + + + 0 + 0 + + + + + + 1 + + + + New Topic + + + + + + Topic Editor + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + No Topic selected. + + + + + + + + Layout Editor + + + + + + + + + + + 0 + 0 + 0 + + + + + + + 170 + 0 + 0 + + + + + + + 255 + 0 + 0 + + + + + + + 212 + 0 + 0 + + + + + + + 85 + 0 + 0 + + + + + + + 113 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 170 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 212 + 127 + 127 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + + 0 + 0 + 0 + + + + + + + 170 + 0 + 0 + + + + + + + 255 + 0 + 0 + + + + + + + 212 + 0 + 0 + + + + + + + 85 + 0 + 0 + + + + + + + 113 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 170 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 212 + 127 + 127 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + + 85 + 0 + 0 + + + + + + + 170 + 0 + 0 + + + + + + + 255 + 0 + 0 + + + + + + + 212 + 0 + 0 + + + + + + + 85 + 0 + 0 + + + + + + + 113 + 0 + 0 + + + + + + + 85 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 85 + 0 + 0 + + + + + + + 170 + 0 + 0 + + + + + + + 170 + 0 + 0 + + + + + + + 0 + 0 + 0 + + + + + + + 170 + 0 + 0 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + + + + + + + + + 0 + 0 + + + + 0 + + + + + 0 + 0 + 453 + 782 + + + + Add Topics + + + + + + Add Audio Topic + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + LtsScriptTimeline::ScriptTimeline + QWidget +
LtsScriptTimeline/script_timeline.h
+ 1 +
+
+ + +
diff --git a/Lts/Common/LtsScriptTimeline/Ui/filter_track_widget.ui b/Lts/Common/LtsScriptTimeline/Ui/filter_track_widget.ui new file mode 100755 index 000000000..ba98d128d --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/Ui/filter_track_widget.ui @@ -0,0 +1,126 @@ + + + FilterTrackWidget + + + + 0 + 0 + 828 + 43 + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + + + Filters + + + + + + + + + + true + + + + + + + + 0 + 0 + + + + Delete + + + + + + + + + + + 0 + 0 + + + + + 0 + 40 + + + + + 16777215 + 40 + + + + + + + + + LtsScriptTimeline::FilterBackgroundWidget + QWidget +
filter_background_widget.h
+ 1 +
+
+ + +
diff --git a/Lts/Common/LtsScriptTimeline/Ui/indicator.ui b/Lts/Common/LtsScriptTimeline/Ui/indicator.ui new file mode 100755 index 000000000..f614f31f8 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/Ui/indicator.ui @@ -0,0 +1,63 @@ + + + Indicator + + + + 0 + 0 + 822 + 88 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + + + + Tracks + + + + + + + + + + + + + LtsScriptTimeline::TrackHeader + QWidget +
track_header.h
+ 1 +
+
+ + +
diff --git a/Lts/Common/LtsScriptTimeline/Ui/script_timeline.ui b/Lts/Common/LtsScriptTimeline/Ui/script_timeline.ui new file mode 100755 index 000000000..3aef25760 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/Ui/script_timeline.ui @@ -0,0 +1,205 @@ + + + ScriptTimeline + + + + 0 + 0 + 570 + 397 + + + + + 0 + 0 + + + + Form + + + + 8 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + true + + + + + 0 + 0 + 568 + 370 + + + + + + + 0 + + + + + + 0 + 50 + + + + + + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + 0 + 0 + + + + P + + + + + + + + 0 + 0 + + + + 1 + + + 6 + + + 1 + + + 1 + + + Qt::Horizontal + + + true + + + false + + + true + + + + + + + + 0 + 0 + + + + L + + + true + + + false + + + + + + + Qt::Vertical + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + true + + + + + + + + + + LtsScriptTimeline::Indicator + QWidget +
indicator.h
+ 1 +
+
+ + +
diff --git a/Lts/Common/LtsScriptTimeline/Ui/track_entry_preview_widget.ui b/Lts/Common/LtsScriptTimeline/Ui/track_entry_preview_widget.ui new file mode 100755 index 000000000..88eb706df --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/Ui/track_entry_preview_widget.ui @@ -0,0 +1,99 @@ + + + TrackEntryPreviewWidget + + + + 0 + 0 + 296 + 78 + + + + + 0 + 0 + + + + Form + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + Drag & Drop: + + + + + + + [Topic id:] + + + + + + + + 200 + 50 + + + + + + + + ? + + + + + + + [Topic length:] + + + + + + + ? + + + + + + + + + + LtsScriptTimeline::TrackEntryPreviewer + QWidget +
LtsScriptTimeline/track_entry_previewer.h
+ 1 +
+
+ + +
diff --git a/Lts/Common/LtsScriptTimeline/Ui/track_widget.ui b/Lts/Common/LtsScriptTimeline/Ui/track_widget.ui new file mode 100755 index 000000000..d00bd2b5e --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/Ui/track_widget.ui @@ -0,0 +1,245 @@ + + + TrackWidget + + + + 0 + 0 + 925 + 120 + + + + + 0 + 0 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 300 + 0 + + + + + + + + + + + + + + Name of Track: + + + + + + + 128 + + + + + + + + 0 + 0 + + + + Edit + + + + + + + + 0 + 0 + + + + Apply + + + + + + + + 0 + 0 + + + + Cancel + + + + + + + + 0 + 0 + + + + More + + + + + + + + 0 + 0 + + + + Add filter track + + + + + + + + + + + + + + + 0 + 0 + + + + + 0 + 40 + + + + + 16777215 + 40 + + + + + + + + + + + + 0 + 0 + + + + + 0 + 40 + + + + + 16777215 + 16777215 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + LtsScriptTimeline::TrackContentWidget + QWidget +
track_content_widget.h
+ 1 +
+
+ + +
diff --git a/Lts/Common/LtsScriptTimeline/filter_background_widget.cpp b/Lts/Common/LtsScriptTimeline/filter_background_widget.cpp new file mode 100755 index 000000000..988a14c3e --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/filter_background_widget.cpp @@ -0,0 +1,190 @@ +#include "filter_background_widget.h" +#include "track_content_widget.h" +#include "tools.h" +#include "track_entry.h" +#include "script_timeline.h" + +#include + + +namespace LtsScriptTimeline +{ + +FilterBackgroundWidget::FilterBackgroundWidget(QWidget *parent) + : QWidget(parent) + , m_readonly(false) + , m_scriptTimeline(nullptr) + , m_tools(nullptr) + , m_trackWidget(nullptr) +{ + +} + + +void FilterBackgroundWidget::setReadonly(bool readonly) +{ + m_readonly = readonly; + + for (auto i = m_filterContentWidgets.begin(); i != m_filterContentWidgets.end(); ++i) + { + TrackContentWidget *tcw = *i; + tcw->setReadonly(readonly); + } +} + + +void FilterBackgroundWidget::paintEvent(QPaintEvent *event) +{ + (void)event; + + QPainter painter(this); + + painter.save(); + + painter.setBrush(QColor("lightgray")); + painter.drawRect(rect()); + + painter.restore(); +} + + +void FilterBackgroundWidget::setTools(Tools *tools) +{ + m_tools = tools; +} + + +void FilterBackgroundWidget::adjustFilterContentWidgets(const TrackEntry *te) +{ + TrackContentWidget *tcw = m_filterContentWidgets.value(te->m_topicId, nullptr); + + if (tcw == nullptr) + { + trackEntryAdded(te); + return; + } + + QRect currentGeo = tcw->geometry(); + int currentX = currentGeo.x(); + int currentWidth = currentGeo.width(); + + + + int x = m_tools->getPos(te->m_startTime); + int width = m_tools->getLength(te->m_startTime, te->m_length); + + if (currentX != x || currentWidth != width) + { + + tcw->setGeometry(x, currentGeo.y(), width, currentGeo.height()); + + } + else + { + + } +} + + +void FilterBackgroundWidget::slotChangesMade() +{ + QObject *tcwObj = sender(); + TrackContentWidget *tcw = qobject_cast(tcwObj); + + if (tcw == nullptr) + { + return; + } + + int topicId = m_filterContentWidgets.key(tcw, -1); + + if (topicId == -1) + { + return; + } + + emit signalChangesMade(topicId); +} + + +void FilterBackgroundWidget::trackEntryDeleted(const int topicId) +{ + auto fcwI = m_filterContentWidgets.find(topicId); + + if (fcwI != m_filterContentWidgets.end()) + { + TrackContentWidget *tcw = *fcwI; + m_filterContentWidgets.erase(fcwI); + tcw->deleteLater(); + } +} + + +void FilterBackgroundWidget::addFilterEntry(TrackEntry *main, TrackEntry *filter) +{ + auto fcwI = m_filterContentWidgets.find(main->m_topicId); + + if (fcwI != m_filterContentWidgets.end()) + { + TrackContentWidget *tcw = *fcwI; + tcw->addTrackEntry(filter, false); + emit tcw->signalChangesMade(); + } +} + + +void FilterBackgroundWidget::trackEntryAdded(const TrackEntry *te) +{ + auto fcwI = m_filterContentWidgets.find(te->m_topicId); + + if (fcwI == m_filterContentWidgets.end()) + { + TrackContentWidget *tcw = new TrackContentWidget(this, true, m_tools, te->m_topicId); + + tcw->setTrackWidget(m_trackWidget); + tcw->setReadonly(m_readonly); + tcw->setScriptTimeline(m_scriptTimeline); + tcw->setGeometry(-1, 0, 0, height()); + tcw->show(); + + connect(tcw, &TrackContentWidget::signalChangesMade, + this, &FilterBackgroundWidget::slotChangesMade); + connect(tcw, &TrackContentWidget::signalUnselectAll, + this, &FilterBackgroundWidget::signalUnselectAll); + connect(tcw, &TrackContentWidget::signalTrackEntrySelectionChanged, + this, &FilterBackgroundWidget::signalTrackEntrySelectionChanged); + connect(tcw, &TrackContentWidget::signalTrackEntryDeleted, + this, &FilterBackgroundWidget::signalTrackEntryDeleted); + connect(tcw, &TrackContentWidget::signalEditEntry, + this, &FilterBackgroundWidget::signalEditEntry); + + connect(tcw, &TrackContentWidget::signalStartRubberband, + m_scriptTimeline, &ScriptTimeline::slotStartRubberband); + connect(tcw, &TrackContentWidget::signalModifyRubberband, + m_scriptTimeline, &ScriptTimeline::slotModifyRubberband); + connect(tcw, &TrackContentWidget::signalEscapeRubberband, + m_scriptTimeline, &ScriptTimeline::slotEscapeRubberband); + connect(tcw, &TrackContentWidget::signalStopRubberband, + m_scriptTimeline, &ScriptTimeline::slotStopRubberband); + connect(tcw, &TrackContentWidget::signalSelectAllGlobal, + m_scriptTimeline, &ScriptTimeline::selectAll); + connect(tcw, &TrackContentWidget::signalCopyTopics, + m_scriptTimeline, &ScriptTimeline::slotCopyTopics); + connect(tcw, &TrackContentWidget::signalPasteTopics, + m_scriptTimeline, &ScriptTimeline::slotPasteTopics); + connect(tcw, &TrackContentWidget::signalCutTopics, + m_scriptTimeline, &ScriptTimeline::slotCutTopics); + connect(tcw, &TrackContentWidget::signalDeleteSelectedTopics, + m_scriptTimeline, &ScriptTimeline::slotDeleteSelectedTopics); + connect(tcw, &TrackContentWidget::signalSave, + m_scriptTimeline, &ScriptTimeline::signalSave); + connect(tcw, &TrackContentWidget::signalMoveAll, + m_scriptTimeline, &ScriptTimeline::slotMoveAll); + + m_filterContentWidgets.insert(te->m_topicId, tcw); + } + + adjustFilterContentWidgets(te); +} + +} diff --git a/Lts/Common/LtsScriptTimeline/filter_background_widget.h b/Lts/Common/LtsScriptTimeline/filter_background_widget.h new file mode 100755 index 000000000..ecb45321a --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/filter_background_widget.h @@ -0,0 +1,53 @@ +#pragma once + +#include "track_content_widget.h" + +#include +#include + + +namespace LtsScriptTimeline +{ +class TrackEntry; +class ScriptTimeline; +class Tools; + + +class FilterBackgroundWidget : public QWidget +{ + Q_OBJECT + +public: + explicit FilterBackgroundWidget(QWidget *parent = nullptr); + const QMap &filterContentWidgets() const { return m_filterContentWidgets; } + void adjustFilterContentWidgets(const TrackEntry *te); + void trackEntryAdded(const TrackEntry *te); + void trackEntryDeleted(const int topicId); + void addFilterEntry(TrackEntry *main, TrackEntry *filter); + void setReadonly(bool readonly); + void setScriptTimeline(ScriptTimeline *scriptTimeline) { m_scriptTimeline = scriptTimeline; } + void setTools(Tools *tools); + void setTrackWidget(TrackWidget *trackWidget) { m_trackWidget = trackWidget; } + +signals: + void signalChangesMade(int topicId); + void signalUnselectAll(); + void signalTrackEntrySelectionChanged(TrackEntry *te); + void signalTrackEntryDeleted(const int topicId); + void signalEditEntry(const int topicId); + +private slots: + void slotChangesMade(); + +private: + QMap m_filterContentWidgets; + bool m_readonly; + ScriptTimeline *m_scriptTimeline; + Tools *m_tools; + TrackWidget *m_trackWidget; + +protected: + void paintEvent(QPaintEvent *event); +}; + +} diff --git a/Lts/Common/LtsScriptTimeline/filter_track_widget.cpp b/Lts/Common/LtsScriptTimeline/filter_track_widget.cpp new file mode 100755 index 000000000..df74bf358 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/filter_track_widget.cpp @@ -0,0 +1,155 @@ +#include "filter_track_widget.h" +#include "ui_filter_track_widget.h" +#include "track_entry.h" +#include "script_timeline.h" + +#include +#include +#include +#include + + +namespace LtsScriptTimeline +{ + +FilterTrackWidget::FilterTrackWidget(int baseWidth, bool readonly, QWidget *parent) + : QWidget(parent) + , ui(new Ui::FilterTrackWidget) + , m_baseWidth(baseWidth) + , m_readonly(readonly) + , m_tools(nullptr) + , m_trackWidget(nullptr) +{ + ui->setupUi(this); + ui->cbSelect->hide(); + + setReadonly(readonly); + setBaseWidth(baseWidth); + + connect(ui->pbDelete, &QPushButton::clicked, + this, &FilterTrackWidget::signalDeleteMe); + + connect(ui->fbw, &FilterBackgroundWidget::signalChangesMade, + this, &FilterTrackWidget::signalChangesMade); + connect(ui->fbw, &FilterBackgroundWidget::signalUnselectAll, + this, &FilterTrackWidget::signalUnselectAll); + connect(ui->fbw, &FilterBackgroundWidget::signalTrackEntrySelectionChanged, + this, &FilterTrackWidget::signalTrackEntrySelectionChanged); + connect(ui->fbw, &FilterBackgroundWidget::signalEditEntry, + this, &FilterTrackWidget::signalEditEntry); + connect(ui->fbw, &FilterBackgroundWidget::signalTrackEntryDeleted, + this, &FilterTrackWidget::signalTrackEntryDeleted); +} + + +FilterTrackWidget::~FilterTrackWidget() +{ + + delete ui; + +} + + +void FilterTrackWidget::setTrackWidget(TrackWidget *trackWidget) +{ + m_trackWidget = trackWidget; + ui->fbw->setTrackWidget(trackWidget); +} + + +void FilterTrackWidget::setReadonly(bool readonly) +{ + m_readonly = readonly; + ui->pbDelete->setVisible(!readonly); + ui->fbw->setReadonly(readonly); +} + + +void FilterTrackWidget::setTools(Tools *tools) +{ + m_tools = tools; + ui->fbw->setTools(tools); +} + + +void FilterTrackWidget::setScriptTimeline(ScriptTimeline *scriptTimeline) +{ + m_scriptTimeline = scriptTimeline; + ui->fbw->setScriptTimeline(m_scriptTimeline); +} + + +FilterBackgroundWidget *FilterTrackWidget::fbw() +{ + return ui->fbw; +} + +const FilterBackgroundWidget *FilterTrackWidget::fbw() const +{ + return ui->fbw; +} + + + + + + + + +void FilterTrackWidget::setBaseWidth(int width) +{ + ui->left->setMinimumWidth(width); + ui->left->setMaximumWidth(width); +} + + +void FilterTrackWidget::addFilterEntry(TrackEntry *main, TrackEntry *filter) +{ + filter->m_topicId = m_scriptTimeline->nextUniqueId(); + ui->fbw->addFilterEntry(main, filter); +} + + +bool FilterTrackWidget::isSelected() const +{ + return (ui->cbSelect->checkState() == Qt::CheckState::Checked); +} + + +void FilterTrackWidget::unselectAll() +{ + +} + + +void FilterTrackWidget::updateMe() +{ + update(); + +} + + + + + + + + +void FilterTrackWidget::paintEvent(QPaintEvent *event) +{ + + + + + + + + + + + + + QWidget::paintEvent(event); +} + +} diff --git a/Lts/Common/LtsScriptTimeline/filter_track_widget.h b/Lts/Common/LtsScriptTimeline/filter_track_widget.h new file mode 100755 index 000000000..9209d9329 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/filter_track_widget.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include "track_entry_painters.h" + +#include + + +namespace Ui +{ +class FilterTrackWidget; +} + + +namespace LtsScriptTimeline +{ +class TrackEntry; +class TrackContentWidget; +class FilterBackgroundWidget; +class ScriptTimeline; +class Tools; +class TrackWidget; + + +class LTSSCRIPTTIMELINE_EXPORT FilterTrackWidget : public QWidget +{ + Q_OBJECT + +public: + explicit FilterTrackWidget(int baseWidth, bool readonly = false, QWidget *parent = nullptr); + ~FilterTrackWidget(); + + FilterBackgroundWidget *fbw(); + const FilterBackgroundWidget *fbw() const; + + void setReadonly(bool readonly); + void updateMe(); + void unselectAll(); + bool isSelected() const; + QString trackName() const; + void addFilterEntry(TrackEntry *main, TrackEntry *filter); + void setTrackName(const QString &name); + + void setBaseWidth(int width); + + + void setScriptTimeline(ScriptTimeline *scriptTimeline); + void setTools(Tools *tools); + void setTrackWidget(TrackWidget *trackWidget); + +private: + Ui::FilterTrackWidget *ui; + int m_baseWidth; + bool m_readonly; + ScriptTimeline *m_scriptTimeline; + Tools *m_tools; + TrackWidget *m_trackWidget; + +protected: + void paintEvent(QPaintEvent *event); + +signals: + void signalUnselectAll(); + void signalSelectTrack(); + void signalTrackEntrySelectionChanged(TrackEntry *te); + void signalChangesMade(int topicId); + void signalDeleteMe(); + void signalEditEntry(const int topicId); + void signalTrackEntryDeleted(const int topicId); + +}; + +} diff --git a/Lts/Common/LtsScriptTimeline/indicator.cpp b/Lts/Common/LtsScriptTimeline/indicator.cpp new file mode 100755 index 000000000..cb4e42ff6 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/indicator.cpp @@ -0,0 +1,82 @@ +#include "indicator.h" +#include "ui_indicator.h" +#include + +namespace LtsScriptTimeline +{ + +Indicator::Indicator(QWidget *parent) + : QWidget(parent) + , ui(new Ui::Indicator) +{ + ui->setupUi(this); + + connect(ui->splitter, &QSplitter::splitterMoved, + this, &Indicator::slotSplitterMoved); + + connect(ui->m_widgetTrackHeader, &TrackHeader::signalHeaderIndicatorTimeChanged, + this, &Indicator::signalHeaderIndicatorTimeChanged); + connect(ui->m_widgetTrackHeader, &TrackHeader::signalHeaderIndicatorTimeUserChanged, + this, &Indicator::signalHeaderIndicatorTimeUserChanged); + + QList sizes; + sizes << 100 << 900; + ui->splitter->setSizes(sizes); + + QTimer::singleShot(500, this, [this]() + { + slotSplitterMoved(); + emit signalHeaderIndicatorTimeUserChanged(ui->m_widgetTrackHeader->indicatorTime()); + ui->m_widgetTrackHeader->recreatePositionLabels(); + }); +} + + +Indicator::~Indicator() +{ + delete ui; +} + + +void Indicator::setTools(Tools *tools) +{ + ui->m_widgetTrackHeader->setTools(tools); +} + + +void Indicator::lockIndicator(bool lock) +{ + ui->m_widgetTrackHeader->lockIndicator(lock); +} + + +void Indicator::setIndicatorTime(int time) +{ + ui->m_widgetTrackHeader->setIndicatorTime(time); +} + + +void Indicator::updateMe() +{ + ui->m_widgetTrackHeader->updateMe(); + update(); +} + + +void Indicator::resizeEvent(QResizeEvent *) +{ + slotSplitterMoved(); +} + + +void Indicator::slotSplitterMoved() +{ + int newSize = ui->m_widgetTrackLabel->width() + ui->splitter->handleWidth(); + + if (newSize < 0) + newSize = 0; + + emit signalHandleResized(newSize); +} + +} diff --git a/Lts/Common/LtsScriptTimeline/indicator.h b/Lts/Common/LtsScriptTimeline/indicator.h new file mode 100755 index 000000000..2b9201a53 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/indicator.h @@ -0,0 +1,41 @@ +#pragma once + +#include + +namespace Ui +{ +class Indicator; +} + +namespace LtsScriptTimeline +{ +class Tools; + +class Indicator : public QWidget +{ + Q_OBJECT + +public: + explicit Indicator(QWidget *parent = nullptr); + ~Indicator(); + + void updateMe(); + void setIndicatorTime(int time); + void lockIndicator(bool lock = true); + void setTools(Tools *tools); + +private: + void resizeEvent(QResizeEvent *event); + + Ui::Indicator *ui; + +signals: + void signalHandleResized(int width); + void signalHeaderIndicatorTimeChanged(int time); + void signalHeaderIndicatorTimeUserChanged(int time); + +private slots: + void slotSplitterMoved(); +}; + +} diff --git a/Lts/Common/LtsScriptTimeline/newsrc.tar.xz b/Lts/Common/LtsScriptTimeline/newsrc.tar.xz new file mode 100755 index 000000000..25e76c824 Binary files /dev/null and b/Lts/Common/LtsScriptTimeline/newsrc.tar.xz differ diff --git a/Lts/Common/LtsScriptTimeline/script_timeline.cpp b/Lts/Common/LtsScriptTimeline/script_timeline.cpp new file mode 100755 index 000000000..52da98b12 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/script_timeline.cpp @@ -0,0 +1,1202 @@ +#include "script_timeline.h" +#include "ui_script_timeline.h" +#include "track_widget.h" +#include "tools.h" +#include "track_entry.h" +#include "track_content_widget.h" +#include "filter_track_widget.h" +#include "filter_background_widget.h" +#include "temporary_pointer_vector.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include + + +namespace LtsScriptTimeline +{ + +ScriptTimeline::ScriptTimeline(QWidget *parent) + : QWidget(parent) + , ui(new Ui::ScriptTimeline) + , m_uniqueId(0) + , m_scriptId(-1) + , m_baseTrackWidgetWidth(150) + , m_readonly(false) + , m_time(0) + , m_tools(new Tools(this)) + , m_mlmsMaster(nullptr) + , m_ignoreChanges(false) +{ + ui->setupUi(this); + + this->setFocusPolicy(Qt::StrongFocus); + + ui->indicator->setTools(m_tools); + + + ui->trackScrollBar->setMinimum(0); + ui->trackScrollBar->setMaximum(5 * 60); + + connect(ui->zoomSlider, &QSlider::valueChanged, this, &ScriptTimeline::slotZoomValueChanged); + + connect(ui->trackScrollBar, &QScrollBar::sliderReleased, this, &ScriptTimeline::slotScrollbarValueChanged); + connect(ui->trackScrollBar, &QScrollBar::actionTriggered, this, &ScriptTimeline::slotScrollbarValueChanged); + + connect(ui->indicator, &Indicator::signalHandleResized, + this, &ScriptTimeline::slotHeaderHandleResized); + connect(ui->indicator, &Indicator::signalHeaderIndicatorTimeChanged, + this, &ScriptTimeline::slotHeaderIndicatorTimeChanged); + connect(ui->indicator, &Indicator::signalHeaderIndicatorTimeUserChanged, + this, &ScriptTimeline::signalHeaderIndicatorTimeUserChanged); + + + QTimer *t = new QTimer(this); + t->setInterval(1500); + connect(t, &QTimer::timeout, + this, &ScriptTimeline::slotSetNewScrollBarMaximum); + t->start(); + + + + + slotZoomValueChanged(ui->zoomSlider->value()); +} + + +ScriptTimeline::~ScriptTimeline() +{ + m_copyTopics.clear(); + m_ignoreChanges = true; + qDeleteAll(m_clipboard); + delete ui; +} + + +void ScriptTimeline::selectAll() +{ + const QList vecTrackWidgets = getTrackWidgets(); + for (TrackWidget *tw : vecTrackWidgets) + { + TrackContentWidget *tcw = tw->getTrackContentWidget(); + tcw->selectAll(); + } +} + + +bool ScriptTimeline::checkNewLength(TrackEntry *te, int newLength) +{ + const QList tws = getTrackWidgets(); + TrackContentWidget *tesTcw = nullptr; + + + for (TrackWidget *tw : tws) + { + const QVector &tes = tw->getTrackContentWidget()->getTrackEntries(); + + auto it = std::find_if(tes.begin(), tes.end(), [te](const TrackEntry * a) + { + return (a->m_topicId == te->m_topicId); + }); + + if (it == tes.end()) + continue; + + tesTcw = tw->getTrackContentWidget(); + break; + } + + if (tesTcw == nullptr) + return false; + + + TrackEntry newTe(te); + newTe.m_length = newLength; + return tesTcw->checkForCollisionsSimple(newTe); +} + + +void ScriptTimeline::lockIndicator(bool lock) +{ + ui->indicator->lockIndicator(lock); +} + + +void ScriptTimeline::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Delete) + { + slotDeleteSelectedTopics(); + } + else if (QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && event->key() == Qt::Key_C) + { + slotCopyTopics(); + } + else if (QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && event->key() == Qt::Key_V) + { + slotPasteTopics(); + } + else if (QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && event->key() == Qt::Key_X) + { + slotCutTopics(); + } + else if (QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && event->key() == Qt::Key_A) + { + selectAll(); + } + else if (QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && event->key() == Qt::Key_S) + { + emit signalSave(); + } + else + { + const QList vecTrackWidgets = getTrackWidgets(); + for (auto w : vecTrackWidgets) + w->keyPressEvent(event); + } +} + + +void ScriptTimeline::mousePressEvent(QMouseEvent *event) +{ + if (event->buttons() != Qt::LeftButton) + return; + slotStartRubberband(event->pos()); +} + + +void ScriptTimeline::mouseMoveEvent(QMouseEvent *) +{ + slotModifyRubberband(); +} + + +void ScriptTimeline::mouseReleaseEvent(QMouseEvent *) +{ + const QList vecTrackWidgets = getTrackWidgets(); + for (auto w : vecTrackWidgets) + w->unselectAll(); + + slotStopRubberband(); +} + + +void ScriptTimeline::slotScroll(int incr) +{ + int oldValue = ui->trackScrollBar->value(); + ui->trackScrollBar->setValue(oldValue + incr); + slotScrollbarValueChanged(); +} + + +void ScriptTimeline::setReadonly(bool readonly) +{ + m_readonly = readonly; + + const QList vecTrackWidgets = getTrackWidgets(); + for (TrackWidget *tw : vecTrackWidgets) + tw->setReadonly(readonly); +} + + +void ScriptTimeline::clear() +{ + m_copyTopics.clear(); + + const QList trackWidgetList = getTrackWidgets(); + + + for (TrackWidget *tw : trackWidgetList) + delete tw; + + qDeleteAll(m_clipboard); + m_clipboard.clear(); + + m_uniqueId = 0; + + updateEverything(); +} + + +void ScriptTimeline::slotSetNewScrollBarMaximum() +{ + int max = 5 * 60; + + const QList trackWidgetList = getTrackWidgets(); + for (const TrackWidget *tw : trackWidgetList) + { + const QVector &trackEntries = tw->getTrackContentWidget()->getTrackEntries(); + + for (const TrackEntry *te : trackEntries) + { + int maxTeTime = te->m_startTime + te->m_length; + + + if (maxTeTime > max) + { + max = maxTeTime + 5 * 60; + } + } + } + + + ui->trackScrollBar->setMaximum(max); + ui->trackScrollBar->setPageStep(60); + m_tools->m_timelineLength = max; + + updateEverything(); +} + + +void ScriptTimeline::setIndicatorTime(int time) +{ + ui->indicator->setIndicatorTime(time); +} + + +int ScriptTimeline::getCurrentScriptEndTime() +{ + int currEndTime = 0; + const QList listTrackWidgets = getTrackWidgets(); + for (const auto ltw : listTrackWidgets) + { + const auto cont = ltw->getTrackEntries(); + for (const auto pTE : cont) + { + int endTime = pTE->m_startTime + pTE->m_length; + currEndTime = qMax(currEndTime, endTime); + } + } + + return currEndTime; +} + + +void ScriptTimeline::slotHeaderIndicatorTimeChanged(int time) +{ + m_time = time; + + + + + const QList trackWidgetList = getTrackWidgets(); + for (TrackWidget *tw : trackWidgetList) + tw->setIndicatorTime(time); + + emit signalHeaderIndicatorTimeChanged(time); +} + + +void ScriptTimeline::slotHeaderHandleResized(int newSize) +{ + m_baseTrackWidgetWidth = newSize; + + updateEverything(); + + const QList trackWidgetList = getTrackWidgets(); + for (TrackWidget *tw : trackWidgetList) + tw->setBaseWidth(newSize); +} + + +void ScriptTimeline::slotDeleteTracks() +{ + int count = 0; + + const QList trackWidgetList = getTrackWidgets(); + for (const TrackWidget *tw : trackWidgetList) + { + if (tw->isSelected()) + count++; + } + + if (count < 1) + { + QApplication::beep(); + return; + } + + if (QMessageBox::question(this, tr("Delete selected tracks"), tr("Are you sure you want to delete all %1 tracks?").arg(count), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) + return; + + for (TrackWidget *tw : trackWidgetList) + { + if (tw->isSelected()) + tw->deleteLater(); + } + + +} + + +void ScriptTimeline::slotZoomValueChanged(int value) +{ + if (value < 1) + value = 1; + + m_tools->m_zoomFactor = value; + + + + updateEverything(); +} + + + +void ScriptTimeline::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + + { + QPixmap p(QStringLiteral(":/style/portrait-mode.svg")); + int h = ui->trackScrollBar->height(); + QSize s(h, h); + ui->lbPortrait->setPixmap(p.scaled(s, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + ui->lbPortrait->setFixedSize(s); + } + { + QPixmap p(QStringLiteral(":/style/landscape-mode.svg")); + int h = ui->trackScrollBar->height(); + QSize s(h, h); + ui->lbLandscape->setPixmap(p.scaled(s, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + ui->lbLandscape->setFixedSize(s); + } + + updateEverything(); +} + + +void ScriptTimeline::slotUnselectAll() +{ + const QList trackWidgetList = getTrackWidgets(); + for (TrackWidget *tw : trackWidgetList) + tw->unselectAll(); +} + + +int ScriptTimeline::getFilterTrackEntryRealTime(TrackEntry *te) +{ + const QList trackWidgetList = getTrackWidgets(); + for (TrackWidget *tw : trackWidgetList) + { + for (FilterTrackWidget *ftw : tw->filterLines()) + { + const QMap &filterContentWidgets = ftw->fbw()->filterContentWidgets(); + + int foundMainTrackEntryId = -1; + for (auto itFcw = filterContentWidgets.begin(); itFcw != filterContentWidgets.end(); ++itFcw) + { + const TrackContentWidget *tcw = itFcw.value(); + const QVector &trackEntries = tcw->getTrackEntries(); + + for (const TrackEntry *tempFilterTe : trackEntries) + { + if (tempFilterTe->m_topicId == te->m_topicId) + { + foundMainTrackEntryId = itFcw.key(); + break; + } + } + if (foundMainTrackEntryId != -1) + break; + } + + if (foundMainTrackEntryId != -1) + { + const QVector &trackEntries = tw->getTrackContentWidget()->getTrackEntries(); + for (const TrackEntry *mainTe : trackEntries) + { + if (mainTe->m_topicId == foundMainTrackEntryId) + return mainTe->m_startTime + te->m_startTime; + } + } + } + } + + return -1; +} + + +QList ScriptTimeline::getTrackWidgets() const +{ + return ui->trackScrollArea->findChildren(); +} + + +TrackEntry *ScriptTimeline::getTrackEntryWithFiltersAsEntries(int topicId) +{ + const QList trackWidgetList = getTrackWidgets(); + for (const TrackWidget *tw : trackWidgetList) + { + const QVector trackEntries = tw->getTrackEntriesFiltersAsEntries(); + for (TrackEntry *te : qAsConst(trackEntries)) + { + if (te->m_topicId == topicId) + return te; + } + } + + return nullptr; +} + + +TrackEntry *ScriptTimeline::getTrackEntry(int topicId) +{ + const QList trackWidgetList = getTrackWidgets(); + for (const TrackWidget *tw : trackWidgetList) + { + const QVector trackEntries = tw->getTrackEntries(); + for (TrackEntry *te : trackEntries) + { + if (te->m_topicId == topicId) + return te; + } + } + + return nullptr; +} + + +QVector ScriptTimeline::getTrackEntriesWithFiltersAsEntries(bool selectedEntriesOnly) const +{ + QVector tes; + const QList trackWidgetList = getTrackWidgets(); + for (TrackWidget *tw : trackWidgetList) + tes += tw->getTrackEntriesFiltersAsEntries(selectedEntriesOnly); + + return tes; +} + + +QMap > ScriptTimeline::getTrackEntriesWithFilters(bool selectedEntriesOnly) const +{ + QMap > trackList; + const QList trackWidgetList = getTrackWidgets(); + for (const TrackWidget *tw : trackWidgetList) + { + QString trackName(tw->trackName()); + const QVector trackEntries = tw->getTrackEntriesWithFilters(selectedEntriesOnly); + + trackList.insert(trackName, trackEntries); + } + + return trackList; +} + + +QVector ScriptTimeline::getTrackEntries(bool selectedEntriesOnly) const +{ + QVector tes; + const QList trackWidgetList = getTrackWidgets(); + for (const TrackWidget *tw : trackWidgetList) + tes += tw->getTrackEntries(selectedEntriesOnly); + + return tes; +} + + +bool ScriptTimeline::importTrackEntries(const QVector &importTes) +{ + + int importLength = 0; + for (TrackEntry *te : qAsConst(importTes)) + { + int teLen = te->m_startTime + te->m_length; + if (teLen > importLength) + importLength = teLen; + } + + + + const QVector tes = getTrackEntries(); + int pasteTime = m_time; + + for (TrackEntry *te : qAsConst(tes)) + { + int start = te->m_startTime; + int sum = start + te->m_length; + + if (start < m_time + && sum > m_time + && pasteTime < sum) + { + pasteTime = sum; + } + } + + int diff = pasteTime - m_time; + + + + for (TrackEntry *te : qAsConst(tes)) + { + if (te->m_startTime >= m_time) + te->m_startTime += importLength + diff; + } + + + + QVector finalImportTes(importTes); + + for (TrackEntry *te : finalImportTes) + { + + te->m_startTime += pasteTime; + + + if (!addTopic(te)) + { + qCritical() << "could not add te #" << te->m_topicId; + delete te; + } + } + + + + m_time = pasteTime; + setIndicatorTime(m_time); + + + return true; +} + + +bool ScriptTimeline::addTopic(TrackEntry *te) +{ + if (te->m_topicId == 0) + te->m_topicId = nextUniqueId(); + + if (te->m_channelName.isEmpty()) + te->m_channelName = tr("Unnamed Track"); + + te->isSet = true; + + + const QList trackWidgetList = getTrackWidgets(); + for (TrackWidget *tw : trackWidgetList) + { + if (tw->trackName() == te->m_channelName) + { + m_ignoreChanges = true; + bool result = tw->addTrackEntry(te, false); + m_ignoreChanges = false; + + if (!result) + return false; + + tw->updateMe(); + return true; + } + } + + + return false; +} + + +QVector ScriptTimeline::getSelectedEntriesWithFilterEntries() const +{ + QVector tes; + + const QList tws = getTrackWidgets(); + + for (const TrackWidget *tw : qAsConst(tws)) + tes += tw->getTrackEntriesFiltersAsEntries(true); + + return tes; +} + + +QString ScriptTimeline::newTrackName() const +{ + QString newTrackName; + int add = 1; + const QList trackWidgetList = getTrackWidgets(); + bool newTrackNameFound = false; + while (!newTrackNameFound) + { + newTrackNameFound = true; + newTrackName = tr("New Track %1").arg(add); + for (const TrackWidget *tw : trackWidgetList) + { + if (tw->trackName() == newTrackName) + { + + add++; + newTrackNameFound = false; + break; + } + } + } + + return newTrackName; +} + + +void ScriptTimeline::updateAllTracks() +{ + const QList tws = getTrackWidgets(); + + for (TrackWidget *tw : tws) + tw->updateMe(); +} + + +QVector ScriptTimeline::getSelectedFilterEntries() const +{ + QVector result; + + const QList tws = getTrackWidgets(); + for (const TrackWidget *tw : qAsConst(tws)) + { + result += tw->getSelectedFilterEntries(); + } + + return result; +} + + +void ScriptTimeline::slotDeleteSelectedTopics() +{ + int topicCount = 0; + + const QList tws = getTrackWidgets(); + for (const TrackWidget *tw : qAsConst(tws)) + { + topicCount += tw->getSelectedFilterEntries().count(); + + TrackContentWidget *tcw = tw->getTrackContentWidget(); + topicCount += tcw->getSelectedEntries().count(); + } + + if (!topicCount) + { + QApplication::beep(); + return; + } + + connect(new LtsWidgets::LtsMessageDialogNew(this, tr("Are you sure that you want to delete %1 topics?").arg(topicCount), + QString(), LtsWidgets::Behaviour::Modal, LtsWidgets::Button::Yes | LtsWidgets::Button::No), + &LtsWidgets::LtsMessageDialogNew::yes, + this, [this]() + { + const QList tws = getTrackWidgets(); + for (const TrackWidget *tw : qAsConst(tws)) + { + + const QVector &ftws = tw->filterLines(); + + for (const FilterTrackWidget *ftw : qAsConst(ftws)) + { + const FilterBackgroundWidget *fbw = ftw->fbw(); + const QMap &fcws = fbw->filterContentWidgets(); + + for (TrackContentWidget *tcw: fcws) + tcw->deleteSelectedTopics(); + } + + TrackContentWidget *tcw = tw->getTrackContentWidget(); + tcw->deleteSelectedTopics(); + } + }); +} + + +void ScriptTimeline::slotCutTopics() +{ + slotCopyTopics(); + + const QList tws = getTrackWidgets(); + for (const TrackWidget *tw : qAsConst(tws)) + { + TrackContentWidget *tcw = tw->getTrackContentWidget(); + tcw->deleteSelectedTopics(); + } +} + + +void ScriptTimeline::slotCopyTopics() +{ + const QList tws = getTrackWidgets(); + int firstTeTime = -1; + + + + for (const TrackWidget *tw : qAsConst(tws)) + { + TrackContentWidget *tcw = tw->getTrackContentWidget(); + + const QVector &selected = tcw->getSelectedEntries(); + + if (tcw->getSelectedEntries().isEmpty()) + continue; + + for (const TrackEntry *te : qAsConst(selected)) + { + if (firstTeTime > te->m_startTime || firstTeTime == -1) + firstTeTime = te->m_startTime; + } + } + + if (firstTeTime == -1) + { + QApplication::beep(); + return; + } + + + m_copyTopics.clear(); + + + + for (TrackWidget *tw : tws) + { + TrackContentWidget *tcw = tw->getTrackContentWidget(); + + if (tcw->getSelectedEntries().isEmpty()) + continue; + + + connect(tw, &QObject::destroyed, + this, [this](QObject *obj) + { + m_copyTopics.remove(static_cast(obj)); + }); + + m_copyTopics.insert(tw, TrackContentWidget::itemsToData(tw->getTrackEntriesWithFilters(true), firstTeTime, false)); + } +} + + +void ScriptTimeline::slotPasteTopics() +{ + if (m_copyTopics.isEmpty() || m_time < 0) + { + QApplication::beep(); + return; + } + + + for (auto i = m_copyTopics.constBegin(); i != m_copyTopics.constEnd(); ++i) + { + const TrackWidget *tw = i.key(); + const QByteArray &data = i.value(); + + TrackContentWidget *tcw = tw->getTrackContentWidget(); + int dummy = 0; + const TemporaryPointerVector tempTes(TrackContentWidget::dataToItems(data, dummy)); + if (!tcw->canPasteTopics(tempTes, 0, m_time)) + { + new LtsWidgets::LtsMessageDialogNew(this, tr("Topics can't be pasted here as one or more topics intersects with another existing topic.")); + return; + } + } + + + for (auto i = m_copyTopics.constBegin(); i != m_copyTopics.constEnd(); ++i) + { + TrackWidget *tw = i.key(); + TrackContentWidget *tcw = tw->getTrackContentWidget(); + const QByteArray &data = i.value(); + + int dummy = 0; + const TemporaryPointerVector tempTes(TrackContentWidget::dataToItems(data, dummy)); + tcw->forcePasteTopics(tempTes, 0, m_time); + } +} + + +void ScriptTimeline::slotMoveAll(int direction) +{ + int factor = 1; + + if (!QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier)) + factor = 10; + + QList tws = getTrackWidgets(); + + + for (TrackWidget *tw : qAsConst(tws)) + { + TrackContentWidget *tcw = tw->getTrackContentWidget(); + if (!tcw->canMoveAll(direction * factor)) + { + QApplication::beep(); + qWarning() << "cols and/or parls @" << tcw; + return; + } + } + + + for (TrackWidget *tw : qAsConst(tws)) + { + TrackContentWidget *tcw = tw->getTrackContentWidget(); + tcw->forceMoveAll(direction * factor); + } +} + + +static inline bool containsFailedTrackContentWidget(const QHash &mlmsTCWStates) +{ + auto end = mlmsTCWStates.end(); + for (auto it = mlmsTCWStates.begin(); it != end; ++it) + { + if (it.value() == false) + return true; + } + return false; +} + +TrackWidget *ScriptTimeline::addTrack(const QString &name, bool filtersAllowed) +{ + TrackWidget *tw = new TrackWidget(m_baseTrackWidgetWidth, m_readonly, m_tools, filtersAllowed); + tw->setScriptTimeline(this); + tw->setTrackName(name); + + ui->trackLayout->insertWidget(1, tw); + + connect(tw, &TrackWidget::signalTrackEntrySelectionChanged, + this, &ScriptTimeline::slotTrackEntrySelectionChanged); + connect(tw, &TrackWidget::signalTrackEntryDeleted, + this, &ScriptTimeline::signalTrackEntryDeleted); + connect(tw, &TrackWidget::signalUnselectAll, + this, &ScriptTimeline::slotUnselectAll); + connect(tw, &TrackWidget::signalSelectTrack, + this, &ScriptTimeline::slotSelectTrack); + connect(tw, &TrackWidget::signalChangesMade, + this, &ScriptTimeline::signalChangesMade); + connect(tw, &TrackWidget::signalEditEntry, + this, &ScriptTimeline::signalEditEntry); + + TrackContentWidget *tcw = tw->getTrackContentWidget(); + + connect(tcw, &TrackContentWidget::signalStartRubberband, + this, &ScriptTimeline::slotStartRubberband); + connect(tcw, &TrackContentWidget::signalStopRubberband, + this, &ScriptTimeline::slotStopRubberband); + connect(tcw, &TrackContentWidget::signalEscapeRubberband, + this, &ScriptTimeline::slotEscapeRubberband); + connect(tcw, &TrackContentWidget::signalModifyRubberband, + this, &ScriptTimeline::slotModifyRubberband); + connect(tcw, &TrackContentWidget::signalSelectAllGlobal, + this, &ScriptTimeline::selectAll); + connect(tcw, &TrackContentWidget::signalCopyTopics, + this, &ScriptTimeline::slotCopyTopics); + connect(tcw, &TrackContentWidget::signalPasteTopics, + this, &ScriptTimeline::slotPasteTopics); + connect(tcw, &TrackContentWidget::signalCutTopics, + this, &ScriptTimeline::slotCutTopics); + connect(tcw, &TrackContentWidget::signalDeleteSelectedTopics, + this, &ScriptTimeline::slotDeleteSelectedTopics); + connect(tcw, &TrackContentWidget::signalSave, + this, &ScriptTimeline::signalSave); + connect(tcw, &TrackContentWidget::signalMoveAll, + this, &ScriptTimeline::slotMoveAll); + connect(tcw, &TrackContentWidget::signalScroll, + this, &ScriptTimeline::slotScroll); + + + + connect(tcw, &TrackContentWidget::signalMultiLineMultiSelectionFailed, + this, [this]() + { + TrackContentWidget *senderTcw = qobject_cast(sender()); + + if (senderTcw == nullptr) + return; + + m_mlmsTCWStates.insert(senderTcw, false); + + const QList tws = getTrackWidgets(); + + for (TrackWidget *tw : tws) + { + + TrackContentWidget *tcw = tw->getTrackContentWidget(); + tcw->setDragPossible(false); + } + }); + + + connect(tcw, &TrackContentWidget::signalMultiLineMultiSelectionOkay, + this, [this]() + { + + + + TrackContentWidget *senderTcw = qobject_cast(sender()); + + if (senderTcw == nullptr) + return; + + m_mlmsTCWStates.insert(senderTcw, true); + + if (containsFailedTrackContentWidget(m_mlmsTCWStates)) + return; + + + + const QList tws = getTrackWidgets(); + + for (TrackWidget *tw : tws) + { + TrackContentWidget *tcw = tw->getTrackContentWidget(); + + if (tcw == senderTcw) + continue; + + tcw->setDragPossible(true); + } + }); + + + connect(tcw, &TrackContentWidget::signalMultiLineMultiSelectionDone, + this, [this](int dropPos) + { + TrackContentWidget *senderTcw = qobject_cast(sender()); + + if (senderTcw == nullptr) + return; + + + + + const QList tws = getTrackWidgets(); + + for (TrackWidget *tw : tws) + { + TrackContentWidget *tcw = tw->getTrackContentWidget(); + + if (tcw == senderTcw) + continue; + + if (containsFailedTrackContentWidget(m_mlmsTCWStates) || dropPos == -1) + { + QApplication::beep(); + tcw->dragEnd(); + } + else + tcw->dragDrop(dropPos); + } + + m_mlmsMaster = nullptr; + m_mlmsTCWStates.clear(); + m_ignoreChanges = false; + + updateAllTracks(); + }); + + + connect(tcw, &TrackContentWidget::signalMultiLineMultiSelectionMove, + this, [this](int x) + { + TrackContentWidget *senderTcw = qobject_cast(sender()); + + if (senderTcw == nullptr) + return; + + + if (senderTcw != m_mlmsMaster) + return; + + const QList tws = getTrackWidgets(); + + for (TrackWidget *tw : tws) + { + TrackContentWidget *tcw = tw->getTrackContentWidget(); + + if (tcw == senderTcw) + continue; + + tcw->dragMove(x); + } + }); + + + connect(tcw, &TrackContentWidget::signalMultiLineMultiSelectionStart, + this, [this](int dragPosTime) + { + if (m_mlmsMaster != nullptr) + return; + + TrackContentWidget *senderTcw = qobject_cast(sender()); + + if (senderTcw == nullptr) + return; + + m_mlmsMaster = senderTcw; + m_ignoreChanges = true; + + const QList tws = getTrackWidgets(); + + for (TrackWidget *tw : tws) + { + TrackContentWidget *tcw = tw->getTrackContentWidget(); + + if (tcw == senderTcw) + continue; + + tcw->dragEnter(tcw->selectedItemsToData(dragPosTime)); + } + }); + + + return tw; +} + + +void ScriptTimeline::slotEscapeRubberband() +{ + if (m_rubberbandAction.rubberBand == nullptr) + return; + + m_rubberbandAction.rubberBand->deleteLater(); +} + + +void ScriptTimeline::slotStopRubberband() +{ + if (m_rubberbandAction.rubberBand == nullptr) + return; + + QRect geo(m_rubberbandAction.rubberBand->geometry()); + + m_rubberbandAction.rubberBand->deleteLater(); + + if (m_readonly) + return; + + QPoint rubberbandLength = geo.topLeft() - geo.bottomRight(); + if (rubberbandLength.manhattanLength() <= 6) + return; + + + const QList tws = getTrackWidgets(); + for (TrackWidget *tw : tws) + { + TrackContentWidget *tcw = tw->getTrackContentWidget(); + + QRect tcwGeo(tcw->geometry()); + QPoint topLeft(tcw->mapTo(this, tcwGeo.topLeft())); + QPoint bottomRight(tcw->mapTo(this, tcwGeo.bottomRight())); + + if (QRect(topLeft, bottomRight).intersects(geo)) + { + QPoint tcwTL(tcw->mapFrom(this, geo.topLeft())); + QPoint tcwBR(tcw->mapFrom(this, geo.bottomRight())); + + int fromPos = tcwTL.x(); + int toPos = tcwBR.x(); + + tcw->selectEntriesAtPositions(fromPos, toPos); + } + } +} + + +void ScriptTimeline::slotStartRubberband(const QPoint &) +{ + if (m_readonly) + return; + + if (m_rubberbandAction.rubberBand == nullptr) + { + QPoint cursorPos(mapFromGlobal(QCursor::pos())); + m_rubberbandAction.origin = cursorPos; + m_rubberbandAction.rubberBand = new QRubberBand(QRubberBand::Rectangle, this); + + m_rubberbandAction.rubberBand->setGeometry(QRect(cursorPos, QSize())); + m_rubberbandAction.rubberBand->show(); + } +} + +void ScriptTimeline::slotModifyRubberband() +{ + if (!m_rubberbandAction.rubberBand) + return; + + m_rubberbandAction.rubberBand->setGeometry(QRect(m_rubberbandAction.origin, mapFromGlobal(QCursor::pos())).normalized()); +} + + +void ScriptTimeline::slotTrackEntrySelectionChanged(TrackEntry *) +{ + if (m_ignoreChanges) + return; + + const QList tws = getTrackWidgets(); + + + bool hasSelections = false; + bool activateMultiLineMultiSelectionMode = false; + for (const TrackWidget *tw : tws) + { + if (!hasSelections) + { + if (!tw->getSelectedEntries().isEmpty() || !tw->getSelectedFilterEntries().isEmpty()) + hasSelections = true; + } + else if (!tw->getSelectedEntries().isEmpty()) + { + + activateMultiLineMultiSelectionMode = true; + break; + } + } + + + for (TrackWidget *tw : qAsConst(tws)) + tw->getTrackContentWidget()->setMultiLineMultiSelectionMode(activateMultiLineMultiSelectionMode); + + emit signalTrackEntrySelectionChanged(nullptr); +} + + +void ScriptTimeline::slotSelectTrack() +{ + const QList trackWidgetList = getTrackWidgets(); + for (const TrackWidget *tw : trackWidgetList) + { + if (tw->isSelected()) + { + + return; + } + } + + +} + + +void ScriptTimeline::updateEverything() +{ + + + + TrackWidget *tw = ui->trackScrollArea->findChild(); + + if (tw != nullptr) + m_tools->m_paintWidth = tw->width(); + + ui->indicator->updateMe(); + + updateAllTracks(); + update(); +} + + +void ScriptTimeline::slotScrollbarValueChanged() +{ + m_tools->m_scrollPos = ui->trackScrollBar->value(); + + updateEverything(); +} +} diff --git a/Lts/Common/LtsScriptTimeline/script_timeline.h b/Lts/Common/LtsScriptTimeline/script_timeline.h new file mode 100755 index 000000000..85e9f3af0 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/script_timeline.h @@ -0,0 +1,125 @@ +#pragma once + +#include +#include "track_entry_painters.h" + +#include +#include +#include + + +namespace Ui +{ +class ScriptTimeline; +} + + +class QRubberBand; + + +namespace LtsScriptTimeline +{ +class TrackContentWidget; +class TrackWidget; +class TrackEntry; +class Tools; + +class LTSSCRIPTTIMELINE_EXPORT ScriptTimeline : public QWidget +{ + Q_OBJECT + +public: + explicit ScriptTimeline(QWidget *parent = nullptr); + ~ScriptTimeline(); + + TrackWidget *addTrack(const QString &name, bool filtersAllowed = true); + void setScriptId(int scriptId) { m_scriptId = scriptId; } + bool addTopic(TrackEntry *te); + QString newTrackName() const; + int nextUniqueId() { return ++m_uniqueId; } + void setUniqueIdCounter(int uniqueId) { m_uniqueId = uniqueId; } + QVector getTrackEntriesWithFiltersAsEntries(bool selectedEntriesOnly = false) const; + QMap > getTrackEntriesWithFilters(bool selectedEntriesOnly = false) const; + QVector getTrackEntries(bool selectedEntriesOnly = false) const; + QVector getSelectedEntriesWithFilterEntries() const; + QVector getSelectedFilterEntries() const; + TrackEntry *getTrackEntry(int topicId); + TrackEntry *getTrackEntryWithFiltersAsEntries(int topicId); + void setIndicatorTime(int time); + int getCurrentScriptEndTime(); + + void updateEverything(); + void clear(); + void setReadonly(bool readonly); + QList getTrackWidgets() const; + void lockIndicator(bool lock = true); + int getFilterTrackEntryRealTime(TrackEntry *te); + bool checkNewLength(TrackEntry *te, int newLength); + bool importTrackEntries(const QVector &importTes); + +protected: + void keyPressEvent(QKeyEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *) override; + void mouseReleaseEvent(QMouseEvent *) override; + +private: + Ui::ScriptTimeline *ui; + int m_uniqueId; + int m_scriptId; + int m_baseTrackWidgetWidth; + QVector m_clipboard; + + bool m_readonly; + int m_time; + Tools *m_tools; + TrackContentWidget *m_mlmsMaster; + QHash m_mlmsTCWStates; + bool m_ignoreChanges; + QHash m_copyTopics; + + struct RubberbandAction + { + QPoint origin; + QPointer rubberBand; + } m_rubberbandAction; + + void resizeEvent(QResizeEvent *event) override; + void updateAllTracks(); + +signals: + void signalTrackEntrySelectionChanged(TrackEntry *te); + void signalChangesMade(); + void signalHeaderIndicatorTimeChanged(int time); + void signalHeaderIndicatorTimeUserChanged(int time); + void signalEditEntry(const int topicId); + void signalTrackEntryDeleted(const int topicId); + void signalTrackEntryAdded(TrackEntry *te); + void signalSave(); + +public slots: + void slotUnselectAll(); + void slotEscapeRubberband(); + void slotStopRubberband(); + void slotStartRubberband(const QPoint &pos); + void slotModifyRubberband(); + void selectAll(); + void slotCopyTopics(); + void slotCutTopics(); + void slotPasteTopics(); + void slotDeleteSelectedTopics(); + void slotMoveAll(int direction); + void slotScroll(int incr); + +private slots: + void slotScrollbarValueChanged(); + void slotZoomValueChanged(int value); + void slotDeleteTracks(); + void slotSelectTrack(); + void slotHeaderHandleResized(int newSize); + void slotHeaderIndicatorTimeChanged(int time); + void slotTrackEntrySelectionChanged(TrackEntry *te); + void slotSetNewScrollBarMaximum(); +}; + +} diff --git a/Lts/Common/LtsScriptTimeline/temporary_pointer_vector.h b/Lts/Common/LtsScriptTimeline/temporary_pointer_vector.h new file mode 100755 index 000000000..f07401d23 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/temporary_pointer_vector.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + + +namespace LtsScriptTimeline +{ + +template +class TemporaryPointerVector : public QVector +{ + static_assert(std::is_pointer::value, "Type must be of pointer type"); + +public: + TemporaryPointerVector() = default; + + explicit TemporaryPointerVector(const QVector &other) + { + discard(); + this->append(other); + } + + + ~TemporaryPointerVector() + { + discard(); + } + + + void setDiscardable(bool discardable) + { + m_discard = discardable; + } + + +private: + bool m_discard = true; + + + void discard() + { + if (m_discard) + qDeleteAll(this->begin(), this->end()); + this->clear(); + } + +}; + +} diff --git a/Lts/Common/LtsScriptTimeline/tools.cpp b/Lts/Common/LtsScriptTimeline/tools.cpp new file mode 100755 index 000000000..71cba4058 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/tools.cpp @@ -0,0 +1,84 @@ +#include "tools.h" + +#include + + +namespace LtsScriptTimeline +{ + + + + + + +int Tools::getLength(int startTime, int duration) +{ + return (getPos(startTime + duration) - getPos(startTime)); +} + + +int Tools::getDuration(int duration) +{ + double max = Tools::m_timelineLength; + max /= Tools::m_zoomFactor; + + int paintWidth = m_paintWidth; + + double x = duration; + x /= paintWidth; + x *= max; + + return lrint(x); +} + + +int Tools::getTime(int pos, bool neutral) +{ + double visibleMax = Tools::m_timelineLength; + visibleMax /= Tools::m_zoomFactor; + + if (!neutral) + visibleMax += Tools::m_scrollPos; + + int visibleMin = 0; + + if (!neutral) + visibleMin = Tools::m_scrollPos; + + double diff = visibleMax - visibleMin; + int paintWidth = m_paintWidth; + + double x = pos; + x /= paintWidth; + x *= diff; + x += visibleMin; + + return lrint(x); +} + + +int Tools::getPos(int time, bool neutral) +{ + double visibleMax = Tools::m_timelineLength; + visibleMax /= Tools::m_zoomFactor; + + if (!neutral) + visibleMax += Tools::m_scrollPos; + + int visibleMin = 0; + + if (!neutral) + visibleMin = Tools::m_scrollPos; + + double diff = visibleMax - visibleMin; + int paintWidth = m_paintWidth; + + double x = time; + x -= visibleMin; + x /= diff; + x *= paintWidth; + + return lrint(x); +} + +} diff --git a/Lts/Common/LtsScriptTimeline/tools.h b/Lts/Common/LtsScriptTimeline/tools.h new file mode 100755 index 000000000..a100012cd --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/tools.h @@ -0,0 +1,33 @@ +#pragma once + +#include + + +namespace LtsScriptTimeline +{ + +class Tools : public QObject +{ + Q_OBJECT + +public: + explicit Tools(QObject *parent) + : QObject(parent) + , m_timelineLength(5 * 60) + , m_zoomFactor(1) + , m_scrollPos(0) + , m_paintWidth(1) + {} + + int m_timelineLength; + float m_zoomFactor; + int m_scrollPos; + int m_paintWidth; + + int getDuration(int duration); + int getTime(int pos, bool neutral = false); + int getPos(int time, bool neutral = false); + int getLength(int startTime, int duration); +}; + +} diff --git a/Lts/Common/LtsScriptTimeline/track_content_widget.cpp b/Lts/Common/LtsScriptTimeline/track_content_widget.cpp new file mode 100755 index 000000000..5285dcfc0 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/track_content_widget.cpp @@ -0,0 +1,1652 @@ +#include "track_content_widget.h" +#include "track_entry.h" +#include "tools.h" +#include "track_entry_painters.h" +#include "track_widget.h" +#include "script_timeline.h" +#include "filter_background_widget.h" +#include "track_drag_handler.h" +#include "track_select_handler.h" +#include "track_resize_handler.h" +#include "temporary_pointer_vector.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +#include + + +namespace LtsScriptTimeline +{ + +QDataStream &operator<<(QDataStream &ds, const TrackEntry &te) +{ + const QByteArray jsonContent = QJsonDocument(te.m_detailData).toBinaryData(); + ds << te.m_topicId + << te.m_startTime + << te.m_length + << static_cast(te.m_type) + << te.isSet + << te.index + << jsonContent + << te.x << te.y << te.h << te.w + << te.m_channelName + << te.m_description; + return ds; +} + + +QDataStream &operator>>(QDataStream &ds, TrackEntry &te) +{ + QByteArray jsonContent; + quint8 type; + ds >> te.m_topicId + >> te.m_startTime + >> te.m_length + >> type + >> te.isSet + >> te.index + >> jsonContent + >> te.x >> te.y >> te.h >> te.w + >> te.m_channelName + >> te.m_description; + + te.m_type = static_cast(type); + te.m_detailData = QJsonDocument::fromBinaryData(jsonContent).object(); + return ds; +} + + +bool isCollision(int start_s, int end_s, int toCompare_start_s, int toCompare_end_s) +{ + + + + + + + + + + + + + + + + + + + + + if (start_s >= toCompare_start_s && + + start_s < toCompare_end_s) + { + + return true; + } + + + if (start_s < toCompare_start_s && + + end_s > toCompare_start_s) + { + + return true; + } + + return false; +} + + +bool isCollision2(int startPreview_s, int endPreview_s, const TrackEntry *pTrackEntry) +{ + return isCollision(startPreview_s, endPreview_s, pTrackEntry->m_startTime, pTrackEntry->m_startTime + pTrackEntry->m_length); +} + + + + +void sortTrackEntriesChronologically(QVector &vecTrackEntries) +{ + std::sort(vecTrackEntries.begin(), vecTrackEntries.end(), + [](const TrackEntry *a, const TrackEntry *b) -> bool + { + return a->m_startTime < b->m_startTime; + }); +} + + +bool TrackContentWidget::checkForCollisionsSimple(const TrackEntry &previewEntry) const +{ + + const int startPreview = previewEntry.m_startTime; + if (startPreview < 0) + return false; + const int endPreview = startPreview + previewEntry.m_length; + + const QVector trackEntries = getTrackEntries(); + for (const TrackEntry *checkTe : trackEntries) + { + if (checkTe->m_topicId == previewEntry.m_topicId) + continue; + + if (LtsScriptTimeline::isCollision2(startPreview, endPreview, checkTe)) + return false; + } + + return true; +} + + +bool TrackContentWidget::checkForCollisions(const TrackContentWidget *tcw, const QVector &preview, int dropTime_s, QVector &vecCollisions, bool ignoreSelected, bool ignoreSelfCheck) +{ + QVector _preview = preview; + sortTrackEntriesChronologically(_preview); + + vecCollisions.clear(); + + QVector vecEntriesToCheck; + const QVector &trackEntries = tcw->getTrackEntries(); + + if (ignoreSelected) + { + + const QVector &selectedEntries = tcw->getSelectedEntries(); + for (TrackEntry *te : qAsConst(trackEntries)) + { + if (!selectedEntries.contains(te)) + vecEntriesToCheck.append(te); + } + } + else + vecEntriesToCheck = trackEntries; + + + auto leftMostI = std::min_element(_preview.constBegin(), _preview.constEnd(), [](const TrackEntry * a, const TrackEntry * b) + { + return a->m_startTime < b->m_startTime; + }); + + if (leftMostI == _preview.constEnd()) + return false; + + int startTimeMostLeft_s = (*leftMostI)->m_startTime; + int startOffset_s = dropTime_s - startTimeMostLeft_s; + + + for (TrackEntry *previewEntry : qAsConst(_preview)) + { + int startPreview_s = previewEntry->m_startTime + startOffset_s; + + if (startPreview_s < 0) + { + vecCollisions.append(previewEntry); + continue; + } + + int endPreview_s = startPreview_s + previewEntry->m_length; + + for (const TrackEntry *checkTe : qAsConst(vecEntriesToCheck)) + { + if (checkTe->m_topicId == previewEntry->m_topicId && !ignoreSelfCheck) + { + continue; + } + + if (LtsScriptTimeline::isCollision2(startPreview_s, endPreview_s, checkTe)) + { + vecCollisions.append(previewEntry); + break; + } + } + } + + return !vecCollisions.isEmpty(); +} + + +bool TrackContentWidget::checkForCollisions(const TrackContentWidget *pTCW, const QVector &vecPreviewTrackEntries, int dropTime_s) +{ + QVector dummy_vecCollidingTopics; + return TrackContentWidget::checkForCollisions(pTCW, vecPreviewTrackEntries, dropTime_s, dummy_vecCollidingTopics, true); +} + + +QString TrackContentWidget::parallelError() +{ + return tr("At least one topic of type Situation, Building, Audio or Video has been selected\nwhich can not be played parallel on both screens. Placement not possible."); +} + + + +bool TrackContentWidget::checkForParallelTopics(const TrackContentWidget *tcw, const QVector &preview, int dropTime_s, QVector &vecParallelTopics, bool ignoreSelectedTopics, bool hack) +{ + + { + QVector vecParallelForbiddenFilter = { TrackEntryType::Building, TrackEntryType::Audio, TrackEntryType::Video, TrackEntryType::SituationMap, TrackEntryType::Situation }; + QVector previewCopy(preview); + sortTrackEntriesChronologically(previewCopy); + QVector vecTrackEntries(tcw->getTrackEntries()); + + + auto itE = std::remove_if(previewCopy.begin(), previewCopy.end(), + [vecParallelForbiddenFilter](TrackEntry *pTE) + { + return !vecParallelForbiddenFilter.contains(pTE->m_type); + }); + previewCopy.erase(itE, previewCopy.end()); + + + if (ignoreSelectedTopics) + { + + itE = std::remove_if(previewCopy.begin(), previewCopy.end(), + [tcw](TrackEntry *pTE) + { + return tcw->getSelectedEntries().contains(pTE); + }); + previewCopy.erase(itE, previewCopy.end()); + } + + + + itE = std::remove_if(vecTrackEntries.begin(), vecTrackEntries.end(), + [vecParallelForbiddenFilter](TrackEntry *pTE) + { + return !vecParallelForbiddenFilter.contains(pTE->m_type); + }); + vecTrackEntries.erase(itE, vecTrackEntries.end()); + + + + itE = std::remove_if(vecTrackEntries.begin(), vecTrackEntries.end(), + [tcw](TrackEntry *pTE) + { + return tcw->getSelectedEntries().contains(pTE); + }); + vecTrackEntries.erase(itE, vecTrackEntries.end()); + + + + if (!previewCopy.isEmpty() && !vecTrackEntries.isEmpty()) + { + auto leftMostI = std::min_element(previewCopy.constBegin(), previewCopy.constEnd(), [](const TrackEntry *a, const TrackEntry *b) + { + return a->m_startTime < b->m_startTime; + }); + + if (leftMostI != previewCopy.constEnd()) + { + int startTimeMostLeft_s = (*leftMostI)->m_startTime; + + + + + for (TrackEntry *previewEntry : qAsConst(previewCopy)) + { + + + int startOffset_s = previewEntry->m_startTime; + + if (!hack) + startOffset_s -= startTimeMostLeft_s; + + + + for (TrackEntry *pTrackEntry : qAsConst(vecTrackEntries)) + { + + + if (previewEntry->m_type != pTrackEntry->m_type) + { + + continue; + } + + + if (previewEntry->m_topicId == pTrackEntry->m_topicId) + { + + continue; + } + + int startPreview_s = startOffset_s; + + if (!hack) + startPreview_s += dropTime_s; + + int endPreview_s = startPreview_s + previewEntry->m_length; + + if (LtsScriptTimeline::isCollision2(startPreview_s, endPreview_s, pTrackEntry)) + { + + vecParallelTopics.append(previewEntry); + break; + } + + + } + } + } + } + else + { + + } + } + + + + + { + + QVector previewCopy(preview); + sortTrackEntriesChronologically(previewCopy); + QVector > differentParallelForbidden = + { + { TrackEntryType::Situation, TrackEntryType::Snapshot }, + { TrackEntryType::Situation, TrackEntryType::SituationReplay }, + { TrackEntryType::Situation, TrackEntryType::SituationMap }, + }; + QVector vecTrackEntries(tcw->getTrackEntries()); + + + int step = 1; + auto findUnusedType = [differentParallelForbidden, &step](const TrackEntry *pTE) + { + auto x = std::find_if(differentParallelForbidden.constBegin(), differentParallelForbidden.constEnd(), [pTE, step](const QPair &pair) + { + bool x = (pair.first == pTE->m_type || pair.second == pTE->m_type); + if (x) + qDebug() << ">>"<m_type << "in type list - not removing"; + return x; + }); + + if (x == differentParallelForbidden.constEnd()) + qDebug() << ">>"<m_type << "not found in type list - removing!"; + + return (x == differentParallelForbidden.constEnd()); + }; + + + + auto itE = std::remove_if(previewCopy.begin(), previewCopy.end(), findUnusedType); + previewCopy.erase(itE, previewCopy.end()); + + + step = 2; + + itE = std::remove_if(vecTrackEntries.begin(), vecTrackEntries.end(), findUnusedType); + vecTrackEntries.erase(itE, vecTrackEntries.end()); + + + if (ignoreSelectedTopics) + { + + itE = std::remove_if(previewCopy.begin(), previewCopy.end(), + [tcw](TrackEntry *pTE) + { + return tcw->getSelectedEntries().contains(pTE); + }); + previewCopy.erase(itE, previewCopy.end()); + } + + + + itE = std::remove_if(vecTrackEntries.begin(), vecTrackEntries.end(), + [tcw](TrackEntry *pTE) + { + bool x = tcw->getSelectedEntries().contains(pTE); + + return x; + }); + vecTrackEntries.erase(itE, vecTrackEntries.end()); + + + + if (!previewCopy.size() || !vecTrackEntries.size()) + { + + return !vecParallelTopics.isEmpty(); + } + + + auto leftMostI = std::min_element(previewCopy.constBegin(), previewCopy.constEnd(), [](const TrackEntry * a, const TrackEntry * b) + { + return a->m_startTime < b->m_startTime; + }); + + if (leftMostI == previewCopy.constEnd()) + return !vecParallelTopics.isEmpty(); + + int startTimeMostLeft_s = (*leftMostI)->m_startTime; + + + + QVector correspondingDifferentTopicTypes; + + for (TrackEntry *previewEntry : qAsConst(previewCopy)) + { + correspondingDifferentTopicTypes.clear(); + + + int startOffset_s = previewEntry->m_startTime; + + if (!hack) + startOffset_s -= startTimeMostLeft_s; + + + for (const QPair &pair : qAsConst(differentParallelForbidden)) + { + if (pair.first == previewEntry->m_type) + correspondingDifferentTopicTypes += pair.second; + + if (pair.second == previewEntry->m_type) + correspondingDifferentTopicTypes += pair.first; + } + + Q_ASSERT(!correspondingDifferentTopicTypes.isEmpty()); + + if (correspondingDifferentTopicTypes.isEmpty()) + continue; + + for (const TrackEntryType correspondingDifferentTopicType : qAsConst(correspondingDifferentTopicTypes)) + { + + + for (TrackEntry *pTrackEntry : qAsConst(vecTrackEntries)) + { + + + if (pTrackEntry->m_type != correspondingDifferentTopicType) + { + + continue; + } + + + if (previewEntry->m_topicId == pTrackEntry->m_topicId) + { + + continue; + } + + int startPreview_s = startOffset_s; + + if (!hack) + startPreview_s += dropTime_s; + + int endPreview_s = startPreview_s + previewEntry->m_length; + + if (LtsScriptTimeline::isCollision2(startPreview_s, endPreview_s, pTrackEntry)) + { + + vecParallelTopics.append(previewEntry); + break; + } + + + } + } + } + } + + return !vecParallelTopics.isEmpty(); +} + + +bool TrackContentWidget::checkForParallelTopics(const TrackContentWidget *pTCW, const QVector &vecPreviewTrackEntries, int dropTime_s, bool hack) +{ + QVector dummy_vecParallelTopics; + return checkForParallelTopics(pTCW, vecPreviewTrackEntries, dropTime_s, dummy_vecParallelTopics, false, hack); +} + + +QString TrackContentWidget::mime = QStringLiteral("application/rheinmetall.script.topic"); + + +TrackContentWidget::TrackContentWidget(QWidget *parent, bool isFilter, Tools *tools, int mainTopicId) + : QWidget(parent) + , m_selectHandler(new SelectHandler(this)) + , m_resizeHandler(new ResizeHandler(this)) + , m_dragHandler(new DragHandler(this)) + , m_dragOffsetTime(-1) + , m_dragPos(-1) + , m_mainSelected(nullptr) + , m_width(0) + , m_cursorSet(false) + , m_oldEntryLength(-1) + , m_unselectAllImmune(false) + , m_indicatorTime(-1) + , m_isFilter(isFilter) + , m_readonly(false) + , m_trackWidget(nullptr) + , m_scriptTimeline(nullptr) + , m_tools(tools) + , m_isMultiLineMultiSelectionMode(false) + , m_multiLineMultiSelectionOkay(false) + , m_dragPossible(false) + , m_moveValid(false) + , m_mainTopicId(mainTopicId) +{ + setAcceptDrops(true); + setFocusPolicy(Qt::StrongFocus); + setMouseTracking(true); + + addFilterType(TrackEntryType::TacticalPlan); + addFilterType(TrackEntryType::SituationMapFilter); + + + if (m_isFilter) + { + addAllowedType(TrackEntryType::Text); + addAllowedType(TrackEntryType::Pdf); + addAllowedType(TrackEntryType::Image); + } +} + + +TrackContentWidget::~TrackContentWidget() +{ + delete m_selectHandler; + delete m_resizeHandler; + delete m_dragHandler; + + QVector entryList = m_entryList; + + + for (TrackEntry *te : entryList) + { + disconnect(te, &TrackEntry::destroyed, + this, nullptr); + } + + qDeleteAll(entryList); +} + + +bool TrackContentWidget::setDescriptionForSelected() +{ + if (getSelectedEntries().count() > 1) + { + QApplication::beep(); + return false; + } + + m_selectHandler->slotSetDescription(); + return true; +} + + +void TrackContentWidget::addCorrespondingTrackWidget(TrackContentWidget *pCorrespondingTrackWidget) +{ + m_tcwCorresponding += pCorrespondingTrackWidget; + m_dragHandler->m_tcwCorresponding += pCorrespondingTrackWidget; + m_resizeHandler->m_tcwCorresponding += pCorrespondingTrackWidget; +} + + +void TrackContentWidget::setTools(Tools *tools) +{ + m_tools = tools; +} + + +void TrackContentWidget::setTrackWidget(TrackWidget *trackWidget) +{ + m_trackWidget = trackWidget; +} + + +void TrackContentWidget::setReadonly(bool readonly) +{ + m_readonly = readonly; +} + + +void TrackContentWidget::addFilterType(const TrackEntryType &tet) +{ + if (m_isFilter) + addAllowedType(tet); + else + addDisallowedType(tet); +} + + +void TrackContentWidget::setIndicatorTime(int time) +{ + m_indicatorTime = time; + update(); +} + + +void TrackContentWidget::mouseMoveEvent(QMouseEvent *event) +{ + if (!m_resizeHandler->mouseMoveEvent(event)) + m_dragHandler->mouseMoveEvent(event); + + emit signalModifyRubberband(); +} + + +TrackEntry *TrackContentWidget::getTrackEntryAt(int clickedTime) +{ + for (auto i = m_entryList.begin(); i != m_entryList.end(); ++i) + { + TrackEntry *te = *i; + + if (te->m_startTime <= clickedTime + && (te->m_startTime + te->m_length) >= clickedTime) + return te; + } + + return nullptr; +} + + +void TrackContentWidget::mousePressEvent(QMouseEvent *event) +{ + if (m_readonly) + return; + + m_selectHandler->mousePressEvent(event); + m_resizeHandler->mousePressEvent(event); + m_dragHandler->mousePressEvent(event); + + if (!m_selected.isEmpty()) + return; + + if (event->buttons() != Qt::LeftButton) + return; + emit signalStartRubberband(event->pos()); +} + + +void TrackContentWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + m_width = event->size().width(); +} + + +void TrackContentWidget::mouseReleaseEvent(QMouseEvent *event) +{ + if (m_readonly) + return; + + if (!m_resizeHandler->m_resizing) + m_selectHandler->mouseReleaseEvent(event); + + m_resizeHandler->mouseReleaseEvent(event); + + emit signalStopRubberband(); +} + + +void TrackContentWidget::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + + QPainter painter(this); + + painter.save(); + + + + if (m_isFilter) + { + painter.setBrush(QGuiApplication::palette().window().color()); + painter.drawRect(rect()); + } + + + + painter.setPen(QColor(36, 36, 36)); + painter.drawLine(QPoint(0, 0), QPoint(0, height())); + + if (m_isFilter) + painter.drawLine(QPoint(width() - 1, 0), QPoint(width() - 1, height())); + + + + painter.setClipping(true); + painter.setClipRect(1, 1, width() - 1, height() - 1); + + + + for (auto i = m_entryList.begin(); i != m_entryList.end(); ++i) + { + TrackEntry *te = *i; + + int startPx = m_tools->getPos(te->m_startTime, m_isFilter); + int endPx = m_tools->getPos(te->m_startTime + te->m_length, m_isFilter); + + if (endPx < 0 || startPx > width()) + continue; + + int heightPx = height() - 2; + int lengthPx = endPx - startPx; + + + painter.save(); + painter.translate(startPx, 1); + + bool isSelected = m_selected.contains(te); + QRect trackRect(0, 0, lengthPx, heightPx); + drawTrackEntry(te, painter, trackRect, isSelected); + + if (isSelected) + { + + + + } + + painter.restore(); + } + + + + if (m_dragPos > -1 && !m_preview.isEmpty()) + { + int firstTeTime = -1; + + for (const TrackEntry *te : qAsConst(m_preview)) + { + if (firstTeTime > te->m_startTime || firstTeTime == -1) + firstTeTime = te->m_startTime; + } + + int drop = m_tools->getTime(m_dragPos, m_isFilter); + int finalDrop = drop - m_dragOffsetTime; + int diff = finalDrop - firstTeTime; + int heightPx = height() - 2; + + for (auto i = m_preview.constBegin(); i != m_preview.constEnd(); ++i) + { + const TrackEntry *te = *i; + + if (m_dragHandler->m_vecCollisions.contains(*i) || m_dragHandler->m_vecParallel.contains(*i) || !isDragPossible() || !m_moveValid) + painter.setBrush(QColor("red")); + else + painter.setBrush(QColor("lightgreen")); + + int time = te->m_startTime + diff; + + int startPx = m_tools->getPos(time, m_isFilter); + int endPx = m_tools->getPos(time + te->m_length, m_isFilter); + + if (endPx < 0 || startPx > width()) + continue; + + int lengthPx = endPx - startPx; + painter.drawRect(startPx, 1, lengthPx, heightPx); + } + } + + + + if (m_indicatorTime > -1) + { + int pos = m_tools->getPos(m_indicatorTime, m_isFilter); + + if (pos > 0 && pos <= m_tools->m_paintWidth) + { + painter.setPen(QColor("cyan")); + painter.drawLine(pos, 0, pos, height()); + } + } + + + painter.restore(); +} + + +void TrackContentWidget::dragEnterEvent(QDragEnterEvent *event) +{ + if (m_readonly) + return; + + m_dragHandler->dragEnterEvent(event); +} + + +void TrackContentWidget::dragLeave() +{ + +} + + +void TrackContentWidget::dragLeaveEvent(QDragLeaveEvent *event) +{ + if (m_readonly) + return; + + m_dragHandler->dragLeaveEvent(event); +} + + +void TrackContentWidget::unselect() +{ + m_unselectAllImmune = true; + emit signalUnselectAll(); + m_unselectAllImmune = false; +} + + +void TrackContentWidget::forceLocalUnselect() +{ + m_mainSelected = nullptr; + m_selected.clear(); +} + + +void TrackContentWidget::selectAll() +{ + m_selected.clear(); + + for (TrackEntry *te : qAsConst(m_entryList)) + { + m_selected.append(te); + m_mainSelected = te; + } + + emit signalTrackEntrySelectionChanged(nullptr); + update(); +} + + +void TrackContentWidget::unselectAll() +{ + if (!m_unselectAllImmune) + forceLocalUnselect(); + else + { + if (!QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier)) + { + forceLocalUnselect(); + emit signalTrackEntrySelectionChanged(nullptr); + } + } + + update(); +} + + +TrackEntry *TrackContentWidget::getParentTopicOfFilter(TrackEntry *filterTe) const +{ + + if (m_isFilter && m_trackWidget != nullptr) + { + + int mainTopicId = m_trackWidget->getParentTopicIdOfFilter(filterTe); + TrackContentWidget *mainTcw = m_trackWidget->getTrackContentWidget(); + const QVector &teList = mainTcw->getTrackEntries(); + + for (auto i = teList.constBegin(); i != teList.constEnd(); ++i) + { + TrackEntry *mainTe = *i; + + if (mainTe->m_topicId != mainTopicId) + continue; + + return mainTe; + } + } + + return nullptr; +} + + +bool TrackContentWidget::addTrackEntry(TrackEntry *te, bool selectTrackEntry) +{ + if (!allowedByType(te)) + { + qWarning() << "TrackContentWidget::addTrackEntry: topic #" << te->m_topicId << "type" << (int)te->m_type << "not allowed"; + return false; + } + + connect(te, &TrackEntry::destroyed, + this, [this]() + { + TrackEntry *o = static_cast(sender()); + m_entryList.removeAll(o); + m_selected.removeAll(o); + + if (m_mainSelected == o) + { + m_mainSelected = nullptr; + emit signalTrackEntrySelectionChanged(nullptr); + } + }); + + te->m_channelName = m_trackName; + m_entryList.append(te); + + + TrackEntry *mainTe = getParentTopicOfFilter(te); + + if (mainTe != nullptr) + { + + if ((te->m_startTime + te->m_length) > mainTe->m_length) + te->m_length = mainTe->m_length - te->m_startTime; + + te->m_filterParentChannel = mainTe->m_channelName; + } + + + if (selectTrackEntry && !m_readonly) + selectEntry(te); + + emit signalTrackEntryAdded(te); + + update(); + return true; +} + + +void TrackContentWidget::removeTrackEntryByTopicId(TrackEntry *te) +{ + auto itE = std::remove_if(m_entryList.begin(), m_entryList.end(), + [te](TrackEntry * pTE) + { + if (pTE->m_topicId == te->m_topicId) + { + pTE->deleteLater(); + return true; + } + + return false; + }); + m_entryList.erase(itE, m_entryList.end()); + + + itE = std::remove_if(m_selected.begin(), m_selected.end(), + [te](TrackEntry * pTE) + { + if (pTE->m_topicId == te->m_topicId) + { + pTE->deleteLater(); + return true; + } + + return false; + }); + m_selected.erase(itE, m_selected.end()); +} + + +void TrackContentWidget::dropEvent(QDropEvent *event) +{ + if (m_readonly) + return; + + m_dragHandler->dropEvent(event); +} + + +void TrackContentWidget::dragMoveEvent(QDragMoveEvent *event) +{ + if (m_readonly) + return; + + m_dragHandler->dragMoveEvent(event); +} + + +bool TrackContentWidget::canMoveAll(int offset) const +{ + if (m_selected.isEmpty()) + return true; + + auto leftMostI = std::min_element(m_selected.constBegin(), m_selected.constEnd(), [](const TrackEntry * a, const TrackEntry * b) + { + return a->m_startTime < b->m_startTime; + }); + + if (leftMostI == m_selected.constEnd()) + return false; + + int startTimeMostLeft = (*leftMostI)->m_startTime; + + + QVector cols; + + int newTime = startTimeMostLeft + offset; + checkForCollisions(this, m_selected, newTime, cols, true); + + QVector parls; + for (const auto &tcw : qAsConst(m_tcwCorresponding)) + checkForParallelTopics(tcw, m_selected, newTime, parls); + + if (!cols.isEmpty() || !parls.isEmpty()) + return false; + + return true; +} + + +void TrackContentWidget::forceMoveAll(int offset) +{ + for (auto i = m_selected.begin(); i != m_selected.end(); ++i) + { + TrackEntry *te = *i; + + + int newTime = te->m_startTime + offset; + + if (newTime < 0) + newTime = 0; + + + + + te->m_startTime = newTime; + } + + update(); + emit signalChangesMade(); +} + + +void TrackContentWidget::deleteSelectedTopics() +{ + m_mainSelected = nullptr; + m_selectHandler->m_pressedEntry = nullptr; + + bool somethingSeleted = false; + for (auto &pTopic : m_selected) + { + m_preview.removeAll(pTopic); + m_entryList.removeAll(pTopic); + emit signalTrackEntryDeleted(pTopic->m_topicId); + pTopic->deleteLater(); + somethingSeleted = true; + } + + if (somethingSeleted) + emit signalTrackEntrySelectionChanged(nullptr); + + m_selected.clear(); + emit signalChangesMade(); + update(); +} + + +void TrackContentWidget::mouseDoubleClickEvent(QMouseEvent *event) +{ + if (m_readonly) + return; + + m_selectHandler->mouseDoubleClick(event); +} + + +void TrackContentWidget::keyPressEvent(QKeyEvent *event) +{ + if (m_readonly) + return; + + if (event->key() == Qt::Key_Delete) + { + emit signalDeleteSelectedTopics(); + } + else if (QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && event->key() == Qt::Key_C) + { + emit signalCopyTopics(); + } + else if (QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && event->key() == Qt::Key_V) + { + emit signalPasteTopics(); + } + else if (QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && event->key() == Qt::Key_X) + { + emit signalCutTopics(); + } + else if (QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && event->key() == Qt::Key_A) + { + emit signalSelectAllGlobal(); + } + else if (QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && event->key() == Qt::Key_S) + { + emit signalSave(); + } + else if (QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && event->key() == Qt::Key_D) + { + setDescriptionForSelected(); + } + else + { + m_resizeHandler->keyPressEvent(event); + m_selectHandler->keyPressEvent(event); + m_dragHandler->keyPressEvent(event); + } + + if (event->key() == Qt::Key_Escape) + { + emit signalEscapeRubberband(); + emit signalMultiLineMultiSelectionDone(-1); + } +} + + +void TrackContentWidget::selectEntry(int topicId) +{ + for (auto i = m_entryList.begin(); i != m_entryList.end(); ++i) + { + TrackEntry *te = *i; + + if (te->m_topicId == topicId) + { + selectEntry((te)); + return; + } + } +} + + +void TrackContentWidget::selectEntriesAtPositions(int fromPos, int toPos) +{ + m_unselectAllImmune = true; + + for (TrackEntry *te : qAsConst(m_entryList)) + { + int start = m_tools->getPos(te->m_startTime); + int end = m_tools->getPos(te->m_startTime + te->m_length); + if (end >= fromPos && start <= toPos) + selectEntry(te); + } + + m_unselectAllImmune = false; +} + + +void TrackContentWidget::selectEntry(TrackEntry *te) +{ + bool ctrl = QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier); + + if (!m_unselectAllImmune && !ctrl) + unselect(); + + if (te == nullptr) + return; + + if (!m_selected.contains(te)) + { + m_selected.append(te); + m_mainSelected = te; + + emit signalTrackEntrySelectionChanged(nullptr); + } + else + { + m_selected.removeAll(te); + if (m_mainSelected == te) + m_mainSelected = nullptr; + + emit signalTrackEntrySelectionChanged(nullptr); + } + + update(); +} + + +bool TrackContentWidget::canPasteTopics(const QVector &topics, int offsetTime, int pasteTime) const +{ + TemporaryPointerVector newTes; + + newTes.reserve(topics.size()); + for (TrackEntry *te : qAsConst(topics)) + { + TrackEntry *newTe = new TrackEntry(te); + newTe->m_startTime = (newTe->m_startTime - offsetTime + pasteTime); + newTes += newTe; + } + + + + QVector dummy_vecCollidingTopics; + + bool parl = false; + + for (const auto &tcw : qAsConst(m_tcwCorresponding)) + { + parl = checkForParallelTopics(tcw, newTes, pasteTime, true ); + if (parl) + break; + } + + if (checkForCollisions(this, newTes, pasteTime, dummy_vecCollidingTopics, false, true) || parl) + return false; + + return true; + + + +} + + +void TrackContentWidget::forcePasteTopics(const QVector &topics, int offsetTime, int pasteTime) +{ + + + m_selected.clear(); + + + for (TrackEntry *te : qAsConst(topics)) + { + TrackEntry *newTe = new TrackEntry(te); + newTe->m_topicId = m_scriptTimeline->nextUniqueId(); + newTe->m_startTime = (newTe->m_startTime - offsetTime + pasteTime); + newTe->isSet = true; + + m_unselectAllImmune = true; + if (!m_trackWidget->addTrackEntry(newTe, false)) + { + qWarning() << "TrackContentWidget::forcePasteTopics: topic #" << te->m_topicId << "type" << (int)te->m_type << "not allowed"; + delete newTe; + delete te; + continue; + } + m_unselectAllImmune = false; + } +} + + +bool TrackContentWidget::allowedByType(const TrackEntry *te) const +{ + if (m_disallowedTypes.contains(te->m_type)) + return false; + + if (m_allowedTypes.isEmpty()) + return true; + + return (m_allowedTypes.contains(te->m_type)); +} + + +void TrackContentWidget::setTrackName(const QString &trackName) +{ + m_trackName = trackName; + + for (auto i = m_entryList.begin(); i != m_entryList.end(); ++i) + { + TrackEntry *te = *i; + te->m_channelName = m_trackName; + } +} + + +TrackEntry *TrackContentWidget::getTrackEntryOnPos(QPoint globalPos) +{ + QPoint clickedPos = mapFromGlobal(globalPos); + int clickedPosY = clickedPos.y(); + int clickedPosX = clickedPos.x(); + + if (clickedPosY < 0 || clickedPosY > height()) + return nullptr; + + int clickedTime = m_tools->getTime(clickedPosX, m_isFilter); + + return getTrackEntryAt(clickedTime); +} + + +QVector TrackContentWidget::dataToItems(const QByteArray &data, int &offset) +{ + if (data.isEmpty()) + return {}; + + QDataStream dataStream(data); + + int count; + dataStream >> count; + dataStream >> offset; + + QVector tes; + tes.reserve(count); + for (int i = 0; i < count; i++) + { + TrackEntry *te = new TrackEntry; + dataStream >> *te; + te->m_startTime -= offset; + tes += te; + } + + return tes; +} + + +bool TrackContentWidget::dragEnter(const QByteArray &itemData) +{ + dragEnd(); + + TemporaryPointerVector tes(dataToItems(itemData, m_dragOffsetTime)); + + if (tes.isEmpty()) + return false; + + for (TrackEntry *te : qAsConst(tes)) + { + if (!allowedByType(te)) + { + qWarning() << "TrackContentWidget::dragEnter: topic #" << te->m_topicId << "type" << (int)te->m_type << "not allowed"; + return false; + } + } + + if (m_isFilter && m_mainTopicId != -1) + { + auto it = std::find_if(tes.begin(), tes.end(), [this](const TrackEntry * a) + { + return (a->m_topicId == m_mainTopicId); + }); + + if (it != tes.end()) + return false; + } + + const QVector &thisTes = getTrackEntries(); + + + if (m_isMultiLineMultiSelectionMode) + { + for (TrackEntry *te : qAsConst(tes)) + { + bool found = false; + + for (TrackEntry *thisTe : qAsConst(thisTes)) + { + if (thisTe->m_topicId == te->m_topicId) + { + found = true; + break; + } + } + + if (!found) + return false; + } + } + + sortTrackEntriesChronologically(tes); + + + + if (!m_isFilter && tes.size() == 1) + { + TrackEntry *pPreview = tes.front(); + bool hasJustBeenAddedByUser = true; + + for (auto pTE : qAsConst(m_entryList)) + { + if (pPreview->m_topicId == pTE->m_topicId) + { + hasJustBeenAddedByUser = false; + break; + } + } + + + for (const auto &tcw : qAsConst(m_tcwCorresponding)) + { + for (auto pTE : qAsConst(tcw->m_entryList)) + { + if (pPreview->m_topicId == pTE->m_topicId) + { + hasJustBeenAddedByUser = false; + break; + } + } + } + + if (hasJustBeenAddedByUser) + { + + m_selected.clear(); + + for (const auto &tcw : qAsConst(m_tcwCorresponding)) + tcw->m_selected.clear(); + } + } + + tes.setDiscardable(false); + m_preview = tes; + return true; +} + +void TrackContentWidget::clearDrag() +{ + qDeleteAll(m_preview); + m_preview.clear(); + m_dragOffsetTime = -1; + m_dragPos = -1; + m_dragPossible = false; + m_moveValid = false; + update(); +} + +int TrackContentWidget::getDropPos() const +{ + return (m_dragHandler == nullptr) ? -1 : m_dragHandler->m_dropPos; +} + + +void TrackContentWidget::dragEnd() +{ + clearDrag(); +} + +bool TrackContentWidget::dragDrop(int pos) +{ + if (!isDragPossible() || !m_moveValid) + { + dragEnd(); + return false; + } + + int dropTime = m_tools->getTime(pos, m_isFilter) - m_dragOffsetTime; + QVector selectedOld = m_selected; + + if (!QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && !m_isMultiLineMultiSelectionMode) + unselect(); + + int firstTeTime = -1; + + for (const TrackEntry *te : qAsConst(m_preview)) + { + if (firstTeTime > te->m_startTime || firstTeTime == -1) + firstTeTime = te->m_startTime; + } + + int selected = firstTeTime; + int diff = dropTime - selected; + + + for (auto i = m_preview.constBegin(); i != m_preview.constEnd(); ++i) + { + TrackEntry *prevTe = *i; + TrackEntry *newTe = new TrackEntry(prevTe); + newTe->m_topicId = m_scriptTimeline->nextUniqueId(); + newTe->m_startTime = prevTe->m_startTime + diff; + newTe->isSet = true; + + + + m_unselectAllImmune = true; + if (m_isFilter) + { + addTrackEntry(newTe, true); + } + else + { + if (m_trackWidget == nullptr || !m_trackWidget->addTrackEntry(newTe, true)) + { + delete newTe; + return false; + } + } + + update(); + + + for (const auto &tcw : qAsConst(m_tcwCorresponding)) + tcw->removeTrackEntryByTopicId(prevTe); + + m_unselectAllImmune = false; + } + + dragEnd(); + + if (m_isMultiLineMultiSelectionMode) + { + + for (TrackEntry *oldSel : qAsConst(selectedOld)) + emit signalTrackEntryDeleted(oldSel->m_topicId); + qDeleteAll(selectedOld); + + emit signalMultiLineMultiSelectionDone(pos); + } + + emit signalChangesMade(); + return true; +} + +bool TrackContentWidget::dragMove(int pos) +{ + m_dragPos = pos; + + + int dropTime_s = m_tools->getTime(m_dragPos, m_isFilter) - m_dragOffsetTime; + checkForCollisions(this, m_preview, dropTime_s, m_dragHandler->m_vecCollisions, true); + + m_dragHandler->m_vecParallel.clear(); + for (const auto &tcw : qAsConst(m_tcwCorresponding)) + checkForParallelTopics(tcw, m_preview, dropTime_s, m_dragHandler->m_vecParallel); + + m_moveValid = m_dragHandler->m_vecCollisions.isEmpty() && m_dragHandler->m_vecParallel.isEmpty(); + + if (!m_moveValid || !isDragPossible()) + { + + setCursor(Qt::ForbiddenCursor); + } + else + { + setCursor(Qt::DragMoveCursor); + } + + update(); + + if (m_isMultiLineMultiSelectionMode) + { + if (m_moveValid) + emit signalMultiLineMultiSelectionOkay(); + else + emit signalMultiLineMultiSelectionFailed(); + } + + + + static int scrollByPixels = 1; + + if (pos >= (m_width - 60)) + emit signalScroll(scrollByPixels); + else if (pos < 60) + emit signalScroll(-scrollByPixels); + + return m_moveValid; +} + + +TrackEntry *TrackContentWidget::trackEntryAtPos(int x) const +{ + int time = m_tools->getTime(x); + const QVector tes = getTrackEntries(); + + for (TrackEntry *te : tes) + { + int start = te->m_startTime; + if (time >= start && time <= (start + te->m_length)) + return te; + } + + return nullptr; +} + + +QByteArray TrackContentWidget::itemsToData(const QVector &items, int dragPosTime, bool calculateFirstTeTime) +{ + int count = items.count(); + if (!count) + return {}; + + QByteArray itemData; + QDataStream dataStream(&itemData, QIODevice::WriteOnly); + + dataStream << count; + + if (dragPosTime != -1) + { + int firstTeTime = -1; + + if (calculateFirstTeTime) + { + for (TrackEntry *te : items) + { + if (firstTeTime > te->m_startTime || firstTeTime == -1) + firstTeTime = te->m_startTime; + } + } + else + firstTeTime = 0; + + int finalOffsetTime = dragPosTime - firstTeTime; + dataStream << finalOffsetTime; + } + else + dataStream << 0; + + + for (auto i = items.constBegin(); i != items.constEnd(); ++i) + { + TrackEntry *te = *i; + dataStream << *te; + } + + return itemData; +} + + +QByteArray TrackContentWidget::selectedItemsToData(int dragPosTime, bool calculateFirstTeTime) +{ + return itemsToData(m_selected, dragPosTime, calculateFirstTeTime); +} + + +void TrackContentWidget::setDragPossible(bool possible) +{ + m_dragPossible = possible; + update(); +} + +} diff --git a/Lts/Common/LtsScriptTimeline/track_content_widget.h b/Lts/Common/LtsScriptTimeline/track_content_widget.h new file mode 100755 index 000000000..a29435791 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/track_content_widget.h @@ -0,0 +1,186 @@ +#pragma once + +#include + +#include + + +namespace LtsScriptTimeline +{ +class TrackEntry; +enum class TrackEntryType; +class TrackWidget; +class ScriptTimeline; +class Tools; +class DragHandler; +class ResizeHandler; +class SelectHandler; + +LTSSCRIPTTIMELINE_EXPORT QDataStream &operator<<(QDataStream &ds, const TrackEntry &te); +LTSSCRIPTTIMELINE_EXPORT QDataStream &operator>>(QDataStream &ds, TrackEntry &te); +LTSSCRIPTTIMELINE_EXPORT bool isCollision(int start_s, int end_s, int toCompare_start_s, int toCompare_end_s); +LTSSCRIPTTIMELINE_EXPORT bool isCollision2(int startPreview_s, int endPreview_s, const TrackEntry *pTrackEntry); + +class LTSSCRIPTTIMELINE_EXPORT TrackContentWidget : public QWidget +{ + Q_OBJECT + +public: + explicit TrackContentWidget(QWidget *parent = nullptr, bool isFilter = false, Tools *tools = nullptr, int mainTopicId = -1); + ~TrackContentWidget() override; + + bool addTrackEntry(TrackEntry *te, bool selectTrackEntry); + void removeTrackEntryByTopicId(TrackEntry *te); + + void selectAll(); + void unselectAll(); + const QVector &getTrackEntries() const { return m_entryList; } + const QVector &getSelectedEntries() const { return m_selected; } + void setIndicatorTime(int time); + void setTrackName(const QString &trackName); + void deleteSelectedTopics(); + void selectEntry(int topicId); + void setTopicFilterId(int id) { m_topicFilterId = id; } + int topicFilterId() const { return m_topicFilterId; } + void setReadonly(bool readonly); + void addAllowedType(const TrackEntryType &tet) { m_allowedTypes.append(tet); } + void addDisallowedType(const TrackEntryType &tet) { m_disallowedTypes.append(tet); } + void setTrackWidget(TrackWidget *trackWidget); + void setScriptTimeline(ScriptTimeline *scriptTimeline) { m_scriptTimeline = scriptTimeline; } + void setTools(Tools *tools); + void addCorrespondingTrackWidget(TrackContentWidget *pCorrespondingTrackWidget); + static QString parallelError(); + bool isMultiLineMultiSelectionMode() const { return m_isMultiLineMultiSelectionMode; } + void setMultiLineMultiSelectionMode(bool active) { m_isMultiLineMultiSelectionMode = active; } + bool isDragPossible() const { return (!m_isMultiLineMultiSelectionMode || m_dragPossible); } + void selectEntriesAtPositions(int fromPos, int toPos); + void forceLocalUnselect(); + + static bool checkForParallelTopics(const TrackContentWidget *pTCW, const QVector &vecPreviewTrackEntries, int dropTime_s, bool hack = false); + static bool checkForParallelTopics(const TrackContentWidget *tcw, const QVector &preview, int dropTime_s, QVector &vecParallelTopics, bool ignoreSelectedTopics = false, bool hack = false); + static bool checkForCollisions(const TrackContentWidget *tcw, const QVector &preview, int dropTime_s, + QVector &vecCollisions, bool ignoreSelected = false, bool ignoreSelfCheck = false); + static bool checkForCollisions(const TrackContentWidget *pTCW, const QVector &vecPreviewTrackEntries, int dropTime_s); + bool checkForCollisionsSimple(const TrackEntry &previewEntry) const; + + QByteArray selectedItemsToData(int dragPosTime = 0, bool calculateFirstTeTime = true); + static QByteArray itemsToData(const QVector &items, int dragPosTime = 0, bool calculateFirstTeTime = true); + static QVector dataToItems(const QByteArray &data, int &offset); + bool canPasteTopics(const QVector &topics, int offsetTime, int pasteTime) const; + void forcePasteTopics(const QVector &topics, int offsetTime, int pasteTime); + bool canMoveAll(int offset) const; + void forceMoveAll(int offset); + bool setDescriptionForSelected(); + + static QString mime; + + void dragEnd(); + void setDragPossible(bool possible); + void dragLeave(); + bool dragEnter(const QByteArray &itemData); + bool dragMove(int pos); + bool dragDrop(int pos); + void clearDrag(); + int getDropPos() const; + + TrackWidget *trackWidget() { return m_trackWidget; } + TrackEntry *trackEntryAtPos(int x) const; + + void keyPressEvent(QKeyEvent *event) override; + +protected: + void paintEvent(QPaintEvent *event) override; + void dragEnterEvent(QDragEnterEvent *event) override; + void dragLeaveEvent(QDragLeaveEvent *event) override; + void dropEvent(QDropEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; + +private: + TrackEntry *getTrackEntryOnPos(QPoint globalPos); + bool allowedByType(const TrackEntry *te) const; + TrackEntry *getParentTopicOfFilter(TrackEntry *filterTe) const; + + int m_topicFilterId = -1; + + friend class SelectHandler; + SelectHandler *m_selectHandler; + + friend class ResizeHandler; + ResizeHandler *m_resizeHandler; + + friend class DragHandler; + DragHandler *m_dragHandler; + + + void selectEntry(TrackEntry *te); + void addFilterType(const TrackEntryType &tet); + void unselect(); + void moveAll(int offset); + bool handleResize(int cursorTime, bool leftButton); + bool handleDrag(int cursorTime, bool leftButton); + void cancelResize(); + TrackEntry *getTrackEntryAt(int clickedTime); + void clearPreview(); + int dragPos() const { return m_dragPos; } + + + + int m_dragOffsetTime; + int m_dragPos; + QVector m_entryList; + QVector m_selected; + + TrackEntry *m_mainSelected; + QVector m_preview; + int m_width; + bool m_cursorSet; + int m_oldEntryLength; + bool m_unselectAllImmune; + int m_indicatorTime; + QString m_trackName; + QVector m_allowedTypes; + QVector m_disallowedTypes; + bool m_isFilter; + bool m_readonly; + TrackWidget *m_trackWidget; + ScriptTimeline *m_scriptTimeline; + Tools *m_tools; + QVector m_tcwCorresponding; + bool m_isMultiLineMultiSelectionMode; + bool m_multiLineMultiSelectionOkay; + bool m_dragPossible; + bool m_moveValid; + int m_mainTopicId; + +signals: + void signalUnselectAll(); + void signalTrackEntrySelectionChanged(TrackEntry *); + void signalChangesMade(); + void signalTrackEntryAdded(TrackEntry *te); + void signalTrackEntryDeleted(const int topicId); + void signalEditEntry(const int topicId); + void signalMultiLineMultiSelectionFailed(); + void signalMultiLineMultiSelectionOkay(); + void signalMultiLineMultiSelectionDone(int dropPos); + void signalMultiLineMultiSelectionStart(int dragPosTime); + void signalMultiLineMultiSelectionMove(int x); + void signalStartRubberband(QPoint pos); + void signalStopRubberband(); + void signalEscapeRubberband(); + void signalModifyRubberband(); + void signalSelectAllGlobal(); + void signalCopyTopics(); + void signalPasteTopics(); + void signalCutTopics(); + void signalDeleteSelectedTopics(); + void signalSave(); + void signalMoveAll(int offset); + void signalScroll(int incr); +}; + +} diff --git a/Lts/Common/LtsScriptTimeline/track_drag_handler.cpp b/Lts/Common/LtsScriptTimeline/track_drag_handler.cpp new file mode 100755 index 000000000..309364b3a --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/track_drag_handler.cpp @@ -0,0 +1,181 @@ +#include "track_drag_handler.h" + +#include "track_content_widget.h" +#include "track_entry.h" +#include "tools.h" + +#include "track_widget.h" +#include "script_timeline.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include + + +namespace LtsScriptTimeline +{ + +void DragHandler::dragEnterEvent(QDragEnterEvent *event) +{ + if (m_tcw->isMultiLineMultiSelectionMode()) + { + if (event->source() != m_tcw) + { + event->ignore(); + return; + } + else + { + for (const auto &tcw : qAsConst(m_tcwCorresponding)) + tcw->dragEnter(tcw->selectedItemsToData(tcw->m_tools->getTime(m_dragStartPos.x()))); + } + } + + m_tcw->dragEnd(); + + if (!event->mimeData()->hasFormat(TrackContentWidget::mime)) + return; + + if (m_tcw->dragEnter(event->mimeData()->data(TrackContentWidget::mime))) + event->acceptProposedAction(); +} + + +void DragHandler::dragLeaveEvent(QDragLeaveEvent *event) +{ + if (m_tcw->isMultiLineMultiSelectionMode()) + { + event->ignore(); + for (const auto &tcw : qAsConst(m_tcwCorresponding)) + tcw->dragEnd(); + } + + m_tcw->dragEnd(); +} + + +void DragHandler::dropEvent(QDropEvent *event) +{ + int dropPos = event->pos().x(); + int dropTime = m_tcw->m_tools->getTime(dropPos, m_tcw->m_isFilter) - m_tcw->m_dragOffsetTime; + + + + for (const auto &tcw : qAsConst(m_tcwCorresponding)) + { + bool hasParallelTopicsOfSameType = TrackContentWidget::checkForParallelTopics(tcw, m_tcw->m_preview, dropTime); + + if (hasParallelTopicsOfSameType) + { + new LtsWidgets::LtsMessageDialogNew(m_tcw, TrackContentWidget::parallelError(), QObject::tr("Error")); + break; + } + } + + if (m_tcw->dragDrop(dropPos)) + event->acceptProposedAction(); + else + emit m_tcw->signalMultiLineMultiSelectionDone(0); +} + + +void DragHandler::dragMoveEvent(QDragMoveEvent *event) +{ + int pos = event->pos().x(); + + if (m_tcw->dragMove(pos)) + event->acceptProposedAction(); + + + if (m_tcw->isMultiLineMultiSelectionMode()) + emit m_tcw->signalMultiLineMultiSelectionMove(pos); +} + + +void DragHandler::mousePressEvent(QMouseEvent *event) +{ + if (event->buttons() != Qt::LeftButton) + return; + m_dragStartPos = event->pos(); +} + + +void DragHandler::mouseMoveEvent(QMouseEvent *event) +{ + if (event->buttons() != Qt::LeftButton) + return; + + QPoint cursorPos = m_tcw->mapFromGlobal(event->globalPos()); + + + if (!m_tcw->m_mainSelected + || ((cursorPos - m_dragStartPos).manhattanLength() < QApplication::startDragDistance() + && (m_dragStartPos - cursorPos).manhattanLength() < QApplication::startDragDistance())) + return; + + QVector selected = m_tcw->getSelectedEntries(); + + if (selected.isEmpty()) + return; + + int cursorPosTime = m_tcw->m_tools->getTime(m_dragStartPos.x()); + + QMimeData *mimeData = new QMimeData(); + mimeData->setData(TrackContentWidget::mime, m_tcw->selectedItemsToData(cursorPosTime)); + + QDrag *drag = new QDrag(m_tcw); + drag->setMimeData(mimeData); + + if (m_tcw->isMultiLineMultiSelectionMode()) + emit m_tcw->signalMultiLineMultiSelectionStart(cursorPosTime); + + QVector selectedOld = m_tcw->m_selected; + + if (drag->exec(Qt::MoveAction) == Qt::MoveAction) + { + if (!m_tcw->isMultiLineMultiSelectionMode()) + { + + for (auto i = selectedOld.begin(); i != selectedOld.end(); ++i) + emit m_tcw->signalTrackEntryDeleted((*i)->m_topicId); + qDeleteAll(selectedOld); + } + } + else + { + emit m_tcw->signalMultiLineMultiSelectionDone(-1); + } +} + + +void DragHandler::keyPressEvent(QKeyEvent *event) +{ + switch (event->key()) + { + case Qt::Key_Left: + { + emit m_tcw->signalMoveAll(-1); + break; + } + + case Qt::Key_Right: + { + emit m_tcw->signalMoveAll(1); + break; + } + + default: + { + break; + } + } +} + +} diff --git a/Lts/Common/LtsScriptTimeline/track_drag_handler.h b/Lts/Common/LtsScriptTimeline/track_drag_handler.h new file mode 100755 index 000000000..185db4c25 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/track_drag_handler.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include + +namespace LtsScriptTimeline +{ +class TrackContentWidget; +class TrackEntry; + +class LTSSCRIPTTIMELINE_EXPORT DragHandler +{ +public: + explicit DragHandler(TrackContentWidget *_tcw) + : m_tcw(_tcw) + , m_dropPos(-1) + {} + + void dragEnterEvent(QDragEnterEvent *event); + void dragLeaveEvent(QDragLeaveEvent *event); + void dropEvent(QDropEvent *event); + void dragMoveEvent(QDragMoveEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void keyPressEvent(QKeyEvent *event); + + TrackContentWidget *m_tcw; + QVector m_tcwCorresponding; + + QPoint m_dragStartPos; + QVector m_vecCollisions, m_vecParallel; + int m_dropPos; +}; +} diff --git a/Lts/Common/LtsScriptTimeline/track_entry.h b/Lts/Common/LtsScriptTimeline/track_entry.h new file mode 100755 index 000000000..98ca84186 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/track_entry.h @@ -0,0 +1,187 @@ +#pragma once + +#include +#include "track_entry_painters.h" + +#include +#include + +namespace LtsScriptTimeline +{ + +class LTSSCRIPTTIMELINE_EXPORT TrackEntry : public QObject +{ + Q_OBJECT + +public: + explicit TrackEntry(const TrackEntryType &tet = TrackEntryType::INVALID) + : QObject() + , m_topicId(-1) + , m_startTime(-1) + , m_length(-1) + , m_type(tet) + , isSet(false) + , index(-1) + , x(-1) + , y(-1) + , h(-1) + , w(-1) + {} + + explicit TrackEntry(int topicId, + int startTime, + int length, + LtsScriptTimeline::TrackEntryType type, + const QJsonObject &detailData, + const QString &channelName, + const QString &description) + : QObject() + , m_topicId(topicId) + , m_startTime(startTime) + , m_length(length) + , m_type(type) + , isSet(false) + , index(-1) + , x(-1) + , y(-1) + , h(-1) + , w(-1) + , m_detailData(detailData) + , m_channelName(channelName) + + , m_description(description) + {} + + + explicit TrackEntry(const TrackEntry &te) + : QObject() + , m_topicId(te.m_topicId) + , m_startTime(te.m_startTime) + , m_length(te.m_length) + , m_type(te.m_type) + , isSet(te.isSet) + , index(te.index) + , x(te.x) + , y(te.y) + , h(te.h) + , w(te.w) + , m_detailData(te.m_detailData) + , m_channelName(te.m_channelName) + , m_description(te.m_description) + , m_filterParentChannel(te.m_filterParentChannel) + {} + + explicit TrackEntry(TrackEntry *te) + : QObject() + , m_topicId(te->m_topicId) + , m_startTime(te->m_startTime) + , m_length(te->m_length) + , m_type(te->m_type) + , isSet(te->isSet) + , index(te->index) + , x(te->x) + , y(te->y) + , h(te->h) + , w(te->w) + , m_detailData(te->m_detailData) + , m_channelName(te->m_channelName) + , m_description(te->m_description) + , m_filterParentChannel(te->m_filterParentChannel) + {} + + TrackEntry(const TrackEntryType &tet, int length) + : QObject() + , m_topicId(-1) + , m_startTime(-1) + , m_length(length) + , m_type(tet) + , isSet(false) + , index(-1) + , x(-1) + , y(-1) + , h(-1) + , w(-1) + {} + + ~TrackEntry() + { + + } + + TrackEntry &operator=(const TrackEntry &other) + { + this->m_topicId = other.m_topicId; + this->m_startTime = other.m_startTime; + this->m_length = other.m_length; + this->m_type = other.m_type; + this->isSet = other.isSet; + this->index = other.index; + this->x = other.x; + this->y = other.y; + this->h = other.h; + this->w = other.w; + this->m_detailData = other.m_detailData; + this->m_channelName = other.m_channelName; + this->m_filterParentChannel = other.m_filterParentChannel; + this->m_description = other.m_description; + + return *this; + } + + int m_topicId; + int m_startTime; + int m_length; + TrackEntryType m_type; + bool isSet; + int index; + + + double x; + double y; + double h; + double w; + + QJsonObject m_detailData; + QString m_channelName; + QString m_description; + QString m_filterParentChannel; + + QJsonObject toJson() const + { + return QJsonObject({{QStringLiteral("topicId"), m_topicId}, + {QStringLiteral("startTime"), m_startTime}, + {QStringLiteral("length"), m_length}, + {QStringLiteral("type"), static_cast(m_type)}, + {QStringLiteral("isSet"), isSet}, + {QStringLiteral("index"), index}, + {QStringLiteral("x"), x}, + {QStringLiteral("y"), y}, + {QStringLiteral("w"), w}, + {QStringLiteral("h"), h}, + {QStringLiteral("detailData"), m_detailData}, + {QStringLiteral("channelName"), m_channelName}, + {QStringLiteral("description"), m_description}, + {QStringLiteral("filterParentChannel"), m_filterParentChannel} + }); + } + + void fromJson(const QJsonObject &obj) + { + m_topicId = obj[QStringLiteral("topicId")].toInt(); + m_startTime = obj[QStringLiteral("startTime")].toInt(-1); + m_length = obj[QStringLiteral("length")].toInt(-1); + m_type = static_cast(obj[QStringLiteral("type")].toInt()); + isSet = obj[QStringLiteral("isSet")].toBool(); + index = obj[QStringLiteral("index")].toInt(); + x = obj[QStringLiteral("x")].toDouble(); + y = obj[QStringLiteral("y")].toDouble(); + w = obj[QStringLiteral("w")].toDouble(); + h = obj[QStringLiteral("h")].toDouble(); + m_detailData = obj[QStringLiteral("detailData")].toObject(); + m_channelName = obj[QStringLiteral("channelName")].toString(); + m_description = obj[QStringLiteral("description")].toString(); + m_filterParentChannel = obj[QStringLiteral("filterParentChannel")].toString(); + } +}; + +} diff --git a/Lts/Common/LtsScriptTimeline/track_entry_painters.cpp b/Lts/Common/LtsScriptTimeline/track_entry_painters.cpp new file mode 100755 index 000000000..4d01a6b47 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/track_entry_painters.cpp @@ -0,0 +1,321 @@ + + + +#include "track_entry_painters.h" +#include "track_entry.h" + +#include +#include + + + + +namespace LtsScriptTimeline +{ + +QString translateTrackEntryType(const TrackEntryType &tet) +{ + switch (tet) + { + case TrackEntryType::INVALID: return QObject::tr(""); + case TrackEntryType::SituationMap: return QObject::tr("Map"); + case TrackEntryType::Situation: return QObject::tr("Situation"); + case TrackEntryType::SituationReplay: return QObject::tr("Replay"); + case TrackEntryType::Snapshot: return QObject::tr("Snapshot"); + case TrackEntryType::Image: return QObject::tr("Image"); + case TrackEntryType::Text: return QObject::tr("Text"); + case TrackEntryType::Pdf: return QObject::tr("Pdf"); + case TrackEntryType::Xml: return QObject::tr("Xml"); + case TrackEntryType::MSOffice: return QObject::tr("MS Office"); + case TrackEntryType::OpenOffice: return QObject::tr("Open Office"); + case TrackEntryType::Audio: return QObject::tr("Audio"); + case TrackEntryType::Video: return QObject::tr("Video"); + case TrackEntryType::Building: return QObject::tr("Building"); + case TrackEntryType::Evaluation: return QObject::tr("Evaluation"); + case TrackEntryType::Report: return QObject::tr("Report"); + + + case TrackEntryType::TacticalPlan: return QObject::tr("Tactical Plan Map Filter"); + case TrackEntryType::SituationMapFilter: return QObject::tr("Map Filter"); + } + + return QString(); +} + + +QString trackEntryTypeName(const TrackEntryType &tet) +{ + switch (tet) + { + case TrackEntryType::INVALID: return QStringLiteral(""); + case TrackEntryType::SituationMap: return QStringLiteral("SituationMap"); + case TrackEntryType::Situation: return QStringLiteral("SituationReplayAndMap"); + case TrackEntryType::SituationReplay: return QStringLiteral("SituationReplay"); + case TrackEntryType::Image: return QStringLiteral("Image"); + case TrackEntryType::Text: return QStringLiteral("Text"); + case TrackEntryType::Pdf: return QStringLiteral("Pdf"); + case TrackEntryType::Xml: return QStringLiteral("Xml"); + case TrackEntryType::TacticalPlan: return QStringLiteral("TacticalPlan"); + case TrackEntryType::MSOffice: return QStringLiteral("MSOffice"); + case TrackEntryType::OpenOffice: return QStringLiteral("OpenOffice"); + case TrackEntryType::Audio: return QStringLiteral("Audio"); + case TrackEntryType::Video: return QStringLiteral("Video"); + case TrackEntryType::Building: return QStringLiteral("Building"); + case TrackEntryType::SituationMapFilter: return QStringLiteral("SituationMapFilter"); + case TrackEntryType::Snapshot: return QStringLiteral("Snapshot"); + case TrackEntryType::Evaluation: return QStringLiteral("Evaluation"); + case TrackEntryType::Report: return QStringLiteral("Report"); + } + + return QString(); +} + + +QIcon appToIcon(const TrackEntryType type) +{ + switch (type) + { + case LtsScriptTimeline::TrackEntryType::INVALID: + return QIcon(); + case LtsScriptTimeline::TrackEntryType::SituationMap: + return QIcon(QLatin1String("icon:add_situation.svg")); + case LtsScriptTimeline::TrackEntryType::Situation: + return QIcon(QLatin1String("icon:add_situation_combined.svg")); + case LtsScriptTimeline::TrackEntryType::SituationReplay: + return QIcon(QLatin1String("icon:add_restart.svg")); + case LtsScriptTimeline::TrackEntryType::Image: + return QIcon(QLatin1String("icon:create_image_topic.svg")); + case LtsScriptTimeline::TrackEntryType::Text: + return QIcon(QLatin1String("icon:document-new-txt.svg")); + case LtsScriptTimeline::TrackEntryType::Pdf: + return QIcon(QLatin1String("icon:create_pdf_topic.svg")); + case LtsScriptTimeline::TrackEntryType::Xml: + return QIcon(QLatin1String("icon:document-new-xml.svg")); + case LtsScriptTimeline::TrackEntryType::TacticalPlan: + return QIcon(QLatin1String("icon:tactical_organization.svg")); + case LtsScriptTimeline::TrackEntryType::MSOffice: + return QIcon(QLatin1String("icon:ms_office.svg")); + case LtsScriptTimeline::TrackEntryType::OpenOffice: + return QIcon(QLatin1String("icon:documents.svg")); + case LtsScriptTimeline::TrackEntryType::Audio: + return QIcon(QLatin1String("icon:create_lecture_topic.svg")); + case LtsScriptTimeline::TrackEntryType::Video: + return QIcon(QLatin1String("icon:add_video_menu.svg")); + case LtsScriptTimeline::TrackEntryType::Building: + return QIcon(QLatin1String("icon:configure_buildings.svg")); + case LtsScriptTimeline::TrackEntryType::SituationMapFilter: + return QIcon(QLatin1String("icon:filter_player.svg")); + case LtsScriptTimeline::TrackEntryType::Snapshot: + return QIcon(QLatin1String("icon:create_snapshot.svg")); + case LtsScriptTimeline::TrackEntryType::Evaluation: + return QIcon(QLatin1String("icon:add_evaluation_elements.svg")); + case LtsScriptTimeline::TrackEntryType::Report: + return QIcon(QLatin1String("icon:add_reports_display.svg")); + } + + return QIcon(); +} + + +TrackEntryPainter *getTrackEntryPainter(const TrackEntryType &type) +{ + TrackEntryPainter *trackEntryPainter = nullptr; + static TrackEntryPainter painter; + + switch (type) + { + case TrackEntryType::SituationMap: + { + painter = TrackEntryPainter(QColor(QStringLiteral("#b1cbbb")), translateTrackEntryType(type)); + trackEntryPainter = static_cast(&painter); + break; + } + + case TrackEntryType::Situation: + { + painter = TrackEntryPainter(QColor(QStringLiteral("#b1c1cb")), translateTrackEntryType(type)); + trackEntryPainter = static_cast(&painter); + break; + } + + case TrackEntryType::SituationReplay: + { + painter = TrackEntryPainter(QColor(QStringLiteral("#c94c4c")), translateTrackEntryType(type)); + trackEntryPainter = static_cast(&painter); + break; + } + + case TrackEntryType::Audio: + { + painter = TrackEntryPainter(QColor(QStringLiteral("#034f84")), translateTrackEntryType(type)); + trackEntryPainter = static_cast(&painter); + break; + } + + case TrackEntryType::Video: + { + painter = TrackEntryPainter(QColor(QStringLiteral("#618685")), translateTrackEntryType(type)); + trackEntryPainter = static_cast(&painter); + break; + } + + case TrackEntryType::Image: + { + painter = TrackEntryPainter(QColor(QStringLiteral("#fefbd8")), translateTrackEntryType(type)); + trackEntryPainter = static_cast(&painter); + break; + } + + case TrackEntryType::Evaluation: + { + painter = TrackEntryPainter(QColor(QStringLiteral("#d6cbd3")), translateTrackEntryType(type)); + trackEntryPainter = static_cast(&painter); + break; + } + + case TrackEntryType::Pdf: + { + painter = TrackEntryPainter(QColor(QStringLiteral("#80ced6")), translateTrackEntryType(type)); + trackEntryPainter = static_cast(&painter); + break; + } + + case TrackEntryType::Text: + { + painter = TrackEntryPainter(QColor(QStringLiteral("#92a8d1")), translateTrackEntryType(type)); + trackEntryPainter = static_cast(&painter); + break; + } + + case TrackEntryType::Xml: + { + painter = TrackEntryPainter(QColor(QStringLiteral("#eea29a")), translateTrackEntryType(type)); + trackEntryPainter = static_cast(&painter); + break; + } + + case TrackEntryType::TacticalPlan: + { + painter = TrackEntryPainter(QColor(QStringLiteral("#dac292")), translateTrackEntryType(type)); + trackEntryPainter = static_cast(&painter); + break; + } + + case TrackEntryType::MSOffice: + { + painter = TrackEntryPainter(QColor(QStringLiteral("#f7cac9")), translateTrackEntryType(type)); + trackEntryPainter = static_cast(&painter); + break; + } + + case TrackEntryType::OpenOffice: + { + painter = TrackEntryPainter(QColor(QStringLiteral("#b2b2b2")), translateTrackEntryType(type)); + trackEntryPainter = static_cast(&painter); + break; + } + + case TrackEntryType::Building: + { + painter = TrackEntryPainter(QColor(QStringLiteral("#c6bcb6")), translateTrackEntryType(type)); + trackEntryPainter = static_cast(&painter); + break; + } + + case TrackEntryType::SituationMapFilter: + { + painter = TrackEntryPainter(QColor(QStringLiteral("#36486b")), translateTrackEntryType(type)); + trackEntryPainter = static_cast(&painter); + break; + } + + case TrackEntryType::Snapshot: + { + painter = TrackEntryPainter(QColor(QStringLiteral("#f18973")), translateTrackEntryType(type)); + trackEntryPainter = static_cast(&painter); + break; + } + + case TrackEntryType::Report: + { + painter = TrackEntryPainter(QColor(QStringLiteral("#feb236")), translateTrackEntryType(type)); + trackEntryPainter = static_cast(&painter); + break; + } + + default: + { + qWarning() << "no painter found for type" << (int)type; + trackEntryPainter = nullptr; + break; + } + } + + return trackEntryPainter; +} + + + +void drawTrackEntry(TrackEntry *te, QPainter &painter, const QRect &trackRect, bool isSelected, bool isEnabled) +{ + TrackEntryPainter *trackEntryPainter = getTrackEntryPainter(te->m_type); + + if (!trackEntryPainter) + + return; + + QColor trackEntryBgColor = trackEntryPainter->bgColor(); + + if (isSelected) + { + int r = trackEntryBgColor.red(); + int g = trackEntryBgColor.green(); + int b = trackEntryBgColor.blue(); + + trackEntryBgColor = trackEntryBgColor.lighter(); + painter.setPen(QColor(255 - r, 255 - g, 255 - b)); + } + else if (!isEnabled) + { + trackEntryBgColor = QColor(200, 200, 200); + painter.setPen(QColor("white")); + } + else + painter.setPen(QColor(36, 36, 36)); + + painter.setBrush(trackEntryBgColor); + painter.drawRect(trackRect); + + if (trackRect.width() < 20) + return; + + QRect trackEntryPainterRect(trackRect.adjusted(1, 1, -1, -1)); + + painter.save(); + painter.setClipping(true); + painter.setClipRect(trackEntryPainterRect, Qt::ReplaceClip); + + + trackEntryPainter->paintEvent(painter, trackEntryPainterRect ); + painter.drawText(trackEntryPainterRect.adjusted(3, 16, 5000, 0), te->m_description); + painter.restore(); + + if (trackRect.width() < 12) + return; + + + int trackRectEnd = trackRect.right(); + int gradientLength = 12; + QRect gradientRect(trackRectEnd - gradientLength, 2, gradientLength, trackRect.height() - 2); + QLinearGradient gradient(gradientRect.bottomRight(), gradientRect.bottomLeft()); + + int r = trackEntryBgColor.red(); + int g = trackEntryBgColor.green(); + int b = trackEntryBgColor.blue(); + + gradient.setColorAt(0, QColor(r, g, b, 255)); + gradient.setColorAt(1, QColor(r, g, b, 0)); + painter.fillRect(gradientRect, gradient); +} + +} diff --git a/Lts/Common/LtsScriptTimeline/track_entry_painters.h b/Lts/Common/LtsScriptTimeline/track_entry_painters.h new file mode 100755 index 000000000..0a19597b4 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/track_entry_painters.h @@ -0,0 +1,90 @@ + + + +#pragma once + +#include + +#include +#include +#include +#include +#include + +namespace LtsScriptTimeline +{ +class TrackEntry; + +enum class TrackEntryType +{ + INVALID, + SituationMap, + SituationReplay, + Image, + Text, + Pdf, + Xml, + TacticalPlan, + MSOffice, + OpenOffice, + Audio, + Video, + Building, + SituationMapFilter, + Snapshot, + Evaluation, + Report, + Situation +}; +LTSSCRIPTTIMELINE_EXPORT QString translateTrackEntryType(const TrackEntryType &tet); +LTSSCRIPTTIMELINE_EXPORT QString trackEntryTypeName(const TrackEntryType &tet); +LTSSCRIPTTIMELINE_EXPORT QIcon appToIcon(const TrackEntryType type); +inline std::ostream &operator<<(std::ostream &stream, const TrackEntryType tet) +{ + return stream << trackEntryTypeName(tet).toStdString(); +} + +class LTSSCRIPTTIMELINE_EXPORT TrackEntryPainter +{ +public: + explicit TrackEntryPainter(const QColor &bgColor = QColor(0, 0, 0), const QString &trackEntryType = QStringLiteral("INVALID")) + : m_bgColor(bgColor) + , m_trackEntryType(trackEntryType) + { + } + + void paintEvent(QPainter &painter, const QRect &rect) + { + painter.setPen(QPen(Qt::black)); + painter.drawText(rect.adjusted(3, 0, 5000 , 0), m_trackEntryType); + } + + QColor bgColor() const + { + return m_bgColor; + } + + void setBgColor(const QColor &bgColor) + { + m_bgColor = bgColor; + } + + const QString &trackEntryType() const + { + return m_trackEntryType; + } + + void setTrackEntryType(const QString &trackEntryType) + { + m_trackEntryType = trackEntryType; + } + +protected: + QColor m_bgColor; + QString m_trackEntryType; +}; + +void drawTrackEntry(TrackEntry *te, QPainter &painter, const QRect &trackRect, bool isSelected, bool isEnabled = true); +TrackEntryPainter *getTrackEntryPainter(const TrackEntryType &type); + +} diff --git a/Lts/Common/LtsScriptTimeline/track_entry_preview_widget.cpp b/Lts/Common/LtsScriptTimeline/track_entry_preview_widget.cpp new file mode 100755 index 000000000..4643569a2 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/track_entry_preview_widget.cpp @@ -0,0 +1,105 @@ +#include "track_entry_preview_widget.h" +#include "ui_track_entry_preview_widget.h" +#include "track_entry.h" + +#include +#include + +#include +#include +#include +#include + + +namespace LtsScriptTimeline +{ +TrackEntryPreviewWidget::TrackEntryPreviewWidget(QWidget *parent) + : QWidget(parent) + , m_ui(new Ui::TrackEntryPreviewWidget) +{ + m_ui->setupUi(this); +#ifndef LTS_DEBUG + m_ui->lbTopicId->hide(); + m_ui->lblTopicId->hide(); + + m_ui->lbLen->hide(); + m_ui->lblLen->hide(); +#endif + connect(m_ui->widget, &TrackEntryPreviewer::signalDelete, + this, &TrackEntryPreviewWidget::signalDelete); + + + connect(m_ui->widget, &TrackEntryPreviewer::signalStartDrag, + this, &TrackEntryPreviewWidget::signalStartDrag); + connect(m_ui->widget, &TrackEntryPreviewer::signalStopDrag, + this, &TrackEntryPreviewWidget::signalStopDrag); +} + + +TrackEntryPreviewWidget::~TrackEntryPreviewWidget() +{ + delete m_ui; +} + + +void TrackEntryPreviewWidget::contextMenuEvent(QContextMenuEvent *event) +{ +#ifndef LTS_DEBUG + if (LtsSessionData::LtsSessionData::getInstance()->debugFlagValue(QStringLiteral("TrackEntryPreviewWidget.DebugMenu")) == QVariant(true)) +#endif + { + QMenu *m = new QMenu(this); + m->setAttribute(Qt::WA_DeleteOnClose); + + QPoint p = mapToGlobal(m->pos()); + m->setGeometry(QRect(p, QPoint(event->x(), event->y()))); + + m->addAction(QStringLiteral("enable"), this, [this]() + { + setTopicEnabled(true); + }); + + m->addAction(QStringLiteral("disable"), this, [this]() + { + setTopicEnabled(false); + }); + + m->addAction(QStringLiteral("show detail data"), this, [this]() + { + TrackEntry *te = m_ui->widget->trackEntry(); + + if (te == nullptr) + { + LtsWidgets::LtsMessageDialog::showMessage(this, QStringLiteral("no te was set")); + return; + } + + QTextEdit *ted = new QTextEdit(nullptr); + ted->setAttribute(Qt::WA_DeleteOnClose); + ted->setWindowTitle(QStringLiteral("Track Entry #%1 - Detail Data string").arg(te->m_topicId)); + ted->setText(QString::fromUtf8(QJsonDocument(te->m_detailData).toJson())); + ted->setReadOnly(true); + ted->show(); + }); + + m->show(); + } +} + + +void TrackEntryPreviewWidget::setTopicEnabled(bool enabled) +{ + m_ui->widget->setEnabled(enabled); +} + + +void TrackEntryPreviewWidget::setTrackEntry(TrackEntry *te) +{ + m_ui->lblDnD->setVisible(!te->isSet); + m_ui->widget->setVisible(!te->isSet); + + m_ui->lbTopicId->setText(QString::number(te->m_topicId)); + m_ui->lbLen->setText(QString::number(te->m_length)); + m_ui->widget->setTrackEntry(te); +} +} diff --git a/Lts/Common/LtsScriptTimeline/track_entry_preview_widget.h b/Lts/Common/LtsScriptTimeline/track_entry_preview_widget.h new file mode 100755 index 000000000..7862f5c82 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/track_entry_preview_widget.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include + + +namespace Ui +{ +class TrackEntryPreviewWidget; +} + +namespace LtsScriptTimeline +{ +class TrackEntry; + + +class LTSSCRIPTTIMELINE_EXPORT TrackEntryPreviewWidget : public QWidget +{ + Q_OBJECT + +public: + explicit TrackEntryPreviewWidget(QWidget *parent = nullptr); + ~TrackEntryPreviewWidget(); + + + + void setTrackEntry(TrackEntry *te); + + void setTopicEnabled(bool enabled); + +private: + Ui::TrackEntryPreviewWidget *m_ui; + + void contextMenuEvent(QContextMenuEvent *event); + +signals: + void signalStartDrag(); + void signalStopDrag(); + void signalDelete(); +}; + +} diff --git a/Lts/Common/LtsScriptTimeline/track_entry_previewer.cpp b/Lts/Common/LtsScriptTimeline/track_entry_previewer.cpp new file mode 100755 index 000000000..c2c67489a --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/track_entry_previewer.cpp @@ -0,0 +1,86 @@ +#include "track_entry_previewer.h" +#include "track_entry.h" +#include "track_entry_painters.h" +#include "track_content_widget.h" + +#include +#include +#include +#include + + +namespace LtsScriptTimeline +{ + +QDataStream &operator<<(QDataStream &ds, const TrackEntry &te); +QDataStream &operator>>(QDataStream &ds, TrackEntry &te); + +TrackEntryPreviewer::TrackEntryPreviewer(QWidget *parent) + : QWidget(parent) + , m_te(nullptr) +{ + + + setVisible(false); +} + + +TrackEntryPreviewer::~TrackEntryPreviewer() +{} + + +void TrackEntryPreviewer::setTrackEntry(TrackEntry *te) +{ + m_te = te; + + if (m_te->isSet) + return; + + connect(m_te, &TrackEntry::destroyed, + this, [this]() + { + m_te = nullptr; + emit signalDelete(); + }); +} + + +void TrackEntryPreviewer::mousePressEvent(QMouseEvent *event) +{ + + if (!m_te || event->buttons() != Qt::LeftButton) + return; + + m_te->isSet = true; + QByteArray itemData(TrackContentWidget::itemsToData({ m_te }, 0)); + m_te->isSet = false; + + QMimeData *mimeData = new QMimeData; + mimeData->setData(TrackContentWidget::mime, itemData); + + QDrag *drag = new QDrag(this); + drag->setDragCursor(QPixmap(), Qt::CopyAction); + drag->setMimeData(mimeData); + + emit signalStartDrag(); + + if (drag->exec(Qt::CopyAction) == Qt::CopyAction) + emit signalDelete(); + + emit signalStopDrag(); +} + + +void TrackEntryPreviewer::paintEvent(QPaintEvent *event) +{ + QWidget::paintEvent(event); + + QPainter painter(this); + + if (!m_te) + return; + + drawTrackEntry(m_te, painter, rect().adjusted(0, 0, -1, -1), false, isEnabled()); +} + +} diff --git a/Lts/Common/LtsScriptTimeline/track_entry_previewer.h b/Lts/Common/LtsScriptTimeline/track_entry_previewer.h new file mode 100755 index 000000000..79261e498 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/track_entry_previewer.h @@ -0,0 +1,38 @@ +#pragma once + +#include + + +namespace LtsScriptTimeline +{ +class TrackEntry; + + +class TrackEntryPreviewer : public QWidget +{ + Q_OBJECT + +public: + explicit TrackEntryPreviewer(QWidget *parent = nullptr); + ~TrackEntryPreviewer(); + + + + void setTrackEntry(TrackEntry *te); + TrackEntry *trackEntry() const { return m_te; } + +protected: + void paintEvent(QPaintEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + +private: + TrackEntry *m_te; + +signals: + void signalDelete(); + void signalStartDrag(); + void signalStopDrag(); + +}; + +} diff --git a/Lts/Common/LtsScriptTimeline/track_header.cpp b/Lts/Common/LtsScriptTimeline/track_header.cpp new file mode 100755 index 000000000..cf24ea392 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/track_header.cpp @@ -0,0 +1,191 @@ +#include "track_header.h" +#include "tools.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +namespace LtsScriptTimeline +{ + +TrackHeader::TrackHeader(QWidget *parent) + : QWidget(parent) + , m_indicatorTime(-1) + , m_textMargin(12) + , m_textSpacing(10) + , m_indicatorLock(false) + , m_tools(nullptr) +{ + setCursor(Qt::IBeamCursor); + + m_timeFormat = QStringLiteral("HH:mm:ss"); +} + + +void TrackHeader::setTools(Tools *tools) +{ + m_tools = tools; +} + + +void TrackHeader::mousePressEvent(QMouseEvent *event) +{ + if (event->buttons() != Qt::LeftButton) + return; + if (!m_indicatorLock) + { + setIndicatorTime(m_tools->getTime(event->localPos().x())); + emit signalHeaderIndicatorTimeUserChanged(m_indicatorTime); + } +} + + +void TrackHeader::setIndicatorTime(int time) +{ + m_indicatorTime = time; + emit signalHeaderIndicatorTimeChanged(m_indicatorTime); + update(); +} + + +void TrackHeader::recreatePositionLabels() +{ + + QTime t; + t.setHMS(23, 59, 59); + + QFontMetrics fm(QApplication::font()); + +#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0) + int currentTextWidth = fm.width(t.toString()); +#else + int currentTextWidth = fm.horizontalAdvance(t.toString()); +#endif + + if (currentTextWidth < 1 ) + { + qCritical() << "TrackHeader::recreatePositionLabels: invalid text size"; + return; + } + + + + int currentTextWidthTime = m_tools->getDuration(currentTextWidth); + + int time = m_tools->getDuration(m_textMargin); + int end = (m_tools->m_timelineLength * 2) + currentTextWidthTime; + + int paintStartTime = m_tools->getTime(0) - currentTextWidthTime; + int paintEndTime = m_tools->getTime(width() * 2); + int count = 0; + + m_positionMarkers.clear(); + + + + int textSpacingTime = m_tools->getDuration(m_textSpacing); + while (time < end && count < 5000) + { + + + if (time >= paintStartTime && time <= paintEndTime) + { + + m_positionMarkers.append(m_tools->getPos(time)); + } + + time += currentTextWidthTime + textSpacingTime; + count++; + } + + + + update(); +} + + +void TrackHeader::resizeEvent(QResizeEvent *event) +{ + recreatePositionLabels(); + + QWidget::resizeEvent(event); +} + + +void TrackHeader::updateMe() +{ + recreatePositionLabels(); + update(); +} + + +void TrackHeader::paintEvent(QPaintEvent *event) +{ + QPainter painter(this); + + QWidget::paintEvent(event); + + painter.save(); + + + + painter.setPen(QGuiApplication::palette().windowText().color()); + painter.setBrush(QGuiApplication::palette().window().color()); + + painter.drawRect(rect().adjusted(0, 0, -1, -1)); + + int h = height(); + + + if (m_indicatorTime > -1 && m_indicatorTime <= m_tools->getTime(width())) + { + painter.setPen(QColor("cyan")); + int indicatorPos = m_tools->getPos(m_indicatorTime); + painter.drawLine(indicatorPos, h - (h / 4), indicatorPos, h); + } + + + + painter.setPen(QGuiApplication::palette().windowText().color()); + + + + for (auto i = m_positionMarkers.constBegin(); i != m_positionMarkers.constEnd(); ++i) + { + int pos = *i; + + + int time = m_tools->getTime(pos); + + + QTime t = QTime::fromMSecsSinceStartOfDay(time * 1000); + + + QString x = t.toString(m_timeFormat); + + + painter.drawText(pos, h / 2 , x); + + painter.save(); + painter.setPen(QGuiApplication::palette().link().color()); + painter.drawLine(pos, (h / 2) + 2, pos, h); + painter.restore(); + + + } + + + painter.restore(); +} + +} diff --git a/Lts/Common/LtsScriptTimeline/track_header.h b/Lts/Common/LtsScriptTimeline/track_header.h new file mode 100755 index 000000000..4d85bc7c2 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/track_header.h @@ -0,0 +1,43 @@ +#pragma once + +#include + + +namespace LtsScriptTimeline +{ +class Tools; + +class TrackHeader : public QWidget +{ + Q_OBJECT + +public: + explicit TrackHeader(QWidget *parent = nullptr); + + + void recreatePositionLabels(); + void updateMe(); + void setIndicatorTime(int time); + void lockIndicator(bool lock = true) { m_indicatorLock = lock; } + void setTools(Tools *tools); + int indicatorTime() const { return m_indicatorTime; } + +private: + void resizeEvent(QResizeEvent *event) override; + void paintEvent(QPaintEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + + int m_indicatorTime; + int m_textMargin; + int m_textSpacing; + QString m_timeFormat; + QVector m_positionMarkers; + bool m_indicatorLock; + Tools *m_tools; + +signals: + void signalHeaderIndicatorTimeChanged(int time); + void signalHeaderIndicatorTimeUserChanged(qint64 time); +}; + +} diff --git a/Lts/Common/LtsScriptTimeline/track_resize_handler.cpp b/Lts/Common/LtsScriptTimeline/track_resize_handler.cpp new file mode 100755 index 000000000..a4d2b2c3e --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/track_resize_handler.cpp @@ -0,0 +1,182 @@ +#include "track_resize_handler.h" +#include "track_entry.h" +#include "track_widget.h" +#include "track_content_widget.h" +#include "filter_track_widget.h" +#include "filter_background_widget.h" + +#include "tools.h" + +#include + +#include +#include + + +namespace LtsScriptTimeline +{ +void ResizeHandler::mousePressEvent(QMouseEvent *event) +{ + if (event->buttons() != Qt::LeftButton || !m_possible) + return; + + TrackEntry *te = m_trackContentWidget->getTrackEntryOnPos(event->globalPos()); + + if (te == nullptr) + return; + + m_oldLength = te->m_length; + m_resizing = true; +} + + +void ResizeHandler::mouseReleaseEvent(QMouseEvent *) +{ + m_resizing = false; + + if (!m_trackContentWidget->m_mainSelected) + return; + + QVector vecPreviewTrackEntries = {m_trackContentWidget->m_mainSelected}; + + bool msg = true; + for (TrackContentWidget *tcw : qAsConst(m_tcwCorresponding)) + { + if (TrackContentWidget::checkForParallelTopics(tcw, vecPreviewTrackEntries, m_trackContentWidget->m_mainSelected->m_startTime)) + { + if (msg) + { + LtsWidgets::LtsMessageDialog::showMessage(m_trackContentWidget, TrackContentWidget::parallelError(), QObject::tr("Error")); + msg = false; + } + m_trackContentWidget->m_mainSelected->m_length = m_oldLength; + } + } +} + + +bool ResizeHandler::isCollision(int newLength) const +{ + int startTime = m_trackContentWidget->m_mainSelected->m_startTime; + int endTime = startTime + newLength; + + + for (auto i = m_trackContentWidget->m_entryList.constBegin(); i != m_trackContentWidget->m_entryList.constEnd(); ++i) + { + const TrackEntry *trackEntry = *i; + + if (!trackEntry || trackEntry == m_trackContentWidget->m_mainSelected) + continue; + + if (LtsScriptTimeline::isCollision2(startTime, endTime, trackEntry)) + return true; + } + + + const QVector &filterTrackWidgets = m_trackContentWidget->trackWidget()->filterLines(); + for (const FilterTrackWidget *filterTrackWidget : qAsConst(filterTrackWidgets)) + { + for (auto iFilterContentWidget = filterTrackWidget->fbw()->filterContentWidgets().begin(); iFilterContentWidget != filterTrackWidget->fbw()->filterContentWidgets().end(); ++iFilterContentWidget) + { + int filterContentWidgetTopicId = iFilterContentWidget.key(); + if (m_trackContentWidget->m_mainSelected->m_topicId != filterContentWidgetTopicId) + continue; + + TrackContentWidget *filterContentWidget = iFilterContentWidget.value(); + for (auto iTrackEntry = filterContentWidget->m_entryList.constBegin(); iTrackEntry != filterContentWidget->m_entryList.end(); ++iTrackEntry) + { + const TrackEntry *trackEntry = *iTrackEntry; + + if (!trackEntry || trackEntry == m_trackContentWidget->m_mainSelected) + continue; + + if (endTime < startTime + trackEntry->m_startTime + trackEntry->m_length) + return true; + } + } + } + + return false; +} + + +bool ResizeHandler::mouseMoveEvent(QMouseEvent *event) +{ + QPoint pos = m_trackContentWidget->mapFromGlobal(event->globalPos()); + int posX = pos.x(); + int cursorTime = m_trackContentWidget->m_tools->getTime(posX, m_trackContentWidget->m_isFilter); + + if (m_resizing && m_trackContentWidget->m_mainSelected) + { + int newLength = cursorTime - m_trackContentWidget->m_mainSelected->m_startTime; + int cursorY = pos.y(); + + if (cursorY < 0 || cursorY > m_trackContentWidget->height() || newLength < 1 || (m_mainTeLength != -1 && newLength > m_mainTeLength) || isCollision(newLength)) + { + newLength = m_oldLength; + m_trackContentWidget->setCursor(Qt::ForbiddenCursor); + } + else + { + m_trackContentWidget->setCursor(m_cursor); + } + + m_trackContentWidget->m_mainSelected->m_length = newLength; + m_trackContentWidget->update(); + emit m_trackContentWidget->signalChangesMade(); + return true; + } + else + { + TrackEntry *te = m_trackContentWidget->getTrackEntryOnPos(event->globalPos()); + + if (m_trackContentWidget->m_isFilter) + { + TrackEntry *mainTe = m_trackContentWidget->getParentTopicOfFilter(te); + + if (mainTe != nullptr) + m_mainTeLength = mainTe->m_length; + else + m_mainTeLength = -1; + } + + + if (te && (te->m_type == LtsScriptTimeline::TrackEntryType::Audio || + te->m_type == LtsScriptTimeline::TrackEntryType::Video)) + { + m_possible = false; + m_trackContentWidget->setCursor(m_cursor = Qt::ArrowCursor); + m_trackContentWidget->update(); + } + else if (te && m_trackContentWidget->m_mainSelected && te == m_trackContentWidget->m_mainSelected && m_trackContentWidget->m_selected.count() == 1 + && (cursorTime >= (m_trackContentWidget->m_mainSelected->m_startTime + m_trackContentWidget->m_mainSelected->m_length - 4) + && cursorTime <= (m_trackContentWidget->m_mainSelected->m_startTime + m_trackContentWidget->m_mainSelected->m_length))) + { + m_possible = true; + m_trackContentWidget->setCursor(m_cursor = Qt::SizeHorCursor); + m_trackContentWidget->update(); + } + else if (!m_resizing) + { + m_possible = false; + m_trackContentWidget->setCursor(m_cursor = Qt::ArrowCursor); + m_trackContentWidget->update(); + } + } + + return false; +} + + +void ResizeHandler::keyPressEvent(QKeyEvent *event) +{ + if (m_trackContentWidget->m_mainSelected && event->key() == Qt::Key_Escape && m_resizing) + { + m_trackContentWidget->m_mainSelected->m_length = m_oldLength; + m_resizing = false; + m_possible = false; + m_trackContentWidget->setCursor(Qt::ArrowCursor); + m_trackContentWidget->update(); + } +} +} diff --git a/Lts/Common/LtsScriptTimeline/track_resize_handler.h b/Lts/Common/LtsScriptTimeline/track_resize_handler.h new file mode 100755 index 000000000..c8133dcd2 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/track_resize_handler.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +class QMouseEvent; +class QKeyEvent; + +namespace LtsScriptTimeline +{ +class LTSSCRIPTTIMELINE_EXPORT ResizeHandler +{ +public: + explicit ResizeHandler(TrackContentWidget *_tcw) + : m_trackContentWidget(_tcw) + , m_resizing(false) + , m_possible(false) + , m_oldLength(-1) + , m_mainTeLength(-1) + {} + + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + bool mouseMoveEvent(QMouseEvent *event); + void keyPressEvent(QKeyEvent *event); + + TrackContentWidget *m_trackContentWidget; + QVector m_tcwCorresponding; + + bool m_resizing; + bool m_possible; + int m_oldLength; + int m_mainTeLength; + Qt::CursorShape m_cursor; + +private: + bool isCollision(int newLength) const; +}; +} diff --git a/Lts/Common/LtsScriptTimeline/track_select_handler.cpp b/Lts/Common/LtsScriptTimeline/track_select_handler.cpp new file mode 100755 index 000000000..cfa5aaa2d --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/track_select_handler.cpp @@ -0,0 +1,182 @@ +#include "track_select_handler.h" +#include "track_content_widget.h" +#include "track_entry.h" + +#include "tools.h" + +#include +#include +#include +#include +#include +#include + +#include + + +namespace LtsScriptTimeline +{ + +void SelectHandler::slotSetDescription() +{ + if (m_pressedEntry == nullptr) + return; + + bool ok; + QString iniDescription = m_pressedEntry->m_description; + QString text = QInputDialog::getText(tcw, + tr("Operator Application"), + tr("Topic Description:"), QLineEdit::Normal, + iniDescription, &ok, Qt::Popup | Qt::WindowTitleHint | Qt::CustomizeWindowHint); + if (ok) + { + m_pressedEntry->m_description = text; + emit tcw->signalChangesMade(); + } +} + + +void SelectHandler::mouseDoubleClick(QMouseEvent *event) +{ + if (m_pressedEntry != nullptr + && tcw->m_selected.count() == 1 + && !tcw->m_isMultiLineMultiSelectionMode + && event->buttons() == Qt::LeftButton) + emit tcw->signalEditEntry(m_pressedEntry->m_topicId); +} + + +void SelectHandler::mousePressEvent(QMouseEvent *event) +{ + if (event->buttons() == Qt::LeftButton) + { + m_pressedEntry = nullptr; + + QPoint clickedPos = tcw->mapFromGlobal(event->globalPos()); + int clickedPosY = clickedPos.y(); + int clickedPosX = clickedPos.x(); + + if (clickedPosY < 0 || clickedPosY > tcw->height()) + return; + + int clickedTime = tcw->m_tools->getTime(clickedPosX, tcw->m_isFilter); + m_pressedEntry = tcw->getTrackEntryAt(clickedTime); + } + else if (event->buttons() == Qt::RightButton) + { + QMenu *contextMenu = new QMenu(tcw); + contextMenu->setAttribute(Qt::WA_DeleteOnClose); + + + contextMenu->addSection(tr("All tracks")); + { + QAction *action = new QAction(tr("Set topic description"), tcw); + action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_D)); +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + action->setShortcutVisibleInContextMenu(true); +#endif + action->setIcon(QIcon(QStringLiteral("icon:document-edit.svg"))); + action->setEnabled(m_pressedEntry); + if (!m_pressedEntry) + action->setToolTip(tr("Right click directly on a single Topic to edit its description.")); + connect(action, &QAction::triggered, this, &SelectHandler::slotSetDescription); + contextMenu->addAction(action); + } + + { + QAction *action = new QAction(tr("Remove selected Topic(s)"), tcw); + action->setShortcut(QKeySequence(Qt::Key_Delete)); +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + action->setShortcutVisibleInContextMenu(true); +#endif + action->setIcon(QIcon(QStringLiteral("icon:delete.svg"))); + connect(action, &QAction::triggered, tcw, &TrackContentWidget::signalDeleteSelectedTopics); + contextMenu->addAction(action); + } + + + contextMenu->addSection(tr("Main track only")); + + { + QAction *action = new QAction(tr("Select all topics"), tcw); + action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_A)); +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + action->setShortcutVisibleInContextMenu(true); +#endif + action->setIcon(QIcon(QStringLiteral("icon:selection_rec.svg"))); + connect(action, &QAction::triggered, tcw, &TrackContentWidget::signalSelectAllGlobal); + contextMenu->addAction(action); + } + + { + + QAction *action = new QAction(tr("Cut selected topic(s)"), tcw); + action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_X)); +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + action->setShortcutVisibleInContextMenu(true); +#endif + action->setIcon(QIcon(QStringLiteral("icon:edit-cut.svg"))); + connect(action, &QAction::triggered, tcw, &TrackContentWidget::signalCutTopics); + contextMenu->addAction(action); + } + + { + QAction *action = new QAction(tr("Copy selected topic(s)"), tcw); + action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_C)); +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + action->setShortcutVisibleInContextMenu(true); +#endif + action->setIcon(QIcon(QStringLiteral("icon:edit-copy.svg"))); + connect(action, &QAction::triggered, tcw, &TrackContentWidget::signalCopyTopics); + contextMenu->addAction(action); + } + + { + QAction *action = new QAction(tr("Paste topic(s) at indicator position"), tcw); + action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_V)); +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + action->setShortcutVisibleInContextMenu(true); +#endif + action->setIcon(QIcon(QStringLiteral("icon:edit-paste.svg"))); + connect(action, &QAction::triggered, tcw, &TrackContentWidget::signalPasteTopics); + contextMenu->addAction(action); + } + + + contextMenu->popup(event->globalPos()); + } +} + + + +void SelectHandler::mouseReleaseEvent(QMouseEvent *event) +{ + QPoint clickedPos = tcw->mapFromGlobal(event->globalPos()); + int clickedPosX = clickedPos.x(); + int clickedTime = tcw->m_tools->getTime(clickedPosX, tcw->m_isFilter); + + TrackEntry *releasedEntry = tcw->getTrackEntryAt(clickedTime); + + if (!QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && (!m_pressedEntry || !releasedEntry)) + { + emit tcw->signalUnselectAll(); + emit tcw->signalTrackEntrySelectionChanged(nullptr); + } + else + { + if (m_pressedEntry == releasedEntry) + tcw->selectEntry(releasedEntry); + } +} + + +void SelectHandler::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Escape) + { + m_pressedEntry = nullptr; + emit tcw->signalUnselectAll(); + emit tcw->signalTrackEntrySelectionChanged(nullptr); + } +} +} diff --git a/Lts/Common/LtsScriptTimeline/track_select_handler.h b/Lts/Common/LtsScriptTimeline/track_select_handler.h new file mode 100755 index 000000000..044ee4cb7 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/track_select_handler.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include + + +class QMouseEvent; +class QKeyEvent; + + +namespace LtsScriptTimeline +{ +class TrackContentWidget; +class TrackEntry; + +class LTSSCRIPTTIMELINE_EXPORT SelectHandler : public QObject +{ + Q_OBJECT + +public: + explicit SelectHandler(TrackContentWidget *_tcw) + : QObject(nullptr) + , tcw(_tcw) + , m_pressedEntry(nullptr) + {} + + void mouseDoubleClick(QMouseEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void keyPressEvent(QKeyEvent *event); + + TrackContentWidget *tcw; + TrackEntry *m_pressedEntry; + +public slots: + void slotSetDescription(); +}; +} diff --git a/Lts/Common/LtsScriptTimeline/track_widget.cpp b/Lts/Common/LtsScriptTimeline/track_widget.cpp new file mode 100755 index 000000000..015709bf8 --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/track_widget.cpp @@ -0,0 +1,559 @@ +#include "track_widget.h" +#include "ui_track_widget.h" +#include "track_entry.h" +#include "filter_track_widget.h" +#include "filter_background_widget.h" +#include "track_content_widget.h" +#include "tools.h" + +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace LtsScriptTimeline +{ +TrackWidget::TrackWidget(int baseWidth, bool readonly, Tools *tools, bool filtersAllowed, QWidget *parent) + : QWidget(parent) + , ui(new Ui::TrackWidget) + , m_baseWidth(baseWidth) + , m_readonly(readonly) + , m_tools(tools) +{ + Q_ASSERT(m_tools != nullptr); + + ui->setupUi(this); + ui->mainTrack->setTrackWidget(this); + ui->mainTrack->setTools(tools); + + ui->chkSelect->hide(); + + ui->filtersWidget->hide(); + ui->leName->hide(); + ui->pbApply->hide(); + ui->pbCancel->hide(); + + ui->pbEditName->hide(); + + if (!filtersAllowed) + ui->pbExpand->hide(); + + ui->pbAddFilter->hide(); + + setBaseWidth(baseWidth); + setReadonly(readonly); + + connect(ui->mainTrack, &TrackContentWidget::signalUnselectAll, + this, &TrackWidget::signalUnselectAll); + connect(ui->mainTrack, &TrackContentWidget::signalTrackEntrySelectionChanged, + this, &TrackWidget::signalTrackEntrySelectionChanged); + connect(ui->mainTrack, &TrackContentWidget::signalChangesMade, + this, &TrackWidget::signalChangesMade); + connect(ui->mainTrack, &TrackContentWidget::signalChangesMade, + this, &TrackWidget::slotMainChangesMade); + connect(ui->mainTrack, &TrackContentWidget::signalTrackEntryAdded, + this, &TrackWidget::slotTrackEntryAdded); + connect(ui->mainTrack, &TrackContentWidget::signalTrackEntryDeleted, + this, &TrackWidget::slotTrackEntryDeleted); + connect(ui->mainTrack, &TrackContentWidget::signalEditEntry, + this, &TrackWidget::signalEditEntry); + + connect(ui->chkSelect, &QCheckBox::stateChanged, + this, &TrackWidget::signalSelectTrack); + connect(ui->pbExpand, &QPushButton::clicked, + this, &TrackWidget::slotExpand); + connect(ui->pbAddFilter, &QPushButton::clicked, + this, &TrackWidget::slotAddFilter); +} + + +TrackWidget::~TrackWidget() +{ + m_filterLines.clear(); + delete ui; +} + + +void TrackWidget::setReadonly(bool readonly) +{ + m_readonly = readonly; + ui->mainTrack->setReadonly(readonly); + + if (readonly) + ui->pbAddFilter->hide(); + else if (ui->filtersWidget->isVisible()) + ui->pbAddFilter->show(); + + for (auto i3 = m_filterLines.begin(); i3 != m_filterLines.end(); ++i3) + { + FilterTrackWidget *ftw = *i3; + ftw->setReadonly(readonly); + } +} + + +void TrackWidget::setScriptTimeline(ScriptTimeline *scriptTimeline) +{ + m_scriptTimeline = scriptTimeline; + ui->mainTrack->setScriptTimeline(m_scriptTimeline); +} + + +void TrackWidget::keyPressEvent(QKeyEvent *event) +{ + getTrackContentWidget()->keyPressEvent(event); +} + + +QVector TrackWidget::getSelectedFilterEntries() const +{ + QVector result; + const QVector &ftws = filterLines(); + + for (const FilterTrackWidget *ftw : qAsConst(ftws)) + { + const FilterBackgroundWidget *fbw = ftw->fbw(); + const QMap &fcws = fbw->filterContentWidgets(); + + for (auto i = fcws.constBegin(); i != fcws.constEnd(); ++i) + { + TrackContentWidget *tcw = i.value(); + result += tcw->getSelectedEntries(); + } + } + + return result; +} + + +const QVector &TrackWidget::getSelectedEntries() const +{ + return ui->mainTrack->getSelectedEntries(); +} + + +void TrackWidget::slotAddFilter() +{ + FilterTrackWidget *ftw = new FilterTrackWidget(m_baseWidth, m_readonly); + ftw->setTrackWidget(this); + ftw->setTools(m_tools); + ftw->setScriptTimeline(m_scriptTimeline); + ui->filtersWidget->layout()->addWidget(ftw); + m_filterLines.append(ftw); + + connect(ftw, &FilterTrackWidget::signalChangesMade, + this, &TrackWidget::signalChangesMade); + + connect(ftw, &FilterTrackWidget::signalUnselectAll, + this, &TrackWidget::signalUnselectAll); + + connect(ftw, &FilterTrackWidget::signalTrackEntrySelectionChanged, + this, &TrackWidget::signalTrackEntrySelectionChanged); + + connect(ftw, &FilterTrackWidget::signalEditEntry, + this, &TrackWidget::signalEditEntry); + + connect(ftw, &FilterTrackWidget::signalTrackEntryDeleted, + this, &TrackWidget::signalTrackEntryDeleted); + + connect(ftw, &FilterTrackWidget::signalDeleteMe, + this, [this]() + { + m_filterLines.removeAll(qobject_cast(sender())); + sender()->deleteLater(); + emit signalChangesMade(); + }); + + emit signalChangesMade(); + slotMainChangesMade(); +} + + +void TrackWidget::slotMainChangesMade() +{ + const QVector trackEntries = ui->mainTrack->getTrackEntries(); + + for (auto i1 = trackEntries.constBegin(); i1 != trackEntries.constEnd(); ++i1) + { + const TrackEntry *te = *i1; + + for (auto i3 = m_filterLines.begin(); i3 != m_filterLines.end(); ++i3) + { + FilterTrackWidget *ftw = *i3; + ftw->fbw()->adjustFilterContentWidgets(te); + } + } + + +} + + +void TrackWidget::slotTrackEntryAdded(TrackEntry *te) +{ + + + for (FilterTrackWidget *ftw : qAsConst(m_filterLines)) + ftw->fbw()->trackEntryAdded(te); + + emit signalTrackEntryAdded(te); +} + + +void TrackWidget::slotTrackEntryDeleted(const int topicId) +{ + + + for (FilterTrackWidget *ftw : qAsConst(m_filterLines)) + ftw->fbw()->trackEntryDeleted(topicId); + + emit signalTrackEntryDeleted(topicId); +} + + + + + + + + +void TrackWidget::slotExpand() +{ + if (ui->filtersWidget->isVisible()) + { + ui->pbExpand->setText(tr("More")); + ui->pbAddFilter->hide(); + ui->filtersWidget->hide(); + } + else + { + ui->pbExpand->setText(tr("Less")); + + if (!m_readonly) + ui->pbAddFilter->show(); + + ui->filtersWidget->show(); + slotMainChangesMade(); + } +} + + +void TrackWidget::selectEntry(int topicId) +{ + ui->mainTrack->selectEntry(topicId); +} + + +int TrackWidget::getParentTopicIdOfFilter(const TrackEntry *te) const +{ + + for (const FilterTrackWidget *ftw : m_filterLines) + { + const FilterBackgroundWidget *fbw = ftw->fbw(); + const QMap &filterContentWidgets = fbw->filterContentWidgets(); + + + for (auto i = filterContentWidgets.constBegin(); i != filterContentWidgets.constEnd(); ++i) + { + int parentTopicId = i.key(); + const TrackContentWidget *datWidget = i.value(); + const QVector &filterEntries = datWidget->getTrackEntries(); + + for (const TrackEntry *filterTE : filterEntries) + { + if (filterTE == te) + return parentTopicId; + } + } + } + + return -1; +} + + +QVector TrackWidget::getTrackEntriesWithFilters(bool selectedEntriesOnly) const +{ + QVector result; + const QVector trackEntryList = ui->mainTrack->getTrackEntries(); + + for (TrackEntry *te : trackEntryList) + { + if (selectedEntriesOnly && !ui->mainTrack->getSelectedEntries().contains(te)) + continue; + + result += te; + + + QJsonArray jsonFilterLines; + + + for (const FilterTrackWidget *ftw : qAsConst(m_filterLines)) + { + const FilterBackgroundWidget *fbw = ftw->fbw(); + const QMap &filterContentWidgets = fbw->filterContentWidgets(); + + + const TrackContentWidget *datWidget = filterContentWidgets.value(te->m_topicId); + + if (datWidget == nullptr) + continue; + + const QVector &filterEntries = datWidget->getTrackEntries(); + + if (filterEntries.isEmpty()) + continue; + + QJsonArray jsonFilterLine; + for (const TrackEntry *fte : filterEntries) + jsonFilterLine.append(fte->toJson()); + + jsonFilterLines.append(jsonFilterLine); + } + + te->m_detailData[QStringLiteral("Filters")] = jsonFilterLines; + } + + return result; +} + + +QVector TrackWidget::getTrackEntriesFiltersAsEntries(bool selectedEntriesOnly) const +{ + QVector result; + const QVector trackEntryList = ui->mainTrack->getTrackEntries(); + + for (TrackEntry *te : trackEntryList) + { + if (selectedEntriesOnly && !ui->mainTrack->getSelectedEntries().contains(te)) + continue; + + result += te; + + + for (FilterTrackWidget *ftw : m_filterLines) + { + const QMap &filterContentWidgets = ftw->fbw()->filterContentWidgets(); + + for (auto i1 = filterContentWidgets.constBegin(); i1 != filterContentWidgets.constEnd(); ++i1) + { + TrackContentWidget *tcw = i1.value(); + result += tcw->getTrackEntries(); + } + } + } + + return result; +} + + +QVector TrackWidget::getTrackEntries(bool selectedEntriesOnly) const +{ + QVector result; + const QVector trackEntryList = ui->mainTrack->getTrackEntries(); + + if (selectedEntriesOnly) + { + for (TrackEntry *te : trackEntryList) + { + if (!ui->mainTrack->getSelectedEntries().contains(te)) + continue; + + result += te; + } + } + else + { + return trackEntryList; + } + + return result; +} + + +void TrackWidget::setBaseWidth(int width) +{ + m_baseWidth = width; + + ui->mainLeft->setMinimumWidth(width); + ui->mainLeft->setMaximumWidth(width); + + for (FilterTrackWidget *ftw : qAsConst(m_filterLines)) + ftw->setBaseWidth(m_baseWidth); +} + + +void TrackWidget::addCorrespondingTrackWidget(TrackContentWidget *pCorrespondingTrackWidget) +{ + ui->mainTrack->addCorrespondingTrackWidget(pCorrespondingTrackWidget); +} + + +TrackContentWidget *TrackWidget::getTrackContentWidget() const +{ + return ui->mainTrack; +} + + +void TrackWidget::setInternalName(const QString &name) +{ + m_internalName = name; + ui->mainTrack->setTrackName(name); +} + + +void TrackWidget::setTrackName(const QString &name) +{ + ui->lblTrackName->setText(name); +} + + +bool TrackWidget::addTrackEntry(TrackEntry *te, bool selectTrackEntry) +{ + if (!ui->mainTrack->addTrackEntry(te, selectTrackEntry)) + return false; + + const QJsonObject detailData = te->m_detailData; + + + + + + + + if (detailData.isEmpty()) + { + + return true; + } + + + + + const QJsonArray filterLines = detailData[QStringLiteral("Filters")].toArray(); + + + if (filterLines.isEmpty()) + return true; + + + + + + + int missingFilterLines = filterLines.count() - m_filterLines.count(); + + while (missingFilterLines > 0) + { + slotAddFilter(); + missingFilterLines--; + } + + + + + for (int i1 = 0; i1 != filterLines.count(); i1++) + { + const QJsonArray filterLine = filterLines.at(i1).toArray(); + + for (const QJsonValue &filter : filterLine) + { + TrackEntry *filterTe = new TrackEntry; + filterTe->fromJson(filter.toObject()); + + + m_filterLines.at(i1)->addFilterEntry(te , filterTe); + + + } + } + + emit signalChangesMade(); + + return true; +} + + +QString TrackWidget::trackName() const +{ + return m_internalName; +} + + +bool TrackWidget::isSelected() const +{ + return (ui->chkSelect->checkState() == Qt::CheckState::Checked); +} + + +void TrackWidget::unselectAll() +{ + ui->mainTrack->unselectAll(); + + for (FilterTrackWidget *ftw : qAsConst(m_filterLines)) + { + const auto fcw = ftw->fbw()->filterContentWidgets(); + for (TrackContentWidget *tcw : fcw) + tcw->unselectAll(); + } +} + + +void TrackWidget::updateMe() +{ + update(); + ui->mainTrack->update(); + slotMainChangesMade(); +} + + +void TrackWidget::setIndicatorTime(int time) +{ + ui->mainTrack->setIndicatorTime(time); + + for (FilterTrackWidget *ftw : qAsConst(m_filterLines)) + { + const auto filterContentWidgets = ftw->fbw()->filterContentWidgets(); + + + for (auto i1 = filterContentWidgets.begin(); i1 != filterContentWidgets.end(); ++i1) + { + int mainTeId = i1.key(); + int mainTeStart = 0; + + const QVector &tes = ui->mainTrack->getTrackEntries(); + for (const TrackEntry *te : tes) + { + if (te->m_topicId == mainTeId) + { + mainTeStart = te->m_startTime; + break; + } + } + + TrackContentWidget *filterTcw = i1.value(); + filterTcw->setIndicatorTime(time - mainTeStart); + } + } +} + + +void TrackWidget::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + + QPainter painter(this); + + painter.save(); + + painter.setPen(QGuiApplication::palette().windowText().color()); + painter.setBrush(QGuiApplication::palette().window().color()); + painter.drawRect(rect()); + + painter.restore(); +} + +} diff --git a/Lts/Common/LtsScriptTimeline/track_widget.h b/Lts/Common/LtsScriptTimeline/track_widget.h new file mode 100755 index 000000000..780725f2d --- /dev/null +++ b/Lts/Common/LtsScriptTimeline/track_widget.h @@ -0,0 +1,89 @@ +#pragma once + +#include +#include "track_entry_painters.h" + +#include +#include +#include + + +namespace Ui +{ +class TrackWidget; +} + + +namespace LtsScriptTimeline +{ +class TrackEntry; +class TrackContentWidget; +class FilterTrackWidget; +class FilterBackgroundWidget; +class ScriptTimeline; +class Tools; + + +class LTSSCRIPTTIMELINE_EXPORT TrackWidget : public QWidget +{ + Q_OBJECT + +public: + explicit TrackWidget(int baseWidth, bool readonly, Tools *tools, bool filtersAllowed, QWidget *parent = nullptr); + ~TrackWidget(); + + void setReadonly(bool readonly); + void updateMe(); + void unselectAll(); + bool isSelected() const; + QString trackName() const; + bool addTrackEntry(TrackEntry *te, bool selectTrackEntry); + void setTrackName(const QString &name); + void addCorrespondingTrackWidget(TrackContentWidget *pCorrespondingTrackWidget); + TrackContentWidget *getTrackContentWidget() const; + void setBaseWidth(int width); + void setIndicatorTime(int time); + void selectEntry(int topicId); + + void setInternalName(const QString &name); + QVector getTrackEntriesFiltersAsEntries(bool selectedEntriesOnly = false) const; + QVector getTrackEntriesWithFilters(bool selectedEntriesOnly = false) const; + QVector getTrackEntries(bool selectedEntriesOnly = false) const; + QVector getSelectedFilterEntries() const; + const QVector &getSelectedEntries() const; + int getParentTopicIdOfFilter(const TrackEntry *te) const; + void setScriptTimeline(ScriptTimeline *scriptTimeline); + const QVector &filterLines() const { return m_filterLines; } + + void keyPressEvent(QKeyEvent *event) override; + +private: + QVector m_filterLines; + Ui::TrackWidget *ui; + int m_baseWidth; + QString m_internalName; + bool m_readonly; + ScriptTimeline *m_scriptTimeline; + Tools *m_tools; + +protected: + void paintEvent(QPaintEvent *event) override; + +private slots: + void slotExpand(); + void slotTrackEntryAdded(TrackEntry *te); + void slotTrackEntryDeleted(const int topicId); + void slotMainChangesMade(); + void slotAddFilter(); + +signals: + void signalUnselectAll(); + void signalSelectTrack(); + void signalTrackEntrySelectionChanged(TrackEntry *te); + void signalChangesMade(); + void signalEditEntry(const int topicId); + void signalTrackEntryDeleted(const int topicId); + void signalTrackEntryAdded(TrackEntry *te); +}; + +} diff --git a/Lts/Common/LtsSerialPortQuerry/CMakeLists.txt b/Lts/Common/LtsSerialPortQuerry/CMakeLists.txt new file mode 100755 index 000000000..05114df6a --- /dev/null +++ b/Lts/Common/LtsSerialPortQuerry/CMakeLists.txt @@ -0,0 +1,31 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +# +# Lts SerialPortQuerry library +# + +SET(ltsspq_SRCS + lts_serial_port_querry.h + lts_serial_port_querry.cpp + logger.h + logger.cpp +) + + +NGL_ADD_LIBRARY2(AAR SV LtsSerialPortQuerry SHARED ${ltsspq_SRCS} ) + +NGL_TARGET_INCLUDE_DIRECTORIES(LtsSerialPortQuerry + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) + +NGL_TARGET_LINK_LIBRARIES(LtsSerialPortQuerry + PRIVATE + PUBLIC + NGLLogger + Qt5::SerialPort +) diff --git a/Lts/Common/LtsSerialPortQuerry/logger.cpp b/Lts/Common/LtsSerialPortQuerry/logger.cpp new file mode 100755 index 000000000..c75d4f318 --- /dev/null +++ b/Lts/Common/LtsSerialPortQuerry/logger.cpp @@ -0,0 +1,32 @@ + + + +#include "logger.h" + +#include + +namespace LtsSerialPortQuerry +{ + +LOGGER_T getLogger(const char *name) +{ + static const char ns[] = "SPQ."; + std::string loggerName; + const size_t len = strlen(name); + loggerName.reserve(sizeof(ns) + len); + loggerName.append(ns, sizeof(ns) - 1); + loggerName.append(name, len); + + try + { + return NGL::NGLLoggerBootstrap::getNGLRootLogger(). + getNGLLogger(loggerName); + } + catch (const std::exception &ex) + { + fprintf(stderr, "Error retrieving logger: %s", ex.what()); + abort(); + } +} + +} diff --git a/Lts/Common/LtsSerialPortQuerry/logger.h b/Lts/Common/LtsSerialPortQuerry/logger.h new file mode 100755 index 000000000..75fc2cdc4 --- /dev/null +++ b/Lts/Common/LtsSerialPortQuerry/logger.h @@ -0,0 +1,18 @@ + + + +#pragma once + +#include + +namespace LtsSerialPortQuerry +{ +typedef NGL::INGLLogger &LOGGER_T; + +LOGGER_T getLogger(const char *name); +inline LOGGER_T getLogger(const QString &loggerName) +{ + return getLogger(loggerName.toUtf8().constData()); +} +} + diff --git a/Lts/Common/LtsSerialPortQuerry/lts_comport_querry.h b/Lts/Common/LtsSerialPortQuerry/lts_comport_querry.h new file mode 100755 index 000000000..e6ecd3a50 --- /dev/null +++ b/Lts/Common/LtsSerialPortQuerry/lts_comport_querry.h @@ -0,0 +1,52 @@ +#pragma once + +#include "lts_serial_port_querry_export.h" +#include "lts_seri" +#include +#include +#include + + + + +namespace LtsSerialPortQuerry +{ +class LTSSERIALPORTQUERRY_EXPORT LtsSerialPortQuerry : public QObject +{ + Q_OBJECT +public: + explicit LtsSerialPortQuerry(QObject *parent = nullptr); + + void setSerialPortSettings(const QSerialPort::BaudRate &baudRate, + const QSerialPort::OpenModeFlag &openModeFlag, + const QSerialPort::DataBits &dataBits, + const QSerialPort::FlowControl &flowControll, + const QSerialPort::Parity &parity, + const QSerialPort::StopBits &stopBits); + void continueQuerry(); + void restartQuerry(); + QSerialPort *currentSerialPort(); + +signals: + void connectionEstablished(); + +private: + void querrySerialPort(const QSerialPortInfo &info); + void closeAndDeleteCurrentPort(); + +private: + void onSerialPortError(QSerialPort::SerialPortError serialPortError); + +private: + QList m_serialPortInfos; + int m_currentQuerry; + QSerialPort::BaudRate m_baudRate; + QSerialPort::OpenModeFlag m_openModeFlag; + QSerialPort::DataBits m_dataBits; + QSerialPort::FlowControl m_flowControll; + QSerialPort::Parity m_parity; + QSerialPort::StopBits m_stopBits; + QSerialPort *m_currentSerialPort; +}; + +} diff --git a/Lts/Common/LtsSerialPortQuerry/lts_serial_port_querry.cpp b/Lts/Common/LtsSerialPortQuerry/lts_serial_port_querry.cpp new file mode 100755 index 000000000..daa9b81b4 --- /dev/null +++ b/Lts/Common/LtsSerialPortQuerry/lts_serial_port_querry.cpp @@ -0,0 +1,153 @@ +#include "lts_serial_port_querry.h" + +#include + +namespace LtsSerialPortQuerry +{ + +LtsSerialPortQuerry::LtsSerialPortQuerry(QObject *parent) + : QObject(parent) + , m_currentQuerry(0) + , m_baudRate(QSerialPort::Baud115200) + , m_openModeFlag(QSerialPort::ReadWrite) + , m_dataBits(QSerialPort::Data8) + , m_flowControll(QSerialPort::NoFlowControl) + , m_parity(QSerialPort::NoParity) + , m_stopBits(QSerialPort::OneStop) + , m_currentSerialPort(nullptr) + , m_logger(getLogger("LtsSerialPortQuerry")) + +{ + +} + +void LtsSerialPortQuerry::setSerialPortSettings(const QSerialPort::BaudRate &baudRate, + const QIODevice::OpenModeFlag &openModeFlag, + const QSerialPort::DataBits &dataBits, + const QSerialPort::FlowControl &flowControll, + const QSerialPort::Parity &parity, + const QSerialPort::StopBits &stopBits) +{ + m_baudRate = baudRate; + m_openModeFlag = openModeFlag; + m_dataBits = dataBits; + m_flowControll = flowControll; + m_parity = parity; + m_stopBits = stopBits; +} + +void LtsSerialPortQuerry::continueQuerry() +{ + m_currentQuerry++; + if (m_currentQuerry>= m_serialPortInfos.size()) + { + + restartQuerry(); + return; + } + querrySerialPort(m_serialPortInfos.at(m_currentQuerry)); +} + +void LtsSerialPortQuerry::restartQuerry() +{ + DEBUG_LOG(m_logger) << "restart querry "; + m_serialPortInfos = QSerialPortInfo::availablePorts(); + m_currentQuerry = 0; + if (m_serialPortInfos.size()) + querrySerialPort(m_serialPortInfos.at(m_currentQuerry)); + else + QTimer::singleShot(1000, this, &LtsSerialPortQuerry::restartQuerry); +} + +QSerialPort *LtsSerialPortQuerry::currentSerialPort() +{ + return m_currentSerialPort; +} + +void LtsSerialPortQuerry::querrySerialPort(const QSerialPortInfo &info) +{ + closeAndDeleteCurrentPort(); + m_currentSerialPort = new QSerialPort(info, this); + m_currentSerialPort->setBaudRate(m_baudRate); + m_currentSerialPort->setDataBits(m_dataBits); + m_currentSerialPort->setParity(m_parity); + m_currentSerialPort->setStopBits(m_stopBits); + m_currentSerialPort->setFlowControl(m_flowControll); + + DEBUG_LOG(m_logger) << "Try opening Comport:" << m_currentSerialPort->portName() << " index is: " << m_currentQuerry; + + + if (!m_currentSerialPort->open(QSerialPort::ReadWrite)) + { + + DEBUG_LOG(m_logger) << "Oppening failed query next"; + + continueQuerry(); + }else + { + connect(m_currentSerialPort, &QSerialPort::errorOccurred, + this, &LtsSerialPortQuerry::onSerialPortError, Qt::UniqueConnection); + DEBUG_LOG(m_logger) << "Oppened comport" << m_currentSerialPort->portName(); + emit connectionEstablished(); + } +} + +void LtsSerialPortQuerry::closeAndDeleteCurrentPort() +{ + if (m_currentSerialPort) + { + m_currentSerialPort->disconnect(); + if (m_currentSerialPort->isOpen()) + m_currentSerialPort->close(); + m_currentSerialPort->deleteLater(); + + m_currentSerialPort = nullptr; + } +} + + +void LtsSerialPortQuerry::onSerialPortError(QSerialPort::SerialPortError serialPortError) +{ + + if (!m_currentSerialPort) + return; + DEBUG_LOG(m_logger) << "Spontaneous error " << serialPortError << " doesn't have to be an error ..."; + + const QString errStr(QStringLiteral("QSerialPort::SerialPortError ") + QString::number(serialPortError) + + QStringLiteral(" from serial port ") + m_currentSerialPort->portName() + + QStringLiteral(" error: ") + m_currentSerialPort->errorString()); + + switch (serialPortError) + { + case QSerialPort::SerialPortError::NoError: + { + + break; + } + case QSerialPort::SerialPortError::DeviceNotFoundError: + case QSerialPort::SerialPortError::PermissionError: + case QSerialPort::SerialPortError::OpenError: + case QSerialPort::SerialPortError::NotOpenError: + case QSerialPort::SerialPortError::UnknownError: + { + ERROR_LOG(m_logger) << "Closing port and try to reopen it. " << m_currentSerialPort->portName(); + continueQuerry(); + + Q_FALLTHROUGH(); + } + case QSerialPort::SerialPortError::UnsupportedOperationError: + { + ERROR_LOG(m_logger) << errStr; + break; + } + default: + { + ERROR_LOG(m_logger) << "Got an Error> " << errStr << "restarting"; + continueQuerry(); + + break; + } + } + +} +} diff --git a/Lts/Common/LtsSerialPortQuerry/lts_serial_port_querry.h b/Lts/Common/LtsSerialPortQuerry/lts_serial_port_querry.h new file mode 100755 index 000000000..c8e4965d5 --- /dev/null +++ b/Lts/Common/LtsSerialPortQuerry/lts_serial_port_querry.h @@ -0,0 +1,56 @@ +#pragma once + +#include + +#include "logger.h" + +#include +#include +#include + +namespace LtsSerialPortQuerry +{ +class LTSSERIALPORTQUERRY_EXPORT LtsSerialPortQuerry : public QObject +{ + Q_OBJECT +public: + explicit LtsSerialPortQuerry(QObject *parent = nullptr); + + void setSerialPortSettings(const QSerialPort::BaudRate &baudRate, + const QSerialPort::OpenModeFlag &openModeFlag, + const QSerialPort::DataBits &dataBits, + const QSerialPort::FlowControl &flowControll, + const QSerialPort::Parity &parity, + const QSerialPort::StopBits &stopBits); + + void restartQuerry(); + QSerialPort *currentSerialPort(); + +public slots: + void continueQuerry(); + +signals: + void connectionEstablished(); + +private: + void querrySerialPort(const QSerialPortInfo &info); + void closeAndDeleteCurrentPort(); + +private: + void onSerialPortError(QSerialPort::SerialPortError serialPortError); + +private: + QList m_serialPortInfos; + int m_currentQuerry; + QSerialPort::BaudRate m_baudRate; + QSerialPort::OpenModeFlag m_openModeFlag; + QSerialPort::DataBits m_dataBits; + QSerialPort::FlowControl m_flowControll; + QSerialPort::Parity m_parity; + QSerialPort::StopBits m_stopBits; + QSerialPort *m_currentSerialPort; + + LOGGER_T m_logger; +}; + +} diff --git a/Lts/Common/LtsSession/CMakeLists.txt b/Lts/Common/LtsSession/CMakeLists.txt new file mode 100755 index 000000000..0282087b2 --- /dev/null +++ b/Lts/Common/LtsSession/CMakeLists.txt @@ -0,0 +1,87 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# Created by Mike Wille, SLSE, 2016-05-15 +# + + +# LtsSessionDataDds +SET(LtsSessionDataDds_SRCS + lts_session_data_dds.cpp + lts_session_data_dds.h +) +NGL_ADD_LIBRARY2(WS LtsSessionDataDds SHARED ${LtsSessionDataDds_SRCS}) +NGL_TARGET_INCLUDE_DIRECTORIES(LtsSessionDataDds + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ + ${CMAKE_CURRENT_BINARY_DIR}/../ +) +NGL_TARGET_LINK_LIBRARIES(LtsSessionDataDds + PRIVATE + ddsconnector + PUBLIC + ddscore + NGLLogger + Qt5::Core +) + + + +# LtsSession +SET(ltssession_SRCS + lts_session_manager.h + lts_session_manager.cpp + lts_session.h + lts_session.cpp + lts_session_lock_handler.h + lts_session_lock_handler.cpp +) + +NGL_ADD_LIBRARY2(WS LtsSession SHARED ${ltssession_SRCS}) +NGL_TARGET_INCLUDE_DIRECTORIES(LtsSession + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) + +NGL_TARGET_LINK_LIBRARIES(LtsSession + PRIVATE + Qt5::Network + LtsMultiMonitor + LtsSessionData + ddsconnector + LtsUtilsGuiDds # ReqHelpr & for Debug Menu ! TODO: rm on release! + LtsNotification + svcCoreClient # Core Ftc - for TLH + OhpOnlineHelpViewerGuiPluginInterface + Qt5::Svg + PUBLIC + LtsWidgets + LtsSessionDataDds + Qt5::Core + GuiPluginInterface + DataPluginInterface + NGLLogger + ProjectConfig +) + + +# LtsSessionData (avoid cross-deps between AppManager, Session and PluginManager) +SET(LtsSessionData_SRCS + lts_session_data.cpp + lts_session_data.h +) + +NGL_ADD_LIBRARY2(WS TOOLSSV TOOLSWS LtsSessionData SHARED ${LtsSessionData_SRCS}) + +NGL_TARGET_INCLUDE_DIRECTORIES(LtsSessionData + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) + +NGL_TARGET_LINK_LIBRARIES(LtsSessionData + PUBLIC + Qt5::Core + Qt5::Gui # QPixmap as Storage for Profile Picture + ddscore +) diff --git a/Lts/Common/LtsSession/lts_session.cpp b/Lts/Common/LtsSession/lts_session.cpp new file mode 100755 index 000000000..023b20a85 --- /dev/null +++ b/Lts/Common/LtsSession/lts_session.cpp @@ -0,0 +1,1649 @@ + + + +#include "lts_session.h" +#include "lts_session_manager.h" +#include "lts_session_data_dds.h" +#include "lts_session_lock_handler.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +#include + + + + +using namespace LtsWidgets; + +namespace LtsSession +{ +bool LtsSession::s_valgrind = false; +int LtsSession::s_nextSessionId = 0; + + +LtsSession::LtsSession(LtsSessionManager *sessionManager) + : m_sessionManager(sessionManager) + , m_mainBackground(nullptr) + , m_hasBaseLayout(false) + , m_baseLayout(nullptr) + , m_markedForDeletion(false) + , m_payloads(false) + , m_initDone(false) + , m_menuVisible(true) + , m_firstActionsFetch(true) + , m_exerciseData(new LtsWidgets::ExerciseData) + , m_currentInitIfc(nullptr) + , m_slotPluginInitDoneMutex(false) + , m_pluginInitDone(false) + , m_hidden(false) + , m_loadedFromSavegame(false) + , m_foreverInvisible(false) + , m_id(s_nextSessionId++) +{ + LtsWidgets::LtsGlobalWaitDialog::getInstance()->wait(); + + connect(this, &LtsSession::signalDoneInitializingSession, + this, &LtsSession::slotStopWaitDialog); + connect(this, &LtsSession::signalInitializationAborted, + this, &LtsSession::slotStopWaitDialog); + + + + connect(this, &LtsSession::_signalDoneInitializingSession, + this, [this]() + { + + + disconnect(this, &LtsSession::_signalDoneInitializingSession, + this, nullptr); + + m_initDone = true; + emit signalDoneInitializingSession(); + }); + + + QTimer *t = new QTimer(this); + t->setInterval(1000); + connect(t, &QTimer::timeout, + this, &LtsSession::signalStillAlive); + t->start(); + m_scriptEditorTimer = new QTimer(this); + + if ( strcmp(PROJECTBUILD, "qatarmout") == 0 + || strcmp(PROJECTBUILD, "qatar") == 0) + { + DEBUG_LOG(m_logger) << "qatarmout Timer = 4000"; + m_scriptEditorTimer->setInterval(4000); + } + else + { + DEBUG_LOG(m_logger) << "qatarmout Timer = 0"; + m_scriptEditorTimer->setInterval(0); + } + + m_scriptEditorTimer->setSingleShot(true); + connect(m_scriptEditorTimer, &QTimer::timeout, this, &LtsSession::delayScriptEditorStart); +} + + +LtsSession::~LtsSession() +{ + + + + delete m_baseLayout; + + + + + + clearGuiPluginInterfaces(); + clearDataPluginInterfaces(); + + delete m_exerciseData; +} + + +QString LtsSession::toString() const +{ + std::ostringstream stream; + stream << "LtsSession:" + << " this=" << this + << ", domainId=" << this->domainId() + << ", partitionId=" << this->partitionId() + << ", partitionType=" << this->partitionType() + << ", exerciseId='" << this->exerciseId() << "'" + << ", workspaceId='" << this->workspaceId() << "'" + << ", appToString='" << this->appToString(this->app()) << "'" + << ", LtsSessionDataDds=" << LtsSessionDataDds::toString(); + return QString::fromStdString(stream.str()); + + + + + + + + + +} + + +QWidget *LtsSession::baseLayoutWidget() +{ + + + return static_cast(m_baseLayout); +} + + +void LtsSession::aboutToCloseTab(LayoutArea area) +{ + m_baseLayout->getTabFromLayoutArea(area)->aboutToClose(); +} + + +void LtsSession::setDependency(LtsSession *session) +{ + if (session == this) + { + Q_ASSERT(session != this); + ERROR_LOG(m_logger) << "Tried to set session dependency on self"; + return; + } + + m_dependency = session; +} + + +void LtsSession::aboutToClose() +{ + emit sessionAboutToBeDestroyed(this); + + if (m_baseLayout == nullptr) + return; + + aboutToCloseTab(LayoutArea::eDialogTab); + aboutToCloseTab(LayoutArea::eInfoTab); + aboutToCloseTab(LayoutArea::eSpecialTab); + aboutToCloseTab(LayoutArea::eMainTab); +} + + +void LtsSession::slotStopWaitDialog() +{ + disconnect(this, &LtsSession::signalDoneInitializingSession, + this, &LtsSession::slotStopWaitDialog); + disconnect(this, &LtsSession::signalInitializationAborted, + this, &LtsSession::slotStopWaitDialog); + + LtsWidgets::LtsGlobalWaitDialog::getInstance()->done(); +} + + +QString LtsSession::appToString(LtsAppDescriptor::App app) +{ + using namespace LtsAppDescriptor; + switch (app) + { + case App::Situation: + return tr("Situation"); + case App::ResourceManager: + return tr("Resource Manager"); + case App::PlanEditor: + return tr("Plan Editor"); + case App::BuildingViewer: + return tr("Building Viewer"); + case App::OC: + return tr("O/C"); + case App::OrgChart: + return tr("Organisation Chart"); + case App::Scripts: + return tr("Scripts"); + case App::Snapshot: + return tr("Snapshot"); + case App::SystemMonitor: + return tr("System Monitor"); + case App::Timeline: + return tr("Timeline"); + case App::SymbolEditor: + return tr("Symbol Editor"); + case App::ScriptEditorEssentialsView: + return tr("Scripts Essentials View"); + case App::BaseDownloader: + return tr("Base Downloader"); + case App::OnlineHelp: + return tr("Online Help"); + case App::ThpSituationReplay: + return tr("THP Situation Replay"); + case App::SituationTablet: + return tr("Situation (Tablet)"); + case App::SilhouetteEditor: + return tr("Silhouette Editor"); + case App::Shootback: + return tr("Shootback"); + case App::Login: + return tr("Login"); + break; + case App::SAAs: + return tr("SAAs"); + break; + } + + return tr("Unknown"); +} + +QString LtsSession::appIconFilename(LtsAppDescriptor::App app) +{ + using namespace LtsAppDescriptor; + switch (app) + { + case App::Situation: + return QStringLiteral("icon:situation.svg"); + case App::ResourceManager: + return QStringLiteral("icon:player_equipment.svg"); + case App::PlanEditor: + return QStringLiteral("icon:plan_editor.svg"); + case App::BuildingViewer: + return QStringLiteral("icon:3d_viewer.svg"); + case App::OC: + return QStringLiteral("icon:display_oc_evaluations.svg"); + case App::OrgChart: + return QStringLiteral("icon:org_chart.svg"); + case App::Scripts: + return QStringLiteral("icon:insert_script.svg"); + case App::Snapshot: + return QStringLiteral("icon:snapshots.svg"); + case App::SystemMonitor: + return QStringLiteral("icon:system_status.svg"); + case App::Timeline: + return QStringLiteral("icon:timeline.svg"); + case App::SymbolEditor: + return QStringLiteral("icon:define_tactical_symbols.svg"); + case App::ScriptEditorEssentialsView: + return QStringLiteral("icon:evaluation_elements.svg"); + case App::BaseDownloader: + return QStringLiteral("icon:download.svg"); + case App::OnlineHelp: + return QStringLiteral("icon:help.svg"); + case App::ThpSituationReplay: + return QStringLiteral("icon:restart.svg"); + case App::SituationTablet: + return QStringLiteral("icon:situation.svg"); + case App::SilhouetteEditor: + return QStringLiteral("icon:situation.svg"); + case App::Shootback: + return QStringLiteral("icon:target.svg"); + case App::SAAs: + return QStringLiteral("icon:saa_remote_desktop.svg"); + case App::Login: + break; + } + + return {}; +} + +QIcon LtsSession::appToIcon(LtsAppDescriptor::App app) +{ + return LtsUtils::iconFromSvg(appIconFilename(app)); +} + + +bool LtsSession::removeMenuAction(LtsWidgets::LtsDragIcon *icon) +{ + if (!m_baseLayout) + return false; + return m_baseLayout->removeMenuAction(icon); +} + + +void LtsSession::setMenuVisible(bool visible) +{ + m_menuVisible = visible; + + if (m_baseLayout == nullptr) + return; + + m_baseLayout->getMenu()->setVisible(visible); +} + +LtsDragIcon *LtsSession::addAction(const LtsWidgets::PluginData &plData, bool isFirstRun) +{ + if (!m_baseLayout) + { + ERROR_LOG(m_logger) << "No BaseLayout available atm, cannot add action!"; + return nullptr; + } + + return m_baseLayout->addMenuAction(plData, isFirstRun); +} + + +LtsNotification::LtsNotification *LtsSession::createNotification(const QString &identifier) +{ + LtsNotification::LtsNotificationController *c = new LtsNotification::LtsNotificationController(identifier); + LtsNotification::LtsNotification *n = new LtsNotification::LtsNotification(c); + + if (m_baseLayout) + m_baseLayout->addNotification(c->createWidget()); + + m_notiCtlList.append(c); + + connect(c, &LtsNotification::LtsNotificationController::destroyed, + this, [this, c]() { m_notiCtlList.removeAll(c); }); + + return n; +} + + +bool LtsSession::init(const DdsConnectionParams &connParams, + const QVector &dataPluginList, + const QVector &guiPluginList, + bool hidden) +{ + m_foreverInvisible = hidden; + if (!LtsSessionDataDds::init(connParams)) + return false; + initializePlugins(dataPluginList, guiPluginList); + return true; +} + + +bool LtsSession::init(const dds::domain::DomainParticipant &dp, + const DdsConnectionParams &connParams, + const QVector &dataPluginList, + const QVector &guiPluginList) +{ + if (!LtsSessionDataDds::init(dp, connParams)) + return false; + initializePlugins(dataPluginList, guiPluginList); + return true; +} + + +void LtsSession::initializePlugins(const QVector &dataPluginList, const QVector &guiPluginList) +{ + initializeDataPlugins(dataPluginList); + initializeGuiPlugins(guiPluginList); +} + + +void LtsSession::initializeDataPlugins(const QVector &dataPluginList) +{ + QMap> newDataPluginInterfaces; + + for (const LtsPluginManager::AppPluginInstance &api : dataPluginList) + { + DataPluginBase::Plugin *plugin = qobject_cast(api.pluginInstance.plugin); + + if (!plugin) + { + WARNING_LOG(m_logger) << "Session: invalid data plugin (" << api.pluginInstance.plugin->metaObject()->className() << ")" << api.pluginInstance.fileName; + continue; + } + + + if (app() == LtsAppDescriptor::App::Login + && (!plugin->pluginName().contains(QStringLiteral("AssetManagement")) + && !plugin->pluginName().contains(QStringLiteral("OperatorManagement")) + && !plugin->pluginName().contains(QStringLiteral("ExerciseManagement"))) + ) + continue; + + + if (!plugin->availableInPartition(partitionType())) + { + DEBUG_LOG(m_logger) << "Data plugin " << api.pluginInstance.pluginName << " is not available in this partition type " << partitionType(); + continue; + } + + + if ((!plugin->pluginName().contains(QStringLiteral("AssetManagement")) + && !plugin->pluginName().contains(QStringLiteral("OperatorManagement")) + && !plugin->pluginName().contains(QStringLiteral("ExerciseManagement")) + ) && app() == LtsAppDescriptor::App::Login + ) + continue; + std::shared_ptr ifc = plugin->createDataInterface(this); + if (!ifc) + { + WARNING_LOG(m_logger) << "Create data interface failed for '" << api.pluginInstance.pluginName << "' !"; + continue; + } + + m_dataPluginInterfaces[plugin->pluginName()] = ifc; + newDataPluginInterfaces[plugin->pluginName()] = ifc; + DEBUG_LOG(m_logger) << "Adding data plugin " << plugin->pluginName() << " for " << partitionType() << " - " << this; + } + + addAvailablaeDataPluginInterfaces(newDataPluginInterfaces); +} + + +void LtsSession::initializeGuiPlugins(const QVector &guiPluginList) +{ + m_failedFetches.clear(); + + + + + for (auto i = guiPluginList.constBegin(); i != guiPluginList.constEnd(); ++i) + { + const LtsPluginManager::AppPluginInstance &api = *i; + + GuiPluginBase::Plugin *plugin = qobject_cast(api.pluginInstance.plugin); + + if (!plugin) + { + WARNING_LOG(m_logger) << "Session: invalid gui plugin (" << api.pluginInstance.plugin->metaObject()->className() << ")" << api.pluginInstance.fileName; + continue; + } + + + + if (!plugin->availableInPartition(partitionType())) + { + DEBUG_LOG(m_logger) << "Gui plugin " << api.pluginInstance.pluginName << " is not available in this partition type " << partitionType(); + continue; + } + + + std::shared_ptr ifc = plugin->createGuiInterface(this); + + if (!ifc) + { + WARNING_LOG(m_logger) << "Create gui interface failed for '" << api.pluginInstance.pluginName << "' (" << api.pluginInstance.plugin->metaObject()->className() << ") !"; + continue; + } + + + QFlags flags = api.m_flags; + + if (api.pluginInstance.pluginName == QStringLiteral("dev")) + flags |= LtsAppDescriptor::PluginInfo::Flag::ACTIONS; + + ifc->setFlags(flags); + ifc->setOptions(api.m_options); + ifc->setAppId(api.m_appId); + m_guiPluginInterfaces[plugin->pluginName()] = ifc; + DEBUG_LOG(m_logger) << "Adding gui plugin " << plugin->pluginName() << " for " << partitionType() << " - " << this; + } + + + + + QMutableMapIterator > it(m_guiPluginInterfaces); + while (it.hasNext()) + { + const auto val = it.next(); + const QString &guiPluginKey = val.key(); + std::shared_ptr guiPlugin = val.value(); + + if (!guiPlugin->fetchInterfaces()) + { + DEBUG_LOG(m_logger) << "Fetching interfaces failed for Gui Plugin '" << guiPluginKey << "' - removing and refetching !"; + m_failedFetches.append(guiPluginKey); + it.remove(); + it.toFront(); + + + for (auto i = m_payloadPriorityMap.begin(); i != m_payloadPriorityMap.end(); ++i) + { + std::shared_ptr payloadIfc = *i; + + if (payloadIfc == guiPlugin) + break; + } + + m_payloadList.removeAll(guiPlugin); + } + else + { + + + int executionPriority = guiPlugin->executionPriority(); + + if (executionPriority > -1) + { + if (m_payloads && guiPlugin->getFlags().testFlag(LtsAppDescriptor::PluginInfo::Flag::PAYLOAD)) + { + if (executionPriority == 9999) + { + if (!m_payloadList.contains(guiPlugin)) + m_payloadList.append(guiPlugin); + } + else + m_payloadPriorityMap.insert(executionPriority, guiPlugin); + + } + } + + + } + } + + + QTimer::singleShot(100, this, &LtsSession::slotStartPluginInit); + +} + + +LtsTabWidget *LtsSession::locateWidget(QWidget *widgetToLocate, LayoutArea area) +{ + if (!m_baseLayout) + return nullptr; + + LtsTabWidget *tabWidget = m_baseLayout->getTabFromLayoutArea(area); + + if (tabWidget->indexOf(widgetToLocate) >= 0) + return tabWidget; + + return nullptr; +} + + +void LtsSession::enableBaseLayoutMenu(bool on) +{ + if (m_baseLayout) + { + QWidget *ltsMenu = m_baseLayout->getMenu(); + + if (ltsMenu) + ltsMenu->setEnabled(on); + } +} + + +void LtsSession::enableBaseLayoutArea(LtsWidgets::LayoutArea area, bool on) +{ + if (m_baseLayout) + { + QWidget *ltsWidget = m_baseLayout->getTabFromLayoutArea(area); + if (ltsWidget) + ltsWidget->setEnabled(on); + } +} + + +int LtsSession::workstationIp() +{ + static int wsId = -1; + if (wsId == -1) + { +#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0) + const QString hostname = workstationName(); + const auto addreses = QHostInfo::fromName(hostname).addresses(); + for (const QHostAddress &ha : addreses) + { + if (ha.isLoopback()) + continue; + if (ha.protocol() != QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) + continue; + wsId = static_cast(ha.toIPv4Address()); + break; + } +#else + const QList interfaces = QNetworkInterface::allInterfaces(); + for (const QNetworkInterface &ninterface : interfaces) + { + if (ninterface.type() == QNetworkInterface::InterfaceType::Wifi) + continue; + const auto addreseEntry = ninterface.addressEntries(); + for (const QNetworkAddressEntry &ent : addreseEntry) + { + const QHostAddress ha = ent.ip(); + if (ha.isLoopback()) + continue; + if (ha.protocol() != QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) + continue; + wsId = static_cast(ha.toIPv4Address()); + break; + } + if (wsId != -1) + break; + } +#endif + } + return wsId; +} + + + +QString LtsSession::workstationName() +{ + return QHostInfo::localHostName().toUpper(); +} + + +const QString LtsSession::workspaceId() const +{ + if (isReplayPartition()) + return QStringLiteral("replay_%1").arg(QString::number(partitionId())); + return m_workspaceId; +} + + +void LtsSession::setWorkspaceId(const QString &newWorkspaceId ) +{ + DEBUG_LOG(m_logger) << LOGFUNCTION << " change workspaceId: cur = '" << m_workspaceId << "' for app '" << appToString(app()) << "'"; + DEBUG_LOG(m_logger) << LOGFUNCTION << " change workspaceId: new = '" << newWorkspaceId << "' for app '" << appToString(app()) << "'"; + m_workspaceId = newWorkspaceId; + + if (m_baseLayout) + m_baseLayout->setWorkspaceId(workspaceId() ); + + emit workspaceIdChanged(newWorkspaceId); +} + + +ExcState LtsSession::exerciseState() const +{ + return m_exerciseData ? m_exerciseData->exState : ExerciseState::eUnknown; +} + + +void LtsSession::connectOnlineHelpButton(LtsWidgets::LtsDialog *widget) +{ + + connect(widget, &LtsDialog::signalShowOnlineHelp, + this, &LtsSession::slotShowOnlineHelp, + Qt::UniqueConnection); +} + + +void LtsSession::slotStapleTopicLockHandler(LtsWidgets::LtsDialog *widget) +{ + if (widget->property("topicLockStapled").isValid()) + { + Q_ASSERT(false); + return; + } + widget->setProperty("topicLockStapled", true); + + connectOnlineHelpButton(widget); + if (m_coreFtc == nullptr) + { + DEBUG_LOG(m_logger) << "no core ftc, no TopicLockHandler for " << toString(); + return; + } + + auto *topicLockHandler = new TopicLockHandler(); + + + + if (!topicLockHandler->init(m_coreFtc, m_exerciseId)) + { + WARNING_LOG(m_logger) << "LtsSession::addWidget: init of TopicLockHandler failed!"; + delete topicLockHandler; + return; + } + + connect(topicLockHandler, &TopicLockHandler::signalLockDialog, + widget, &LtsDialog::slotLock, Qt::QueuedConnection); + connect(topicLockHandler, &TopicLockHandler::signalUnlockDialog, + widget, &LtsDialog::slotUnlock, Qt::QueuedConnection); + connect(widget, &LtsDialog::signalLockTopic, + topicLockHandler, &TopicLockHandler::slotTryLock, Qt::DirectConnection); + + + + connect(widget, &LtsDialog::destroyed, + topicLockHandler, &TopicLockHandler::slotFinalize, Qt::QueuedConnection); + + + + + connectOnlineHelpButton(widget); +} + + +bool LtsSession::focusTab(const QString &menuGroup, const QString &identifier, LtsWidgets::LayoutArea area) +{ + if (!m_baseLayout) + { + ERROR_LOG(m_logger) << "no base layout to focus a tab on! menuGroup: " << menuGroup << "ident: " << identifier; + return false; + } + + return m_baseLayout->focusTab(menuGroup, identifier, area); +} + + +LtsWidgets::LtsDialog *LtsSession::getWidget(const QString &menuGroup, const QString &identifier, LtsWidgets::LayoutArea area) +{ + if (!m_baseLayout) + { + ERROR_LOG(m_logger) << "no base layout to get a widget from! menuGroup: " << menuGroup << "ident: " << identifier; + return nullptr; + } + + return m_baseLayout->getWidget(menuGroup, identifier, area); +} + + +bool LtsSession::widgetExists(const QString &menuGroup, const QString &identifier, LayoutArea area) const +{ + if (!m_baseLayout) + return false; + LtsTabWidget *tab = m_baseLayout->getTabFromLayoutArea(area); + if (!tab) + return false; + QWidget *existingWidget = tab->getWidget(menuGroup, identifier); + return existingWidget != nullptr; +} + + +bool LtsSession::addWidget(LtsDialog *widget, const QString &menuGroup, const QString &identifier, LayoutArea area, bool setClosable) +{ + + + if (!m_baseLayout) + { + ERROR_LOG(m_logger) << "no base layout to put a widget on! menuGroup: " << menuGroup << "ident: " << identifier; + return false; + } + + + + + + + return m_baseLayout->addWidget(widget, menuGroup, identifier, area, setClosable); +} + + +void LtsSession::setExerciseData(const LtsWidgets::ExerciseData &exerciseData) +{ + + if ((*m_exerciseData) == exerciseData) + return; + + *m_exerciseData = exerciseData; + + if (!m_baseLayout) + return; + m_baseLayout->setExerciseData(exerciseData); + emit signalExerciseDataChanged(); +} + + +const LtsWidgets::ExerciseData *LtsSession::exerciseData() const +{ + return m_exerciseData; +} + + +void LtsSession::setProfilePicture(const QPixmap &pixmap) +{ + + if (!m_baseLayout) + return; + m_baseLayout->setProfilePicture(pixmap); +} + + +void LtsSession::makeVisible() +{ + Q_ASSERT(m_baseLayout == nullptr); + if (m_baseLayout) + { + qCritical() << "base layout already visible"; + return; + } + + createBaseLayout(); +} + + +void LtsSession::refetchActions() +{ + + for (auto i = m_guiPluginInterfaces.cbegin(); i != m_guiPluginInterfaces.cend(); ++i) + { + std::shared_ptr ifc = *i; + + if (!ifc->availableInPartition(partitionType())) + { + DEBUG_LOG(m_logger) << i.key() << " is not available in this partition type " << partitionType(); + continue; + } + + if (m_firstActionsFetch && ifc->getFlags().testFlag(LtsAppDescriptor::PluginInfo::Flag::MAIN) && !ifc->getFlags().testFlag(LtsAppDescriptor::PluginInfo::Flag::ACTIONS)) + { + ERROR_LOG(m_logger) << "Plugin '" << i.key() << "' has flag 'MAIN' but is missing the 'ACTIONS' flag - ingoring actions!"; + continue; + } + + if (ifc->getFlags().testFlag(LtsAppDescriptor::PluginInfo::Flag::ACTIONS)) + { + + const QVector plData = ifc->getActions(); + + if (plData.isEmpty()) + DEBUG_LOG(m_logger) << i.key() << " (" << ifc->menuGroup() << ") does not provide actions"; + + + + for (const LtsWidgets::PluginData &pd : plData) + { + if (!pd.menuGroup.isEmpty() && !pd.menuText.isEmpty() && pd.identifier.isEmpty()) + ERROR_LOG(m_logger) << "Identifier for Action " << pd.menuGroup << " - " << pd.menuText << " missing - not added!"; + else + { + if (!LtsSessionData::LtsSessionData::getInstance()->hasOperatorRight(pd.operatorRight)) + m_missingRightActions += pd; + + m_baseLayout->addMenuAction(pd, m_firstActionsFetch); + } + } + } + else + { + + } + } + +#ifdef LTS_DEBUG + if (m_firstActionsFetch) + { + const QString &executeAction(qEnvironmentVariable("EXCON_EXECUTE_ACTION", {})); + if (!executeAction.isEmpty()) + { + WARNING_LOG(m_logger) << "EXCON_EXECUTE_ACTION was set to: " << executeAction; + m_baseLayout->executeAction(executeAction); + } + } +#endif + + m_firstActionsFetch = false; +} + +void LtsSession::slotSwap() +{ + LtsMultiMonitor::LtsMultiMonitor::getInstance()->selectAndSwap(m_baseLayout); +} + + +int LtsSession::screenId() const +{ + if (m_baseLayout == nullptr) + return -1; + + LtsMultiMonitor::LtsScreenBase *screen = LtsMultiMonitor::LtsMultiMonitor::getInstance()->widgetsScreen(m_baseLayout); + + if (screen == nullptr) + return -2; + + return screen->screenId(); +} + + +int LtsSession::screenContainerId() const +{ + if (m_baseLayout == nullptr) + return -1; + + LtsMultiMonitor::LtsScreenBase *screen = LtsMultiMonitor::LtsMultiMonitor::getInstance()->widgetsScreen(m_baseLayout); + + if (screen == nullptr) + return -2; + + try + { + return screen->containerIdOfWidget(m_baseLayout); + } + catch(...){} + + return -3; +} + + +int LtsSession::screenTypeId() const +{ + if (m_baseLayout == nullptr) + return -1; + + LtsMultiMonitor::LtsScreenBase *screen = LtsMultiMonitor::LtsMultiMonitor::getInstance()->widgetsScreen(m_baseLayout); + + if (screen == nullptr) + return -2; + + return screen->screenType(); +} + + +LtsAppDescriptor::App LtsSession::app() const +{ + return m_sessionManager ? m_sessionManager->appId() : LtsAppDescriptor::App::Login; +} + + +bool LtsSession::event(QEvent *e) +{ + if (e->type() == QEvent::LanguageChange) + m_baseLayout->setAppName(appToString(app())); + + return LtsSessionDataDds::event(e); +} + + +void LtsSession::addDeveloperActions() +{ + + { + LtsWidgets::PluginData p; + p.menuGroup = QStringLiteral("[Dev]"); + p.menuText = QStringLiteral("Open Admin Session"); + p.identifier = QStringLiteral("OpenAdminSession"); + + p.directFunc = [&](const LtsWidgets::PluginData &) + { + LtsSession *adminSession = m_sessionManager->getSession(IDL::PartitionType::Global, 0); + if (!adminSession) + { + + DdsConnectionParams connParams; + connParams.domainId = domainId(); + connParams.partitionId = 0; + connParams.exerciseId = 0; + connParams.partitionType = IDL::PartitionType::Global; + connParams.tcpConnectionUrl = tcpConnectionUrl(); + connParams.qos_file = m_qosFileList; + adminSession = m_sessionManager->createSession(connParams, + true, + false); + return; + } + else + adminSession->makeVisible(); + }; + + m_baseLayout->addMenuAction(p); + } + + { + LtsWidgets::PluginData p; + p.menuGroup = QStringLiteral("[Dev]"); + p.menuText = QStringLiteral("Slow down RequestHelper"); + p.identifier = QStringLiteral("SlowDownRequestHelper"); + + p.directFunc = [&](const LtsWidgets::PluginData &) + { + LtsUtils::RequestHelper::s_debug = true; + }; + + m_baseLayout->addMenuAction(p); + } + + { + LtsWidgets::PluginData p; + p.menuGroup = QStringLiteral("[Dev]"); + p.menuText = QStringLiteral("Reset RequestHelper"); + p.identifier = QStringLiteral("ResetRequestHelper"); + + p.directFunc = [&](const LtsWidgets::PluginData &) + { + LtsUtils::RequestHelper::s_debug = false; + }; + + m_baseLayout->addMenuAction(p); + } + + { + LtsWidgets::PluginData p; + p.menuGroup = QStringLiteral("[Dev]"); + p.menuText = QStringLiteral("fake DoneInitializingSession"); + p.identifier = QStringLiteral("fakeDoneInitializingSession"); + + p.directFunc = [&](const LtsWidgets::PluginData &) + { + m_initDone = true; + emit _signalDoneInitializingSession(); + }; + + m_baseLayout->addMenuAction(p); + } + +} + + +bool LtsSession::notifyUnsavedChanges() +{ + if (m_baseLayout == nullptr) + return true; + + return m_baseLayout->notifyUnsavedChanges(); +} + + +void LtsSession::createBaseLayout() +{ + + + using namespace LtsAppDescriptor; + + TRACE_LOG(m_logger) << LOGFUNCTION << " START, Creating Base Layout for Session " << partitionId(); + + Q_ASSERT(m_baseLayout == nullptr); + if (m_baseLayout != nullptr) + { + WARNING_LOG(m_logger) << "Base Layout already exists for Session" << this; + return; + } + + m_baseLayout = new LtsBaseLayoutWidget; + + connect(m_baseLayout, &LtsBaseLayoutWidget::signalSuccessfullyPlaced, + this, [this]() + { + DEBUG_LOG(m_logger) << "current APP "<app()); + + if ( this->app() == App::Scripts) + { + m_scriptEditorTimer->start(); + } + else + { + if (m_needsToBeSuccessfullyPlaced) + { + + emit _signalDoneInitializingSession(); + } + } + }); + + connect(m_baseLayout, &LtsBaseLayoutWidget::destroyed, + this, [this]() + { + m_baseLayout = nullptr; + + }); + + m_baseLayout->hide(); + m_baseLayout->setSessionInfo(domainId(), partitionType(), partitionId(), workspaceId()); + m_baseLayout->setWindowIcon(appToIcon(app())); + + + + + + + if (app() == App::ScriptEditorEssentialsView || + m_presetBaseLayoutAsSecondaryWidget) + m_baseLayout->setSecondaryWidget(); + + connect(m_baseLayout, &LtsBaseLayoutWidget::signalRetranslateActions, + this, &LtsSession::refetchActions); + connect(m_baseLayout, &LtsBaseLayoutWidget::signalClose, + this, &LtsSession::signalClose); + connect(m_baseLayout, &LtsBaseLayoutWidget::signalLogout, + this, &LtsSession::signalLogout); + connect(m_baseLayout, &LtsBaseLayoutWidget::signalShowOperatorSettings, + this, &LtsSession::slotShowOperatorSettings); + connect(m_baseLayout, &LtsBaseLayoutWidget::signalSwap, + this, &LtsSession::slotSwap); + connect(this, &LtsSession::partitionTimeChanged, + m_baseLayout, &LtsBaseLayoutWidget::setExerciseTime); + connect(m_baseLayout, &LtsBaseLayoutWidget::signalStapleTopicLockHandler, + this, &LtsSession::slotStapleTopicLockHandler, Qt::DirectConnection); + + const auto *sessionData = LtsSessionData::LtsSessionData::getInstance(); + + const QString &operatorName = sessionData->getOperatorName(); + bool showTerminalWindow = sessionData->getShowTerminalWindow(); + m_baseLayout->setOperatorName(operatorName, showTerminalWindow); + m_baseLayout->setProfilePicture(sessionData->getProfilePicture()); + m_baseLayout->setAppName(appToString(app())); + + refetchActions(); + +#ifdef LTS_DEBUG + bool showDeveloperActions = true; +#else + bool showDeveloperActions = false; +#endif + + if (sessionData->hasDebugFlag(QStringLiteral("DeveloperTools.Active"))) + showDeveloperActions = true; + + if (showDeveloperActions) + addDeveloperActions(); + + + for (LtsNotification::LtsNotificationController *ctl : qAsConst(m_notiCtlList)) + { + if (!m_baseLayout->notificationExists(ctl->identifier())) + m_baseLayout->addNotification(ctl->createWidget()); + } + + m_hasBaseLayout = true; + + m_baseLayout->getMenu()->setVisible(m_menuVisible); + m_baseLayout->initilizationFinished(); + + + + if (!m_foreverInvisible) + { + + QVector irreplacableWidgets {baseLayoutWidget()}; + LtsSession *dependencySession = dependency(); + int runaway = 100; + while (dependencySession && runaway > 0) + { + irreplacableWidgets += dependencySession->baseLayoutWidget(); + dependencySession = dependencySession->dependency(); + runaway--; + } + + + + + + + + + LtsMultiMonitor::LtsMultiMonitor *multiMonitor = LtsMultiMonitor::LtsMultiMonitor::getInstance(); + + WARNING_LOG(m_logger) << "Presets: ScreenId: " << m_screenIdPreset << ", screenContainerId: " << m_screenContainerIdPreset; + if (m_screenIdPreset >= 0) + multiMonitor->putScreen(m_baseLayout, m_screenIdPreset, m_screenContainerIdPreset); + else + multiMonitor->selectScreen(m_baseLayout, nullptr, true, false, irreplacableWidgets); + + slotMonitorSelected(); + connect(multiMonitor, &LtsMultiMonitor::LtsMultiMonitor::signalSelected, + this, &LtsSession::slotMonitorSelected, Qt::UniqueConnection); + + connect(multiMonitor, &LtsMultiMonitor::LtsMultiMonitor::signalScreenCountChanged, + m_baseLayout, &LtsBaseLayoutWidget::slotScreenCountChanged); + + m_baseLayout->slotScreenCountChanged(multiMonitor->screenCount()); + } + + + if (!sessionData->isTablet()) + return; + + + LtsNotification::LtsNotification *noti = createNotification(QStringLiteral("TabletWindowSwitcher")); + + noti->setIcon(QIcon(QStringLiteral("icon:switch_display.svg"))); + noti->setAction([this]() + { + emit taskSwitch(this ); + }); + + + emit hideAllOtherBLWs(this); +} + + +void LtsSession::slotMonitorSelected() +{ + auto mmm = LtsMultiMonitor::LtsMultiMonitor::getInstance(); + LtsMultiMonitor::LtsScreenBase *screen = mmm->widgetsScreen(m_baseLayout); + + if (!screen) + return; + + int newScreenContainerId; + try + { + newScreenContainerId = screen->containerIdOfWidget(m_baseLayout); + } + catch (...) + { + WARNING_LOG(m_logger) << "cannot find container for base layout"; + return; + } + + const int newScreenId = screen->screenId(); + if (m_screenId != newScreenId) + { + m_screenId = newScreenId; + m_screenContainerId = newScreenContainerId; + emit signalScreenIdChanged(m_screenId, m_screenContainerId, screen->screenType()); + } +} + +void LtsSession::delayScriptEditorStart() +{ + DEBUG_LOG(m_logger)<<"LtsSession::delayScriptEditorStart "; + if (m_needsToBeSuccessfullyPlaced) + { + + + emit _signalDoneInitializingSession(); + } +} + +bool LtsSession::isChildOfSession(QWidget *child) +{ + if (!child) + { + return false; + } + return baseLayout()->isAncestorOf(child); +} + +void LtsSession::doneInitializingSession() +{ + LtsGlobalWaitDialog::getInstance()->done(); + + + + if (m_markedForDeletion) + { + emit signalInitializationAborted(); + emit signalClose(); + return; + } + + if (!m_sessionManager->isHeadless()) + { + int sessionCount = m_sessionManager->sessionCount(); + + if (!m_hidden && (m_hasBaseLayout || sessionCount < 1)) + makeVisible(); + + + + + + + } + + + + if (!createTopicClient(m_coreFtc)) + WARNING_LOG(m_logger) << "could not create core ftc - no tlh"; + m_coreFtc->enableAll(); + + if (!getForeignGuiInterface(m_ohvIfc)) + WARNING_LOG(m_logger) << "no ovh ifc, no Help-Button !"; + + INFO_LOG(m_logger) << "emit signalDoneInitializingSession, session=" << this; + + + if (!m_needsToBeSuccessfullyPlaced || m_sessionManager->isHeadless() || !m_hasBaseLayout) + { + + + emit _signalDoneInitializingSession(); + } + + +} + + +void LtsSession::slotShowOnlineHelp(const QString &fileName) +{ + if (m_ohvIfc == nullptr) + { + LtsWidgets::LtsMessageDialog::showMessage(baseLayout() , tr("Online Help not available.")); + return; + } + + m_ohvIfc->showHelp(fileName); +} + + +void LtsSession::slotShowOperatorSettings() +{ + const QMap &mapButtons = m_baseLayout->mapButtons(); + + for (auto i = mapButtons.constBegin(); i != mapButtons.constEnd(); ++i) + { + const PluginData &pd = *i; + + if (pd.identifier == QStringLiteral("_OpmShowOperatorSettings")) + { + LtsDialog *showOperatorSettingsDlg = pd.createWidgetFunc(nullptr); + + if (showOperatorSettingsDlg != nullptr) + { + addWidget(showOperatorSettingsDlg, pd.menuGroup, pd.identifier, pd.layoutArea, pd.closable); + + } + else + WARNING_LOG(m_logger) << "could not create operator settings dialog from action"; + + return; + } + } + + WARNING_LOG(m_logger) << "action for showing operator settings not found !!"; +} + + +void LtsSession::slotStartPluginInit() +{ + m_initializeThesePlugins = m_guiPluginInterfaces; + slotPluginInitDone(); +} + + +void LtsSession::slotPluginInitDone() +{ + + + if (m_pluginInitDone) + return; + + if (m_slotPluginInitDoneMutex) + { + + + return; + } + + m_slotPluginInitDoneMutex = true; + + GuiPluginBase::Interface *theSender = dynamic_cast(sender()); + + if (theSender != m_currentInitIfc) + { + WARNING_LOG(m_logger) << this << " called slotPluginInitDone from plugin which was not currently active for initialization - ignoring"; +#ifdef LTS_DEBUG + WARNING_LOG(m_logger) << "current init ifc: " << m_currentInitIfc << ", sender: " << sender(); +#endif + m_slotPluginInitDoneMutex = false; + return; + } + + + + m_currentInitIfc = nullptr; + + + auto i = m_initializeThesePlugins.begin(); + + if (i == m_initializeThesePlugins.end()) + { + m_pluginInitDone = true; + + + executePayloads(); + + return; + } + + std::shared_ptr ifc = i.value(); + + if (ifc->availableInPartition(partitionType())) + { + m_currentInitIfc = ifc.get(); + + + + + ifc->init(); + if (!ifc->m_hasInit) + { + + m_currentInitIfc = nullptr; + QTimer::singleShot(0, this, &LtsSession::slotPluginInitDone); + } + } + else + { + m_currentInitIfc = nullptr; + QTimer::singleShot(0, this, &LtsSession::slotPluginInitDone); + } + + m_initializeThesePlugins.erase(i); + + m_slotPluginInitDoneMutex = false; +} + + +void LtsSession::slotExecuteNextPayload() +{ + LtsWidgets::LtsGlobalWaitDialog::getInstance()->wait(); + + + + + + + + + auto it1 = m_payloadPriorityMap.begin(); + + if (it1 != m_payloadPriorityMap.end()) + { + std::shared_ptr ifc = (*it1); + m_payloadPriorityMap.erase(it1); + + DEBUG_LOG(m_logger) << "Executing priorized Payload in Partition " << partitionId() << ", Type " << partitionType(); + + LtsWidgets::LtsGlobalWaitDialog::getInstance()->done(); + + + if (m_blub) + { + m_blub->stop(); + m_blub->deleteLater(); + + } + m_blub = new QTimer(this); + m_blub2 = 0; + QString name(QString::fromUtf8(typeid(ifc.get()).name())); + connect(m_blub, &QObject::destroyed, + this, [name]() + { + + }); + connect(m_blub, &QTimer::timeout, + this, [this, name]() + { + m_blub2++; + + }); + m_blub->start(1000); + + + ifc->executePayload(m_mainBackground); + } + else + { + auto it2 = m_payloadList.begin(); + + if (it2 != m_payloadList.end()) + { + std::shared_ptr ifc = (*it2); + m_payloadList.erase(it2); + + DEBUG_LOG(m_logger) << "Executing unpriorized Payload in Partition " << partitionId() << ", Type " << partitionType(); + + LtsWidgets::LtsGlobalWaitDialog::getInstance()->done(); + + + if (m_blub) + { + m_blub->stop(); + m_blub->deleteLater(); + + } + m_blub = new QTimer(this); + m_blub2 = 0; + QString name(QString::fromUtf8(typeid(ifc.get()).name())); + connect(m_blub, &QObject::destroyed, + this, [name]() + { + + }); + connect(m_blub, &QTimer::timeout, + this, [this, name]() + { + m_blub2++; + + }); + m_blub->start(1000); + + + ifc->executePayload(m_mainBackground); + } + else + { + Q_ASSERT(m_payloadsDone == false); + m_payloadsDone = true; + + m_mainBackground = nullptr; + + if (m_blub) + { + m_blub->stop(); + m_blub->deleteLater(); + + m_blub = nullptr; + } + + + + doneInitializingSession(); + } + } + + +} + + +void LtsSession::executePayloads() +{ + + if (!m_payloadPriorityMap.isEmpty() || !m_payloadList.isEmpty()) + { + + + + + + LtsWidgets::LtsGlobalWaitDialog::getInstance()->done(); + + slotExecuteNextPayload(); + } + else + { + + DEBUG_LOG(m_logger) << "no payloads"; + + if (partitionType() == IDL::PartitionType::Global) + { + m_hasBaseLayout = true; + } + + doneInitializingSession(); + } +} + + +void LtsSession::clearDataPluginInterfaces() +{ + QMap> weakDataPluginInterfaces; + for (auto i = m_dataPluginInterfaces.cbegin(); i != m_dataPluginInterfaces.cend(); ++i) + { + + weakDataPluginInterfaces.insert(i.key(), i.value()); + } + + DEBUG_LOG(m_logger) << "Clearing data plugin interfaces for " << partitionType() << " - " << this; + m_dataPluginInterfaces.clear(); + +#ifdef LTS_DEBUG + + if (partitionType() != IDL::PartitionType::Global) + { + for (auto i = weakDataPluginInterfaces.cbegin(); i != weakDataPluginInterfaces.cend(); ++i) + { + std::shared_ptr sp = i.value().lock(); + if (!sp) + continue; + WARNING_LOG(m_logger) << "still referenced Data Plugin '" << i.key() + << "' in partition type " << partitionType() + << " (use_count=" << sp.use_count() << ")" + << " - application may crash on exit! " << this; + } + } +#endif +} + + +void LtsSession::clearGuiPluginInterfaces() +{ + bool bInClearPluginInterfaces = true; + auto cbCheckEventLoop = [this, &bInClearPluginInterfaces]() + { + if (bInClearPluginInterfaces) + { + ERROR_LOG(m_logger) << "QEventLoop was called within clearGuiPluginInterfaces which is not allowed!"; + Q_ASSERT(false); + } + }; + QTimer::singleShot(0, this, cbCheckEventLoop); + m_ohvIfc = nullptr; + QMap> weakGuiPluginInterfaces; + for (auto i = m_guiPluginInterfaces.cbegin(); i != m_guiPluginInterfaces.cend(); ++i) + { + i.value()->clearInterfaces(); + weakGuiPluginInterfaces.insert(i.key(), i.value()); + } + bInClearPluginInterfaces = false; + + DEBUG_LOG(m_logger) << "Clearing gui plugin interfaces for " << partitionType() << " - " << this; + m_guiPluginInterfaces.clear(); + +#ifdef LTS_DEBUG + + if (partitionType() != IDL::PartitionType::Global) + { + for (auto i = weakGuiPluginInterfaces.cbegin(); i != weakGuiPluginInterfaces.cend(); ++i) + { + std::shared_ptr sp = i.value().lock(); + if (!sp) + continue; + WARNING_LOG(m_logger) << "still referenced Gui Plugin '" << i.key() + << "'in partition type " << partitionType() + << " (use_count=" << sp.use_count() << ")" + << " Gui Plugin '" << i.key() << "'" + << " - application may crash on exit! " << this; + } + } +#endif +} + +LtsSessionManager *LtsSession::getSessionManager() +{ + return m_sessionManager; +} + +} diff --git a/Lts/Common/LtsSession/lts_session.h b/Lts/Common/LtsSession/lts_session.h new file mode 100755 index 000000000..0a739bab4 --- /dev/null +++ b/Lts/Common/LtsSession/lts_session.h @@ -0,0 +1,285 @@ + + + +#pragma once + +#include "lts_session_data_dds.h" +#include "lts_session_manager.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + + +#include + + +namespace LtsWidgets +{ +class LtsBackgroundWidget; +class LtsTabWidget; +class LtsBaseLayoutWidget; +struct ExerciseData; +class LtsDialog; +class LtsDragIcon; +} +namespace LtsNotification +{ +class LtsNotification; +class LtsNotificationController; +} +namespace LtsPluginManager +{ +struct PluginInstance; +struct AppPluginInstance; +} +namespace DeveloperTools +{ +class DevDialog; +class AppWidget; +} +namespace Core +{ +namespace Interface +{ +class Ftc; +} +} +namespace OnlineHelp +{ +class ForeignOnlineHelpViewerGuiInterface; +} + + + +namespace LtsSession +{ +class LTSSESSION_EXPORT LtsSession : public LtsSessionDataDds +{ + Q_OBJECT +public: + static bool s_valgrind; + static int s_nextSessionId; + + enum class PluginInstanceStatus + { + UNKNOWN = 0, + LOADED, + FAILED, + }; + + static QString appToString(LtsAppDescriptor::App app); + static QString appIconFilename(LtsAppDescriptor::App app); + static QIcon appToIcon(LtsAppDescriptor::App app); + + explicit LtsSession(LtsSessionManager *sessionManager); + ~LtsSession(); + + QString toString() const; + + LtsSessionManager *getSessionManager(); + bool init(const DdsConnectionParams &connParams, + const QVector &dataPluginList, + const QVector &guiPluginList, bool hidden); + bool init(const dds::domain::DomainParticipant &dp, + const DdsConnectionParams &connParams, + const QVector &dataPluginList, + const QVector &guiPluginList); + void makeVisible(); + void setPayloadsEnabled() { m_payloads = true; } + void aboutToClose(); + bool isInitializationDone() const { return m_initDone; } + void presetBaseLayoutAsSecondaryWidget() { m_presetBaseLayoutAsSecondaryWidget = true; } + + + + LtsWidgets::LtsDragIcon *addAction(const LtsWidgets::PluginData &plData, bool isFirstRun = false); + void setMenuVisible(bool visible = true); + bool removeMenuAction(LtsWidgets::LtsDragIcon *icon); + int screenId() const; + int screenContainerId() const; + int screenTypeId() const; + LtsAppDescriptor::App app() const; + void setScreenIdPreset(int screenId) { m_screenIdPreset = screenId; } + void setScreenContainerIdPreset(int screenPart) { m_screenContainerIdPreset = screenPart; } + bool isForeverInvisible() const { return m_foreverInvisible; } + + LtsNotification::LtsNotification *createNotification(const QString &identifier); + + + + void setExerciseData(const LtsWidgets::ExerciseData &exerciseData); + const LtsWidgets::ExerciseData *exerciseData() const; + void setProfilePicture(const QPixmap &pixmap); + void setBaseLayoutVisible() { m_hasBaseLayout = true; } + bool isVisible() const { return (m_baseLayout != nullptr); } + const QString &name() const { return m_name; } + void setName(const QString &name) { m_name = name; } + + + bool isChildOfSession(QWidget* child); + LtsWidgets::LtsBaseLayoutWidget* baseLayout() { return m_baseLayout; } + QWidget *baseLayoutWidget(); + void setMarkForDeletion() { m_markedForDeletion = true; } + void doneInitializingSession(); + bool notifyUnsavedChanges(); + const QVector &bgWidgets() const { return m_bgWidgets; } + + + bool widgetExists(const QString &menuGroup, const QString &identifier, LtsWidgets::LayoutArea area) const; + + + bool addWidget(LtsWidgets::LtsDialog *widget, + const QString &menuGroup, + const QString &identifier, + LtsWidgets::LayoutArea area = LtsWidgets::LayoutArea::eUnknown, + bool setClosable = true); + + LtsWidgets::LtsDialog *getWidget(const QString &menuGroup, const QString &identifier, LtsWidgets::LayoutArea area); + bool focusTab(const QString &menuGroup, const QString &identifier, LtsWidgets::LayoutArea area); + + + + LtsWidgets::LtsTabWidget *locateWidget(QWidget *widgetToLocate, LtsWidgets::LayoutArea area); + + + + + void enableBaseLayoutMenu(bool on = true); + + + + void enableBaseLayoutArea(LtsWidgets::LayoutArea area, bool on = true); + + + + static int workstationIp(); + + + static QString workstationName(); + + + + QVector &missingRightActions() { return m_missingRightActions; } + + void setSavegame(const QJsonObject &sg) { m_savegame = sg; } + const QJsonObject &getSavegame() const { return m_savegame; } + + void setHidden() { m_hidden = true; } + + const QString workspaceId() const; + void setWorkspaceId(const QString &newWorkspaceId); + + LtsWidgets::ExcState exerciseState() const; + + void setLoadedFromSavegame() { m_loadedFromSavegame = true; } + bool isLoadedFromSavegame() const { return m_loadedFromSavegame; } + void addDeveloperActions(); + void refetchActions(); + void setDependency(LtsSession *session); + bool hasDependency(LtsSession *session) { return (m_dependency.data() == session); } + LtsSession *dependency() const { return m_dependency; } + + + void setNeedsToBeSuccessfullyPlaced(bool needed = true) { m_needsToBeSuccessfullyPlaced = needed; } + + int id() const { return m_id; } + +public slots: + void slotExecuteNextPayload(); + void slotPluginInitDone(); + void slotShowOnlineHelp(const QString &fileName); + + +private slots: + void slotShowOperatorSettings(); + void slotSwap(); + void slotStapleTopicLockHandler(LtsWidgets::LtsDialog *widget); + void slotStopWaitDialog(); + void slotStartPluginInit(); + void slotMonitorSelected(); + void delayScriptEditorStart(); + +signals: + void signalInitializationAborted(); + void _signalDoneInitializingSession(); + void signalDoneInitializingSession(); + void signalClose(); + void signalLogout(); + void signalQuit(); + void signalStillAlive(); + void signalExerciseDataChanged(); + void signalScreenIdChanged(int screenId, int screenContainerId, int screenTypeId); + void sessionAboutToBeDestroyed(LtsSession *session); + void workspaceIdChanged(const QString &newWorkspaceId); + void signalTakeOverBdv(); + void hideAllOtherBLWs(QObject *caller); + void taskSwitch(QObject *caller); + +private: + friend class DeveloperTools::DevDialog; + friend class DeveloperTools::AppWidget; + friend class LtsSessionManager; + + bool event(QEvent *e) override; + void createBaseLayout(); + void initializePlugins(const QVector &dataPluginList, const QVector &guiPluginList); + void initializeDataPlugins(const QVector &dataPluginList); + void initializeGuiPlugins(const QVector &guiPluginList); + bool tryInitGuiPlugin(const QString &guiPluginKey, std::shared_ptr &guiPlugin); + void clearDataPluginInterfaces(); + void clearGuiPluginInterfaces(); + void executePayloads(); + void connectOnlineHelpButton(LtsWidgets::LtsDialog *widget); + + void aboutToCloseTab(LtsWidgets::LayoutArea area); + + QPointer m_sessionManager; + QMap > m_payloadPriorityMap; + QList > m_payloadList; + LtsWidgets::LtsBackgroundWidget *m_mainBackground; + bool m_hasBaseLayout; + LtsWidgets::LtsBaseLayoutWidget *m_baseLayout; + bool m_markedForDeletion; + bool m_payloads; + QVector m_notiCtlList; + bool m_initDone; + bool m_payloadsDone { false }; + bool m_menuVisible; + + bool m_firstActionsFetch; + LtsWidgets::ExerciseData *m_exerciseData; + QVector m_missingRightActions; + std::shared_ptr m_coreFtc; + GuiPluginBase::Interface *m_currentInitIfc; + bool m_slotPluginInitDoneMutex; + bool m_pluginInitDone; + QJsonObject m_savegame; + bool m_hidden; + int m_screenId { -1 }; + int m_screenContainerId { -1 }; + int m_screenIdPreset { -1 }; + int m_screenContainerIdPreset { -1 }; + bool m_loadedFromSavegame; + std::shared_ptr m_ohvIfc; + bool m_foreverInvisible; + QStringList m_failedFetches; + QVector m_bgWidgets; + QString m_workspaceId; + bool m_needsToBeSuccessfullyPlaced { true }; + QString m_name; + QTimer *m_blub { nullptr }; + QTimer *m_scriptEditorTimer { nullptr }; + int m_blub2; + QPointer m_dependency; + bool m_presetBaseLayoutAsSecondaryWidget { false }; + int m_id; +}; + +} diff --git a/Lts/Common/LtsSession/lts_session_data.cpp b/Lts/Common/LtsSession/lts_session_data.cpp new file mode 100755 index 000000000..1c8621467 --- /dev/null +++ b/Lts/Common/LtsSession/lts_session_data.cpp @@ -0,0 +1,107 @@ +#include "lts_session_data.h" + + +namespace LtsSessionData +{ + +struct LtsSessionDataHandler +{ + LtsSessionData ltsSessionData; +}; + +Q_GLOBAL_STATIC(LtsSessionDataHandler, s_ltsSessionData) + + +LtsSessionData::LtsSessionData() + : m_domainIdPreset(-1) + , m_partitionIdPreset(-1) + , m_partitionTypePreset(IDL::PartitionType::Any) + , m_operatorId(-1) + , m_scriptId(0) + , m_showTerminalWindow(false) + , m_requestedAppId(LtsAppDescriptor::App::Login) + , m_workstationId(-1) + , m_isTablet(false) + , m_placeScriptScreens(false) +{ + + setAsTablet(false); + + clear(); +} + + +LtsSessionData *LtsSessionData::getInstance() +{ + return &s_ltsSessionData->ltsSessionData; +} + + +void LtsSessionData::loggedOut() +{ + m_operatorId = -1; + m_operatorRights.clear(); + m_operatorName.clear(); + m_showTerminalWindow = false; + m_profilePicture = QPixmap(); + m_visibleParties.clear(); +} + + +void LtsSessionData::removePresets() +{ + + m_partitionIdPreset = -1; + m_partitionTypePreset = IDL::PartitionType::Any; + + m_operatorNamePreset.clear(); + m_passwordPreset.clear(); +} + + +void LtsSessionData::clear() +{ + removePresets(); + loggedOut(); +} + + +bool LtsSessionData::hasOperatorRight(int operatorRight) const +{ + if (operatorRight == 0) + return true; + if (m_operatorRights.contains(operatorRight)) + return true; + return m_operatorRights.contains(-1); +} + + +bool LtsSessionData::isVisibleParty(int exerciseId, int partyId) const +{ + for (const auto &visibleParties : m_visibleParties) + { + if (visibleParties.exerciseId != exerciseId) + continue; + return visibleParties.visibleParties.contains(partyId); + } + return false; +} + +const QVector LtsSessionData::getVisibleParties(int exerciseId) const +{ + for(auto visibleParties : m_visibleParties) + { + if (visibleParties.exerciseId == exerciseId) + return visibleParties.visibleParties; + } + return {}; +} + + +void LtsSessionData::setAsTablet(bool isTablet) +{ + m_isTablet = isTablet; + +} + +} diff --git a/Lts/Common/LtsSession/lts_session_data.h b/Lts/Common/LtsSession/lts_session_data.h new file mode 100755 index 000000000..a79f5e812 --- /dev/null +++ b/Lts/Common/LtsSession/lts_session_data.h @@ -0,0 +1,133 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +namespace LtsSessionData +{ + +struct PartiesByExercise +{ + int exerciseId = 0; + QVector visibleParties; +}; + +class LTSSESSIONDATA_EXPORT LtsSessionData +{ +public: + static LtsSessionData *getInstance(); + + + const QString &getOperatorNamePreset() const { return m_operatorNamePreset; } + const QString &getPasswordPreset() const { return m_passwordPreset; } + void setOperatorPreset(const QString &operatorName, const QString &password) { m_operatorNamePreset = operatorName; m_passwordPreset = password; } + + int getDomainIdPreset() const { return m_domainIdPreset; } + void setDomainIdPreset(int domainIdPreset) { m_domainIdPreset = domainIdPreset; } + + QStringList getQosFile() const {return m_qosFile; } + void setQosFile(const QStringList &qosFile) { m_qosFile = qosFile; } + + int getPartitionIdPreset() const { return m_partitionIdPreset; } + IDL::PartitionType getPartitionTypePreset() const { return m_partitionTypePreset; } + void setPartitionPreset(const int partitionId, IDL::PartitionType partitionType) { m_partitionIdPreset = partitionId; m_partitionTypePreset = partitionType; } + + + + void removePresets(); + + + + int getScriptId() const { return m_scriptId; } + void setScriptId(int scriptId) { m_scriptId = scriptId; } + + int getOperatorId() const { return m_operatorId; } + void setOperatorId(int operatorId) { m_operatorId = operatorId; } + + const QString &getOperatorName() const { return m_operatorName; } + void setOperatorName(const QString &operatorName) { m_operatorName = operatorName; } + + const QSet &getOperatorRights() const { return m_operatorRights; } + void setOperatorRights(const QSet &operatorRights) { m_operatorRights = operatorRights; } + bool hasOperatorRight(int operatorRight) const; + + const QPixmap &getProfilePicture() const { return m_profilePicture; } + void setProfilePicture(const QPixmap &profilePicture) { m_profilePicture = profilePicture; } + + bool isVisibleParty(int exerciseId, int partyId) const; + const QVector &getVisibleParties() const { return m_visibleParties; } + const QVector getVisibleParties(int exerciseId) const; + void setVisibleParties(const QVector &visibleParties) { m_visibleParties = visibleParties; } + + QUrl getTcpConnectionUrl() const { return m_tcpConnectionUrl; } + void setTcpConnectionUrl(const QUrl &tcpConnectionUrl) { m_tcpConnectionUrl = tcpConnectionUrl; } + + bool getShowTerminalWindow() const { return m_showTerminalWindow; } + void setShowTerminalWindow(bool showTerminalWindow) { m_showTerminalWindow = showTerminalWindow; } + + LtsAppDescriptor::App getRequestedAppId() const { return m_requestedAppId; } + void setRequestedAppId(LtsAppDescriptor::App requestedAppId) { m_requestedAppId = requestedAppId; } + + int getWorkstationId() const { return m_workstationId; } + void setWorkstationId(int workstationId) { m_workstationId = workstationId; } + + bool isTablet() const { return m_isTablet; } + void setAsTablet(bool isTablet = true); + + bool getPlaceScriptScreens() const { return m_placeScriptScreens; } + void setPlaceScriptScreens(bool placeScriptScreens) { m_placeScriptScreens = placeScriptScreens; } + + bool hasDebugFlag(const QString &debugFlag) const { return m_debugFlags.constEnd() != m_debugFlags.constFind(debugFlag); } + QVariant debugFlagValue(const QString &debugFlag) const { QVariant result; auto it = m_debugFlags.constFind(debugFlag); if (it != m_debugFlags.constEnd()) result = *it; return result; } + void setDebugFlagValue(const QString &debugFlag, const QVariant &value) { m_debugFlags.insert(debugFlag, value); } + const QMap &debugFlags() const { return m_debugFlags; } + void removeDebugFlag(const QString &debugFlag) { m_debugFlags.remove(debugFlag); } + + void clear(); + void loggedOut(); + +private: + LtsSessionData(); + ~LtsSessionData() = default; + friend struct LtsSessionDataHandler; + + + QString m_operatorNamePreset; + QString m_passwordPreset; + QStringList m_qosFile; + QUrl m_tcpConnectionUrl; + int m_domainIdPreset; + int m_partitionIdPreset; + IDL::PartitionType m_partitionTypePreset; + + int m_operatorId; + int m_scriptId; + QString m_operatorName; + bool m_showTerminalWindow; + QSet m_operatorRights; + QPixmap m_profilePicture; + QVector m_visibleParties; + QMap m_debugFlags; + LtsAppDescriptor::App m_requestedAppId; + int m_workstationId; + bool m_isTablet; + bool m_placeScriptScreens; +}; + +static inline bool hasOperatorRight(int operatorRight) +{ + auto instance = LtsSessionData::getInstance(); + return instance->hasOperatorRight(operatorRight); +} + +} diff --git a/Lts/Common/LtsSession/lts_session_data_dds.cpp b/Lts/Common/LtsSession/lts_session_data_dds.cpp new file mode 100755 index 000000000..71e29f4bf --- /dev/null +++ b/Lts/Common/LtsSession/lts_session_data_dds.cpp @@ -0,0 +1,389 @@ + + + +#include "lts_session_data_dds.h" +#include +#include +#include +#include +#include +#include +#include + +namespace LtsSession +{ + + + + + + +class DdsConnectionContainer +{ + struct DdsConnection : public DdsConnectionParams + { + std::weak_ptr ddsConnector = std::weak_ptr(); + + DdsConnection() = default; + explicit DdsConnection(const DdsConnectionParams &p) + : DdsConnectionParams(p) + {} + }; + QVector ddsConnections; +public: + + std::shared_ptr findGlobalConnection() + { + for (int i = ddsConnections.size() - 1; i >= 0; --i) + { + const DdsConnection &conn = ddsConnections.at(i); + if (conn.partitionType != IDL::PartitionType::Global) + continue; + auto ret = conn.ddsConnector.lock(); + if (ret != nullptr) + return ret; + + ddsConnections.removeAt(i); + } + Q_ASSERT(false); + return std::shared_ptr(); + } + + std::shared_ptr findConnection(const DdsConnectionParams &connParams) + { + for (int i = ddsConnections.size() - 1; i >= 0; --i) + { + const DdsConnection &conn = ddsConnections.at(i); + if (conn != connParams) + continue; + auto ret = conn.ddsConnector.lock(); + if (ret != nullptr) + return ret; + + ddsConnections.removeAt(i); + } + return std::shared_ptr(); + } + + dds::domain::DomainParticipant findMatchingDomainParticipant(const DdsConnectionParams &connParams) + { + for (int i = ddsConnections.size() - 1; i >= 0; --i) + { + const DdsConnection &conn = ddsConnections.at(i); + if (!connParams.hasSameDomainParams(conn)) + continue; + auto ret = conn.ddsConnector.lock(); + if (ret != nullptr) + return ret->getDomainParticipant(); + + ddsConnections.removeAt(i); + } + return nullptr; + } + + void insertConnection(const DdsConnectionParams &connParams, + const std::shared_ptr &ddsConnector) + { + DdsConnection val(connParams); + val.ddsConnector = ddsConnector; + + ddsConnections += val; + } +}; +Q_GLOBAL_STATIC(DdsConnectionContainer, s_ddsConnections) + + + +class DataPluginContainer +{ +public: + struct DataPluginValue + { + QString identifier; + std::weak_ptr weakIfc; + + bool operator==(const DataPluginValue &o) const + { + return identifier == o.identifier && weakIfc.lock().get() == o.weakIfc.lock().get(); + } + }; + + QMap> m_dataPlugins; +public: + QMap> findDataPlugins(const DdsConnectionParams &connParams); + void insertDataPlugins(const DdsConnectionParams &connParams, + const QMap> &dataPlugins); +}; + +uint qHash(const DataPluginContainer::DataPluginValue &val) +{ + qint64 ptr = reinterpret_cast(val.weakIfc.lock().get()); + return ::qHash(val.identifier) ^ ::qHash(ptr); +} + +QMap> DataPluginContainer::findDataPlugins(const DdsConnectionParams &connParams) +{ + QMap> ret; + DdsConnectionParams key(connParams); + key.exerciseId = 0; + + auto it = m_dataPlugins.find(key); + if (it != m_dataPlugins.end()) + { + QSet &cont = it.value(); + auto itCont = cont.begin(); + while (itCont != cont.end()) + { + auto spPlugin = itCont->weakIfc.lock(); + if (spPlugin) + { + ret[itCont->identifier] = spPlugin; + ++itCont; + } + else + { + itCont = cont.erase(itCont); + } + } + } + return ret; +} + +void DataPluginContainer::insertDataPlugins(const DdsConnectionParams &connParams, + const QMap> &dataPlugins) +{ + DdsConnectionParams key(connParams); + key.exerciseId = 0; + + QSet cont; + cont.reserve(dataPlugins.size()); + for (auto it = dataPlugins.begin(); it != dataPlugins.end(); ++it) + { + DataPluginValue val; + val.identifier = it.key(); + val.weakIfc = it.value(); + cont += val; + } + + m_dataPlugins[key].unite(cont); +} + +Q_GLOBAL_STATIC(DataPluginContainer, s_dataPlugins) + + + + + + + +LtsSessionDataDds::LtsSessionDataDds(QObject *parent) + : QObject(parent) + , m_exerciseId(-1) + , m_emittedTime(0) + , m_curMSecSinceEpoch(0) + , m_active(true) + , m_fetcher(nullptr) + , m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("LtsSession.Session")) +{} + +LtsSessionDataDds::~LtsSessionDataDds() +{ + m_dtcPartitionTime = nullptr; + m_ddsConnector = nullptr; + delete m_fetcher; +} + +bool LtsSessionDataDds::init(const DdsConnectionParams &connParams) +{ + + m_ddsConnector = s_ddsConnections->findConnection(connParams); + m_exerciseId = connParams.exerciseId; + m_qosFileList = QStringList(connParams.qos_file); + if (m_ddsConnector == nullptr) + { + + dds::domain::DomainParticipant dp = s_ddsConnections->findMatchingDomainParticipant(connParams); + if (dp != nullptr) + return init(dp, connParams); + #ifdef WIN_32 + DDS::QosProfile::ConnectionType type = DDS::QosProfile::ConnectionType::Tablet; + #else + DDS::QosProfile::ConnectionType type = DDS::QosProfile::ConnectionType::Client; + #endif + + m_ddsConnector = std::make_shared(connParams.domainId, + connParams.partitionType, + connParams.partitionId, + connParams.exerciseId, + type); + s_ddsConnections()->insertConnection(connParams, m_ddsConnector); + } + return initInternal(); +} + +bool LtsSessionDataDds::init(const dds::domain::DomainParticipant &dp, const DdsConnectionParams &connParams) +{ + Q_ASSERT(dp.domain_id() == connParams.domainId); + + m_ddsConnector = s_ddsConnections->findConnection(connParams); + m_exerciseId = connParams.exerciseId; + + + + QStringList qosList; + if (!connParams.qos_file.isEmpty()) + { + qosList.append(connParams.qos_file); + } + + if (!qosList.isEmpty()) + m_qosFileList = qosList; + else + m_qosFileList.clear(); + +#ifdef WIN_32 + DDS::QosProfile::ConnectionType type = DDS::QosProfile::ConnectionType::Tablet; +#else + DDS::QosProfile::ConnectionType type = DDS::QosProfile::ConnectionType::Client; +#endif + + if (m_ddsConnector == nullptr) + { + m_ddsConnector = std::make_shared(dp, + connParams.partitionType, + type, + connParams.partitionId + ); + + + + s_ddsConnections()->insertConnection(connParams, m_ddsConnector); + } + return initInternal(); +} + +Fetcher *LtsSessionDataDds::fetcher(const QString &yourPluginsName) +{ + if (m_fetcher == nullptr) + m_fetcher = new Fetcher(this, yourPluginsName); + + return m_fetcher; +} + +QString LtsSessionDataDds::partitionTypeString(IDL::PartitionType partitionType) +{ + switch (partitionType) + { + case IDL::PartitionType::AllExercises: + return QStringLiteral("AllExercises"); + case IDL::PartitionType::Any: + return QStringLiteral("Any"); + case IDL::PartitionType::Exercise: + return QStringLiteral("Exercise"); + case IDL::PartitionType::Global: + return QStringLiteral("Global"); + case IDL::PartitionType::Replay: + return QStringLiteral("Replay"); + } + + return QStringLiteral("Invalid (%1)").arg(int(partitionType)); +} + +QString LtsSessionDataDds::toString() const +{ + return QStringLiteral("(LtsSessionDataDds: id=%1, partitionId=%2, partitionType=%3(%4), exerciseId=%5)").arg((qulonglong)this).arg(partitionId()).arg(static_cast(partitionType())).arg(partitionTypeString(partitionType())).arg(exerciseId()); +} + +int LtsSessionDataDds::domainId() const +{ + return m_ddsConnector ? m_ddsConnector->getDomainId() : -1; +} + +int LtsSessionDataDds::exerciseId() const +{ + return m_exerciseId; +} + +int LtsSessionDataDds::partitionId() const +{ + return m_ddsConnector ? m_ddsConnector->getPartitionId() : -1; +} + +IDL::PartitionType LtsSessionDataDds::partitionType() const +{ + return m_ddsConnector ? m_ddsConnector->getPartitionType() : IDL::PartitionType::Global; +} + +QStringList LtsSessionDataDds::qosFile() const +{ + return m_ddsConnector ? m_qosFileList : QStringList(); +} + +QUrl LtsSessionDataDds::tcpConnectionUrl() const +{ + return m_ddsConnector ? m_ddsConnector->getTcpConnectionUrl() : QUrl(); +} + +DdsConnectionParams LtsSessionDataDds::connParams() const +{ + DdsConnectionParams connParams; + connParams.domainId = domainId(); + connParams.exerciseId = exerciseId(); + connParams.partitionId = partitionId(); + connParams.partitionType = partitionType(); + connParams.qos_file = qosFile(); + connParams.tcpConnectionUrl = tcpConnectionUrl(); + return connParams; +} + + +bool LtsSessionDataDds::initInternal() +{ + if (!m_ddsConnector || !m_ddsConnector->init(m_qosFileList)) + { + ERROR_LOG(m_logger) << "DDS connector could not be initialized - session aborted!"; + m_ddsConnector = nullptr; + return false; + } + + m_ddsConnector->initPublisherSubscriber(); + m_ddsConnectorGlobal = s_ddsConnections->findGlobalConnection(); + if (!m_ddsConnectorGlobal) + m_ddsConnectorGlobal = m_ddsConnector; + + m_dtcPartitionTime.reset(m_ddsConnector->createDTC()); + assert(m_dtcPartitionTime.get()); + + m_dtcPartitionTime->setSubscriberCallback([this](const Core::PartitionTime & data, DataModification) + { + m_curMSecSinceEpoch = data.timeStamp(); + emit partitionTimeChangedInternal(); + }); + connect(this, &LtsSessionDataDds::partitionTimeChangedInternal, + this, &LtsSessionDataDds::onPartitionTimeChangedInternal, Qt::QueuedConnection); + return true; +} + +void LtsSessionDataDds::onPartitionTimeChangedInternal() +{ + if (!m_active) + return; + if (m_emittedTime == m_curMSecSinceEpoch) + return; + m_emittedTime = m_curMSecSinceEpoch; + emit partitionTimeChanged(m_curMSecSinceEpoch); +} + +QMap> LtsSessionDataDds::availableDataPluginInterfaces() const +{ + return s_dataPlugins->findDataPlugins(connParams()); + +} + +void LtsSessionDataDds::addAvailablaeDataPluginInterfaces(const QMap> &dataPlugins) +{ + s_dataPlugins->insertDataPlugins(connParams(), + dataPlugins); +} + +} diff --git a/Lts/Common/LtsSession/lts_session_data_dds.h b/Lts/Common/LtsSession/lts_session_data_dds.h new file mode 100755 index 000000000..e9e3fbbb5 --- /dev/null +++ b/Lts/Common/LtsSession/lts_session_data_dds.h @@ -0,0 +1,349 @@ + + + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include + + +namespace DataPluginBase { class Interface; } +namespace DDSConnector { class DdsConnector; } +namespace GuiPluginBase { class Interface; } + +namespace LtsSession +{ +struct DdsConnectionParams +{ + int domainId = -1; + int partitionId = 0; + int exerciseId = 0; + IDL::PartitionType partitionType = IDL::PartitionType::Any; + QUrl tcpConnectionUrl; + QStringList qos_file; + + DdsConnectionParams() = default; + explicit DdsConnectionParams(const Core::ReplayPartition &rp) + : domainId(rp.domainId()) + , partitionId(rp.id()) + , exerciseId(rp.exerciseId()) + , partitionType(IDL::PartitionType::Replay) + { + setTcpConnectionUrl(rp.tcpConnectionUrl()); + } + + bool isValid() const { return domainId >= 0 && partitionType != IDL::PartitionType::Any; } + bool hasSameDomainParams(const DdsConnectionParams &o) const + { + return domainId == o.domainId && + tcpConnectionUrl == o.tcpConnectionUrl; + } + bool operator==(const DdsConnectionParams &o) const + { + return domainId == o.domainId && + partitionId == o.partitionId && + exerciseId == o.exerciseId && + qos_file == o.qos_file && + partitionType == o.partitionType && + tcpConnectionUrl == o.tcpConnectionUrl; + } + bool operator !=(const DdsConnectionParams &o) const + { + return !operator==(o); + } + bool operator <(const DdsConnectionParams &o) const + { + if (domainId != o.domainId) + return domainId < o.domainId; + if (partitionId != o.partitionId) + return partitionId < o.partitionId; + if (exerciseId != o.exerciseId) + return exerciseId < o.exerciseId; + if (qos_file != o.qos_file) + return qos_file < o.qos_file; + if (partitionType != o.partitionType) + return partitionType < o.partitionType; + return tcpConnectionUrl.toString() < o.tcpConnectionUrl.toString(); + } + + void setTcpConnectionUrl(const std::string &url) + { + tcpConnectionUrl = QUrl::fromUserInput(QString::fromStdString(url)); + } +}; + + +class Fetcher; + + +class LTSSESSIONDATADDS_EXPORT LtsSessionDataDds + : public QObject +{ + Q_OBJECT +protected: + + explicit LtsSessionDataDds(QObject *parent = nullptr); +public: + ~LtsSessionDataDds(); + + bool init(const DdsConnectionParams &connParams); + bool init(const dds::domain::DomainParticipant &dp, + const DdsConnectionParams &connParams); + + std::shared_ptr ddsConnector() { return m_ddsConnector; } + std::shared_ptr ddsConnectorGlobal() { return m_ddsConnectorGlobal; } + int domainId() const; + int exerciseId() const; + int partitionId() const; + QStringList qosFile() const; + IDL::PartitionType partitionType() const; + static QString partitionTypeString(IDL::PartitionType partitionType); + bool isExercisePartition() const { return (partitionType() == IDL::PartitionType::Exercise); } + bool isReplayPartition() const { return (partitionType() == IDL::PartitionType::Replay); } + QString toString() const; + Fetcher *fetcher(const QString &yourPluginsName); + + QUrl tcpConnectionUrl() const; + DdsConnectionParams connParams() const; + + qint64 partitionTime() const { return m_curMSecSinceEpoch; } + + + template + bool createTopicClient(std::shared_ptr &member, bool bEnableAll = false, bool bGlobal = false) + { + member = T::create(bGlobal ? m_ddsConnectorGlobal : m_ddsConnector); + if ( bEnableAll && !member->enableAll()) + { + member = nullptr; + ERROR_LOG(m_logger) << "Error initializing dtc/ftc for service " << T::identifier(); + return false; + } + return true; + } + + template + bool createTopicClient(std::shared_ptr &dtc, std::shared_ptr &ftc, bool bEnableAll = false, bool bGlobal = false) + { + dtc = T::create(bGlobal ? m_ddsConnectorGlobal : m_ddsConnector); + ftc = S::create(bGlobal ? m_ddsConnectorGlobal : m_ddsConnector); + if ( (bEnableAll && !dtc->enableAll()) || !ftc->enableAll()) + { + dtc = nullptr; + ftc = nullptr; + ERROR_LOG(m_logger) << "Error initializing dtc/ftc for service " << T::identifier(); + return false; + } + return true; + } + + template + bool getDtc(std::shared_ptr &val) const + { + for (const auto &i : m_dataPluginInterfaces) + { + auto ifc = std::dynamic_pointer_cast(i); + if (ifc) + { + val = ifc->getDtc(); + return val != nullptr; + } + } + val.reset(); + DEBUG_LOG(m_logger) << "Trying to load dtc " << typeid(T).name() << " - " << typeid(S).name() << " failed!" + << ", m_dataPluginInterfaces.size() = " << m_dataPluginInterfaces.size() << " - " << this; + return false; + } + + + template + bool getFtc(std::shared_ptr &val) const + { + for (const auto &i : m_dataPluginInterfaces) + { + auto ifc = std::dynamic_pointer_cast(i); + if (ifc) + { + val = ifc->getFtc(); + return val != nullptr; + } + } + val.reset(); + DEBUG_LOG(m_logger) << "Trying to load ftc " << typeid(T).name() << " - " << typeid(S).name() << " failed!" + << ", m_dataPluginInterfaces.size() = " << m_dataPluginInterfaces.size() << " - " << this; + return false; + } + + + + template + bool getForeignDataInterface(std::shared_ptr &val) const + { + + + for (const auto &i : m_dataPluginInterfaces) + { + auto ifc = std::dynamic_pointer_cast(i); + if (ifc) + { + val = ifc->getForeignDataInterface(); + return val != nullptr; + } + } + val.reset(); + DEBUG_LOG(m_logger) << "Trying to load foreign data ifc " << typeid(T).name() << " - " << typeid(S).name() << " failed!" + << ", m_dataPluginInterfaces.size() = " << m_dataPluginInterfaces.size() << " - " << this; + return false; + } + + + + + + + template + bool getForeignDataInterface_NEW(std::shared_ptr &val) const + { + + + for (const auto &i : m_dataPluginInterfaces) + { + val = std::dynamic_pointer_cast(i); + if (val) + return true; + } + val.reset(); + DEBUG_LOG(m_logger) << "Trying to load foreign data ifc " << typeid(S).name() << " failed!" + << ", m_guiPluginInterfaces.size() = " << m_guiPluginInterfaces.size() << " - " << this; + return false; + } + + + + + template + bool getForeignGuiInterface(std::shared_ptr &val) const + { + for (const auto &i : m_guiPluginInterfaces) + { + val = std::dynamic_pointer_cast(i); + if (val) + return true; + } + val.reset(); + DEBUG_LOG(m_logger) << "Cannot find foreign gui ifc " << typeid(S).name() << " in " << toString(); + + return false; + } + + + template + bool interfaceFromGui(std::shared_ptr &val) const + { + for (const auto &i : m_guiPluginInterfaces) + { + val = std::dynamic_pointer_cast(i); + if (val) + return true; + } + val.reset(); + DEBUG_LOG(m_logger) << "Trying to load foreign gui ifc #2 " << typeid(S).name() << " failed!" + << ", m_guiPluginInterfaces.size() = " << m_guiPluginInterfaces.size() << " - " << this; + return false; + } + +signals: + void partitionTimeChanged(qint64 newPartitionTime); + void partitionTimeChangedInternal(); + +private: + void onPartitionTimeChangedInternal(); + bool initInternal(); + +protected: + std::shared_ptr m_ddsConnector; + std::shared_ptr m_ddsConnectorGlobal; + int m_exerciseId; + std::shared_ptr m_dtcPartitionTime; + qint64 m_emittedTime; + qint64 m_curMSecSinceEpoch; + bool m_active; + void setInactive() { m_active = false; } + + + QMap> availableDataPluginInterfaces() const; + void addAvailablaeDataPluginInterfaces(const QMap> &dataPlugins); + QMap> m_dataPluginInterfaces; + QMap> m_guiPluginInterfaces; + QMap> m_initializeThesePlugins; + Fetcher *m_fetcher; + QStringList m_qosFileList; + NGL::INGLLogger &m_logger; +}; + + +class Fetcher +{ +public: + Fetcher(LtsSessionDataDds *sessionDataDds, const QString &caller) + : m_sessionDataDds(sessionDataDds) + , m_errors(false) + , m_caller(caller) + , m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("LtsSession.Fetcher")) + , m_optional(false) + { + + } + + LtsSessionDataDds *m_sessionDataDds; + bool m_errors; + QString m_caller; + QString m_whatWillNotWork; + NGL::INGLLogger &m_logger; + bool m_optional; + QString m_resultType; + QString m_getterFunctionType; + + template + void getForeignGuiInterface(std::shared_ptr &foreignGuiInterface, const QString &whatWillNotWork , bool optional) + { + if (!m_sessionDataDds->getForeignGuiInterface(foreignGuiInterface)) + { + m_whatWillNotWork = whatWillNotWork; + m_optional = optional; + m_resultType = QString::fromUtf8(typeid(ForeignGuiInterface).name()); + m_getterFunctionType = QStringLiteral("gui interface"); + print(); + } + } + + bool status() const + { + return !m_errors; + } + + void print() + { + m_errors = true; + + QString msg(QStringLiteral("%1 cannot find '%2' in %3 %4")); + + if (m_optional) + { + INFO_LOG(m_logger) << msg.arg(m_caller, m_resultType, m_sessionDataDds->toString(), QStringLiteral("' - optional but %1 will not work").arg(m_whatWillNotWork)); + } + else + { + ERROR_LOG(m_logger) << msg.arg(m_caller, m_resultType, m_sessionDataDds->toString(), QStringLiteral(" which is required")); + } + } +}; + +} diff --git a/Lts/Common/LtsSession/lts_session_lock_handler.cpp b/Lts/Common/LtsSession/lts_session_lock_handler.cpp new file mode 100755 index 000000000..7099cf1c3 --- /dev/null +++ b/Lts/Common/LtsSession/lts_session_lock_handler.cpp @@ -0,0 +1,290 @@ +#include "lts_session_lock_handler.h" + +#include +#include + +#include + +#include + + +namespace LtsSession +{ + +TopicLockHandler::TopicLockHandler() + : QObject() + , m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("LtsSession.TopicLockHandler")) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +TopicLockHandler::~TopicLockHandler() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +bool TopicLockHandler::init(const std::shared_ptr &coreFtc, + int exerciseId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + if (coreFtc == nullptr) + return false; + coreFtc->enableAll(); + m_exerciseId = exerciseId; + m_coreFtc = coreFtc; + + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; + return true; +} + + +bool TopicLockHandler::lock() +{ + INFO_LOG(m_logger) << "LOCK " << m_topicInfo.topicName(); + TRACE_LOG(m_logger) << LOGFUNCTION << objStateAsString(); + + if (m_coreFtc == nullptr) + return false; + + if (m_locked) + { + ERROR_LOG(m_logger) << LOGFUNCTION << ": called lock while still locked! " << m_lockEntityId; + Q_ASSERT(false); + return false; + } + + const auto *sessionData = LtsSessionData::LtsSessionData::getInstance(); + + Core::TryLockEntityRequest req; + req.id(m_lockEntityId); + + req.entityTypeName(m_topicInfo.topicName().toStdString()); + req.entityId(m_topicInfo.entityId()); + req.exerciseId(m_exerciseId); + + req.operatorId(sessionData->getOperatorId()); + req.workstationId(sessionData->getWorkstationId()); + + try + { + const Core::TryLockEntityReply reply(m_coreFtc->tryLockEntityRequest(req)); + + TRACE_LOG(m_logger) << LOGFUNCTION << ": Reply received - state=" << objStateAsString() + << " - reply=" << reply; + + if (reply.alreadyLocked().is_set()) + { + const Core::LockEntityError &alreadyLocked = reply.alreadyLocked().get(); + INFO_LOG(m_logger) << LOGFUNCTION << ": Topic already locked by ws " << alreadyLocked.workstationName() << " and op " << alreadyLocked.operatorName(); + + + + + const auto lockedBy(QStringLiteral("%2/%1").arg(QString::fromStdString(alreadyLocked.operatorName()), QString::fromStdString(alreadyLocked.workstationName()))); + + emit signalLockDialog(lockedBy); + } + else + { + DEBUG_LOG(m_logger) << LOGFUNCTION << ": Topic NOT already locked"; + + Q_ASSERT(reply.id() > 0); + m_locked = true; + m_lockEntityId = reply.id(); + } + } + catch (const std::runtime_error &err) + { + ERROR_LOG(m_logger) << LOGFUNCTION << ": Failed to lock topic! " << err.what(); + Q_ASSERT(false); + return false; + } + + return true; +} + +bool TopicLockHandler::unlock() +{ + INFO_LOG(m_logger) << "UNLOCK " << m_topicInfo.topicName() << " - " << m_lockEntityId; + TRACE_LOG(m_logger) << LOGFUNCTION << objStateAsString(); + + if (m_coreFtc == nullptr) + return false; + + if (!m_locked) + { + ERROR_LOG(m_logger) << LOGFUNCTION << ": Tried to unlock while unlocked.. " << objStateAsString(); + Q_ASSERT(false); + return false; + } + + Q_ASSERT(m_lockEntityId > 0); + + const Core::TryUnlockEntityRequest req(m_lockEntityId); + + + m_locked = false; + m_lockEntityId = 0; + + try + { + const auto reply(m_coreFtc->tryUnlockEntityRequest(req)); + + TRACE_LOG(m_logger) << LOGFUNCTION << ": Reply received - state=" << objStateAsString() + << " - reply=" << reply; + + DEBUG_LOG(m_logger) << LOGFUNCTION << ": Topic unlocked!"; + } + catch (const std::runtime_error &err) + { + ERROR_LOG(m_logger) << LOGFUNCTION << ": Failed to unlock topic! " << err.what(); + return false; + } + + + + emit signalUnlockDialog(); + + + + return true; +} + + +std::string TopicLockHandler::objStateAsString() const +{ + std::ostringstream o; + o << " m_topicInfoId=" << m_lockEntityId + << ", entityId=" << m_topicInfo.entityId() + << ", exId=" << m_exerciseId + << ", lckd=" << m_locked + << ", asked=" << m_askedForLock + << ", delftrAnwr=" << m_deleteAfterAnswer; + return o.str(); +} + + +bool TopicLockHandler::slotUnlock() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " slotUnlock" << objStateAsString(); + return unlock(); +} + + +void TopicLockHandler::slotTryLock(const LtsWidgets::TopicInfo &topicInfo) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START " << objStateAsString(); + + m_topicInfo = topicInfo; + + if (m_topicInfo.topicName().isEmpty() || !topicInfo.isValid()) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " no lockable topic, that's okay"; + return; + } + + lock(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END " << objStateAsString(); +} + + +void TopicLockHandler::slotFinalize() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START " << objStateAsString(); + + if (m_locked) + { + slotUnlock(); + } + + deleteLater(); + + TRACE_LOG(m_logger) << LOGFUNCTION << " END " << objStateAsString(); +} + + + + +TopicLockHanderData::TopicLockHanderData() + : QObject() +{ +} + +void TopicLockHanderData::init(const std::shared_ptr &coreFtc, int exerciseId) +{ + m_topicLockHandler = new TopicLockHandler(); + m_topicLockHandler->init(coreFtc, exerciseId); + + connect(m_topicLockHandler.data(), &TopicLockHandler::signalLockDialog, + this, &TopicLockHanderData::slotLock, Qt::UniqueConnection); + connect(m_topicLockHandler.data(), &TopicLockHandler::signalUnlockDialog, + this, &TopicLockHanderData::slotUnlock, Qt::UniqueConnection); +} + +TopicLockHanderData::~TopicLockHanderData() +{ + + if (m_topicLockHandler) + m_topicLockHandler->slotFinalize(); +} + +void TopicLockHanderData::unlock() +{ + qDebug() << "Debugname=" << m_debugName << ": " + << " slotUnlock"; + if (m_topicLockHandler) + m_topicLockHandler->slotUnlock(); +} + +bool TopicLockHanderData::isLocked() const +{ + if (!m_topicLockHandler) + { + qDebug() << "Debugname=" << m_debugName << ": " + << " isLocked -> no handler"; + return false; + } + qDebug() << "Debugname=" << m_debugName << ": " + << " isLocked = " << m_topicLockHandler->isLocked(); + return m_topicLockHandler->isLocked(); +} + +LtsWidgets::TopicInfo TopicLockHanderData::getTopicInfo() const +{ + if (m_topicLockHandler) + return m_topicLockHandler->getTopicInfo(); + return {}; +} + +void TopicLockHanderData::slotTryLock(const LtsWidgets::TopicInfo &topicInfo) +{ + qDebug() << "Debugname=" << m_debugName << ": " + << "slotTryLock " << m_topicLockHandler; + if (!m_topicLockHandler) + return; + m_topicLockHandler->slotTryLock(topicInfo); +} + + +void TopicLockHanderData::slotLock(const QString &lockedBy) +{ + + + qDebug() << "Debugname=" << m_debugName << ": " + << "slotLock"; + m_lockedBy = lockedBy; + emit lockDataChanged(); +} + +void TopicLockHanderData::slotUnlock() +{ + qDebug() << "Debugname=" << m_debugName << ": " + << "slotUnlock"; + m_lockedBy.clear(); + emit lockDataChanged(); +} + + +} diff --git a/Lts/Common/LtsSession/lts_session_lock_handler.h b/Lts/Common/LtsSession/lts_session_lock_handler.h new file mode 100755 index 000000000..947da5433 --- /dev/null +++ b/Lts/Common/LtsSession/lts_session_lock_handler.h @@ -0,0 +1,87 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include + + + + + +namespace LtsSession +{ +class LTSSESSION_EXPORT TopicLockHandler : public QObject +{ + Q_OBJECT +public: + TopicLockHandler(); + ~TopicLockHandler(); + + bool init(const std::shared_ptr &coreFtc, int exerciseId); + + bool isLocked() const { return m_locked; } + LtsWidgets::TopicInfo getTopicInfo() const { return m_topicInfo; } + +public slots: + void slotTryLock(const LtsWidgets::TopicInfo &topicInfo); + bool slotUnlock(); + void slotFinalize(); + +signals: + void signalLockDialog(const QString &lockedBy); + void signalUnlockDialog(); + +private: + bool lock(); + bool unlock(); + std::string objStateAsString() const; + + std::shared_ptr m_coreFtc; + + LtsWidgets::TopicInfo m_topicInfo; + int m_lockEntityId{0}; + + int m_exerciseId{0}; + bool m_locked{false}; + bool m_askedForLock{false}; + bool m_deleteAfterAnswer{false}; + + NGL::INGLLogger &m_logger; +}; + + +class LTSSESSION_EXPORT TopicLockHanderData : public QObject +{ + Q_OBJECT + +public: + TopicLockHanderData(); + ~TopicLockHanderData(); + void init(const std::shared_ptr &coreFtc, int exerciseId); + + void unlock(); + void slotTryLock(const LtsWidgets::TopicInfo &topicInfo); + bool isLocked() const; + LtsWidgets::TopicInfo getTopicInfo() const; + const QString &getLockedBy() const { return m_lockedBy; } + +Q_SIGNALS: + void lockDataChanged(); + +private Q_SLOTS: + void slotLock(const QString &lockedBy); + void slotUnlock(); +public: + QString m_debugName; +private: + QPointer m_topicLockHandler; + QString m_lockedBy; +}; + + +} diff --git a/Lts/Common/LtsSession/lts_session_manager.cpp b/Lts/Common/LtsSession/lts_session_manager.cpp new file mode 100755 index 000000000..6b3563617 --- /dev/null +++ b/Lts/Common/LtsSession/lts_session_manager.cpp @@ -0,0 +1,240 @@ + + + + +#include "lts_session_manager.h" +#include "lts_session.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +namespace LtsSession +{ + +LtsSessionManager::LtsSessionManager() + : QObject() + , m_appId(LtsAppDescriptor::App::Login) + , m_headless(false) + , m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("LtsSession.Manager")) +{} + + +LtsSessionManager::~LtsSessionManager() +{ + + + for (auto i = m_sessionMap.begin(); i != m_sessionMap.end(); ++i) + { + LtsSession *session = i.value(); + session->setInactive(); + session->deleteLater(); + } +} + +bool LtsSessionManager::init(const DdsConnectionParams &connParams, + const QVector &dataPluginList, + const QVector &guiPluginList, + LtsAppDescriptor::App appId, + const QString &workspaceId, + bool headless, + bool hidden) +{ + + m_dataPluginList = dataPluginList; + m_guiPluginList = guiPluginList; + m_appId = appId; + m_headless = headless; + + + + + + + + + bool bHasBaseLayout = connParams.exerciseId > 0; + + LtsSession *session = createSession(connParams, bHasBaseLayout, true, hidden); + session->setWorkspaceId(workspaceId); + return session; +} + + +QVector LtsSessionManager::sessions() +{ + return m_sessionMap.values().toVector(); + + + + + + +} + + +void LtsSessionManager::softQuit() +{ + for (auto i = m_sessionMap.begin(); i != m_sessionMap.end(); ++i) + { + LtsSession *session = *i; + session->aboutToClose(); + session->setInactive(); + session->deleteLater(); + } + + m_sessionMap.clear(); + + emit hideAllOtherBLWs(nullptr); + + QMetaObject::invokeMethod(this, &LtsSessionManager::signalQuit, Qt::QueuedConnection); +} + + +LtsSession *LtsSessionManager::createSession(const DdsConnectionParams &connParams, + bool hasBaseLayout, + bool payloads, + bool hidden) +{ + LtsSession *session = getSession(connParams.partitionType, connParams.partitionId); + + if (session != nullptr) + { + WARNING_LOG(m_logger) << "Tried to create a Session that already exists"; + return nullptr; + } + + session = new LtsSession(this); + + connect(session, &LtsSession::signalQuit, + this, &LtsSessionManager::signalQuit); + + + connect(session, &LtsSession::signalClose, + this, &LtsSessionManager::slotCloseSession, Qt::QueuedConnection); + connect(session, &LtsSession::signalLogout, + this, &LtsSessionManager::signalLogout); + connect(session, &LtsSession::signalInitializationAborted, + this, &LtsSessionManager::signalInitializationAborted); + connect(session, &LtsSession::hideAllOtherBLWs, + this, &LtsSessionManager::hideAllOtherBLWs); + connect(session, &LtsSession::taskSwitch, + this, &LtsSessionManager::taskSwitch); +#ifdef LTS_DEBUG + connect(session, &LtsSession::signalStillAlive, + this, [this]() + { + LtsSession *session = qobject_cast(sender()); + + for (auto i = m_sessionMap.constBegin(); i != m_sessionMap.constEnd(); ++i) + { + if (i.value() == session) + return; + } + + WARNING_LOG(m_logger) << "Previously deleted Session " << session->partitionId() << "/" + << session->partitionType() << " is still alive!"; + }); +#endif + + if (hasBaseLayout) + session->setBaseLayoutVisible(); + + if (payloads) + session->setPayloadsEnabled(); + + m_sessionMap[SessionKey(connParams.partitionType, connParams.partitionId)] = session; + + + LtsSession *globSession = getSession(IDL::PartitionType::Global, 0); + if (globSession && globSession->ddsConnector() && globSession->domainId() == connParams.domainId) + { + + dds::domain::DomainParticipant dp = globSession->ddsConnector()->getDomainParticipant(); + if (!session->init(dp, connParams, m_dataPluginList, m_guiPluginList)) + { + session->deleteLater(); + return nullptr; + } + } + else + { + + + + if (!session->init(connParams, m_dataPluginList, m_guiPluginList, hidden)) + { + session->deleteLater(); + return nullptr; + } + } + + + if (session->isInitializationDone()) + QMetaObject::invokeMethod(this, &LtsSessionManager::signalInitializationFinished, + Qt::QueuedConnection); + else + connect(session, &LtsSession::signalDoneInitializingSession, + this, &LtsSessionManager::signalInitializationFinished, + Qt::QueuedConnection); + + + return session; +} + + +void LtsSessionManager::slotCloseSession() +{ + LtsSession *session = qobject_cast(QObject::sender()); + + if (!session) + { + FATAL_LOG(m_logger) << "LtsSessionManager: slot called without object - so it cannot be deleted from the session list!"; + return; + } + + bool found = false; + for (auto i = m_sessionMap.begin(); i != m_sessionMap.end(); ++i) + { + if (i.value() == session) + { + const auto key = i.key(); + session->aboutToClose(); + session->setInactive(); + session->deleteLater(); + m_sessionMap.remove(key); + + found = true; + break; + } + } + + if (!found) + FATAL_LOG(m_logger) << "LtsSessionManager: Cannot find Session in map!"; + + emit hideAllOtherBLWs(nullptr); + + if (m_sessionMap.isEmpty()) + emit signalQuit(); +} + + +LtsSession *LtsSessionManager::getSession(IDL::PartitionType partitionType, int partitionId) +{ + auto it = m_sessionMap.find(SessionKey(partitionType, partitionId)); + + if (it != m_sessionMap.end()) + return it.value(); + + return nullptr; +} + +} diff --git a/Lts/Common/LtsSession/lts_session_manager.h b/Lts/Common/LtsSession/lts_session_manager.h new file mode 100755 index 000000000..74477c3c7 --- /dev/null +++ b/Lts/Common/LtsSession/lts_session_manager.h @@ -0,0 +1,106 @@ + + + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +namespace NGL +{ +class INGLLogger; +} +namespace DeveloperTools +{ +class AppWidget; +} + + +namespace LtsSession +{ +class LtsSession; +struct DdsConnectionParams; + +class LTSSESSION_EXPORT LtsSessionManager : public QObject +{ + Q_OBJECT + +public: + LtsSessionManager(); + ~LtsSessionManager(); + + + LtsSession *createSession(const DdsConnectionParams &connParams, + bool hasBaseLayout = true, + bool payloads = true, + bool hidden = false); + + + LtsSession *getSession(IDL::PartitionType partitionType, int partitionId); + bool init(const DdsConnectionParams &connParams, + const QVector &dataPluginList, + const QVector &guiPluginList, + LtsAppDescriptor::App appId, const QString &workspaceId, + bool headless, bool hidden); + int sessionCount() const { return m_sessionMap.count(); } + LtsAppDescriptor::App appId() const { return m_appId; } + bool isHeadless() const { return m_headless; } + QVector sessions(); + void softQuit(); + +private: + friend class DeveloperTools::DevDialog; + friend class DeveloperTools::AppWidget; + + struct SessionKey + { + SessionKey(IDL::PartitionType pt = IDL::PartitionType::Any, int pId = 0) + : partitionType(pt) + , partitionId(pId) + {} + IDL::PartitionType partitionType; + int partitionId; + + bool operator<(const SessionKey &o) const + { + if (partitionType != o.partitionType) + return partitionType < o.partitionType; + return partitionId < o.partitionId; + } + }; + + void logout(); + void addPayloads(LtsSession *session); + + QMap m_sessionMap; + QVector m_dataPluginList; + QVector m_guiPluginList; + LtsAppDescriptor::App m_appId; + bool m_headless; + NGL::INGLLogger &m_logger; + +private slots: + void slotCloseSession(); + +signals: + + + void signalStopBlock(); + void signalLogout(); + void signalQuit(); + void signalInitializationFinished(); + void signalInitializationAborted(); + void hideAllOtherBLWs(QObject *caller); + void taskSwitch(QObject *caller); +}; + +} diff --git a/Lts/Common/LtsTimeline/CMakeLists.txt b/Lts/Common/LtsTimeline/CMakeLists.txt new file mode 100755 index 000000000..2c6d092b7 --- /dev/null +++ b/Lts/Common/LtsTimeline/CMakeLists.txt @@ -0,0 +1,74 @@ +# +# CMakeLists.txt for LtsTimeline, the base for EventTimeline and ScriptTimeline aka. Script Editor +# +# $Id: CMakeLists.txt 39615 2018-03-16 13:36:41Z wille_m $ +# $HeadURL: svn://192.168.133.21/TDS/branches/product/features/hotline/Lts/Common/LtsEventTimeline/CMakeLists.txt $ +# $Author: wille_m $ +# +# Created by Mike Wille, SLTE, 2021-05-18 +# + +SET(src + timeline_model.h + timeline_model.cpp + + item.h + item.cpp + item_delegate.h + item_delegate.cpp + + timeline_header_view.cpp + timeline_header_view.h + timeline_header_view_mouse_handler.cpp + timeline_header_view_mouse_handler.h + timeline_utils.cpp + timeline_utils.h + timeline_view.h + timeline_view.cpp + timeline_view_drag_handler.cpp + timeline_view_drag_handler.h + timeline_view_resize_handler.cpp + timeline_view_resize_handler.h + timeline_view_selection_handler.cpp + timeline_view_selection_handler.h + timeline_view_track_handler.cpp + timeline_view_track_handler.h + timeline_widget.h + timeline_widget.cpp + + track.h + track.cpp +) + +SET(uisrc + timeline_widget.ui +) + +NGL_WRAP_UI(src ${uisrc}) +NGL_ADD_LIBRARY2(WS LtsTimeline SHARED ${src}) +NGL_TARGET_INCLUDE_DIRECTORIES(LtsTimeline + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) +NGL_TARGET_LINK_LIBRARIES(LtsTimeline + PRIVATE + NGLLogger + ngltools # DTG + LtsWidgets # LtsMessageDialogNew for remove filter track + Qt5::Core + Qt5::Gui + Qt5::Widgets +) +NGL_INSTALL_TARGET(LtsTimeline) + + +# LtsTimelineTest +NGL_ADD_EXECUTABLE(WS LtsTimelineTest main.cpp) +NGL_TARGET_LINK_LIBRARIES(LtsTimelineTest + LtsTimeline + nglapplication + NGLLogger + Qt5::Core + Qt5::Gui + Qt5::Widgets +) diff --git a/Lts/Common/LtsTimeline/item.cpp b/Lts/Common/LtsTimeline/item.cpp new file mode 100755 index 000000000..2eec74152 --- /dev/null +++ b/Lts/Common/LtsTimeline/item.cpp @@ -0,0 +1,45 @@ + + + +#include "item.h" + +#include +#include +#include +#include +#include + +#include + + +namespace LtsTimeline +{ + +QString Item::mimetype = QStringLiteral("application/rheinmetall.timeline.item"); + +QColor Item::s_highlightColor = QRgb(0x43b0e0); + + +Item::Item(qint64 startTime, int duration, const QColor &color, bool isEditable) + : m_color(color) + , m_startTime(startTime) + , m_duration(duration) + , m_isEditable(isEditable) +{} + + + + +void Item::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &, bool highlight) const +{ + painter->save(); + if (highlight) + painter->setPen(QPen(QBrush(s_highlightColor), 5)); + else + painter->setPen(QPen(QBrush(Qt::black), 1)); + painter->setBrush(QBrush(m_color)); + painter->drawRect(option.rect); + painter->restore(); +} + +} diff --git a/Lts/Common/LtsTimeline/item.h b/Lts/Common/LtsTimeline/item.h new file mode 100755 index 000000000..955c56e20 --- /dev/null +++ b/Lts/Common/LtsTimeline/item.h @@ -0,0 +1,87 @@ + + + +#pragma once + +#include + +#include + +#include +#include + + +class QPainter; +class QStyleOptionViewItem; +class QModelIndex; + +namespace PresentationManagement +{ +class ScriptModel; +} + + +namespace LtsTimeline +{ + +class ItemDelegate; + + +class LTSTIMELINE_EXPORT Item +{ + friend class ItemDelegate; + +public: + static QString mimetype; + static QColor s_highlightColor; + + Item() = default; + Item(qint64 startTime, int duration, const QColor &color, bool isEditable = true); + Item(const Item &) = delete; + Item &operator=(const Item &) = delete; + Item(Item &&) noexcept = default; + virtual ~Item() = default; + + int id() const { return m_id; } + int parentTrackId() const { return m_parentTrackId; } + + QColor color() const { return m_color; } + void setColor(const QColor &color) { m_color = color; } + + qint64 startTime() const { return m_startTime; } + [[deprecated("use model's setItemStartTime instead, needed to notify the model that it has to re-sort items")]] + void setStartTime(qint64 startTime) { m_startTime = startTime; } + + int duration() const { return m_duration; } + void setDuration(int duration) { m_duration = duration; } + + bool isEditable() const { return m_isEditable; } + void setEditable(bool editable) { m_isEditable = editable; } + + virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, bool highlight = false) const; + + void _setStartTime(qint64 startTime) { m_startTime = startTime; } + void _setDuration(int duration) { m_duration = duration; } + +protected: + QColor m_color; + qint64 m_startTime { -1 }; + int m_duration { -1 }; + bool m_isEditable { false }; + +private: + friend class TimelineModel; + friend class PresentationManagement::ScriptModel; + + int m_id { -1 }; + int m_parentTrackId { -1 }; +}; + +typedef std::unique_ptr ItemPtr; +typedef std::vector Items; +typedef std::reference_wrapper ItemPtrRef; +typedef std::reference_wrapper ItemPtrNCVRef; +typedef std::vector ItemPtrNCVRefs; +typedef std::vector ItemPtrRefs; + +} diff --git a/Lts/Common/LtsTimeline/item_delegate.cpp b/Lts/Common/LtsTimeline/item_delegate.cpp new file mode 100755 index 000000000..51aeb4b31 --- /dev/null +++ b/Lts/Common/LtsTimeline/item_delegate.cpp @@ -0,0 +1,185 @@ + + + +#include "item_delegate.h" +#include "item.h" +#include "track.h" +#include "timeline_view.h" + +#include +#include +#include +#include +#include + + +namespace LtsTimeline +{ + +ItemDelegate::ItemDelegate(TimelineView *parent) + : QStyledItemDelegate(parent) + , m_view(parent) + , m_pushButton(new QPushButton) + , m_pushButtonPressed(new QPushButton) +{ + m_pushButtonPressed->setDown(true); +} + +ItemDelegate::~ItemDelegate() +{ + m_pushButton->deleteLater(); + m_pushButton = nullptr; + m_pushButtonPressed->deleteLater(); + m_pushButtonPressed = nullptr; +} + + + + +QSize ItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + Q_UNUSED(option); + Q_UNUSED(index); + return QSize(100, 50); +} + +void ItemDelegate::paintDragItems(QPainter *painter) const +{ + painter->save(); + + const QRect rightSectionRect = QRect(QPoint(m_view->m_header->sectionPosition(Section::eItems), 0), + QPoint(m_view->geometry().right(), m_view->geometry().bottom())); + painter->setClipRect(rightSectionRect); + + int dx = m_view->m_dragHandler->deltaX(); + int dy = m_view->m_dragHandler->deltaY(); + try + { + for (const int &itemId : m_view->m_dragHandler->m_dragItemIds) + { + Item &item = m_view->timelineModel()->item(itemId); + if (item.startTime() + item.duration() < m_view->currentTime()) + continue; + + if (item.startTime() > m_view->endTime()) + break; + + + + const QModelIndex rowIndex(m_view->timelineModel()->indexOfTrack(item.parentTrackId())); + const QModelIndex columnIndex(m_view->timelineModel()->index(rowIndex.row(), 1, rowIndex.parent())); + const QRect visualRect(m_view->visualRect(columnIndex)); + int left = m_view->toPixel(item.startTime()) + dx; + int right = m_view->toPixel(item.startTime() + item.duration()) + dx; + int top = visualRect.top() + dy; + int bottom = visualRect.bottom() + dy; + QStyleOptionViewItem option; + option.rect = QRect(left, top, right - left - 1, bottom - top); + item.paint(painter, option, columnIndex, m_view->selectedItemIds().contains(itemId)); + } + } + catch (...) {} + + painter->restore(); +} + + +void ItemDelegate::paintTrackButtons(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + try + { + + const Track &track = m_view->timelineModel()->track(index.data(Qt::UserRole).toInt()); + + int buttonLength = option.rect.height() / 2; + if (track.hasInsertButton()) + { + QPushButton *button = m_view->m_trackHandler->isInsertButtonPressed(index) ? m_pushButtonPressed : m_pushButton; + QRect rect = QRect(0, 0, buttonLength, buttonLength); + button->setGeometry(rect); + button->setText(QStringLiteral("+")); + button->setEnabled(m_view->timelineModel()->isEditable()); + QPixmap map = button->grab(); + painter->drawPixmap(option.rect.right() - buttonLength, option.rect.top(), map); + } + if (track.hasRemoveButton()) + { + QPushButton *button = m_view->m_trackHandler->isRemoveButtonPressed(index) ? m_pushButtonPressed : m_pushButton; + QRect rect = QRect(0, 0, buttonLength, buttonLength); + button->setGeometry(rect); + button->setText(QStringLiteral("-")); + button->setEnabled(m_view->timelineModel()->isEditable()); + QPixmap map = button->grab(); + painter->drawPixmap(option.rect.right() - buttonLength, option.rect.center().y(), map); + } + } + catch (...) {} +} + + + + +void ItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + if (index.column() == Section::eActuator) + { + painter->save(); + QStyledItemDelegate::paint(painter, option, index); + paintTrackButtons(painter, option, index); + painter->restore(); + return; + } + + auto drawItem = [this, &option, &painter, &index](const Item &item) + { + + + + + + + if (m_view->timelineModel()->isEditable()) + if (m_view->m_dragHandler->m_dragItemIds.contains(item.id())) + return; + + QStyleOptionViewItem opt = option; + opt.rect.setLeft(std::max(m_view->toPixel(item.startTime()), option.rect.left())); + opt.rect.setRight(std::min(m_view->toPixel(item.startTime() + item.duration()), option.rect.right()) - 1); + item.paint(painter, opt, index, m_view->selectedItemIds().contains(item.id())); + }; + + try + { + const Track &track = m_view->timelineModel()->track(index.data().toInt()); + painter->save(); + + + painter->setClipping(true); + painter->setClipRect(option.rect); + + const Items &items = track.items(); + for (const ItemPtr &item : items) + drawItem(static_cast(*item.get())); + const QSet copyItemIds = track.copyItemIds(); + for (const int &itemId : copyItemIds) + { + try + { + const Item &item = m_view->timelineModel()->item(itemId); + drawItem(item); + } + catch(...) + { + fprintf(stderr, "ItemDelegate::paint: cannot find item copy of %d\n", itemId); + } + } + + painter->restore(); + } + catch (...) + { + ERROR_LOG(m_view->m_logger) << "ItemDelegate::paint -> CATCH"; + } +} + +} diff --git a/Lts/Common/LtsTimeline/item_delegate.h b/Lts/Common/LtsTimeline/item_delegate.h new file mode 100755 index 000000000..b7aa26af8 --- /dev/null +++ b/Lts/Common/LtsTimeline/item_delegate.h @@ -0,0 +1,47 @@ + + + +#pragma once + +#include + +#include +#include + + +namespace LtsTimeline +{ + +class TimelineView; +class TimelineModel; + + +class LTSTIMELINE_EXPORT ItemDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + ItemDelegate(TimelineView *parent = nullptr); + ~ItemDelegate(); + + + + QSize sizeHint(const QStyleOptionViewItem &option = {}, const QModelIndex &index = {}) const override; + + + + virtual void paintDragItems(QPainter *painter) const; + virtual void paintTrackButtons(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + +protected: + virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + +protected: + TimelineView *m_view { nullptr }; + + QPushButton *m_pushButton { nullptr }; + QPushButton *m_pushButtonPressed { nullptr }; + +}; + +} diff --git a/Lts/Common/LtsTimeline/log4cxx_timeline.props b/Lts/Common/LtsTimeline/log4cxx_timeline.props new file mode 100755 index 000000000..499ebc0d3 --- /dev/null +++ b/Lts/Common/LtsTimeline/log4cxx_timeline.props @@ -0,0 +1,66 @@ +log4j.reset=true +# exclusivly for timelinetest build + + +log4j.rootLogger=ERROR, file +#log4j.rootLogger=WARN, stdout + +#log4j.logger.Timeline.EventInfoWindow=DEBUG +#log4j.logger.Timeline.EventTools=DEBUG +#log4j.logger.Timeline=WARN + + +log4j.appender.file=org.apache.log4j.RollingFileAppender +log4j.appender.file.MaxFileSize=1MB +log4j.appender.file.MaxBackupIndex=9 +log4j.appender.file.file=${LOG4QT_LOGDIR}/${LOG4QT_USER}_timeline.log +log4j.appender.file.layout=org.apache.log4j.PatternLayout +log4j.appender.file.layout.ConversionPattern=%d %-5p %c |#| %m (%.50F:%L) %n + +# Appnd_Console +########################################################### +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern= %d %p %c %c{1} - %m (%-.30F:%L) %n + +########################################################### +########################################################### +########################################################### +# 1) Available Levels in LOG4CXX Version 0.10.0: +# TRACE, +# DEBUG, +# INFO, +# WARN, +# ERROR +# FATAL +# +# d = date/time +# p = priority (Level) +# c = category/channel +# m = message +# F = file +# L = line +# n = platform dependent line separator character or characters. +# .... +# +# Examples for console: +# a) +# %d %p %c #%m# (%.50F:%L)%n +# date/time +# priority +# category/channel +# #message# +# (Maximal 50Character des Filename von rechts:Line) +# line separator +# +# b) +# %d (%.15F:%L) %-5p %c |#| %m%n +# date/time +# (Maximal 15Character des Filename von rechts:Line) +# priority, Right pad with spaces if the priority name is less than 5 characters long. +# category/channel +# message +# line separator +# +# Examples for files: + diff --git a/Lts/Common/LtsTimeline/main.cpp b/Lts/Common/LtsTimeline/main.cpp new file mode 100755 index 000000000..36556030d --- /dev/null +++ b/Lts/Common/LtsTimeline/main.cpp @@ -0,0 +1,289 @@ +#include +#include +#include +#include + +#include +#include + +#include +#include + + + + + + + + + + + + + +using namespace LtsTimeline; + + + + + + + +class AbstractStuff +{ +public: + virtual void thing() = 0; +}; + + +class TheStuff : public AbstractStuff +{ + void thing() override { fprintf(stderr, "thing() !\n"); } +}; + + +std::unique_ptr get() +{ + return std::make_unique(std::move(TheStuff())); +} + + +std::vector> test; + + +void move(std::unique_ptr &&stuff) +{ + test.push_back(std::move(stuff)); +} + + + + + +void populateModel(LtsTimeline::TimelineModel *model) +{ + { + std::unique_ptr asp = get(); + move(std::move(asp)); + } + + { + AbstractStuff &as = *test.front(); + as.thing(); + } + + + + +#define l() qCritical() + qint64 rtlnau = QDateTime::currentMSecsSinceEpoch(); + + class TestItem : public Item + { + public: + TestItem(qint64 startTime, int duration, const QColor &color, bool isEditable = true) + : Item(startTime, duration, color, isEditable) + , m_1234(1234) + {} + + int get1234() const { return m_1234; } + + private: + int m_1234 { 9876 }; + }; + + + int trackId; + int itemId; + + + + + + model->addTrack(0, Track(QStringLiteral("1st Track"))); + model->addItem(1, Item(rtlnau + 120'000, 30'000, Qt::green)); + + int firstChildId = + model->addTrack(1, Track(QStringLiteral("1st Track's 1st Child"))); + model->addItem(2, Item(rtlnau + 180'000, 30'000, Qt::magenta)); + l() << "firstChildId:" << firstChildId; + + model->addTrack(0, Track(QStringLiteral("2nd Track"))); + model->addItem(3, Item(rtlnau + 120'000, 60'000, Qt::blue)); + model->addItem(3, Item(rtlnau + 60'000, 30'000, Qt::red)); + + model->addTrack(0, Track(QStringLiteral("3rd Track"))); + model->addItem(4, Item(rtlnau + 150'000, 60'000, Qt::yellow)); + model->addItem(4, TestItem(rtlnau + 90'000, 30'000, Qt::cyan)); + + + + model->print(true); + + + + try + { + const TestItem &testItem = dynamic_cast(model->item(5)); + + } + catch(const std::exception &ex) + { + l() << "dynamic_cast back to TestItem failed!" << ex.what(); + } + + + (void)itemId; + (void)trackId; + + + + qint64 now = rtlnau; + + { +#define non_strict false +#define strict true +#define recursive true +#define sorted true +#define unsorted false + QVector x; + bool y; + + + + x = model->itemIdsBetween(now + 60'000 + 1, now + 150'000 - 1, non_strict, recursive); + + + x = model->itemIdsBetween(now + 60'000 + 1, now + 150'000 - 1, strict, recursive); + + + + y = model->isItemsBetween(now + 60'000 + 1, now + 150'000 - 1, -1, non_strict, recursive); + + + y = model->isItemsBetween(now + 60'000 + 1, now + 150'000 - 1, -1, strict, recursive); + + + + y = model->isItemsBetween(now + 60'000 + 1, now + 150'000 - 1, 5, non_strict, recursive); + + + y = model->isItemsBetween(now + 60'000 + 1, now + 150'000 - 1, 5, strict, recursive); + + + + + + x = model->itemIdsBetween(now + 60'000, now + 150'000, non_strict, recursive); + + + x = model->itemIdsBetween(now + 60'000, now + 150'000, strict, recursive); + + + + y = model->isItemsBetween(now + 60'000, now + 150'000, -1, non_strict, recursive); + + + y = model->isItemsBetween(now + 60'000, now + 150'000, -1, strict, recursive); + + + + y = model->isItemsBetween(now + 60'000, now + 150'000, 5, non_strict, recursive); + + + y = model->isItemsBetween(now + 60'000, now + 150'000, 5, strict, recursive); + + + + y = model->isItemsBetween(now + 60'000, now + 150'000, QVector({ 0, 3, 5 }), strict, recursive); + + + y = model->isItemsBetween(now + 60'000, now + 150'000, QVector({ 0, 5 }), strict, recursive); + + + + + x = model->itemIdsBetween(0, now + 999'000, non_strict, recursive, sorted); + // l() << "itemIdsBetween(0, now + 999'000, non_strict, recursive, sorted)" << x << "- okay?" << (x == QVector({ 3, 5, 0, 2, 4, 1 })); +#undef non_strict +#undef strict +#undef recursive +#undef unsorted +#undef sorted + } + + + try + { + // item 1 von track 2 auf track 3, zu startTime 210'000 + model->moveItemToTrack(1, 3, now + 210'000); + } + catch(...) + { + fprintf(stderr, "That didn't work. %s:%d\n", __FUNCTION__, __LINE__); + } + + + model->print(true); + + + try + { + // item 3 von track 3 auf track 2, zu startTime 150'000 + model->moveItemToTrack(3, 2, now + 150'000); + } + catch(...) + { + fprintf(stderr, "That didn't work. %s:%d\n", __FUNCTION__, __LINE__); + } + + + /* now + /30 /60 /90 /120 /150 /180 /210 /240 + | ' ' ' | ' ' ' | ' ' ' | ' ' ' | ' ' ' | ' ' ' | ' ' ' | + T1 000000000 + >T2 333333333 + T3 2222222222222222111111111 + T4 55555555 44444444444444444 + */ + + + model->print(true); + + +#undef l +} + + +int main(int argc, char *argv[]) +{ +// qputenv("TDS_DEVEL", "yes, of course"); + + qWarning() << "LtsTimeline - Test"; + NGLApplication app(argc, argv); + app.createLogger("Timeline", "log4cxx_timeline.props"); + + STARTUP_LOG(app.baseLogger()); + + int ret = 0; + + LtsTimeline::TimelineWidget *timeline = new LtsTimeline::TimelineWidget(); + LtsTimeline::TimelineModel *model = new LtsTimeline::TimelineModel(timeline->view()); + model->setSnapEnabled(true); + populateModel(model); + timeline->setModel(model); + timeline->showDebugWidget(); + timeline->setWindowState(Qt::WindowMaximized); + timeline->show(); + +// timeline->setMaximumZoomFactor(LtsTimeline::ZoomFactor::ThirtyMinutes); + timeline->setZoomFactor(LtsTimeline::ZoomFactor::SixtyMinutes); + timeline->setStartTime(QDateTime::currentMSecsSinceEpoch()); + timeline->setDuration(12 * 60 * 60 * 1000); + + ret = app.exec(); + + SHUTDOWN_LOG(app.baseLogger()); + + return ret; +} diff --git a/Lts/Common/LtsTimeline/timeline_header_view.cpp b/Lts/Common/LtsTimeline/timeline_header_view.cpp new file mode 100755 index 000000000..6d9d4688f --- /dev/null +++ b/Lts/Common/LtsTimeline/timeline_header_view.cpp @@ -0,0 +1,679 @@ +#include "timeline_header_view.h" +#include "timeline_view.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace LtsTimeline +{ + +QPixmap getMinLimitIndicator() +{ + QPixmap pixmap(11, 11); + pixmap.fill(Qt::transparent); + QPolygon triangle; + triangle << QPoint(4, 5); + triangle << QPoint(10, 0); + triangle << QPoint(10, 10); + triangle << QPoint(4, 5); + QPainter painter(&pixmap); + painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); + painter.setPen(QGuiApplication::palette().color(QPalette::Text)); + painter.setBrush(QBrush(QGuiApplication::palette().color(QPalette::Text))); + painter.drawLine(1, 0, 1, 10); + painter.drawConvexPolygon(triangle); + return pixmap; +} + +QPixmap getMaxLimitIndicator() +{ + QPixmap pixmap(11, 11); + pixmap.fill(Qt::transparent); + QPolygon triangle; + triangle << QPoint(6, 5); + triangle << QPoint(0, 0); + triangle << QPoint(0, 10); + triangle << QPoint(6, 5); + QPainter painter(&pixmap); + painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); + painter.setPen(QGuiApplication::palette().color(QPalette::Text)); + painter.setBrush(QBrush(QGuiApplication::palette().color(QPalette::Text))); + painter.drawLine(10, 0, 10, 10); + painter.drawConvexPolygon(triangle); + return pixmap; +} + + +const QColor TimelineHeaderView::backgroundColor() +{ + return QGuiApplication::palette().color(QPalette::Base); +} + +const QColor TimelineHeaderView::highlightColor() +{ + return QRgb(0x43b0e0); +} + + +const int TimelineHeaderView::s_limitHeight = 22; + + +TimelineHeaderView::TimelineHeaderView(QWidget *parent) + : QHeaderView(Qt::Horizontal, parent) + , m_mouseHandler(new TimelineHeaderViewMouseHandler(this)) + , m_liveTimer(new QTimer(this)) + , m_minLimitIndicator(getMinLimitIndicator()) + , m_maxLimitIndicator(getMaxLimitIndicator()) +{ + const QColor color = QGuiApplication::palette().color(QPalette::Inactive, QPalette::Highlight); + setStyleSheet(QStringLiteral("QTreeView { background-color: %1 }").arg(color.name(QColor::HexRgb))); + try + { + m_timelineView = qobject_cast(parent); + updateSection(Section::eActuator); + updateSection(Section::eItems); + if (m_timelineView) + { + connect(m_timelineView, &TimelineView::currentTimeChanged, this, &TimelineHeaderView::onUpdateTicks); + connect(m_timelineView, &TimelineView::rangeChanged, this, &TimelineHeaderView::onUpdateTicks); + connect(m_timelineView, &TimelineView::zoomChanged, this, &TimelineHeaderView::onUpdateTicks); + } + else + qWarning() << "WARNING: main functionalities are inoperable without a TimelineView as parent."; + } + catch (...) + { + qWarning() << "WARNING: main functionalities are inoperable without a TimelineView as parent."; + } + setStretchLastSection(true); + setMinimumSectionSize(200); + setFixedHeight(50); + connect(this, &TimelineHeaderView::sectionResized, this, &TimelineHeaderView::onUpdateTicks); + connect(m_liveTimer, &QTimer::timeout, this, &TimelineHeaderView::onLiveTimeout); + + m_liveIndicatorColor = QGuiApplication::palette().color(QPalette::Link); +} + +TimelineHeaderView::~TimelineHeaderView() +{ + m_timelineView = nullptr; +} + + + + +void TimelineHeaderView::setParent(QWidget *) +{ + qWarning() << QStringLiteral("WARNING: setting parent after construction is not allowed."); +} + +void TimelineHeaderView::setTrackLabel(const QString &trackLabel) +{ + m_trackLabel = trackLabel; + updateSection(Section::eActuator); +} + + +void TimelineHeaderView::setDateTimeFormat(DateTimeFormat dateTimeFormat) +{ + if (!m_isDateTimeFormatChangable) + return; + + m_dateTimeFormat = dateTimeFormat; + for (Tick &majorTick : m_majorTicks) + majorTick.label = DateTime::toString(majorTick.time, m_dateTimeFormat); + for (Tick &mediumTick : m_mediumTicks) + mediumTick.label = DateTime::toString(mediumTick.time, m_dateTimeFormat); + for (Tick &minorTick : m_minorTicks) + minorTick.label = DateTime::toString(minorTick.time, m_dateTimeFormat); + updateSection(Section::eItems); +} + + +void TimelineHeaderView::setIndicatorScalingFactor(double factor) +{ + if (factor == m_indicatorScalingFactor || factor <= 0.) + return; + + m_indicatorScalingFactor = factor; + viewport()->update(); +} + +void TimelineHeaderView::setIndicatorTime(qint64 time) +{ + if (time != -1 && m_timelineView) + if (m_timelineView->timelineModel()) + if (m_timelineView->timelineModel()->isSnapEnabled()) + time = m_timelineView->timelineModel()->snapTime(time); + + + + + if (time == m_indicatorTime) + return; + + m_indicatorTime = time; + viewport()->update(); + emit indicatorTimeChanged(m_indicatorTime); +} + +void TimelineHeaderView::setIndicatorPosition(int position) +{ + setIndicatorTime(toTime(position)); +} + +void TimelineHeaderView::setIndicatorVisible(bool isIndicatorVisible) +{ + if (isIndicatorVisible == m_isIndicatorVisible) + return; + + m_isIndicatorVisible = isIndicatorVisible; + m_timelineView->viewport()->update(); + emit indicatorVisibilityChanged(m_isIndicatorVisible); +} + + +void TimelineHeaderView::setLiveIndicatorScalingFactor(double factor) +{ + if (factor == m_liveIndicatorScalingFactor || factor <= 0.) + return; + + m_liveIndicatorScalingFactor = factor; + viewport()->update(); +} + +void TimelineHeaderView::setLiveIndicatorColor(const QColor &color) +{ + if (color == m_liveIndicatorColor) + return; + + m_liveIndicatorColor = color; + viewport()->update(); +} + +void TimelineHeaderView::setLiveIndicatorTime(qint64 time) +{ + if (time != -1 && m_timelineView) + if (m_timelineView->timelineModel()) + if (m_timelineView->timelineModel()->isSnapEnabled()) + time = m_timelineView->timelineModel()->snapTime(time); + + if (time == m_liveIndicatorTime) + return; + + m_liveIndicatorTime = time; + viewport()->update(); + emit liveIndicatorTimeChanged(m_liveIndicatorTime); +} + +void TimelineHeaderView::setLiveIndicatorPosition(int position) +{ + setLiveIndicatorTime(toTime(position)); +} + +void TimelineHeaderView::setLiveIndicatorVisible(bool isLiveIndicatorVisible) +{ + if (isLiveIndicatorVisible == m_isLiveIndicatorVisible) + return; + + m_isLiveIndicatorVisible = isLiveIndicatorVisible; + viewport()->update(); + m_timelineView->viewport()->update(); + emit liveIndicatorVisibilityChanged(m_isLiveIndicatorVisible); +} + +void TimelineHeaderView::startLiveTimer(qint64 startTime, int interval) +{ + if (interval <= 0) + { + qCritical() << LOGFUNCTION << "invalid time interval:" << interval; + return; + } + + if (m_liveTimer->isActive()) + { + qWarning() << LOGFUNCTION << "timer is already active - restarting"; + m_liveTimer->stop(); + } + + if (startTime < 0) + startTime = m_timelineView->currentTime(); + + m_liveTimerInterval = interval; + setLiveIndicatorTime(startTime); + setLiveIndicatorVisible(true); + m_liveTimer->setSingleShot(false); + m_liveTimer->start(m_liveTimerInterval); +} + +void TimelineHeaderView::stopLiveTimer() +{ + if (!m_liveTimer->isActive()) + return; + + qDebug() << LOGFUNCTION; + m_liveTimer->stop(); + m_liveIndicatorTime = -1; + setLiveIndicatorVisible(false); +} + + +void TimelineHeaderView::setHoverTime(qint64 time) +{ + if (!m_isHoverVisible) + return; + + if (time < m_timelineView->currentTime() || m_timelineView->currentTime() + m_timelineView->zoomFactor() < time) + time = -1; + + m_hoverTime = time; + m_hoverPosition = toPixel(m_hoverTime); + updateSection(Section::eItems); + emit hoverPositionChanged(m_hoverPosition); +} + +void TimelineHeaderView::setHoverPosition(int position) +{ + if (!m_isHoverVisible) + return; + + m_hoverPosition = position; + m_hoverTime = toTime(m_hoverPosition); + updateSection(Section::eItems); + emit hoverPositionChanged(m_hoverPosition); +} + +void TimelineHeaderView::setHoverVisible(bool visible) +{ + if (visible == m_isHoverVisible) + return; + + m_isHoverVisible = visible; + updateSection(Section::eItems); + m_timelineView->viewport()->update(); + emit hoverVisibilityChanged(m_isHoverVisible); +} + +void TimelineHeaderView::setHoverTimeVisible(bool visible) +{ + if (visible == m_isHoverTimeVisible) + return; + + m_isHoverTimeVisible = visible; + updateSection(Section::eItems); + m_timelineView->viewport()->update(); +} + +void TimelineHeaderView::setHoverDateTimeFormat(DateTimeFormat format) +{ + if (format == m_hoverDateTimeFormat) + return; + + m_hoverDateTimeFormat = format; + updateSection(Section::eItems); + m_timelineView->viewport()->update(); +} + + +void TimelineHeaderView::setLimitsVisible(bool visible) +{ + if (visible == m_isLimitsVisible) + return; + + m_isLimitsVisible = visible; + if (m_isLimitsVisible) + setFixedHeight(70); + else + setFixedHeight(50); + viewport()->update(); +} + +void TimelineHeaderView::setLimitsDateTimeFormat(DateTimeFormat format) +{ + if (format == m_limitsDateTimeFormat) + return; + + m_limitsDateTimeFormat = format; + viewport()->update(); +} + + +int TimelineHeaderView::toPixel(qint64 time) const +{ + if (time == -1) + return -1; + + return static_cast(double(time - m_timelineView->currentTime()) / m_timelineView->zoomFactor() * + (geometry().width() - sectionPosition(Section::eItems)) + sectionPosition(Section::eItems)); +} + +qint64 TimelineHeaderView::toTime(int pixel) const +{ + return m_timelineView->currentTime() + + (m_timelineView->zoomFactor() * (double(pixel - sectionPosition(Section::eItems)) / + (geometry().width() - sectionPosition(Section::eItems)))); +} + + + + +void TimelineHeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const +{ + if (logicalIndex == Section::eActuator) + { + painter->save(); + painter->setRenderHints(QPainter::TextAntialiasing | QPainter::HighQualityAntialiasing); + painter->setPen(backgroundColor()); + painter->setBrush(QBrush(backgroundColor())); + painter->drawRect(rect); + painter->setPen(QPen(QGuiApplication::palette().color(QPalette::Text), 2)); + painter->drawLine(rect.topRight(), QPoint(rect.right() - rect.height() / 2, rect.top() + rect.height() / 2)); + painter->drawLine(QPoint(rect.right() - rect.height() / 2, rect.top() + rect.height() / 2), rect.bottomRight()); + painter->setFont(m_font); + painter->drawText(rect, Qt::AlignCenter, m_trackLabel); + painter->restore(); + return; + } + + painter->save(); + + painter->setClipping(true); + painter->setClipRect(rect); + painter->setFont(m_font); + painter->setRenderHints(QPainter::TextAntialiasing | QPainter::HighQualityAntialiasing); + painter->setPen(backgroundColor()); + painter->setBrush(backgroundColor()); + painter->drawRect(rect); + + int bottom = m_isLimitsVisible ? rect.bottom() - s_limitHeight : rect.bottom(); + int height = (bottom - rect.top()) / 2; + + + for (const Tick &tick : qAsConst(m_majorTicks)) + { + painter->setPen(QPen(QGuiApplication::palette().color(QPalette::Text), 1)); + painter->drawText(QRect(tick.pos - 160 / 2, rect.top(), 160, height), Qt::AlignCenter, tick.label); + if (m_isLimitsVisible) + painter->drawLine(tick.pos, bottom, tick.pos, rect.bottom()); + painter->setPen(QPen(QGuiApplication::palette().color(QPalette::Text), 1)); + painter->drawLine(tick.pos, rect.top() + height, tick.pos, bottom); + } + + painter->setPen(QPen(QGuiApplication::palette().color(QPalette::Text), 1)); + + + for (const Tick &tick : qAsConst(m_mediumTicks)) + painter->drawLine(tick.pos, bottom - static_cast(double(height) * 2 / 3), tick.pos, bottom); + + + for (const Tick &tick : qAsConst(m_minorTicks)) + painter->drawLine(tick.pos, bottom - static_cast(double(height) / 3), tick.pos, bottom); + + + if (m_isLimitsVisible) + { + QString minLimitString = DateTime::toString(m_timelineView->m_currentTime, m_limitsDateTimeFormat); + QString maxLimitString = DateTime::toString(m_timelineView->m_currentTime + m_timelineView->m_zoomFactor, m_limitsDateTimeFormat); + int minLimitWidth = painter->fontMetrics().horizontalAdvance(minLimitString); + int maxLimitWidth = painter->fontMetrics().horizontalAdvance(maxLimitString); + painter->setPen(backgroundColor()); + painter->setBrush(QBrush(backgroundColor())); + painter->drawRect(rect.left() + 4, geometry().bottom() - s_limitHeight, minLimitWidth + 11, 19); + painter->drawRect(rect.right() - maxLimitWidth - 18, geometry().bottom() - s_limitHeight, maxLimitWidth + 11, 19); + painter->setPen(QGuiApplication::palette().color(QPalette::Text)); + painter->drawPixmap(rect.left() + 4, geometry().bottom() - 15, 11, 11, m_minLimitIndicator); + painter->drawPixmap(rect.right() - 15, geometry().bottom() - 15, 11, 11, m_maxLimitIndicator); + painter->drawText( + QRect(rect.left() + 18, geometry().bottom() - s_limitHeight, minLimitWidth, s_limitHeight - 1), + Qt::AlignLeft, minLimitString + ); + painter->drawText( + QRect(rect.right() - maxLimitWidth - 18, geometry().bottom() - s_limitHeight, maxLimitWidth, s_limitHeight - 1), + Qt::AlignRight, maxLimitString + ); + } + + + if (m_isHoverVisible) + { + if (m_hoverPosition != -1) + { + painter->setPen(highlightColor()); + painter->drawLine(m_hoverPosition, rect.top(), m_hoverPosition, rect.bottom()); + if (m_isHoverTimeVisible) + { + QString dateTimeString = DateTime::toString(m_hoverTime, m_hoverDateTimeFormat); + + + + const int textWidth = painter->fontMetrics().horizontalAdvance(dateTimeString) + 2; + const int textHeight = m_isLimitsVisible ? s_limitHeight - 1 : 19; + QRect dateTimeRect(m_hoverPosition, geometry().bottom() - textHeight, textWidth, textHeight - 1); + if (dateTimeRect.right() >= geometry().right()) + { + dateTimeRect.setLeft(m_hoverPosition - textWidth); + dateTimeRect.setWidth(textWidth); + } + painter->setBrush(QBrush(backgroundColor())); + painter->drawRect(dateTimeRect); + painter->drawText(dateTimeRect, Qt::AlignCenter, dateTimeString); + } + } + } + + + if (m_isIndicatorVisible) + { + int pos = toPixel(m_indicatorTime); + if (m_indicatorTime != -1 && (m_timelineView->currentTime() <= m_indicatorTime && + m_indicatorTime <= m_timelineView->currentTime() + m_timelineView->zoomFactor())) + { + painter->setPen(highlightColor()); + painter->setBrush(QBrush(highlightColor())); + int sideLength = double(m_isLimitsVisible ? rect.height() - height : rect.height()) * m_indicatorScalingFactor; + int top = bottom - static_cast(0.866 * sideLength); + QPolygon polygon; + polygon << QPoint(pos, bottom); + polygon << QPoint(pos + sideLength / 2, top); + polygon << QPoint(pos - sideLength / 2, top); + polygon << QPoint(pos, bottom); + QString dateTimeString = DateTime::toString(m_indicatorTime, m_hoverDateTimeFormat); + const int textWidth = painter->fontMetrics().horizontalAdvance(dateTimeString); + painter->setClipRect(rect.adjusted(-textWidth / 2, 0, 0, 0)); + painter->drawPolygon(polygon); + if (m_isLimitsVisible) + painter->drawLine(pos, bottom, pos, rect.bottom()); + QRect dateTimeRect(pos - textWidth / 2, geometry().top(), textWidth + 2, 19); + painter->setBrush(QBrush(backgroundColor())); + painter->drawRect(dateTimeRect); + painter->drawText(dateTimeRect, Qt::AlignCenter, dateTimeString); + painter->drawLine(pos, geometry().top() + 19, pos, bottom); + } + } + + + if (m_isLiveIndicatorVisible) + { + int pos = toPixel(m_liveIndicatorTime); + if (pos != -1 && (m_timelineView->currentTime() <= m_liveIndicatorTime && + m_liveIndicatorTime <= m_timelineView->currentTime() + m_timelineView->zoomFactor())) + { + painter->setPen(m_liveIndicatorColor); + painter->setBrush(QBrush(m_liveIndicatorColor)); + int sideLength = double(m_isLimitsVisible ? rect.height() - height : rect.height()) * m_liveIndicatorScalingFactor; + int top = bottom - static_cast(0.866 * sideLength); + QPolygon polygon; + polygon << QPoint(pos, bottom); + polygon << QPoint(pos + sideLength / 2, top); + polygon << QPoint(pos - sideLength / 2, top); + polygon << QPoint(pos, bottom); + QString dateTimeString = DateTime::toString(m_liveIndicatorTime, m_hoverDateTimeFormat); + const int textWidth = painter->fontMetrics().horizontalAdvance(dateTimeString); + painter->setClipRect(rect.adjusted(-textWidth / 2, 0, 0, 0)); + painter->drawPolygon(polygon); + QRect dateTimeRect(pos - textWidth / 2, geometry().top(), textWidth + 2, 19); + if (dateTimeRect.right() > geometry().right() - 20) + dateTimeRect.adjust(-22, 0, -22, 0); + painter->setBrush(QBrush(backgroundColor())); + painter->drawRect(dateTimeRect); + painter->drawText(dateTimeRect, Qt::AlignCenter, dateTimeString); + } + } + + painter->restore(); +} + + + + +void TimelineHeaderView::updateMajorTicks() +{ + m_majorTicks.clear(); + + if (m_timelineView->duration() <= 0) + return; + + const int interval = Zoom::majorInterval(m_timelineView->zoomFactor()); + if (interval == -1) + return; + + qint64 time = m_timelineView->currentTime(); + const int mod = time % interval; + if (mod) + time += interval - mod; + const qint64 end = m_timelineView->currentTime() + m_timelineView->zoomFactor(); + while (time <= end) + { + QString label = DateTime::toString(time, m_dateTimeFormat); + Tick tick(toPixel(time), time, label); + m_majorTicks.append(tick); + time += interval; + } +} + +void TimelineHeaderView::updateMediumTicks() +{ + m_mediumTicks.clear(); + + if (m_timelineView->duration() <= 0) + return; + + const int interval = Zoom::mediumInterval(m_timelineView->zoomFactor()); + if (interval == -1) + return; + + qint64 time = m_timelineView->currentTime(); + const int mod = time % interval; + if (mod) + time += interval - mod; + const qint64 end = m_timelineView->currentTime() + m_timelineView->zoomFactor(); + while (time <= end) + { + Tick tick(time); + + + auto it = std::find_if(m_majorTicks.begin(), m_majorTicks.end(), [tick](const Tick &other) + { + return tick.time == other.time; + }); + if (it != m_majorTicks.end()) + { + time += interval; + continue; + } + + tick.pos = toPixel(tick.time); + tick.label = DateTime::toString(tick.time, m_dateTimeFormat); + m_mediumTicks.append(tick); + time += interval; + } +} + +void TimelineHeaderView::updateMinorTicks() +{ + m_minorTicks.clear(); + + if (m_timelineView->duration() <= 0) + return; + + const int interval = Zoom::minorInterval(m_timelineView->zoomFactor()); + if (interval == -1) + return; + + qint64 time = m_timelineView->currentTime(); + const int mod = time % interval; + if (mod) + time += interval - mod; + const qint64 end = m_timelineView->currentTime() + m_timelineView->zoomFactor(); + while (time <= end) + { + Tick tick(time); + + + auto it = std::find_if(m_majorTicks.begin(), m_majorTicks.end(), [tick](const Tick &other) + { + return tick.time == other.time; + }); + if (it != m_majorTicks.end()) + { + time += interval; + continue; + } + + + it = std::find_if(m_mediumTicks.begin(), m_mediumTicks.end(), [tick](const Tick &other) + { + return tick.time == other.time; + }); + if (it != m_mediumTicks.end()) + { + time += interval; + continue; + } + + tick.pos = toPixel(tick.time); + tick.label = DateTime::toString(tick.time, m_dateTimeFormat); + m_minorTicks.append(tick); + time += interval; + } +} + + + + +void TimelineHeaderView::onUpdateTicks() +{ + updateMajorTicks(); + updateMediumTicks(); + updateMinorTicks(); + + if (m_isIndicatorVisible) + setIndicatorTime(m_indicatorTime); + + if (m_isLiveIndicatorVisible) + setLiveIndicatorTime(m_liveIndicatorTime); + + updateSection(Section::eItems); + emit ticksChanged(m_majorTicks); +} + + + + +void TimelineHeaderView::onLiveTimeout() +{ + setLiveIndicatorTime(m_liveIndicatorTime + m_liveTimerInterval); +} + +} diff --git a/Lts/Common/LtsTimeline/timeline_header_view.h b/Lts/Common/LtsTimeline/timeline_header_view.h new file mode 100755 index 000000000..ecf074f40 --- /dev/null +++ b/Lts/Common/LtsTimeline/timeline_header_view.h @@ -0,0 +1,172 @@ +#pragma once + +#include "timeline_header_view_mouse_handler.h" +#include "timeline_utils.h" + +#include + +#include + + +class QMouseEvent; +class QPainter; +class QRect; +class QTimer; +class QWheelEvent; + + +namespace LtsTimeline +{ + +class TimelineView; + + +class LTSTIMELINE_EXPORT TimelineHeaderView : public QHeaderView +{ + Q_OBJECT + + friend class TimelineHeaderViewMouseHandler; + friend class TimelineViewMouseHandler; + +public: + static const QColor backgroundColor(); + static const QColor highlightColor(); + + static const int s_limitHeight; + + enum Section { eActuator = 0, eItems = 1 }; + + TimelineHeaderView(QWidget *parent = nullptr); + ~TimelineHeaderView(); + + void setParent(QWidget *); + void setTrackLabel(const QString &trackLabel); + + const Ticks &majorTicks() const { return m_majorTicks; } + const Ticks &mediumTicks() const { return m_mediumTicks; } + const Ticks &minorTicks() const { return m_minorTicks; } + + DateTimeFormat dateTimeFormat() const { return m_dateTimeFormat; } + void setDateTimeFormat(DateTimeFormat dateTimeFormat); + void setDateTimeFormatChangable(bool changable) { m_isDateTimeFormatChangable = changable; } + bool isDateTimeFormatChangable() const { return m_isDateTimeFormatChangable; } + + double indicatorScalingFactor() const { return m_indicatorScalingFactor; } + void setIndicatorScalingFactor(double factor); + qint64 indicatorTime() const { return m_indicatorTime; } + void setIndicatorTime(qint64 time); + int indicatorPosition() const { return toPixel(m_indicatorTime); } + void setIndicatorPosition(int position); + bool isIndicatorVisible() const { return m_isIndicatorVisible; } + void setIndicatorVisible(bool isIndicatorVisible); + bool indicatorLocked() const { return m_isIndicatorLocked; } + void setIndicatorLocked(bool locked) { m_isIndicatorLocked = locked; } + void lockIndicator() { setIndicatorLocked(true); } + void unlockIndicator() { setIndicatorLocked(false); } + + double liveIndicatorScalingFactor() const { return m_liveIndicatorScalingFactor; } + void setLiveIndicatorScalingFactor(double factor); + QColor liveIndicatorColor() const { return m_liveIndicatorColor; } + void setLiveIndicatorColor(const QColor &color); + qint64 liveIndicatorTime() const { return m_liveIndicatorTime; } + void setLiveIndicatorTime(qint64 time); + int liveIndicatorPosition() const { return toPixel(m_liveIndicatorTime); } + void setLiveIndicatorPosition(int position); + bool isLiveIndicatorVisible() const { return m_isLiveIndicatorVisible; } + void setLiveIndicatorVisible(bool isLiveIndicatorVisible); + void startLiveTimer(qint64 startTime = -1, int interval = 1000); + void stopLiveTimer(); + + qint64 hoverTime() const { return m_hoverTime; } + void setHoverTime(qint64 time); + int hoverPosition() const { return m_hoverPosition; } + void setHoverPosition(int position); + bool isHoverVisible() const { return m_isHoverVisible; } + void setHoverVisible(bool visible); + bool isHoverTimeVisible() const { return m_isHoverTimeVisible; } + void setHoverTimeVisible(bool visible); + DateTimeFormat hoverDateTimeFormat() const { return m_hoverDateTimeFormat; } + void setHoverDateTimeFormat(DateTimeFormat format); + + bool isLimitsVisible() const { return m_isLimitsVisible; } + void setLimitsVisible(bool visible); + DateTimeFormat limitsDateTimeFormat() const { return m_limitsDateTimeFormat; } + void setLimitsDateTimeFormat(DateTimeFormat format); + + int toPixel(qint64 time) const; + qint64 toTime(int pixel) const; + + QFont &font() { return m_font; } + + TimelineHeaderViewMouseHandler *mouseHandler() { return m_mouseHandler; } + + void wheelEvent(QWheelEvent *e) override { m_mouseHandler->wheelEvent(e); } + +protected: + void mouseMoveEvent(QMouseEvent *e) override { m_mouseHandler->mouseMoveEvent(e); } + void mousePressEvent(QMouseEvent *e) override { m_mouseHandler->mousePressEvent(e); } + void mouseReleaseEvent(QMouseEvent *e) override { m_mouseHandler->mouseReleaseEvent(e); } + + void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const; + +private: + void updateMajorTicks(); + void updateMediumTicks(); + void updateMinorTicks(); + +public slots: + void onUpdateTicks(); + +private slots: + void onLiveTimeout(); + +signals: + void ticksChanged(const Ticks &); + void indicatorTimeChanged(qint64); + void indicatorVisibilityChanged(bool); + void liveIndicatorTimeChanged(qint64); + void liveIndicatorVisibilityChanged(bool); + void hoverPositionChanged(int); + void hoverVisibilityChanged(bool); + +private: + TimelineHeaderViewMouseHandler *m_mouseHandler{nullptr}; + TimelineView *m_timelineView{nullptr}; + Ticks m_majorTicks; + Ticks m_mediumTicks; + Ticks m_minorTicks; + DateTimeFormat m_dateTimeFormat{DateTimeFormat::DTG}; + bool m_isDateTimeFormatChangable{true}; + + double m_indicatorScalingFactor{0.5}; + qint64 m_indicatorTime{-1}; + bool m_isIndicatorVisible{true}; + bool m_isIndicatorLocked{false}; + + double m_liveIndicatorScalingFactor{0.5}; + QColor m_liveIndicatorColor; + qint64 m_liveIndicatorTime{-1}; + bool m_isLiveIndicatorVisible{false}; + QTimer *m_liveTimer{nullptr}; + int m_liveTimerInterval{1000}; + + qint64 m_hoverTime{-1}; + int m_hoverPosition{-1}; + bool m_isHoverVisible{true}; + bool m_isHoverTimeVisible{true}; + DateTimeFormat m_hoverDateTimeFormat{DateTimeFormat::DTG}; + + bool m_isLimitsVisible{false}; + DateTimeFormat m_limitsDateTimeFormat{DateTimeFormat::DTG}; + const QPixmap m_minLimitIndicator; + const QPixmap m_maxLimitIndicator; + + QString m_trackLabel; + + QFont m_font; + +}; + +typedef TimelineHeaderView::Section Section; + +} diff --git a/Lts/Common/LtsTimeline/timeline_header_view_mouse_handler.cpp b/Lts/Common/LtsTimeline/timeline_header_view_mouse_handler.cpp new file mode 100755 index 000000000..8b39e09ed --- /dev/null +++ b/Lts/Common/LtsTimeline/timeline_header_view_mouse_handler.cpp @@ -0,0 +1,160 @@ +#include "timeline_header_view_mouse_handler.h" +#include "timeline_header_view.h" +#include "timeline_view.h" + +#include +#include +#include +#include + + +namespace LtsTimeline +{ + +TimelineHeaderViewMouseHandler::TimelineHeaderViewMouseHandler(TimelineHeaderView *header) + : m_header(header) +{} + + + + + +void TimelineHeaderViewMouseHandler::mouseMoveEvent(QMouseEvent *e) +{ + if (m_isLeftButtonPressed) + m_hasMouseMovedLeft = true; + if (m_isRightButtonPressed) + m_hasMouseMovedRight = true; + + if (!m_isResizingSection) + { + if (m_header->sectionPosition(Section::eItems) - 10 < e->pos().x() && e->pos().x() < m_header->sectionPosition(Section::eItems)) + m_header->m_timelineView->setCursor(QCursor(Qt::SizeHorCursor)); + else if (e->pos().y() <= m_header->geometry().bottom()) + m_header->m_timelineView->setCursor(QCursor(Qt::ArrowCursor)); + } + else + m_header->resizeSection(Section::eActuator, std::min(e->pos().x(), m_header->geometry().width() / 2)); + + const bool isWithinRangeX = isMouseWithinRangeX(e->pos().x()); + if (!isWithinRangeX && !m_isLeftButtonPressed) + { + m_header->setHoverPosition(-1); + return; + } + + if (isWithinRangeX) + m_header->setHoverPosition(e->pos().x()); + if (m_isLeftButtonPressed) + { + if (m_isIndicatorSelected && !m_header->m_isIndicatorLocked) + { + m_header->m_timelineView->setCursor(QCursor(Qt::ClosedHandCursor)); + if (isWithinRangeX) + m_header->setIndicatorPosition(e->pos().x()); + } + } + else + { + if (isMouseOverIndicator(e->pos()) && m_header->m_isIndicatorVisible && !m_header->m_isIndicatorLocked) + m_header->m_timelineView->setCursor(QCursor(Qt::OpenHandCursor)); + else if (e->pos().y() <= m_header->geometry().bottom()) + m_header->m_timelineView->setCursor(QCursor(Qt::ArrowCursor)); + } +} + +void TimelineHeaderViewMouseHandler::mousePressEvent(QMouseEvent *e) +{ + if (e->button() == Qt::LeftButton) + { + m_lastPos = e->pos(); + m_isLeftButtonPressed = true; + if (isMouseOverIndicator(e->pos()) && m_header->m_isIndicatorVisible && !m_header->m_isIndicatorLocked) + { + m_header->m_timelineView->setCursor(QCursor(Qt::ClosedHandCursor)); + m_isIndicatorSelected = true; + } + else if (m_header->m_timelineView->cursor().shape() == Qt::SizeHorCursor) + { + m_isResizingSection = true; + } + } + if (e->button() == Qt::RightButton) + m_isRightButtonPressed = true; +} + +void TimelineHeaderViewMouseHandler::mouseReleaseEvent(QMouseEvent *e) +{ + if (e->button() == Qt::LeftButton) + { + if (!m_hasMouseMovedLeft && isMouseWithinRangeX(e->pos().x()) && !m_header->m_isIndicatorLocked) + m_header->setIndicatorPosition(e->pos().x()); + if (isMouseOverIndicator(e->pos())) + m_header->m_timelineView->setCursor(QCursor(Qt::OpenHandCursor)); + else + m_header->m_timelineView->setCursor(QCursor(Qt::ArrowCursor)); + m_lastPos = {}; + m_isIndicatorSelected = false; + m_isResizingSection = false; + m_isLeftButtonPressed = false; + m_hasMouseMovedLeft = false; + } + if (e->button() == Qt::RightButton) + { + m_header->setDateTimeFormat(static_cast((m_header->dateTimeFormat() + 1) % DateTime::numberOfDateTimeFormats())); + m_isRightButtonPressed = false; + m_hasMouseMovedRight = false; + } +} + +void TimelineHeaderViewMouseHandler::wheelEvent(QWheelEvent *e) +{ + if (!m_header->m_timelineView) + return; + + if (QGuiApplication::queryKeyboardModifiers() == Qt::ControlModifier) + { + if (e->angleDelta().y() < 0) + m_header->m_timelineView->setZoomFactor(Zoom::increment(m_header->m_timelineView->zoomFactor())); + else + m_header->m_timelineView->setZoomFactor(Zoom::decrement(m_header->m_timelineView->zoomFactor())); + } + else + { + if (e->angleDelta().y() > 0) + m_header->m_timelineView->setCurrentTime( + m_header->m_timelineView->currentTime() - Zoom::majorInterval(m_header->m_timelineView->zoomFactor()) + ); + else + m_header->m_timelineView->setCurrentTime( + m_header->m_timelineView->currentTime() + Zoom::majorInterval(m_header->m_timelineView->zoomFactor()) + ); + } +} + + + + + + + + + +bool TimelineHeaderViewMouseHandler::isMouseWithinRangeX(int x) const +{ + return m_header->sectionPosition(Section::eItems) < x && x < m_header->geometry().right(); +} + +bool TimelineHeaderViewMouseHandler::isMouseWithinRangeY(int y) const +{ + return m_header->geometry().top() < y && y < m_header->geometry().bottom(); +} + +bool TimelineHeaderViewMouseHandler::isMouseOverIndicator(const QPoint &pos) const +{ + const int height = m_header->height(); + return m_header->indicatorPosition() - height / 4 <= pos.x() && pos.x() <= m_header->indicatorPosition() + height / 4 && + m_header->geometry().center().y() <= pos.y() && pos.y() < m_header->geometry().bottom(); +} + +} diff --git a/Lts/Common/LtsTimeline/timeline_header_view_mouse_handler.h b/Lts/Common/LtsTimeline/timeline_header_view_mouse_handler.h new file mode 100755 index 000000000..8ca858f9f --- /dev/null +++ b/Lts/Common/LtsTimeline/timeline_header_view_mouse_handler.h @@ -0,0 +1,49 @@ +#pragma once + +#include + +#include + + +class QMouseEvent; +class QWheelEvent; + + +namespace LtsTimeline +{ + +class TimelineHeaderView; + + +class LTSTIMELINE_EXPORT TimelineHeaderViewMouseHandler +{ + +public: + explicit TimelineHeaderViewMouseHandler(TimelineHeaderView *header); + + void mouseMoveEvent(QMouseEvent *e); + void mousePressEvent(QMouseEvent *e); + void mouseReleaseEvent(QMouseEvent *e); + void wheelEvent(QWheelEvent *e); + + bool isBusy() const { return m_isIndicatorSelected || m_isResizingSection; } + bool isIndicatorSelected() const { return m_isIndicatorSelected; } + +private: + + bool isMouseWithinRangeX(int x) const; + bool isMouseWithinRangeY(int y) const; + bool isMouseOverIndicator(const QPoint &pos) const; + +private: + TimelineHeaderView *m_header{nullptr}; + QPoint m_lastPos; + bool m_isLeftButtonPressed{false}; + bool m_isRightButtonPressed{false}; + bool m_hasMouseMovedLeft{false}; + bool m_hasMouseMovedRight{false}; + bool m_isIndicatorSelected{false}; + bool m_isResizingSection{false}; +}; + +} diff --git a/Lts/Common/LtsTimeline/timeline_model.cpp b/Lts/Common/LtsTimeline/timeline_model.cpp new file mode 100755 index 000000000..89a9ca27e --- /dev/null +++ b/Lts/Common/LtsTimeline/timeline_model.cpp @@ -0,0 +1,761 @@ + + + +#include "timeline_model.h" +#include "timeline_view_drag_handler.h" +#include "track.h" + +#include + + +#include + + +namespace LtsTimeline +{ + +TimelineModel::TimelineModel(TimelineView *view, QObject *parent) + : QAbstractItemModel(parent) + , m_view(view) + , m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Timeline.TimelineModel")) +{ + m_rootTrack.m_id = nextFreeTrackId(); + m_trackCache.insert(std::pair(m_rootTrack.m_id, &m_rootTrack)); +} + + +void TimelineModel::clear() +{ + m_rootTrack.m_childrenTracks.clear(); + m_rootTrack.m_items.clear(); + + + m_rootTrack.m_copyItemIds.clear(); + m_trackCache.clear(); + + m_itemCache.clear(); + + m_currentTrackId = 1; + m_currentItemId = 0; + m_trackCache.insert(std::pair(m_rootTrack.m_id, &m_rootTrack)); +} + + +void TimelineModel::clearItems() +{ + for (std::pair p : m_trackCache) + p.second->clearItems(); + + m_itemCache.clear(); + m_currentItemId = 0; +} + + +Track &TimelineModel::track(int id) noexcept(false) +{ + if (!m_trackCaching) + { + if (id == m_rootTrack.id()) + return m_rootTrack; + return m_rootTrack.track(id); + } + + auto it = m_trackCache.find(id); + + if (it == m_trackCache.end()) + throw QStringLiteral("Model: track with id %1 not found in cache").arg(id); + + return *it->second; +} + + +const Track &TimelineModel::track(int id) const noexcept(false) +{ + if (!m_trackCaching) + { + if (id == m_rootTrack.id()) + return m_rootTrack; + return m_rootTrack.track(id); + } + + std::map::const_iterator it = m_trackCache.find(id); + + if (it == m_trackCache.cend()) + throw QStringLiteral("Model: const track with id %1 not found in cache").arg(id); + + return *it->second; +} + + +Item &TimelineModel::item(int id) noexcept(false) +{ + if (!m_itemCaching) + return m_rootTrack.item(id); + + auto it = m_itemCache.find(id); + + if (it == m_itemCache.end()) + throw QStringLiteral("Model: item with id %1 not found in cache").arg(id); + + return *it->second; +} + + +const Item &TimelineModel::item(int id) const noexcept(false) +{ + if (!m_itemCaching) + return m_rootTrack.item(id); + + decltype(m_itemCache)::const_iterator it = m_itemCache.find(id); + + if (it == m_itemCache.cend()) + throw QStringLiteral("Model: const item with id %1 not found in cache").arg(id); + + return *it->second; +} + + +const QVector TimelineModel::itemIds() const +{ + if (!m_itemCaching) + return m_rootTrack.itemIds(true); + + QVector ids; + std::transform(m_itemCache.cbegin(), m_itemCache.cend(), std::back_inserter(ids), + [](const std::pair &pair) + { + return pair.first; + }); + + return ids; +} + + + +const QVector TimelineModel::itemIdsBetween(qint64 start, qint64 end, bool strict, bool complete, bool recursive, bool sorted) const +{ + return m_rootTrack.itemIdsBetween(start, end, strict, complete, recursive, sorted); +} + +bool TimelineModel::isItemsBetween(qint64 start, qint64 end, const QVector &ignoreIds, bool strict, bool complete, bool recursive) const +{ + return m_rootTrack.isItemsBetween(start, end, ignoreIds, strict, complete, recursive); +} + +const QVector TimelineModel::copyItemIdsBetween(qint64 start, qint64 end, bool strict, bool complete, bool sorted) const +{ + return m_rootTrack.copyItemIdsBetween(start, end, strict, complete, true, sorted); +} + +bool TimelineModel::isItemsBetween(qint64 start, qint64 end, int ignoreId, bool strict, bool complete, bool recursive) const +{ + return m_rootTrack.isItemsBetween(start, end, ignoreId, strict, complete, recursive); +} + + +const QVector TimelineModel::_copyItemIdsBetween(Track &t, qint64 start, qint64 end, bool strict, bool complete, bool recursive, bool sorted, qint64 minimumDuration) const +{ + QVector itemIds; + itemIds.reserve(t.itemCount()); + + + for (const int itemId : qAsConst(t.copyItemIds())) + { + const Item &i = item(itemId); + if (Track::isItemBetween(i, start, end, strict, complete, minimumDuration)) + itemIds += i.id(); + } + + if (recursive) + for (const TrackPtr &trackPtr : qAsConst(t.tracks())) + itemIds += _copyItemIdsBetween(*trackPtr.get(), start, end, strict, complete, true, false, minimumDuration); + + + itemIds.erase(std::unique(itemIds.begin(), itemIds.end()), itemIds.end()); + + itemIds.squeeze(); + + if (!sorted) + return itemIds; + + + std::sort(itemIds.begin(), itemIds.end(), [this](const int itemIdA, const int itemIdB) + { + try + { + return item(itemIdA).startTime() < item(itemIdB).startTime(); + } + catch (...) { return false; } + }); + + return itemIds; +} + + +const QVector TimelineModel::_itemIdsBetween(Track &t, qint64 start, qint64 end, bool strict, bool complete, bool recursive, bool sorted, qint64 minimumDuration) const +{ + QVector itemIds; + itemIds.reserve(t.itemCount()); + + for (const ItemPtr &itemPtr : qAsConst(t.items())) + { + const Item &i = *itemPtr; + if (Track::isItemBetween(i, start, end, strict, complete, minimumDuration)) + itemIds += i.id(); + } + + if (recursive) + for (const TrackPtr &trackPtr : qAsConst(t.tracks())) + itemIds += _itemIdsBetween(*trackPtr.get(), start, end, strict, complete, true, false, minimumDuration); + + + + + itemIds.squeeze(); + + if (!sorted) + return itemIds; + + + std::sort(itemIds.begin(), itemIds.end(), [this](const int itemIdA, const int itemIdB) + { + try + { + return item(itemIdA).startTime() < item(itemIdB).startTime(); + } + catch (...) { return false; } + }); + + return itemIds; +} + + +bool TimelineModel::_isItemsBetween(const Track &t, qint64 start, qint64 end, const QVector &ignoreIds, bool strict, bool complete, bool recursive, qint64 minimumDuration) const +{ + for (const ItemPtr &itemPtr : qAsConst(t.items())) + { + const Item &i = *itemPtr; + if (!ignoreIds.contains(i.id())) + if (Track::isItemBetween(i, start, end, strict, complete, minimumDuration)) + return true; + } + + if (!recursive) + return false; + + for (const TrackPtr &trackPtr : qAsConst(t.tracks())) + if (_isItemsBetween(*trackPtr.get(), start, end, ignoreIds, strict, complete, true, minimumDuration)) + return true; + + return false; +} + + +bool TimelineModel::_isItemsBetween(const Track &t, qint64 start, qint64 end, int ignoreId, bool strict, bool complete, bool recursive, qint64 minimumDuration) const +{ + return _isItemsBetween(t, start, end, QVector({ ignoreId }), strict, complete, recursive, minimumDuration); +} + + +QVariant TimelineModel::data(const QModelIndex &index, int role) const +{ + if ((role != Qt::DisplayRole && role != Qt::UserRole) || !index.isValid()) + return {}; + + int trackId = index.internalId(); + int column = index.column(); + + if (column == 1 && role == Qt::DisplayRole) + return trackId; + + if (role == Qt::UserRole) + { + if (column == 0) + return trackId; + + try + { + const Track &t = track(trackId); + return t.userData(); + } + catch(...) + { + Q_ASSERT(false); + return {}; + } + } + + + if (column != 0) + return {}; + + + QString text; + + try + { + const Track &t = track(trackId); + +#if 0 + text = QStringLiteral("[%1] %3").arg(t.id()).arg(t.text()); +#else + text = t.text(); +#endif + } + catch(...) + { + Q_ASSERT(false); + return QStringLiteral("[?????]"); + } + + if (text.isEmpty()) + text = tr("[unknown]"); + + return text; +} + + +Qt::ItemFlags TimelineModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return {}; + + return QAbstractItemModel::flags(index); +} + + +QModelIndex TimelineModel::index(int row, int column, const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) + return {}; + + int childTrackId; + try + { + const Track &parentTrack = parent.isValid() ? track(parent.internalId()) : m_rootTrack; + const Track &childTrack = parentTrack.trackAt(row); + childTrackId = childTrack.id(); + } + catch (const QString &msg) + { + DEBUG_LOG(m_logger) << Q_FUNC_INFO << "cannot get parent track " << msg; + return {}; + } + catch (const std::out_of_range &oor) + { + DEBUG_LOG(m_logger) << Q_FUNC_INFO << "cannot get child track " << QString::fromUtf8(oor.what()); + return {}; + } + + return createIndex(row, column, childTrackId); +} + + +int TimelineModel::rowOfTrackId(int id) const noexcept(false) +{ + const Track &parent = track(track(id).parentId()); + + if (parent.items().empty()) + throw("parent doesn't have items"); + + int row = 0; + + for (const TrackPtr &trackPtr : qAsConst(parent.tracks())) + { + if (trackPtr->id() == id) + return row; + row++; + } + + throw("not found"); +} + + +const Track &TimelineModel::trackOfIndex(const QModelIndex &index) const +{ + if (!index.isValid()) + throw QStringLiteral("trackOfIndex: index is invalid!"); + + + try + { + return track(index.internalId()); + } + catch (const QString &msg) + { + throw QStringLiteral("trackOfIndex: cannot find track by internal id %1: %2").arg(index.internalId()).arg(msg); + } + + + return m_rootTrack; +} + + + + + + + + + + + + + + + + + + +const Track &TimelineModel::parentOfTrackId(int id) const noexcept(false) +{ + if (!m_trackCaching) + { + if (m_rootTrack.hasTrack(id)) + return m_rootTrack; + + + } + + const Track &theTrack = track(id); + + if (theTrack.parentId() == m_rootTrack.id()) + return m_rootTrack; + + return track(theTrack.parentId()); +} + + +QModelIndex TimelineModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) + return {}; + + + + + + + + + + + + + + int trackId = index.internalId(); + int parentId; + int superParentId; + try + { + const Track &parent = parentOfTrackId(trackId); + parentId = parent.id(); + superParentId = parent.parentId(); + } + catch (const QString &msg) + { + DEBUG_LOG(m_logger) << Q_FUNC_INFO << "cannot get parent of track with id" << trackId << msg; + return {}; + } + + if (parentId == m_rootTrack.id()) + return {}; + + int parentsRow; + try + { + const Track &superParent = track(superParentId); + + try + { + parentsRow = superParent.rowOfTrack(parentId); + } + catch (const QString &msg) + { + DEBUG_LOG(m_logger) << Q_FUNC_INFO << "row of track for parent not found for parent id" << parentId << "-" << msg; + return {}; + } + } + catch (const QString &msg) + { + DEBUG_LOG(m_logger) << Q_FUNC_INFO << "super parent not found for super parent id" << superParentId << "-" << msg; + return {}; + } + + return createIndex(parentsRow, 0, parentId); +} + + +void TimelineModel::setItemStartTime(Track &t, Item &i, qint64 newStart) +{ + i._setStartTime(newStart); + t.sort(); +} + + +void TimelineModel::setItemStartTime(Item &i, qint64 newStart) noexcept(false) +{ + setItemStartTime(track(i.parentTrackId()), i, newStart); +} + + +void TimelineModel::setItemStartTime(int itemId, qint64 newStart) noexcept(false) +{ + setItemStartTime(item(itemId), newStart); +} + + +void TimelineModel::setItemDuration(Track &t, Item &i, int duration) +{ + i._setDuration(std::max(duration, m_minItemDuration)); + t.sort(); +} + +void TimelineModel::setItemDuration(Item &i, int duration) noexcept(false) +{ + setItemDuration(track(i.parentTrackId()), i, duration); +} + +void TimelineModel::setItemDuration(int itemId, int duration) noexcept(false) +{ + setItemDuration(item(itemId), duration); +} + + +void TimelineModel::copyToClipboard(const QVector &ids) noexcept(false) +{ + m_clipboard.clear(); + m_clipboard.reserve(ids.size()); + qint64 tStartMin = LLONG_MAX; + for (int itemId : qAsConst(ids)) + { + const Item &i = item(itemId); + ItemPtr iPtr = std::make_unique(Item(i.startTime(), i.duration(), i.color())); + iPtr->m_parentTrackId = i.m_parentTrackId; + m_clipboard.push_back(std::move(iPtr)); + tStartMin = std::min(tStartMin, i.startTime()); + } + for (ItemPtr &iPtr : m_clipboard) + iPtr->_setStartTime(iPtr->startTime() - tStartMin); +} + +void TimelineModel::cutToClipboard(const QVector &ids) noexcept(false) +{ + copyToClipboard(ids); + for (int itemId : qAsConst(ids)) + removeItem(itemId); +} + +void TimelineModel::pasteFromClipboard(qint64 time) noexcept(false) +{ + if (m_isSnapEnabled) + time -= (time % m_snapStep); + for (ItemPtr &iPtr : m_clipboard) + { + iPtr->_setStartTime(time + iPtr->startTime()); + addItem(iPtr->parentTrackId(), std::move(iPtr)); + } + m_clipboard.clear(); +} + + +int TimelineModel::rowCount(const QModelIndex &parent) const +{ + if (parent.column() > 0) + return 0; + + if (!parent.isValid()) + return m_rootTrack.tracks().size(); + + try + { + return track(parent.internalId()).tracks().size(); + } + catch(...) {} + + return 0; +} + + +QModelIndex TimelineModel::indexOfTrack(int trackId) const +{ + for (int row = 0; row < rowCount(); ++row) + { + QModelIndex idx = indexOfTrackFromIndex(index(row, 0, {}), trackId); + if (idx.isValid()) + return idx; + } + return {}; +} + + +QModelIndex TimelineModel::indexOfTrackFromIndex(const QModelIndex &idx, int trackId) const +{ + if (static_cast(idx.internalId()) == trackId) + return index(idx.row(), 1, idx.parent()); + + for (int row = 0; row < rowCount(idx); ++row) + { + const QModelIndex i = indexOfTrackFromIndex(index(row, 0, idx), trackId); + if (i.isValid()) + return i; + } + + return {}; +} + + +QModelIndex TimelineModel::indexOfTrackSafe(int trackId) const noexcept(false) +{ + for (int row = 0; row < rowCount(); ++row) + { + const QModelIndex idx(indexOfTrackFromIndex(index(row, 0 , {}), trackId)); + if (idx.isValid()) + return idx; + } + + QString trackText; + try + { + trackText = track(trackId).text(); + } catch(...){} + + throw QStringLiteral("indexOfTrackSafe: index for trackId %1 ('%2') not found ").arg(trackId).arg(trackText.isEmpty() ? QStringLiteral("(unknown)") : trackText); +} + + +QModelIndex TimelineModel::indexOfTrackFromIndexSafe(const QModelIndex &idx, int trackId) const noexcept(false) +{ + if (static_cast(idx.internalId()) == trackId) + return idx; + + for (int row = 0; row < rowCount(idx); ++row) + { + const QModelIndex i = indexOfTrackFromIndex(index(row, 1, idx), trackId); + if (i.isValid()) + return i; + } + + throw QStringLiteral("indexOfTrackFromIndexSafe: index for trackId %1 not found").arg(trackId); +} + + +void TimelineModel::moveItemToTrack(int itemId, int trackId, qint64 time) noexcept(false) +{ + + + + const Item &theItem = item(itemId); + Track &oldTrack = track(theItem.parentTrackId()); + Track &newTrack = track(trackId); + + ItemPtr itemPtr = oldTrack.takeItem(itemId); + + Item *theItemPtr = newTrack.addItem(std::move(itemPtr)); + setItemStartTime(newTrack, *theItemPtr, time); + theItemPtr->m_parentTrackId = trackId; +} + + +void TimelineModel::removeItem(int itemId) noexcept(false) +{ + const Item &i = item(itemId); + Track &t = track(i.parentTrackId()); + + t.removeItem(itemId); + + auto it = m_itemCache.find(itemId); + if (it == m_itemCache.end()) + { + Q_ASSERT(false); + throw QStringLiteral("removeItem: item with id %1 not found").arg(itemId); + } + + m_itemCache.erase(it); + + + +} + + +void TimelineModel::removeTrack(int trackId) noexcept(false) +{ + if (trackId == m_rootTrack.id()) + { + Q_ASSERT(false); + throw QStringLiteral("removeTrack: the root track shouldn't and *can't* be deleted"); + } + + + const Track &t = track(trackId); + Track &parentTrack = track(t.parentId()); + + for (int itemId : t.itemIds(true)) + { + auto it = m_itemCache.find(itemId); + + if (it == m_itemCache.end()) + { + Q_ASSERT(false); + continue; + } + + m_itemCache.erase(it); + } + + + parentTrack.removeTrack(trackId); + + auto it = m_trackCache.find(trackId); + if (it == m_trackCache.end()) + { + Q_ASSERT(false); + return; + } + + m_trackCache.erase(it); +} + + +void TimelineModel::setMinimumDuration(int duration) +{ + if (duration < 0) + { + WARNING_LOG(m_logger) << "negative duration is not possible -> setting minimum duration to 0"; + duration = 0; + } + if (duration % 1000 && m_isSnapEnabled) + WARNING_LOG(m_logger) << "Snapping to 1000ms enabled, yet minimum duration is not a multiple of 1000ms: " << duration; + m_minItemDuration = duration; +} + +bool TimelineModel::canDropItem(const Item &item, int trackId, qint64 start) const +{ + Q_UNUSED(item); + Q_UNUSED(trackId); + Q_UNUSED(start); + return true; +} + +bool TimelineModel::canMoveItem(const Item &item, qint64 start) const +{ + Q_UNUSED(item); + Q_UNUSED(start); + return true; +} + +bool TimelineModel::canResizeItem(const Item &item, qint64 start, int duration) const +{ + Q_UNUSED(item); + Q_UNUSED(start); + Q_UNUSED(duration); + return true; +} + + +bool TimelineModel::isItemDragged(int _itemId) const +{ + return m_view->dragHandler() ? m_view->dragHandler()->dragItemIds().contains(_itemId) : false; +} + +} diff --git a/Lts/Common/LtsTimeline/timeline_model.h b/Lts/Common/LtsTimeline/timeline_model.h new file mode 100755 index 000000000..40fd3ffea --- /dev/null +++ b/Lts/Common/LtsTimeline/timeline_model.h @@ -0,0 +1,247 @@ + + + +#pragma once + +#include "item.h" +#include "track.h" + +#include + +#include +#include + +#include + + +namespace LtsTimeline +{ + +class TimelineView; + + +class LTSTIMELINE_EXPORT TimelineModel : public QAbstractItemModel +{ + Q_OBJECT + + friend class TimelineViewSelectHandler; + +public: + explicit TimelineModel(TimelineView *view, QObject *parent = nullptr); + virtual ~TimelineModel() = default; + + void clear(); + Track &rootTrack() { return m_rootTrack; } + Track &track(int id) noexcept(false); + const Track &track(int id) const noexcept(false); + const Item &item(int id) const noexcept(false); + Item &item(int id) noexcept(false); + const QVector itemIds() const; + const QVector itemIdsBetween(qint64 start, qint64 end, bool strict = false, bool complete = false, bool recursive = false, bool sorted = false) const; + const QVector copyItemIdsBetween(qint64 start, qint64 end, bool strict, bool complete, bool sorted) const; + + bool isItemsBetween(qint64 start, qint64 end, const QVector &ignoreIds = {}, bool strict = false, bool complete = false, bool recursive = false) const; + bool isItemsBetween(qint64 start, qint64 end, int ignoreId = -1 , bool strict = false, bool complete = false, bool recursive = false) const; + const Track &parentOfTrackId(int id) const noexcept(false); + + virtual void setItemStartTime(Track &t, Item &i, qint64 newStart); + virtual void setItemStartTime(Item &i, qint64 newStart) noexcept(false); + virtual void setItemStartTime(int itemId, qint64 newStart) noexcept(false); + + virtual void setItemDuration(Track &t, Item &i, int duration); + virtual void setItemDuration(Item &i, int duration) noexcept(false); + virtual void setItemDuration(int itemId, int duration) noexcept(false); + + + void print(bool withItems = false) const { m_rootTrack.printTrackIds(withItems); } + + template ::value, TrackClass>::type> + int addTrack(int parentTrackId, TrackClass &&newTrack ) noexcept(false) + { + int id = nextFreeTrackId(); + + TrackClass _track(std::move(newTrack)); + _track.m_id = id; + _track.m_parentId = parentTrackId; + + + + + + auto *ptr = track(parentTrackId).addTrack(std::move(_track)); + m_trackCache.insert(std::pair(id, ptr)); + + + + return id; + } + + template ::value, ItemClass>::type> + int addItem(int parentTrackId, ItemClass &&item) noexcept(false) + { + int id = nextFreeItemId(); + + ItemClass _item(std::move(item)); + _item.m_id = id; + _item.m_parentTrackId = parentTrackId; + + if (m_isSnapEnabled) + { + _item.m_startTime = _item.m_startTime - (_item.m_startTime % 1000); + _item.m_duration = _item.m_duration - (_item.m_duration % 1000); + } + + Item *ptr = track(parentTrackId).addItem(std::move(_item)); + m_itemCache.insert(std::pair(id, ptr)); + + QModelIndex idx = indexOfTrack(parentTrackId); + emit dataChanged(idx, idx); + + return id; + } + + bool isEditable() const { return m_isEditable; } + void setEditable(bool editable) { m_isEditable = editable; } + bool isSnapEnabled() const { return m_isSnapEnabled; } + void setSnapEnabled(bool enabled) { m_isSnapEnabled = enabled; } + int snapStep() const { return m_snapStep; } + void setSnapStep(int step) { m_snapStep = step; } + int snapTolerance() const { return m_snapTol; } + void setSnapTolerance(int tolerance) { m_snapTol = tolerance; } + qint64 snapTime(qint64 time) const { return m_isSnapEnabled ? time / m_snapStep * m_snapStep : time; } + int minimumDuration() const { return m_minItemDuration; } + void setMinimumDuration(int duration); + + QModelIndex indexOfTrack(int trackId) const; + QModelIndex indexOfTrackSafe(int trackId) const noexcept(false); + + virtual void moveItemToTrack(int itemId, int trackId, qint64 time) noexcept(false); + virtual void removeItem(int itemId) noexcept(false); + virtual void removeTrack(int trackId) noexcept(false); + + virtual bool canDropItem(const Item &item, int trackId, qint64 start) const; + virtual bool canMoveItem(const Item &item, qint64 start) const; + virtual bool canResizeItem(const Item &item, qint64 start, int duration) const; + + virtual bool isItemDragged(int _itemId) const; + + virtual void copyToClipboard(const QVector &ids) noexcept(false); + virtual void cutToClipboard(const QVector &ids) noexcept(false); + virtual void pasteFromClipboard(qint64 time) noexcept(false); + + bool isClipboardEmpty() const { return m_clipboard.empty(); } + + + int rowCount(const QModelIndex &parent = {}) const override; + QVariant data(const QModelIndex &index, int role) const override; + QModelIndex index(int row, int column, const QModelIndex &parent) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + QModelIndex parent(const QModelIndex &index) const override; + int columnCount(const QModelIndex &) const override { return 2; } + +protected: + ItemPtrNCVRef _itemNCV(Track &track, int id) noexcept(false) + { + return track._itemNCV(id); + } + + ItemPtrNCVRef _itemNCV(const TrackPtr &trackPtr, int id) noexcept(false) + { + return trackPtr->_itemNCV(id); + } + + const ItemPtrRefs _items(const TrackPtr &trackPtr, bool recursive) const + { + return trackPtr->_items(recursive); + } + + ItemPtrNCVRefs _itemsNCV(const TrackPtr &trackPtr, bool recursive) + { + return trackPtr->_itemsNCV(recursive); + } + + + + int addItem(int parentTrackId, ItemPtr &&itemPtr, bool forceTheCompilerToUseMe = false) noexcept(false) + { + (void)forceTheCompilerToUseMe; + + int id = nextFreeItemId(); + + ItemPtr _itemPtr(std::move(itemPtr)); + + _itemPtr->m_id = id; + + _itemPtr->m_parentTrackId = parentTrackId; + + if (m_isSnapEnabled) + { + _itemPtr->m_startTime = _itemPtr->m_startTime - (_itemPtr->m_startTime % 1000); + _itemPtr->m_duration = _itemPtr->m_duration - (_itemPtr->m_duration % 1000); + } + Item *ptr = track(parentTrackId).addItem(std::move(_itemPtr)); + + m_itemCache.insert(std::pair(id, ptr)); + + QModelIndex idx = indexOfTrack(parentTrackId); + emit dataChanged(idx, idx); + + return id; + } + + + + + + + + + + void clearItems(); + int rowOfTrackId(int id) const noexcept(false); + + + + const Track &trackOfIndex(const QModelIndex &index) const; + int indexOf(int id) const; + + const QVector _itemIdsBetween(Track &t, qint64 start, qint64 end, bool strict, bool complete, bool recursive, bool sorted, qint64 minimumDuration) const; + const QVector _copyItemIdsBetween(Track &t, qint64 start, qint64 end, bool strict, bool complete, bool recursive, bool sorted, qint64 minimumDuration) const; + bool _isItemsBetween(const Track &t, qint64 start, qint64 end, const QVector &ignoreIds, bool strict, bool complete, bool recursive, qint64 minimumDuration) const; + bool _isItemsBetween(const Track &t, qint64 start, qint64 end, int ignoreId, bool strict, bool complete, bool recursive, qint64 minimumDuration) const; + +private: + int nextFreeTrackId() { return m_currentTrackId++; } + int nextFreeItemId() { return m_currentItemId++; } + + QModelIndex indexOfTrackFromIndex(const QModelIndex &idx, int trackId) const; + QModelIndex indexOfTrackFromIndexSafe(const QModelIndex &idx, int trackId) const noexcept(false); + +public slots: + virtual void onInsertTrack(const QModelIndex &index) { Q_UNUSED(index); } + virtual void onRemoveTrack(const QModelIndex &index) { Q_UNUSED(index); } + +protected: + TimelineView *m_view{nullptr}; + + Track m_rootTrack; + int m_currentTrackId{0}; + int m_currentItemId{0}; + bool m_isEditable{true}; + bool m_isSnapEnabled{false}; + int m_snapStep{500}; + int m_snapTol{1000}; + int m_minItemDuration{0}; + + + bool m_itemCaching{true}; + bool m_trackCaching{true}; + std::map m_trackCache; + + std::map m_itemCache; + Items m_clipboard; + + NGL::INGLLogger &m_logger; +}; + +} diff --git a/Lts/Common/LtsTimeline/timeline_utils.cpp b/Lts/Common/LtsTimeline/timeline_utils.cpp new file mode 100755 index 000000000..a717a4afc --- /dev/null +++ b/Lts/Common/LtsTimeline/timeline_utils.cpp @@ -0,0 +1,343 @@ +#include "timeline_utils.h" + +#include +#include + +#include +#include + + +namespace LtsTimeline +{ + +namespace DateTime +{ + + + + +int numberOfDateTimeFormats() +{ + return 6; +} + +QString toString(qint64 time, Format format) +{ + switch (format) + { + case Format::DTG: + return DTG::toStringMSec(time, DTG::DisplayFormat::Long); + case Format::ISODate: + return QDateTime::fromMSecsSinceEpoch(time).toString(Qt::ISODate); + case Format::Standard: + return QDateTime::fromMSecsSinceEpoch(time).toString(QStringLiteral("dd.MM.yyyy hh:mm:ss")); + case Format::DTGShort: + { + QDateTime dateTime = QDateTime::fromMSecsSinceEpoch(time).toUTC().addSecs(DTG::timezone2Hours(DTG::timezone()) * 3600); + return dateTime.toString(QStringLiteral("hhmmss ")) + QStringLiteral("%1").arg(DTG::timezone()); + } + case Format::DTGVeryShort: + { + QDateTime dateTime = QDateTime::fromMSecsSinceEpoch(time).toUTC().addSecs(DTG::timezone2Hours(DTG::timezone()) * 3600); + return dateTime.toString(QStringLiteral("hhmm")); + } + case Format::TimeOnly: + return QTime::fromMSecsSinceStartOfDay(time).toString(); + } + return {}; +} + +} + + + + + +namespace Zoom +{ + +int numberOfZoomFactors() +{ + return 15; +} + +Factor LTSTIMELINE_EXPORT increment(Factor factor) +{ + switch (factor) + { + case ZoomFactor::FifteenSeconds: + return ZoomFactor::ThirtySeconds; + case ZoomFactor::ThirtySeconds: + return ZoomFactor::SixtySeconds; + case ZoomFactor::SixtySeconds: + return ZoomFactor::TwoMinutes; + case ZoomFactor::TwoMinutes: + return ZoomFactor::ThreeMinutes; + case ZoomFactor::ThreeMinutes: + return ZoomFactor::FiveMinutes; + case ZoomFactor::FiveMinutes: + return ZoomFactor::TenMinutes; + case ZoomFactor::TenMinutes: + return ZoomFactor::FifteenMinutes; + case ZoomFactor::FifteenMinutes: + return ZoomFactor::ThirtyMinutes; + case ZoomFactor::ThirtyMinutes: + return ZoomFactor::SixtyMinutes; + case ZoomFactor::SixtyMinutes: + return ZoomFactor::TwoHours; + case ZoomFactor::TwoHours: + return ZoomFactor::FourHours; + case ZoomFactor::FourHours: + return ZoomFactor::EightHours; + case ZoomFactor::EightHours: + return ZoomFactor::TwelveHours; + case ZoomFactor::TwelveHours: + return ZoomFactor::OneDay; + case ZoomFactor::OneDay: + return ZoomFactor::OneDay; + } + return factor; +} + +Factor LTSTIMELINE_EXPORT decrement(Factor factor) +{ + switch (factor) + { + case ZoomFactor::FifteenSeconds: + return ZoomFactor::FifteenSeconds; + case ZoomFactor::ThirtySeconds: + return ZoomFactor::FifteenSeconds; + case ZoomFactor::SixtySeconds: + return ZoomFactor::ThirtySeconds; + case ZoomFactor::TwoMinutes: + return ZoomFactor::SixtySeconds; + case ZoomFactor::ThreeMinutes: + return ZoomFactor::TwoMinutes; + case ZoomFactor::FiveMinutes: + return ZoomFactor::ThreeMinutes; + case ZoomFactor::TenMinutes: + return ZoomFactor::FiveMinutes; + case ZoomFactor::FifteenMinutes: + return ZoomFactor::TenMinutes; + case ZoomFactor::ThirtyMinutes: + return ZoomFactor::FifteenMinutes; + case ZoomFactor::SixtyMinutes: + return ZoomFactor::ThirtyMinutes; + case ZoomFactor::TwoHours: + return ZoomFactor::SixtyMinutes; + case ZoomFactor::FourHours: + return ZoomFactor::TwoHours; + case ZoomFactor::EightHours: + return ZoomFactor::FourHours; + case ZoomFactor::TwelveHours: + return ZoomFactor::EightHours; + case ZoomFactor::OneDay: + return ZoomFactor::TwelveHours; + } + return factor; +} + +Factor ceil(int value) +{ + if (value >= Factor::OneDay) + return Factor::OneDay; + + Factor ret = Factor::FifteenSeconds; + while (static_cast(ret) < value) + { + ret = increment(ret); + } + return ret; +} + +Factor floor(int value) +{ + if (value <= Factor::FifteenSeconds) + return Factor::FifteenSeconds; + + Factor ret = Factor::OneDay; + while (static_cast(ret) > value) + { + ret = decrement(ret); + } + return ret; +} + +QVector availableZoomFactors(Factor min, Factor max) +{ + if (min > max) + return {}; + + ZoomFactors ret; + ret.reserve(numberOfZoomFactors()); + Factor factor = min; + do + { + ret.append(factor); + if (factor == max) + break; + factor = increment(factor); + } while (increment(factor) > factor); + return ret; +} + +QString toString(ZoomFactor factor, bool useShort) +{ + switch (factor) + { + case ZoomFactor::FifteenSeconds: + return useShort ? QApplication::tr("15 sec") : QApplication::tr("15 Seconds"); + case ZoomFactor::ThirtySeconds: + return useShort ? QApplication::tr("30 sec") : QApplication::tr("30 Seconds"); + case ZoomFactor::SixtySeconds: + return useShort ? QApplication::tr("1 min") : QApplication::tr("1 Minute"); + case ZoomFactor::TwoMinutes: + return useShort ? QApplication::tr("2 min") : QApplication::tr("2 Minutes"); + case ZoomFactor::ThreeMinutes: + return useShort ? QApplication::tr("3 min") : QApplication::tr("3 Minutes"); + case ZoomFactor::FiveMinutes: + return useShort ? QApplication::tr("5 min") : QApplication::tr("5 Minutes"); + case ZoomFactor::TenMinutes: + return useShort ? QApplication::tr("10 min") : QApplication::tr("10 Minutes"); + case ZoomFactor::FifteenMinutes: + return useShort ? QApplication::tr("15 min") : QApplication::tr("15 Minutes"); + case ZoomFactor::ThirtyMinutes: + return useShort ? QApplication::tr("30 min") : QApplication::tr("30 Minutes"); + case ZoomFactor::SixtyMinutes: + return useShort ? QApplication::tr("1 h") : QApplication::tr("1 Hour"); + case ZoomFactor::TwoHours: + return useShort ? QApplication::tr("2 h") : QApplication::tr("2 Hours"); + case ZoomFactor::FourHours: + return useShort ? QApplication::tr("4 h") : QApplication::tr("4 Hours"); + case ZoomFactor::EightHours: + return useShort ? QApplication::tr("8 h") : QApplication::tr("8 Hours"); + case ZoomFactor::TwelveHours: + return useShort ? QApplication::tr("12 h") : QApplication::tr("12 Hours"); + case ZoomFactor::OneDay: + return useShort ? QApplication::tr("1 d") : QApplication::tr("1 Day"); + } + return useShort ? QApplication::tr("N/A") : QApplication::tr("Unknown"); +} + +QString toShortString(Factor factor) +{ + return toString(factor, true); +} + +int majorInterval(ZoomFactor factor) +{ + switch (factor) + { + case ZoomFactor::FifteenSeconds: + return 3000; + case ZoomFactor::ThirtySeconds: + return 5000; + case ZoomFactor::SixtySeconds: + return 10000; + case ZoomFactor::TwoMinutes: + return 15000; + case ZoomFactor::ThreeMinutes: + return 30000; + case ZoomFactor::FiveMinutes: + return 60000; + case ZoomFactor::TenMinutes: + return 60000; + case ZoomFactor::FifteenMinutes: + return 180000; + case ZoomFactor::ThirtyMinutes: + return 300000; + case ZoomFactor::SixtyMinutes: + return 600000; + case ZoomFactor::TwoHours: + return 900000; + case ZoomFactor::FourHours: + return 1800000; + case ZoomFactor::EightHours: + return 3600000; + case ZoomFactor::TwelveHours: + return 5400000; + case ZoomFactor::OneDay: + return 7200000; + } + return -1; +} + +int mediumInterval(ZoomFactor factor) +{ + switch (factor) + { + case ZoomFactor::FifteenSeconds: + return -1; + case ZoomFactor::ThirtySeconds: + return -1; + case ZoomFactor::SixtySeconds: + return 5000; + case ZoomFactor::TwoMinutes: + return 6000; + case ZoomFactor::ThreeMinutes: + return 10000; + case ZoomFactor::FiveMinutes: + return 15000; + case ZoomFactor::TenMinutes: + return 20000; + case ZoomFactor::FifteenMinutes: + return 60000; + case ZoomFactor::ThirtyMinutes: + return 100000; + case ZoomFactor::SixtyMinutes: + return 150000; + case ZoomFactor::TwoHours: + return 300000; + case ZoomFactor::FourHours: + return 600000; + case ZoomFactor::EightHours: + return 900000; + case ZoomFactor::TwelveHours: + return 1800000; + case ZoomFactor::OneDay: + return 2400000; + } + return -1; +} + +int minorInterval(ZoomFactor factor) +{ + switch (factor) + { + case ZoomFactor::FifteenSeconds: + return 1000; + case ZoomFactor::ThirtySeconds: + return 1000; + case ZoomFactor::SixtySeconds: + return 1000; + case ZoomFactor::TwoMinutes: + return 3000; + case ZoomFactor::ThreeMinutes: + return 5000; + case ZoomFactor::FiveMinutes: + return 5000; + case ZoomFactor::TenMinutes: + return 10000; + case ZoomFactor::FifteenMinutes: + return 20000; + case ZoomFactor::ThirtyMinutes: + return 25000; + case ZoomFactor::SixtyMinutes: + return 50000; + case ZoomFactor::TwoHours: + return 60000; + case ZoomFactor::FourHours: + return 150000; + case ZoomFactor::EightHours: + return 300000; + case ZoomFactor::TwelveHours: + return 600000; + case ZoomFactor::OneDay: + return 800000; + } + return -1; +} + +} + +} diff --git a/Lts/Common/LtsTimeline/timeline_utils.h b/Lts/Common/LtsTimeline/timeline_utils.h new file mode 100755 index 000000000..2b710c471 --- /dev/null +++ b/Lts/Common/LtsTimeline/timeline_utils.h @@ -0,0 +1,83 @@ +#pragma once + +#include + +#include + + +namespace LtsTimeline +{ + +struct LTSTIMELINE_EXPORT Tick +{ + int pos{-1}; + qint64 time{0}; + QString label; + Tick() {} + explicit Tick(qint64 time) : time(time) {} + explicit Tick(int pos, qint64 time, const QString &label) : pos(pos), time(time), label(label) {} +}; + +typedef QVector Ticks; + + + + + +namespace DateTime +{ + +enum Format { DTG = 0, ISODate = Qt::ISODate, Standard = 2, DTGShort = 3, DTGVeryShort, TimeOnly = 5 }; + +extern int numberOfDateTimeFormats(); + +extern LTSTIMELINE_EXPORT QString toString(qint64 time, Format format); + +} + +typedef DateTime::Format DateTimeFormat; + + + + + +namespace Zoom +{ + +enum Factor : int +{ + FifteenSeconds = 15 * 1000, ThirtySeconds = 30 * 1000, SixtySeconds = 60 * 1000, + TwoMinutes = 2 * 60 * 1000, ThreeMinutes = 3 * 60 * 1000, FiveMinutes = 5 * 60 * 1000, + TenMinutes = 10 * 60 * 1000, FifteenMinutes = 15 * 60 * 1000, ThirtyMinutes = 30 * 60 * 1000, SixtyMinutes = 60 * 60 * 1000, + TwoHours = 2 * 60 * 60 * 1000, FourHours = 4 * 60 * 60 * 1000, EightHours = 8 * 60 * 60 * 1000, TwelveHours = 12 * 60 * 60 * 1000, + OneDay = 24 * 60 * 60 * 1000 +}; + +extern LTSTIMELINE_EXPORT int numberOfZoomFactors(); + +extern LTSTIMELINE_EXPORT Factor increment(Factor factor); + +extern LTSTIMELINE_EXPORT Factor decrement(Factor factor); + +extern LTSTIMELINE_EXPORT Factor ceil(int value); + +extern LTSTIMELINE_EXPORT Factor floor(int value); + +extern LTSTIMELINE_EXPORT QVector availableZoomFactors(Factor min, Factor max); + +extern LTSTIMELINE_EXPORT QString toString(Factor factor, bool useShort = false); + +extern LTSTIMELINE_EXPORT QString toShortString(Factor factor); + +extern LTSTIMELINE_EXPORT int majorInterval(Factor factor); + +extern LTSTIMELINE_EXPORT int mediumInterval(Factor factor); + +extern LTSTIMELINE_EXPORT int minorInterval(Factor factor); + +} + +typedef Zoom::Factor ZoomFactor; +typedef QVector ZoomFactors; + +} diff --git a/Lts/Common/LtsTimeline/timeline_view.cpp b/Lts/Common/LtsTimeline/timeline_view.cpp new file mode 100755 index 000000000..b02f46d36 --- /dev/null +++ b/Lts/Common/LtsTimeline/timeline_view.cpp @@ -0,0 +1,672 @@ + + + +#include "timeline_view.h" +#include "item_delegate.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace LtsTimeline +{ + +const int TimelineView::s_textHeight = 20; + + +TimelineView::TimelineView(QWidget *parent) + : QTreeView(parent) + , m_header(new TimelineHeaderView(this)) + , m_dragHandler(new TimelineViewDragHandler(this)) + , m_resizeHandler(new TimelineViewResizeHandler(this)) + , m_selectHandler(new TimelineViewSelectHandler(this)) + , m_trackHandler(new TimelineViewTrackHandler(this)) + , m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Timeline.TimelineView")) +{ + setAlternatingRowColors(true); + setEditTriggers(QAbstractItemView::NoEditTriggers); + setSelectionBehavior(QAbstractItemView::SelectItems); + setSelectionMode(QAbstractItemView::NoSelection); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + setHeader(m_header); + setAcceptDrops(true); +} + + + + +void TimelineView::setHeader(QHeaderView *header) +{ + try + { + if (m_header && m_header != header) + delete m_header; + m_header = qobject_cast(header); + QTreeView::setHeader(m_header); + connect(m_header, &TimelineHeaderView::indicatorTimeChanged, this, [this]() + { + viewport()->update(); + }); + connect(m_header, &TimelineHeaderView::indicatorVisibilityChanged, this, [this]() + { + viewport()->update(); + }); + connect(m_header, &TimelineHeaderView::hoverPositionChanged, this, [this]() + { + if (m_model != nullptr) + emit m_model->layoutChanged(); + }); + connect(m_header, &TimelineHeaderView::hoverVisibilityChanged, this, [this]() + { + viewport()->update(); + }); + } + catch (...) + { + ERROR_LOG(m_logger) << "header view should be of type TimelineHeaderView"; + } +} + + +void TimelineView::setModel(QAbstractItemModel *model) +{ + m_model = model; + QTreeView::setModel(model); + setMouseTracking(true); + + + m_timelineModel = qobject_cast(model); + + if (m_timelineModel != nullptr) + return; + + + m_proxyModel = qobject_cast(model); + if (m_proxyModel != nullptr) + m_timelineModel = qobject_cast(m_proxyModel->sourceModel()); + + if (m_timelineModel == nullptr) + qWarning() << "TimelineSelectHandler:c_tor: model is not a QAbstractItemModel!"; + + Q_ASSERT(m_timelineModel != nullptr); +} + + +void TimelineView::setProxyModel(QAbstractProxyModel *proxy) noexcept(false) +{ + if (!qobject_cast(proxy->sourceModel())) + throw QStringLiteral("source model cannot be cast to TimelineModel"); + + m_model = qobject_cast(proxy->sourceModel()); + m_proxyModel = proxy; + QTreeView::setModel(m_proxyModel); +} + + +void TimelineView::setItemDelegate(QAbstractItemDelegate *delegate) +{ + try + { + m_delegate = static_cast(delegate); + QTreeView::setItemDelegate(m_delegate); + } + catch (...) + { + ERROR_LOG(m_logger) << "item delegate for TimelineView must be derived from ItemDelegate"; + } +} + + +void TimelineView::setZoomFactor(ZoomFactor zoomFactor) +{ + if (zoomFactor == m_zoomFactor) + return; + + if (m_minZoomFactor > zoomFactor) + { + setZoomFactor(m_minZoomFactor); + return; + } + + if (m_upperZoomFactor < zoomFactor) + { + setZoomFactor(m_upperZoomFactor); + return; + } + + m_zoomFactor = zoomFactor; + emit zoomChanged(m_zoomFactor); + if (m_currentTime + m_zoomFactor > endTime()) + setCurrentTime(endTime() - m_zoomFactor); + else + viewport()->update(); +} + +void TimelineView::setMinimumZoomFactor(ZoomFactor zoomFactor) +{ + if (zoomFactor == m_minZoomFactor) + return; + + m_minZoomFactor = zoomFactor; + if (m_zoomFactor < m_minZoomFactor) + setZoomFactor(m_minZoomFactor); +} + +void TimelineView::setMaximumZoomFactor(ZoomFactor zoomFactor) +{ + if (zoomFactor == m_maxZoomFactor) + return; + + m_maxZoomFactor = zoomFactor; + if (m_upperZoomFactor > m_maxZoomFactor) + m_upperZoomFactor = m_maxZoomFactor; + if (m_zoomFactor > m_maxZoomFactor) + setZoomFactor(m_maxZoomFactor); +} + + +void TimelineView::setStartTime(qint64 time) +{ + if (time == m_startTime || time < 0) + return; + + m_startTime = time; + if (m_currentTime < m_startTime) + setCurrentTime(m_startTime); + if (m_duration > 0) + if (m_currentTime > endTime() - m_zoomFactor) + setCurrentTime(std::max(endTime() - m_zoomFactor, m_startTime)); + emit rangeChanged(m_startTime, endTime()); +} + +void TimelineView::setCurrentTime(qint64 time) +{ + if (time == m_currentTime) + return; + + if (time < m_startTime) + time = m_startTime; + if (m_duration > 0) + if (time > endTime() - m_zoomFactor) + time = std::max(endTime() - m_zoomFactor, m_startTime); + + if (time == m_currentTime) + return; + + m_currentTime = time; + emit currentTimeChanged(m_currentTime); + viewport()->update(); +} + +void TimelineView::setEndTime(qint64 time) +{ + if (time == m_startTime + m_duration) + return; + + if (time - m_startTime < m_minZoomFactor) + time = m_startTime + m_minZoomFactor; + + setDuration(time - m_startTime); +} + +void TimelineView::setDuration(int duration) +{ + if (duration == m_duration) + return; + + if (duration <= m_minZoomFactor) + duration = m_minZoomFactor; + m_duration = duration; + emit rangeChanged(m_startTime, endTime()); + ZoomFactor upperZoomFactor = Zoom::floor(duration); + m_upperZoomFactor = std::min(upperZoomFactor, m_maxZoomFactor); + if (duration < m_zoomFactor) + setZoomFactor(m_upperZoomFactor); + if (m_currentTime > endTime()) + setCurrentTime(endTime() - m_zoomFactor); + viewport()->update(); +} + +void TimelineView::setRange(qint64 start, qint64 end) +{ + if (start == m_startTime && end - start == m_duration) + return; + + if (start > end) + { + m_startTime = end; + m_duration = std::max(start - m_startTime, static_cast(m_minZoomFactor)); + } + else + { + m_startTime = start; + m_duration = std::max(end - m_startTime, static_cast(m_minZoomFactor)); + } + if (m_currentTime < m_startTime) + m_currentTime = m_startTime; + if (m_duration > 0) + if (m_currentTime > endTime() - m_zoomFactor) + m_currentTime = std::max(endTime() - m_zoomFactor, m_startTime); + ZoomFactor upperZoomFactor = Zoom::floor(m_duration); + m_upperZoomFactor = upperZoomFactor > m_maxZoomFactor ? m_maxZoomFactor : upperZoomFactor; + emit rangeChanged(m_startTime, endTime()); + viewport()->update(); +} + + +void TimelineView::setCursorTextVisible(bool visible) +{ + m_isCursorTextVisible = visible; + viewport()->update(); +} + +void TimelineView::setCursorText(const QString &text) +{ + m_cursorText = text; + viewport()->update(); +} + + +int TimelineView::toPixel(qint64 time) const +{ + return m_header ? m_header->toPixel(time) : -1; +} + +qint64 TimelineView::toTime(int pixel) const +{ + return m_header ? m_header->toTime(pixel) : -1; +} + +qint64 TimelineView::toDuration(int pixel) const +{ + if (!m_header) + return -1; + + int pos = m_header->sectionPosition(Section::eItems); + return double(pixel - pos) / (geometry().width() - pos) * m_zoomFactor; +} + +int TimelineView::fromDuration(qint64 time) const +{ + if (!m_header) + return -1; + + return (time - m_startTime) / m_zoomFactor * (geometry().width() - m_header->sectionPosition(Section::eItems)); +} + + +void TimelineView::clearSelectedItems() +{ + m_selectedItemIds.clear(); + emit selectedItemsChanged(m_selectedItemIds); + viewport()->update(); +} + +void TimelineView::selectAllItems() +{ + if (m_model == nullptr) + return; + + m_selectedItemIds.clear(); + m_selectedItemIds = m_timelineModel->itemIds(); + emit selectedItemsChanged(m_selectedItemIds); + viewport()->update(); +} + +void TimelineView::selectItem(int itemId, bool reset) +{ + if (reset) + m_selectedItemIds.clear(); + else if (m_selectedItemIds.contains(itemId)) + return; + m_selectedItemIds.append(itemId); + emit selectedItemsChanged(m_selectedItemIds); + viewport()->update(); +} + +void TimelineView::selectItems(const QVector &itemIds, bool reset) +{ + if (reset) + m_selectedItemIds.clear(); + for (const int &itemId : qAsConst(itemIds)) + if (!m_selectedItemIds.contains(itemId)) + m_selectedItemIds.append(itemId); + emit selectedItemsChanged(m_selectedItemIds); + viewport()->update(); +} + +void TimelineView::unselectItem(int itemId) +{ + const int iItem = m_selectedItemIds.indexOf(itemId); + if (iItem == -1) + return; + m_selectedItemIds.remove(iItem); + emit selectedItemsChanged(m_selectedItemIds); + viewport()->update(); +} + + +bool TimelineView::isCursorWithinRangeX(int x) const +{ + if (m_header) + return m_header->sectionPosition(Section::eItems) < x && x < geometry().right(); + return false; +} + +bool TimelineView::isCursorWithinRangeY(int y) const +{ + return 0 < y && y < geometry().bottom(); + +} + + + + +void TimelineView::dragEnterEvent(QDragEnterEvent *event) +{ + if (m_dragHandler) + m_dragHandler->dragEnterEvent(event); +} + +void TimelineView::dragMoveEvent(QDragMoveEvent *event) +{ + if (m_dragHandler) + m_dragHandler->dragMoveEvent(event); +} + +void TimelineView::dragLeaveEvent(QDragLeaveEvent *event) +{ + if (m_dragHandler) + m_dragHandler->dragLeaveEvent(event); +} + +void TimelineView::dropEvent(QDropEvent *event) +{ + if (m_dragHandler) + m_dragHandler->dropEvent(event); +} + +void TimelineView::mouseDoubleClickEvent(QMouseEvent *event) +{ + if (m_trackHandler) + m_trackHandler->mouseDoubleClickEvent(event); + if (m_selectHandler) + m_selectHandler->mouseDoubleClickEvent(event); +} + +void TimelineView::mouseMoveEvent(QMouseEvent *event) +{ + m_cursorPosition = event->pos(); + + + const QModelIndex index(indexAt(event->pos())); + + if (m_header) + { + m_header->setHoverPosition(isCursorWithinRange(event->pos()) ? event->pos().x() : -1); + if (m_header->mouseHandler()->isBusy()) + { + m_header->mouseHandler()->mouseMoveEvent(event); + return; + } + if (cursor() == Qt::SizeHorCursor) + { + if (m_resizeHandler) + { + if (!m_resizeHandler->isActive()) + setCursor(Qt::ArrowCursor); + } + else + setCursor(Qt::ArrowCursor); + } + } + + if (!m_model) + return; + + if (m_dragHandler && m_timelineModel->isEditable()) + if (cursor().shape() == Qt::ClosedHandCursor || cursor().shape() == Qt::ForbiddenCursor) + if (!m_dragHandler->m_dragItemIds.isEmpty() || !m_selectedItemIds.isEmpty()) + { + m_dragHandler->mouseMoveEvent(event); + return; + } + + if (m_resizeHandler && m_timelineModel->isEditable()) + if (cursor().shape() == Qt::SizeHorCursor) + if (m_resizeHandler->isActive()) + { + m_resizeHandler->mouseMoveEvent(event); + return; + } + + if (m_selectHandler) + m_selectHandler->mouseMoveEvent(event); + + if (!index.isValid() || index.column() == Section::eActuator) + { + if (m_resizeHandler && m_timelineModel->isEditable()) + if (!m_resizeHandler->isActive()) + setCursor(QCursor(Qt::ArrowCursor)); + + event->ignore(); + return; + } + + if (!m_timelineModel->isEditable()) + return; + + try + { + Track &track = m_timelineModel->track(index.data().toInt()); + Item &item = track.itemAt(toTime(event->pos().x())); + int startPos = toPixel(item.startTime()); + int endPos = toPixel(item.startTime() + item.duration()); + if (endPos - startPos < 15 || (startPos + 5 < event->pos().x() && event->pos().x() < endPos - 5)) + setCursor(QCursor(Qt::ArrowCursor)); + else + setCursor(QCursor(Qt::SizeHorCursor)); + } + catch (...) + { + setCursor(QCursor(Qt::ArrowCursor)); + } +} + +void TimelineView::mousePressEvent(QMouseEvent *event) +{ + if (!m_model) + return; + + if (m_header) + if (m_header->mouseHandler()->isBusy()) + { + m_header->mouseHandler()->mousePressEvent(event); + return; + } + + + const QModelIndex index = indexAt(event->pos()); + if (index.column() == Section::eActuator) + { + QTreeView::mousePressEvent(event); + if (m_trackHandler) + m_trackHandler->mousePressEvent(event); + return; + } + + if (m_timelineModel->isEditable() && cursor().shape() == Qt::SizeHorCursor) + { + if (m_resizeHandler) + m_resizeHandler->mousePressEvent(event); + } + else + { + if (m_selectHandler) + m_selectHandler->mousePressEvent(event); + + if (m_dragHandler && m_timelineModel->isEditable()) + { + m_dragHandler->mousePressEvent(event); + if (m_dragHandler->isActive()) + return; + } + + if (event->button() == Qt::RightButton) + emit customContextMenuRequested(event->pos()); + } +} + +void TimelineView::mouseReleaseEvent(QMouseEvent *event) +{ + if (!m_model) + return; + + if (m_header) + if (m_header->mouseHandler()->isBusy()) + { + m_header->mouseHandler()->mouseReleaseEvent(event); + return; + } + + if (m_trackHandler) + m_trackHandler->mouseReleaseEvent(event); + + if (m_dragHandler && m_timelineModel->isEditable()) + if (m_dragHandler->isActive()) + { + m_dragHandler->mouseReleaseEvent(event); + viewport()->update(); + return; + } + + if (m_resizeHandler && m_timelineModel->isEditable()) + if (m_resizeHandler->isActive()) + { + m_resizeHandler->mouseReleaseEvent(event); + viewport()->update(); + return; + } + + if (m_selectHandler) + if (m_selectHandler->isActive() || !m_timelineModel->isEditable()) + m_selectHandler->mouseReleaseEvent(event); + + viewport()->update(); +} + +void TimelineView::keyPressEvent(QKeyEvent *event) +{ + if (m_model == nullptr) + return; + if (m_timelineModel->isEditable()) + { + if (m_resizeHandler) + if (m_resizeHandler->isActive()) + return; + + if (m_dragHandler) + m_dragHandler->keyPressEvent(event); + } + if (m_selectHandler) + m_selectHandler->keyPressEvent(event); + + viewport()->update(); +} + +void TimelineView::wheelEvent(QWheelEvent *event) +{ + if (event->pos().y() <= m_header->geometry().bottom()) + m_header->wheelEvent(event); + else if (QGuiApplication::queryKeyboardModifiers() == Qt::ControlModifier) + { + if (event->angleDelta().y() < 0) + setZoomFactor(Zoom::increment(m_zoomFactor)); + else + setZoomFactor(Zoom::decrement(m_zoomFactor)); + } + else + QTreeView::wheelEvent(event); +} + +void TimelineView::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + + QWidget *viewPort = viewport(); + QPainter painter(viewPort); + + painter.save(); + QTreeView::paintEvent(event); + if (m_dragHandler) + { + ItemDelegate *delegate = qobject_cast(itemDelegate()); + if (delegate) + delegate->paintDragItems(&painter); + } + painter.restore(); + + painter.save(); + + painter.setClipping(true); + + + if (m_selectHandler) + if (m_selectHandler->isRubberBandActive()) + { + QColor color = TimelineHeaderView::highlightColor(); + painter.setPen(color); + color.setAlpha(100); + painter.setBrush(QBrush(color)); + painter.drawRect(m_selectHandler->m_rubberBand); + } + + + if (m_header->isIndicatorVisible() && m_header->indicatorPosition() != -1 && + m_header->indicatorPosition() >= m_header->sectionPosition(Section::eItems)) + { + painter.setPen(TimelineHeaderView::highlightColor()); + painter.drawLine(m_header->indicatorPosition(), 0, m_header->indicatorPosition(), geometry().bottom()); + } + + + if (m_header->isHoverVisible() && m_header->hoverPosition() != -1) + { + painter.setPen(TimelineHeaderView::highlightColor()); + painter.drawLine(m_header->hoverPosition(), 0, m_header->hoverPosition(), geometry().bottom()); + if (m_isCursorTextVisible && !m_cursorText.isEmpty()) + { + painter.setBrush(QBrush(TimelineHeaderView::backgroundColor())); + QRect rect = QRect( + m_cursorPosition.x(), m_cursorPosition.y() - s_textHeight - 2, + painter.fontMetrics().horizontalAdvance(m_cursorText) + 2, s_textHeight + 2 + ); + painter.drawRect(rect); + painter.drawText(rect.adjusted(1, 1, -1, -1), Qt::AlignCenter, m_cursorText); + } + } + + + if (m_header->isLiveIndicatorVisible() && m_header->liveIndicatorTime() != -1 && + m_currentTime <= m_header->liveIndicatorTime() && m_header->liveIndicatorTime() <= m_currentTime + m_zoomFactor) + { + painter.setPen(m_header->liveIndicatorColor()); + painter.drawLine(m_header->liveIndicatorPosition(), 0, m_header->liveIndicatorPosition(), geometry().bottom()); + } + + painter.restore(); +} + +} diff --git a/Lts/Common/LtsTimeline/timeline_view.h b/Lts/Common/LtsTimeline/timeline_view.h new file mode 100755 index 000000000..87d667bb5 --- /dev/null +++ b/Lts/Common/LtsTimeline/timeline_view.h @@ -0,0 +1,175 @@ + + + +#pragma once + +#include "timeline_view_drag_handler.h" +#include "timeline_view_resize_handler.h" +#include "timeline_view_selection_handler.h" +#include "timeline_view_track_handler.h" +#include "timeline_header_view.h" +#include "timeline_model.h" + +#include + +#include + +#include +#include + + +class QAbstractProxyModel; + + +namespace LtsTimeline +{ + +class TimelineViewDragHandler; +class TimelineViewResizeHandler; +class TimelineViewSelectHandler; +class TimelineViewTrackHandler; +class TimelineWidget; + + +class LTSTIMELINE_EXPORT TimelineView : public QTreeView +{ + Q_OBJECT + + friend class ItemDelegate; + friend class TimelineHeaderView; + friend class TimelineViewDragHandler; + friend class TimelineViewResizeHandler; + friend class TimelineViewSelectHandler; + friend class TimelineViewTrackHandler; + friend class TimelineWidget; + +public: + static const int s_textHeight; + + + + explicit TimelineView(QWidget *parent = nullptr); + + TimelineHeaderView *header() { return m_header; } + void setHeader(QHeaderView *header); + + QAbstractItemModel *model() { return m_model; } + TimelineModel *timelineModel() { return m_timelineModel; } + void setModel(QAbstractItemModel *model) override; + + QAbstractProxyModel *proxyModel() { return m_proxyModel; } + void setProxyModel(QAbstractProxyModel *proxy) noexcept(false); + + void setItemDelegate(QAbstractItemDelegate *delegate); + ItemDelegate *itemDelegate() const { return m_delegate; } + + ZoomFactor zoomFactor() const { return m_zoomFactor; } + + + void setZoomFactor(ZoomFactor zoomFactor); + + ZoomFactor minimumZoomFactor() const { return m_minZoomFactor; } + void setMinimumZoomFactor(ZoomFactor zoomFactor); + + ZoomFactor maximumZoomFactor() const { return m_maxZoomFactor; } + void setMaximumZoomFactor(ZoomFactor zoomFactor); + + ZoomFactor upperZoomFactor() const { return m_upperZoomFactor; } + + qint64 startTime() const { return m_startTime; } + void setStartTime(qint64 time); + + qint64 currentTime() const { return m_currentTime; } + void setCurrentTime(qint64 time); + + qint64 endTime() const { return m_startTime + m_duration; } + void setEndTime(qint64 time); + + int duration() const { return m_duration; } + void setDuration(int duration); + + void setRange(qint64 start, qint64 end); + + bool isCursorTextVisible() const { return m_isCursorTextVisible; } + void setCursorTextVisible(bool visible); + void setCursorText(const QString &text); + + int toPixel(qint64 time) const; + qint64 toTime(int pixel) const; + qint64 toDuration(int pixel) const; + int fromDuration(qint64 time) const; + + const QVector &selectedItemIds() const { return m_selectedItemIds; } + bool hasSelectedItems() const { return !m_selectedItemIds.isEmpty(); } + void clearSelectedItems(); + void selectAllItems(); + void selectItem(int itemId, bool reset = true); + void selectItems(const QVector &itemIds, bool reset = true); + void unselectItem(int itemId); + + TimelineViewDragHandler *dragHandler() { return m_dragHandler; } + void setDragHandler(TimelineViewDragHandler *dragHandler) { m_dragHandler = dragHandler; } + TimelineViewResizeHandler *resizeHandler() { return m_resizeHandler; } + void setResizeHandler(TimelineViewResizeHandler *resizeHandler) { m_resizeHandler = resizeHandler; } + TimelineViewSelectHandler *selectHandler() { return m_selectHandler; } + void setSelectHandler(TimelineViewSelectHandler *selectHandler) { m_selectHandler = selectHandler; } + TimelineViewTrackHandler *trackHandler() { return m_trackHandler; } + void setTrackHandler(TimelineViewTrackHandler *trackHandler) { m_trackHandler = trackHandler; } + + bool isCursorWithinRange(const QPoint &pos) const { return isCursorWithinRangeX(pos.x()) && isCursorWithinRangeY(pos.y()); } + bool isCursorWithinRangeX(int x) const; + bool isCursorWithinRangeY(int y) const; + +protected: + void dragEnterEvent(QDragEnterEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dragLeaveEvent(QDragLeaveEvent *event) override; + void dropEvent(QDropEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + void wheelEvent(QWheelEvent *event) override; + virtual void paintEvent(QPaintEvent *event) override; + +signals: + void rangeChanged(qint64, qint64); + void currentTimeChanged(qint64); + void zoomChanged(ZoomFactor); + void trackActivated(int); + void selectedItemsChanged(const QVector &); + void itemActivated(int); + void currentItemTimeChanged(); + +private: + TimelineHeaderView *m_header{nullptr}; + TimelineModel *m_timelineModel{nullptr}; + QAbstractItemModel *m_model{nullptr}; + QAbstractProxyModel *m_proxyModel{nullptr}; + ItemDelegate *m_delegate{nullptr}; + TimelineViewDragHandler *m_dragHandler{nullptr}; + TimelineViewResizeHandler *m_resizeHandler{nullptr}; + TimelineViewSelectHandler *m_selectHandler{nullptr}; + TimelineViewTrackHandler *m_trackHandler{nullptr}; + + ZoomFactor m_zoomFactor{ZoomFactor::ThirtyMinutes}; + ZoomFactor m_upperZoomFactor{ZoomFactor::OneDay}; + ZoomFactor m_minZoomFactor{ZoomFactor::FifteenSeconds}; + ZoomFactor m_maxZoomFactor{ZoomFactor::OneDay}; + + qint64 m_startTime{-1}; + qint64 m_currentTime{-1}; + int m_duration{-1}; + + bool m_isCursorTextVisible{false}; + QString m_cursorText; + QPoint m_cursorPosition; + + QVector m_selectedItemIds; + + NGL::INGLLogger &m_logger; + +}; + +} diff --git a/Lts/Common/LtsTimeline/timeline_view_drag_handler.cpp b/Lts/Common/LtsTimeline/timeline_view_drag_handler.cpp new file mode 100755 index 000000000..d2d5c2bf9 --- /dev/null +++ b/Lts/Common/LtsTimeline/timeline_view_drag_handler.cpp @@ -0,0 +1,505 @@ +#include "timeline_view_drag_handler.h" +#include "item.h" +#include "track.h" + +#include +#include +#include +#include +#include + + +namespace LtsTimeline +{ + +TimelineViewDragHandler::TimelineViewDragHandler(TimelineView *view) + : m_view(view) +{} + +TimelineViewDragHandler::~TimelineViewDragHandler() +{ + m_view = nullptr; +} + + + + +void TimelineViewDragHandler::dragEnterEvent(QDragEnterEvent *e) +{ + if (!m_view->timelineModel()->isEditable() || e->source() != m_view || m_dragItemIds.isEmpty() || !m_dragStartIndex.isValid()) + { + e->ignore(); + return; + } + if (e->mimeData()->hasFormat(Item::mimetype)) + e->acceptProposedAction(); + else + e->ignore(); +} + +void TimelineViewDragHandler::dragMoveEvent(QDragMoveEvent *e) +{ + if (!m_view->timelineModel()->isEditable() || e->source() != m_view || m_dragItemIds.isEmpty() || !m_dragStartIndex.isValid()) + { + e->ignore(); + return; + } + if (!e->mimeData()->hasFormat(Item::mimetype)) + return; + + m_dragPos = e->pos(); + m_view->m_header->setHoverPosition(m_dragPos.x()); + e->setAccepted(canDragItemsTo(e->pos())); + m_view->viewport()->update(); +} + +void TimelineViewDragHandler::dragLeaveEvent(QDragLeaveEvent *e) +{ + Q_UNUSED(e); + m_dragPos = {}; +} + +void TimelineViewDragHandler::dropEvent(QDropEvent *e) +{ + if (!m_view->timelineModel()->isEditable() || e->source() != m_view || m_dragItemIds.isEmpty()) + { + e->ignore(); + return; + } + if (!e->mimeData()->hasFormat(Item::mimetype)) + return; + + if (!canDragItemsTo(e->pos())) + { + clear(); + e->acceptProposedAction(); + return; + } + + qint64 dt = deltaTime(); + try + { + for (const int &itemId : qAsConst(m_dragItemIds)) + { + const Item &item = m_view->timelineModel()->item(itemId); + m_view->timelineModel()->moveItemToTrack(itemId, dragItemTrackId(item), item.startTime() + dt); + } + } + catch (const QString &str) { ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " " << str; } + catch (...) { ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " failed"; } + clear(); + e->acceptProposedAction(); +} + +void TimelineViewDragHandler::mouseMoveEvent(QMouseEvent *e) +{ + if (!m_view->timelineModel()->isEditable()) + return; + + if (e->buttons() != Qt::LeftButton) + { + clear(); + return; + } + + if (m_dragItemIds.isEmpty() || !m_dragStartIndex.isValid()) + return; + + if ((e->pos() - m_dragStartPos).manhattanLength() < QApplication::startDragDistance()) + return; + + + const QModelIndex index = m_view->indexAt(e->pos()); + if (!index.isValid() || index.column() == Section::eActuator) + { + m_view->setCursor(QCursor(Qt::ForbiddenCursor)); + return; + } + + m_dragPos = e->pos(); + m_view->setCursor(QCursor(canDragItemsTo(m_dragPos) ? Qt::ClosedHandCursor : Qt::ForbiddenCursor)); + if (index.data().toInt() == m_dragStartIndex.data().toInt()) + return; + + QMimeData *mimeData = new QMimeData(); + mimeData->setData(Item::mimetype, {}); + QDrag *drag = new QDrag(m_view); + drag->setMimeData(mimeData); + if (drag->exec(Qt::MoveAction) == Qt::MoveAction) + { + + } + else + { + + } +} + +void TimelineViewDragHandler::mousePressEvent(QMouseEvent *e) +{ + if (!m_view->timelineModel()->isEditable()) + return; + + if (e->button() != Qt::LeftButton) + return; + + + const QModelIndex index = m_view->indexAt(e->pos()); + if (!index.isValid() || index.column() == Section::eActuator) + return; + + try + { + Track &track = m_view->timelineModel()->track(index.data().toInt()); + Item &item = track.itemAt(m_view->toTime(e->pos().x())); + m_dragItemIds = m_view->m_selectedItemIds; + if (!m_dragItemIds.contains(item.id())) + m_dragItemIds = { item.id() }; + + for (const int &itemId : m_dragItemIds) + if (!m_view->timelineModel()->item(itemId).isEditable()) + return; + + m_dragStartIndex = index; + m_dragStartTime = m_view->toTime(e->pos().x()); + m_dragStartPos = e->pos(); + m_dragPos = m_dragStartPos; + m_view->setCursor(QCursor(Qt::ClosedHandCursor)); + } + catch (const QString &str) { DEBUG_LOG(m_view->m_logger) << LOGFUNCTION << " " << str; } + catch (...) {} +} + +void TimelineViewDragHandler::mouseReleaseEvent(QMouseEvent *e) +{ + if (!m_view->timelineModel()->isEditable()) + return; + + if (e->button() != Qt::LeftButton) + return; + + if (m_dragItemIds.isEmpty() || !m_dragStartIndex.isValid()) + return; + + + const QModelIndex index = m_view->indexAt(e->pos()); + if (!index.isValid() || index.column() == Section::eActuator) + return; + + if (index.data().toInt() != m_dragStartIndex.data().toInt()) + return; + + m_dragPos = e->pos(); + if (m_dragPos.x() == m_dragStartPos.x()) + { + clear(); + return; + } + if (!canMoveItemsTo(m_dragPos)) + { + clear(); + return; + } + + emit m_view->currentItemTimeChanged(); + qint64 dt = deltaTime(); + for (const int &itemId : m_dragItemIds) + { + Item &item = m_view->timelineModel()->item(itemId); + m_view->timelineModel()->setItemStartTime(item, item.startTime() + dt); + } + clear(); +} + +void TimelineViewDragHandler::keyPressEvent(QKeyEvent *e) +{ + if (!m_view->timelineModel()->isEditable()) + return; + if (m_view->m_selectedItemIds.isEmpty()) + return; + + if (e->key() == Qt::Key_Escape) + { + clear(); + return; + } + + if (e->key() != Qt::Key_Left && e->key() != Qt::Key_Right) + return; + + int dt = m_view->timelineModel()->snapStep(); + if (e->key() == Qt::Key_Left) + moveItemsLeft(m_view->selectedItemIds(), QApplication::keyboardModifiers().testFlag(Qt::ControlModifier) ? -dt : -10 * dt); + else + moveItemsRight(m_view->selectedItemIds(), QApplication::keyboardModifiers().testFlag(Qt::ControlModifier) ? dt : 10 * dt); +} + + +bool TimelineViewDragHandler::isActive() const +{ + return !m_dragItemIds.isEmpty() && (m_dragPos - m_dragStartPos).manhattanLength() >= QApplication::startDragDistance(); +} + + +int TimelineViewDragHandler::deltaX() const +{ + if (!m_view->timelineModel()->isSnapEnabled()) + return m_dragPos.x() - m_dragStartPos.x(); + return m_view->toPixel(m_dragStartTime + deltaTime()) - m_view->toPixel(m_dragStartTime); +} + +int TimelineViewDragHandler::deltaY() const +{ + + const QModelIndex dragIndex = m_view->indexAt(m_dragPos); + if (!dragIndex.isValid()) + return m_dragPos.y() - m_dragStartPos.y(); + return m_view->visualRect(dragIndex).top() - m_view->visualRect(m_dragStartIndex).top(); +} + +qint64 TimelineViewDragHandler::deltaTime() const +{ + if (!m_view->timelineModel()->isSnapEnabled()) + return m_view->toTime(m_dragPos.x()) - m_view->toTime(m_dragStartPos.x()); + + qint64 dt = ((m_view->toTime(m_dragPos.x()) - m_dragStartTime) / m_view->timelineModel()->snapStep()) * m_view->timelineModel()->snapStep(); + int dtAbsMin = 2 * m_view->timelineModel()->snapTolerance(); + int eps = m_view->timelineModel()->snapTolerance(); + for (const int &dragItemId : m_dragItemIds) + { + const Item &dragItem = m_view->timelineModel()->item(dragItemId); + qint64 start = dragItem.startTime() + dt; + qint64 end = start + dragItem.duration(); + for (int itemId : m_view->timelineModel()->itemIds()) + { + if (m_dragItemIds.contains(itemId)) + continue; + + const Item &item = m_view->timelineModel()->item(itemId); + if (start - eps < item.startTime() + item.duration() && item.startTime() + item.duration() < start + eps) + { + qint64 dtCurrent = item.startTime() + item.duration() - dragItem.startTime(); + qint64 dtAbsCurrent = std::abs(dtCurrent); + if (dtAbsCurrent < dtAbsMin) + { + dtAbsMin = dtAbsCurrent; + dt = dtCurrent; + } + } + else if (end - eps < item.startTime() && item.startTime() < end + eps) + { + qint64 dtCurrent = item.startTime() - (dragItem.startTime() + dragItem.duration()); + qint64 dtAbsCurrent = std::abs(dtCurrent); + if (dtAbsCurrent < dtAbsMin) + { + dtAbsMin = dtAbsCurrent; + dt = dtCurrent; + } + } + } + } + return dt; +} + + + + +bool TimelineViewDragHandler::canDragItemsTo(const QPoint &point) const +{ + if (!m_view->timelineModel()->isEditable()) + return false; + + if (m_dragItemIds.isEmpty() || !m_dragStartIndex.isValid()) + return false; + + + const QModelIndex index = m_view->indexAt(point); + if (!index.isValid() || index.column() == Section::eActuator) + return false; + + if (index.data().toInt() == m_dragStartIndex.data().toInt()) + return canMoveItemsTo(point); + else + { + try + { + qint64 dt = deltaTime(); + for (const int &itemId : m_dragItemIds) + { + const Item &item = m_view->timelineModel()->item(itemId); + int trackId = dragItemTrackId(item); + if (trackId == 0) + return false; + + const Track &track = m_view->timelineModel()->track(trackId); + qint64 startNew = item.startTime() + dt; + if (!m_view->timelineModel()->canDropItem(item, track.id(), startNew)) + return false; + + if (track.isItemsBetween(startNew, startNew + item.duration(), m_dragItemIds, true)) + return false; + } + } + catch (...) + { + return true; + } + } + return true; +} + +bool TimelineViewDragHandler::canMoveItemsTo(const QPoint &point) const +{ + if (!m_view->timelineModel()->isEditable()) + return false; + if (m_dragItemIds.isEmpty() || !m_dragStartIndex.isValid()) + return false; + + + const QModelIndex index = m_view->indexAt(point); + if (!index.isValid() || index.column() == Section::eActuator || index.data().toInt() != m_dragStartIndex.data().toInt()) + return false; + + qint64 dt = deltaTime(); + for (const int &itemId : m_dragItemIds) + { + const Item &item = m_view->timelineModel()->item(itemId); + qint64 startNew = item.startTime() + dt; + if (!m_view->timelineModel()->canMoveItem(item, startNew)) + return false; + + const Track &track = m_view->timelineModel()->track(item.parentTrackId()); + if (track.isItemsBetween(startNew, startNew + item.duration(), m_dragItemIds, true)) + return false; + } + return true; +} + + +void TimelineViewDragHandler::moveItemsLeft(const QVector &itemIds, int dt) +{ + Q_ASSERT(dt < 0); + try + { + for (int itemId : itemIds) + { + const Item &item = m_view->timelineModel()->item(itemId); + const Track &track = m_view->timelineModel()->track(item.parentTrackId()); + if (track.isItemsBetween(item.startTime() + dt, item.startTime() + item.duration() + dt, itemIds, true)) + { + if (dt < - m_view->timelineModel()->snapStep()) + moveItemsLeft(itemIds, dt + m_view->timelineModel()->snapStep()); + return; + } + if (!m_view->timelineModel()->canMoveItem(item, item.startTime() + dt)) + { + if (dt < - m_view->timelineModel()->snapStep()) + moveItemsLeft(itemIds, dt + m_view->timelineModel()->snapStep()); + return; + } + } + } + catch (const QString &errMsg) + { + ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " failed: " << errMsg; + return; + } + catch (...) + { + ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " failed"; + return; + } + + try + { + for (int itemId : itemIds) + { + Item &item = m_view->timelineModel()->item(itemId); + m_view->timelineModel()->setItemStartTime(item, item.startTime() + dt); + } + } + catch (const QString &str) { ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " " << str; } + catch (...) {} +} + +void TimelineViewDragHandler::moveItemsRight(const QVector &itemIds, int dt) +{ + Q_ASSERT(dt > 0); + try + { + for (int itemId : itemIds) + { + const Item &item = m_view->timelineModel()->item(itemId); + const Track &track = m_view->timelineModel()->track(item.parentTrackId()); + if (track.isItemsBetween(item.startTime() + dt, item.startTime() + item.duration() + dt, itemIds, true)) + { + if (dt > m_view->timelineModel()->snapStep()) + moveItemsRight(itemIds, dt - m_view->timelineModel()->snapStep()); + return; + } + if (!m_view->timelineModel()->canMoveItem(item, item.startTime() + dt)) + { + if (dt > m_view->timelineModel()->snapStep()) + moveItemsRight(itemIds, dt - m_view->timelineModel()->snapStep()); + return; + } + } + } + catch (const QString &errMsg) + { + ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " failed: " << errMsg; + return; + } + catch (...) + { + ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " failed"; + return; + } + + try + { + for (int itemId : itemIds) + { + Item &item = m_view->timelineModel()->item(itemId); + m_view->timelineModel()->setItemStartTime(item, item.startTime() + dt); + } + } + catch (const QString &errMsg) { ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " " << errMsg; } + catch (...) {} +} + + +int TimelineViewDragHandler::dragItemTrackId(const Item &item) const +{ + int dy = deltaY(); + + const QModelIndex index = m_view->indexAt(m_dragStartPos + QPoint(0, dy)); + if (index.data().toInt() == m_dragStartIndex.data().toInt()) + return item.parentTrackId(); + + try + { + + const QModelIndex originalIndex = m_view->timelineModel()->indexOfTrack(item.parentTrackId()); + + const QModelIndex newIndex = m_view->indexAt(m_view->visualRect(originalIndex).center() + QPoint(0, dy)); + return newIndex.data().toInt(); + } + catch (...) + { + return 0; + } +} + +void TimelineViewDragHandler::clear() +{ + m_dragStartIndex = {}; + m_dragStartTime = -1; + m_dragStartPos = {}; + m_dragPos = {}; + m_dragItemIds.clear(); + m_view->setCursor(QCursor(Qt::ArrowCursor)); + m_view->viewport()->update(); +} + +} diff --git a/Lts/Common/LtsTimeline/timeline_view_drag_handler.h b/Lts/Common/LtsTimeline/timeline_view_drag_handler.h new file mode 100755 index 000000000..409c1d3c9 --- /dev/null +++ b/Lts/Common/LtsTimeline/timeline_view_drag_handler.h @@ -0,0 +1,82 @@ +#pragma once + +#include "timeline_view.h" + +#include + +#include +#include +#include + + +class QDragEnterEvent; +class QDragMoveEvent; +class QDragLeaveEvent; +class QDropEvent; +class QMouseEvent; +class QKeyEvent; + + +namespace LtsTimeline +{ + +class Item; +class Track; +class TimelineView; +class ItemDelegate; + + +class LTSTIMELINE_EXPORT TimelineViewDragHandler +{ + friend class TimelineView; + friend class ItemDelegate; + +public: + explicit TimelineViewDragHandler(TimelineView *view); + virtual ~TimelineViewDragHandler(); + + virtual void dragEnterEvent(QDragEnterEvent *e); + virtual void dragMoveEvent(QDragMoveEvent *e); + virtual void dragLeaveEvent(QDragLeaveEvent *e); + virtual void dropEvent(QDropEvent *e); + virtual void mouseMoveEvent(QMouseEvent *e); + virtual void mousePressEvent(QMouseEvent *e); + virtual void mouseReleaseEvent(QMouseEvent *e); + virtual void keyPressEvent(QKeyEvent *e); + + virtual bool isActive() const; + + virtual int deltaX() const; + virtual int deltaY() const; + virtual qint64 deltaTime() const; + + + const QVector &dragItemIds() const { return m_dragItemIds; } + const QModelIndex &dragStartIndex() const { return m_dragStartIndex; } + qint64 dragStartTime() const { return m_dragStartTime; } + const QPoint &dragStartPos() const { return m_dragStartPos; } + const QPoint &dragPos() const { return m_dragPos; } + +protected: + virtual bool canDragItemsTo(const QPoint &point) const; + virtual bool canMoveItemsTo(const QPoint &point) const; + + virtual void moveItemsLeft(const QVector &itemIds, int dt); + virtual void moveItemsRight(const QVector &itemIds, int dt); + + int dragItemTrackId(const Item &item) const; + void clear(); + +protected: + TimelineView *m_view{nullptr}; + QVector m_dragItemIds; + QModelIndex m_dragStartIndex; + qint64 m_dragStartTime{-1}; + QPoint m_dragStartPos; + QPoint m_dragPos; + + int m_snapLinePos{-1}; + +}; + +} diff --git a/Lts/Common/LtsTimeline/timeline_view_resize_handler.cpp b/Lts/Common/LtsTimeline/timeline_view_resize_handler.cpp new file mode 100755 index 000000000..9525b3f2f --- /dev/null +++ b/Lts/Common/LtsTimeline/timeline_view_resize_handler.cpp @@ -0,0 +1,279 @@ +#include "timeline_view_resize_handler.h" +#include "timeline_view.h" +#include "timeline_header_view.h" +#include "item.h" +#include "track.h" + +#include +#include +#include + + +namespace LtsTimeline +{ + +TimelineViewResizeHandler::TimelineViewResizeHandler(TimelineView *view) + : m_view(view) +{} + +TimelineViewResizeHandler::~TimelineViewResizeHandler() +{ + m_view = nullptr; +} + + + + +void TimelineViewResizeHandler::mouseMoveEvent(QMouseEvent *e) +{ + if (!m_view->timelineModel()->isEditable()) + return; + if (m_currentTrackId == -1 || m_currentItemId == -1 || m_resizePos == ResizePosition::eNone) + return; + if (!m_view->timelineModel()->item(m_currentItemId).isEditable()) + return; + + + const QModelIndex index(m_view->indexAt(e->pos())); + if (index.column() == Section::eActuator || !index.isValid()) + return; + + Track &track = m_view->timelineModel()->track(index.data().toInt()); + if (track.id() != m_currentTrackId) + return; + + qint64 time = m_view->timelineModel()->isSnapEnabled() ? globalSnapTime(e->pos().x()) : localSnapTime(e->pos().x()); + try + { + Item &item = m_view->timelineModel()->item(m_currentItemId); + if (m_resizePos == ResizePosition::eStart) + { + if (item.startTime() == time) + return; + + if (!m_view->timelineModel()->canResizeItem(item, time, item.startTime() + item.duration() - time)) + return; + + m_view->timelineModel()->setItemDuration(track, item, item.startTime() + item.duration() - time); + m_view->timelineModel()->setItemStartTime(track, item, time); + } + else + { + if (item.duration() == time - item.startTime()) + return; + + if (!m_view->timelineModel()->canResizeItem(item, item.startTime(), time - item.startTime())) + return; + + m_view->timelineModel()->setItemDuration(track, item, time - item.startTime()); + } + } + catch (...) + { + mouseReleaseEvent(e); + } +} + +void TimelineViewResizeHandler::mousePressEvent(QMouseEvent *e) +{ + if (!m_view->timelineModel()->isEditable()) + return; + + if (e->button() != Qt::LeftButton) + return; + + + const QModelIndex index(m_view->indexAt(e->pos())); + if (!index.isValid() || index.column() == Section::eActuator) + return; + + try + { + const Track &track = m_view->timelineModel()->track(index.data().toInt()); + const Item &item = track.itemAt(m_view->toTime(e->pos().x())); + if (!item.isEditable()) + return; + + const int startPos = m_view->toPixel(item.startTime()); + const int endPos = m_view->toPixel(item.startTime() + item.duration()); + if (endPos - startPos < 15 || (startPos + 5 < e->pos().x() && e->pos().x() < endPos - 5)) + { + m_resizePos = ResizePosition::eNone; + return; + } + else if (startPos + 5 >= e->pos().x()) + m_resizePos = ResizePosition::eStart; + else + m_resizePos = ResizePosition::eEnd; + + m_currentTrackId = track.id(); + m_currentItemId = item.id(); + } + catch (const QString &str) + { + ERROR_LOG(m_view->m_logger) << str; + } + catch (...) {} +} + +void TimelineViewResizeHandler::mouseReleaseEvent(QMouseEvent *e) +{ + if (!m_view->timelineModel()->isEditable()) + return; + + if (e->button() != Qt::LeftButton) + return; + + emit m_view->currentItemTimeChanged(); + m_view->setCursor(QCursor(Qt::ArrowCursor)); + m_currentTrackId = -1; + m_currentItemId = -1; + m_resizePos = ResizePosition::eNone; +} + + + + +qint64 TimelineViewResizeHandler::localSnapTime(int pos) const +{ + qint64 time = m_view->toTime(pos); + if (m_currentItemId == -1 || m_resizePos == ResizePosition::eNone) + return time; + + try + { + Track &track = m_view->timelineModel()->track(m_currentTrackId); + const Item ¤tItem = m_view->timelineModel()->item(m_currentItemId); + if (m_resizePos == ResizePosition::eStart) + { + if (time >= currentItem.startTime() + currentItem.duration()) + time = currentItem.startTime() + currentItem.duration() - m_view->timelineModel()->minimumDuration(); + else + { + try + { + const Item &previous = track.previous(m_currentItemId); + const qint64 endTime = previous.startTime() + previous.duration(); + if (time <= endTime) + time = endTime; + } + catch (const QString &str) + { + ERROR_LOG(m_view->m_logger) << str; + } + catch (...) {} + } + } + else + { + if (time <= currentItem.startTime()) + time = currentItem.startTime() + m_view->timelineModel()->minimumDuration(); + else + { + try + { + Item &next = track.next(m_currentItemId); + if (time >= next.startTime()) + time = next.startTime(); + } + catch (const QString &str) + { + ERROR_LOG(m_view->m_logger) << str; + } + catch (...) {} + } + } + } + catch (const QString &str) + { + ERROR_LOG(m_view->m_logger) << str; + } + catch (...) {} + return time; +} + +qint64 TimelineViewResizeHandler::globalSnapTime(int pos) const +{ + qint64 tPos = (m_view->toTime(pos) / m_view->timelineModel()->snapStep()) * m_view->timelineModel()->snapStep(); + if (m_currentItemId == -1 || m_resizePos == ResizePosition::eNone) + return tPos; + + qint64 tMin = m_view->m_startTime; + qint64 tMax = m_view->endTime(); + try + { + const Item &previous = m_view->timelineModel()->track(m_currentTrackId).previous(m_currentItemId); + tMin = previous.startTime() + previous.duration(); + } + catch (const QString &str) + { + DEBUG_LOG(m_view->m_logger) << str; + } + catch (...) {} + try + { + const Item &next = m_view->timelineModel()->track(m_currentTrackId).next(m_currentItemId); + tMax = next.startTime(); + } + catch (const QString &str) + { + DEBUG_LOG(m_view->m_logger) << str; + } + catch (...) {} + + qint64 time = tPos; + try + { + const Item ¤tItem = m_view->timelineModel()->item(m_currentItemId); + + int minDist = 2 * m_view->timelineModel()->snapTolerance(); + int eps = m_view->timelineModel()->snapTolerance(); + for (int itemId : m_view->timelineModel()->itemIds()) + { + if (itemId == m_currentItemId) + continue; + + try + { + const Item &item = m_view->timelineModel()->item(itemId); + qint64 t; + if (time - eps < item.startTime() && item.startTime() < time + eps) + t = item.startTime(); + else if (time - eps < item.startTime() + item.duration() && item.startTime() + item.duration() < time + eps) + t = item.startTime() + item.duration(); + else + continue; + + if (std::abs(t - tPos) < minDist) + { + if ((m_resizePos == ResizePosition::eStart && t >= currentItem.startTime() + currentItem.duration()) || + (m_resizePos == ResizePosition::eEnd && t <= currentItem.startTime())) + continue; + time = t; + minDist = time - tPos; + } + } + catch (const QString &str) + { + ERROR_LOG(m_view->m_logger) << str; + } + catch (...) {} + } + + if (m_resizePos == ResizePosition::eStart && time >= currentItem.startTime() + currentItem.duration()) + return currentItem.startTime() + currentItem.duration() - m_view->timelineModel()->minimumDuration(); + else if (m_resizePos == ResizePosition::eEnd && time <= currentItem.startTime()) + return currentItem.startTime() + m_view->timelineModel()->minimumDuration(); + } + catch (const QString &str) + { + ERROR_LOG(m_view->m_logger) << str; + } + catch (...) {} + + time = std::max(tMin, time); + time = std::min(tMax, time); + return time; +} + +} diff --git a/Lts/Common/LtsTimeline/timeline_view_resize_handler.h b/Lts/Common/LtsTimeline/timeline_view_resize_handler.h new file mode 100755 index 000000000..7fcaff9b5 --- /dev/null +++ b/Lts/Common/LtsTimeline/timeline_view_resize_handler.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include + + +namespace LtsTimeline +{ + +class Track; +class TimelineView; + + +class LTSTIMELINE_EXPORT TimelineViewResizeHandler +{ + +public: + enum class ResizePosition { eNone, eStart, eEnd }; + + explicit TimelineViewResizeHandler(TimelineView *view); + virtual ~TimelineViewResizeHandler(); + + virtual void mouseMoveEvent(QMouseEvent *e); + virtual void mousePressEvent(QMouseEvent *e); + virtual void mouseReleaseEvent(QMouseEvent *e); + + virtual bool isActive() const { return m_currentTrackId != -1 && m_currentItemId != -1 && m_resizePos != ResizePosition::eNone; } + +private: + virtual qint64 localSnapTime(int pos) const; + virtual qint64 globalSnapTime(int pos) const; + +protected: + TimelineView *m_view{nullptr}; + int m_currentTrackId{-1}; + int m_currentItemId{-1}; + ResizePosition m_resizePos{ResizePosition::eNone}; + +}; + +} diff --git a/Lts/Common/LtsTimeline/timeline_view_selection_handler.cpp b/Lts/Common/LtsTimeline/timeline_view_selection_handler.cpp new file mode 100755 index 000000000..9b14287d7 --- /dev/null +++ b/Lts/Common/LtsTimeline/timeline_view_selection_handler.cpp @@ -0,0 +1,320 @@ +#include "timeline_view_selection_handler.h" +#include "item_delegate.h" + +#include +#include +#include +#include +#include + +#include + + +namespace LtsTimeline +{ + +TimelineViewSelectHandler::TimelineViewSelectHandler(TimelineView *view) + : m_view(view) +{ +} + +TimelineViewSelectHandler::~TimelineViewSelectHandler() +{ + m_view = nullptr; +} + + + + +void TimelineViewSelectHandler::mouseDoubleClickEvent(QMouseEvent *e) +{ + if (e->button() != Qt::LeftButton) + return; + + m_view->clearSelectedItems(); + + const QModelIndex index = m_view->indexAt(e->pos()); + if (!index.isValid() || index.column() == Section::eActuator) + return; + + try + { + Track &track = m_view->timelineModel()->track(index.data().toInt()); + Item &item = track.itemAt(m_view->toTime(e->pos().x())); + emit m_view->itemActivated(item.id()); + } + catch (const QString &str) { DEBUG_LOG(m_view->m_logger) << LOGFUNCTION << " " << str; } + catch (...) { ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " failed"; } +} + +void TimelineViewSelectHandler::mouseMoveEvent(QMouseEvent *e) +{ + if (m_isRubberBandActive) + { + m_view->setCursor(QCursor(Qt::CrossCursor)); + if (!m_view->isCursorWithinRangeX(e->pos().x())) + m_rubberBand.setHeight(e->pos().y() - m_rubberBand.top()); + + + else + m_rubberBand.setRect( + m_rubberBand.left(), m_rubberBand.top(), e->pos().x() - m_rubberBand.left(), e->pos().y() - m_rubberBand.top() + ); + } + else + m_selectedItemId = -1; +} + +void TimelineViewSelectHandler::mousePressEvent(QMouseEvent *e) +{ + if (e->button() != Qt::LeftButton && e->button() != Qt::RightButton) + return; + + + const QModelIndex index = m_view->indexAt(e->pos()); + if (index.isValid()) + { + if (index.column() == Section::eActuator) + { + qWarning() << "clicked on row with actuator" << index.data().toString(); + return; + } + + try + { + Track &track = m_view->timelineModel()->track(index.data().toInt()); + Item &item = track.itemAt(m_view->toTime(e->pos().x())); + if (e->button() == Qt::LeftButton) + m_selectedItemId = item.id(); + return; + } + catch (...) + { + if (QApplication::keyboardModifiers().testFlag(Qt::ControlModifier)) + return; + + if (e->button() == Qt::RightButton) + { + emit m_view->customContextMenuRequested(e->pos()); + return; + } + + m_view->clearSelectedItems(); + m_rubberBand.setLeft(e->pos().x()); + m_rubberBand.setTop(e->pos().y()); + m_rubberBand.setSize(QSize(0, 0)); + m_isRubberBandActive = true; + return; + } + + return; + } + else + { + qWarning() << "TimelineViewSelectHandler::mousePressEvent: index invalid"; + } + + if (e->button() == Qt::RightButton) + return; + + if (QApplication::keyboardModifiers().testFlag(Qt::ControlModifier)) + return; + + m_view->clearSelectedItems(); + + if (!m_view->isCursorWithinRangeX(e->pos().x())) + return; + + m_rubberBand.setLeft(e->pos().x()); + m_rubberBand.setTop(e->pos().y()); + m_rubberBand.setSize(QSize(0, 0)); + m_isRubberBandActive = true; +} + +void TimelineViewSelectHandler::mouseReleaseEvent(QMouseEvent *e) +{ + if (e->button() == Qt::RightButton) + { + + return; + } + + if (e->button() != Qt::LeftButton) + return; + + if (m_isRubberBandActive) + { + m_view->selectItems(getItemsWithinRubberBand()); + m_isRubberBandActive = false; + m_rubberBand.setSize(QSize(0, 0)); + m_view->setCursor(QCursor(Qt::ArrowCursor)); + return; + } + else + { + + const QModelIndex index = m_view->indexAt(e->pos()); + if (!index.isValid() || index.column() == Section::eActuator) + { + m_selectedItemId = -1; + return; + } + + try + { + Track &track = m_view->timelineModel()->track(index.data().toInt()); + Item &item = track.itemAt(m_view->toTime(e->pos().x())); + if (item.id() == m_selectedItemId) + { + if (QApplication::keyboardModifiers().testFlag(Qt::ControlModifier)) + { + if (m_view->selectedItemIds().contains(m_selectedItemId)) + m_view->unselectItem(m_selectedItemId); + else + m_view->selectItem(m_selectedItemId, false); + } + else + { + if (m_view->m_selectedItemIds.size() > 1 || !m_view->m_selectedItemIds.contains(m_selectedItemId)) + m_view->selectItem(m_selectedItemId); + else + m_view->unselectItem(m_selectedItemId); + } + } + } catch (...) {} + m_selectedItemId = -1; + } +} + +void TimelineViewSelectHandler::keyPressEvent(QKeyEvent *e) +{ + if (e->key() == Qt::Key_Escape) + { + m_selectedItemId = -1; + m_view->clearSelectedItems(); + } + else if (e->key() == Qt::Key_Delete) + { + if (!m_view->m_selectedItemIds.isEmpty()) + { + for (const int &itemId : qAsConst(m_view->m_selectedItemIds)) + { + try + { + m_view->timelineModel()->removeItem(itemId); + } + catch (const QString &str) { ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " " << str; } + catch (...) { ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " failed"; } + } + m_view->clearSelectedItems(); + m_selectedItemId = -1; + } + } + else if (QApplication::keyboardModifiers().testFlag(Qt::ControlModifier)) + { + try + { + switch (e->key()) + { + case Qt::Key_A: + m_view->selectAllItems(); + m_selectedItemId = -1; + break; + + case Qt::Key_C: + if (m_view->hasSelectedItems()) + m_view->timelineModel()->copyToClipboard(m_view->selectedItemIds()); + break; + + case Qt::Key_X: + if (m_view->hasSelectedItems()) + { + m_view->timelineModel()->cutToClipboard(m_view->selectedItemIds()); + m_view->clearSelectedItems(); + } + break; + + case Qt::Key_V: + if (!m_view->timelineModel()->m_clipboard.empty()) + { + qint64 time = m_view->currentTime(); + if (m_view->m_header->isIndicatorVisible() && m_view->m_header->indicatorTime() != -1) + time = m_view->m_header->indicatorTime(); + m_view->timelineModel()->pasteFromClipboard(time); + } + break; + + default: + break; + } + } + catch (const QString &str) { ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " " << str; } + catch (...) { ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " failed"; } + } +} + + +bool TimelineViewSelectHandler::isActive() const +{ + if (QApplication::keyboardModifiers().testFlag(Qt::ControlModifier)) + return true; + if (m_isRubberBandActive) + return true; + return m_selectedItemId != -1; +} + + + + +QVector TimelineViewSelectHandler::getItemsWithinRubberBand() const +{ + const QPoint topLeft = { std::min(m_rubberBand.left(), m_rubberBand.right()), std::min(m_rubberBand.top(), m_rubberBand.bottom()) }; + const QPoint bottomRight = { std::max(m_rubberBand.left(), m_rubberBand.right()), std::max(m_rubberBand.top(), m_rubberBand.bottom()) }; + const qint64 start = m_view->toTime(topLeft.x()); + const qint64 end = m_view->toTime(bottomRight.x()); + if (start == end) + return {}; + + QVector itemsWithinRubberBand; + const int h = m_view->itemDelegate()->sizeHint({}, {}).height(); + for (int y = topLeft.y(); y <= bottomRight.y(); y += h) + { + const QPoint point = { topLeft.x(), y }; + + const QModelIndex index = m_view->indexAt(point); + if (!index.isValid()) + continue; + + try + { + const Track &track = m_view->timelineModel()->track(index.data().toInt()); + itemsWithinRubberBand += track.itemIdsBetween(start, end); + } + catch (const QString &str) + { + ERROR_LOG(m_view->m_logger) << str; + } + catch (...) {} + } + if (topLeft.y() + h > bottomRight.y()) + { + + const QModelIndex index = m_view->indexAt(bottomRight); + if (!index.isValid()) + return itemsWithinRubberBand; + + try + { + const Track &track = m_view->timelineModel()->track(index.data().toInt()); + itemsWithinRubberBand += track.itemIdsBetween(start, end); + } + catch (const QString &str) + { + ERROR_LOG(m_view->m_logger) << str; + } + catch (...) {} + } + return itemsWithinRubberBand; +} + +} diff --git a/Lts/Common/LtsTimeline/timeline_view_selection_handler.h b/Lts/Common/LtsTimeline/timeline_view_selection_handler.h new file mode 100755 index 000000000..354a2cc55 --- /dev/null +++ b/Lts/Common/LtsTimeline/timeline_view_selection_handler.h @@ -0,0 +1,48 @@ +#pragma once + +#include "timeline_view.h" + +#include + +#include + + +class QMouseEvent; +class QKeyEvent; + + +namespace LtsTimeline +{ + +class TimelineView; + + +class LTSTIMELINE_EXPORT TimelineViewSelectHandler +{ + friend class TimelineView; + +public: + explicit TimelineViewSelectHandler(TimelineView *view); + virtual ~TimelineViewSelectHandler(); + + virtual void mouseDoubleClickEvent(QMouseEvent *e); + virtual void mouseMoveEvent(QMouseEvent *e); + virtual void mousePressEvent(QMouseEvent *e); + virtual void mouseReleaseEvent(QMouseEvent *e); + virtual void keyPressEvent(QKeyEvent *e); + + virtual bool isActive() const; + virtual bool isRubberBandActive() const { return m_isRubberBandActive; } + +protected: + virtual QVector getItemsWithinRubberBand() const; + +protected: + TimelineView *m_view{nullptr}; + int m_selectedItemId{-1}; + QRect m_rubberBand; + bool m_isRubberBandActive{false}; + +}; + +} diff --git a/Lts/Common/LtsTimeline/timeline_view_track_handler.cpp b/Lts/Common/LtsTimeline/timeline_view_track_handler.cpp new file mode 100755 index 000000000..4c2098e47 --- /dev/null +++ b/Lts/Common/LtsTimeline/timeline_view_track_handler.cpp @@ -0,0 +1,107 @@ +#include "timeline_view_resize_handler.h" +#include "timeline_view.h" +#include "timeline_header_view.h" +#include "item.h" +#include "track.h" + +#include + +#include +#include +#include + + +namespace LtsTimeline +{ + +TimelineViewTrackHandler::TimelineViewTrackHandler(TimelineView *view) + : m_view(view) +{ +} + +TimelineViewTrackHandler::~TimelineViewTrackHandler() +{ + m_view = nullptr; +} + + + + +void TimelineViewTrackHandler::mouseDoubleClickEvent(QMouseEvent *e) +{ + if (e->button() != Qt::LeftButton) + return; + + + const QModelIndex index = m_view->indexAt(e->pos()); + if (!index.isValid() || index.column() != Section::eActuator) + return; + + emit m_view->trackActivated(index.data().toInt()); +} + +void TimelineViewTrackHandler::mousePressEvent(QMouseEvent *e) +{ + if (e->button() != Qt::LeftButton) + { + if (e->button() == Qt::RightButton) + emit m_view->customContextMenuRequested(e->pos()); + return; + } + + + const QModelIndex index = m_view->indexAt(e->pos()); + if (!index.isValid() || index.column() != Section::eActuator) + return; + + try + { + if(!m_view->timelineModel()->isEditable()) + return; + + const Track &track = m_view->timelineModel()->track(index.data(Qt::UserRole).toInt()); + QRect visRect = m_view->visualRect(index); + int buttonLength = visRect.height() / 2; + if (track.hasInsertButton()) + { + QRect rect = QRect(visRect.right() - buttonLength, visRect.top(), buttonLength, buttonLength); + if (rect.contains(e->pos())) + { + m_pressedInsertButtonIndex = index; + + m_view->timelineModel()->onInsertTrack(index); + return; + } + } + if (track.hasRemoveButton()) + { + QRect rect = QRect(visRect.right() - buttonLength, visRect.center().y(), buttonLength, buttonLength); + if (rect.contains(e->pos())) + { + m_pressedRemoveButtonIndex = index; + + auto msg = new LtsWidgets::LtsMessageDialogNew(m_view, {}, QApplication::translate("LtsTimeline", "Do you really want to remove this filter track and all of its contents?"), + LtsWidgets::Behaviour::Modal, LtsWidgets::Button::Yes | LtsWidgets::Button::Cancel); + + QObject::connect(msg, &LtsWidgets::LtsMessageDialogNew::yes, + m_view, [this, index]() + { + + m_view->timelineModel()->onRemoveTrack(index); + }); + + return; + } + } + } + catch (...) {} +} + +void TimelineViewTrackHandler::mouseReleaseEvent(QMouseEvent *e) +{ + Q_UNUSED(e); + m_pressedInsertButtonIndex = {}; + m_pressedRemoveButtonIndex = {}; +} + +} diff --git a/Lts/Common/LtsTimeline/timeline_view_track_handler.h b/Lts/Common/LtsTimeline/timeline_view_track_handler.h new file mode 100755 index 000000000..16b108291 --- /dev/null +++ b/Lts/Common/LtsTimeline/timeline_view_track_handler.h @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include +#include + + +namespace LtsTimeline +{ + +class Track; +class TimelineView; + + +class LTSTIMELINE_EXPORT TimelineViewTrackHandler +{ + +public: + explicit TimelineViewTrackHandler(TimelineView *view); + virtual ~TimelineViewTrackHandler(); + + virtual void mouseDoubleClickEvent(QMouseEvent *e); + virtual void mousePressEvent(QMouseEvent *e); + virtual void mouseReleaseEvent(QMouseEvent *e); + + bool isInsertButtonPressed(const QModelIndex &index) const { return index == m_pressedInsertButtonIndex; } + bool isRemoveButtonPressed(const QModelIndex &index) const { return index == m_pressedRemoveButtonIndex; } + +protected: + TimelineView *m_view{nullptr}; + + QModelIndex m_pressedInsertButtonIndex; + QModelIndex m_pressedRemoveButtonIndex; + +}; + +} diff --git a/Lts/Common/LtsTimeline/timeline_widget.cpp b/Lts/Common/LtsTimeline/timeline_widget.cpp new file mode 100755 index 000000000..6e153b6c6 --- /dev/null +++ b/Lts/Common/LtsTimeline/timeline_widget.cpp @@ -0,0 +1,402 @@ + + + +#include "timeline_widget.h" +#include "ui_timeline_widget.h" +#include "timeline_view.h" +#include "timeline_header_view.h" +#include "timeline_model.h" +#include "item_delegate.h" + +#include + +#include +#include +#include +#include +#include +#include + + +namespace LtsTimeline +{ + +TimelineWidget::TimelineWidget(QWidget *parent) + : QWidget(parent) + , m_ui(new Ui::Timeline) + , m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Timeline.Widget")) +{ + m_ui->setupUi(this); + m_ui->debugWidget->hide(); + m_ui->wPlaceholder->hide(); + m_ui->pbZoomIn->setProperty("const", true); + m_ui->pbZoomOut->setProperty("const", true); + m_ui->cbZoom->setProperty("const", true); + + connect(m_ui->cbZoom, qOverload(&QComboBox::currentIndexChanged), + this, &TimelineWidget::onZoomSelected); + m_ui->pbZoomIn->setIcon(QIcon(QStringLiteral("icon:bgv_zoom_in.svg"))); + connect(m_ui->pbZoomIn, &QPushButton::clicked, + this, &TimelineWidget::onZoomIn); + m_ui->pbZoomOut->setIcon(QIcon(QStringLiteral("icon:bgv_zoom_out.svg"))); + connect(m_ui->pbZoomOut, &QPushButton::clicked, + this, &TimelineWidget::onZoomOut); + + connect(m_ui->view, &TimelineView::rangeChanged, this, [this](qint64 min, qint64 max) + { + m_ui->slider->setRange(min / 1000, (max - m_ui->view->zoomFactor()) / 1000); + onUpdateZoomButtons(); + }); + connect(m_ui->view, &TimelineView::currentTimeChanged, this, [this](qint64 time) + { + m_ui->slider->setValue(time / 1000); + }); + + connect(m_ui->view, &TimelineView::zoomChanged, + this, &TimelineWidget::onZoomChanged); + + connect(m_ui->view->header(), &TimelineHeaderView::sectionResized, + this, &TimelineWidget::onSectionResized); + + connect(m_ui->slider, &QScrollBar::valueChanged, + this, &TimelineWidget::onSliderValueChanged); + + onZoomChanged(m_ui->view->zoomFactor()); +} + +TimelineWidget::~TimelineWidget() +{ + delete m_ui; +} + + + + +TimelineView *TimelineWidget::view() +{ + return m_ui->view; +} + + +TimelineHeaderView *TimelineWidget::header() +{ + return m_ui->view->header(); +} + + +TimelineModel *TimelineWidget::timelineModel() +{ + return m_ui->view->timelineModel(); +} + + +void TimelineWidget::setModel(QAbstractItemModel *model) +{ + m_ui->view->setModel(model); +} + + +QScrollBar *TimelineWidget::slider() +{ + return m_ui->slider; +} + + +ZoomFactor TimelineWidget::zoomFactor() const +{ + return m_ui->view->zoomFactor(); +} + +void TimelineWidget::setZoomFactor(ZoomFactor factor) +{ + m_ui->view->setZoomFactor(factor); +} + + +ZoomFactor TimelineWidget::minimumZoomFactor() const +{ + return m_ui->view->minimumZoomFactor(); +} + +void TimelineWidget::setMinimumZoomFactor(ZoomFactor factor) +{ + m_ui->view->setMinimumZoomFactor(factor); + populateZoomFactorComboBox(); +} + + +ZoomFactor TimelineWidget::maximumZoomFactor() const +{ + return m_ui->view->maximumZoomFactor(); +} + +void TimelineWidget::setMaximumZoomFactor(ZoomFactor factor) +{ + m_ui->view->setMaximumZoomFactor(factor); + populateZoomFactorComboBox(); +} + + +qint64 TimelineWidget::startTime() const +{ + return m_ui->view->startTime(); +} + +void TimelineWidget::setStartTime(qint64 time) +{ + m_ui->view->setStartTime(time); +} + + +qint64 TimelineWidget::currentTime() const +{ + return m_ui->view->currentTime(); +} + +void TimelineWidget::setCurrentTime(qint64 time) +{ + m_ui->view->setCurrentTime(time); +} + + +qint64 TimelineWidget::endTime() const +{ + return m_ui->view->endTime(); +} + +void TimelineWidget::setEndTime(qint64 time) +{ + m_ui->view->setEndTime(time); +} + + +int TimelineWidget::duration() const +{ + return m_ui->view->duration(); +} + +void TimelineWidget::setDuration(int duration) +{ + m_ui->view->setDuration(duration); +} + + +bool TimelineWidget::isCursorTextVisible() const +{ + return m_ui->view->isCursorTextVisible(); +} + +void TimelineWidget::setCursorTextVisible(bool visible) +{ + m_ui->view->setCursorTextVisible(visible); +} + +void TimelineWidget::setCursorText(const QString &text) +{ + m_ui->view->setCursorText(text); +} + + +bool TimelineWidget::isSnapEnabled() const +{ + return m_ui->view->timelineModel()->isSnapEnabled(); +} + +void TimelineWidget::setSnapEnabled(bool enabled) +{ + m_ui->view->timelineModel()->setSnapEnabled(enabled); +} + +void TimelineWidget::enableSnap() +{ + m_ui->view->timelineModel()->setSnapEnabled(true); +} + +void TimelineWidget::disableSnap() +{ + m_ui->view->timelineModel()->setSnapEnabled(false); +} + +void TimelineWidget::triggerSnap() +{ + m_ui->view->timelineModel()->setSnapEnabled(!m_ui->view->timelineModel()->isSnapEnabled()); + if (m_ui->debugWidget->isVisible()) + m_ui->pbDebugTriggerSnap->setText(m_ui->view->timelineModel()->isSnapEnabled() ? QStringLiteral("Disable Snap") : + QStringLiteral("Enable Snap")); +} + + +void TimelineWidget::setZoomInIcon(QIcon icon) +{ + m_ui->pbZoomIn->setIcon(icon); +} + +void TimelineWidget::setZoomOutIcon(QIcon icon) +{ + m_ui->pbZoomOut->setIcon(icon); +} + + +QWidget *TimelineWidget::placeholder() +{ + if (m_ui->wPlaceholder->layout()->count() == 0) + return nullptr; + return m_ui->wPlaceholder->layout()->itemAt(0)->widget(); +} + +void TimelineWidget::setPlaceholder(QWidget *widget) +{ + QWidget *ptr = placeholder(); + if (ptr) + { + if (ptr == widget) + return; + m_ui->wPlaceholder->layout()->removeWidget(ptr); + delete ptr; + } + m_ui->wPlaceholder->layout()->addWidget(widget); +} + + +void TimelineWidget::showDebugWidget() +{ + m_ui->debugWidget->show(); + + m_ui->sbDebugSnapStep->setValue(m_ui->view->timelineModel()->snapStep()); + m_ui->sbDebugSnapTol->setValue(m_ui->view->timelineModel()->snapTolerance()); + m_ui->pbDebugTriggerSnap->setText(m_ui->view->header()->isIndicatorVisible() ? QStringLiteral("Disable Snap") : + QStringLiteral("Enable Snap")); + m_ui->pbDebugTriggerIndicator->setText(m_ui->view->header()->isIndicatorVisible() ? QStringLiteral("Disable Indicator") : + QStringLiteral("Enable Indicator")); + m_ui->pbDebugTriggerHover->setText(m_ui->view->header()->isIndicatorVisible() ? QStringLiteral("Disable Hover") : + QStringLiteral("Enable Hover")); + + connect(m_ui->view, &TimelineView::currentTimeChanged, this, &TimelineWidget::onUpdateDebugInfo); + connect(m_ui->view, &TimelineView::rangeChanged, this, &TimelineWidget::onUpdateDebugInfo); + connect(m_ui->view, &TimelineView::selectedItemsChanged, this, &TimelineWidget::onUpdateDebugInfo); + connect(m_ui->view, &TimelineView::zoomChanged, this, &TimelineWidget::onUpdateDebugInfo); + connect(m_ui->pbDebugTriggerSnap, &QPushButton::clicked, this, &TimelineWidget::triggerSnap); + connect(m_ui->sbDebugSnapStep, qOverload(&QSpinBox::valueChanged), m_ui->view->timelineModel(), &TimelineModel::setSnapStep); + connect(m_ui->sbDebugSnapTol, qOverload(&QSpinBox::valueChanged), m_ui->view->timelineModel(), &TimelineModel::setSnapTolerance); + connect(m_ui->pbDebugTriggerIndicator, &QPushButton::clicked, this, [this]() + { + m_ui->view->header()->setIndicatorVisible(!m_ui->view->header()->isIndicatorVisible()); + m_ui->pbDebugTriggerIndicator->setText(m_ui->view->header()->isIndicatorVisible() ? QStringLiteral("Disable Indicator") : + QStringLiteral("Enable Indicator")); + }); + connect(m_ui->pbDebugTriggerHover, &QPushButton::clicked, this, [this]() + { + m_ui->view->header()->setHoverVisible(!m_ui->view->header()->isHoverVisible()); + m_ui->pbDebugTriggerHover->setText(m_ui->view->header()->isHoverVisible() ? QStringLiteral("Disable Hover") : + QStringLiteral("Enable Hover")); + }); + + onUpdateDebugInfo(); +} + +void TimelineWidget::hideDebugWidget() +{ + m_ui->debugWidget->hide(); + disconnect(m_ui->view, &TimelineView::currentTimeChanged, this, &TimelineWidget::onUpdateDebugInfo); + disconnect(m_ui->view, &TimelineView::rangeChanged, this, &TimelineWidget::onUpdateDebugInfo); + disconnect(m_ui->view, &TimelineView::selectedItemsChanged, this, &TimelineWidget::onUpdateDebugInfo); + disconnect(m_ui->view, &TimelineView::zoomChanged, this, &TimelineWidget::onUpdateDebugInfo); + disconnect(m_ui->pbDebugTriggerSnap, &QPushButton::clicked, this, &TimelineWidget::triggerSnap); + disconnect(m_ui->sbDebugSnapStep, qOverload(&QSpinBox::valueChanged), m_ui->view->timelineModel(), &TimelineModel::setSnapStep); + disconnect(m_ui->sbDebugSnapTol, qOverload(&QSpinBox::valueChanged), m_ui->view->timelineModel(), &TimelineModel::setSnapTolerance); + disconnect(m_ui->pbDebugTriggerIndicator); + disconnect(m_ui->pbDebugTriggerHover); +} + + + + +void TimelineWidget::populateZoomFactorComboBox() +{ + disconnect(m_ui->cbZoom, qOverload(&QComboBox::currentIndexChanged), + this, &TimelineWidget::onZoomSelected); + m_ui->cbZoom->clear(); + QVector availableZooms = Zoom::availableZoomFactors(m_ui->view->minimumZoomFactor(), m_ui->view->upperZoomFactor()); + for (ZoomFactor factor : qAsConst(availableZooms)) + { + m_ui->cbZoom->addItem(Zoom::toString(factor), QVariant(factor)); + if (factor == zoomFactor()) + m_ui->cbZoom->setCurrentIndex(m_ui->cbZoom->findData(static_cast(zoomFactor()))); + } + connect(m_ui->cbZoom, qOverload(&QComboBox::currentIndexChanged), + this, &TimelineWidget::onZoomSelected); +} + + + + +void TimelineWidget::onSectionResized(int logicalIndex, int oldSize, int newSize) +{ + Q_UNUSED(oldSize); + if (logicalIndex == Section::eItems) + { + int width = m_ui->view->verticalScrollBar()->width(); + m_ui->slider->setMinimumHeight(width); + m_ui->slider->setMaximumHeight(width); + m_ui->slider->setMaximumWidth(newSize); + m_ui->slider->setMinimumWidth(newSize); + } +} + +void TimelineWidget::onSliderValueChanged(int value) +{ + setCurrentTime(qint64(value) * 1000); +} + +void TimelineWidget::onUpdateZoomButtons() +{ + m_ui->pbZoomIn->setEnabled(zoomFactor() > minimumZoomFactor()); + m_ui->pbZoomOut->setEnabled(zoomFactor() < m_ui->view->upperZoomFactor()); + populateZoomFactorComboBox(); +} + +void TimelineWidget::onZoomChanged(ZoomFactor factor) +{ + m_ui->slider->setMaximum((m_ui->view->endTime() - m_ui->view->zoomFactor()) / 1000); + m_ui->slider->setPageStep(factor / 1000); + m_ui->slider->setSingleStep(Zoom::minorInterval(factor)); + onUpdateZoomButtons(); + if (static_cast(m_ui->cbZoom->currentData().toInt()) != factor) + m_ui->cbZoom->setCurrentIndex(m_ui->cbZoom->findData(QVariant(factor))); +} + +void TimelineWidget::onZoomSelected() +{ + ZoomFactor factor = static_cast(m_ui->cbZoom->currentData().toInt()); + if (factor != zoomFactor()) + setZoomFactor(factor); +} + +void TimelineWidget::onZoomIn() +{ + setZoomFactor(Zoom::decrement(zoomFactor())); +} + +void TimelineWidget::onZoomOut() +{ + setZoomFactor(Zoom::increment(zoomFactor())); +} + + +void TimelineWidget::onUpdateDebugInfo() +{ + m_ui->lblDebugStart->setText(DateTime::toString(startTime(), DateTimeFormat::DTG)); + m_ui->lblDebugEnd->setText(DateTime::toString(endTime(), DateTimeFormat::DTG)); + m_ui->lblDebugDuration->setText(QString::number(duration() / 1000) + QStringLiteral("s")); + m_ui->lblDebugCurrent->setText(DateTime::toString(currentTime(), DateTimeFormat::DTG)); + m_ui->lblDebugSlider->setText(DateTime::toString(qint64(m_ui->slider->value()) * 1000, DateTimeFormat::DTG)); + m_ui->lblDebugZoom->setText(Zoom::toString(m_ui->view->zoomFactor())); + QString selection; + for (int itemId : qAsConst(m_ui->view->selectedItemIds())) + selection += QString::number(itemId) + QStringLiteral(", "); + if (selection.size() > 1) + selection.chop(2); + m_ui->lblDebugSelection->setText(selection); +} + +} diff --git a/Lts/Common/LtsTimeline/timeline_widget.h b/Lts/Common/LtsTimeline/timeline_widget.h new file mode 100755 index 000000000..baa4642cc --- /dev/null +++ b/Lts/Common/LtsTimeline/timeline_widget.h @@ -0,0 +1,114 @@ + + + +#pragma once + +#include "timeline_utils.h" + +#include +#include + +#include + + +class QScrollBar; +class QAbstractItemModel; + + +namespace Ui +{ +class Timeline; +} + + +namespace LtsTimeline +{ + +class TimelineView; +class TimelineHeaderView; +class TimelineModel; + + +class LTSTIMELINE_EXPORT TimelineWidget : public QWidget +{ + Q_OBJECT + +public: + explicit TimelineWidget(QWidget *parent = nullptr); + ~TimelineWidget(); + + TimelineView *view(); + + TimelineHeaderView *header(); + + TimelineModel *timelineModel(); + void setModel(QAbstractItemModel *model); + + QScrollBar *slider(); + + ZoomFactor zoomFactor() const; + void setZoomFactor(ZoomFactor factor); + + ZoomFactor minimumZoomFactor() const; + void setMinimumZoomFactor(ZoomFactor factor); + + ZoomFactor maximumZoomFactor() const; + void setMaximumZoomFactor(ZoomFactor factor); + + qint64 startTime() const; + void setStartTime(qint64 time); + + qint64 currentTime() const; + void setCurrentTime(qint64 time); + + qint64 endTime() const; + void setEndTime(qint64 time); + + int duration() const; + void setDuration(int duration); + + bool isCursorTextVisible() const; + void setCursorTextVisible(bool visible); + void setCursorText(const QString &text); + void setCursorText(const QStringList &texts); + + bool isSnapEnabled() const; + void setSnapEnabled(bool enabled); + void enableSnap(); + void disableSnap(); + void triggerSnap(); + + void setZoomInIcon(QIcon icon); + void setZoomOutIcon(QIcon icon); + + QWidget *placeholder(); + void setPlaceholder(QWidget *widget); + + void showDebugWidget(); + void hideDebugWidget(); + +private: + void populateZoomFactorComboBox(); + +public: + void onUpdateZoomButtons(); + +private slots: + void onSectionResized(int logicalIndex, int oldSize, int newSize); + void onSliderValueChanged(int value); + void onZoomChanged(ZoomFactor factor); + void onZoomSelected(); + void onZoomIn(); + void onZoomOut(); + + void onUpdateDebugInfo(); + +signals: + +private: + Ui::Timeline *m_ui{nullptr}; + NGL::INGLLogger &m_logger; + +}; + +} diff --git a/Lts/Common/LtsTimeline/timeline_widget.ui b/Lts/Common/LtsTimeline/timeline_widget.ui new file mode 100755 index 000000000..acc52166e --- /dev/null +++ b/Lts/Common/LtsTimeline/timeline_widget.ui @@ -0,0 +1,434 @@ + + + Timeline + + + + 0 + 0 + 1298 + 409 + + + + true + + + Timeline + + + + + + + + + + + + + + start: + + + + + + + [START] + + + + + + + Qt::Vertical + + + + + + + end: + + + + + + + [END] + + + + + + + Qt::Vertical + + + + + + + current: + + + + + + + + + + [CURRENT] + + + + + + + Qt::Vertical + + + + + + + duration: + + + + + + + [DURATION] + + + + + + + Qt::Vertical + + + + + + + slider: + + + + + + + [SLIDER] + + + + + + + Qt::Vertical + + + + + + + zoom: + + + + + + + [ZOOM] + + + + + + + Qt::Vertical + + + + + + + selection: + + + + + + + + + + [SELECTION] + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Update + + + + + + + + + + Enable Snap + + + + + + + Snap Step: + + + + + + + 60000 + + + 100 + + + + + + + Snap Tol: + + + + + + + 60000 + + + 100 + + + + + + + + + + Disable Indicator + + + + + + + + + + Disable Hover + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + 0 + 20 + + + + + 16777215 + 16777215 + + + + ArrowCursor + + + true + + + Qt::NoFocus + + + Qt::LeftToRight + + + 0 + + + 0 + + + true + + + Qt::Horizontal + + + false + + + true + + + + + + + + 0 + 0 + + + + + 30 + 0 + + + + + 25 + 16777215 + + + + + + + + + + + + 0 + 0 + + + + false + + + QAbstractItemView::NoSelection + + + + + + + + 0 + 0 + + + + + 30 + 0 + + + + + 25 + 16777215 + + + + + + + + + + + true + + + + 0 + 0 + + + + + 20 + 0 + + + + + 125 + 16777215 + + + + + 0 + 0 + + + + + + + + + LtsTimeline::TimelineView + QTreeView +
timeline_view.h
+
+
+ + +
diff --git a/Lts/Common/LtsTimeline/track.cpp b/Lts/Common/LtsTimeline/track.cpp new file mode 100755 index 000000000..76bad68e6 --- /dev/null +++ b/Lts/Common/LtsTimeline/track.cpp @@ -0,0 +1,853 @@ + + + +#include "track.h" + +namespace LtsTimeline +{ + +const Track &Track::track(int id) const noexcept(false) +{ + + auto const it = std::find_if(m_childrenTracks.cbegin(), m_childrenTracks.cend(), + [id](const TrackPtr &trackPtr) + { + return (trackPtr.get()->id() == id); + }); + + if (it != m_childrenTracks.cend()) + return **it; + + + for (const TrackPtr &track : qAsConst(m_childrenTracks)) + { + try + { + return track->track(id); + } + catch(...) + { + + + } + } + + throw QStringLiteral("lookup failed!");; +} + + +bool Track::hasTrack(int id, bool recursive) const +{ + if (std::find_if(m_childrenTracks.cbegin(), m_childrenTracks.cend(), + [id](const TrackPtr &trackPtr) + { + return (trackPtr.get()->id() == id); + }) != m_childrenTracks.cend()) + return true; + + if (!recursive) + return false; + + for (const TrackPtr &trackPtr : qAsConst(m_childrenTracks)) + { + if (trackPtr->hasTrack(id, true)) + return true; + } + + return false; +} + + + + + + + + + + + + + + + + + + + + + + + + + +void Track::addCopyItemIds(const QSet &ids) +{ + m_copyItemIds += ids; +} + + +void Track::addCopyItemId(int id) +{ + m_copyItemIds += id; +} + + +const Track &Track::parentOf(int id) const noexcept(false) +{ + for (const TrackPtr &trackPtr : qAsConst(m_childrenTracks)) + { + if (trackPtr->hasTrack(id)) + return *trackPtr; + + if (trackPtr->hasTrack(id, true)) + return trackPtr->parentOf(id); + } + + throw("not found"); +} + + +bool Track::isItemBetween(const Item &item, qint64 start, qint64 end, bool strict, bool complete, qint64 minimumDuration) +{ + if (end - start == -1) + { + if (item.duration() == -1) + { + if (!complete) + return true; + + return strict ? item.startTime() > start : item.startTime() >= start; + } + + if (strict) + { + return (complete && item.startTime() > start) || + (!complete && item.startTime() + item.duration() > start); + } + else + { + return (complete && item.startTime() >= start) || + (!complete && item.startTime() + item.duration() >= start); + } + } + + if (item.duration() == -1) + { + if (complete) + return false; + + return strict ? item.startTime() < end : item.startTime() <= end; + } + + Q_ASSERT(end+1 > start); + qint64 itemStart = item.startTime(), itemEnd = item.startTime() + (std::max((qint64)item.duration(), minimumDuration)); + + if (strict) + return ((complete && (itemStart > start && itemEnd < end)) || + (!complete && (itemEnd > start && itemStart < end))); + else + return ((complete && (itemStart >= start && itemEnd <= end)) || + (!complete && (itemEnd >= start && itemStart <= end))); +} + + +void Track::removeCopyItem(int itemId) noexcept(false) +{ + m_copyItemIds.remove(itemId); + + for (TrackPtr &t : m_childrenTracks) + { + t->removeCopyItem(itemId); + } +} + + +const QVector Track::copyItemIdsBetween(qint64 start, qint64 end, bool strict, bool complete, bool recursive, bool sorted, qint64 minimumDuration) const +{ + auto itemPtrRefs = _copyItems(); + + + if (recursive) + { + for (const TrackPtr &trackPtr : qAsConst(m_childrenTracks)) + { + auto itemPtrRefs2 = trackPtr->_copyItems(); + itemPtrRefs.insert(itemPtrRefs.end(), itemPtrRefs2.begin(), itemPtrRefs2.end()); + } + } + + itemPtrRefs.erase(std::remove_if(itemPtrRefs.begin(), itemPtrRefs.end(), + [this, start, end, strict, complete](const ItemPtr &itemPtr) + { + return !isItemBetween(*itemPtr, start, end, strict, complete); + }), itemPtrRefs.end()); + + if (sorted) + { + sort(itemPtrRefs); + } + + + + return transformItemPtrsToIds(itemPtrRefs); +} + + +const QVector Track::itemIdsBetween(qint64 start, qint64 end, bool strict, bool complete, bool recursive, bool sorted, qint64 minimumDuration) const +{ + if (sorted) + { + auto itemPtrRefs = _items(recursive); + itemPtrRefs.erase(std::remove_if(itemPtrRefs.begin(), itemPtrRefs.end(), + [this, start, end, strict, complete, minimumDuration](const ItemPtr &itemPtr) + { + return !isItemBetween(*itemPtr, start, end, strict, complete, minimumDuration); + }), itemPtrRefs.end()); + + sort(itemPtrRefs); + + return transformItemPtrsToIds(itemPtrRefs); + } + + + + QVector itemIds; + itemIds.reserve(m_items.size()); + + for (const ItemPtr &itemPtr : qAsConst(m_items)) + { + const Item &item = *itemPtr; + if (isItemBetween(item, start, end, strict, complete, minimumDuration)) + itemIds += item.id(); + } + + if (!recursive) + { + itemIds.squeeze(); + return itemIds; + } + + for (const TrackPtr &trackPtr : qAsConst(m_childrenTracks)) + itemIds += trackPtr->itemIdsBetween(start, end, strict, complete, true, minimumDuration); + + return itemIds; +} + + +bool Track::isItemsBetween(qint64 start, qint64 end, const QVector &ignoreIds, bool strict, bool complete, bool recursive, qint64 minimumDuration) const +{ + for (const ItemPtr &itemPtr : qAsConst(m_items)) + { + const Item &i = *itemPtr; + if (!ignoreIds.contains(i.id())) + if (isItemBetween(i, start, end, strict, complete, minimumDuration)) + return true; + } + + + for (const int itemId : qAsConst(m_copyItemIds)) + { + const Item &i = item(itemId); + if (!ignoreIds.contains(i.id())) + if (isItemBetween(i, start, end, strict, complete, minimumDuration)) + return true; + } + + + if (!recursive) + return false; + + for (const TrackPtr &trackPtr : qAsConst(m_childrenTracks)) + if (trackPtr->isItemsBetween(start, end, ignoreIds, strict, complete, true, minimumDuration)) + return true; + + return false; +} + + +bool Track::isItemsBetween(qint64 start, qint64 end, int ignoreId, bool strict, bool complete, bool recursive, qint64 minimumDuration) const +{ + return isItemsBetween(start, end, QVector({ ignoreId }), strict, complete, recursive, minimumDuration); +} + + +ItemPtrRefs Track::_items(bool recursive) const +{ + + ItemPtrRefs x(m_items.cbegin(), m_items.cend()); + + if (!m_copyItemIds.isEmpty()) + x.reserve(x.size() + m_copyItemIds.count()); + + + for (const int itemId : qAsConst(m_copyItemIds)) + { + try + { + x.push_back(std::cref(_item(itemId))); + } + catch (const QString &msg) + { + fprintf(stderr, "cannot add item ptr ref from copy item with copy item id %d: %s\n", itemId, msg.toStdString().c_str()); + } + } + + if (!recursive) + return x; + + + for (const TrackPtr &t : qAsConst(m_childrenTracks)) + { + const ItemPtrRefs x2(t->_items(true)); + x.insert(x.end(), x2.begin(), x2.end()); + + } + + + + return x; +} + + +ItemPtrRefs Track::_copyItems() const +{ + + ItemPtrRefs x; + + for (int id : qAsConst(m_copyItemIds)) + { + try + { + x.push_back(_item(id)); + } + catch(...) + { + fprintf(stderr, "Track::_copyItems: cannot find item for item id %d\n", id); + continue; + } + } + + return x; +} + + +ItemPtrNCVRefs Track::_itemsNCV(bool recursive) +{ + + ItemPtrNCVRefs x(m_items.begin(), m_items.end()); + + if (!recursive) + return x; + + + for (TrackPtr &t : m_childrenTracks) + { + ItemPtrNCVRefs x2(t->_itemsNCV(true)); + x.insert(x.end(), x2.begin(), x2.end()); + + } + + return x; +} + + + +const QVector Track::_itemIds(bool recursive) const +{ + QVector itemIds(transformItemPtrsToIds(m_items)); + + if (!recursive) + return itemIds; + + + for (const TrackPtr &trackPtr : qAsConst(m_childrenTracks)) + itemIds += trackPtr->_itemIds(true); + + return itemIds; +} + + +ItemPtrRef Track::_item(int id) const noexcept(false) +{ + + auto const it = std::find_if(m_items.cbegin(), m_items.cend(), + [id](const ItemPtr &itemPtr) + { + return (itemPtr.get()->id() == id); + }); + + if (it != m_items.cend()) + return *it; + + + for (const TrackPtr &track : qAsConst(m_childrenTracks)) + { + try + { + return track->_item(id); + } + catch(...) + { + + + } + } + + throw QStringLiteral("_item: item with id %1 not found!").arg(id); +} + + +void Track::printTrackIds(bool withItems, int level) const +{ + fprintf(stderr, "%*c Track %16.16s ", level * 2, '>', text().toStdString().c_str()); + + if (withItems) + { + fprintf(stderr, " ( "); + for (const ItemPtr &itemPtr : qAsConst(m_items)) + { + fprintf(stderr, "%d ", itemPtr->id()); + } + fprintf(stderr, ")"); + } + + fprintf(stderr, "\n"); + + + for (const TrackPtr &trackPtr : qAsConst(m_childrenTracks)) + trackPtr->printTrackIds(level + 1); +} + + +QVector Track::itemIds(bool recursive, bool sorted) const +{ + if (sorted) + { + if (!recursive) + return transformItemPtrsToIds(m_items); + + + ItemPtrRefs is(_items(true)); + + sort(is); + + return transformItemPtrsToIds(is); + } + + + + QVector itemIds; + itemIds.reserve(m_items.size()); + + itemIds += transformItemPtrsToIds(m_items); + + if (!recursive) + return itemIds; + + + for (const TrackPtr &trackPtr : qAsConst(m_childrenTracks)) + itemIds += trackPtr->_itemIds(true); + + return itemIds; +} + + +void Track::addItemPtr(ItemPtr &&itemPtr) +{ + m_items.insert(std::upper_bound(m_items.begin(), m_items.end(), itemPtr->startTime(), + [](qint64 startTime, const ItemPtr &other) + { + return (other->startTime() > startTime); + }), std::move(itemPtr)); +} + + +void Track::removeItem(int itemId) noexcept(false) +{ + auto it = std::find_if(m_items.begin(), m_items.end(), + [itemId](const ItemPtr &itemPtr) + { + return (itemPtr->id() == itemId); + }); + + if (it == m_items.end()) + throw QStringLiteral("removeItem: item %1 not found").arg(itemId); + + m_items.erase(it); +} + + +void Track::removeTrack(int trackId) noexcept(false) +{ + auto it = std::find_if(m_childrenTracks.begin(), m_childrenTracks.end(), + [trackId](const TrackPtr &trackPtr) + { + return (trackPtr->id() == trackId); + }); + + if (it == m_childrenTracks.end()) + throw QStringLiteral("removeTrack: track %1 not found on track %2").arg(trackId, id()); + + m_childrenTracks.erase(it); +} + + +ItemPtr Track::takeItem(int itemId) noexcept(false) +{ + auto it = std::find_if(m_items.begin(), m_items.end(), + [itemId](const ItemPtr &itemPtr) + { + return (itemPtr->id() == itemId); + }); + + if (it == m_items.end()) + throw QStringLiteral("Track::takeItem: item with id %1 not found").arg(itemId); + + ItemPtr ptr = std::move(*it); + + m_items.erase(it); + + return ptr; +} + + + + + + + + + + + + + + + + + + + + + + + +Track &Track::track(int id) noexcept(false) +{ + + auto it = std::find_if(m_childrenTracks.begin(), m_childrenTracks.end(), + [id](TrackPtr &trackPtr) + { + return (trackPtr.get()->id() == id); + }); + + if (it != m_childrenTracks.end()) + { + Track &t = **it; + + return t; + } + + + for (TrackPtr &track : m_childrenTracks) + { + try + { + return track->track(id); + } + catch(...) + { + + + } + } + + throw QStringLiteral("Track: track with id %1 not found").arg(id); +} + + +ItemPtrNCVRef Track::_itemNCV(int id) noexcept(false) +{ + + auto const it = std::find_if(m_items.begin(), m_items.end(), + [id](ItemPtr &itemPtr) + { + return (itemPtr.get()->id() == id); + }); + + if (it != m_items.end()) + return *it; + + + for (TrackPtr &track : m_childrenTracks) + { + try + { + return track->_itemNCV(id); + } + catch(...) + { + + + } + } + + throw QStringLiteral("lookup failed!");; +} + + +Item &Track::item(int id) noexcept(false) +{ + + auto const it = std::find_if(m_items.begin(), m_items.end(), + [id](ItemPtr &itemPtr) + { + return (itemPtr.get()->id() == id); + }); + + if (it != m_items.end()) + return **it; + + + for (TrackPtr &track : m_childrenTracks) + { + try + { + return track->item(id); + } + catch(...) + { + + + } + } + + throw QStringLiteral("lookup failed!");; +} + + +const Item &Track::item(int id) const noexcept(false) +{ + + auto const it = std::find_if(m_items.cbegin(), m_items.cend(), + [id](const ItemPtr &itemPtr) + { + return (itemPtr.get()->id() == id); + }); + + if (it != m_items.cend()) + return **it; + + + for (const TrackPtr &track : qAsConst(m_childrenTracks)) + { + try + { + return track->item(id); + } + catch(...) + { + + + } + } + + throw QStringLiteral("lookup failed!");; +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Item &Track::previous(int id) noexcept(false) +{ + + auto it = std::find_if(m_items.begin(), m_items.end(), [id](ItemPtr &itemPtr) + { + return (itemPtr.get()->id() == id); + }); + + if (it != m_items.end() && (*it) != m_items.front()) + return **(--it); + + throw QStringLiteral("Track::previous: previous item for id %1 not found").arg(id); +} + + +Item &Track::next(int id) noexcept(false) +{ + + auto it = std::find_if(m_items.begin(), m_items.end(), [id](ItemPtr &itemPtr) + { + return (itemPtr.get()->id() == id); + }); + + if (it != m_items.end() && (*it) != m_items.back()) + return **(++it); + + throw QStringLiteral("lookup failed!");; +} + + + + + + + + + + + + + + + + + + + + +int Track::rowOfTrack(int id) const noexcept(false) +{ + int row = 0; + for (const TrackPtr &trackPtr : qAsConst(m_childrenTracks)) + { + if (trackPtr->id() == id) + return row; + row++; + } + + throw("not found"); +} + + +Track &Track::trackAt(int index) noexcept(false) +{ + return *m_childrenTracks.at(index); +} + + +const Track &Track::trackAt(int index) const noexcept(false) +{ + return *m_childrenTracks.at(index); +} + + +const Item &Track::copyItemAt(qint64 time) const noexcept(false) +{ + auto const itemPtrRefs = _copyItems(); + const ItemPtrRefs::const_iterator it = + std::find_if(itemPtrRefs.cbegin(), itemPtrRefs.cend(), [time](const ItemPtr &itemPtr) + { + return itemPtr.get()->startTime() <= time && time <= itemPtr.get()->startTime() + itemPtr.get()->duration(); + }); + + if (it != itemPtrRefs.cend()) + return *it->get(); + + throw QStringLiteral("Track: can't find copy item at %1").arg(time); +} + + +Item &Track::itemAt(qint64 time) noexcept(false) +{ + + auto it = std::find_if(m_items.cbegin(), m_items.cend(), [time](const ItemPtr &itemPtr) + { + return itemPtr.get()->startTime() <= time && time <= itemPtr.get()->startTime() + itemPtr.get()->duration(); + }); + + if (it != m_items.cend()) + return **it; + + throw QStringLiteral("lookup failed!");; +} + + +const Item &Track::itemAt(qint64 time) const noexcept(false) +{ + + auto const it = std::find_if(m_items.cbegin(), m_items.cend(), [time](const ItemPtr &itemPtr) + { + return itemPtr.get()->startTime() <= time && time <= itemPtr.get()->startTime() + itemPtr.get()->duration(); + }); + + if (it != m_items.cend()) + return **it; + + throw QStringLiteral("lookup failed!");; +} + +} + + + + + + + + + + + + + + + + + + + + diff --git a/Lts/Common/LtsTimeline/track.h b/Lts/Common/LtsTimeline/track.h new file mode 100755 index 000000000..6ef03320d --- /dev/null +++ b/Lts/Common/LtsTimeline/track.h @@ -0,0 +1,263 @@ + + + +#pragma once + +#include "item.h" + +#include + +#include +#include +#include + + +#include + + +namespace LtsTimeline +{ +class Track; + +class TimelineModel; + +typedef std::unique_ptr TrackPtr; +typedef std::vector Tracks; +typedef std::reference_wrapper TrackPtrRef; +typedef std::vector TrackPtrRefs; + + +class LTSTIMELINE_EXPORT Track +{ +public: + Track(const QString &text = {}) + : m_text(text) + {} + + Track(const QString &text, int specialTrackType) + : m_text(text) + , m_userData(specialTrackType) + {} + + Track(const Track &) = delete; + Track &operator=(const Track &) = delete; + Track(Track &&t) noexcept = default; + virtual ~Track() = default; + + [[deprecated("Use model's track(int) instead.")]] const Track &track(int id) const noexcept(false); + [[deprecated("Use model's track(int) instead.")]] Track &track(int id) noexcept(false); + [[deprecated("Use model's item(int) instead.")]] Item &item(int id) noexcept(false); + [[deprecated("Use model's item(int) instead.")]] const Item &item(int id) const noexcept(false); + Item &previous(int id) noexcept(false); + Item &next(int id) noexcept(false); + + const Item ©ItemAt(qint64 time) const noexcept(false); + Item &itemAt(qint64 time) noexcept(false); + const Item &itemAt(qint64 time) const noexcept(false); + Track &trackAt(int index) noexcept(false); + const Track &trackAt(int index) const noexcept(false); + bool hasTrack(int id, bool recursive = false) const; + bool hasTracks() const { return !m_childrenTracks.empty(); } + int rowOfTrack(int id) const noexcept(false); + const Track &parentOf(int id) const noexcept(false); + + + const QSet ©ItemIds() const { return m_copyItemIds; } + void clearCopyItemIds() { m_copyItemIds.clear(); } + void addCopyItemIds(const QSet &ids); + void addCopyItemId(int id); + bool hasItems() const { return !m_items.empty(); } + bool hasCopyItems() const { return !m_copyItemIds.empty(); } + int itemCount() const { return m_items.size(); } + Items &items() { return m_items; } + const Items &items() const { return m_items; } + int trackCount() const { return m_childrenTracks.size(); } + Tracks &tracks() { return m_childrenTracks; } + const Tracks &tracks() const { return m_childrenTracks; } + QVector itemIds(bool recursive = false, bool sorted = false) const; + const QVector itemIdsBetween(qint64 start, qint64 end, bool strict = false, bool complete = false, bool recursive = false, bool sorted = false, qint64 minimumDuration = 0) const; + [[deprecated]] const QVector copyItemIdsBetween(qint64 start, qint64 end, bool strict = false, bool complete = false, bool recursive = false, bool sorted = false, qint64 minimumDuration = 0) const; + bool isItemsBetween(qint64 start, qint64 end, const QVector &ignoreIds, bool strict = false, bool complete = false, bool recursive = false, qint64 minimumDuration = 0) const; + bool isItemsBetween(qint64 start, qint64 end, int ignoreId, bool strict = false, bool complete = false, bool recursive = false, qint64 minimumDuration = 0) const; + + + int id() const { return m_id; } + virtual const QString text() const { return m_text; } + int parent() const { return m_parentId; } + int parentId() const { return m_parentId; } + void printTrackIds(bool withItems = false, int level = 0) const; + void removeCopyItem(int itemId) noexcept(false); + void removeItem(int itemId) noexcept(false); + ItemPtr takeItem(int itemId) noexcept(false); + QVariant userData() const { return m_userData; } + void setUserData(const QVariant &data) { m_userData = data; } + + + inline static void sort(Items &items) + { + std::sort(items.begin(), items.end(), [](const ItemPtr &itemA, const ItemPtr &itemB) + { + return itemA->startTime() < itemB->startTime(); + }); + } + + + inline static void sort(ItemPtrNCVRefs &items) + { + std::sort(items.begin(), items.end(), [](const ItemPtrNCVRef &itemA, const ItemPtrNCVRef &itemB) + { + return itemA.get()->startTime() < itemB.get()->startTime(); + }); + } + + + inline static void sort(ItemPtrRefs &items) + { + std::sort(items.begin(), items.end(), [](const ItemPtrRef &itemA, const ItemPtrRef &itemB) + { + return itemA.get()->startTime() < itemB.get()->startTime(); + }); + } + + bool hasInsertButton() const { return m_hasInsertButton; } + void setInsertButton(bool hasButton) { m_hasInsertButton = hasButton; } + bool hasRemoveButton() const { return m_hasRemoveButton; } + void setRemoveButton(bool hasButton) { m_hasRemoveButton = hasButton; } + + +protected: + QString m_text; + QVariant m_userData; + + +private: + void removeTrack(int trackId) noexcept(false); + void addItemPtr(ItemPtr &&itemPtr); + + static bool isItemBetween(const Item &item, qint64 start, qint64 end, bool strict, bool complete, qint64 minimumDuration = 0); + ItemPtrRefs _copyItems() const; + ItemPtrRefs _items(bool recursive) const; + ItemPtrNCVRefs _itemsNCV(bool recursive); + ItemPtrNCVRef _itemNCV(int id) noexcept(false); + ItemPtrRef _item(int id) const noexcept(false); + const QVector _itemIds(bool recursive) const; + + +public: + void clearItems() { m_items.clear(); } +private: + + friend class TimelineModel; + + + void sort() + { + sort(m_items); + } + + + template + Track *addTrack(TrackClass &&trackClass) + { + TrackClass _trackClass = std::move(trackClass); + m_childrenTracks.push_back(std::move(std::move(std::make_unique(std::move(_trackClass))))); + return m_childrenTracks.back().get(); + } + + + template + [[deprecated("Use model's track(...) and call addTrack(TrackClass) directly on that track.")]] + Track *addTrack(int parentTrackId, TrackClass &&trackClass) noexcept(false) + { + if (m_id == parentTrackId) + return addTrack(std::move(trackClass)); + + return track(parentTrackId).addTrack(std::move(trackClass)); + } + + + template + Item *addItem(ItemClass &&itemClass) + { + ItemClass _itemClass(std::move(itemClass)); + + auto it = m_items.insert(std::upper_bound(m_items.begin(), m_items.end(), _itemClass.startTime(), + [](qint64 startTime, const ItemPtr &other) + { + return (other->startTime() > startTime); + }), std::move(std::make_unique(std::move(_itemClass)))); + + return it->get(); + } + + + Item *addItem(ItemPtr &&itemPtr) + { + auto it = m_items.insert(std::upper_bound(m_items.begin(), m_items.end(), itemPtr->startTime(), + [](qint64 startTime, const ItemPtr &other) + { + return (other->startTime() > startTime); + }), std::move(itemPtr)); + + return it->get(); + } + + + template + [[deprecated]] + QVector transformItemPtrsToIds(const ItemContainer &itemContainer) const + { + QVector ids; + std::transform(itemContainer.cbegin(), itemContainer.cend(), std::back_inserter(ids), + [](const ItemPtr &itemPtr) -> int + { + return itemPtr->id(); + }); + return ids; + } + + + + + + + + + + + + + + int m_id { -1 }; + Tracks m_childrenTracks; + Items m_items; + int m_parentId { -1 }; + bool m_hasInsertButton { false }; + bool m_hasRemoveButton { false }; + + + QSet m_copyItemIds; +}; + +} + + + + + + + + + + + + + + + + + + + + + diff --git a/Lts/Common/LtsTimelineClassic/AbstractTimelineModel.cpp b/Lts/Common/LtsTimelineClassic/AbstractTimelineModel.cpp new file mode 100755 index 000000000..75e4aae23 --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/AbstractTimelineModel.cpp @@ -0,0 +1,283 @@ + + + +#include "AbstractTimelineModel.h" + +#include + + +namespace LtsTimelineClassic +{ + +AbstractTimelineModel::AbstractTimelineModel(QObject *parent) + : QAbstractItemModel(parent) + , m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Timeline.AbstractTimelineModel")) + , m_highestPositionedItem(0) + , m_lowestPositionedItem(0) + , m_getEventListsRecursive(true) + , m_rootItem(new Item(QLatin1String("#mwille/r00t/"))) + , m_markersItem(new Item(tr("Markers"), QStringLiteral("Markers"))) +{ + + + + m_rootItem->addItem(m_markersItem); +} + + +AbstractTimelineModel::~AbstractTimelineModel() +{ + delete m_rootItem; +} + + +void AbstractTimelineModel::retranslate() +{ + m_markersItem->setName(tr("Markers")); +} + + +void AbstractTimelineModel::invalidateTimeline() +{ + const LtsTimelineClassic::ItemList il = m_rootItem->getFullItemList(true); + for (auto i = il.constBegin(); i != il.constEnd(); ++i) + { + LtsTimelineClassic::Item *item = *i; + + if (item != nullptr) + item->setEventListChanged(); + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Event *AbstractTimelineModel::getEvent(int uniqueId) +{ + const LtsTimelineClassic::ItemList il = m_rootItem->getFullItemList(true); + for (auto i = il.constBegin(); i != il.constEnd(); ++i) + { + LtsTimelineClassic::Item *item = *i; + + if (item != nullptr && item->getIdentifier() != QStringLiteral("Markers")) + { + const LtsTimelineClassic::EventList el = item->getFullEventList(true, true); + + for (LtsTimelineClassic::Event *event : el) + { + if (event->uniqueId() == uniqueId) + return event; + } + } + } + + return nullptr; +} + + +void AbstractTimelineModel::addEvent(Event *event) +{ + QStringList actuatorPath = event->getActuator(); + + + Item *currentItem = m_rootItem; + QModelIndex currentIndex = QModelIndex(); + + for (const QString &curPath : qAsConst(actuatorPath)) + { + Item *oldItem = currentItem; + const ItemList *itemList = currentItem->getChildren(); + + if (itemList != nullptr) + { + for (int row = 0; row < itemList->count(); row++) + { + Item *childItem = itemList->at(row); + if (childItem->getName() == curPath) + { + currentIndex = index(row, 0, currentIndex); + currentItem = childItem; + break; + } + } + } + + if (oldItem == currentItem) + { + beginInsertRows(currentIndex, currentItem->childCount(), currentItem->childCount()); + Item *newItem = new Item(curPath); + currentItem->addItem(newItem); + endInsertRows(); + + currentIndex = index(currentItem->childCount() - 1, 0, currentIndex); + currentItem = newItem; + } + } + + currentItem->addEvent(event); +} + + +Item *AbstractTimelineModel::getRootItem() +{ + return m_rootItem; +} + + +Item *AbstractTimelineModel::itemFromIndex(const QModelIndex &idx) const +{ + if (!idx.isValid()) + return m_rootItem; + return static_cast(idx.internalPointer()); +} + + +QVariant AbstractTimelineModel::data(const QModelIndex &index, int role) const +{ + if (role != Qt::DisplayRole || !index.isValid()) + return QVariant(); + + Item *item = itemFromIndex(index); + if (!item) + return QVariant(); + + + + + + if (index.column() == 1) + { + PassiveEventContainer *pevcon = item->getPevcon(); + return QVariant::fromValue(pevcon); + } + + + QString name(item->getName()); + + if (name.isEmpty()) + name = tr("[unknown]"); + + return QVariant(name); +} + + +Qt::ItemFlags AbstractTimelineModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return {}; + + return QAbstractItemModel::flags(index) | Qt::ItemIsEditable; +} + + +QVariant AbstractTimelineModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + + switch (section) + { + case 0: return QString(tr("Actuator") + QStringLiteral("\r\n")); + case 1: return QString(tr("Events") + QStringLiteral("\r\n")); + } + + + + + + + } + + return QVariant(); +} + + +QModelIndex AbstractTimelineModel::index(int row, int column, const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + Item *item = itemFromIndex(parent); + Item *child = item ? item->child(row) : nullptr; + + if (child) + { + + return createIndex(row, column, child); + } + + return QModelIndex(); +} + + +QModelIndex AbstractTimelineModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) + return QModelIndex(); + + Item *item = itemFromIndex(index); + Item *parent = item ? item->getParent() : nullptr; + + if (parent == m_rootItem || parent == nullptr) + return QModelIndex(); + + int idx = parent->indexOfParent(); + + if (idx == -1) + return QModelIndex(); + + return createIndex(idx, 0, parent); +} + + +int AbstractTimelineModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) + { + int r = m_rootItem->visibleChildCount(); + + return r; + } + + Item *item = itemFromIndex(parent); + if (item) + { + int r = item->visibleChildCount(); + + return r; + } + + return 0; +} + + +int AbstractTimelineModel::columnCount(const QModelIndex &) const +{ + return 2; +} + +} diff --git a/Lts/Common/LtsTimelineClassic/AbstractTimelineModel.h b/Lts/Common/LtsTimelineClassic/AbstractTimelineModel.h new file mode 100755 index 000000000..88daea22a --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/AbstractTimelineModel.h @@ -0,0 +1,112 @@ + + + +#pragma once + +#include "Event.h" +#include "Item.h" +#include + +#include +#include + + +namespace LtsTimelineClassic +{ + + + +class LTSTIMELINECLASSIC_EXPORT AbstractTimelineModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + explicit AbstractTimelineModel( QObject *parent = nullptr); + ~AbstractTimelineModel(); + + void retranslate(); + + + + Item *getRootItem(); + + Event *getEvent(int uniqueId); + + + void invalidateTimeline(); + + + + void refreshData(); + + qint64 highestPositionedItem() const { return m_highestPositionedItem; } + qint64 lowestPositionedItem() const { return m_lowestPositionedItem; } + + +protected: + NGL::INGLLogger &m_logger; + + +protected: + + + void addEvent(Event *event); + + + + QVariant data(const QModelIndex &index, int role) const; + + + + Qt::ItemFlags flags(const QModelIndex &index) const; + + + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + + + Item *itemFromIndex(const QModelIndex &idx) const; + + + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + + + + QModelIndex parent(const QModelIndex &index) const; + + + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + + + + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + + virtual qint64 currentTime() const = 0; + + + qint64 m_highestPositionedItem; + qint64 m_lowestPositionedItem; + bool m_getEventListsRecursive; + Item *m_rootItem; + Item *m_markersItem; + + +signals: + + + void signalContextMenu(const QPoint &position, Event *affectedEvent); + + + + void signalClicked(Event *affectedEvent); + + + void signalToolTip(const QPoint &p, Event *e); + +}; + +} diff --git a/Lts/Common/LtsTimelineClassic/CMakeLists.txt b/Lts/Common/LtsTimelineClassic/CMakeLists.txt new file mode 100755 index 000000000..f61941877 --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/CMakeLists.txt @@ -0,0 +1,82 @@ +# +# CMakeLists.txt for Timeline Library +# +# $Id$ +# $HeadURL$ +# $Author$ +# +# Created by Mike Wille, SLSE, 2017-02-10 +# + +SET(timeline_SRCS + EventDelegate.cpp + EventTools.cpp + Item.cpp + PassiveEventContainer.cpp + TimelineWidget.cpp + TimelineHeaderView.cpp + TimelineIndicator.cpp + TestTimelineModel.cpp + AbstractTimelineModel.cpp + TimelineView.cpp + Event.cpp + EventGraphic.cpp + + EventDelegate.h + EventList.h + EventTools.h + Item.h + PassiveEventContainer.h + TimelineWidget.h + TimelineHeaderView.h + TimelineIndicator.h + AbstractTimelineModel.h + TimelineView.h + #timeline_export.h + Event.h + TestTimelineModel.h + EventGraphic.h + LineMap.h +) + +SET(timeline_UICS + Ui/TimelineWidgetUi.ui +) + +NGL_WRAP_UI(timeline_SRCS ${timeline_UICS}) + +NGL_ADD_LIBRARY2(WS LtsTimelineClassic SHARED ${timeline_SRCS} ${timeline_UICS}) + +NGL_TARGET_INCLUDE_DIRECTORIES(LtsTimelineClassic + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) + +NGL_TARGET_LINK_LIBRARIES(LtsTimelineClassic + PRIVATE + NGLLogger + ngltools # DTG + Qt5::Core + Qt5::Gui + Qt5::Widgets + LtsSessionDataDds +) + + +IF(NEVER) + NGL_ADD_EXECUTABLE(WS LtsTimelineClassicTest main.cpp) + NGL_TARGET_LINK_LIBRARIES(LtsTimelineClassicTest + nglapplication + NGLLogger + Qt5::Core + Qt5::Gui + Qt5::Widgets + LtsTimelineClassic + svcEemCommon + svcEemClient + PdmDataPluginInterface + ) +ENDIF() + +NGL_INSTALL_TARGET(LtsTimelineClassic) + diff --git a/Lts/Common/LtsTimelineClassic/Event.cpp b/Lts/Common/LtsTimelineClassic/Event.cpp new file mode 100755 index 000000000..d2305c973 --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/Event.cpp @@ -0,0 +1,219 @@ + + + +#include "Event.h" + +#include + +#include +#include +#include + +#include + +namespace LtsTimelineClassic +{ +Event::Event(int uniqueId, int typeId, qint64 startTime, + qint64 endTime, int playerId, int partyId, int equipmentId, const QStringList &path, + const EventPaintFunc &paintFunc, const QColor &paintColor, const QJsonObject &detailData, int subEventId, + const LtsUtils::QPoint3D &pos, LtsSession::LtsSession *session) + : m_path(path) + , m_detailData(detailData) + , m_position(pos) + , m_paintFunc(paintFunc) + , m_session(session) + , m_eventColor(paintColor) + , m_startTime(startTime) + , m_endTime(endTime) + , m_uniqueId(uniqueId) + , m_typeId(typeId) + , m_subEventId(subEventId) + , m_playerId(playerId) + , m_partyId(partyId) + , m_equipmentId(equipmentId) + , m_deleted(false) + , m_visible(true) + +{ + checkDuration(); +} + +void Event::setVisible(bool visibility) +{ + if (visibility) + show(); + else + hide(); +} + + +void Event::hide() +{ + this->m_visible = false; +} + + +void Event::show() +{ + this->m_visible = true; +} + +QString Event::getShortActuator() const +{ + return m_path.isEmpty() ? QString() : m_path.last(); +} + + + + + + + + + + + + + + +void Event::setEndT(qint64 newEnd) +{ + m_endTime = newEnd; + + checkDuration(); +} + + +bool Event::checkDuration() +{ + + + + + + + + + + + + + + + + + + + + + return true; +} + + +qint64 Event::duration(bool ignoreLiveEvents) const +{ + if (m_endTime > m_startTime) + { + if (m_duration == -1) + m_duration = m_endTime - m_startTime; + + return m_duration; + } + + if (m_endTime < 0 && !ignoreLiveEvents) + { + qint64 currentTime = (m_session != nullptr) ? m_session->partitionTime() : time(nullptr) ; + qint64 duration = currentTime - m_startTime; + + return duration; + } + + return 0; +} + + +void Event::drawMultipleEventGraphic(QPainter *painter, const QRect &rect) +{ + painter->save(); + painter->setBrush(Qt::white); + + int x = rect.x(); + int y = rect.y(); + int h = rect.height(); + int w = rect.width(); + + + painter->drawRect(rect); + + + int s1 = y + h - (h * 0.4); + int s2 = y + h - (h * 0.2); + + painter->drawLine(QPoint(x, s1), + QPoint(x + w, s1)); + painter->drawLine(QPoint(x, s2), + QPoint(x + w, s2)); + + painter->restore(); +} + +void Event::drawCustomEventGraphicTriangle(QPainter *painter, const QRect &rect) +{ + int x = rect.x(); + int y = rect.y(); + int h = rect.height(); + int w = rect.width(); + + QPolygon pol; + pol.append(QPoint(x + 0, y + h)); + pol.append(QPoint(x + (w / 2), y + 0)); + pol.append(QPoint(x + w, y + h)); + pol.append(QPoint(x + 0, y + h)); + + QPainterPath path; + path.moveTo(x, y); + path.addPolygon(pol); + + painter->drawPath(path); +} + +void Event::drawCustomEventGraphicInvertTriangle(QPainter *painter, const QRect &rect) +{ + int x = rect.x(); + int y = rect.y(); + int h = rect.height(); + int w = rect.width(); + + QPolygon pol; + pol.append(QPoint( x + 0 , y + 0)); + pol.append(QPoint( x+ w , y + 0)); + pol.append(QPoint(x + ( w / 2 ), y + h)); + pol.append(QPoint( x + 0 , y + 0)); + + QPainterPath path; + path.moveTo(x, y); + path.addPolygon(pol); + + painter->drawPath(path); +} +void Event::drawCustomEventGraphicEllipse(QPainter *painter, const QRect &rect) +{ + QPoint ellipsePos = rect.center(); + ellipsePos.rx()++; + ellipsePos.ry()++; + + painter->drawEllipse(ellipsePos, rect.width() / 2, rect.height() / 2); +} + +void Event::drawCustomEventGraphicRect(QPainter *painter, const QRect &rect) +{ + painter->drawRect(rect); +} + + +void Event::drawCustomEventGraphicRoundedRect(QPainter *painter, const QRect &rect) +{ + painter->drawRoundedRect(rect, 50, 50, Qt::RelativeSize); +} + +} diff --git a/Lts/Common/LtsTimelineClassic/Event.h b/Lts/Common/LtsTimelineClassic/Event.h new file mode 100755 index 000000000..45001518f --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/Event.h @@ -0,0 +1,155 @@ + + + +#pragma once + +#include + +#include +#include +#include +#include + +class QPainter; +class QRect; + +namespace LtsSession +{ +class LtsSession; +} + +namespace LtsTimelineClassic +{ + + + +class LTSTIMELINECLASSIC_EXPORT Event +{ +public: + enum ButtonType + { + ActiveEvent, + EventBlock, + }; + + + typedef void (*EventPaintFunc)(QPainter *, const QRect &); + static void drawCustomEventGraphicTriangle(QPainter *painter, const QRect &rect); + static void drawCustomEventGraphicInvertTriangle(QPainter *painter, const QRect &rect); + static void drawCustomEventGraphicEllipse(QPainter *painter, const QRect &rect); + static void drawCustomEventGraphicRect(QPainter *painter, const QRect &rect); + static void drawCustomEventGraphicRoundedRect(QPainter *painter, const QRect &rect); + static void drawMultipleEventGraphic(QPainter *painter, const QRect &rect); + + + Event() = default; + Event(int uniqueId, int typeId, qint64 startTime, qint64 endTime, + int playerId, int partyId, int equipmentId, const QStringList &path, + const EventPaintFunc &paintFunc, const QColor &paintColor, + const QJsonObject &detailData, int subEventId = -1, + const LtsUtils::QPoint3D &pos = LtsUtils::QPoint3D(), + LtsSession::LtsSession *session = nullptr); + + + + + QStringList getActuator() const { return m_path; } + + int typeId() const { return m_typeId; } + int uniqueId() const { return m_uniqueId; } + + int getSubEventID() const; + + QColor getColor() const { return m_eventColor; } + + EventPaintFunc getPaintFunc() const { return m_paintFunc; } + + + + qint64 startTime() const { return m_startTime; } + qint64 endTime() const { return m_endTime; } + + + + + + + + + + const QJsonObject &detailData() const { return m_detailData; } + void setDetailData(const QJsonObject &detailData) { m_detailData = detailData; } + + void setEndT(qint64 newEnd); + + + + qint64 duration(bool ignoreLiveEvents = false) const; + + int playerId() const { return m_playerId; } + void setPlayerId(int playerId) { m_playerId = playerId; } + + int partyId() const { return m_partyId; } + void setPartyId(int teamId) { m_partyId = teamId; } + + int equipmentId() const { return m_equipmentId; } + void setEquipmentId(int eqId) { m_equipmentId = eqId; } + + QString getShortActuator() const; + + bool positionValid() const { return !std::isnan(m_position.x()) && !std::isnan(m_position.y()); } + const LtsUtils::QPoint3D &position() const { return m_position; } + QPointF pos2d() const { return QPointF(m_position.x(), m_position.y()); } + void setPosition(const LtsUtils::QPoint3D &pos) { m_position = pos; } + + + + + void hide(); + + + + void show(); + + + + bool isVisible() const { return m_visible; } + + + + void setVisible(bool visibility = true); + + void setDeleted() { m_deleted = true; } + + bool isDeleted() const { return m_deleted; } + + +protected: + QStringList m_path; + QJsonObject m_detailData; + + LtsUtils::QPoint3D m_position; + EventPaintFunc m_paintFunc = nullptr; + LtsSession::LtsSession *m_session = nullptr; + QColor m_eventColor; + qint64 m_startTime = -1; + qint64 m_endTime = -1; + mutable qint64 m_duration = -1; + + + int m_uniqueId = -1; + int m_typeId = -1; + int m_subEventId = -1; + int m_playerId = -1; + int m_partyId = -1; + int m_equipmentId = -1; + bool m_deleted = false; + bool m_visible = false; + +private: + + + static bool checkDuration(); +}; + +} diff --git a/Lts/Common/LtsTimelineClassic/EventDelegate.cpp b/Lts/Common/LtsTimelineClassic/EventDelegate.cpp new file mode 100755 index 000000000..11929c6a2 --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/EventDelegate.cpp @@ -0,0 +1,62 @@ + + + +#include "EventDelegate.h" +#include "PassiveEventContainer.h" + +#include + +#include + + +namespace LtsTimelineClassic +{ + +EventDelegate::EventDelegate(QWidget *parent) + : QStyledItemDelegate(parent) + , m_liveTime(0) +{ + +} + +QSize EventDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + (void)option; + (void)index; + + return QSize(50, 20); +} + +void EventDelegate::setKnownRanges(const QVector &knownRanges) +{ + m_knownRanges = knownRanges; +} + +void EventDelegate::setLiveTime(qint64 liveTime) +{ + m_liveTime = liveTime; +} + +void EventDelegate::slotAddLine(int posX, qint64 time) +{ + m_lineMap.insert(posX, time); +} + +void EventDelegate::slotResetLineList() +{ + m_lineMap.clear(); +} + +void EventDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + if (index.data().canConvert()) + { + PassiveEventContainer *passiveEventContainer = qvariant_cast(index.data()); + + passiveEventContainer->paint(painter, option, m_lineMap, m_knownRanges); + } + else + QStyledItemDelegate::paint(painter, option, index); +} + +} diff --git a/Lts/Common/LtsTimelineClassic/EventDelegate.h b/Lts/Common/LtsTimelineClassic/EventDelegate.h new file mode 100755 index 000000000..a6ab695c6 --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/EventDelegate.h @@ -0,0 +1,55 @@ + + + +#pragma once + +#include "AbstractTimelineModel.h" +#include "Range.h" + +#include + + +namespace LtsTimelineClassic +{ + + + +class EventDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + + + EventDelegate(QWidget *parent = nullptr); + void setKnownRanges(const QVector &knownRanges); + + + + QSize sizeHint(const QStyleOptionViewItem &option = QStyleOptionViewItem(), const QModelIndex &index = QModelIndex()) const; + + void setLiveTime(qint64 liveTime); +private: + + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + + + + + + LineMap m_lineMap; + QVector m_knownRanges; + qint64 m_liveTime; + +public slots: + + + void slotResetLineList(); + + + + void slotAddLine(int posX, qint64 time); +}; + +} diff --git a/Lts/Common/LtsTimelineClassic/EventGraphic.cpp b/Lts/Common/LtsTimelineClassic/EventGraphic.cpp new file mode 100755 index 000000000..36217f1ce --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/EventGraphic.cpp @@ -0,0 +1,175 @@ + + + + +#include "EventGraphic.h" + +#include +#include +#include +#include +#include +#include + + +#include + + +namespace LtsTimelineClassic +{ + +EventGraphic::EventGraphic(Event *event) + : m_lowest(0) + , m_duration(-1) + , m_isMultipleEvent(false) +{ + m_eventList += event; +} + + +void EventGraphic::addEvent(Event *event) +{ + if (event == nullptr) + return; + + m_eventList += event; + m_isMultipleEvent = true; + m_lowest = 0; + m_duration = -1; +} + + +void EventGraphic::addEventList(const QVector &eventList) +{ + if (eventList.isEmpty()) + return; + + m_eventList += eventList; + m_isMultipleEvent = true; + m_lowest = 0; + m_duration = -1; +} + + +qint64 EventGraphic::getDuration() +{ + if (m_duration != -1) + return m_duration; + + qint64 highestDuration = 0; + qint64 lowest = lowestTime(); + + for (Event *event : qAsConst(m_eventList)) + { + + + + + + + qint64 duration = (event->startTime() - lowest) + event->duration(); + + if (duration > highestDuration) + highestDuration = duration; + } + + if (highestDuration < 0) + { + qWarning() << "duration < 0 !!" << highestDuration; + Q_ASSERT(false); + return 0; + } + + m_duration = highestDuration; + return m_duration; +} + + +qint64 EventGraphic::lowestTime() const +{ + if (m_lowest != 0) + return m_lowest; + + auto i = std::min_element(m_eventList.begin(), m_eventList.end(), [](const Event *a, const Event *b) + { + return a->startTime() < b->startTime(); + }); + + if (i == m_eventList.end()) + return 0; + + m_lowest = (*i)->startTime(); + return m_lowest; +} + + +bool EventGraphic::isMultipleEvent() const +{ + return m_isMultipleEvent; +} + + +void EventGraphic::drawEventGraphic(QPainter *painter, const QRect &rect) const +{ + painter->save(); + + painter->setPen(QPen(QBrush(QGuiApplication::palette().text()), 1.0)); + + if (isMultipleEvent()) + Event::drawMultipleEventGraphic(painter, rect); + else + { + Event *e = m_eventList.first(); + + if (e == nullptr) + { + painter->restore(); + return; + } + + painter->setBrush(QBrush(e->getColor())); + + if (e->getPaintFunc() == nullptr) + { + painter->restore(); + return; + } + + e->getPaintFunc()(painter, rect); + } + + painter->restore(); +} + + +void EventGraphic::drawDurationBackground(QPainter *painter, const QRect &rect) const +{ + painter->save(); + if (!isMultipleEvent()) + { + Event *e = m_eventList.first(); + + if (e == nullptr) + { + painter->restore(); + return; + } + + painter->setBrush(QBrush(e->getColor())); + painter->setPen(QPen(QBrush(e->getColor()), 0)); + } + else + { + painter->setBrush(QBrush(QColor("white"))); + } + + painter->drawRect(rect.adjusted(0, 0, -2, 0)); + + painter->setPen(QPen(QBrush(QGuiApplication::palette().text()), 1.0)); + + painter->drawLine(rect.topLeft(), rect.topRight()); + painter->drawLine(rect.bottomLeft() + QPoint(0, 1), rect.bottomRight() + QPoint(1, 1)); + painter->restore(); +} + +} diff --git a/Lts/Common/LtsTimelineClassic/EventGraphic.h b/Lts/Common/LtsTimelineClassic/EventGraphic.h new file mode 100755 index 000000000..969ce88c7 --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/EventGraphic.h @@ -0,0 +1,48 @@ + + + + +#pragma once + +#include "EventList.h" +#include "Event.h" +#include + +namespace LtsTimelineClassic +{ + +class LTSTIMELINECLASSIC_EXPORT EventGraphic +{ +public: + + enum Action + { + ACTION_LEFT_CLICKED, + ACTION_RIGHT_CLICKED, + ACTION_TOOLTIP, + }; + + explicit EventGraphic(Event *event); + + bool isMultipleEvent() const; + + const QVector eventList() const { return m_eventList; } + void addEventList(const QVector &eventList); + void addEvent(Event *event); + qint64 lowestTime() const; + qint64 getDuration(); + + void drawEventGraphic(QPainter *painter, const QRect &rect) const; + void drawDurationBackground(QPainter *painter, const QRect &rect) const; + + +private: + EventList m_eventList; + + mutable qint64 m_lowest; + mutable qint64 m_duration; + bool m_isMultipleEvent; + +}; + +} diff --git a/Lts/Common/LtsTimelineClassic/EventList.h b/Lts/Common/LtsTimelineClassic/EventList.h new file mode 100755 index 000000000..bb76db606 --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/EventList.h @@ -0,0 +1,17 @@ + + + +#pragma once + +#include + +namespace LtsTimelineClassic +{ + +class Event; +using EventList = QVector; + +class EventGraphic; +using EventGraphicList = QVector; + +} diff --git a/Lts/Common/LtsTimelineClassic/EventTools.cpp b/Lts/Common/LtsTimelineClassic/EventTools.cpp new file mode 100755 index 000000000..61fd7415a --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/EventTools.cpp @@ -0,0 +1,420 @@ + + + +#include "EventTools.h" +#include "EventGraphic.h" +#include "Event.h" + +#include +#include +#include +#include + + +namespace LtsTimelineClassic +{ + +#define logger NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Timeline.EventTools") +#ifdef LTS_DEBUG + +#endif +#ifdef VERBOSE + #define dbg_print(x) qWarning() << #x << x +#else + #define dbg_print(x) (void)x; +#endif + +qint64 EventTools::s_minAdjusted = 0; +qint64 EventTools::s_maxAdjusted = 1; +qint64 EventTools::s_minTimeline = 0; +qint64 EventTools::s_maxTimeline = 1; +qint64 EventTools::s_sbPos = 0; +qint64 EventTools::s_zoomFactor = 1; +int EventTools::s_paintMin = 0; +int EventTools::s_paintDiff = 0; +bool EventTools::s_isValid = false; +qint64 EventTools::s_diff = 0; +int EventTools::s_sectionHeight = 0; +int EventTools::s_test = 0; +qint64 EventTools::s_blockSizeTime = 0; +bool EventTools::s_inUse = false; + + + + +void EventTools::setValues(const qint64 min, const qint64 max, const qint64 sbPos, const qint64 zoomFactor) +{ + if (s_minTimeline == min && s_maxTimeline == max && s_sbPos == sbPos && s_zoomFactor == zoomFactor) + return; + + s_sbPos = sbPos; + s_zoomFactor = zoomFactor; + + + + + s_isValid = false; + + s_minAdjusted = s_sbPos; + + double d_max = s_minAdjusted; + d_max += s_zoomFactor; + + s_maxAdjusted = d_max; + s_diff = s_maxAdjusted - s_minAdjusted; + + s_minTimeline = min; + s_maxTimeline = max - EventTools::s_diff + (EventTools::getBlockSizeTime() * 2); + + recalcBlockSizeTime(); + + if (min > 0 && max > 1) + { + s_isValid = true; + + + } + else + { + + } + +#ifdef VERBOSE + qWarning() << "\r\n\r\nEventTools::setValues: " << "\r\n" + << "s_sbPos" << s_sbPos << "\r\n" + << "s_zoomFactor" << s_zoomFactor << "\r\n" + << "s_minTimeline" << s_minTimeline << "\r\n" + << "s_maxTimeline" << s_maxTimeline << "\r\n" + << "s_isValid" << s_isValid << "\r\n" + << "s_minAdjusted" << s_minAdjusted << "\r\n" + << "s_maxAdjusted" << (long)s_maxAdjusted << "\r\n" + << "s_diff" << s_diff << "\r\n" + ; +#endif +} + + +void EventTools::setSection(const int x, const int length) +{ + s_paintMin = x; + s_paintDiff = length; + +} + + +bool EventTools::isValid() +{ +#ifdef VERBOSE + if (!s_isValid) + qWarning() << "<<<< INVALID EVENT TOOLS!!"; + + qWarning() << "\r\nEventTools::isValid: " << "\r\n" + << "s_isValid" << s_isValid << "\r\n" + ; +#endif + + return s_isValid; +} + +qint64 EventTools::getAdjustedMin() +{ + if (!isValid()) + return 0; + + return s_minAdjusted; +} + +qint64 EventTools::getAdjustedMax() +{ + if (!isValid()) + return 1; + + return s_maxAdjusted; +} + +qint64 EventTools::getTimelineMin() +{ + if (!isValid()) + return 0; + + return s_minTimeline; +} + +qint64 EventTools::getScrollbarPosition() +{ + return s_sbPos; +} + +qint64 EventTools::getTimelineMax() +{ + if (!isValid()) + return 1; + + return s_maxTimeline; +} + +int EventTools::getEventPosition(qint64 eventTime) +{ + qint64 min = s_minAdjusted; + + qint64 diff = s_diff; + + + + + if (diff == 0) + { +#ifdef VERBOSE + qWarning() << "\n\ngetEventPosition: diff == 0\n"; +#endif + return 0; + } + + double pDiff = s_paintDiff; + double pMin = s_paintMin; + + double x1 = eventTime; + x1 -= min; + x1 /= diff; + + double x2 = x1; + x2 *= pDiff; + x2 += pMin; + +#ifdef VERBOSE + qWarning() << "\r\n\r\nEventTools::getEventPosition: " << "\r\n" + << "min" << min << "\r\n" + << "diff" << diff << "\r\n" + << "pDiff" << pDiff << "\r\n" + << "pMin" << pMin << "\r\n" + << (long)x1 << "[x1]" << "= (" << eventTime << "[eventTime]" << "-" << min << "[min]" << ") /" << diff << "[diff]" << "\r\n" + << (long)x2 << "[x2]" << "= (" << (long)x1 << "[x1]" << "*" << pDiff << "[pDiff]" << ") +" << pMin << "[pMin]" << "\r\n" + ; + + dbg_print(min); +#endif + + return qRound(x2); +} + +qint64 EventTools::getEventTime(double eventPosition) +{ + if (!isValid()) + { + + return 0; + } + + if (eventPosition > 999999) + { + + WARNING_LOG(logger) << "getEventTime: pos > 999999 => returning 0" ; + return 0; + } + + double min = s_minAdjusted; + double diff = s_diff; + double pDiff = s_paintDiff; + + if (pDiff < 1) + { + + WARNING_LOG(logger) << "getEventTime: pDiff < 1" ; + return 0; + } + + double pMin = s_paintMin; + + double x1 = eventPosition; + x1 -= pMin; + x1 /= pDiff; + + double x2 = x1; + x2 *= diff; + x2 += min; + + return qRound64(x2); +} + + +void EventTools::drawMarkLines(QPainter *painter, const EventList &eventList, int viewHeight) +{ + if (!isValid()) + return; + + + + for (int k = 0; k < eventList.count(); k++) + { + Event *e = eventList.at(k); + int pos = getEventPosition(e->startTime()); + + if (pos < (s_paintMin) || pos > (s_paintMin + s_paintDiff)) + continue; + + painter->drawLine(pos, 0, pos, viewHeight); + } +} + + +void EventTools::recalcBlockSizeTime() +{ + s_blockSizeTime = _getBlockSizeTime(); +} + + +qint64 EventTools::_getBlockSizeTime() +{ + + int startPos = getEventPosition(s_minAdjusted); + int endPos = getEventPosition(s_maxAdjusted); + + int posDiff = endPos - startPos; + + if (posDiff < 1) + { + + return 0; + } + + qint64 diff = s_maxAdjusted - s_minAdjusted; + + double pixelWidthInSeconds = diff; + pixelWidthInSeconds /= posDiff; + + int eventGraphicsWidth = eventWidth(); + qint64 result = eventGraphicsWidth; result *= pixelWidthInSeconds; + + #ifdef VERBOSE + qWarning() << "\r\n\r\nEventTools::recalcBlockSizeTime: " << "\r\n" + << "minAdj" << s_minAdjusted << "\r\n" + << "maxAdj" << s_maxAdjusted << "\r\n" + << "endPos" << endPos << "\r\n" + << "diff" << diff << "\r\n" + << "pixelWidthInSeconds" << pixelWidthInSeconds << "\r\n" + << "eventGraphicsWidth" << eventGraphicsWidth << "\r\n" + << "result" << result << "\r\n" + << "(duration > result) ?" << (duration > result) + ; + #endif + + return result; +} + + +qint64 EventTools::getBlockSizeTime(qint64 duration) +{ + if (duration < s_blockSizeTime) + return s_blockSizeTime; + else + return duration; +} + + +void EventTools::drawEvents(QPainter *painter, const EventGraphicList &eventGraphicList, const int verticalPosition ) +{ + painter->save(); + + + painter->setClipping(true); + painter->setClipRect(QRect(s_paintMin, verticalPosition, s_paintDiff, s_sectionHeight)); + + + for (int i = 0; i < eventGraphicList.count(); i++) + { + EventGraphic *eg = eventGraphicList.at(i); + + if (!eg) + { + + + continue; + } + + qint64 eventStart = eg->lowestTime(); + + if (eventStart == 0) + continue; + + + +#ifdef VERBOSE + qWarning() << "\r\n\r\nEventTools::drawEvents" << "\r\n" + << "eventStart" << eventStart << "\r\n"; +#endif + + qint64 eventDuration = eg->getDuration(); + + int newEventPosition = getEventPosition(eventStart); + + int newEventEndPosition = 0; + int finalEventEndPos = 0; + + int eventGraphicsWidth = eventWidth(); + + if (eventDuration > 1) + { + qint64 eventPosition = eventStart; + eventPosition += eventDuration; + newEventEndPosition = getEventPosition(eventPosition); + finalEventEndPos = GetEndPos(newEventEndPosition, eventGraphicsWidth); + } + else + { + newEventEndPosition = newEventPosition; + } + + + if (newEventPosition + (newEventEndPosition - newEventPosition) < ((-eventGraphicsWidth) + s_paintMin) || + newEventPosition > (s_paintMin + s_paintDiff)) + { +#ifdef VERBOSE + qWarning() << "\n\ndrawEvents: boundary check result was negative\n" << + " newEventPosition=" << newEventPosition << "\n" << + " newEventEndPosition=" << newEventPosition << "\n" << + " eventGraphicsWidth=" << eventGraphicsWidth << "\n" << + " s_paintMin=" << s_paintMin << "\n" << + " s_paintDiff=" << s_paintDiff << "\n"; +#endif + continue; + } + + + + int finalEventPos = newEventPosition; + + QRect eventGraphicRect(finalEventPos, verticalPosition + 2, eventGraphicsWidth, s_sectionHeight - 6); + + + + eg->drawEventGraphic(painter, eventGraphicRect); + +#ifdef VERBOSE + qWarning() << "drawEvents > actually draw an event at " + "x=" << eventGraphicRect.x() << " " + "y=" << eventGraphicRect.y() << " " + "eventStart=" << eventStart ; +#endif + + if (eventDuration > 1 && + (finalEventEndPos - 1) > finalEventPos) + { + QRect eventGraphicEndRect(finalEventEndPos, verticalPosition + 2, eventGraphicsWidth, s_sectionHeight - 6); + eg->drawEventGraphic(painter, eventGraphicEndRect); + + + + QRect eventWholeRect(newEventPosition + (eventGraphicsWidth / 2) + + , verticalPosition + 2 + , (newEventEndPosition - newEventPosition) - (eventGraphicsWidth) + , s_sectionHeight - 6); + + eg->drawDurationBackground(painter, eventWholeRect); + } + } + + painter->restore(); +} + +} diff --git a/Lts/Common/LtsTimelineClassic/EventTools.h b/Lts/Common/LtsTimelineClassic/EventTools.h new file mode 100755 index 000000000..0919c108b --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/EventTools.h @@ -0,0 +1,152 @@ + + + +#pragma once + +#include "EventList.h" + +#include +#include + +#include + + +class QPainter; + + +namespace LtsTimelineClassic +{ + + + +class LTSTIMELINECLASSIC_EXPORT EventTools +{ + static const int s_EventTopPadding = 2; + static const int s_EventHeightPadding = -6; + static const int s_ButtonTopPadding = s_EventTopPadding + 2; + static const int s_ButtonHeightPadding = s_EventHeightPadding + 7; + +public: + + static int s_test; + + + static qint64 fromMsecsToSeconds(qint64 msecs) + { + return (msecs / 1000); + } + + static qint64 fromSecondsToMsecs(qint64 minutes) + { + return (minutes * 1000); + } + + + static qint64 getBlockSizeTime(qint64 duration = 0); + + + + static void setValues(const qint64 min, const qint64 max, const qint64 sbPos, const qint64 zoomFactor); + + + + + + + static void setSection(const int x, const int length); + + + + static bool isValid(); + + + + static bool isRelative(); + + + + static int getEventPosition(qint64 eventTime); + + + + static qint64 getAdjustedMin(); + + + + static qint64 getAdjustedMax(); + + + + static qint64 getTimelineMin(); + + + + static qint64 getTimelineMax(); + + static qint64 getScrollbarPosition(); + + static void recalcBlockSizeTime(); + + + + static qint64 getEventTime(double eventPosition); + + + + static void drawEvents(QPainter *painter, const EventGraphicList &eventGraphicList, const int verticalPosition); + + + + static void drawMarkLines(QPainter *painter, const EventList &eventList, int viewHeight); + + + + + + + + + + + + static inline int eventWidth() + { + return s_sectionHeight / 2; + } + + + + static inline int GetEndPos(int position, int graphicsWidth) + { + + return position - graphicsWidth - 1; + } + + static inline float zoomFactor() { return s_zoomFactor; } + + static inline void setSectionHeight(int sectionHeight) { s_sectionHeight = sectionHeight; } + static bool s_inUse; + +private: + static qint64 _getBlockSizeTime(); + + + + + static qint64 s_minAdjusted; + static qint64 s_maxAdjusted; + static qint64 s_minTimeline; + static qint64 s_maxTimeline; + static qint64 s_sbPos; + static qint64 s_zoomFactor; + static int s_paintMin; + static int s_paintDiff; + static bool s_isValid; + static int s_sectionHeight; + static qint64 s_blockSizeTime; + +public: + static qint64 s_diff; +}; + +} diff --git a/Lts/Common/LtsTimelineClassic/Item.cpp b/Lts/Common/LtsTimelineClassic/Item.cpp new file mode 100755 index 000000000..8cb17e29f --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/Item.cpp @@ -0,0 +1,388 @@ + + + +#include + +#include "Item.h" +#include "EventTools.h" +#include "EventGraphic.h" + + +#ifdef LTS_DEBUG + +#endif + + +namespace LtsTimelineClassic +{ + +Item::Item(const QString &name, const QString &identifier) + : m_name(name) + , m_identifier(identifier) + , m_children(nullptr) + , m_parent(nullptr) + , m_pevcon(nullptr) + , m_eventListChanged(true) +{ + +} + + +Item::~Item() +{ + delete m_pevcon; + + if (m_children) + { + qDeleteAll(*m_children); + delete m_children; + } + + qDeleteAll(m_eventList); +} + + +PassiveEventContainer *Item::getPevcon() +{ + if (!m_pevcon) + m_pevcon = new PassiveEventContainer(m_name == QStringLiteral("Markers")); + + if (m_eventListChanged) + { + m_eventListChanged = false; + m_pevcon->setEventList(getEventGraphics()); + } + + return m_pevcon; +} + + +const QString Item::path() const +{ + if (m_parent == nullptr) + return m_name; + + return QStringLiteral("%1/%2").arg(m_parent->path(), m_name); +} + + +const QString &Item::getName() const +{ + return m_name; +} + + +Item *Item::getParent() const +{ + return m_parent; +} + + +ItemList Item::getFullItemList(bool recursive) const +{ + ItemList il; + + if (m_children == nullptr) + return il; + + il += *m_children; + + if (!recursive) + return il; + + auto end = m_children->constEnd(); + for (auto i = m_children->constBegin(); i != end; ++i) + { + il += (*i)->getFullItemList(recursive); + } + + return il; +} + + + + + + + + + + + + + + + + + +const EventGraphicList Item::getCurrentEGLFromPevcon() const +{ + if (m_pevcon == nullptr) + return EventGraphicList(); + return m_pevcon->getEventGraphicList(); +} + + +const EventGraphicList Item::getEventGraphics() const +{ + EventGraphicList egl; + EventList el = getFullEventList(true, false); + + std::sort(el.begin(), el.end(), [](const Event *a, const Event *b) + { + return a->startTime() < b->startTime(); + }); + + for (auto i1 = el.begin(); i1 != el.end(); ++i1) + { + Event *e1 = (*i1); + + if (e1->isDeleted()) + continue; + + qint64 egStart = e1->startTime(); + qint64 egEnd = egStart + EventTools::getBlockSizeTime(e1->duration()); + qint64 blockSizeTime = EventTools::getBlockSizeTime(); + bool egChanged = false; + + EventGraphic *eg = new EventGraphic(e1); + egl += eg; + + auto i2 = i1 + 1; + for (; i2 != el.end(); ++i2) + { + if (egChanged) + { + egEnd = egStart + eg->getDuration(); + + if (egEnd - egStart < blockSizeTime) + egEnd = egStart + blockSizeTime; + + egChanged = false; + } + + Event *e2 = (*i2); + + if (e2->isDeleted()) + continue; + + qint64 start2 = e2->startTime(); + + if (start2 >= egStart && egEnd >= start2) + { + eg->addEvent(e2); + egChanged = true; + } + else + { + + --i2; + break; + } + } + + if (i2 == el.end()) + break; + i1 = i2; + + } + + return egl; +} + + +EventList Item::getFullEventList(bool recursive, bool getHiddenEvents) const +{ + EventList el; + + if (getHiddenEvents) + { + el = m_eventList; + } + else + { + el.reserve(m_eventList.count()); + + for (Event *e : m_eventList) + { + if (e == nullptr || !e->isVisible()) + continue; + + el.append(e); + } + } + + if (!m_children || !recursive) + return el; + + for (const Item *item : qAsConst(*m_children)) + el += item->getFullEventList(recursive, getHiddenEvents); + + return el; +} + + +void Item::setParent(Item *parent) +{ + m_parent = parent; +} + + +void Item::addEvent(Event *event) +{ + m_eventList.append(event); + setEventListChanged(); +} + + +void Item::setEventListChanged() +{ + m_eventListChanged = true; + + if (m_parent != nullptr) + { + + m_parent->setEventListChanged(); + } +} + + +bool Item::removeEvent(int eventId) +{ + for (auto i = m_eventList.begin(); i != m_eventList.end(); ++i) + { + Event *event = *i; + + if (event->uniqueId() == eventId) + { + m_eventList.erase(i); + setEventListChanged(); + + return true; + } + } + + return false; +} + + +void Item::addItem(Item *item) +{ + if (!m_children) + m_children = new ItemList(); + + item->setParent(this); + m_children->append(item); +} + + +bool Item::hasVisibleEvents() const +{ + + if (!m_eventList.isEmpty() && + std::find_if(m_eventList.constBegin(), m_eventList.constEnd(), [](Event *e) { return e->isVisible(); }) != m_eventList.constEnd()) + return true; + + + if (m_children == nullptr) + return false; + + + + for (auto i = m_children->constBegin(); i != m_children->constEnd(); ++i) + { + Item *item = *i; + + if (item->hasVisibleEvents()) + return true; + } + + + return false; +} + + +Item *Item::child(int row) const +{ + if (m_children == nullptr) + return nullptr; + + int idx = 0; + + + for (auto i = m_children->constBegin(); i != m_children->constEnd(); ++i) + { + Item *item = *i; + + if (item->hasVisibleEvents()) + { + if (row == idx) + return item; + else + idx++; + } + } + + return nullptr; +} + + +int Item::childCount() const +{ + if (m_children) + return m_children->count(); + + return 0; +} + + +int Item::visibleChildCount() const +{ + if (m_children == nullptr) + return 0; + + int result = 0; + + for (auto i = m_children->constBegin(); i != m_children->constEnd(); ++i) + { + Item *item = *i; + + if (item->hasVisibleEvents()) + result++; + } + + return result; +} + + +ItemList *Item::getChildren() +{ + return m_children; +} + + +int Item::indexOfParent() const +{ + if (m_parent == nullptr || m_parent->getChildren() == nullptr) + return -1; + + int idx = 0; + + ItemList *parentItemList = m_parent->getChildren(); + + + for (auto i = parentItemList->constBegin(); i != parentItemList->constEnd(); ++i) + { + Item *item = *i; + + if (item == this) + return idx; + + idx++; + } + + Q_ASSERT(false); + return -1; +} + +} diff --git a/Lts/Common/LtsTimelineClassic/Item.h b/Lts/Common/LtsTimelineClassic/Item.h new file mode 100755 index 000000000..346351095 --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/Item.h @@ -0,0 +1,128 @@ + + + +#pragma once + +#include +#include "EventList.h" +#include "PassiveEventContainer.h" + +namespace LtsTimelineClassic +{ + +class Item; +using ItemList = QList; + + + +class LTSTIMELINECLASSIC_EXPORT Item +{ + + +public: + + Item(const Item &) = delete; + Item &operator=(const Item &) = delete; + const EventGraphicList getEventGraphics() const; + + + + explicit Item(const QString &name, const QString &identifier = QString()); + + + + ~Item(); + + + + void addItem(Item *item); + + bool removeEvent(int eventId); + + void setEventListChanged(); + + const QString path() const; + + + + + + + + PassiveEventContainer *getPevcon(); + + const EventGraphicList getCurrentEGLFromPevcon() const; + + + + + + + void setParent(Item *parent); + + + + const QString &getName() const; + + void setName(const QString &name) { m_name = name; } + + const QString &getIdentifier() const { return m_identifier; } + + + + ItemList *getChildren(); + + + + + + + + Item *getParent() const; + + + + void addEvent(Event *event); + + + + EventList getFullEventList(bool recursive = true, bool getHiddenEvents = false) const; + + + + ItemList getFullItemList(bool recursive = true) const; + + + + Item *child(int row) const; + + + + int indexOfParent() const; + + + + + + + int childCount() const; + + int visibleChildCount() const; + + bool hasVisibleEvents() const; + + + +private: + QString m_name; + QString m_identifier; + ItemList *m_children; + Item *m_parent; + PassiveEventContainer *m_pevcon; + EventList m_eventList; + + + bool m_eventListChanged; +}; + +} diff --git a/Lts/Common/LtsTimelineClassic/LineMap.h b/Lts/Common/LtsTimelineClassic/LineMap.h new file mode 100755 index 000000000..c97b47cd4 --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/LineMap.h @@ -0,0 +1,13 @@ + + + +#pragma once + +#include + +namespace LtsTimelineClassic +{ + +using LineMap = QMap; + +} diff --git a/Lts/Common/LtsTimelineClassic/PassiveEventContainer.cpp b/Lts/Common/LtsTimelineClassic/PassiveEventContainer.cpp new file mode 100755 index 000000000..428e0033c --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/PassiveEventContainer.cpp @@ -0,0 +1,133 @@ + + + +#include "PassiveEventContainer.h" +#include "EventGraphic.h" +#include "EventTools.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include + + +namespace LtsTimelineClassic +{ + +PassiveEventContainer::PassiveEventContainer(bool ignoreKnownRanges, QObject *parent) + : QObject(parent) + , m_ignoreKnownRanges(ignoreKnownRanges) +{ + +} + + +PassiveEventContainer::~PassiveEventContainer() +{ + qDeleteAll(m_eventGraphicList); +} + + +EventGraphicList PassiveEventContainer::getEventGraphicList() const +{ + return m_eventGraphicList; +} + + +void PassiveEventContainer::setEventList(const EventGraphicList &eventGraphicList) +{ + qDeleteAll(m_eventGraphicList); + + m_eventGraphicList = eventGraphicList; +} + + +void PassiveEventContainer::paint(QPainter *painter, const QStyleOptionViewItem &option, const LineMap &lineMap, + const QVector &knownRanges) const +{ + EventTools::s_test++; + + bool alternate = option.features.testFlag(QStyleOptionViewItem::ViewItemFeature::Alternate); + const QColor bg = QColor(alternate ? QRgb(0xF6D8CE) : QRgb(0xFBF2EF)); + + QColor cachedBg(Qt::white); + if (alternate) + cachedBg = cachedBg.darker(110); + + + painter->fillRect(option.rect, QBrush(bg)); + + int posX1 = option.rect.x(); + int posX2 = option.rect.right(); + int posY1 = option.rect.y(); + int posY2 = option.rect.bottom(); + int h = posY2 - posY1 + 1; + + painter->save(); + painter->setClipping(true); + painter->setClipRect(option.rect.adjusted(0, 0, 1, 1)); + + int eventWidth = EventTools::eventWidth(); + + + if (m_ignoreKnownRanges) + { + painter->fillRect(QRect(posX1, posY1, posX2 - posX1, h), cachedBg); + } + else + { + for (const auto &range : knownRanges) + { + int rangeStart = EventTools::getEventPosition(range.start); + int rangeEnd = EventTools::getEventPosition(range.end); + + + if (rangeEnd >= posX1 && rangeStart <= posX2) + painter->fillRect(QRect(rangeStart, posY1, rangeEnd - rangeStart + eventWidth + 1, h), cachedBg); + } + } + + + + + + + + + + + painter->restore(); + + + + + + for (auto i = lineMap.constBegin(); i != lineMap.constEnd(); ++i) + { + int posX = i.key(); + + painter->setPen(QPen(QBrush(QGuiApplication::palette().shadow()), 1.0)); + + if (posX > option.rect.x() && + posX < (option.rect.x() + option.rect.width())) + painter->drawLine(posX, posY1, posX, posY2); + } + + + + + + + + + EventTools::drawEvents(painter, m_eventGraphicList, option.rect.y()); + +} + +} diff --git a/Lts/Common/LtsTimelineClassic/PassiveEventContainer.h b/Lts/Common/LtsTimelineClassic/PassiveEventContainer.h new file mode 100755 index 000000000..46dcf1fb8 --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/PassiveEventContainer.h @@ -0,0 +1,53 @@ + + + +#pragma once + +#include "EventList.h" +#include "LineMap.h" +#include "Range.h" + +#include + + +class QPainter; +class QStyleOptionViewItem; + + +namespace LtsTimelineClassic +{ + + + +class PassiveEventContainer: public QObject +{ + Q_OBJECT + +public: + + + explicit PassiveEventContainer(bool ignoreKnownRanges, QObject *parent = nullptr); + + ~PassiveEventContainer(); + + + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const LineMap &lineMap, const QVector &knownRanges) const; + + + + EventGraphicList getEventGraphicList() const; + + + + void setEventList(const EventGraphicList &eventGraphicList); + +private: + EventGraphicList m_eventGraphicList; + bool m_ignoreKnownRanges; + +}; + +} + +Q_DECLARE_METATYPE(LtsTimelineClassic::PassiveEventContainer *) diff --git a/Lts/Common/LtsTimelineClassic/Range.h b/Lts/Common/LtsTimelineClassic/Range.h new file mode 100755 index 000000000..8dab924a9 --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/Range.h @@ -0,0 +1,18 @@ + + + +#pragma once + +#include + +namespace LtsTimelineClassic +{ +struct Range +{ + qint64 start = 0; + qint64 end = 0; + Range() = default; + Range(qint64 s, qint64 e) : start(s), end(e) {} +}; + +} diff --git a/Lts/Common/LtsTimelineClassic/TestTimelineModel.cpp b/Lts/Common/LtsTimelineClassic/TestTimelineModel.cpp new file mode 100755 index 000000000..50069ed82 --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/TestTimelineModel.cpp @@ -0,0 +1,40 @@ + + + + +#include "TestTimelineModel.h" + +#include "EventGraphic.h" + +#include +#include + + +namespace LtsTimelineClassic +{ + +TestTimelineModel::TestTimelineModel() + : AbstractTimelineModel() +{ + +} + +void TestTimelineModel::addTestEvent(Event *event) +{ + AbstractTimelineModel::addEvent(event); +} + +void TestTimelineModel::slotEventAction(EventGraphic *eg, int actionType) +{ + (void)actionType; + + QString content = tr("Event count: %1").arg(QString::number(eg->eventList().count())); + + for (int i = 0; i < eg->eventList().count(); i++) + { + content += QLatin1String("\r\nTime: "); + content += QString::number(eg->eventList().at(i)->startTime()); + } +} + +} diff --git a/Lts/Common/LtsTimelineClassic/TestTimelineModel.h b/Lts/Common/LtsTimelineClassic/TestTimelineModel.h new file mode 100755 index 000000000..98ad77c24 --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/TestTimelineModel.h @@ -0,0 +1,25 @@ + + + +#pragma once + +#include "AbstractTimelineModel.h" + + +namespace LtsTimelineClassic +{ + +class LTSTIMELINECLASSIC_EXPORT TestTimelineModel : public AbstractTimelineModel +{ + Q_OBJECT + +public: + TestTimelineModel(); + void addTestEvent(Event *event); + + +private slots: + void slotEventAction(EventGraphic *, int); +}; + +} diff --git a/Lts/Common/LtsTimelineClassic/TimelineHeaderView.cpp b/Lts/Common/LtsTimelineClassic/TimelineHeaderView.cpp new file mode 100755 index 000000000..5cb11e167 --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/TimelineHeaderView.cpp @@ -0,0 +1,149 @@ + + + +#include "TimelineWidget.h" +#include "TimelineHeaderView.h" + +#include +#include +#include +#include +#include +#include + + +namespace LtsTimelineClassic +{ + +TimelineHeaderView::TimelineHeaderView(QWidget *parent) + : QHeaderView(Qt::Horizontal, parent) + , m_posMarkTime(0) + , m_paintingRange(rect()) + , m_liveTime(0) +{ + setSectionsClickable(true); + + setStretchLastSection(true); + + connect(this, &TimelineHeaderView::sectionResized, + this, &TimelineHeaderView::slotSectionResized); + +} + +#define GET_LOGGER NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Timeline.HeaderView") + +void TimelineHeaderView::setPosMarkTime(qint64 time) +{ + m_posMarkTime = time; + refreshData(); +} + +void TimelineHeaderView::setLiveTime(qint64 time) +{ + m_liveTime = time; + refreshData(); +} + +void TimelineHeaderView::setPosMarkPos() +{ + + + if (m_posMarkTime > 0) + { + int posMarkPos = EventTools::getEventPosition(m_posMarkTime); + emit signalSetPosMarkPos(posMarkPos); + refreshData(); + } +} + +void TimelineHeaderView::refreshData() +{ + if (EventTools::getTimelineMin() > 0 && EventTools::getTimelineMax() > 1) + { + updateSection(1); + updateSection(0); + } +} + +void TimelineHeaderView::slotSectionResized(int logicalIndex, int oldSize, int newSize) +{ + (void)oldSize; + + if (logicalIndex == 1) + m_paintingRange = QRect(0, 0, newSize, height()); + + refreshData(); +} + +void TimelineHeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const +{ + painter->save(); + QHeaderView::paintSection(painter, rect, logicalIndex); + painter->restore(); + + + if (logicalIndex == 1) + { + int h = (rect.height() / 3); + int y = rect.height() - h; + int w = h; + + + + painter->save(); + painter->setClipping(true); + + if (m_posMarkTime != 0) + { + double posMark = EventTools::getEventPosition(m_posMarkTime); + int x = + posMark; + + if (x >= sectionPosition(1)) + { + painter->setPen(QPen(QGuiApplication::palette().color(QPalette::Inactive, QPalette::Highlight))); + painter->setBrush(QGuiApplication::palette().highlight()); + + QPolygon pol; + pol.append(QPoint(x + (-w / 2), y + 0)); + pol.append(QPoint(x + 0, y + h)); + pol.append(QPoint(x + (w / 2), y + 0)); + pol.append(QPoint(x + (-w / 2), y + 0)); + + QPainterPath path; + path.moveTo(x, y); + path.addPolygon(pol); + + painter->drawPath(path); + } + } + + + if (m_liveTime != 0) + { + int posLive = EventTools::getEventPosition(m_liveTime); + int x = posLive; + + if (x >= sectionPosition(1)) + { + painter->setPen(QPen(QColor("green"))); + painter->setBrush(QBrush(QColor("green"))); + + QPolygon pol; + pol.append(QPoint(x + (-w / 2), y + 0)); + pol.append(QPoint(x + 0, y + h)); + pol.append(QPoint(x + (w / 2), y + 0)); + pol.append(QPoint(x + (-w / 2), y + 0)); + + QPainterPath path; + path.moveTo(x, y); + path.addPolygon(pol); + + painter->drawPath(path); + } + } + + painter->restore(); + } +} + +} diff --git a/Lts/Common/LtsTimelineClassic/TimelineHeaderView.h b/Lts/Common/LtsTimelineClassic/TimelineHeaderView.h new file mode 100755 index 000000000..35267a358 --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/TimelineHeaderView.h @@ -0,0 +1,63 @@ + + + +#pragma once + +#include "EventTools.h" + +#include + + +class QPainter; + +namespace LtsTimelineClassic +{ + + + +class TimelineHeaderView : public QHeaderView +{ + Q_OBJECT + +public: + explicit TimelineHeaderView(QWidget *parent = nullptr); + + + + void refreshData(); + void setLiveTime(qint64 time); + +private: + qint64 m_posMarkTime; + QRect m_paintingRange; + + qint64 m_liveTime; + +protected: + + + void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const; + +signals: + + + void signalSetPosMarkPos(int pos); + +public slots: + + + + void slotSectionResized(int logicalIndex, int oldSize, int newSize); + + + + void setPosMarkTime(qint64 time); + +private: + + + + void setPosMarkPos(); +}; + +} diff --git a/Lts/Common/LtsTimelineClassic/TimelineIndicator.cpp b/Lts/Common/LtsTimelineClassic/TimelineIndicator.cpp new file mode 100755 index 000000000..4247b90e9 --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/TimelineIndicator.cpp @@ -0,0 +1,519 @@ + + + +#include "TimelineWidget.h" +#include "TimelineIndicator.h" +#include "EventTools.h" + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + + +#ifdef LTS_DEBUG + +#endif +#ifdef VERBOSE + #define dbg_print(x) qWarning() << #x << x +#else + #define dbg_print(x) (void)x; +#endif + +namespace LtsTimelineClassic +{ + +TimelineIndicator::TimelineIndicator(QWidget *parent) + : QWidget(parent) + , m_zoomFactorPoints(0) + , m_pbZoom(new QPushButton(this)) + , m_pbUnzoom(new QPushButton(this)) + , m_cbZoomLevel(new QComboBox(this)) + , m_currentTextHeight(0) + , m_currentTextWidth(0) + , m_sectionPos(0) + , m_sectionSize(0) + , m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Timeline.Indicator")) +{ + m_timeArray.reserve(11 + 11 + 7 + 10); + + m_timeArray << + + 1 << 2 << 3 << 4 << 5 << 6 << 12 << 15 << 20 << 30 << 60 << + + + 1 * 60 << 2 * 60 << 3 * 60 << 4 * 60 << 5 * 60 << 6 * 60 << 12 * 60 << 15 * 60 << 20 * 60 << 30 * 60 << 60 * 60 << + + + 1 * 60 * 60 << 2 * 60 * 60 << 4 * 60 * 60 << 6 * 60 * 60 << 8 * 60 * 60 << 12 * 60 * 60 << 24 * 60 * 60 << + + + 1 * 60 * 60 << 2 * 60 * 60 << 4 * 60 * 60 << 6 * 60 * 60 << 8 * 60 * 60 << 10 * 60 * 60 << 14 * 60 * 60 << 18 * 60 * 60 << 22 * 60 * 60 << 26 * 60 * 60 << 30 * 60 * 60; + + QHBoxLayout *centralLayout = new QHBoxLayout(); + centralLayout->setSpacing(0); + + setLayout(centralLayout); + + m_pbZoom->setText(QLatin1String("+")); + m_pbZoom->setFixedSize(s_pbSize, s_pbSize); + + m_pbUnzoom->setText(QLatin1String("-")); + m_pbUnzoom->setFixedSize(s_pbSize, s_pbSize); + + + m_cbZoomLevel->addItem(tr("10 min"), 600); + m_cbZoomLevel->addItem(tr("30 min"), 1800); + m_cbZoomLevel->addItem(tr("1 h"), 3600); + m_cbZoomLevel->addItem(tr("2 h"), 7200); + m_cbZoomLevel->addItem(tr("4 h"), 14400); + m_cbZoomLevel->addItem(tr("8 h"), 28800); + + m_cbZoomLevel->setCurrentIndex(0); + + m_cbZoomLevel->setFixedHeight(s_pbSize); + m_zoomFactorPoints = m_cbZoomLevel->currentData().toLongLong(); + + connect(m_cbZoomLevel, &QComboBox::currentTextChanged, + this, &TimelineIndicator::afterZoom); + connect(m_pbZoom, &QPushButton::clicked, + this, &TimelineIndicator::slotZoom); + connect(m_pbUnzoom, &QPushButton::clicked, + this, &TimelineIndicator::slotUnzoom); + connect(this, &TimelineIndicator::signalAddLine, + this, &TimelineIndicator::slotAddLine); + connect(this, &TimelineIndicator::signalResetLineList, + this, &TimelineIndicator::slotResetLineList); + + centralLayout->addWidget(m_pbZoom, 0, Qt::AlignLeft | Qt::AlignTop); + centralLayout->addWidget(m_pbUnzoom, 0, Qt::AlignLeft | Qt::AlignTop); + centralLayout->addWidget(m_cbZoomLevel, 1, Qt::AlignLeft | Qt::AlignTop); + + + + + + QFontMetrics fm(QApplication::font()); + m_currentTextHeight = fm.height(); + +#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0) + m_currentTextWidth = fm.width(DTG::toString(QDateTime::currentDateTimeUtc(), DTG::Short)); +#else + m_currentTextWidth = fm.horizontalAdvance(DTG::toString(QDateTime::currentDateTimeUtc(), DTG::Short)); +#endif + + dbg_print(m_currentTextWidth); + + +} + +TimelineIndicator::~TimelineIndicator() +{ + +} + +void TimelineIndicator::afterZoom() +{ + m_zoomFactorPoints = m_cbZoomLevel->currentData().toLongLong() * 1000; + emit signalSetZoomFactor(m_zoomFactorPoints); + update(); +} + + +void TimelineIndicator::slotZoom() +{ + int pos = m_cbZoomLevel->currentIndex(); + + if (pos == 0) + return; + + m_cbZoomLevel->setCurrentIndex(pos - 1); + emit signalZoomChanged(); +} + +void TimelineIndicator::slotUnzoom() +{ + int count = m_cbZoomLevel->count(); + int pos = m_cbZoomLevel->currentIndex(); + + if (pos >= count - 1) + return; + + m_cbZoomLevel->setCurrentIndex(pos + 1); + emit signalZoomChanged(); +} + +enum highestUnit +{ + HU_INVALID, + + HU_DAYS, + HU_HOURS, + HU_MINUTES, + HU_SECONDS +}; + +void TimelineIndicator::refreshData() +{ +#ifdef VERBOSE + qWarning() << ""; +#endif + + + + emit signalResetLineList(); + +#ifdef VERBOSE + qWarning() << "section x" << m_sectionPos << "length" << m_sectionSize; +#endif + + + + + + + + double abstand = m_sectionPos + m_currentTextWidth + s_TimeTextDistanceTolerancePixels; + dbg_print(abstand); + + + + qint64 eventTimeOfAbstand = EventTools::getEventTime(abstand); + dbg_print(eventTimeOfAbstand); + qint64 adjustedMin = EventTools::getAdjustedMin(); + dbg_print(adjustedMin); + qint64 textWidthTime = (eventTimeOfAbstand - adjustedMin); + + if (textWidthTime < 0) + { + qWarning() << "TimelineIndicator: paintEvent: textWidthTime invalid! (" << textWidthTime << ")"; + return; + } + + qint64 eventTime = EventTools::getEventTime(abstand); + dbg_print(eventTime); + + + QDateTime textWidthDt = QDateTime::fromMSecsSinceEpoch(textWidthTime); + + + + int days = textWidthDt.date().day() - 1; + int hours = textWidthDt.time().hour() - 1; + int minutes = textWidthDt.time().minute(); + int seconds = textWidthDt.time().second() - 1; + + highestUnit unit = HU_INVALID; + + if (days == 0) + if (hours == 0) + if (minutes == 0) + if (seconds == 0) + { + #ifdef VERBOSE + qWarning() << "refreshData: no lower time unit available" ; + #endif + return; + } + else + { + + unit = HU_SECONDS; + } + else + { + + unit = HU_MINUTES; + } + else + { + + unit = HU_HOURS; + } + else + { + + unit = HU_DAYS; + } + + if (unit == HU_INVALID) + { +#ifdef VERBOSE + qWarning() << "unit == HU_INVALID"; +#endif + return; + } + + int higherValue = 0; + for (int i = 0; i < m_timeArray.size(); i++) + { + qint64 time = (m_timeArray[i] * 1000); + + if (time >= textWidthTime) + { + higherValue = time; + + break; + } + else + { + continue; + } + + + +#ifdef VERBOSE + qWarning() << "refreshData: no fitting highest unit found !" ; +#endif + return; + } + + + if (higherValue < 1) + { + + return; + } + + + + + + QDateTime minDt = QDateTime::fromMSecsSinceEpoch(adjustedMin); + dbg_print(minDt); + dbg_print(unit); + + switch (unit) + { + case HU_SECONDS: + { + minDt = minDt.addSecs(-(minDt.time().second())); +#ifdef VERBOSE + qCritical() << "HU_SECONDS"; +#endif + + break; + } + + case HU_MINUTES: + { + minDt = minDt.addSecs(-(minDt.time().second())); + minDt = minDt.addSecs(-(minDt.time().minute() * 60)); +#ifdef VERBOSE + qCritical() << "HU_MINUTES"; +#endif + + break; + } + + case HU_HOURS: + { + minDt = minDt.addSecs(-(minDt.time().second())); + minDt = minDt.addSecs(-(minDt.time().minute() * 60)); + minDt = minDt.addSecs(-(minDt.time().hour() * 60 * 60)); +#ifdef VERBOSE + qCritical() << "HU_HOURS"; +#endif + + break; + } + + case HU_DAYS: + { + minDt = minDt.addSecs(-(minDt.time().second())); + minDt = minDt.addSecs(-(minDt.time().minute() * 60)); + minDt = minDt.addSecs(-(minDt.time().hour() * 60 * 60)); + minDt = minDt.addDays(-(minDt.date().day())); +#ifdef VERBOSE + qCritical() << "HU_DAYS"; +#endif + + break; + } + + default: + { +#ifdef VERBOSE + qWarning() << "unit / scaling is off da charz !"; +#endif + return; + } + } + + dbg_print(minDt); + + + minDt = minDt.addMSecs(-higherValue); + + dbg_print(minDt); + + + qint64 maxTimelineView = EventTools::getAdjustedMax() + (higherValue ); + qint64 additional = textWidthTime / 2; + maxTimelineView += additional; + + qint64 minTimeline = minDt.toMSecsSinceEpoch(); + + dbg_print(minTimeline); + dbg_print(maxTimelineView); + dbg_print(higherValue); + + qint64 steps = (maxTimelineView - minTimeline) / higherValue; + if (steps < 0) + steps = steps * (-1); + + if (steps > 60000 || steps < 1) + { + ERROR_LOG(m_logger) << "More than 60000 ("< 60000) + break; + + currentStep++; + + int pos = EventTools::getEventPosition(indicatorTime); + dbg_print(indicatorTime); + + if (pos + (m_currentTextWidth / 4) + 1 < m_sectionPos) + { +#ifdef VERBOSE + qCritical() << "Indicator: pos" << pos << "invisible #1"; +#endif + continue; + } + else + if (pos - (m_currentTextWidth ) > (m_sectionPos + m_sectionSize)) + { +#ifdef VERBOSE + qCritical() << "Indicator: pos" << pos << "invisible #2"; +#endif + continue; + } + else + { +#ifdef VERBOSE + qCritical() << "Indicator: OKAY!! pos" << pos << "visible"; +#endif + } + + + + emit signalAddLine(pos, indicatorTime); + } + + dbg_print(currentStep); + + QFontMetrics fm(QApplication::font()); + m_currentTextHeight = fm.height(); + + update(); +} + +void TimelineIndicator::paintEvent(QPaintEvent *) +{ + + + QPainter painter(this); + painter.save(); + painter.setClipping(true); + painter.setClipRect(QRect(x() - 8 + m_sectionPos, 0, m_sectionSize, height())); + +#ifdef VERBOSE + int textY = (height() - m_currentTextHeight - 5); +#endif + + int h = height(); + + for (auto i = m_lineMap.constBegin(); i != m_lineMap.constEnd(); ++i) + { + + + int pos = i.key(); + + + painter.setPen(QPen(QColor("black"), 1.0)); + painter.drawLine(pos, m_currentTextHeight, pos, h); + + int centeredPos = pos - (m_currentTextWidth / 2); + qint64 time = (*i); + + + + + painter.drawText(centeredPos, (m_currentTextHeight / 2) + 4, + DTG::toStringMSec(time, DTG::Short)); + +#ifdef VERBOSE + WARNING_LOG(m_logger) << "paintEvent: actual drawing position: x=" << centeredPos << " y=" << textY; +#endif + } + + painter.restore(); + + +} + +void TimelineIndicator::slotSectionResized(int logicalIndex, int oldSize, int newSize) +{ + (void)oldSize; + + + QHeaderView *header = qobject_cast(QObject::sender()); + + if (!header) + return; + + if (logicalIndex == 1) + { + m_sectionPos = header->sectionPosition(1); + m_sectionSize = newSize; + + } + + update(); +} + +void TimelineIndicator::slotAddLine(double posX, qint64 time) +{ +#ifdef VERBOSE + qCritical() << "slotAddLine: posX" << posX << "time" << time; +#endif + m_lineMap.insert(posX, time); +} + +void TimelineIndicator::slotResetLineList() +{ + m_lineMap.clear(); +} + +} diff --git a/Lts/Common/LtsTimelineClassic/TimelineIndicator.h b/Lts/Common/LtsTimelineClassic/TimelineIndicator.h new file mode 100755 index 000000000..710e1d3f1 --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/TimelineIndicator.h @@ -0,0 +1,111 @@ + + + +#pragma once + +#include "EventTools.h" +#include "LineMap.h" +#include +#include +#include + + +namespace NGL +{ +class INGLLogger; + +} +class QPushButton; +class QLabel; +class QComboBox; + + +namespace LtsTimelineClassic +{ + + + +class TimelineIndicator : public QWidget +{ + Q_OBJECT + + + static const int s_TimeTextDistanceTolerancePixels = 10; + static const int s_pbSize = 18; + +public: + explicit TimelineIndicator(QWidget *parent); + ~TimelineIndicator(); + qint64 zoomFactorPoints() const { return m_zoomFactorPoints * 1000; } + + + + void refreshData(); + +private: + QVector m_timeArray; + qint64 m_zoomFactorPoints; + QPushButton *m_pbZoom; + QPushButton *m_pbUnzoom; + QComboBox *m_cbZoomLevel; + int m_currentTextHeight; + int m_currentTextWidth; + LineMap m_lineMap; + int m_sectionPos; + int m_sectionSize; + NGL::INGLLogger &m_logger; + +protected: + + + void paintEvent(QPaintEvent *); + +signals: + + + void signalSetTimelineRange(qint64 min, qint64 max); + + + + void signalSetZoomFactor(qint64 zoomFactor); + + + + void signalResetLineList(); + + + + void signalAddLine(int posX, qint64 time); + + void signalZoomChanged(); + + +private slots: + void afterZoom(); + + +public slots: + + + + void slotSectionResized(int logicalIndex, int oldSize, int newSize); + + + + void slotZoom(); + + + + void slotUnzoom(); + + + + void slotResetLineList(); + + + + void slotAddLine(double posX, qint64 time); + +}; + +} diff --git a/Lts/Common/LtsTimelineClassic/TimelineView.cpp b/Lts/Common/LtsTimelineClassic/TimelineView.cpp new file mode 100755 index 000000000..4e3bae7c6 --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/TimelineView.cpp @@ -0,0 +1,392 @@ + + + +#include "TimelineView.h" +#include "TimelineHeaderView.h" +#include "TimelineWidget.h" +#include "EventDelegate.h" +#include "EventGraphic.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace LtsTimelineClassic +{ + + +TimelineView::TimelineView(QWidget *parent) + : QTreeView(parent) + + , m_model(nullptr) + , m_drawCache(false) +{ + m_posMarkTime = 0; + + + setAlternatingRowColors(true); + setEditTriggers(QAbstractItemView::NoEditTriggers); + setSelectionBehavior(QAbstractItemView::SelectItems); + setSelectionMode(QAbstractItemView::NoSelection); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); +} + + +void TimelineView::setHeader(QHeaderView *header) +{ + if (header) + { + QTreeView::setHeader(header); + header->setMinimumSectionSize(150); + } + else + { + + } +} + + +AbstractTimelineModel *TimelineView::timelineModel() +{ + return m_model; +} + + +void TimelineView::setTimelineModel(AbstractTimelineModel *model) +{ + m_model = model; + setMouseTracking(true); + setModel(m_model); + + EventDelegate *ed = new EventDelegate(this); + + EventTools::setSectionHeight(ed->sizeHint().height()); + + setItemDelegate(ed); +} + + +EventGraphic *TimelineView::getEventOnPosition(QPoint position) +{ + position = viewport()->mapFromGlobal(position); + QModelIndex index = indexAt(position); + + if (index.isValid() && position.x() > header()->sectionSize(0)) + { + Item *item = static_cast(index.internalPointer()); + + if (!item) + return nullptr; + + qint64 curTime = EventTools::getEventTime(position.x()); + EventGraphicList egl = item->getCurrentEGLFromPevcon(); + + qint64 blockSizeTime = EventTools::getBlockSizeTime(); + + for (int i = 0; i < egl.count(); i++) + { + EventGraphic *eg = egl.at(i); + + if (!eg) + continue; + + qint64 eventStartTime = eg->lowestTime(); + + if (eventStartTime == 0) + continue; + + qint64 eventEndTime = eventStartTime; + + qint64 eglDuration = eg->getDuration(); + + if (eglDuration > blockSizeTime) + eventEndTime += eglDuration; + else + eventEndTime += blockSizeTime; + + if (curTime >= eventStartTime && curTime <= eventEndTime) + return eg; + } + } + + return nullptr; +} + + +bool TimelineView::event(QEvent *event) +{ + switch (event->type()) + { + case QEvent::ToolTip: + { + + + QHelpEvent *helpEvent = static_cast(event); + + if (!helpEvent) + return true; + + EventGraphic *eg = getEventOnPosition(helpEvent->globalPos()); + + if (!eg) + return true; + + emit signalEventAction(eg, EventGraphic::ACTION_TOOLTIP); + + return true; + } + + default: + { + break; + } + } + + return QTreeView::event(event); +} + + +void TimelineView::wheelEvent(QWheelEvent *event) +{ + QTreeView::wheelEvent(event); + + if (QGuiApplication::queryKeyboardModifiers() != Qt::ControlModifier) + return; + + int scrolls = event->delta() / 120; + + if (scrolls > 0) + { + for (int i = 0; i < scrolls; i++) + emit signalZoom(); + } + + if (scrolls < 0) + { + for (int i = 0; i > scrolls; i--) + emit signalUnzoom(); + } +} + + +void TimelineView::mouseReleaseEvent(QMouseEvent *event) +{ + QTreeView::mouseReleaseEvent(event); + + QPoint position = event->globalPos(); + EventGraphic *eg = getEventOnPosition(position); + + if (!eg) + return; + + switch (event->button()) + { + case Qt::LeftButton: + { + emit signalEventAction(eg, EventGraphic::ACTION_LEFT_CLICKED); + break; + } + + case Qt::RightButton: + { + emit signalEventAction(eg, EventGraphic::ACTION_RIGHT_CLICKED); + break; + } + + default: + { + break; + } + } +} + + +void TimelineView::slotAddLine(int posX, qint64 time) +{ + + m_lineMap.insert(posX, time); +} + + +void TimelineView::slotResetLineList() +{ + m_lineMap.clear(); +} + + + + + + +void TimelineView::slotSetPosMarkTime(qint64 posMarkTime) +{ + m_posMarkTime = posMarkTime; + + TimelineHeaderView *headerView = qobject_cast(header()); + if (headerView) + headerView->setPosMarkTime(posMarkTime); + + refreshData(); +} + + +void TimelineView::refreshData() +{ + update(); + viewport()->update(); +} + + +void TimelineView::paintEvent(QPaintEvent *event) +{ + if (!m_model) + return; + + QWidget *viewp0rt = viewport(); + + + + QPainter painter(viewp0rt); + + if (!painter.isActive()) + { + + } + + painter.save(); + + int viewPortHeight = viewp0rt->height(); + + QHeaderView *head0r = header(); + + + + int sectionPos = head0r->sectionPosition(1); + int sectionSize = head0r->sectionSize(1); + + int minSection = sectionPos; + int maxSection = minSection + sectionSize; + + + + + QRect paintRect(minSection, 0, + maxSection - minSection, height()); + painter.setClipping(true); + painter.setClipRect(paintRect); + + + + + + + + + + for (auto i = m_lineMap.constBegin(); i != m_lineMap.constEnd(); ++i) + { + int pos = i.key(); + + painter.setPen(QPen(QBrush(QGuiApplication::palette().shadow()), 1.0)); + + if (pos > minSection && + pos < maxSection) + painter.drawLine(pos, 0, pos, viewPortHeight); + else + { +#ifdef VERBOSE + qCritical() << "out of view => pos" << pos << "min" << minSection << "max" << maxSection; +#endif + } + } + + + + painter.restore(); + QTreeView::paintEvent(event); + painter.save(); + + + + + + + + + + if (m_drawCache) + { + + painter.setPen(QPen(QBrush("gray"), 1)); + painter.setBrush(QBrush("gray")); + painter.drawRect(minSection, 0, maxSection, height()); + + + + painter.setPen(QPen(QBrush("white"), 1)); + painter.setBrush(QBrush("white")); + int h = height(); + + for (const auto &range : qAsConst(m_knownRanges)) + { + int start = EventTools::getEventPosition(range.start); + + if (start < minSection) + start = minSection; + + int end = EventTools::getEventPosition(range.end); + + if (end > maxSection) + end = maxSection; + + painter.drawRect(start, 0, end - start, h); + } + } + + + + + + + + + + + + ItemList il = m_model->getRootItem()->getFullItemList(true); + for (int i = 0; i < il.count(); i++) + { + if (il.at(i)->getName() == tr("Markers")) + EventTools::drawMarkLines(&painter, il.at(i)->getFullEventList(true), height()); + } + + + + + if (m_posMarkTime > 0) + { + int posMarkPosition = EventTools::getEventPosition(m_posMarkTime); + + if (posMarkPosition > sectionPos && + posMarkPosition < sectionSize + sectionPos) + { + painter.setPen(QPen(QGuiApplication::palette().highlight(), 1.0)); + int xpos = posMarkPosition; + painter.drawLine(xpos, 0, xpos, height()); + } + } + + painter.restore(); + + +} + +} diff --git a/Lts/Common/LtsTimelineClassic/TimelineView.h b/Lts/Common/LtsTimelineClassic/TimelineView.h new file mode 100755 index 000000000..caae12374 --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/TimelineView.h @@ -0,0 +1,107 @@ + + + +#pragma once + +#include "AbstractTimelineModel.h" +#include "TimelineIndicator.h" +#include "TimelineHeaderView.h" +#include "LineMap.h" +#include "Range.h" + +#include + + +namespace LtsTimelineClassic +{ + + +class TimelineView : public QTreeView +{ + Q_OBJECT + +public: + + + explicit TimelineView(QWidget *parent); + + EventGraphic *getEventOnPosition(QPoint position); + + + + void setTimelineModel(AbstractTimelineModel *model); + + + + AbstractTimelineModel *timelineModel(); + + + + void refreshData(); + + + + void setHeader(QHeaderView *header); + + + void setKnownRanges(const QVector &newRanges) + { + m_knownRanges = newRanges; + } + + void setDrawCache(bool show) + { + m_drawCache = show; + } + + +private: + qint64 m_posMarkTime; + AbstractTimelineModel *m_model; + LineMap m_lineMap; + QVector m_knownRanges; + bool m_drawCache; + + +protected: + bool event(QEvent *event) override; + void mouseReleaseEvent(QMouseEvent *) override; + void wheelEvent(QWheelEvent *event) override; + + + + + + + + + virtual void paintEvent(QPaintEvent *event) override; + + +public slots: + + + void slotResetLineList(); + + + + void slotAddLine(int posX, qint64 time); + + + + + + + + void slotSetPosMarkTime(qint64 posMarkTime); + + +signals: + + void signalEventAction(EventGraphic *eg, int); + void signalZoom(); + void signalUnzoom(); + +}; + +} diff --git a/Lts/Common/LtsTimelineClassic/TimelineWidget.cpp b/Lts/Common/LtsTimelineClassic/TimelineWidget.cpp new file mode 100755 index 000000000..be7d69e34 --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/TimelineWidget.cpp @@ -0,0 +1,324 @@ + + + +#include "TimelineWidget.h" +#include "EventDelegate.h" +#include "ui_TimelineWidgetUi.h" +#include "AbstractTimelineModel.h" +#include "EventGraphic.h" + +#include + +#include +#include +#include +#include + +#include + + +namespace LtsTimelineClassic +{ + +TimelineWidget::TimelineWidget(QWidget *parent) + : QWidget(parent) + , m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Timeline.Widget")) + , m_timelineUi(new Ui::Timeline) + , m_min(0) + , m_max(1) + , m_sbPos(0) + , m_zoomFactor(1) + , m_headerView(new TimelineHeaderView) + , m_model(nullptr) +{ + m_timelineUi->setupUi(this); + m_timelineUi->view->setHeader(m_headerView); + m_headerView->setMinimumSectionSize(150); + + m_timelineUi->scrollbar->setMouseTracking(true); + connect(m_timelineUi->scrollbar, &QScrollBar::sliderReleased, this, &TimelineWidget::scrollBarFilter2); + connect(m_timelineUi->scrollbar, &QScrollBar::actionTriggered, this, &TimelineWidget::scrollBarFilter1); + + connect(m_headerView, &QHeaderView::sectionResized, m_timelineUi->indicator, &TimelineIndicator::slotSectionResized); + connect(m_headerView, &QHeaderView::sectionResized, this, &TimelineWidget::slotSectionResized); + connect(m_timelineUi->view, &TimelineView::signalZoom, m_timelineUi->indicator, &TimelineIndicator::slotZoom); + connect(m_timelineUi->view, &TimelineView::signalUnzoom, m_timelineUi->indicator, &TimelineIndicator::slotUnzoom); + + connect(m_timelineUi->view, &TimelineView::signalZoom, this, &TimelineWidget::signalZoomChanged); + connect(m_timelineUi->view, &TimelineView::signalUnzoom, this, &TimelineWidget::signalZoomChanged); + + connect(m_timelineUi->indicator, &TimelineIndicator::signalZoomChanged, this, &TimelineWidget::signalZoomChanged); + connect(m_timelineUi->indicator, &TimelineIndicator::signalSetZoomFactor, this, &TimelineWidget::slotSetZoomFactor); + + connect(m_timelineUi->view, &TimelineView::signalEventAction, + this, &TimelineWidget::signalEventAction); + + connect(m_timelineUi->cbDrawCache, &QCheckBox::toggled, + this, [this](bool toggled) + { + m_timelineUi->view->setDrawCache(toggled); + update(); + }); + + connect(m_timelineUi->cbArm, &QCheckBox::toggled, + this, &TimelineWidget::signalArm); + connect(m_timelineUi->updateTimeline, &QPushButton::clicked, + this, &TimelineWidget::slotUpdateTimeline); +} + + +TimelineWidget::~TimelineWidget() +{ + delete m_timelineUi; +} + + +void TimelineWidget::setLiveTime(qint64 time) +{ + m_headerView->setLiveTime(time); + + EventDelegate *ed = qobject_cast(m_timelineUi->view->itemDelegate()); + if (ed == nullptr) + { + qWarning() << "TimelineWidget::setLiveTime: ed == nullptr"; + return; + } + + ed->setLiveTime(time); +} + + +void TimelineWidget::hideDebugWidget() +{ + m_timelineUi->debugWidget->hide(); + m_timelineUi->lbMinMax->hide(); +} + +void TimelineWidget::knownRangesChanged(const QVector &newRanges) +{ + EventDelegate *ed = qobject_cast(m_timelineUi->view->itemDelegate()); + if (ed == nullptr) + { + WARNING_LOG(m_logger) << "ed == nullptr"; + return; + } + + m_timelineUi->view->setKnownRanges(newRanges); + ed->setKnownRanges(newRanges); + m_timelineUi->view->refreshData(); +} + + +void TimelineWidget::setMaxPos() +{ + m_timelineUi->scrollbar->setValue(m_timelineUi->scrollbar->maximum()); +} + + +int TimelineWidget::getMaxPos() const +{ + return m_timelineUi->scrollbar->maximum(); +} + + +int TimelineWidget::getPos() const +{ + return m_timelineUi->scrollbar->value(); +} + + +void TimelineWidget::setPos(qint64 pos) +{ + m_timelineUi->scrollbar->setValue(pos / 1000); +} + + +void TimelineWidget::setPositionMarkerTime(const qint64 time) +{ + m_timelineUi->view->slotSetPosMarkTime(time); +} + + +void TimelineWidget::slotUpdateTimeline() +{ + qint64 min = m_model->lowestPositionedItem(); + qint64 max = m_model->highestPositionedItem(); + + if (m_timelineUi->cbMinMax->isChecked()) + { + QString minmax(QStringLiteral("%1 (%2) - %3 (%4)") + .arg(QDateTime::fromMSecsSinceEpoch(min).toString()) + .arg(min) + .arg(QDateTime::fromMSecsSinceEpoch(max).toString()) + .arg(max)); + m_timelineUi->lbMinMax->setText(minmax); + } + + setTimelineRange(min, max); + emit signalRangeChanged(); +} + + +void TimelineWidget::setTimelineModel(AbstractTimelineModel *model) +{ + m_model = model; + + connect(m_model, &AbstractTimelineModel::dataChanged, + this, &TimelineWidget::slotRefreshData ); + + + + m_timelineUi->view->setTimelineModel(m_model); + + + connect(m_timelineUi->indicator, &TimelineIndicator::signalAddLine, + m_timelineUi->view, &TimelineView::slotAddLine); + connect(m_timelineUi->indicator, &TimelineIndicator::signalResetLineList, + m_timelineUi->view, &TimelineView::slotResetLineList); + + EventDelegate *ed = qobject_cast(m_timelineUi->view->itemDelegate()); + if (ed != nullptr) + { + connect(m_timelineUi->indicator, &TimelineIndicator::signalAddLine, + ed, &EventDelegate::slotAddLine); + connect(m_timelineUi->indicator, &TimelineIndicator::signalResetLineList, + ed, &EventDelegate::slotResetLineList); + } + + m_timelineUi->view->expandToDepth(1); + + m_headerView->resizeSection(0, m_headerView->minimumSectionSize()); + m_headerView->resizeSection(1, m_headerView->minimumSectionSize()); + + slotSetZoomFactor(m_timelineUi->indicator->zoomFactorPoints()); +} + + +qint64 TimelineWidget::getRangeMinimum() const +{ + return EventTools::getAdjustedMin(); +} + + +qint64 TimelineWidget::getRangeMaximum() const +{ + return EventTools::getAdjustedMax(); +} + + +void TimelineWidget::setTimelineRange(qint64 min, qint64 max) +{ + m_min = min; + m_max = max; + + if (m_min > 0 && m_max > 2) + { + m_sbPos = EventTools::fromSecondsToMsecs(m_timelineUi->scrollbar->sliderPosition()); + + if (m_max <= m_min) + { + if (m_min > (10 * 60)) + m_min -= (10 * 60); + } + else + { + qint64 diff = m_max - m_min; + if (diff < (10 * 60)) + if (m_min > (10 * 60)) + m_min -= (10 * 60); + } + + + + + + + + EventTools::setValues(m_min, m_max, m_sbPos, m_zoomFactor); + + + + + m_timelineUi->scrollbar->setRange(EventTools::fromMsecsToSeconds(EventTools::getTimelineMin()), EventTools::fromMsecsToSeconds(EventTools::getTimelineMax())); + + if (m_sbPos < m_min) + { + m_sbPos = m_min; + + m_timelineUi->scrollbar->setSliderPosition(EventTools::fromMsecsToSeconds(m_sbPos)); + } + + + + int pagestep = EventTools::fromMsecsToSeconds(EventTools::s_diff); + if (pagestep < 0) + pagestep = 1; + + + m_timelineUi->scrollbar->setPageStep(pagestep); + + + + + m_timelineUi->bsz->setText(QString::number(EventTools::getBlockSizeTime())); + } + + + refreshData(); +} + + +void TimelineWidget::slotRefreshData() +{ + refreshData(); +} + + +void TimelineWidget::refreshData() +{ + m_headerView->refreshData(); + m_timelineUi->indicator->refreshData(); + m_timelineUi->view->refreshData(); +} + + +void TimelineWidget::slotSetZoomFactor(qint64 zoomFactor) +{ + m_zoomFactor = zoomFactor; + slotUpdateTimeline(); +} + + +void TimelineWidget::scrollBarFilter1(int ) +{ + scrollBarFilter2(); +} + + +void TimelineWidget::scrollBarFilter2() +{ + slotUpdateTimeline(); +} + + +void TimelineWidget::slotSectionResized(int logicalIndex, int oldSize, int newSize) +{ + (void)oldSize; + + if (logicalIndex == 1) + { + int viewScrollbarWidth = m_timelineUi->view->verticalScrollBar()->width(); + m_timelineUi->scrollbar->setMinimumHeight(viewScrollbarWidth); + m_timelineUi->scrollbar->setMaximumHeight(viewScrollbarWidth); + + m_timelineUi->scrollbar->setMaximumWidth(newSize); + m_timelineUi->scrollbar->setMinimumWidth(newSize); + + EventTools::setSection(m_headerView->sectionPosition(1), + newSize ); + } + + slotUpdateTimeline(); +} + +} diff --git a/Lts/Common/LtsTimelineClassic/TimelineWidget.h b/Lts/Common/LtsTimelineClassic/TimelineWidget.h new file mode 100755 index 000000000..3c527d9b9 --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/TimelineWidget.h @@ -0,0 +1,123 @@ + + + +#pragma once + +#include + +#include "Range.h" +#include + +#include + + +namespace Ui +{ +class Timeline; +} + + +namespace LtsTimelineClassic +{ +class AbstractTimelineModel; +class EventGraphic; +class TimelineHeaderView; + + + +class LTSTIMELINECLASSIC_EXPORT TimelineWidget : public QWidget +{ + Q_OBJECT + +public: + + + explicit TimelineWidget(QWidget *parent = nullptr); + + ~TimelineWidget(); + + + + void setPositionMarkerTime(const qint64 time); + + qint64 getRangeMinimum() const; + qint64 getRangeMaximum() const; + + void setPos(qint64 pos); + void setMaxPos(); + int getPos() const; + int getMaxPos() const; + + void knownRangesChanged(const QVector &newRanges); + void hideDebugWidget(); + void setLiveTime(qint64 time); + + +private: + + + void setTimelineRange(qint64 min, qint64 max); + + void refreshData(); + + NGL::INGLLogger &m_logger; + Ui::Timeline *m_timelineUi; + + + qint64 m_min; + qint64 m_max; + qint64 m_sbPos; + qint64 m_zoomFactor; + + TimelineHeaderView *m_headerView; + AbstractTimelineModel *m_model; + + +signals: + + + void signalLoadNewEvents(qint64 from, qint64 to); + + + + + + void signalEventAction(EventGraphic *eg, int); + void signalRangeChanged(); + void signalArm(bool arm); + void signalZoomChanged(); + + +public slots: + + + + void scrollBarFilter1(int x); + + + + void scrollBarFilter2(); + + + + void slotSetZoomFactor(qint64 zoomFactor); + + + + void slotSectionResized(int logicalIndex, int oldSize, int newSize); + + + + void setTimelineModel(AbstractTimelineModel *model); + + + + + void slotUpdateTimeline(); + + + void slotRefreshData(); + +}; + +} diff --git a/Lts/Common/LtsTimelineClassic/Ui/TimelineWidgetUi.ui b/Lts/Common/LtsTimelineClassic/Ui/TimelineWidgetUi.ui new file mode 100755 index 000000000..f3fb7bacb --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/Ui/TimelineWidgetUi.ui @@ -0,0 +1,167 @@ + + + Timeline + + + + 0 + 0 + 628 + 393 + + + + Timeline + + + + 0 + + + + + + + + + + [draw cache] + + + + + + + [arm] + + + + + + + [min/max] + + + + + + + [update timeline] + + + + + + + Block size time: + + + + + + + TextLabel + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + 0 + 200 + + + + + + + + + 0 + 20 + + + + + 16777215 + 16777215 + + + + Qt::LeftToRight + + + 0 + + + 0 + + + Qt::Horizontal + + + false + + + + + + + + 100 + 30 + + + + + 16777215 + 30 + + + + + + + + + + + + + + + + LtsTimelineClassic::TimelineView + QWidget +
LtsTimelineClassic/TimelineView.h
+ 1 +
+ + LtsTimelineClassic::TimelineIndicator + QWidget +
LtsTimelineClassic/TimelineIndicator.h
+ 1 +
+
+ + +
diff --git a/Lts/Common/LtsTimelineClassic/log4cxx_timeline.props b/Lts/Common/LtsTimelineClassic/log4cxx_timeline.props new file mode 100755 index 000000000..64c4f6833 --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/log4cxx_timeline.props @@ -0,0 +1,66 @@ +log4j.reset=true +# exclusivly for timelinetest build + + +log4j.rootLogger=ERROR, file +#log4j.rootLogger=WARN, stdout + +log4j.logger.Timeline.EventInfoWindow=DEBUG +log4j.logger.Timeline.EventTools=DEBUG +log4j.logger.Timeline=WARN + + +log4j.appender.file=org.apache.log4j.RollingFileAppender +log4j.appender.file.MaxFileSize=1MB +log4j.appender.file.MaxBackupIndex=9 +log4j.appender.file.file=${LOG4QT_LOGDIR}/${LOG4QT_USER}_timeline.log +log4j.appender.file.layout=org.apache.log4j.PatternLayout +log4j.appender.file.layout.ConversionPattern=%d %-5p %c |#| %m (%.50F:%L) %n + +# Appnd_Console +########################################################### +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern= %d %p %c %c{1} - %m (%-.30F:%L) %n + +########################################################### +########################################################### +########################################################### +# 1) Available Levels in LOG4CXX Version 0.10.0: +# TRACE, +# DEBUG, +# INFO, +# WARN, +# ERROR +# FATAL +# +# d = date/time +# p = priority (Level) +# c = category/channel +# m = message +# F = file +# L = line +# n = platform dependent line separator character or characters. +# .... +# +# Examples for console: +# a) +# %d %p %c #%m# (%.50F:%L)%n +# date/time +# priority +# category/channel +# #message# +# (Maximal 50Character des Filename von rechts:Line) +# line separator +# +# b) +# %d (%.15F:%L) %-5p %c |#| %m%n +# date/time +# (Maximal 15Character des Filename von rechts:Line) +# priority, Right pad with spaces if the priority name is less than 5 characters long. +# category/channel +# message +# line separator +# +# Examples for files: + diff --git a/Lts/Common/LtsTimelineClassic/main.cpp b/Lts/Common/LtsTimelineClassic/main.cpp new file mode 100755 index 000000000..203b50ce5 --- /dev/null +++ b/Lts/Common/LtsTimelineClassic/main.cpp @@ -0,0 +1,82 @@ + + + +#include "TimelineWidget.h" +#include "TestTimelineModel.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + + +LtsTimelineClassic::TestTimelineModel *ttm; + + +void add(qint64 time_offset) +{ + qint64 time = 1505292189000; + time += time_offset; + + ttm->addTestEvent(new LtsTimelineClassic::Event(0, (int)ProjectDataManagement::EventType_def::Hit, time, 0, 0, 0, 0, + QStringList() << QStringLiteral("Test"), + LtsTimelineClassic::Event::drawCustomEventGraphicEllipse, QColor("green"), QString())); +} + + +int main(int argc, char *argv[]) +{ + qputenv("TDS_DEVEL", "yes, of course"); + qWarning() << "LtsTimelineClassicTest"; + NGLApplication app(argc, argv); + app.createLogger("Timeline", "log4cxx_timeline.props"); + + STARTUP_LOG(app.baseLogger()); + + int ret = 0; + +#if 1 + QWidget *widget = new QWidget(); + widget->setMinimumWidth(500); + + QVBoxLayout *layout = new QVBoxLayout(); + widget->setLayout(layout); + + LtsTimelineClassic::TimelineWidget *timelineWidget = new LtsTimelineClassic::TimelineWidget(); + layout->addWidget(timelineWidget); + + QHBoxLayout *btnLayout = new QHBoxLayout(); + QWidget *btnLayoutWidget = new QWidget(); + btnLayoutWidget->setLayout(btnLayout); + layout->addWidget(btnLayoutWidget); + + btnLayout->addWidget(new QPushButton(QStringLiteral("Filter"))); + btnLayout->addWidget(new QPushButton(QStringLiteral("CSV Export"))); + btnLayout->addStretch(1); + + widget->show(); + + ttm = new LtsTimelineClassic::TestTimelineModel(); + timelineWidget->setTimelineModel(ttm); + + + + + + add(0); + add(100 * 60 * 1000); + add(3000 * 60 * 1000); + + + + ret = app.exec(); + SHUTDOWN_LOG(app.baseLogger()); +#endif + + return ret; +} diff --git a/Lts/Common/LtsUtils/CMakeLists.txt b/Lts/Common/LtsUtils/CMakeLists.txt new file mode 100755 index 000000000..7051cc9a5 --- /dev/null +++ b/Lts/Common/LtsUtils/CMakeLists.txt @@ -0,0 +1,12 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# +# Created by Mike Wille, SLSE, 2016-09-19 +# + + +ADD_SUBDIRECTORY(LtsUtilsCore) +ADD_SUBDIRECTORY(LtsUtilsGui) +ADD_SUBDIRECTORY(LtsUtilsSettings) diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/CMakeLists.txt b/Lts/Common/LtsUtils/LtsUtilsCore/CMakeLists.txt new file mode 100755 index 000000000..666d70a6c --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/CMakeLists.txt @@ -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 $) +ENDIF() diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/UnitTests/utils_test.cpp b/Lts/Common/LtsUtils/LtsUtilsCore/UnitTests/utils_test.cpp new file mode 100755 index 000000000..5cfd50391 --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/UnitTests/utils_test.cpp @@ -0,0 +1,1202 @@ + + + +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "utils_test.h" +#include "pgarray.h" +#include "qpoint3d.h" +#include "connection.h" +#include "json_object.h" +#include "sfm_bytemessage_converter.h" +#include "promise.h" +#include "promise_examples.h" +#include "qbytearray_byteaccess.h" +#include "dds_vector.h" + +#define SUPPORT_QBITARRAY +#include "dds_bitarray.h" +#include + +#include +#include +#include +#include + + +using namespace LtsUtils; +namespace QTest +{ +template <> +char *toString(const QVector &vec) +{ + QStringList sl; + sl.reserve(vec.size()); + for (int i : vec) + sl += QString::number(i); + return qstrdup(sl.join(QLatin1Char(',')).toLatin1().constData()); +} +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) +template +inline bool qCompare(const T1 &t1, const T2 &t2, const char *actual, const char *expected, + const char *file, int line) +{ + return compare_helper(t1 == t2, "Compared values are not the same", + toString(t1), toString(t2), actual, expected, file, line); +} +#endif +} + + + + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + LtsUtilsTest tc; + return QTest::qExec(&tc, argc, argv); +} + + +void LtsUtilsTest::test_promise() +{ + QVector results; + + results.append(1); + + + LtsUtils::Promise p([&results](LtsUtils::ResultFunction resolve, LtsUtils::ResultFunction ) + { + results.append(4); + qDebug() << "LtsUtils Promise: Promise executed! Resolving now"; + + resolve(QStringLiteral("hello world!")); + }); + + results.append(2); + + bool resolved = false; + p.resolved([&resolved, &results](const QVariant &value) + { + results.append(5); + qDebug() << "LtsUtils Promise: Promise was resolved with value" << value; + resolved = true; + }); + + p.rejected([](const QVariant &val) + { + qCritical() << "rejected with val" << val; + QFAIL("Promise unexpectedly rejected"); + }); + + results.append(3); + + QTRY_VERIFY2(resolved, "Promise was not resolved"); + QCOMPARE((QVector() << 1 << 2 << 3 << 4 << 5), results); +} + + +void LtsUtilsTest::test_promise_example1() +{ + qDebug("LtsUtils: test_promise_example1"); + + LtsUtils::PromiseExample1 p; + + QTRY_VERIFY2(p.isSuccess(), "Promise was not resolved"); +} + + +void LtsUtilsTest::test_promise_example2() +{ + qDebug("LtsUtils: test_promise_example2"); + + LtsUtils::PromiseExample2 p; + + QTRY_VERIFY2(p.isSuccess(), "Promise didn't reject as expected"); +} + + +void LtsUtilsTest::test_promise_example3() +{ + qDebug("LtsUtils: test_promise_example3"); + + LtsUtils::PromiseExample3 p; + + qDebug() << "waiting for expected reject to happen, and wait 2 seconds to see if the timeout triggers even though it shouldn't, because the promise was already rejected"; + + QTRY_VERIFY2(p.isSuccess(), "Promise was not resolved"); +} + + +void LtsUtilsTest::test_toIntArray() +{ + qDebug("LtsUtils: test_toIntArray"); + + QString str; + std::vector vec; + + str = QLatin1String("{0,1,2,3,4,999999999,-2147483647,2147483647}"); + qDebug() << "LtsUtils: test " << str; + vec = LtsUtils::IntArray::fromPgString(str)(); + QVERIFY2(vec.size() == 8, "Wrong size of QVector! (expected 8)"); + + QVERIFY2(vec[0] == 0, "Wrong value on position 0"); + QVERIFY2(vec[1] == 1, "Wrong value on position 1"); + QVERIFY2(vec[2] == 2, "Wrong value on position 2"); + QVERIFY2(vec[3] == 3, "Wrong value on position 3"); + QVERIFY2(vec[4] == 4, "Wrong value on position 4"); + QVERIFY2(vec[5] == 999999999, "Wrong value on position 5"); + QVERIFY2(vec[6] == -2147483647, "Wrong vtest_fromIntArray2Dalue on position 6"); + QVERIFY2(vec[7] == 2147483647, "Wrong value on position 7"); + + str = QLatin1String("1,2,3,"); + qDebug() << "LtsUtils: test " << str; + vec = LtsUtils::IntArray::fromPgString(str)(); + QVERIFY2(vec.size() == 3, "Wrong size of QVector! (expected 3)"); + + QVERIFY2(vec[0] == 1, "Wrong value on position 0"); + QVERIFY2(vec[1] == 2, "Wrong value on position 1"); + QVERIFY2(vec[2] == 3, "Wrong value on position 2"); + + str = QLatin1String("{a,2,3}"); + qDebug() << "LtsUtils: test " << str; + vec = LtsUtils::IntArray::fromPgString(str)(); + QVERIFY2(vec.size() == 2, "Wrong size of QVector! (expected 2)"); + + QVERIFY2(vec[0] == 2, "Wrong value on position 0"); + QVERIFY2(vec[1] == 3, "Wrong value on position 1"); + + str = QLatin1String("{abc}"); + qDebug() << "LtsUtils: test " << str; + vec = LtsUtils::IntArray::fromPgString(str)(); + QVERIFY2(vec.size() == 0, "Wrong size of QVector! (expected 0)"); +} + + +void LtsUtilsTest::test_fromIntArray() +{ + qDebug("LtsUtils: test_fromIntArray"); + + QString str = LtsUtils::IntArray({ 0, 1, 2, 3, 999999999, -2147483647, 2147483647 }).toPgString(); + + qDebug() << "LtsUtils: Result " << str; + QCOMPARE(str, QLatin1String("{0,1,2,3,999999999,-2147483647,2147483647}")); + + str = LtsUtils::IntArray(std::vector()).toPgString(); + + qDebug() << "LtsUtils: Result " << str; + QCOMPARE(str, QLatin1String("{}")); +} + + +void LtsUtilsTest::test_toBigintArray() +{ + qDebug("LtsUtils: test_toBigintArray"); + + QString str; + std::vector vec; + + str = QLatin1String("{0,1,2147483647,-2147483647,999999999999}"); + qDebug() << "LtsUtils: test " << str; + vec = LtsUtils::BigintArray::fromPgString(str)(); + QVERIFY2(vec.size() == 5, "Wrong size of QVector! (expected 5)"); + + QVERIFY2(vec[0] == 0, "Wrong value on position 0"); + QVERIFY2(vec[1] == 1, "Wrong value on position 1"); + QVERIFY2(vec[2] == 2147483647, "Wrong value on position 2"); + QVERIFY2(vec[3] == -2147483647, "Wrong value on position 3"); + QVERIFY2(vec[4] == 999999999999, "Wrong value on position 4"); + + str = QLatin1String("0,-2147483647,999999999999,"); + qDebug() << "LtsUtils: test " << str; + vec = LtsUtils::BigintArray::fromPgString(str)(); + QVERIFY2(vec.size() == 3, "Wrong size of QVector! (expected 3)"); + + QVERIFY2(vec[0] == 0, "Wrong value on position 0"); + QVERIFY2(vec[1] == -2147483647, "Wrong value on position 1"); + QVERIFY2(vec[2] == 999999999999, "Wrong value on position 2"); + + str = QLatin1String("{a,2,3}"); + qDebug() << "LtsUtils: test " << str; + vec = LtsUtils::BigintArray::fromPgString(str)(); + QVERIFY2(vec.size() == 2, "Wrong size of QVector! (expected 2)"); + + QVERIFY2(vec[0] == 2, "Wrong value on position 0"); + QVERIFY2(vec[1] == 3, "Wrong value on position 1"); + + str = QLatin1String("{abc}"); + qDebug() << "LtsUtils: test " << str; + vec = LtsUtils::BigintArray::fromPgString(str)(); + QVERIFY2(vec.size() == 0, "Wrong size of QVector! (expected 0)"); +} + + +void LtsUtilsTest::test_fromBigintArray() +{ + qDebug("LtsUtils: test_fromBigintArray"); + + QString str = LtsUtils::BigintArray({ 0, -2147483647, 2147483647, 999999999999, 123456789}).toPgString(); + + qDebug() << "LtsUtils: Result " << str; + QCOMPARE(str, QLatin1String("{0,-2147483647,2147483647,999999999999,123456789}")); + + str = LtsUtils::BigintArray(std::vector()).toPgString(); + + qDebug() << "LtsUtils: Result " << str; + QCOMPARE(str, QLatin1String("{}")); +} + + +void LtsUtilsTest::test_toIntArray2D() +{ + qDebug("LtsUtils: test_toIntArray2D"); + + QString str; + std::map vec; + + str = QLatin1String("{{0,2},{1,3},{2,999999999},{3,-2147483647},{4,2147483647}}"); + qDebug() << "LtsUtils: test " << str; + vec = LtsUtils::IntArray2D::fromPgString(str)(); + QVERIFY2(vec.size() == 5, "Wrong size of QVector! (expected 5)"); + + QVERIFY2(vec[0] == 2, "Wrong value on position 0"); + QVERIFY2(vec[1] == 3, "Wrong value on position 1"); + QVERIFY2(vec[2] == 999999999, "Wrong value on position 2"); + QVERIFY2(vec[3] == -2147483647, "Wrong value on position 3"); + QVERIFY2(vec[4] == 2147483647, "Wrong value on position 4"); +} + + +void LtsUtilsTest::test_fromIntArray2D() +{ + qDebug("LtsUtils: test_fromIntArray2D"); + + QString str = LtsUtils::IntArray2D({{0,2},{1,3},{2,999999999},{3,-2147483647},{4,2147483647},{5,123456}}).toPgString(); + + qDebug() << "LtsUtils: Result " << str; + QCOMPARE(str, QLatin1String("{{0,2},{1,3},{2,999999999},{3,-2147483647},{4,2147483647},{5,123456}}")); + + str = LtsUtils::IntArray2D(std::map()).toPgString(); + + qDebug() << "LtsUtils: Result " << str; + QCOMPARE(str, QLatin1String("{}")); +} + + +void LtsUtilsTest::test_toBigintArray2D() +{ + qDebug("LtsUtils: test_toBigintArray2D"); + + QString str; + std::map vec; + + str = QLatin1String("{{0,2},{1,3},{2,999999999999},{3,-2147483647},{4,2147483647}}"); + qDebug() << "LtsUtils: test " << str; + vec = LtsUtils::BigintArray2D::fromPgString(str)(); + QVERIFY2(vec.size() == 5, "Wrong size of QVector! (expected 5)"); + + QVERIFY2(vec[0] == 2, "Wrong value on position 0"); + QVERIFY2(vec[1] == 3, "Wrong value on position 1"); + QVERIFY2(vec[2] == 999999999999, "Wrong value on position 2"); + QVERIFY2(vec[3] == -2147483647, "Wrong value on position 3"); + QVERIFY2(vec[4] == 2147483647, "Wrong value on position 4"); +} + + +void LtsUtilsTest::test_fromBigintArray2D() +{ + qDebug("LtsUtils: test_fromBigintArray2D"); + + QString str = LtsUtils::BigintArray2D({{0,-123456789},{1,-2147483647},{2,999999999999},{3,123456789},{4,2147483647}}).toPgString(); + + qDebug() << "LtsUtils: Result " << str; + QCOMPARE(str, QLatin1String("{{0,-123456789},{1,-2147483647},{2,999999999999},{3,123456789},{4,2147483647}}")); + + str = LtsUtils::BigintArray2D(std::map()).toPgString(); + + qDebug() << "LtsUtils: Result " << str; + QCOMPARE(str, QLatin1String("{}")); +} + + +void LtsUtilsTest::test_QPoint3D() +{ + qDebug("LtsUtils: test_QPoint3D"); + + LtsUtils::QPoint3D pos(0,0,0); + + qDebug() << "LstUtils: position (X,Y,Z) = (" << pos.x() << "," << pos.y() << "," << pos.z() << ")"; + + QVERIFY2(pos.x() == 0, "Wrong position (X)"); + QVERIFY2(pos.y() == 0, "Wrong position (Y)"); + QVERIFY2(pos.z() == 0, "Wrong position (Z)"); + + pos.setX(92000); + pos.setY(77400); + pos.setZ(0); + + qDebug() << "LstUtils: position (X,Y,Z) = (" << pos.x() << "," << pos.y() << "," << pos.z() << ")"; + + QVERIFY2(pos.x() == 92000, "Wrong position (X)"); + QVERIFY2(pos.y() == 77400, "Wrong position (Y)"); + QVERIFY2(pos.z() == 0, "Wrong position (Z)"); +} + + + +void LtsUtilsTest::test_bitArray() +{ + qDebug("LtsUtils: test_bitArray"); + + { + QVERIFY2(LtsUtils::BitArray::convert>(QBitArray(8)).size() == 1, " wrong size of underlying data"); + QVERIFY2(LtsUtils::BitArray::convert>(QBitArray(9)).size() == 2, " wrong size of underlying data"); + QVERIFY2(LtsUtils::BitArray::convert>(QBitArray(16)).size() == 2, " wrong size of underlying data"); + QVERIFY2(LtsUtils::BitArray::convert>(QBitArray(17)).size() == 3, " wrong size of underlying data"); + } + + + + { + QBitArray ba(10); + { + int i = 0; + ba[i++] = true; + ba[i++] = false; + ba[i++] = true; + ba[i++] = false; + ba[i++] = true; + + ba[i++] = false; + ba[i++] = true; + ba[i++] = false; + ba[i++] = true; + ba[i++] = false; + } + + const std::vector bArray = LtsUtils::BitArray::convert>(ba); + + + for(int position=0; position<20;++position) + LtsUtils::BitArray::testBit(bArray, position); + + + + { + QBitArray retrievedBd = LtsUtils::BitArray::convert(bArray, ba.size()); + QVERIFY2(ba == retrievedBd, " forward/backward failed"); + } + + + int i = 0; + QVERIFY2(LtsUtils::BitArray::testBit(bArray, i++) == true, " testBit failed"); + QVERIFY2(LtsUtils::BitArray::testBit(bArray, i++) == false, " testBit failed"); + QVERIFY2(LtsUtils::BitArray::testBit(bArray, i++) == true, " testBit failed"); + QVERIFY2(LtsUtils::BitArray::testBit(bArray, i++) == false, " testBit failed"); + QVERIFY2(LtsUtils::BitArray::testBit(bArray, i++) == true, " testBit failed"); + + QVERIFY2(LtsUtils::BitArray::testBit(bArray, i++) == false, " testBit failed"); + QVERIFY2(LtsUtils::BitArray::testBit(bArray, i++) == true, " testBit failed"); + QVERIFY2(LtsUtils::BitArray::testBit(bArray, i++) == false, " testBit failed"); + QVERIFY2(LtsUtils::BitArray::testBit(bArray, i++) == true, " testBit failed"); + QVERIFY2(LtsUtils::BitArray::testBit(bArray, i++) == false, " testBit failed"); + } + + + std::vector bArrayCopy; + { + QBitArray ba(10); + { + int i = 0; + ba[i++] = true; + ba[i++] = true; + ba[i++] = true; + ba[i++] = true; + ba[i++] = true; + + ba[i++] = false; + ba[i++] = false; + ba[i++] = false; + ba[i++] = false; + ba[i++] = false; + } + + const std::vector bArray = LtsUtils::BitArray::convert>(ba); + + + { + QBitArray retrievedBd = LtsUtils::BitArray::convert(bArray, ba.size()); + QVERIFY2(ba == retrievedBd, " forward/backward failed"); + } + + int i = 0; + QVERIFY2(LtsUtils::BitArray::testBit(bArray, i++) == true, " testBit failed"); + QVERIFY2(LtsUtils::BitArray::testBit(bArray, i++) == true, " testBit failed"); + QVERIFY2(LtsUtils::BitArray::testBit(bArray, i++) == true, " testBit failed"); + QVERIFY2(LtsUtils::BitArray::testBit(bArray, i++) == true, " testBit failed"); + QVERIFY2(LtsUtils::BitArray::testBit(bArray, i++) == true, " testBit failed"); + + QVERIFY2(LtsUtils::BitArray::testBit(bArray, i++) == false, " testBit failed"); + QVERIFY2(LtsUtils::BitArray::testBit(bArray, i++) == false, " testBit failed"); + QVERIFY2(LtsUtils::BitArray::testBit(bArray, i++) == false, " testBit failed"); + QVERIFY2(LtsUtils::BitArray::testBit(bArray, i++) == false, " testBit failed"); + QVERIFY2(LtsUtils::BitArray::testBit(bArray, i++) == false, " testBit failed"); + + + bArrayCopy = bArray; + } + + { + + + + LtsUtils::BitArray::clearBit(bArrayCopy, 0); + LtsUtils::BitArray::clearBit(bArrayCopy, 1); + LtsUtils::BitArray::clearBit(bArrayCopy, 2); + LtsUtils::BitArray::clearBit(bArrayCopy, 3); + LtsUtils::BitArray::clearBit(bArrayCopy, 4); + QVERIFY2(bArrayCopy[0] == 0, " clearBit failed"); + + + LtsUtils::BitArray::setBit(bArrayCopy, 1); + LtsUtils::BitArray::setBit(bArrayCopy, 2); + LtsUtils::BitArray::setBit(bArrayCopy, 3); + LtsUtils::BitArray::setBit(bArrayCopy, 4); + LtsUtils::BitArray::setBit(bArrayCopy, 5); + + int i = 0; + QVERIFY2(LtsUtils::BitArray::testBit(bArrayCopy, i++) == false, " testBit failed"); + QVERIFY2(LtsUtils::BitArray::testBit(bArrayCopy, i++) == true, " testBit failed"); + QVERIFY2(LtsUtils::BitArray::testBit(bArrayCopy, i++) == true, " testBit failed"); + QVERIFY2(LtsUtils::BitArray::testBit(bArrayCopy, i++) == true, " testBit failed"); + QVERIFY2(LtsUtils::BitArray::testBit(bArrayCopy, i++) == true, " testBit failed"); + + QVERIFY2(LtsUtils::BitArray::testBit(bArrayCopy, i++) == true, " testBit failed"); + QVERIFY2(LtsUtils::BitArray::testBit(bArrayCopy, i++) == false, " testBit failed"); + QVERIFY2(LtsUtils::BitArray::testBit(bArrayCopy, i++) == false, " testBit failed"); + QVERIFY2(LtsUtils::BitArray::testBit(bArrayCopy, i++) == false, " testBit failed"); + QVERIFY2(LtsUtils::BitArray::testBit(bArrayCopy, i++) == false, " testBit failed"); + + + { + QBitArray ba = LtsUtils::BitArray::convert(bArrayCopy, 10); + const std::vector retrievedVec = LtsUtils::BitArray::convert>(ba); + QVERIFY2(bArrayCopy == retrievedVec, " forward/backward failed"); + } + + } +} + + + +void LtsUtilsTest::test_convert() +{ + constexpr int containerLen = 50; + + + QByteArray ba(containerLen, Qt::Uninitialized); + for (int i = 0; i < containerLen; ++i) + ba[i] = i + 1; + const auto result = LtsUtils::convert>(ba); + const auto resultBA = LtsUtils::toQByteArray(result); + QCOMPARE(result.size(), containerLen); + QCOMPARE(resultBA.size(), containerLen); + for (int i = 0; i < containerLen; ++i) + QCOMPARE(resultBA.at(i), i + 1); + + + QVector> outer; + outer.reserve(containerLen); + for (int i = 0; i < containerLen; ++i) + outer.push_back(result); + const auto resultVec = LtsUtils::convert>>(outer); + const auto resultQVec = LtsUtils::convert>>(resultVec); + QCOMPARE(resultVec.size(), containerLen); + QCOMPARE(resultQVec.size(), containerLen); + for (const auto &inner : resultQVec) + { + for (int i = 0; i < containerLen; ++i) + QCOMPARE(inner.at(i), i + 1); + } + + + QVERIFY(!LtsUtils::isInVector(result, 0)); + QVERIFY(LtsUtils::isInVector(result, 1)); + QVERIFY(LtsUtils::isInVector(result, containerLen)); + QVERIFY(!LtsUtils::isInVector(result, containerLen + 1)); +} + + + +class Emitter : public QObject +{ + Q_OBJECT + +signals: + void signal1(); + void signal2(int i); +}; + +class Spy : public QObject +{ + Q_OBJECT + +public slots: + void slot1() { ++m_slot1Count; } + +public: + int m_slot1Count{0}; +}; + +void LtsUtilsTest::test_connection() +{ + Emitter emitter; + Spy spy; + std::unique_ptr conn(LtsUtils::createConnection(&emitter, &Emitter::signal1, &spy, &Spy::slot1)); + emit emitter.signal1(); + QCOMPARE(spy.m_slot1Count, 0); + + conn->connect(); + emit emitter.signal1(); + QCOMPARE(spy.m_slot1Count, 1); + + conn->disconnect(); + emit emitter.signal1(); + QCOMPARE(spy.m_slot1Count, 1); +} + +void LtsUtilsTest::test_queuedConnection() +{ + Emitter emitter; + Spy spy; + std::unique_ptr conn(LtsUtils::createConnection(&emitter, &Emitter::signal2, &spy, &Spy::slot1)); + emit emitter.signal2(42); + QCOMPARE(spy.m_slot1Count, 0); + + conn->connect(Qt::QueuedConnection); + emit emitter.signal2(42); + QCOMPARE(spy.m_slot1Count, 0); + + QTRY_COMPARE(spy.m_slot1Count, 1); +} + +void LtsUtilsTest::test_signalSignalConnection() +{ + Emitter emitter; + Spy spy; + std::unique_ptr signalSignalConn(LtsUtils::createConnection(&emitter, &Emitter::signal2, &emitter, &Emitter::signal1)); + std::unique_ptr signalSlotConn(LtsUtils::createConnection(&emitter, &Emitter::signal1, &spy, &Spy::slot1)); + + signalSignalConn->connect(); + signalSlotConn->connect(); + + emit emitter.signal2(42); + QCOMPARE(spy.m_slot1Count, 1); +} + +void LtsUtilsTest::test_danglingConnection1() +{ + Spy spy; + std::unique_ptr conn; + + { + Emitter emitter; + conn.reset(LtsUtils::createConnection(&emitter, &Emitter::signal2, &spy, &Spy::slot1)); + } + + conn->connect(); + conn->disconnect(); +} + +void LtsUtilsTest::test_danglingConnection2() +{ + Emitter emitter; + std::unique_ptr conn; + + { + Spy spy; + conn.reset(LtsUtils::createConnection(&emitter, &Emitter::signal2, &spy, &Spy::slot1)); + } + + conn->connect(); + conn->disconnect(); +} + +void LtsUtilsTest::test_danglingConnection3() +{ + Emitter emitter; + std::unique_ptr spy(new Spy); + std::unique_ptr conn; + + conn.reset(LtsUtils::createConnection(&emitter, &Emitter::signal2, spy.get(), &Spy::slot1)); + + conn->connect(); + spy.reset(); + conn->disconnect(); +} + +void LtsUtilsTest::test_jsonObject_fromJson() +{ + { + std::string json = R"({})"; + const auto obj = JsonObject::fromJson(json); + QVERIFY(obj.isValid()); + QVERIFY(obj.isEmpty()); + } + + { + std::string json = R"({"a": 1, "b": true})"; + const auto obj = JsonObject::fromJson(json); + QVERIFY(obj.isValid()); + QVERIFY(!obj.isEmpty()); + } + + { + std::string json = R"({"a": 1, "b": })"; + const auto obj = JsonObject::fromJson(json); + QVERIFY(!obj.isValid()); + QVERIFY(obj.isEmpty()); + } + + { + std::string json = "[1, 2, 3, 4]"; + const auto obj = JsonObject::fromJson(json); + QVERIFY(!obj.isValid()); + QVERIFY(obj.isEmpty()); + } +} + +void LtsUtilsTest::test_jsonObject_contains() +{ + std::string json = R"({ "int": 42, "double": 3.14, "string": "hello json", "bool": true, "null": null, + "object": {}, + "int array": [1, 2, 3], + "string array": ["hello", "json"], + "mixed array": ["hello", 42], + "int map": {"i": 1, "j": 2, "k": 3}, + "mixed map": {"i": 1, "j": 2, "k": "3"} + })"; + JsonObject obj = JsonObject::fromJson(json); + + QVERIFY(obj.contains("int")); + QVERIFY(obj.contains("int")); + QVERIFY(!obj.contains("int")); + QVERIFY(!obj.contains("int")); + QVERIFY(!obj.contains("int")); + QVERIFY(!obj.contains("int")); + QVERIFY(!obj.contains("int")); + QVERIFY(!obj.contains("int")); + + QVERIFY(!obj.contains("double")); + QVERIFY(obj.contains("double")); + QVERIFY(!obj.contains("double")); + QVERIFY(!obj.contains("double")); + QVERIFY(!obj.contains("double")); + QVERIFY(!obj.contains("double")); + QVERIFY(!obj.contains("double")); + QVERIFY(!obj.contains("double")); + + QVERIFY(!obj.contains("string")); + QVERIFY(!obj.contains("string")); + QVERIFY(!obj.contains("string")); + QVERIFY(obj.contains("string")); + QVERIFY(obj.contains("string")); + QVERIFY(!obj.contains("string")); + QVERIFY(!obj.contains("string")); + QVERIFY(!obj.contains("string")); + + QVERIFY(!obj.contains("bool")); + QVERIFY(!obj.contains("bool")); + QVERIFY(obj.contains("bool")); + QVERIFY(!obj.contains("bool")); + QVERIFY(!obj.contains("bool")); + QVERIFY(!obj.contains("bool")); + QVERIFY(!obj.contains("bool")); + QVERIFY(!obj.contains("bool")); + + QVERIFY(!obj.contains("null")); + QVERIFY(!obj.contains("null")); + QVERIFY(!obj.contains("null")); + QVERIFY(!obj.contains("null")); + QVERIFY(!obj.contains("null")); + QVERIFY(!obj.contains("null")); + QVERIFY(!obj.contains("null")); + QVERIFY(obj.contains("null")); + + QVERIFY(!obj.contains("object")); + QVERIFY(!obj.contains("object")); + QVERIFY(!obj.contains("object")); + QVERIFY(!obj.contains("object")); + QVERIFY(!obj.contains("object")); + QVERIFY(obj.contains("object")); + QVERIFY(obj.contains("object")); + QVERIFY(!obj.contains("object")); + QVERIFY(!obj.contains("object")); + + QVERIFY(!obj.contains("int array")); + QVERIFY(!obj.contains("int array")); + QVERIFY(!obj.contains("int array")); + QVERIFY(!obj.contains("int array")); + QVERIFY(!obj.contains("int array")); + QVERIFY(!obj.contains("int array")); + QVERIFY(obj.contains("int array")); + QVERIFY(obj.contains("int array")); + QVERIFY(!obj.contains("int array")); + + QVERIFY(obj.contains("mixed array")); + QVERIFY(obj.contains("mixed array")); + + + QVERIFY(obj.contains>("int array")); + QVERIFY(!obj.contains>("int array")); + QVERIFY(obj.contains>("int array")); + QVERIFY(!obj.contains>("int array")); + + QVERIFY((obj.contains>("int map"))); + QVERIFY(!(obj.contains>("mixed map"))); + QVERIFY((obj.contains>("int map"))); + QVERIFY(!(obj.contains>("mixed map"))); +} + +void LtsUtilsTest::test_jsonObject_value() +{ + std::string json = R"({ "int": 42, "double": 3.14, "string": "hello json", "bool": true, "null": null, + "object": {"a": 1, "b": 2, "c": 3}, + "int array": [1, 2, 3], + "string array": ["hello", "json"], + "mixed array": ["hello", 42], + "int map": {"i": 1, "j": 2, "k": 3} + })"; + JsonObject obj = JsonObject::fromJson(json); + + QCOMPARE(obj.value("int"), 42); + QCOMPARE(obj.value("double"), 3.14); + QCOMPARE(obj.value("string").c_str(), "hello json"); + QCOMPARE(obj.value("string"), QStringLiteral("hello json")); + QCOMPARE(obj.value("bool"), true); + + QJsonObject expectedObject{ {QStringLiteral("a"), 1}, {QStringLiteral("b"), 2}, {QStringLiteral("c"), 3} }; + QCOMPARE(obj.value("object"), expectedObject); + QCOMPARE(obj.value("object"), JsonObject(expectedObject)); + + QJsonArray expectedMixedArray{QStringLiteral("hello"), 42}; + QCOMPARE(obj.value("mixed array"), expectedMixedArray); + QCOMPARE(obj.value("mixed array"), JsonArray(expectedMixedArray)); + + QVector expectedIntArray{1, 2, 3}; + QCOMPARE(obj.value>("int array"), expectedIntArray); + + std::vector expectedStdIntArray{1, 2, 3}; + QCOMPARE(obj.value>("int array"), expectedStdIntArray); + + QVector expectedStringArray{QStringLiteral("hello"), QStringLiteral("json")}; + QCOMPARE(obj.value>("string array"), expectedStringArray); + + std::vector expectedStdStringArray{"hello", "json"}; + QCOMPARE(obj.value>("string array"), expectedStdStringArray); + + QMap expectedIntMap{ {QStringLiteral("i"), 1}, {QStringLiteral("j"), 2}, {QStringLiteral("k"), 3} }; + QCOMPARE((obj.value>("int map")), expectedIntMap); + + std::map expectedStdIntMap{ {"i", 1}, {"j", 2}, {"k", 3} }; + QCOMPARE((obj.value>("int map")), expectedStdIntMap); + + QVERIFY(obj.isNull("null")); +} + +void LtsUtilsTest::test_jsonObject_valueWithError() +{ + std::string json = R"({ "int": 42, "double": 3.14, "string": "hello json", "bool": true, "null": null, + "object": {"a": 1, "b": 2, "c": 3}, + "int array": [1, 2, 3], + "string array": ["hello", "json"], + "mixed array": ["hello", 42], + "mixed map": {"i": 1, "j": 2, "k": "3"} + })"; + JsonObject obj = JsonObject::fromJson(json); + + QVERIFY_EXCEPTION_THROWN(obj.value("unknown key"), JsonKeyError); + + QVERIFY_EXCEPTION_THROWN(obj.value("double"), JsonTypeError); + QVERIFY_EXCEPTION_THROWN(obj.value("string"), JsonTypeError); + QVERIFY_EXCEPTION_THROWN(obj.value("int"), JsonTypeError); + QVERIFY_EXCEPTION_THROWN(obj.value("int"), JsonTypeError); + QVERIFY_EXCEPTION_THROWN(obj.value("int"), JsonTypeError); + QVERIFY_EXCEPTION_THROWN(obj.value("int"), JsonTypeError); + QVERIFY_EXCEPTION_THROWN(obj.value("object"), JsonTypeError); + QVERIFY_EXCEPTION_THROWN(obj.value>("mixed array"), JsonTypeError); + QVERIFY_EXCEPTION_THROWN((obj.value>("mixed map")), JsonTypeError); + QVERIFY_EXCEPTION_THROWN((obj.value>("mixed map")), JsonTypeError); +} + +void LtsUtilsTest::test_jsonObject_valueOr() +{ + QJsonObject obj_{ {QStringLiteral("int"), 42} }; + JsonObject obj(obj_); + + QCOMPARE(obj.valueOr("int", -1), 42); + + + QCOMPARE(obj.valueOr("string", -1), -1); + + + QVERIFY_EXCEPTION_THROWN(obj.valueOr("int", QString()), JsonTypeError); +} + +void LtsUtilsTest::test_jsonObject_integers() +{ + std::string json = R"({ + "int16_min": -32768, + "int16_max": 32767, + "int16_min-1": -32769, + "int16_max+1": 32768, + + "int32_min": -2147483648, + "int32_max": 2147483647, + "int32_min-1": -2147483649, + "int32_max+1": 2147483648, + + "int64_min": -9007199254740991, + "int64_max": 9007199254740991, + "int64_min-1": -9007199254740992, + "int64_max+1": 9007199254740992, + + "uint_min": 0, + "uint16_max": 65535, + "uint_min-1": -1, + "uint16_max+1": 65536, + + "uint32_max": 4294967295, + "uint32_max+1": 4294967296, + + "uint64_max": 9007199254740991, + "uint64_max+1": 9007199254740992 + })"; + JsonObject obj = JsonObject::fromJson(json); + QVERIFY(obj.contains("int16_min")); + QVERIFY(obj.contains("int16_max")); + QVERIFY(!obj.contains("int16_min-1")); + QVERIFY(!obj.contains("int16_max+1")); + QVERIFY(obj.contains("int16_min-1")); + QVERIFY(obj.contains("int16_max+1")); + + QVERIFY(obj.contains("int32_min")); + QVERIFY(obj.contains("int32_max")); + QVERIFY(!obj.contains("int32_min-1")); + QVERIFY(!obj.contains("int32_max+1")); + QVERIFY(obj.contains("int32_min-1")); + QVERIFY(obj.contains("int32_max+1")); + + QVERIFY(obj.contains("int64_min")); + QVERIFY(obj.contains("int64_max")); + QVERIFY(!obj.contains("int64_min-1")); + QVERIFY(!obj.contains("int64_max+1")); + + + QVERIFY(obj.contains("uint_min")); + QVERIFY(obj.contains("uint16_max")); + QVERIFY(!obj.contains("uint_min-1")); + QVERIFY(!obj.contains("uint16_max+1")); + QVERIFY(obj.contains("uint16_max+1")); + + QVERIFY(obj.contains("uint_min")); + QVERIFY(obj.contains("uint32_max")); + QVERIFY(!obj.contains("uint_min-1")); + QVERIFY(!obj.contains("uint32_max+1")); + QVERIFY(obj.contains("uint32_max+1")); + + QVERIFY(obj.contains("uint_min")); + QVERIFY(obj.contains("uint64_max")); + QVERIFY(!obj.contains("uint_min-1")); + QVERIFY(!obj.contains("uint64_max+1")); +} + +void LtsUtilsTest::test_jsonObject_nullValues() +{ + std::string jsonNullMap = R"( {"a": null, "b": null, "c": null} )"; + JsonObject obj = JsonObject::fromJson(jsonNullMap); + QVERIFY(obj.isMap()); +} + +void LtsUtilsTest::test_jsonArray_fromJson() +{ + { + std::string json = "[]"; + const auto arr = JsonArray::fromJson(json); + QVERIFY(arr.isValid()); + QVERIFY(arr.isEmpty()); + } + + { + std::string json = R"([1, 2, true, false, null])"; + const auto arr = JsonArray::fromJson(json); + QVERIFY(arr.isValid()); + QVERIFY(!arr.isEmpty()); + } + + { + std::string json = R"({"a": 1, "b": })"; + const auto arr = JsonArray::fromJson(json); + QVERIFY(!arr.isValid()); + QVERIFY(arr.isEmpty()); + } + + { + std::string json = R"({"a": 1, "b": true})"; + const auto arr = JsonArray::fromJson(json); + QVERIFY(!arr.isValid()); + QVERIFY(arr.isEmpty()); + } +} + +void LtsUtilsTest::test_jsonArray() +{ + std::string jsonIntArray = R"( [0, 1, 2, 3] )"; + JsonArray array = JsonArray::fromJson(jsonIntArray); + QCOMPARE(array.size(), 4); + + QVERIFY(array.contains(0)); + QVERIFY(!array.contains(0)); + QCOMPARE(array.at(0), 0); + + QVERIFY_EXCEPTION_THROWN(array.at(0), JsonTypeError); + + QVERIFY_EXCEPTION_THROWN(array.contains(-1), JsonRangeError); + QVERIFY_EXCEPTION_THROWN(array.contains(4), JsonRangeError); + QVERIFY_EXCEPTION_THROWN(array.at(-1), JsonRangeError); + QVERIFY_EXCEPTION_THROWN(array.at(4), JsonRangeError); + + QVERIFY(array.isVector()); + QVERIFY(!array.isVector()); + QCOMPARE(array.toVector(), QVector({0, 1, 2, 3})); + QCOMPARE(array.toStdVector(), std::vector({0, 1, 2, 3})); +} + +void LtsUtilsTest::test_jsonArray_nullArray() +{ + std::string jsonNullArray = R"( [null, null, null, null] )"; + JsonArray array = JsonArray::fromJson(jsonNullArray); + QVERIFY(array.isNull(0)); + QVERIFY(array.isVector()); +} + +void LtsUtilsTest::test_byteStreamFromSfmMessage() +{ + SfmMessage msg; + + msg.setId(16); + msg.setRtr(true); + const quint8 payloadArray [] = {0x11, 0x02, 0x03, 0x04, 0x50}; + msg.setPayload(QByteArray(reinterpret_cast(payloadArray), sizeof(payloadArray))); + + const quint8 expectedResultArray [] = {0x02, 0x80, 0x00, 0x10, 0x05, + 0x80, 0x91, 0x80, 0x82, 0x80, + 0x83, 0x04, 0x50, 0x00, 0x03}; + QByteArray expectedResult = QByteArray(reinterpret_cast(expectedResultArray), sizeof(expectedResultArray)); + + QByteArray byteMessage = byteStreamFromSfmMessage (msg); + + QCOMPARE(byteMessage, expectedResult); +} + +void LtsUtilsTest::test_byteStreamFromSfmMessage2() +{ + SfmMessage msg = LtsUtils::byteStreamToSfmMessage(QByteArray::fromHex("0280004f003003")); + QCOMPARE(msg.rtr(), true); + QVERIFY2(msg.id() == 0x4f, "Value not expected"); + QVERIFY2((msg.id() | (msg.rtr() << 15)) == 0x804f, "Value not expected"); +} + +void LtsUtilsTest::test_byteStreamToSfmMessage() +{ + SfmMessage msgComplete; + msgComplete.setId(16); + msgComplete.setRtr(true); + + const quint8 payloadArray [] = {0x11, 0x02, 0x03, 0x04, 0x50, 0x00}; + msgComplete.setPayload(QByteArray(reinterpret_cast(payloadArray), sizeof(payloadArray))); + + const quint8 dataArray [] = {0x02, 0x80, 0x00, 0x10, 0x06, + 0x80, 0x91, 0x80, 0x82, 0x80, + 0x83, 0x04, 0x50, 0x00, 0xFF, 0x03}; + QByteArray dataComplete = QByteArray(reinterpret_cast(dataArray), sizeof(dataArray)); + + SfmMessage result; + try + { + result = byteStreamToSfmMessage(dataComplete); + } + catch (const ConversionError &ex) + { + qDebug() << ex.what(); + } + QCOMPARE(result, msgComplete); + + + QVERIFY_EXCEPTION_THROWN(byteStreamToSfmMessage(QByteArray()), ConversionError); + + + QByteArray framingError = dataComplete; + framingError.chop(1); + QVERIFY_EXCEPTION_THROWN(byteStreamToSfmMessage(framingError), ConversionError); + + + QByteArray checksumError = dataComplete; + checksumError[14] = 0x01; + QVERIFY_EXCEPTION_THROWN(byteStreamToSfmMessage(checksumError), ConversionError); + + + QByteArray lengthError = dataComplete; + QVERIFY_EXCEPTION_THROWN(byteStreamToSfmMessage(lengthError.remove(13,1)), ConversionError); +} + +void LtsUtilsTest::byteAccess() +{ + QByteArray ba; + LtsUtils::set1Byte(0x67, ba); + LtsUtils::set1Byte(0xcb, ba); + LtsUtils::set2Byte(0x1234, ba); + LtsUtils::set2Byte(0x9876, ba); + LtsUtils::set3Byte(0x6263fb, ba); + LtsUtils::set3Byte(0x80e87b, ba); + LtsUtils::set4Byte(0x66b9dd15, ba); + LtsUtils::set4Byte(0xa254a715, ba); + LtsUtils::set8Byte(0x1234567812345678, ba); + LtsUtils::set8Byte(0x8234567812345678, ba); + + { + int64_t i = 0; + size_t pos = 0; + LtsUtils::get1Byte(ba, pos, i); + QCOMPARE(i, 103); + LtsUtils::get1Byte(ba, pos, i); + QCOMPARE(i, -53); + LtsUtils::get2Byte(ba, pos, i); + QCOMPARE(i, 4660); + LtsUtils::get2Byte(ba, pos, i); + QCOMPARE(i, -26506); + LtsUtils::get3Byte(ba, pos, i); + QCOMPARE(i, 6448123); + LtsUtils::get3Byte(ba, pos, i); + QCOMPARE(i, -8329093); + LtsUtils::get4Byte(ba, pos, i); + QCOMPARE(i, 1723456789); + LtsUtils::get4Byte(ba, pos, i); + QCOMPARE(i, -1571510507); + LtsUtils::get8Byte(ba, pos, i); + QCOMPARE(i, 0x1234567812345678); + LtsUtils::get8Byte(ba, pos, i); + QCOMPARE(i, 0x8234567812345678); + + uint64_t u = 0; + pos = 0; + LtsUtils::get1Byte(ba, pos, u); + QCOMPARE(u, 103); + LtsUtils::get1Byte(ba, pos, u); + QCOMPARE(u, 203); + LtsUtils::get2Byte(ba, pos, u); + QCOMPARE(u, 4660); + LtsUtils::get2Byte(ba, pos, u); + QCOMPARE(u, 39030); + LtsUtils::get3Byte(ba, pos, u); + QCOMPARE(u, 6448123); + LtsUtils::get3Byte(ba, pos, u); + QCOMPARE(u, 8448123); + LtsUtils::get4Byte(ba, pos, u); + QCOMPARE(u, 1723456789); + LtsUtils::get4Byte(ba, pos, u); + QCOMPARE(u, 2723456789); + LtsUtils::get8Byte(ba, pos, u); + QCOMPARE(u, 0x1234567812345678); + LtsUtils::get8Byte(ba, pos, u); + QCOMPARE(u, 0x8234567812345678); + } + { + int32_t i = 0; + size_t pos = 0; + LtsUtils::get1Byte(ba, pos, i); + QCOMPARE(i, 103); + LtsUtils::get1Byte(ba, pos, i); + QCOMPARE(i, -53); + LtsUtils::get2Byte(ba, pos, i); + QCOMPARE(i, 4660); + LtsUtils::get2Byte(ba, pos, i); + QCOMPARE(i, -26506); + LtsUtils::get3Byte(ba, pos, i); + QCOMPARE(i, 6448123); + LtsUtils::get3Byte(ba, pos, i); + QCOMPARE(i, -8329093); + LtsUtils::get4Byte(ba, pos, i); + QCOMPARE(i, 1723456789); + LtsUtils::get4Byte(ba, pos, i); + QCOMPARE(i, -1571510507); + + uint32_t u = 0; + pos = 0; + LtsUtils::get1Byte(ba, pos, u); + QCOMPARE(u, 103); + LtsUtils::get1Byte(ba, pos, u); + QCOMPARE(u, 203); + LtsUtils::get2Byte(ba, pos, u); + QCOMPARE(u, 4660); + LtsUtils::get2Byte(ba, pos, u); + QCOMPARE(u, 39030); + LtsUtils::get3Byte(ba, pos, u); + QCOMPARE(u, 6448123); + LtsUtils::get3Byte(ba, pos, u); + QCOMPARE(u, 8448123); + LtsUtils::get4Byte(ba, pos, u); + QCOMPARE(u, 1723456789); + LtsUtils::get4Byte(ba, pos, u); + QCOMPARE(u, 2723456789); + } + { + int16_t i = 0; + size_t pos = 0; + LtsUtils::get1Byte(ba, pos, i); + QCOMPARE(i, 103); + LtsUtils::get1Byte(ba, pos, i); + QCOMPARE(i, -53); + LtsUtils::get2Byte(ba, pos, i); + QCOMPARE(i, 4660); + LtsUtils::get2Byte(ba, pos, i); + QCOMPARE(i, -26506); + + uint16_t u = 0; + pos = 0; + LtsUtils::get1Byte(ba, pos, u); + QCOMPARE(u, 103); + LtsUtils::get1Byte(ba, pos, u); + QCOMPARE(u, 203); + LtsUtils::get2Byte(ba, pos, u); + QCOMPARE(u, 4660); + LtsUtils::get2Byte(ba, pos, u); + QCOMPARE(u, 39030); + } + { + int8_t i = 0; + size_t pos = 0; + LtsUtils::get1Byte(ba, pos, i); + QCOMPARE(i, 103); + LtsUtils::get1Byte(ba, pos, i); + QCOMPARE(i, -53); + + uint8_t u = 0; + pos = 0; + LtsUtils::get1Byte(ba, pos, u); + QCOMPARE(u, 103); + LtsUtils::get1Byte(ba, pos, u); + QCOMPARE(u, 203); + } +} + + +#include "utils_test.moc" diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/UnitTests/utils_test.h b/Lts/Common/LtsUtils/LtsUtilsCore/UnitTests/utils_test.h new file mode 100755 index 000000000..e6a37453b --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/UnitTests/utils_test.h @@ -0,0 +1,62 @@ + + +#pragma once + +#include + +class LtsUtilsTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + + void test_toIntArray(); + void test_fromIntArray(); + void test_toBigintArray(); + void test_fromBigintArray(); + void test_toIntArray2D(); + void test_fromIntArray2D(); + void test_toBigintArray2D(); + void test_fromBigintArray2D(); + + void test_QPoint3D(); + + + void test_bitArray(); + + + void test_convert(); + + + void test_connection(); + void test_queuedConnection(); + void test_signalSignalConnection(); + void test_danglingConnection1(); + void test_danglingConnection2(); + void test_danglingConnection3(); + + + 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(); + + + void test_byteStreamFromSfmMessage(); + void test_byteStreamFromSfmMessage2(); + void test_byteStreamToSfmMessage(); + + + void test_promise(); + void test_promise_example1(); + void test_promise_example2(); + void test_promise_example3(); + + + void byteAccess(); +}; diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/await.h b/Lts/Common/LtsUtils/LtsUtilsCore/await.h new file mode 100755 index 000000000..d48cbe915 --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/await.h @@ -0,0 +1,116 @@ +#pragma once + + + + +#include +#include +#include + +#include + + +namespace LtsUtils +{ +template +void addConnect(QEventLoop &x, bool &success, const typename QtPrivate::FunctionPointer::Object *sender, Func signal, bool isSuccess) +{ + if (isSuccess) + { + QObject::connect(sender, signal, &x, [&success]() + { + + success = true; + }); + } + + QObject::connect(sender, signal, &x, &QEventLoop::quit, Qt::QueuedConnection); +} + + +template +void addConnect(QEventLoop &x, bool &success, const typename QtPrivate::FunctionPointer::Object *sender, Func signal, bool isSuccess, Connect... connects) +{ + addConnect(x, success, sender, signal, isSuccess); + addConnect(x, success, connects...); +} + + +template +void await(Connect... connects) noexcept(false) +{ + QEventLoop x; + bool success = false; + + addConnect(x, success, connects...); + + + x.exec(); + + + if (!success) + { + + throw std::runtime_error("Failed."); + } +} + + +template +void awaitTimeout(const int timeoutMs, Connect... connects) noexcept(false) +{ + std::unique_ptr 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...); + + + if (timeout) + { + + throw std::runtime_error("Timeout."); + } +} + + + + + + + + + + + + + + + + + + +template +void awaitWithCancelationSignal(const typename QtPrivate::FunctionPointer::Object *sender1, Func1 signal1, + const typename QtPrivate::FunctionPointer::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); +} +} diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/connection.h b/Lts/Common/LtsUtils/LtsUtilsCore/connection.h new file mode 100755 index 000000000..47f736947 --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/connection.h @@ -0,0 +1,126 @@ + + + +#pragma once + +#include +#include +#include +#include +#include + + +namespace LtsUtils +{ + + + +class IConnection +{ +public: + virtual ~IConnection() = default; + virtual void connect(Qt::ConnectionType = Qt::AutoConnection) = 0; + virtual void disconnect() = 0; +}; + + + + +class DummyConnection : public IConnection +{ +public: + void connect(Qt::ConnectionType = Qt::AutoConnection) override {} + void disconnect() override {} +}; + + +namespace detail +{ + + + +template +struct ExtractClass; + + + +template +struct ExtractClass +{ + using type = T; +}; + +} + + + + +template +class Connection : public IConnection +{ + static_assert(std::is_member_function_pointer::value, "Must be a pointer to method"); + static_assert(std::is_member_function_pointer::value, "Must be a pointer to method"); + + using Emitter = typename detail::ExtractClass::type; + using Target = typename detail::ExtractClass::type; + + static_assert(std::is_base_of::value, "Must be derived from QObject"); + static_assert(std::is_base_of::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; + + + + 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 m_emitter; + Signal m_signal; + QPointer m_target; + Slot m_slot; + QMetaObject::Connection m_conn; +}; + + + +template +Connection* createConnection(typename detail::ExtractClass::type *emitter, Signal signal, + typename detail::ExtractClass::type *target, Slot slot) +{ + return new Connection(emitter, signal, target, slot); +} + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/data_modification_enum.h b/Lts/Common/LtsUtils/LtsUtilsCore/data_modification_enum.h new file mode 100755 index 000000000..c2a316a43 --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/data_modification_enum.h @@ -0,0 +1,30 @@ + + + +#pragma once + +#include + +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; +} diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/dds_bitarray.h b/Lts/Common/LtsUtils/LtsUtilsCore/dds_bitarray.h new file mode 100755 index 000000000..a26a6c73a --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/dds_bitarray.h @@ -0,0 +1,98 @@ + + + +#pragma once + +#ifdef SUPPORT_QBITARRAY +# include +#endif + +namespace LtsUtils +{ + +namespace BitArray +{ +constexpr inline int bitNumber(int position) +{ + return 7 - (position % 8); + +} + +template +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 +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; + else + bitField[byteNum] &= ~mask; + + return true; +} + +template +inline bool clearBit(T &bitField, int position) +{ + return setBit(bitField, position, false); +} + + +#ifdef SUPPORT_QBITARRAY +template +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; + else + ret[byteNum] &= ~mask; + } + return ret; +} + +template +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); + ba[i] = bit; + } + return ba; +} + +#endif + +} + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/dds_vector.h b/Lts/Common/LtsUtils/LtsUtilsCore/dds_vector.h new file mode 100755 index 000000000..7d92a2f23 --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/dds_vector.h @@ -0,0 +1,43 @@ + + + +#pragma once + +#include +#include + +namespace LtsUtils +{ + +template +inline void convert(const S &in, T &out) +{ + static_assert(!std::is_same::value, "Try to convert a type to the same type - is useless"); + out.reserve(static_cast(in.size())); + std::copy(in.begin(), in.end(), std::back_inserter(out)); +} + +template +inline T convert(const S &in) +{ + static_assert(!std::is_same::value, "Try to convert a type to the same type - is useless"); + T out; + convert(in, out); + return out; +} + + +template +inline QByteArray toQByteArray(const T &in) +{ + return convert(in); +} + +template +bool isInVector(const T &vec, const V &valueToSearch) +{ + const auto end = vec.end(); + return std::find(vec.begin(), end, valueToSearch) != end; +} + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/dds_vector_to_pixmap.h b/Lts/Common/LtsUtils/LtsUtilsCore/dds_vector_to_pixmap.h new file mode 100755 index 000000000..f94966b45 --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/dds_vector_to_pixmap.h @@ -0,0 +1,24 @@ + + + +#pragma once + +#include +#include +#include + +class QByteArray; +class QPixmap; +class QImage; + +namespace LtsUtils +{ + +LTSUTILSCORE_EXPORT QPixmap getPixmap(const std::vector &data); +LTSUTILSCORE_EXPORT QPixmap getPixmap(const QByteArray &data); +LTSUTILSCORE_EXPORT QImage getImage(const std::vector &data); +LTSUTILSCORE_EXPORT QImage getImage(const QByteArray &data); +LTSUTILSCORE_EXPORT std::vector fromPixmap(const QPixmap &pixmap, bool *bIsPng = nullptr); +LTSUTILSCORE_EXPORT std::vector fromImage(const QImage &image, bool *bIsPng = nullptr); + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/json.h b/Lts/Common/LtsUtils/LtsUtilsCore/json.h new file mode 100755 index 000000000..c91ae03cc --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/json.h @@ -0,0 +1 @@ +#include diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/json_object.h b/Lts/Common/LtsUtils/LtsUtilsCore/json_object.h new file mode 100755 index 000000000..b896ef56d --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/json_object.h @@ -0,0 +1,696 @@ + + + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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; +}; + + + + +class JsonObject +{ +public: + + + 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) + {} + + + + + + + + + + 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()); } + + + + + template + bool contains(const Key &key) const; + + bool isNull(const Key &key) const { return contains(key); } + + bool isEmpty() const { return m_json.isEmpty(); } + bool isValid() const { return m_jsonIsSet; } + + + + template + T value(const Key &key) const noexcept(false); + + + + template + T valueOr(const Key &key, const T &defaultValue) const noexcept(false); + + + + + template + bool isMap() const; + + + + template + QMap toMap() const noexcept(false); + + + template + std::map 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()); +} + + + + + +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) + {} + + + + + + + + + + + + + + + + + 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; } + + + template + bool contains(int index) const noexcept(false); + + + bool isNull(int index) const noexcept(false) { return contains(index); } + + + template + T at(int index) const noexcept(false); + + + + template + bool isVector() const; + + + + template + QVector toVector() const noexcept(false); + + template + std::vector 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()); +} + + + + + +template +struct JsonValue; + + + +template +struct JsonLimits +{ + static constexpr Int lowest() { return std::numeric_limits::lowest(); } + static constexpr Int max() { return std::numeric_limits::max(); } +}; + + + +template<> +struct JsonLimits +{ + static constexpr int64_t lowest() { return -(static_cast(1) << 53) + 1; } + static constexpr int64_t max() { return (static_cast(1) << 53) - 1; } +}; + + + +template<> +struct JsonLimits +{ + static constexpr uint64_t lowest() { return 0; } + static constexpr uint64_t max() { return JsonLimits::max(); } +}; + + + +template +struct JsonIntValue +{ + static bool matchesType(const QJsonValue &value) + { + if (!value.isDouble()) + return false; + double val = value.toDouble(); + + if (val < JsonLimits::lowest()) + return false; + if (val > JsonLimits::max()) + return false; + + double wholeInt = static_cast(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(value.toDouble()); + } +}; + + + + +template<> +struct JsonValue : JsonIntValue +{}; + +template<> +struct JsonValue : JsonIntValue +{}; + +template<> +struct JsonValue : JsonIntValue +{}; + +template<> +struct JsonValue : JsonIntValue +{}; + +template<> +struct JsonValue : JsonIntValue +{}; + +template<> +struct JsonValue : JsonIntValue +{}; + + + + +template<> +struct JsonValue +{ + 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(); + } +}; + + + +template<> +struct JsonValue +{ + 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(); + } +}; + + + +template<> +struct JsonValue +{ + 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(); + } +}; + + + +template<> +struct JsonValue +{ + static bool matchesType(const QJsonValue &value) { return value.isString(); } + + static std::string value(const QJsonValue &value) + { + return JsonValue::value(value).toStdString(); + } +}; + + + +template<> +struct JsonValue +{ + 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(); + } +}; + + + +template<> +struct JsonValue +{ + static bool matchesType(const QJsonValue &value) { return value.isObject(); } + static JsonObject value(const QJsonValue &value) { return JsonObject(JsonValue::value(value)); } +}; + + + +template<> +struct JsonValue +{ + static bool matchesType(const QJsonValue &value) { return value.isNull(); } +}; + + + +template<> +struct JsonValue +{ + 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(); + } +}; + + + +template<> +struct JsonValue +{ + static bool matchesType(const QJsonValue &value) { return value.isArray(); } + static JsonArray value(const QJsonValue &value) { return JsonArray(JsonValue::value(value)); } +}; + + + +template +struct JsonValue> +{ + static bool matchesType(const QJsonValue &value) + { + if (!value.isArray()) + return false; + + const JsonArray array(value.toArray()); + return array.isVector(); + } + + static QVector value(const QJsonValue &value) + { + const JsonArray array(JsonValue::value(value)); + return array.toVector(); + } +}; + + + +template +struct JsonValue> +{ + static bool matchesType(const QJsonValue &value) + { + return JsonValue>::matchesType(value); + } + + static std::vector value(const QJsonValue &value) + { + const JsonArray array(JsonValue::value(value)); + return array.toStdVector(); + } +}; + + + +template +struct JsonValue> +{ + static bool matchesType(const QJsonValue &value) + { + if (!value.isObject()) + return false; + + const JsonObject obj(JsonValue::value(value)); + return obj.isMap(); + } + + static QMap value(const QJsonValue &value) + { + const JsonObject obj(JsonValue::value(value)); + return obj.toMap(); + } +}; + + + +template +struct JsonValue> +{ + static bool matchesType(const QJsonValue &value) + { + return JsonValue>::matchesType(value); + } + + static std::map value(const QJsonValue &value) + { + const JsonObject obj(JsonValue::value(value)); + return obj.toStdMap(); + } +}; + + + +template +bool JsonObject::contains(const Key &key) const +{ + if (!m_json.contains(key)) + return false; + return JsonValue::matchesType(m_json.value(key)); +} + +template +T JsonObject::value(const Key &key) const noexcept(false) +{ + if (!containsAny(key)) + throw JsonKeyError("no element with key: " + static_cast(key).toStdString()); + try + { + return JsonValue::value(m_json.value(key)); + } + catch(const JsonTypeError &typeError) + { + throw JsonTypeError(std::string(typeError.what()) + " - key: " + static_cast(key).toStdString()); + } +} + +template +T JsonObject::valueOr(const Key &key, const T &defaultValue) const +{ + try + { + return value(key); + } + + catch (const JsonKeyError &) + { + return defaultValue; + } +} + +template +bool JsonObject::isMap() const +{ + const QStringList keys = m_json.keys(); + for (const auto &key : keys) + if (!JsonValue::matchesType(m_json.value(key))) + return false; + + return true; +} + +template +QMap JsonObject::toMap() const +{ + QMap result; + for (auto current = m_json.begin(); current != m_json.end(); ++current) + result.insert(current.key(), JsonValue::value(current.value())); + return result; +} + +template +std::map JsonObject::toStdMap() const +{ + std::map result; + for (auto current = m_json.begin(); current != m_json.end(); ++current) + result.insert({current.key().toStdString(), JsonValue::value(current.value())}); + return result; +} + + + +template +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::matchesType(m_json.at(index)); +} + +template +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::value(m_json.at(index)); +} + +template +bool JsonArray::isVector() const +{ + for (const QJsonValue &value : m_json) + if (!JsonValue::matchesType(value)) + return false; + return true; +} + +template +QVector JsonArray::toVector() const +{ + QVector result; + result.reserve(m_json.size()); + for (const QJsonValue &value : m_json) + result.push_back(JsonValue::value(value)); + return result; +} + +template +std::vector JsonArray::toStdVector() const +{ + std::vector result; + result.reserve(m_json.size()); + for (const QJsonValue &value : m_json) + result.push_back(JsonValue::value(value)); + return result; +} + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/lts_portrange.cpp b/Lts/Common/LtsUtils/LtsUtilsCore/lts_portrange.cpp new file mode 100755 index 000000000..5bd5cb61d --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/lts_portrange.cpp @@ -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(point.x()), static_cast(point.y())}; +} + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/lts_portrange.h b/Lts/Common/LtsUtils/LtsUtilsCore/lts_portrange.h new file mode 100755 index 000000000..43d8f364a --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/lts_portrange.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include +#include + +namespace LtsUtils +{ + typedef QPair LtsPortRange; + + LTSUTILSCORE_EXPORT LtsPortRange fromQPoint(const QPoint &point); +} + + diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/lts_translatable_identifier.cpp b/Lts/Common/LtsUtils/LtsUtilsCore/lts_translatable_identifier.cpp new file mode 100755 index 000000000..239a14cc4 --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/lts_translatable_identifier.cpp @@ -0,0 +1,25 @@ + + + +#include "lts_translatable_identifier.h" + +#include + + +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); +} +} diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/lts_translatable_identifier.h b/Lts/Common/LtsUtils/LtsUtilsCore/lts_translatable_identifier.h new file mode 100755 index 000000000..84a4a3bec --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/lts_translatable_identifier.h @@ -0,0 +1,33 @@ +#pragma once + + + + +#include + +#include +#include + + +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 char *m_translatableText; +}; +} diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/outdated_position.h b/Lts/Common/LtsUtils/LtsUtilsCore/outdated_position.h new file mode 100755 index 000000000..20f04b9f4 --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/outdated_position.h @@ -0,0 +1,15 @@ + + + +#pragma once + +#include + +namespace LtsUtils +{ +inline bool isPositionTimeOutdated(qint64 curTime, qint64 posTime) +{ + + return curTime > posTime + 11 * 60 * 1000; +} +} diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/pgarray.cpp b/Lts/Common/LtsUtils/LtsUtilsCore/pgarray.cpp new file mode 100755 index 000000000..f99ded658 --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/pgarray.cpp @@ -0,0 +1,169 @@ + + + +#include "pgarray.h" +#include +#include + +#include +#include + +namespace LtsUtils +{ + +template +static inline QString convertToPgString(const std::vector &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 +static inline T convertFromPgString(const QString &str, const std::function &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) + { + + idxStart = -1; + idxEnd = str.length() + 1; + } + + const QStringRef strRef = str.midRef(idxStart + 1, idxEnd - idxStart - 1); + const QVector 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(str, + [](const QStringRef & val, bool * bOk) { return val.toInt(bOk);}); +} + +template<> +LTSUTILSCORE_EXPORT BigintArray BigintArray::fromPgString(const QString &str) +{ + return convertFromPgString(str, + [](const QStringRef & val, bool * bOk) { return val.toLongLong(bOk);}); +} + + + + + + +template +static inline QString convertToPgString(const std::map &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(m_data); +} + +template<> +LTSUTILSCORE_EXPORT QString BigintArray2D::toPgString() const +{ + return convertToPgString(m_data); +} + + +template +static inline T convertFromPgString2D(const QString &str, const std::function &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) + { + + idxStart = -1; + idxEnd = str.length() + 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; + + const QStringRef keyValueStr = strRef.mid(idxStart + 1, idxEnd - idxStart - 1); + const QVector 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)); + + + idxStart = strRef.indexOf(QLatin1Char('{'), idxEnd + 1); + } + + return T(ret); +} + +template<> +LTSUTILSCORE_EXPORT IntArray2D IntArray2D::fromPgString(const QString &str) +{ + return convertFromPgString2D(str, + [](const QStringRef & val) { return val.toInt();}); +} + +template<> +LTSUTILSCORE_EXPORT BigintArray2D BigintArray2D::fromPgString(const QString &str) +{ + return convertFromPgString2D(str, + [](const QStringRef & val) { return val.toLongLong();}); +} + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/pgarray.h b/Lts/Common/LtsUtils/LtsUtilsCore/pgarray.h new file mode 100755 index 000000000..ddafb4f9a --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/pgarray.h @@ -0,0 +1,45 @@ + + + +#pragma once +#include +#include +#include +#include + +namespace LtsUtils +{ + +template +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; +}; + + + +using IntArray = PgArray>; +using BigintArray = PgArray>; + + +using IntArray2D = PgArray>; +using BigintArray2D = PgArray>; + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/promise.cpp b/Lts/Common/LtsUtils/LtsUtilsCore/promise.cpp new file mode 100755 index 000000000..0fc9bfd5c --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/promise.cpp @@ -0,0 +1,103 @@ + + + +#include "promise.h" + +#include + +namespace LtsUtils +{ + +Promise::Promise(const std::function &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 &function) +{ + if (!function || m_functionAlreadySet) + return; + + m_functionAlreadySet = true; + + if (m_timeoutSet) + m_timeout->start(); + + + QTimer::singleShot(0, this, [this, function]() + { + + function([this](const QVariant &value) + { + resolve(value); + }, [this](const QVariant &value) + { + + 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; +} + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/promise.h b/Lts/Common/LtsUtils/LtsUtilsCore/promise.h new file mode 100755 index 000000000..7bb4e2c2c --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/promise.h @@ -0,0 +1,72 @@ +#pragma once + + + + +#include + +#include +#include + +#include + + +class QTimer; + + +namespace LtsUtils +{ +typedef std::function RawResultFunction; +typedef const RawResultFunction &ResultFunction; + + + + +class LTSUTILSCORE_EXPORT Promise : public QObject +{ + Q_OBJECT + +public: + + + Promise(const std::function &function, QObject *parent = nullptr); + + + + void setFunction(const std::function &function); + + + + void resolve(const QVariant &value = QVariant()); + + + + void reject(const QVariant &value = QVariant()); + + + + void setTimeout(int interval); + + + + Promise &resolved(ResultFunction resolveFn); + + + + 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(); +}; + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/promise_examples.cpp b/Lts/Common/LtsUtils/LtsUtilsCore/promise_examples.cpp new file mode 100755 index 000000000..4fa512d34 --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/promise_examples.cpp @@ -0,0 +1,165 @@ +#include "promise_examples.h" +#include "promise.h" + +#include +#include + + +#include + + +namespace LtsUtils +{ + + +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) +{ + + + + auto func = [this](ResultFunction resolve + , ResultFunction reject ) + { + + PromiseExample1_Class *c = new PromiseExample1_Class(this); + + + connect(c, &PromiseExample1_Class::signalDone1, + this, [this, c, resolve, reject](bool didThisWork) + { + + if (!didThisWork) + { + + reject(QStringLiteral("First Action did not succeed")); + return; + } + else + { + + + + connect(c, &PromiseExample1_Class::signalDone2, + this, [resolve, reject](bool didThisWork, int someNumberThatReturns) + { + if (didThisWork) + resolve(someNumberThatReturns); + else + reject(QStringLiteral("Second Action did not succeed")); + + + }); + + c->doSomethingAsync2(); + } + }); + + c->doSomethingAsync1(); + }; + Promise *p = new Promise(func, this); + + + + + + p->resolved([this](const QVariant &value) + { + + qDebug() << "example1 promise succeeded! 'someNumberThatReturns' was" << value; + + m_success = true; + + }).rejected([](const QVariant &value) + { + + qDebug() << "example1 promise failed.." << value; + }); +} + + + + +PromiseExample2::PromiseExample2(QObject *parent) + : QObject(parent) + , m_success(false) +{ + + + + Promise *p = new Promise([](ResultFunction , ResultFunction ) + { + + }, 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; + }); +} + + + + +PromiseExample3::PromiseExample3(QObject *parent) + : QObject(parent) + , m_success(true) + , m_alreadyRejected(false) +{ + + + + Promise *p = new Promise([](ResultFunction , ResultFunction reject) + { + reject(QString()); + }, this); + + p->setTimeout(1000); + + 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"; + }); +} + + + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/promise_examples.h b/Lts/Common/LtsUtils/LtsUtilsCore/promise_examples.h new file mode 100755 index 000000000..9edb31123 --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/promise_examples.h @@ -0,0 +1,70 @@ +#pragma once + + +#include + + +namespace LtsUtils +{ + + +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; +}; + + + + +class PromiseExample2 : public QObject +{ + Q_OBJECT +public: + explicit PromiseExample2(QObject *parent = nullptr); + + bool isSuccess() const { return m_success; } + +private: + bool m_success; +}; + + + + +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; +}; + + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/qbytearray_byteaccess.h b/Lts/Common/LtsUtils/LtsUtilsCore/qbytearray_byteaccess.h new file mode 100755 index 000000000..99d16f47f --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/qbytearray_byteaccess.h @@ -0,0 +1,282 @@ + + + +#pragma once + +#include +#include + +namespace LtsUtils +{ + + + + template + void inline set8Byte(const T in, QByteArray& out) + { + static_assert(std::is_integral::value || std::is_enum::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 + bool inline get8Byte(const QByteArray& baData, + size_t &pos, + T& out) + { + static_assert(std::is_integral::value || std::is_enum::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; + + if(std::numeric_limits::is_signed) + out |= static_cast(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 + bool inline get8Byte(const QByteArray& data, T& out) + { + size_t pos = 0; + return get8Byte(data, pos, out); + } + + + template + void inline set4Byte(const T in, QByteArray& out) + { + static_assert(std::is_integral::value || std::is_enum::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 + bool inline get4Byte(const QByteArray& baData, + size_t &pos, + T& out) + { + static_assert(std::is_integral::value || std::is_enum::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; + + if(std::numeric_limits::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 + bool inline get4Byte(const QByteArray& data, T& out) + { + size_t pos = 0; + return get4Byte(data, pos, out); + } + + + template + void inline set3Byte(const T in, QByteArray& out) + { + static_assert(std::is_integral::value || std::is_enum::value, "Only integrals allowed for raw byte accessd"); + out += (in & 0x00FF0000) >> 16; + out += (in & 0x0000FF00) >> 8; + out += (in & 0x000000FF); + } + + template + bool inline get3Byte(const QByteArray& baData, + size_t &pos, + T& out) + { + static_assert(std::is_integral::value || std::is_enum::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; + + if(std::numeric_limits::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 + bool inline get3Byte(const QByteArray& data, T& out) + { + size_t pos = 0; + return get3Byte(data, pos, out); + } + + + template + void inline set2Byte(const T in, QByteArray& out) + { + static_assert(std::is_integral::value || std::is_enum::value, "Only integrals allowed for raw byte accessd"); + out += (in & 0x0000FF00) >> 8; + out += (in & 0x000000FF); + } + + template + bool inline get2Byte(const QByteArray& baData, + size_t &pos, + T& out) + { + static_assert(std::is_integral::value || std::is_enum::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; + + if(std::numeric_limits::is_signed) + out |= data[0] << 8; + else + out |= (data[0] & 0xFFU) << 8; + out |= data[1] & 0xFFU; + + pos += 2; + + return true; + } + + template + bool inline get2Byte(const QByteArray& data, T& out) + { + size_t pos = 0; + return get2Byte(data, pos, out); + } + + + template + void inline set1Byte(const T in, QByteArray& out) + { + static_assert(std::is_integral::value || std::is_enum::value, "Only integrals allowed for raw byte accessd"); + unsigned char c = in; + out += c; + } + + template + bool inline get1Byte(const QByteArray& baData, + size_t &pos, + T& out) + { + static_assert(std::is_integral::value || std::is_enum::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; + + if(std::numeric_limits::is_signed) + out = (T)(data[0]); + else + out = (T)(data[0] & 0xFF); + + pos += 1; + + return true; + } + + template + bool inline get1Byte(const QByteArray& data, T& out) + { + size_t pos = 0; + return get1Byte(data, pos, out); + } +} diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/qplane3d.cpp b/Lts/Common/LtsUtils/LtsUtilsCore/qplane3d.cpp new file mode 100755 index 000000000..2e2435096 --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/qplane3d.cpp @@ -0,0 +1,88 @@ +#include "qplane3d.h" + +#include + + +namespace LtsUtils +{ + + + + + +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(const QVector &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); + } + return qFuzzyIsNull(M_2_PI - angleSum); +} + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/qplane3d.h b/Lts/Common/LtsUtils/LtsUtilsCore/qplane3d.h new file mode 100755 index 000000000..f9abd932c --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/qplane3d.h @@ -0,0 +1,59 @@ + + + +#pragma once + +#include "qpoint3d.h" + +#include + +#include +#include + + +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 &vertices); + + bool contains(const QPoint3D &point) const { return m_vertices.contains(point); } + bool containsPoint(const QPoint3D &point) const; + +private: + QVector m_vertices; + +}; + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/qpoint3d.cpp b/Lts/Common/LtsUtils/LtsUtilsCore/qpoint3d.cpp new file mode 100755 index 000000000..dc4fc26c6 --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/qpoint3d.cpp @@ -0,0 +1,48 @@ + + + +#include "qpoint3d.h" +#include + +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; +} diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/qpoint3d.h b/Lts/Common/LtsUtils/LtsUtilsCore/qpoint3d.h new file mode 100755 index 000000000..e0398a44f --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/qpoint3d.h @@ -0,0 +1,296 @@ + + + +#pragma once + +#include +#include + +#include + +#include +#include + + +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 + 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; + static QPoint3D fromQPointF(const QPointF &p) + { + 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; +}; + + + +LTSUTILSCORE_EXPORT QDataStream &operator<<(QDataStream &s, const QPoint3D &p); +LTSUTILSCORE_EXPORT QDataStream &operator>>(QDataStream &s, QPoint3D &p); + + + + +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; +} + +} + +Q_DECLARE_TYPEINFO(LtsUtils::QPoint3D, Q_MOVABLE_TYPE); diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/scoped_connection.cpp b/Lts/Common/LtsUtils/LtsUtilsCore/scoped_connection.cpp new file mode 100755 index 000000000..a8d543e3d --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/scoped_connection.cpp @@ -0,0 +1,22 @@ + + + +#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(); +} + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/scoped_connection.h b/Lts/Common/LtsUtils/LtsUtilsCore/scoped_connection.h new file mode 100755 index 000000000..74e6e98af --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/scoped_connection.h @@ -0,0 +1,26 @@ + + + +#pragma once + +#include +#include + +namespace LtsUtils +{ + +class IConnection; + + + +class LTSUTILSCORE_EXPORT ScopedConnection +{ +public: + explicit ScopedConnection(IConnection *connection, Qt::ConnectionType = Qt::AutoConnection); + ~ScopedConnection(); + +private: + IConnection *m_conn; +}; + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/sfm_bytemessage_converter.cpp b/Lts/Common/LtsUtils/LtsUtilsCore/sfm_bytemessage_converter.cpp new file mode 100755 index 000000000..a5876f216 --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/sfm_bytemessage_converter.cpp @@ -0,0 +1,301 @@ + + + +#include "sfm_bytemessage_converter.h" + +namespace LtsUtils +{ + + + +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; +static constexpr int CHECKSUM_SIZE = 1; +static constexpr int MINIMUM_BYTE_SIZE = 1 + HEADER_SIZE + CHECKSUM_SIZE + 1; + + + +static SfmMessage getSfmMessage(const QByteArray &p_data) +{ + + unsigned short id = 0; + id |= (((unsigned short)p_data[0]) & 0x7F) << 8; + id |= (((unsigned short)p_data[1]) & 0xFF) << 0; + + SfmMessage msg; + msg.setId(id); + msg.setRtr(p_data[0]&0x80); + msg.setPayload(p_data.right(p_data.size() - HEADER_SIZE)); + + return msg; +} + + + +static QByteArray getHeader(const SfmMessage &p_msg) +{ + QByteArray header(2, Qt::Uninitialized); + + header[0] = ((p_msg.id() >> 8) & 0x7f) | (p_msg.rtr() << 7); + + header[1] = (p_msg.id() >> 0) & 0xff; + + return header; +} + + + +static inline char getChecksum(const QByteArray &p_data) +{ + char sum = 0; + + + for (const char c : p_data) + sum += c; + + + return sum ^ 0xff; +} + + + +static QByteArray removeChecksum(const QByteArray &p_data) +{ + QByteArray withoutChecksum(p_data); + ConversionError error ("Checksum error"); + + if (p_data.size() >= CHECKSUM_SIZE) + { + withoutChecksum.chop(1); + + + if (p_data.at(p_data.size() - 1) != getChecksum(withoutChecksum)) + throw error; + } + else + throw error; + + return withoutChecksum; +} + + + +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(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; +} + + + + +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; + } + } + else + unmaskData.append(byte); + } + return unmaskData; +} + + + +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; +} + + + +static QByteArray removeFrame(const QByteArray &p_data) +{ + + if (p_data[0] == STX && p_data[p_data.size() - 1] == ETX) + { + + return p_data.mid(1, p_data.size() - 2); + } + else + throw ConversionError("Framing Error"); +} + + + +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]; + if (length != p_data.size() - HEADER_SIZE) + throw ConversionError("Telegram length does not match"); +} + +QByteArray byteStreamFromSfmMessage(const SfmMessage &message) +{ + QByteArray byteData(getHeader(message)); + + byteData.append(message.payload().size()); + byteData.append(message.payload()); + byteData.append(getChecksum(byteData)); + + 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; + + + checkMsg = removeFrame(datastream); + checkMsg = unmaskData(checkMsg); + + checkMsg = removeChecksum(checkMsg); + + checkLength(checkMsg); + + + return getSfmMessage(checkMsg); +} + +QByteArray byteStreamFromEscMessage(const QByteArray &message) +{ + QByteArray byteData = message; + if (message.size() == 1) + { + + byteData.append(message); + } + else + byteData.append(getChecksum(byteData)); + + byteData = maskData(byteData); + byteData = addFrame(byteData); + + return byteData; +} + +QByteArray byteStreamToEscMessage(const QByteArray &datastream) +{ + if (datastream.size() < 2) + return QByteArray("ERROR"); + + QByteArray checkMsg; + + + checkMsg = removeFrame(datastream); + checkMsg = unmaskData(checkMsg); + + + checkMsg.chop(1); + return checkMsg; +} + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/sfm_bytemessage_converter.h b/Lts/Common/LtsUtils/LtsUtilsCore/sfm_bytemessage_converter.h new file mode 100755 index 000000000..94dc41b4d --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/sfm_bytemessage_converter.h @@ -0,0 +1,64 @@ + + + +#pragma once + +#include +#include + +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; +}; + + + +LTSUTILSCORE_EXPORT QByteArray byteStreamFromSfmMessage(const SfmMessage &message); + + + +LTSUTILSCORE_EXPORT SfmMessage byteStreamToSfmMessage(const QByteArray &datastream); + + + +LTSUTILSCORE_EXPORT QByteArray byteStreamFromEscMessage(const QByteArray &message); + + + +LTSUTILSCORE_EXPORT QByteArray byteStreamToEscMessage(const QByteArray &datastream); + + + +LTSUTILSCORE_EXPORT int lenMaskedData(const QByteArray &data); + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsCore/variant_operators.h b/Lts/Common/LtsUtils/LtsUtilsCore/variant_operators.h new file mode 100755 index 000000000..0be695230 --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsCore/variant_operators.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +#include + + +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; + } + else if (var1.canConvert(QMetaType::Double) && var2.canConvert(QMetaType::Double)) + return var1.toDouble() < var2.toDouble(); + + return false; +} + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsGui/CMakeLists.txt b/Lts/Common/LtsUtils/LtsUtilsGui/CMakeLists.txt new file mode 100755 index 000000000..f4f8be558 --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsGui/CMakeLists.txt @@ -0,0 +1,11 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# +# Created by Mike Wille, SLSE, 2016-09-19 +# + +ADD_SUBDIRECTORY(LtsUtilsGuiDds) +ADD_SUBDIRECTORY(ConnectDialog) +ADD_SUBDIRECTORY(StyleManager) diff --git a/Lts/Common/LtsUtils/LtsUtilsGui/ConnectDialog/CMakeLists.txt b/Lts/Common/LtsUtils/LtsUtilsGui/ConnectDialog/CMakeLists.txt new file mode 100755 index 000000000..543855271 --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsGui/ConnectDialog/CMakeLists.txt @@ -0,0 +1,28 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# Created by Mike Wille, SLSE, 2016-05-12 +# + +SET(srcs + connect_dialog.h + connect_dialog.cpp + connect_dialog_nt.h + connect_dialog_nt.cpp +) + +NGL_WRAP_UI(srcs + connect_dialog.ui +) + +NGL_ADD_LIBRARY2(WS LtsUtilsGuiConnectDialog SHARED ${srcs}) +NGL_TARGET_INCLUDE_DIRECTORIES(LtsUtilsGuiConnectDialog + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ + ${CMAKE_CURRENT_BINARY_DIR}/../ +) +NGL_TARGET_LINK_LIBRARIES(LtsUtilsGuiConnectDialog + Qt5::Widgets + LtsWidgets +) diff --git a/Lts/Common/LtsUtils/LtsUtilsGui/ConnectDialog/connect_dialog.cpp b/Lts/Common/LtsUtils/LtsUtilsGui/ConnectDialog/connect_dialog.cpp new file mode 100755 index 000000000..f16856c1d --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsGui/ConnectDialog/connect_dialog.cpp @@ -0,0 +1,73 @@ + + + +#include "connect_dialog.h" +#include "ui_connect_dialog.h" + +#include + + +namespace LtsUtils +{ +int ConnectingDialog::s_waitForHistoricalDataWaitTime = 10000; + + +void ConnectingDialog::reset() +{ + m_ui->label->setText(QStringLiteral("%1").arg(tr("Connecting to %1 on %2 ...").arg(m_serviceName, m_exerciseName))); +} + + +void ConnectingDialog::setup() +{ + m_ui = new Ui::ConnectingDialog; + m_ui->setupUi(this); + reset(); + setMinimumSize(sizeHint()); + connect(m_ui->pbRetry, &QPushButton::clicked, + this, [this]() + { + m_ui->pbRetry->setEnabled(false); + m_ui->pbIgnore->setEnabled(false); + + reset(); + emit signalStopLoop(); + }); + + connect(m_ui->pbIgnore, &QPushButton::clicked, + this, [this]() + { + m_ui->pbIgnore->setEnabled(false); + m_ui->pbRetry->setEnabled(false); + m_ui->pbIgnore->setText(tr("continuing..")); + + deleteLater(); + return; + + + + + + + + }); +} + + +ConnectingDialog::~ConnectingDialog() +{ + delete m_ui; +} + + +void ConnectingDialog::failed() +{ + m_ui->label->setText(QStringLiteral("%1").arg(tr("Could not connect to %1 on %2 !").arg(m_serviceName, m_exerciseName))); + + m_ui->pbRetry->setEnabled(true); + m_ui->pbIgnore->setEnabled(true); + + raise(); +} + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsGui/ConnectDialog/connect_dialog.h b/Lts/Common/LtsUtils/LtsUtilsGui/ConnectDialog/connect_dialog.h new file mode 100755 index 000000000..eb6b3216b --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsGui/ConnectDialog/connect_dialog.h @@ -0,0 +1,82 @@ + + + +#pragma once + +#include +#include + +#include +#include + +#include + + +namespace Ui +{ +class ConnectingDialog; +} + +namespace LtsUtils +{ + +class LTSUTILSGUICONNECTDIALOG_EXPORT ConnectingDialog : public LtsWidgets::LtsFramelessDialog +{ + Q_OBJECT + +public: + static int s_waitForHistoricalDataWaitTime; + + template + explicit ConnectingDialog(DtcType dtc, + WaitForHistoricalDataFunction waitForHistoricalDataFunction, + const QString &serviceName, + const QString &exerciseName, + QWidget *parent = nullptr) + : LtsWidgets::LtsFramelessDialog(parent, LtsWidgets::Behaviour::Modal) + , m_serviceName(serviceName) + , m_exerciseName(exerciseName) + , m_ui(nullptr) + { + setWindowTitle(tr("Connecting ..")); + setup(); + show(); + + while (true) + { + if ((dtc.get()->*waitForHistoricalDataFunction)(s_waitForHistoricalDataWaitTime)) + { + deleteLater(); + return; + } + + if (s_waitForHistoricalDataWaitTime >= 999999) + { + deleteLater(); + return; + } + else + { + failed(); + } + } + } + + ~ConnectingDialog(); + + void failed(); + +private: + QString m_serviceName; + QString m_exerciseName; + Ui::ConnectingDialog *m_ui; + + void reset(); + void setup(); + +signals: + void signalStopLoop(); + +}; + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsGui/ConnectDialog/connect_dialog.ui b/Lts/Common/LtsUtils/LtsUtilsGui/ConnectDialog/connect_dialog.ui new file mode 100755 index 000000000..082b3cb6e --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsGui/ConnectDialog/connect_dialog.ui @@ -0,0 +1,76 @@ + + + ConnectingDialog + + + + 0 + 0 + 260 + 200 + + + + + 0 + 0 + + + + + 260 + 200 + + + + Login + + + + + + + 0 + 0 + + + + + + + Qt::AlignCenter + + + true + + + + + + + + + false + + + Retry + + + + + + + false + + + Ignore + + + + + + + + + + diff --git a/Lts/Common/LtsUtils/LtsUtilsGui/ConnectDialog/connect_dialog_nt.cpp b/Lts/Common/LtsUtils/LtsUtilsGui/ConnectDialog/connect_dialog_nt.cpp new file mode 100755 index 000000000..3d35ee492 --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsGui/ConnectDialog/connect_dialog_nt.cpp @@ -0,0 +1,157 @@ + + + +#include "connect_dialog.h" +#include "connect_dialog_nt.h" +#include "ui_connect_dialog.h" + +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) +#include +#endif + + +namespace LtsUtils +{ + + +ConnectingDialogNT::ConnectingDialogNT(const std::function &checkFn, + const QString &serviceName, + const QString &exerciseName, + QWidget *parent) + : LtsWidgets::LtsFramelessDialog(parent, LtsWidgets::Behaviour::Modal) + , m_serviceName(serviceName) + , m_exerciseName(exerciseName) + , m_checkFn(checkFn) + , m_go(false) + , m_autoRetryTimer(new QTimer(this)) + , m_ui(new Ui::ConnectingDialog ) +{ + m_ui->setupUi(this); + + m_autoRetryTimer->setInterval(1000); + connect(m_autoRetryTimer, &QTimer::timeout, + this, [this]() + { + if (m_autoRetryTick < 1) + { + slotRetry(); + return; + } + + m_ui->pbRetry->setText(QStringLiteral("%1 [%2]").arg(tr("Retry")).arg(m_autoRetryTick)); + m_autoRetryTick--; + }); + + reset(); + setMinimumSize(sizeHint()); + connect(m_ui->pbRetry, &QPushButton::clicked, + this, &ConnectingDialogNT::slotRetry); + + connect(m_ui->pbIgnore, &QPushButton::clicked, + this, [this]() + { + m_ui->pbIgnore->setEnabled(false); + m_ui->pbRetry->setEnabled(false); + m_ui->pbIgnore->setText(tr("continuing..")); + + emit signalFailed(); + deleteLater(); + return; + + + + + + + + }); + + show(); + + connect(this, &ConnectingDialogNT::signalGo, + this, &ConnectingDialogNT::slotGo, Qt::QueuedConnection); + + m_go = true; + m_ui->pbRetry->setEnabled(false); +} + + +int ConnectingDialogNT::random(int min, int max) +{ +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + return QRandomGenerator::global()->bounded(min, max); +#else + return (qrand() % (((max + 1) - min) + min)); +#endif +} + + + +bool ConnectingDialogNT::event(QEvent *event) +{ + bool result = LtsWidgets::LtsFramelessDialog::event(event); + + if (event->type() == QEvent::Paint && m_go) + { + m_go = false; + QTimer::singleShot(0, this, &ConnectingDialogNT::signalGo); + } + + return result; +} + + +void ConnectingDialogNT::slotRetry() +{ + m_autoRetryTimer->stop(); + + m_ui->pbRetry->setText(tr("Retry")); + m_ui->pbRetry->setEnabled(false); + m_ui->pbIgnore->setEnabled(false); + + reset(); + QTimer::singleShot(1, this, &ConnectingDialogNT::slotGo); +} + + +void ConnectingDialogNT::slotGo() +{ + + if (m_checkFn(ConnectingDialog::s_waitForHistoricalDataWaitTime)) + { + emit signalSuccess(); + deleteLater(); + return; + } + + + failed(); +} + + +void ConnectingDialogNT::reset() +{ + m_ui->label->setText(QStringLiteral("%1").arg(tr("Connecting to %1 on %2 ...").arg(m_serviceName, m_exerciseName))); +} + + +ConnectingDialogNT::~ConnectingDialogNT() +{ + delete m_ui; +} + + +void ConnectingDialogNT::failed() +{ + m_ui->label->setText(QStringLiteral("%1").arg(tr("Could not connect to %1 on %2 !").arg(m_serviceName, m_exerciseName))); + + m_ui->pbRetry->setEnabled(true); + m_ui->pbIgnore->setEnabled(true); + + m_autoRetryTick = random(5, 20); + m_autoRetryTimer->start(); + + raise(); +} + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsGui/ConnectDialog/connect_dialog_nt.h b/Lts/Common/LtsUtils/LtsUtilsGui/ConnectDialog/connect_dialog_nt.h new file mode 100755 index 000000000..00d20ef0f --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsGui/ConnectDialog/connect_dialog_nt.h @@ -0,0 +1,63 @@ + + + +#pragma once + +#include +#include + +#include + + +class QTimer; + +namespace Ui +{ +class ConnectingDialog; +} + +namespace LtsUtils +{ + +class LTSUTILSGUICONNECTDIALOG_EXPORT ConnectingDialogNT : public LtsWidgets::LtsFramelessDialog +{ + Q_OBJECT + +public: + + + explicit ConnectingDialogNT(const std::function &checkFn, + const QString &serviceName, + const QString &exerciseName, + QWidget *parent); + ~ConnectingDialogNT(); + + + void failed(); + +private slots: + void slotGo(); + void slotRetry(); + +signals: + void signalGo(); + void signalFailed(); + void signalSuccess(); + +private: + QString m_serviceName; + QString m_exerciseName; + std::function m_checkFn; + bool m_go; + + QTimer *m_autoRetryTimer; + int m_autoRetryTick { 0 }; + + Ui::ConnectingDialog *m_ui; + + int random(int min, int max); + bool event(QEvent *event) override; + void reset(); +}; + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsGui/LtsUtilsGuiDds/CMakeLists.txt b/Lts/Common/LtsUtils/LtsUtilsGui/LtsUtilsGuiDds/CMakeLists.txt new file mode 100755 index 000000000..2e647035b --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsGui/LtsUtilsGuiDds/CMakeLists.txt @@ -0,0 +1,49 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# +# Created by Mike Wille, SLSE, 2016-09-19 +# + +SET(SRCS + dds_vector_to_pixmap.cpp + dds_vector_to_pixmap.h + request_helper.cpp + request_helper.h +) + +NGL_ADD_LIBRARY2(WS LtsUtilsGuiDds SHARED ${SRCS}) +NGL_TARGET_INCLUDE_DIRECTORIES(LtsUtilsGuiDds + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) +NGL_TARGET_LINK_LIBRARIES(LtsUtilsGuiDds + PRIVATE + LtsUtilsCoreHeaderOnly + LtsWidgets + Qt5::Gui + Qt5::Widgets + Qt5::Svg + PUBLIC + Qt5::Core + ddscore +) +NGL_TARGET_COMPILE_DEFINITIONS(LtsUtilsGuiDds + PUBLIC + REQUEST_HELPER_WITH_GUI +) + + +NGL_ADD_LIBRARY2(AAR HLA SV LtsUtilsDds SHARED request_helper.cpp request_helper.h) +NGL_TARGET_INCLUDE_DIRECTORIES(LtsUtilsDds + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) +NGL_TARGET_LINK_LIBRARIES(LtsUtilsDds + PRIVATE + LtsUtilsCoreHeaderOnly + PUBLIC + Qt5::Core + ddscore +) diff --git a/Lts/Common/LtsUtils/LtsUtilsGui/LtsUtilsGuiDds/dds_vector_to_pixmap.cpp b/Lts/Common/LtsUtils/LtsUtilsGui/LtsUtilsGuiDds/dds_vector_to_pixmap.cpp new file mode 100755 index 000000000..c117b395b --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsGui/LtsUtilsGuiDds/dds_vector_to_pixmap.cpp @@ -0,0 +1,134 @@ + + + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +namespace LtsUtils +{ + +QIcon iconFromSvg(const QByteArray &svgData, QSize iconSize) +{ + QSvgRenderer renderer(svgData); + QPixmap p(iconSize); + p.fill(Qt::transparent); + QPainter painter(&p); + renderer.render(&painter); + painter.end(); + + return {p}; +} + +QIcon iconFromSvg(const QString &svgFilename, QSize iconSize) +{ + QFile f(svgFilename); + if (!f.open(QIODevice::ReadOnly)) + return {}; + return iconFromSvg(f.readAll(), iconSize); +} + +QPixmap pixmapFromIcon(const QIcon &icon, QSize iconSize) +{ + return icon.pixmap(iconSize).scaled(100, 100, Qt::AspectRatioMode::IgnoreAspectRatio); +} + +QPixmap pixmapFromSvg(const QString &svgFilename, QSize iconSize) +{ + return pixmapFromIcon(iconFromSvg(svgFilename, iconSize), iconSize); +} + +QPixmap getPixmap(const std::vector &data) +{ + if (data.size() == 0) + return QPixmap(); + + return getPixmap(QByteArray(reinterpret_cast(&data.at(0)), data.size())); +} + +QPixmap getPixmap(const QByteArray &data) +{ + return QPixmap::fromImage(QImage::fromData(data)); +} + +QImage getImage(const std::vector &data) +{ + if (data.size() == 0) + return QImage(); + + return getImage(QByteArray(reinterpret_cast(&data.at(0)), data.size())); +} + +QImage getImage(const QByteArray &data) +{ + return QImage::fromData(data); +} + +static inline void setIf(bool *boolPtr, bool val) +{ + if (boolPtr) + *boolPtr = val; +} + +std::vector fromPixmap(const QPixmap &pixmap, bool *bIsPng) +{ + QBuffer buf; + buf.open(QIODevice::WriteOnly); + pixmap.save(&buf, "PNG"); + buf.close(); + QByteArray ba = buf.buffer(); + + setIf(bIsPng, true); + + if (ba.size() > 10240 && !pixmap.hasAlpha() && !pixmap.hasAlphaChannel()) + { + QBuffer buf2; + buf2.open(QIODevice::WriteOnly); + pixmap.save(&buf2, "JPG"); + buf2.close(); + if (buf2.buffer().size() < buf.buffer().size()) + { + setIf(bIsPng, false); + ba = buf2.buffer(); + } + } + + return convert>(ba); +} + +std::vector fromImage(const QImage &image, bool *bIsPng) +{ + QBuffer buf; + buf.open(QIODevice::WriteOnly); + image.save(&buf, "PNG"); + buf.close(); + QByteArray ba = buf.buffer(); + + + setIf(bIsPng, true); + if ((ba.size() > 10240 && !image.hasAlphaChannel()) || + ba.size() > 1024 * 1024) + { + QBuffer buf2; + buf2.open(QIODevice::WriteOnly); + image.save(&buf2, "JPG"); + buf2.close(); + if (buf2.buffer().size() < buf.buffer().size()) + { + setIf(bIsPng, false); + ba = buf2.buffer(); + } + } + + return convert>(ba); +} + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsGui/LtsUtilsGuiDds/dds_vector_to_pixmap.h b/Lts/Common/LtsUtils/LtsUtilsGui/LtsUtilsGuiDds/dds_vector_to_pixmap.h new file mode 100755 index 000000000..80cccc1d5 --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsGui/LtsUtilsGuiDds/dds_vector_to_pixmap.h @@ -0,0 +1,33 @@ + + + +#pragma once + +#include + +#include +#include + +#include +#include + +class QByteArray; +class QPixmap; +class QImage; +class QIcon; + +namespace LtsUtils +{ + +LTSUTILSCORE_EXPORT QPixmap getPixmap(const std::vector &data); +LTSUTILSCORE_EXPORT QPixmap getPixmap(const QByteArray &data); +LTSUTILSCORE_EXPORT QImage getImage(const std::vector &data); +LTSUTILSCORE_EXPORT QImage getImage(const QByteArray &data); +LTSUTILSCORE_EXPORT QIcon iconFromSvg(const QByteArray &svgData, QSize iconSize = QSize(100, 100)); +LTSUTILSCORE_EXPORT QIcon iconFromSvg(const QString &svgFilename, QSize iconSize = QSize(100, 100)); +LTSUTILSCORE_EXPORT QPixmap pixmapFromIcon(const QIcon &icon, QSize iconSize = QSize(100, 100)); +LTSUTILSCORE_EXPORT QPixmap pixmapFromSvg(const QString &svgFilename, QSize iconSize = QSize(100, 100)); +LTSUTILSCORE_EXPORT std::vector fromPixmap(const QPixmap &pixmap, bool *bIsPng = nullptr); +LTSUTILSCORE_EXPORT std::vector fromImage(const QImage &image, bool *bIsPng = nullptr); + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsGui/LtsUtilsGuiDds/request_helper.cpp b/Lts/Common/LtsUtils/LtsUtilsGui/LtsUtilsGuiDds/request_helper.cpp new file mode 100755 index 000000000..ec92f01df --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsGui/LtsUtilsGuiDds/request_helper.cpp @@ -0,0 +1,276 @@ + + + +#include "request_helper.h" + +#ifdef REQUEST_HELPER_WITH_GUI +#include +#include +#include +#endif + + +#include +#include +#include +#include +#include + +#define DEBUG + + +namespace LtsUtils +{ +bool RequestHelper::s_debug = false; + +RequestHelper::RequestHelper(QObject *parent, bool quiet, + + const std::function &success, + const std::function &failure) + : QObject(parent) + , m_failed(false) + , m_repliedId(-1) + , m_quiet(quiet) + , m_blockingCall(true) + , m_successFn(success) + , m_failureFn(failure) +{ + connect(this, &RequestHelper::signalSuccess, this, m_successFn, Qt::QueuedConnection); + connect(this, &RequestHelper::signalFailed, this, m_failureFn, Qt::QueuedConnection); + + if (s_debug) + { + QTimer *t = new QTimer(this); + t->setInterval(2000); + t->setSingleShot(true); + connect(this, &RequestHelper::signalNextRequest, t, static_cast(&QTimer::start)); + connect(t, &QTimer::timeout, this, &RequestHelper::slotNextRequest, Qt::QueuedConnection); + } + else + { + connect(this, &RequestHelper::signalNextRequest, + this, &RequestHelper::slotNextRequest, + Qt::QueuedConnection + +); + } +} + + +void RequestHelper::addRequest(const std::function &requestFunction, + const std::function &baseReplyHandler) +{ + m_requestFunctions.append(RequestFunctionContainer(requestFunction, baseReplyHandler)); +} + +bool RequestHelper::requestReply(const Core::FunctionReply &baseReply) +{ + const Core::FunctionReplyObject fro(baseReply.id(), + baseReply.errorString(), + baseReply.errorParams()); + return _requestReply(fro, &baseReply); +} + +bool RequestHelper::requestReply(const Core::FunctionReplyObject &replyObject) +{ + return _requestReply(replyObject, nullptr); +} + + +bool RequestHelper::_requestReply(const Core::FunctionReplyObject &fro, const Core::FunctionReply *baseReply) +{ + m_errorString = fro.translated(); + +#ifdef REQUEST_HELPER_WITH_GUI + if (fro.error()) + { + + if (!m_quiet) + { + if (m_blockingCall) + { + LtsWidgets::LtsMessageDialog::showMessage(qobject_cast(parent()), + m_errorString, + tr("Request failed")); + } + else + { + new LtsWidgets::LtsMessageDialogNew(qobject_cast(parent()), + m_errorString, + tr("Request failed")); + } + } + } +#endif + + m_repliedId = fro.id(); + + if (m_singleRequestCustomFunctionReply != nullptr) + { + if (baseReply) + m_singleRequestCustomFunctionReply(*baseReply); + else + m_singleRequestCustomFunctionReply(Core::FunctionReply(fro.id(), fro.str(), fro.args(), fro.additionalInfo())); + } + + if (fro.error()) + { + failure(); + return false; + } + + emit signalNextRequest(); + return true; +} + + +bool RequestHelper::startSingleRequest(const std::function &requestFunction, + const std::function &baseReplyHandler) +{ + addRequest(requestFunction, baseReplyHandler); + return startRequests(); +} + + +void RequestHelper::failure() +{ + m_failed = true; + emit signalFailed(); + done(); +} + + +void RequestHelper::done() +{ +#ifdef REQUEST_HELPER_WITH_GUI + if (!m_quiet) + LtsWidgets::LtsGlobalWaitDialog::getInstance()->done(); +#endif + + m_requestFunctions.clear(); + m_singleRequestCustomFunctionReply = nullptr; + + if (m_blockingCall) + emit signalQuitLoop(); + + m_blockingCall = true; + + if (!m_blockingCall) + deleteLater(); +} + + +void RequestHelper::slotNextRequest() +{ + if (m_requestFunctions.isEmpty()) + { + if (!m_blockingCall) + emit signalSuccess(); + + done(); + + return; + } + + const RequestFunctionContainer rfc = m_requestFunctions.takeFirst(); + m_singleRequestCustomFunctionReply = rfc.second; + + int64_t result = rfc.first(); + + if (result == 0) + { +#ifdef REQUEST_HELPER_WITH_GUI + if (!m_quiet) + { + if (m_blockingCall) + { + LtsWidgets::LtsMessageDialog::showMessage(qobject_cast(parent()), + tr("Request failed")); + } + else + { + connect(new LtsWidgets::LtsMessageDialogNew(qobject_cast(parent()), + tr("Request failed")), &LtsWidgets::LtsMessageDialogNew::destroyed, + this, [this]() + { + failure(); + }); + + return; + } + } +#endif + failure(); + } +} + + +void RequestHelper::startSingleBackgroundRequest(const std::function &requestFunction, + const std::function &baseReplyHandler) +{ + m_blockingCall = false; + startSingleRequest(requestFunction, baseReplyHandler); +} + + +void RequestHelper::startBackgroundRequests() +{ + m_blockingCall = false; + startRequests(); +} + + +void RequestHelper::setCustomWaitMessage(const QString &customWaitMessage) +{ + m_customWaitMessage = (customWaitMessage + QStringLiteral(" ...")); +} + + +bool RequestHelper::startRequests() +{ + if (m_requestFunctions.isEmpty()) + { +#ifdef LTS_DEBUG + qWarning("RequestHelper: started request helper without requests!"); +#endif + return true; + } + + m_failed = false; + +#ifdef REQUEST_HELPER_WITH_GUI + if (!m_quiet) + LtsWidgets::LtsGlobalWaitDialog::getInstance()->wait(m_customWaitMessage); + m_customWaitMessage.clear(); +#endif + + m_errorString.clear(); + m_repliedId = -1; + + if (s_debug) + { + QTimer *t = new QTimer(this); + t->setInterval(2000); + connect(t, &QTimer::timeout, this, &RequestHelper::slotNextRequest, Qt::QueuedConnection); + connect(t, &QTimer::timeout, t, &QTimer::deleteLater); + t->start(); + } + else + { + slotNextRequest(); + + if (m_blockingCall) + { + QEventLoop loop; + connect(this, &RequestHelper::signalQuitLoop, + &loop, &QEventLoop::quit, + Qt::QueuedConnection); + loop.exec(); + } + } + + + return (!m_failed); +} + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsGui/LtsUtilsGuiDds/request_helper.h b/Lts/Common/LtsUtils/LtsUtilsGui/LtsUtilsGuiDds/request_helper.h new file mode 100755 index 000000000..9557aeb1b --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsGui/LtsUtilsGuiDds/request_helper.h @@ -0,0 +1,123 @@ + + + +#pragma once + +#ifdef REQUEST_HELPER_WITH_GUI +#include +#define LTSUTILSGUIDDS_INTERNAL_EXPORT LTSUTILSGUIDDS_EXPORT +#else +#include +#define LTSUTILSGUIDDS_INTERNAL_EXPORT LTSUTILSDDS_EXPORT +#endif + +#include +#include + +#include +#include +#include +#include + + +namespace LtsUtils +{ + +template +dds::core::optional toOptional(T inVal) +{ + return inVal != T() ? dds::core::optional(inVal) : dds::core::optional(); +} + +template +dds::core::optional optional() +{ + return dds::core::optional(); +} + + +class LTSUTILSGUIDDS_INTERNAL_EXPORT RequestHelper : public QObject +{ + Q_OBJECT + +public: + static bool s_debug; + + explicit RequestHelper(QObject *parent = nullptr, bool quiet = false, + + const std::function &success = [](){}, + const std::function &failure = [](){}); + ~RequestHelper() = default; + + bool quiet() const { return m_quiet; } + void setQuiet(bool quiet) { m_quiet = quiet; } + void addRequest(const std::function &requestFunction, + const std::function &baseReplyHandler = nullptr); + bool startRequests(); + + + bool startSingleRequest(const std::function &requestFunction, + const std::function &baseReplyHandler = nullptr + ); + + + + + void startSingleBackgroundRequest(const std::function &requestFunction, + const std::function &baseReplyHandler = nullptr); + + + void startBackgroundRequests(); + + bool requestReply(const Core::FunctionReply &baseReply); + bool requestReply(const Core::FunctionReplyObject &replyObject); + + + + bool hasRequests() const { return !m_requestFunctions.isEmpty(); } + + + + + int getRepliedId() const { return m_repliedId; } + + + + QString getErrorString() const { return m_errorString; } + + + + void setCustomWaitMessage(const QString &customWaitMessage); + + bool failed() const { return m_failed; } + void failure(); + +private: + using RequestFunctionContainer = QPair, std::function>; + + QVector m_requestFunctions; + bool m_failed; + int m_repliedId; + std::function m_singleRequestCustomFunctionReply; + bool m_quiet; + bool m_blockingCall; + std::function m_successFn; + std::function m_failureFn; + QString m_errorString; + QString m_customWaitMessage; + + void done(); + bool _requestReply(const Core::FunctionReplyObject &fro, const Core::FunctionReply *baseReply); + +signals: + void signalNextRequest(); + void signalQuitLoop(); + void signalSuccess(); + void signalFailed(); + +private slots: + void slotNextRequest(); + +}; + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsGui/StyleManager/CMakeLists.txt b/Lts/Common/LtsUtils/LtsUtilsGui/StyleManager/CMakeLists.txt new file mode 100755 index 000000000..ba7a53540 --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsGui/StyleManager/CMakeLists.txt @@ -0,0 +1,22 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# Created by Mike Wille, NLSE, 2018-02-15 +# + +SET(srcs + style_manager.h + style_manager.cpp +) + +NGL_ADD_LIBRARY2(WS LtsUtilsGuiStyleManager SHARED ${srcs}) +NGL_TARGET_INCLUDE_DIRECTORIES(LtsUtilsGuiStyleManager + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ + ${CMAKE_CURRENT_BINARY_DIR}/../ +) +NGL_TARGET_LINK_LIBRARIES(LtsUtilsGuiStyleManager + Qt5::Core + LtsPluginLoader +) diff --git a/Lts/Common/LtsUtils/LtsUtilsGui/StyleManager/style_manager.cpp b/Lts/Common/LtsUtils/LtsUtilsGui/StyleManager/style_manager.cpp new file mode 100755 index 000000000..99308ea52 --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsGui/StyleManager/style_manager.cpp @@ -0,0 +1,114 @@ + + + +#include "style_manager.h" + +#include +#include + +#include +#include + + +namespace LtsUtils +{ + +struct StyleManagerHandler +{ + StyleManager styleManager; +}; +Q_GLOBAL_STATIC(StyleManagerHandler, s_styleManagerHandler) + + +StyleManager *StyleManager::getInstance() +{ + return &s_styleManagerHandler->styleManager; +} + +bool StyleManager::loadStyle(const QString &stylePluginName, bool highDpi) +{ + if (m_currentStylePluginLoader != nullptr) + { + + + QObject *pluginInstance = m_currentStylePluginLoader->instance(); + StylePlugin::Plugin *sp = qobject_cast(pluginInstance); + + if (sp == nullptr) + { + qWarning() << "StyleManager::loadStyle: could not unload style plugin - wrong type (" + << pluginInstance->metaObject()->className() << ") - new style not loaded"; + return false; + } + + sp->unloadStyle(); + + + + + + + + + + m_currentStylePluginLoader = nullptr; + } + + + + LtsPluginManager::LtsPluginLoader *pluginLoader = LtsPluginManager::LtsPluginLoader::getInstance(); + + QPluginLoader *stylePluginLoader = nullptr; + int pluginsLoaded = 0; + + std::function loadPluginFn = [&stylePluginLoader, &pluginsLoaded] + (const LtsPluginManager::PluginInstance &pi) + { + stylePluginLoader = pi.pluginLoader; + pluginsLoaded++; + return true; + }; + + if (!pluginLoader->loadPlugins(LtsPluginManager::PluginType::STYLE, loadPluginFn, QStringList() << stylePluginName)) + { + qWarning() << "StyleManager::loadStyle: Error loading Style Plugin - using default Qt style"; + return false; + } + + if (pluginsLoaded == 0) + { + qWarning() << "StyleManager::loadStyle: Could not find Style Plugin" << stylePluginName << "- using default Qt style"; + return false; + } + + if (pluginsLoaded > 1) + { + qWarning() << "StyleManager::loadStyle: Style Plugin" << stylePluginName << " is ambiguous - using default Qt style"; + return false; + } + + if (stylePluginLoader == nullptr) + { + qWarning() << "StyleManager::loadStyle: No Style Plugin loader available - using default Qt style"; + return false; + } + + StylePlugin::Plugin *sp = qobject_cast(stylePluginLoader->instance()); + + if (sp == nullptr) + { + qWarning() << "StyleManager::loadStyle: could not load style plugin - wrong type (" + << stylePluginLoader->instance()->metaObject()->className() << ") - new style not loaded"; + return false; + } + + sp->loadStyle(highDpi); + + m_currentStylePluginLoader = stylePluginLoader; + m_stylePluginName = stylePluginName; + emit signalStyleChanged(stylePluginName); + + return true; +} + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsGui/StyleManager/style_manager.h b/Lts/Common/LtsUtils/LtsUtilsGui/StyleManager/style_manager.h new file mode 100755 index 000000000..6b6233f7d --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsGui/StyleManager/style_manager.h @@ -0,0 +1,36 @@ + + + +#pragma once + +#include +#include + + +class QPluginLoader; + + +namespace LtsUtils +{ +class LTSUTILSGUISTYLEMANAGER_EXPORT StyleManager : public QObject +{ + Q_OBJECT + using QObject::QObject; + ~StyleManager() = default; +public: + static StyleManager *getInstance(); + + bool loadStyle(const QString &stylePluginName, bool highDpi = false); + const QString ¤tStyleName() const { return m_stylePluginName; } + +private: + friend struct StyleManagerHandler; + + QPluginLoader *m_currentStylePluginLoader = nullptr; + QString m_stylePluginName; + +signals: + void signalStyleChanged(const QString &stylePluginName); + +}; +} diff --git a/Lts/Common/LtsUtils/LtsUtilsSettings/CMakeLists.txt b/Lts/Common/LtsUtils/LtsUtilsSettings/CMakeLists.txt new file mode 100755 index 000000000..51eb46a04 --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsSettings/CMakeLists.txt @@ -0,0 +1,21 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +SET(SRCS + settings.h + settings.cpp +) + +NGL_ADD_LIBRARY2(ALL LtsUtilsSettings SHARED ${SRCS}) +NGL_TARGET_INCLUDE_DIRECTORIES(LtsUtilsSettings + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) + +NGL_TARGET_LINK_LIBRARIES(LtsUtilsSettings + PUBLIC + Qt5::Core +) diff --git a/Lts/Common/LtsUtils/LtsUtilsSettings/settings.cpp b/Lts/Common/LtsUtils/LtsUtilsSettings/settings.cpp new file mode 100755 index 000000000..daf785816 --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsSettings/settings.cpp @@ -0,0 +1,264 @@ + + +#include "settings.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +namespace LtsUtils +{ + +enum class SettingType +{ + String = 1, + StringList = 2, + Json = 3, + FunctionKeys = 4, + Boolean = 5, +}; + +QString typeToString(SettingType t) +{ + switch (t) + { + case SettingType::String: + return QStringLiteral("String"); + case SettingType::StringList: + return QStringLiteral("StringList"); + case SettingType::Json: + return QStringLiteral("Json"); + case SettingType::FunctionKeys: + return QStringLiteral("FunctionKeys"); + case SettingType::Boolean: + return QStringLiteral("Boolean"); + } + Q_ASSERT(false); + return QStringLiteral("Unknown SettingType (%1)").arg(int(t)); +} + +SettingType stringToType(const QString &s) +{ + if (s == typeToString(SettingType::String)) + return SettingType::String; + if (s == typeToString(SettingType::StringList)) + return SettingType::StringList; + if (s == typeToString(SettingType::Json)) + return SettingType::Json; + if (s == typeToString(SettingType::FunctionKeys)) + return SettingType::FunctionKeys; + if (s == typeToString(SettingType::Boolean)) + return SettingType::Boolean; + Q_ASSERT(false); + return SettingType::String; +} + + +static QString craftKey(const QString &group, const QString &identifier) +{ + return group + QLatin1Char('.') + identifier; +} + + + + +static OperatorSettings *s_operatorSettings = nullptr; +static void cleanupOperatorSettings() +{ + delete s_operatorSettings; + s_operatorSettings = nullptr; +} + +OperatorSettings *OperatorSettings::getInstance() +{ + if (!s_operatorSettings) + { + s_operatorSettings = new OperatorSettings; + qAddPostRoutine(&cleanupOperatorSettings); + } + return s_operatorSettings; +} + + + + + +static ProjectSettings *s_projectSettings = nullptr; +static void cleanupProjectSettings() +{ + delete s_projectSettings; + s_projectSettings = nullptr; +} + +ProjectSettings *ProjectSettings::getInstance() +{ + if (!s_projectSettings) + { + s_projectSettings = new ProjectSettings; + qAddPostRoutine(&cleanupProjectSettings); + } + return s_projectSettings; +} + +bool ProjectSettings::initFromFile(const QString &filename) +{ + QFile f(filename); + if (!f.open(QIODevice::ReadOnly)) + return false; + + QJsonParseError jErr; + const QJsonObject settings = QJsonDocument::fromJson(f.readAll(), &jErr).object(); + if (jErr.error != QJsonParseError::NoError) + return false; + + const QJsonObject psObj = settings[QStringLiteral("ProjectSettings")].toObject(); + if (psObj.isEmpty()) + return false; + + QHash newData; + const QStringList &groupKeys(psObj.keys()); + for (const auto &gKey : groupKeys) + { + const QJsonObject objGroup(psObj[gKey].toObject()); + const QStringList &itemKeys(objGroup.keys()); + for (const auto &key : itemKeys) + { + const QJsonObject obj(objGroup[key].toObject()); + const auto strType(obj[QStringLiteral("Type")].toString()); + const auto type = stringToType(strType); + const QJsonValue jVal(obj[QStringLiteral("Default")]); + QVariant val; + switch (type) + { + case SettingType::Json: + val = QJsonDocument(jVal.toObject()).toJson(); + break; + case SettingType::StringList: + { + const QJsonArray arr(jVal.toArray()); + QStringList sl; + sl.reserve(arr.size()); + for (const auto &ele : arr) + sl.push_back(ele.toString()); + val = sl; + break; + } + case SettingType::FunctionKeys: + val = jVal.toVariant(); + break; + case SettingType::String: + val = jVal.toString(); + break; + case SettingType::Boolean: + val = jVal.toBool(); + break; + } + newData[craftKey(gKey, key)] = val; + } + } + getInstance()->m_data = newData; + return true; +} + +bool ProjectSettings::isFunctionEnabled(const QString &group, const QString &function) +{ + const QStringList functionNames = getInstance()->value(QStringLiteral("Functionalities"), + QStringLiteral("IdentifierList")).toStringList(); + + return functionNames.contains(group + QLatin1Char(':') + function); +} + + + + +void Settings::init(const QByteArray &settings) +{ + QByteArray uncompressed; + try + { + uncompressed = qUncompress(settings); + } + catch (...) + {} + + if (uncompressed.isEmpty()) + uncompressed = settings; + + QDataStream stream(uncompressed); + stream >> m_data; +} + + +QByteArray Settings::exportSettings() const +{ + if (m_data.isEmpty()) + return QByteArray(); + + QByteArray ba; + QDataStream stream(&ba, QIODevice::WriteOnly); + stream << m_data; + return qCompress(ba); +} + + +void Settings::clear() +{ + m_data.clear(); +} + +void Settings::dump() +{ + qWarning() << "Settings::m_data: keys=" << m_data.keys(); + QHashIterator it(m_data); + int i=0; + while (it.hasNext()) + { + it.next(); + qWarning() << i++ << ":" << " - key='" << it.key() << "' value -> '" << it.value() << "'"; + } + qWarning() << "Settings::m_data: ------------------"; +} + +void Settings::setValue(const QString &group, const QString &identifier, const QVariant &value) +{ + + m_data.insert(craftKey(group, identifier), value); + emit valueChanged(group, identifier, value); +} + + +void Settings::removeValue(const QString &group, const QString &identifier) +{ + if (m_data.remove(craftKey(group, identifier))) + emit valueRemoved(group, identifier); +} + + +QStringList Settings::identifiers(const QString &group) const +{ + QStringList result; + for (auto it = m_data.begin(); it != m_data.end(); ++it) + { + QVector decrafted = it.key().splitRef(QLatin1Char('.')); + if (decrafted.size() > 1 && group.compare(decrafted.at(0)) == 0) + result.append(decrafted.at(1).toString()); + } + return result; +} + + +QVariant Settings::value(const QString &group, const QString &identifier, const QVariant &defaultValue) const +{ + return m_data.value(craftKey(group, identifier), defaultValue); +} + +} diff --git a/Lts/Common/LtsUtils/LtsUtilsSettings/settings.h b/Lts/Common/LtsUtils/LtsUtilsSettings/settings.h new file mode 100755 index 000000000..b1563de7a --- /dev/null +++ b/Lts/Common/LtsUtils/LtsUtilsSettings/settings.h @@ -0,0 +1,73 @@ + +#pragma once + +#include +#include +#include +#include + +class QJsonObject; + +namespace LtsUtils +{ + +class LTSUTILSSETTINGS_EXPORT Settings : public QObject +{ + Q_OBJECT + +public: + ~Settings() = default; + + + + void setValue(const QString &group, const QString &identifier, const QVariant &value); + + + + void removeValue(const QString &group, const QString &identifier); + + + + QVariant value(const QString &group, const QString &identifier, const QVariant &defaultValue = QVariant()) const; + + + + QStringList identifiers(const QString &group) const; + + void init(const QByteArray &settings); + QByteArray exportSettings() const; + void clear(); + + void dump(); + +protected: + Settings() = default; + +signals: + void valueChanged(const QString &group, const QString &identifier, const QVariant &value); + void valueRemoved(const QString &group, const QString &identifier); + +protected: + QHash m_data; + +}; + +class LTSUTILSSETTINGS_EXPORT OperatorSettings : public Settings +{ + Q_OBJECT +public: + static OperatorSettings *getInstance(); +}; + +class LTSUTILSSETTINGS_EXPORT ProjectSettings : public Settings +{ + Q_OBJECT +public: + static ProjectSettings *getInstance(); + + static bool initFromFile(const QString &filename); + static bool isFunctionEnabled(const QString &group, const QString &function); +}; + + +} diff --git a/Lts/Common/LtsVirtualKeyboard/CMakeLists.txt b/Lts/Common/LtsVirtualKeyboard/CMakeLists.txt new file mode 100755 index 000000000..874965a18 --- /dev/null +++ b/Lts/Common/LtsVirtualKeyboard/CMakeLists.txt @@ -0,0 +1,36 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +# +# Lts Widgets library +# + +SET(ltsvkeyboard_SRCS + LtsKeyPushButton.h + LtsKeyPushButton.cpp + LtsVirtualKeyboard.h + LtsVirtualKeyboard.cpp +) + +NGL_WRAP_UI(ltsvkeyboard_SRCS +) + +NGL_ADD_LIBRARY2(WS LtsVirtualKeyboard SHARED ${ltsvkeyboard_SRCS} ) + +NGL_TARGET_INCLUDE_DIRECTORIES(LtsVirtualKeyboard + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) + +NGL_TARGET_LINK_LIBRARIES(LtsVirtualKeyboard + PRIVATE + PUBLIC + LtsWidgets + LtsSession + LtsAppManager + Qt5::Widgets + Qt5::Gui +) diff --git a/Lts/Common/LtsVirtualKeyboard/LtsKeyPushButton.cpp b/Lts/Common/LtsVirtualKeyboard/LtsKeyPushButton.cpp new file mode 100755 index 000000000..b27b645f8 --- /dev/null +++ b/Lts/Common/LtsVirtualKeyboard/LtsKeyPushButton.cpp @@ -0,0 +1,150 @@ + + + +#include "LtsKeyPushButton.h" +#include "LtsVirtualKeyboard.h" +#include +#include +#include +#include + + +namespace LtsVirtualKeyboard +{ +LtsKeyPushButton::LtsKeyPushButton(const QString ¬TranslatedText, const QString ¬TranslatedTextAlt, QWidget *parent) + : QToolButton(parent) + , m_parent(parent) + , m_notTranslatedText(notTranslatedText) + , m_notTranslatedTextAlt(notTranslatedTextAlt) + , m_altMode(false) + , m_capsMode(false) +{ + setFocusPolicy(Qt::NoFocus); + setStyleSheet(QStringLiteral(DEFAULT_STYLE_BUTTON) + QStringLiteral(DEFAULT_BACKGROUND_BUTTON)); + + connect(this, &LtsKeyPushButton::pressedKey, this, &LtsKeyPushButton::getKeyPress); +} + +void LtsKeyPushButton::getKeyPress() +{ + int keyCode = 0; + QKeyEvent *key = nullptr; + QString text = this->text(); + + + if (!qobject_cast(m_parent)) + return; + LtsVirtualKeyboard *keyboard = qobject_cast(m_parent); + + + if (text == QStringLiteral("&&")) + text = QStringLiteral("&"); + + if (NO_SPECIAL_KEY(text) == false && (IS_BACK(text) == true || IS_TAB(text) == true || IS_RETURN(text) == true || IS_CTRL_LEFT(text) == true || + IS_ALT(text) == true || IS_CANC(text) == true || IS_CUT_LEFT(text) == true || IS_PASSWORD(text) || IS_PASTE(text) || IS_COPY(text))) + key = new QKeyEvent(QEvent::KeyPress, keyCode, Qt::NoModifier, text); + else + { + keyCode = text.toUtf8()[0]; + if (m_capsMode == false) + { + text = text.toLower(); + key = new QKeyEvent(QEvent::KeyPress, keyCode, Qt::NoModifier, text); + } + else + key = new QKeyEvent(QEvent::KeyPress, keyCode, Qt::ShiftModifier, text); + } + keyboard->receiptChildKey(nullptr, key); + QCoreApplication::processEvents(); +} + + +void LtsKeyPushButton::mousePressEvent(QMouseEvent *) +{ + if (!qobject_cast(m_parent)) + return; + QString text = this->text(); + QString defaultStyleButton = QStringLiteral(DEFAULT_STYLE_BUTTON); + QString changedColorButton = QStringLiteral(CHANGED_BACKGROUND_BUTTON); + + if (IS_CAPS(text) == true) + { + if (!styleSheet().contains(QStringLiteral(DEFAULT_BACKGROUND_BUTTON))) + changedColorButton = QStringLiteral(DEFAULT_BACKGROUND_BUTTON); + setStyleSheet(defaultStyleButton + changedColorButton); + repaint(); + QCoreApplication::processEvents(); + } + + + else if (IS_FIN(text) == true) + setStyleSheet(defaultStyleButton + QStringLiteral(CHANGED_BACKGROUND_BUTTON)); + else + { + setStyleSheet(defaultStyleButton + changedColorButton); + repaint(); + QCoreApplication::processEvents(); + } + emit pressedKey(); + + m_oldYKey = 0; +} + +void LtsKeyPushButton::mouseReleaseEvent(QMouseEvent * ) +{ + if (!qobject_cast(m_parent)) + return; + + LtsVirtualKeyboard *tmpKeyBoard = qobject_cast(m_parent); + + bool pressedEcho = IS_PASSWORD(text()) == true; + + if (IS_CAPS(text()) == false && pressedEcho == false) + setStyleSheet(QStringLiteral(DEFAULT_STYLE_BUTTON) + QStringLiteral(DEFAULT_BACKGROUND_BUTTON)); + else if (pressedEcho == true && tmpKeyBoard->isEnabledSwitchingEcho() == false) + setStyleSheet(QStringLiteral(DEFAULT_STYLE_BUTTON) + QStringLiteral(DEFAULT_BACKGROUND_BUTTON)); +} + +void LtsKeyPushButton::changeEvent(QEvent *event) +{ + if (event->type() == QEvent::LanguageChange) + { + setAltMode(m_altMode); + } + else + QToolButton::changeEvent(event); +} + +void LtsKeyPushButton::setAltMode(bool altmode) +{ + m_altMode = altmode; + if (m_altMode) + { + if (!IS_CANC(text()) && !IS_FIN(text()) && !IS_CAPS(text()) + && !IS_COPY(text()) && !IS_PASTE(text()) && !IS_CUT_LEFT(text()) ) + setText(m_notTranslatedTextAlt); + } + else + { + setText(m_notTranslatedText); + } + setObjectName(text()); + + setCapsMode(m_capsMode); +} + +void LtsKeyPushButton::setCapsMode(bool caps) +{ + if (text().length() > 2 || IS_FIN(text())) + return; + if (m_notTranslatedText == QChar(223)) + return; + m_capsMode = caps; + if (m_capsMode) + { + setText(text().toUpper()); + } + else + setText(text().toLower()); +} +} diff --git a/Lts/Common/LtsVirtualKeyboard/LtsKeyPushButton.h b/Lts/Common/LtsVirtualKeyboard/LtsKeyPushButton.h new file mode 100755 index 000000000..66e87ea9f --- /dev/null +++ b/Lts/Common/LtsVirtualKeyboard/LtsKeyPushButton.h @@ -0,0 +1,89 @@ + + + +#pragma once +#include +#include + +#define DEFAULT_BACKGROUND_BUTTON "background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 #8C8F8C, stop: 1 #333831);" +#define CHANGED_BACKGROUND_BUTTON "background:lightgray;color:darkred;" + + + +#define DEFAULT_STYLE_BUTTON "color:white;border: 1px solid #000000;border-radius:6px;" + +#define STYLE_FOCUS_TEXT "border: 1px solid red" + + + + + + + +namespace LtsVirtualKeyboard +{ +class LTSVIRTUALKEYBOARD_EXPORT LtsKeyPushButton : public QToolButton +{ + Q_OBJECT + + +#define KEY_TAB LtsKeyPushButton::tr("TAB") +#define KEY_ALT LtsKeyPushButton::tr("!?#") +#define KEY_ALT2 LtsKeyPushButton::tr("Abc") +#define KEY_CAPS QStringLiteral("CAPS") +#define KEY_CTRL_LEFT LtsKeyPushButton::tr("CTRL") +#define KEY_CUT_LEFT LtsKeyPushButton::tr("cut") +#define KEY_COPY LtsKeyPushButton::tr("copy") +#define KEY_PASTE LtsKeyPushButton::tr("paste") +#define KEY_BACKSPACE LtsKeyPushButton::tr("<--") +#define KEY_HIDECHAR LtsKeyPushButton::tr("echo") +#define KEY_CANC QStringLiteral("->") +#define KEY_RETURN QStringLiteral("RETURN") +#define KEY_FIN QStringLiteral("Ok") +#define KEY_SHIFT QStringLiteral("SHIFT") +#define KEY_SPACE " " + +#define IS_KEY(keyTextPressed, keyValCompare) (keyTextPressed).contains((keyValCompare), Qt::CaseInsensitive) + +#define IS_TAB(text) IS_KEY(text, KEY_TAB) +#define IS_ALT(text) IS_KEY(text, KEY_ALT) +#define IS_ALT2(text) IS_KEY(text, KEY_ALT2) +#define IS_CAPS(text) IS_KEY(text, KEY_CAPS) +#define IS_CTRL_LEFT(text) IS_KEY(text, KEY_CTRL_LEFT) +#define IS_CUT_LEFT(text) IS_KEY(text, KEY_CUT_LEFT) +#define IS_BACK(text) IS_KEY(text, KEY_BACKSPACE) +#define IS_CANC(text) IS_KEY(text, KEY_CANC) +#define IS_RETURN(text) IS_KEY(text, KEY_RETURN) +#define IS_FIN(text) IS_KEY(text, KEY_FIN) +#define IS_SHIFT(text) IS_KEY(text, KEY_SHIFT) +#define IS_SPACE(text) IS_KEY(text, QStringLiteral(KEY_SPACE)) +#define IS_PASSWORD(text) IS_KEY(text, KEY_HIDECHAR) +#define IS_COPY(text) IS_KEY(text, KEY_COPY) +#define IS_PASTE(text) IS_KEY(text, KEY_PASTE) +#define NO_SPECIAL_KEY(text) ((text).length() == 1) + +public: + LtsKeyPushButton(const QString ¬TranslatedText, const QString ¬TranslatedTextAlt, QWidget *parent = nullptr); + void setAltMode(bool altmode); + void setCapsMode(bool caps); + +private slots: + void getKeyPress(); + +signals: + void pressedKey(); + +private: + QWidget *m_parent; + QString m_notTranslatedText; + QString m_notTranslatedTextAlt; + int m_oldYKey; + bool m_altMode; + bool m_capsMode; + +protected: + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void changeEvent(QEvent *event); +}; +} diff --git a/Lts/Common/LtsVirtualKeyboard/LtsVirtualKeyboard.cpp b/Lts/Common/LtsVirtualKeyboard/LtsVirtualKeyboard.cpp new file mode 100755 index 000000000..83efc9476 --- /dev/null +++ b/Lts/Common/LtsVirtualKeyboard/LtsVirtualKeyboard.cpp @@ -0,0 +1,953 @@ + + + +#include "LtsVirtualKeyboard.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace LtsVirtualKeyboard +{ + +static LtsVirtualKeyboard *s_ltsVirtualKeyboard = nullptr; +static bool lastState = false; +LtsVirtualKeyboard::LtsVirtualKeyboard(LtsSession::LtsSession *session, QWidget *activeForm, bool onlyNumericPad, QWidget *parent) + : QWidget(parent) + + , m_te(nullptr) + , m_le(nullptr) + , m_nextInput(nullptr) + , m_activeWindow(activeForm) + , m_echoMode(false) + , m_enablePasswordEcho(false) + , m_numericPad(onlyNumericPad) + , m_isAlt(false) + , m_isCap(false) + , m_enableEnter(true) + , m_ready(false) + , m_height(0) + , m_clipboard(QApplication::clipboard()) + , m_session(session) +{ + setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowStaysOnTopHint | Qt::Dialog); + setFocusPolicy(Qt::NoFocus); + + setWindowTitle(QString()); + connect(m_activeWindow, &QWidget::destroyed, + this, [this](QObject *obj) + { + if (static_cast(m_activeWindow) == obj) + m_activeWindow = nullptr; + }); +} + +LtsVirtualKeyboard::~LtsVirtualKeyboard() +{ +} + +LtsVirtualKeyboard *LtsVirtualKeyboard::getInstance(LtsSession::LtsSession *session, QWidget *activeForm, bool onlyNumericPad, QWidget *parent) +{ + onlyNumericPad = false; + + + + if (s_ltsVirtualKeyboard && onlyNumericPad == lastState ) + { + + if (activeForm) + s_ltsVirtualKeyboard->setActiveWindw(activeForm); + if (parent) + s_ltsVirtualKeyboard->setParent(parent); + s_ltsVirtualKeyboard->setSession(session); + + return s_ltsVirtualKeyboard; + } + cleanupLtsVirtualKeyboard(); + lastState = onlyNumericPad; + s_ltsVirtualKeyboard = new LtsVirtualKeyboard(session, activeForm, onlyNumericPad, parent); + s_ltsVirtualKeyboard->createKeyboard(); + qAddPostRoutine(&cleanupLtsVirtualKeyboard); + + return s_ltsVirtualKeyboard; +} + +void LtsVirtualKeyboard::cleanupLtsVirtualKeyboard() +{ + delete s_ltsVirtualKeyboard; + s_ltsVirtualKeyboard = nullptr; +} + +void LtsVirtualKeyboard::enableSwitchingEcho(bool status) +{ + m_enablePasswordEcho = status; +} + +bool LtsVirtualKeyboard::isEnabledSwitchingEcho() const +{ + return (m_enablePasswordEcho); +} + +LtsKeyPushButton *LtsVirtualKeyboard::createNewKey(const QString &keyValue, const QString ¬TranslatedValue, const QString ¬TranslatedValueAlt) +{ + QString altText = notTranslatedValueAlt; + if (altText.isEmpty()) + altText = notTranslatedValue; + LtsKeyPushButton *tmp = new LtsKeyPushButton(notTranslatedValue, altText, this); + QString style = QStringLiteral(DEFAULT_STYLE_BUTTON) + QStringLiteral(DEFAULT_BACKGROUND_BUTTON); + QSize imageDim; + int width = 0, height = 0; + + imageDim.setWidth(32); + imageDim.setHeight(32); + + if (IS_PASSWORD(keyValue) == true) + { + tmp->setIconSize(imageDim); + tmp->setIcon(QPixmap(QStringLiteral(":/TastieraVirtuale/normalEcho"))); + } + else if (IS_PASTE(keyValue) == true) + { + tmp->setIconSize(imageDim); + tmp->setEnabled(false); + tmp->setIcon(QPixmap(QStringLiteral("icon:edit-paste.svg"))); + tmp->setToolButtonStyle(Qt::ToolButtonIconOnly); + } + else if (IS_COPY(keyValue) == true) + { + tmp->setIconSize(imageDim); + tmp->setIcon(QPixmap(QStringLiteral("icon:edit-copy.svg"))); + tmp->setToolButtonStyle(Qt::ToolButtonIconOnly); + } + + else if (IS_CUT_LEFT(keyValue) == true) + { + tmp->setIconSize(imageDim); + tmp->setIcon(QPixmap(QStringLiteral("icon:edit-cut.svg"))); + tmp->setToolButtonStyle(Qt::ToolButtonIconOnly); + } + + else if (IS_CAPS(keyValue) == true) + { + tmp->setIconSize(imageDim); + tmp->setIcon(QPixmap(QStringLiteral("icon:shift_capslook.svg"))); + tmp->setToolButtonStyle(Qt::ToolButtonIconOnly); + } + + else if (IS_RETURN(keyValue) == true) + { + tmp->setIconSize(imageDim); + tmp->setIcon(QPixmap(QStringLiteral("icon:carriage_return.svg"))); + tmp->setToolButtonStyle(Qt::ToolButtonIconOnly); + } + + tmp->setStyleSheet(style); + + + width = 54; + height = 40; +#ifdef TABLET_MODE + width = width * 2.0; + height = height * 2.0; +#endif + + tmp->setMinimumSize(width, height); + tmp->setMaximumSize(width, height); + + if (IS_SPACE(keyValue) == true) + { + tmp->setMinimumSize(width * 20, height); + tmp->setMaximumSize(width * 20, height); + } + + if (IS_FIN(keyValue) == true) + { + tmp->setStyleSheet(style + QStringLiteral(CHANGED_BACKGROUND_BUTTON)); + } + tmp->setText(keyValue); + tmp->setCapsMode(m_isCap); + + + tmp->setObjectName(keyValue); + + return tmp; +} + +void LtsVirtualKeyboard::controlKeyEcho(QWidget *control) +{ + Q_UNUSED(control); + QString textToSearch; + LtsKeyPushButton *echoControlKey; + QString tmpStyle; + QPushButton *pasteButton = findChild(KEY_PASTE); + QPushButton *cutButton = findChild(KEY_CUT_LEFT); + QPushButton *copyButton = findChild(KEY_COPY); + + textToSearch = KEY_HIDECHAR; + + if (pasteButton != nullptr && copyButton != nullptr) + { + cutButton->setEnabled(true); + copyButton->setEnabled(true); + if (m_clipboard->text().length() > 0) + pasteButton->setEnabled(true); + else + pasteButton->setEnabled(false); + } + + echoControlKey = findChild(textToSearch); + if (echoControlKey != nullptr) + { + echoControlKey->setStyleSheet(QStringLiteral(DEFAULT_STYLE_BUTTON) + QStringLiteral(DEFAULT_BACKGROUND_BUTTON) + tmpStyle); + echoControlKey->setIcon(QPixmap(QStringLiteral(":/TastieraVirtuale/normalEcho"))); + + echoControlKey->repaint(); + QApplication::processEvents(); + } +} + +void LtsVirtualKeyboard::setTextToCurrentWidget(const QString &text) +{ + + + if (m_nextInput->inherits("QLineEdit")) + qobject_cast(m_nextInput)->setText(text); + + if (m_nextInput->inherits("QTextEdit")) + qobject_cast(m_nextInput)->setText(text); + + if (m_nextInput->inherits("QPlainTextEdit")) + qobject_cast(m_nextInput)->setPlainText(text); + + if (m_nextInput->inherits("QSpinBox")) + qobject_cast(m_nextInput)->setValue(text.toInt()); + + if (m_nextInput->inherits("QDoubleSpinBox")) + qobject_cast(m_nextInput)->setValue(text.toDouble()); +} + +QString LtsVirtualKeyboard::getTextFromCurrentWidget() +{ + if (qobject_cast(m_nextInput)) + return qobject_cast(m_nextInput)->text(); + + if (qobject_cast(m_nextInput)) + return qobject_cast(m_nextInput)->toPlainText(); + if (qobject_cast(m_nextInput)) + return qobject_cast(m_nextInput)->toPlainText(); + if (qobject_cast(m_nextInput)) + return QString::number(qobject_cast(m_nextInput)->value()); + if (qobject_cast(m_nextInput)) + return QString::number(qobject_cast(m_nextInput)->value()); + return QString(); + +} + +QString LtsVirtualKeyboard::getCurretText() const +{ + if (m_te->isVisible()) + return m_te->toPlainText(); + return m_le->text(); +} + +void LtsVirtualKeyboard::setCurrentText(const QString &text) +{ + if(m_te->isVisible()) + m_te->setText(text); + else + m_le->setText(text); +} + +int LtsVirtualKeyboard::getCurrentTextCursorPosition() +{ + if(m_te->isVisible()) + return m_te->textCursor().position(); + return m_le->cursorPosition(); +} + +void LtsVirtualKeyboard::setCurrentTextCursorPosition(int cursor) +{ + if(m_te->isVisible()) + { + QTextCursor cur = m_te->textCursor(); + cur.setPosition(cursor); + m_te->setTextCursor(cur); + }else + { + m_le->setCursorPosition(cursor); + } + +} + +bool LtsVirtualKeyboard::hasSelection() const +{ + if(m_te->isVisible()) + return m_te->textCursor().hasSelection(); + return m_le->hasSelectedText(); +} + +QString LtsVirtualKeyboard::getSelectedText() const +{ + if (m_te->isVisible()) + return m_te->textCursor().selectedText(); + return m_le->selectedText(); +} + +int LtsVirtualKeyboard::getSelectionStart() const +{ + if (m_te->isVisible()) + return m_te->textCursor().selectionStart(); + return m_le->selectionStart(); + +} + +void LtsVirtualKeyboard::reinit() +{ + qDeleteAll(findChildren()); + m_te->deleteLater(); + m_le->deleteLater(); + delete layout(); + createKeyboard(); +} + + +void LtsVirtualKeyboard::receiptChildKey(QWidget *focusThisControl, QKeyEvent *event, bool reset) +{ + + if (!m_ready) + return; + + if (reset == true) + { + m_nextInput = getNextTextbox(focusThisControl, true); + return; + } + if (m_nextInput == nullptr || !event) + { + return; + } + + + QString newKey = event->text(); + QString tmpReceiptString = getCurretText(); + int tmpPos = getCurrentTextCursorPosition(); + if (isNumericPad()) + newKey = newKey.trimmed(); + if (NO_SPECIAL_KEY(newKey) == false) + { + + if (IS_RETURN(newKey) == true) + { + tmpReceiptString = tmpReceiptString.insert(tmpPos, QChar(13)); + + setTextToCurrentWidget(tmpReceiptString); + setCurrentText(tmpReceiptString); + setCurrentTextCursorPosition(tmpPos+1); + } + else if (IS_FIN(newKey) == true) + { + + hide(true); + + + if (!qobject_cast(m_nextInput) && !qobject_cast(m_nextInput)) + { + QKeyEvent event(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier); + QApplication::sendEvent(m_nextInput, &event); + } + m_nextInput->clearFocus(); + m_nextInput = nullptr; + + return; + } + + + + + + + + + + + + + + + else if (IS_BACK(newKey) == true) + { + if (!hasSelection()) + { + tmpReceiptString = tmpReceiptString.remove(tmpPos - 1, 1); + setTextToCurrentWidget(tmpReceiptString); + setCurrentText(tmpReceiptString); + setCurrentTextCursorPosition(tmpPos-1); + } + else + { + if (tmpPos - 1 >= 0) + { + int selectionStart = getSelectionStart(); + tmpReceiptString = tmpReceiptString.remove(selectionStart, getSelectedText().size()); + setCurrentText(tmpReceiptString); + setCurrentTextCursorPosition(selectionStart); + setTextToCurrentWidget(tmpReceiptString); + } + } + + } + else if (IS_CANC(newKey) == true) + { + tmpReceiptString = tmpReceiptString.remove(tmpPos, 1); + setTextToCurrentWidget(tmpReceiptString); + setCurrentText(tmpReceiptString); + setCurrentTextCursorPosition(tmpPos); + } + else if (IS_COPY(newKey) == true || IS_CUT_LEFT(newKey) == true) + { + LtsKeyPushButton *button = findChild(KEY_PASTE); + + if (button != nullptr) + { + if (hasSelection()) + { + m_clipboard->setText(getSelectedText()); + + if (IS_CUT_LEFT(newKey) == true) + { + int selectionStart =getSelectionStart(); + tmpReceiptString = tmpReceiptString.remove(selectionStart, getSelectedText().size()); + setCurrentText(tmpReceiptString); + + setCurrentTextCursorPosition(selectionStart); + + setTextToCurrentWidget(tmpReceiptString); + } + button->setEnabled(true); + } + else + button->setEnabled(false); + } + } + else if (IS_PASTE(newKey) == true) + { + tmpReceiptString.insert(tmpPos, m_clipboard->text()); + setTextToCurrentWidget(tmpReceiptString); + setCurrentText(tmpReceiptString); + setCurrentTextCursorPosition(tmpPos + m_clipboard->text().length()); + } + else if (IS_ALT(newKey) == true || IS_ALT2(newKey) == true) + { + QList kList = findChildren(); + m_isAlt = !m_isAlt; + + auto i = kList.begin(); + + for (; i != kList.end(); ++i) + { + LtsKeyPushButton *tw = *i; + tw->setAltMode(m_isAlt); + } + } + else if (IS_CAPS(newKey) == true) + { + QList kList = findChildren(); + m_isCap = !m_isCap; + + auto i = kList.begin(); + + for (; i != kList.end(); ++i) + { + LtsKeyPushButton *tw = *i; + tw->setCapsMode(m_isCap); + } + } + } + else + { + if (getSelectedText().size()) + { + tmpPos = getSelectionStart(); + tmpReceiptString = tmpReceiptString.remove(tmpPos, getSelectedText().size()); + } + + tmpReceiptString = tmpReceiptString.insert(tmpPos, newKey); + setTextToCurrentWidget(tmpReceiptString); + setCurrentText(tmpReceiptString); + setCurrentTextCursorPosition(tmpPos + 1); + } +} + + +void LtsVirtualKeyboard::switchKeyEchoMode(QWidget *control) +{ + setStatusEchoMode(!statusEchoMode()); + controlKeyEcho(control); +} + + +QWidget *LtsVirtualKeyboard::getNextTextbox(QWidget *thisControl, bool reset) +{ + QWidget *tmpInputFound = nullptr; + m_echoMode = false; + if (m_activeWindow == nullptr) + { + m_nextInput = nullptr; + return (nullptr); + } + if (thisControl != nullptr) + { + if (m_activeWindow->findChild(thisControl->objectName()) == 0 && + m_activeWindow->findChild(thisControl->objectName()) == 0 && + m_activeWindow->findChild(thisControl->objectName()) == 0 && + m_activeWindow->findChild(thisControl->objectName()) == 0 && + m_activeWindow->findChild(thisControl->objectName()) == 0) + { + return (nullptr); + } + else + { + return setCurrentTextStyle(thisControl); + } + } + if (m_nextInput == nullptr || reset == true) + m_nextInput = m_activeWindow->nextInFocusChain(); + else + m_nextInput = m_nextInput->nextInFocusChain(); + do + { + if (m_nextInput->inherits("QLineEdit") == true) + { + tmpInputFound = qobject_cast(m_nextInput); + return setCurrentTextStyle(tmpInputFound); + } + if (m_nextInput->inherits("QTextEdit") == true) + { + tmpInputFound = qobject_cast(m_nextInput); + return setCurrentTextStyle(tmpInputFound); + } + if (m_nextInput->inherits("QPlainTextEdit") == true) + { + tmpInputFound = qobject_cast(m_nextInput); + return setCurrentTextStyle(tmpInputFound); + } + if (m_nextInput->inherits("QSpinBox") == true) + { + tmpInputFound = qobject_cast(m_nextInput); + return setCurrentTextStyle(tmpInputFound); + } + if (m_nextInput->inherits("QDoubleSpinBox") == true) + { + tmpInputFound = qobject_cast(m_nextInput); + return setCurrentTextStyle(tmpInputFound); + } + m_nextInput = m_nextInput->nextInFocusChain(); + } + while (m_nextInput != nullptr); + return nullptr; +} + + +QWidget *LtsVirtualKeyboard::setCurrentTextStyle(QWidget *control) +{ + m_nextInput = control; + control->setStyleSheet(QStringLiteral(STYLE_FOCUS_TEXT)); + return (control); +} + +QWidget *LtsVirtualKeyboard::setDefaultTextStyle(QWidget *control) +{ + if (!m_nextInput) + return nullptr; + QLineEdit le; + control->setStyleSheet(le.styleSheet()); + return (control); +} + +QSize LtsVirtualKeyboard::availableDesktopSize() const +{ +#if QT_VERSION < QT_VERSION_CHECK(5, 12, 0) + return QApplication::desktop()->availableGeometry().size(); +#else + const QWindow *win = window()->windowHandle(); + const QScreen *scr = (win && win->screen()) ? win->screen() : QGuiApplication::primaryScreen(); + return scr ? scr->availableSize() : QSize(1280, 1024); +#endif +} + +void LtsVirtualKeyboard::init_keyboard(QWidget *focusThisControl) +{ + m_nextInput = focusThisControl; + checkInput(); + + receiptChildKey(focusThisControl, nullptr, true); +} + +void LtsVirtualKeyboard::show(QWidget *first, bool frameless) +{ + Q_UNUSED(frameless); + init_keyboard(first); + + if (windowState() == Qt::WindowMinimized) + setWindowState(Qt::WindowNoState); + setStatusEchoMode(false); + setVisible(true); + + const auto availSize = availableDesktopSize(); + move((availSize.width() - width()) / 2, + (availSize.height() - height()) - 10); + QTimer::singleShot(300, this, [this]() { m_ready = true; }); +} + + +void LtsVirtualKeyboard::borderFrame(bool visible) +{ + if (visible == true) + setWindowFlags(Qt::Dialog | Qt::WindowStaysOnTopHint | Qt::MSWindowsFixedSizeDialogHint); + else + setWindowFlags(Qt::Window | Qt::FramelessWindowHint); +} + +void LtsVirtualKeyboard::checkInput() +{ + m_enableEnter = false; + m_echoMode = false; + + m_le->setVisible(true); + m_te->setVisible(false); + if (qobject_cast(m_nextInput)) + { + QLineEdit *le = qobject_cast(m_nextInput); + m_le->setEchoMode(le->echoMode()); + } + if (qobject_cast(m_nextInput) || qobject_cast(m_nextInput)) + { + m_le->setVisible(false); + m_te->setVisible(true); + m_enableEnter = true; + } + + LtsKeyPushButton *crButton = findChild(KEY_RETURN); + if (crButton) + { + crButton->setEnabled(m_enableEnter); + } + + if (m_te->isVisible()) + setMaximumHeight(m_height+m_te->height()); + else + setMaximumHeight(m_height+m_le->height()); + +} + +bool LtsVirtualKeyboard::isNumericPad() const +{ + return (m_numericPad); +} + +void LtsVirtualKeyboard::focusThis(QWidget *control, bool numpad) +{ + m_ready = false; + if (m_activeWindow != nullptr) + { + if (m_numericPad != numpad) + { + m_numericPad = numpad; + reinit(); + } + + if (m_nextInput != nullptr) + m_nextInput = setDefaultTextStyle(m_nextInput); + init_keyboard(control); + controlKeyEcho(control); + + setCurrentText(getTextFromCurrentWidget()); + if(m_te->isVisible()) + m_te->moveCursor(QTextCursor::Start); + else + m_le->end(false); + if (m_nextInput) + setWindowTitle(m_nextInput->toolTip()); + } + QTimer::singleShot(300, this, [this]() { m_ready = true; }); + const auto availSize = availableDesktopSize(); + if (isNumericPad()) + move((availSize.width() - width()) / 2, + (availSize.height() - height()) - 10); + else + move(10, availSize.height() - height() - 10); +} + +void LtsVirtualKeyboard::closeEvent(QCloseEvent *event) +{ + event->ignore(); +} + +void LtsVirtualKeyboard::hide(bool changeColor) +{ + + m_ready = false; + try + { + if (changeColor == true) + { + if (m_activeWindow != nullptr) + setDefaultTextStyle(m_nextInput); + } + if (!parent()) + setVisible(false); + + } + catch (...) + { + } +} + + +void LtsVirtualKeyboard::createKeyboard() +{ + LtsKeyPushButton *tmp = nullptr; + QVBoxLayout *tmpVLayout = new QVBoxLayout; + QHBoxLayout *tmpLayout = new QHBoxLayout; + QString tmpStyle; + QString backspace; + m_ready = false; + + m_te = new QTextEdit(this); + m_te->setMaximumHeight(210); + m_te->setMinimumHeight(210); + + m_te->setObjectName(QStringLiteral("internalLE")); + + tmpVLayout->addWidget(m_te); + + m_te->hide(); + + + m_le = new QLineEdit(this); + m_le->setObjectName(QStringLiteral("internalLE")); + + tmpVLayout->addWidget(m_le); + + + if (isNumericPad() == true) + { + QGridLayout *gridLayout = new QGridLayout(this); + gridLayout->addLayout(tmpVLayout,0,0); + for (short i = 49; i <= 51; i++) + tmpLayout->addWidget(createNewKey(QChar(i), QString::number(i - 48), QString::number(i - 48))); + gridLayout->addLayout(tmpLayout, 3, 0); + tmpLayout = new QHBoxLayout(this); + for (short i = 52; i <= 54; i++) + tmpLayout->addWidget(createNewKey(QChar(i), QString::number(i - 48), QString::number(i - 48))); + gridLayout->addLayout(tmpLayout, 2, 0); + tmpLayout = new QHBoxLayout(this); + for (short i = 55; i <= 57; i++) + tmpLayout->addWidget(createNewKey(QChar(i), QString::number(i - 48), QString::number(i - 48))); + gridLayout->addLayout(tmpLayout, 1, 0); + tmpLayout = new QHBoxLayout(this); + tmp = createNewKey(QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("0")); + tmp->setMinimumSize(tmp->minimumSize().width() * 2, tmp->minimumSize().height()); + tmpLayout->addWidget(tmp); + tmpLayout->addWidget(createNewKey(QStringLiteral("."), QStringLiteral(" . "), QStringLiteral(" . "))); + gridLayout->addLayout(tmpLayout, 4, 0); + tmpLayout = new QHBoxLayout(this); + tmpLayout->addWidget(createNewKey(QStringLiteral(","), QStringLiteral(" , "), QStringLiteral(" , "))); + backspace = QStringLiteral("BACKSPACE"); + tmpStyle = (KEY_BACKSPACE); + + tmp = createNewKey(tmpStyle, backspace); + tmp->setMaximumWidth(tmp->maximumWidth() * 1.5); + tmp->setMinimumWidth(tmp->minimumWidth() * 1.5); + tmpLayout->addWidget(tmp); + + tmp = createNewKey(KEY_FIN,KEY_FIN); + tmp->setMaximumWidth(tmp->maximumWidth() * 1.5); + tmp->setMinimumWidth(tmp->minimumWidth() * 1.5); + tmpLayout->addWidget(tmp); + gridLayout->addLayout(tmpLayout, 5, 0); + + + + setLayout(gridLayout); + setMaximumWidth(tmp->width()*3); + m_height= tmp->height()*5; + } + else + { + + + + tmpLayout->addWidget(createNewKey(QStringLiteral("^"), QStringLiteral("^"), QStringLiteral("^"))); + + tmpLayout->addWidget(createNewKey(QStringLiteral("1"), QStringLiteral("1"), QStringLiteral("!"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("2"), QStringLiteral("2"), QStringLiteral("\""))); + tmpLayout->addWidget(createNewKey(QStringLiteral("3"), QStringLiteral("3"), QChar(207))); + tmpLayout->addWidget(createNewKey(QStringLiteral("4"), QStringLiteral("4"), QStringLiteral("$"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("5"), QStringLiteral("5"), QStringLiteral("%"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("6"), QStringLiteral("6"), QStringLiteral("&&"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("7"), QStringLiteral("7"), QStringLiteral("/"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("8"), QStringLiteral("8"), QStringLiteral("("))); + tmpLayout->addWidget(createNewKey(QStringLiteral("9"), QStringLiteral("9"), QStringLiteral(")"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("="))); + tmpLayout->addWidget(createNewKey(QChar(223), QChar(223), QStringLiteral("?"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("`"), QChar(180), QStringLiteral("`"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("["), QStringLiteral("["), QStringLiteral("{"))); + backspace = QStringLiteral("<----"); + tmpStyle = KEY_BACKSPACE; + tmp = createNewKey(tmpStyle, backspace); + tmp->setMaximumWidth(tmp->maximumWidth() * 1.2); + tmp->setMinimumWidth(tmp->maximumWidth() * 1.2); + tmpLayout->addWidget(tmp); + tmpLayout->insertStretch(-1, 1); + tmpVLayout->addLayout(tmpLayout); + + + + tmpLayout = new QHBoxLayout(); + + QVBoxLayout *layoutReturn = new QVBoxLayout(); + tmp = createNewKey(KEY_ALT, KEY_ALT, KEY_ALT2); + tmp->setMaximumWidth(tmp->maximumWidth()); + tmp->setMinimumWidth(tmp->minimumWidth()); + tmpLayout->addWidget(tmp); + + tmpLayout->addWidget(createNewKey(QStringLiteral("Q"), QStringLiteral("Q"), QStringLiteral("@"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("W"), QStringLiteral("W"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("E"), QStringLiteral("E"), QChar(8364))); + tmpLayout->addWidget(createNewKey(QStringLiteral("R"), QStringLiteral("R"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("T"), QStringLiteral("T"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("Z"), QStringLiteral("Z"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("U"), QStringLiteral("U"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("I"), QStringLiteral("I"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("O"), QStringLiteral("O"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("P"), QStringLiteral("P"))); + tmpLayout->addWidget(createNewKey(QChar(220), QChar(220))); + tmpLayout->addWidget(createNewKey(QStringLiteral("+"), QStringLiteral("+"), QStringLiteral("*"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("]"), QStringLiteral("]"), QStringLiteral("}"))); + tmpStyle = KEY_CANC; + tmp = createNewKey(tmpStyle, QStringLiteral("->")); + tmp->setMaximumWidth(tmp->maximumWidth()); + tmp->setMinimumWidth(tmp->minimumWidth()); + tmpLayout->addWidget(tmp); + tmpLayout->insertStretch(-1, 1); + + + + + layoutReturn->addLayout(tmpLayout, 1); + + + tmpLayout = new QHBoxLayout(); + tmp = createNewKey(KEY_CAPS, KEY_CAPS); + tmp->setMinimumWidth(tmp->minimumWidth() * 1.5 + 5); + tmpLayout->addWidget(tmp); + tmpLayout->addWidget(createNewKey(QStringLiteral("A"), QStringLiteral("A"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("S"), QStringLiteral("S"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("D"), QStringLiteral("D"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("F"), QStringLiteral("F"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("G"), QStringLiteral("G"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("H"), QStringLiteral("H"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("J"), QStringLiteral("J"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("K"), QStringLiteral("K"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("L"), QStringLiteral("L"))); + tmpLayout->addWidget(createNewKey(QChar(214), QChar(214))); + tmpLayout->addWidget(createNewKey(QChar(196), QChar(196))); + tmpLayout->addWidget(createNewKey(QStringLiteral("'"), QStringLiteral("'"), QStringLiteral("#"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("|"), QStringLiteral("|"), QStringLiteral("\\"))); + tmpLayout->insertStretch(-1, 1); + + layoutReturn->setSpacing(5); + layoutReturn->addLayout(tmpLayout, 1); + + tmpLayout = new QHBoxLayout(); + tmpLayout->addLayout(layoutReturn, 1); + + + tmp = createNewKey(KEY_RETURN, QStringLiteral("RETURN")); + tmp->setEnabled(m_enableEnter); + tmp->setMaximumWidth(tmp->maximumWidth() * 1.4); + tmp->setMinimumWidth(tmp->minimumWidth() * 1.4); + tmp->setMinimumHeight(tmp->minimumHeight() * 1.4); + + tmpLayout->addWidget(tmp, 2); + + + tmpVLayout->addLayout(tmpLayout); + + + + tmpLayout = new QHBoxLayout(); + tmp = createNewKey(KEY_CUT_LEFT, KEY_CUT_LEFT); + tmp->setMaximumWidth(tmp->maximumWidth() * 1.2); + tmp->setMinimumWidth(tmp->minimumWidth() * 1.2); + tmpLayout->addWidget(tmp); + tmpLayout->addWidget(createNewKey(QStringLiteral("<"), QStringLiteral("<"), QStringLiteral(">"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("Y"), QStringLiteral("Y"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("X"), QStringLiteral("X"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("C"), QStringLiteral("C"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("V"), QStringLiteral("V"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("B"), QStringLiteral("B"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("N"), QStringLiteral("N"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("M"), QStringLiteral("M"))); + tmpLayout->addWidget(createNewKey(QStringLiteral(","), QStringLiteral(","), QStringLiteral(";"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("."), QStringLiteral("."), QStringLiteral(":"))); + tmpLayout->addWidget(createNewKey(QStringLiteral("-"), QStringLiteral("-"), QStringLiteral("_"))); + tmpLayout->insertStretch(-1, 1); + + + tmpVLayout->addLayout(tmpLayout); + tmpLayout = new QHBoxLayout(); + + tmp = createNewKey(KEY_COPY, KEY_COPY); + tmp->setMaximumWidth(tmp->maximumWidth()); + tmp->setMinimumWidth(tmp->maximumWidth()); + tmpLayout->addWidget(tmp); + + tmp = createNewKey(KEY_PASTE, KEY_PASTE); + tmp->setMaximumWidth(tmp->maximumWidth()); + tmp->setMinimumWidth(tmp->maximumWidth()); + tmpLayout->addWidget(tmp); + + QWidget *empty = new QWidget(this); + empty->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + tmpLayout->addWidget(empty); + + tmp = createNewKey(QStringLiteral(KEY_SPACE), QStringLiteral(" ")); + tmp->setMinimumWidth(300); + tmp->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + tmpLayout->addWidget(tmp, 3); + + empty = new QWidget(this); + empty->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + tmpLayout->addWidget(empty); + + tmp = createNewKey(KEY_FIN, KEY_FIN); + tmp->setMaximumWidth(tmp->maximumWidth() * 1.5); + tmp->setMinimumWidth(tmp->minimumWidth() * 1.5); + tmpLayout->addWidget(tmp); + + tmpVLayout->addLayout(tmpLayout); + setLayout(tmpVLayout); + + m_height = tmp->height()*5+10; + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + } + + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); +} + +} diff --git a/Lts/Common/LtsVirtualKeyboard/LtsVirtualKeyboard.h b/Lts/Common/LtsVirtualKeyboard/LtsVirtualKeyboard.h new file mode 100755 index 000000000..2478bc10d --- /dev/null +++ b/Lts/Common/LtsVirtualKeyboard/LtsVirtualKeyboard.h @@ -0,0 +1,97 @@ + + + +#pragma once + +#include "LtsKeyPushButton.h" +#include +#include + +class QClipboard; +class QLabel; +class QLineEdit; +class QSize; +class QTextEdit; + +namespace LtsVirtualKeyboard +{ + +class LTSVIRTUALKEYBOARD_EXPORT LtsVirtualKeyboard : public QWidget +{ + Q_OBJECT + + +private: + LtsVirtualKeyboard(LtsSession::LtsSession *session, QWidget *activeForm = nullptr, bool onlyNumericPad = false, QWidget *parent = nullptr); + ~LtsVirtualKeyboard(); + +public: + static LtsVirtualKeyboard *getInstance(LtsSession::LtsSession *session, QWidget *activeForm = nullptr, bool onlyNumericPad = false, QWidget *parent = nullptr); + static void cleanupLtsVirtualKeyboard(); + + void createKeyboard(); + bool statusEchoMode() const { return (m_echoMode); } + void setStatusEchoMode(bool echo) { m_echoMode = echo; } + void receiptChildKey(QWidget *focusThisControl = nullptr, QKeyEvent *event = nullptr, bool reset = false); + void switchKeyEchoMode(QWidget *control); + void enableSwitchingEcho(bool status); + bool isEnabledSwitchingEcho() const; + void borderFrame(bool visible = true); + void checkInput(); + QWidget *setDefaultTextStyle(QWidget *control); + bool isNumericPad() const; + QLabel *zoomedWidget(); + void setActiveWindw(QWidget *widget) { m_activeWindow = widget; } + void setSession(LtsSession::LtsSession *session) { m_session= session; } + + + + + +public slots: + void show(QWidget *first = nullptr, bool frameless = false); + void hide(bool changeColor); + void focusThis(QWidget *control, bool numpad = false); + +protected: + void closeEvent(QCloseEvent *event) override; + +private: + QSize availableDesktopSize() const; + void init_keyboard(QWidget *focusThisControl); + QWidget *setCurrentTextStyle(QWidget *control); + LtsKeyPushButton *createNewKey(const QString &keyValue, const QString ¬TranslatedValue, const QString ¬TranslatedValueAlt = QString()); + QWidget *getNextTextbox(QWidget *thisControl = nullptr, bool reset = false); + void controlKeyEcho(QWidget *control); + void setTextToCurrentWidget(const QString &text); + QString getTextFromCurrentWidget(); + QString getCurretText()const; + void setCurrentText(const QString &text); + int getCurrentTextCursorPosition(); + void setCurrentTextCursorPosition(int cursor); + bool hasSelection() const; + QString getSelectedText() const; + int getSelectionStart() const; + void reinit(); +private: + + QTextEdit *m_te; + QLineEdit *m_le; + + QWidget *m_nextInput; + QWidget *m_activeWindow; + bool m_echoMode; + bool m_enablePasswordEcho; + bool m_numericPad; + bool m_isAlt; + bool m_isCap; + bool m_enableEnter; + bool m_ready; + + int m_height; + QClipboard *m_clipboard; + LtsSession::LtsSession *m_session; + +}; + +} diff --git a/Lts/Common/LtsWebSocket/CMakeLists.txt b/Lts/Common/LtsWebSocket/CMakeLists.txt new file mode 100755 index 000000000..68c7cb5f8 --- /dev/null +++ b/Lts/Common/LtsWebSocket/CMakeLists.txt @@ -0,0 +1,79 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# Created by Philipp Dassow, SLST, 2017-11-28 + +# +# --- Library LtsWebSocketCommon -------------------------------------------------------------------------------- +# +SET(LtsWebSocketCommon_SRCS + client_message_factory.cpp + client_message_factory.h + client_message.cpp + client_message.h + reply_message.cpp + reply_message.h + data_manager_base_dl.h + json_keys.h + + logger.cpp + logger.h +) + +NGL_ADD_LIBRARY2(WS AAR SV LtsWebSocketCommon SHARED ${LtsWebSocketCommon_SRCS}) + +NGL_TARGET_INCLUDE_DIRECTORIES(LtsWebSocketCommon + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) + +NGL_TARGET_LINK_LIBRARIES(LtsWebSocketCommon + Qt5::Core + Qt5::WebSockets + NGLLogger +) + +# +# --- Library ltsWebSocketServer -------------------------------------------------------------------------------- +# +SET(LtsWebSocketServer_SRCS + client_connection.cpp + client_connection.h + connection_handler.cpp + connection_handler.h + lts_web_socket_server.cpp + lts_web_socket_server.h +) + +NGL_ADD_LIBRARY2(WS AAR SV LtsWebSocketServer SHARED ${LtsWebSocketServer_SRCS}) + +NGL_TARGET_INCLUDE_DIRECTORIES(LtsWebSocketServer + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) + +NGL_TARGET_LINK_LIBRARIES(LtsWebSocketServer + Qt5::Core + LtsWebSocketCommon +) + +# +# --- Library ltsWebSocketClient -------------------------------------------------------------------------------- +# +SET(LtsWebSocketClient_SRCS + lts_web_socket_client.cpp + lts_web_socket_client.h +) + +NGL_ADD_LIBRARY2(WS AAR SV LtsWebSocketClient SHARED ${LtsWebSocketClient_SRCS}) + +NGL_TARGET_INCLUDE_DIRECTORIES(LtsWebSocketClient + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) + +NGL_TARGET_LINK_LIBRARIES(LtsWebSocketClient + Qt5::Core + LtsWebSocketCommon +) diff --git a/Lts/Common/LtsWebSocket/client_connection.cpp b/Lts/Common/LtsWebSocket/client_connection.cpp new file mode 100755 index 000000000..1b8bdf8d1 --- /dev/null +++ b/Lts/Common/LtsWebSocket/client_connection.cpp @@ -0,0 +1,84 @@ + + +#include "client_connection.h" + +#include + +namespace LtsWebSocket +{ + +ClientConnection::ClientConnection(QWebSocket *socket, QObject *parent) + : QThread(parent) + , m_socket(socket ? socket : new QWebSocket) + , m_logger(getLogger("ClientConnection")) +{ +} + +ClientConnection::~ClientConnection() +{ + DEBUG_LOG(m_logger) << "Thread closed" << this; + delete m_socket; + qDeleteAll(m_messageQueue); +} + +bool ClientConnection::init() +{ + if (m_socket == nullptr) + { + ERROR_LOG(m_logger) << "Initialisation failed due to an invalid socket"; + return false; + } + + DEBUG_LOG(m_logger) << "Started new thread" << this; + connect(m_socket, &QWebSocket::textMessageReceived, this, &ClientConnection::onNewTextMessageFromSocket, Qt::DirectConnection); + connect(m_socket, &QWebSocket::connected, this, &ClientConnection::sendMessagesFromQueue); + connect(m_socket, &QWebSocket::disconnected, this, &ClientConnection::onSocketDisconnect); + + return true; +} + +void ClientConnection::addReply(ReplyMessage *message) +{ + if (!m_socket || m_socket->state() != QAbstractSocket::ConnectedState) + { + m_messageQueue.enqueue(message); + DEBUG_LOG(m_logger) << "Adding message to queue, size: " << m_messageQueue.size(); + return; + } + const QString msg(message->toJsonString()); + DEBUG_LOG(m_logger) << "Sending message: " << msg ; + m_socket->sendTextMessage(msg); + delete message; +} + +void ClientConnection::onNewTextMessageFromSocket(const QString &message) +{ + DEBUG_LOG(m_logger) << "Recv msg: " << message << this; + emit newTextMessage(this, message); +} + +void ClientConnection::sendMessagesFromQueue() +{ + INFO_LOG(m_logger) << "Client connected ip: " << m_socket->peerAddress().toString() << ":" << m_socket->peerPort(); + + if (m_socket->state() != QAbstractSocket::ConnectedState) + return; + while (!m_messageQueue.isEmpty()) + { + ReplyMessage *message = m_messageQueue.dequeue(); + const QString msg(message->toJsonString()); + TRACE_LOG(m_logger) << "Send msg: " << msg; + m_socket->sendTextMessage(msg); + delete message; + DEBUG_LOG(m_logger) << "Sent message from queue, new size: " << m_messageQueue.size(); + } +} + +void ClientConnection::onSocketDisconnect() +{ + DEBUG_LOG(m_logger) << "Client disconnected ip: " << m_socket->peerAddress().toString() << ":" << m_socket->peerPort(); + exit(0); + emit disconnected(this); +} + +} diff --git a/Lts/Common/LtsWebSocket/client_connection.h b/Lts/Common/LtsWebSocket/client_connection.h new file mode 100755 index 000000000..e2f6e48d4 --- /dev/null +++ b/Lts/Common/LtsWebSocket/client_connection.h @@ -0,0 +1,70 @@ + + +#pragma once + +#include +#include + +#include "reply_message.h" +#include "logger.h" +#include + +class QWebSocket; + +namespace LtsWebSocket +{ + + + +class LTSWEBSOCKETSERVER_EXPORT ClientConnection + : public QThread + , public ReplyHandler +{ + Q_OBJECT +public: + + + explicit ClientConnection(QWebSocket *socket, QObject *parent = nullptr); + + + + + ~ClientConnection(); + + + + bool init(); + + + + void addReply(ReplyMessage *message) override; + + QWebSocket *webSocket() { return m_socket; } +signals: + + + void newTextMessage(ClientConnection *client, const QString &message); + + + + void disconnected(ClientConnection *client); + + +private slots: + void onNewTextMessageFromSocket(const QString &message); + void sendMessagesFromQueue(); + void onSocketDisconnect(); +private: + + + void sendMessages(); + +private: + QWebSocket *m_socket; + QQueue m_messageQueue; + + LOGGER_T m_logger; + + +}; +} diff --git a/Lts/Common/LtsWebSocket/client_message.cpp b/Lts/Common/LtsWebSocket/client_message.cpp new file mode 100755 index 000000000..cb6d93881 --- /dev/null +++ b/Lts/Common/LtsWebSocket/client_message.cpp @@ -0,0 +1,38 @@ + + +#include "client_message.h" + +#include "json_keys.h" + +#include + +namespace LtsWebSocket +{ + + + + +InvalidMessage::InvalidMessage(ReplyHandler *client, const QString &uuid, const QString &errorText) + : AbstractClientMessage(client, QString(), 1, uuid) + , m_errorText(errorText) +{} + +void InvalidMessage::process() +{ + ErrorMessage::ErrorData errorData; + + errorData.errorClass = 1; + errorData.errorMessage = m_errorText; + errorData.uuid = m_uuid; + + m_client->addReply(new ErrorMessage(errorData)); +} + +bool InvalidMessage::hasValidData() const +{ + + return true; +} + + +} diff --git a/Lts/Common/LtsWebSocket/client_message.h b/Lts/Common/LtsWebSocket/client_message.h new file mode 100755 index 000000000..fdd47d7df --- /dev/null +++ b/Lts/Common/LtsWebSocket/client_message.h @@ -0,0 +1,70 @@ + + +#pragma once + +#include +#include "reply_message.h" + +#include +#include + +namespace LtsWebSocket +{ + + + +class LTSWEBSOCKETCOMMON_EXPORT AbstractClientMessage +{ +public: + AbstractClientMessage(ReplyHandler *client, const QString &type, const int typeId, const QString &uuid, const QJsonValue &data = QJsonValue()) + : m_type(type) + , m_typeId(typeId) + , m_uuid(uuid) + , m_data(data) + , m_client(client) + {} + + virtual ~AbstractClientMessage() = default; + + + + virtual void process() = 0; + + + + virtual bool hasValidData() const = 0; + + + + void reply(ReplyMessage *reply) + { + m_client->addReply(reply); + } + +protected: + const QString m_type; + const int m_typeId; + const QString m_uuid; + const QJsonValue m_data; + ReplyHandler *m_client; + +}; + + + + + +class LTSWEBSOCKETCOMMON_EXPORT InvalidMessage final : public AbstractClientMessage +{ +public: + explicit InvalidMessage(ReplyHandler *client, const QString &uuid, const QString &errorText); + + void process() override; + bool hasValidData() const override; + +private: + QString m_errorText; + +}; + +} diff --git a/Lts/Common/LtsWebSocket/client_message_factory.cpp b/Lts/Common/LtsWebSocket/client_message_factory.cpp new file mode 100755 index 000000000..26a9c20cf --- /dev/null +++ b/Lts/Common/LtsWebSocket/client_message_factory.cpp @@ -0,0 +1,69 @@ + + + +#include "client_message_factory.h" + +#include "client_connection.h" +#include "json_keys.h" + +#include + +namespace LtsWebSocket +{ + + +QJsonParseError AbstractClientMessageFactory::parseMessage(const QString &message) +{ + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(message.toUtf8(), &error); + + if (error.error != QJsonParseError::NoError) + return error; + + QJsonObject obj = doc.object(); + + m_data = obj.value(JsonKeys::DATA()); + m_type = obj.value(JsonKeys::TYPE()).toString(); + m_typeId = obj.value(JsonKeys::TYPEID()).toInt(); + m_uuid = obj.value(JsonKeys::UUID_()).toString(); + + return error; +} + +AbstractClientMessage *AbstractClientMessageFactory::checkAttributes(ReplyHandler *client, + const QString &message) const +{ + if (!m_uuid.isEmpty() && !m_type.isEmpty()) + return nullptr; + if (m_uuid.isEmpty()) + { + WARNING_LOG(m_logger) << "Got message without uuid: " << message << "'"; + return new LtsWebSocket::InvalidMessage(client, QString(), QStringLiteral("Got message without uuid")); + } + WARNING_LOG(m_logger) << "Got message no type: " << message << "'"; + return new LtsWebSocket::InvalidMessage(client, m_uuid, QStringLiteral("Message has no type")); +} + +AbstractClientMessage *AbstractClientMessageFactory::checkResult(ReplyHandler *client, + const QString &message, + AbstractClientMessage *result) const +{ + if (result == nullptr) + { + WARNING_LOG(m_logger) << "Could not parse message: " << message; + result = new LtsWebSocket::InvalidMessage(client, m_uuid, QStringLiteral("Invalid type")); + } + + if (!result->hasValidData()) + { + WARNING_LOG(m_logger) << "Resulting JSon message has invalid data... " << message; + delete result; + result = new LtsWebSocket::InvalidMessage(client, m_uuid, QStringLiteral("Has invalid data part")); + } + + return result; +} + + + +} diff --git a/Lts/Common/LtsWebSocket/client_message_factory.h b/Lts/Common/LtsWebSocket/client_message_factory.h new file mode 100755 index 000000000..e262e704d --- /dev/null +++ b/Lts/Common/LtsWebSocket/client_message_factory.h @@ -0,0 +1,71 @@ + + +#pragma once + +#include "client_message.h" +#include "reply_message.h" +#include + +#include +#include + +namespace NGL +{ +class INGLLogger; +} +namespace LtsWebSocket +{ + + + +class LTSWEBSOCKETCOMMON_EXPORT AbstractClientMessageFactory +{ +public: + explicit AbstractClientMessageFactory(NGL::INGLLogger &logger) + : m_logger(logger) + {} + + virtual ~AbstractClientMessageFactory() {} + + + + QJsonParseError parseMessage(const QString &message); + + + + virtual AbstractClientMessage *createMessage(ReplyHandler *client, const QString &message) = 0; + +protected: + AbstractClientMessage *checkAttributes(ReplyHandler *client, + const QString &message) const; + AbstractClientMessage *checkResult(ReplyHandler *client, + const QString &message, + AbstractClientMessage *result) const; +protected: + QString m_type; + int m_typeId; + QString m_uuid; + QJsonValue m_data; + NGL::INGLLogger &m_logger; + +}; + +class PongMessage final : public ReplyMessage +{ +public: + static QString strPong() { return QStringLiteral("pong"); } + static int idPong() { return 2; } + explicit PongMessage(const QString &payload) + : ReplyMessage(strPong(), idPong(), QString()) + , m_payload(payload) + {} + + QString toJsonString() const override + { + return m_payload.isEmpty() ? strPong() : QStringLiteral("%1 %2").arg(strPong(), m_payload); + } +private: + QString m_payload; +}; + +} diff --git a/Lts/Common/LtsWebSocket/connection_handler.cpp b/Lts/Common/LtsWebSocket/connection_handler.cpp new file mode 100755 index 000000000..88425f6fa --- /dev/null +++ b/Lts/Common/LtsWebSocket/connection_handler.cpp @@ -0,0 +1,89 @@ + + +#include "connection_handler.h" + +#include "client_connection.h" + +#include + +using namespace LtsWebSocket; + +ConnectionHandler::ConnectionHandler(QObject *parent) + : QObject(parent) + , m_server(nullptr) + , m_logger(getLogger("ConnectionHandler")) +{} + +ConnectionHandler::~ConnectionHandler() +{ + for (auto connection : qAsConst(m_connections)) + connection->disconnect(this); +} + +bool ConnectionHandler::init(const QString &serviceName, unsigned short port) +{ + if (m_server != nullptr) + { + m_server->disconnect(); + delete m_server; + } + + m_server = new QWebSocketServer(QStringLiteral("RME-") + serviceName, QWebSocketServer::NonSecureMode, this); + + connect(m_server, &QWebSocketServer::newConnection, this, &ConnectionHandler::onNewConnection); + connect(m_server, &QWebSocketServer::serverError, this, &ConnectionHandler::onServerError); + + DEBUG_LOG(m_logger) << "Trying to listen "; + if (!m_server->listen(QHostAddress::Any, port)) + { + ERROR_LOG(m_logger) << "Not possible to listen to port " << port; + return false; + } + DEBUG_LOG(m_logger) << "... ok, port " << m_server->serverPort(); + + return true; +} + +void ConnectionHandler::sendToAllClients(ReplyMessage *message) +{ + for (auto connection : qAsConst(m_connections)) + connection->addReply(new ReplyMessage(*message)); + + delete message; +} + +void ConnectionHandler::onServerError(QWebSocketProtocol::CloseCode closeCode) +{ + ERROR_LOG(m_logger) << "Got server error with close code " << closeCode; +} + +void ConnectionHandler::onNewConnection() +{ + while (m_server->hasPendingConnections()) + { + QWebSocket *socket = m_server->nextPendingConnection(); + + ClientConnection *newConnection = new ClientConnection(socket, this); + connect(newConnection, &ClientConnection::destroyed, this, [this](QObject* o) + { + m_connections.remove(static_cast(o)); + }); + + if (!newConnection->init()) + { + ERROR_LOG(m_logger) << "Failed to initialise new client"; + delete newConnection; + newConnection = nullptr; + continue; + } + newConnection->start(); + + connect(newConnection, &ClientConnection::newTextMessage, this, &ConnectionHandler::newTextMessageFromClient); + connect(newConnection, &ClientConnection::disconnected, this, &ConnectionHandler::clientDisconnected); + connect(newConnection, &ClientConnection::finished, newConnection, &ClientConnection::deleteLater); + + m_connections.insert(newConnection); + emit clientConnected(newConnection); + INFO_LOG(m_logger) << "New client connected ip: " << socket->localAddress().toString() << ":" << socket->localPort(); + } +} diff --git a/Lts/Common/LtsWebSocket/connection_handler.h b/Lts/Common/LtsWebSocket/connection_handler.h new file mode 100755 index 000000000..1c7b23b8f --- /dev/null +++ b/Lts/Common/LtsWebSocket/connection_handler.h @@ -0,0 +1,64 @@ + + +#pragma once + +#include +#include +#include + +#include "logger.h" + +namespace LtsWebSocket +{ +class MessageHandler; +class ClientConnection; +class ReplyMessage; + + + +class ConnectionHandler + : public QObject +{ + Q_OBJECT +public: + + + explicit ConnectionHandler(QObject *parent = nullptr); + ~ConnectionHandler(); + + + + bool init(const QString &serviceName, unsigned short port); + + + + void sendToAllClients(ReplyMessage *message); + + + + unsigned short webSocketPort() { return m_server->serverPort(); } + +signals: + + + void newTextMessageFromClient(ClientConnection *client, const QString &message); + + + + void clientDisconnected(ClientConnection *client); + + + + void clientConnected(ClientConnection *client); + +private slots: + void onServerError(QWebSocketProtocol::CloseCode closeCode); + void onNewConnection(); + +private: + QWebSocketServer *m_server; + QSet m_connections; + LOGGER_T m_logger; + +}; +} diff --git a/Lts/Common/LtsWebSocket/data_manager_base_dl.h b/Lts/Common/LtsWebSocket/data_manager_base_dl.h new file mode 100755 index 000000000..d2abfd609 --- /dev/null +++ b/Lts/Common/LtsWebSocket/data_manager_base_dl.h @@ -0,0 +1,48 @@ + + +#pragma once +#include +#include + +#include "reply_message.h" + +namespace LtsWebSocket +{ + + +template +class DataManagerBaseDl +{ +protected: + + + explicit DataManagerBaseDl(const std::shared_ptr &dbInterface, NGL::INGLLogger &logger) + : m_logger(logger) + , m_dbInterface(dbInterface) + {} + + + + template + ReplyMessage *toBaseDataMessageFromDb(const QString &uuid) const + { + QVector dataVec; + + if (!m_dbInterface->select(dataVec)) + { + const QString errorMessage = QStringLiteral("Error reading data from database!"); + ERROR_LOG(m_logger) << errorMessage; + ErrorMessage::ErrorData errData; + errData.errorMessage = errorMessage; + errData.uuid = uuid; + return new ErrorMessage(errData); + } + + return new MessageType (m_dbInterface->template toJson(dataVec), uuid); + } + +protected: + NGL::INGLLogger &m_logger; + std::shared_ptr m_dbInterface; +}; +} diff --git a/Lts/Common/LtsWebSocket/json_keys.h b/Lts/Common/LtsWebSocket/json_keys.h new file mode 100755 index 000000000..b5c69c09c --- /dev/null +++ b/Lts/Common/LtsWebSocket/json_keys.h @@ -0,0 +1,26 @@ + + +#pragma once + +#include + +namespace LtsWebSocket +{ + + +namespace JsonKeys +{ + + inline QString DATA() { return QStringLiteral("data"); } + inline QString UUID_() { return QStringLiteral("uuid"); } + inline QString TYPE() { return QStringLiteral("type"); } + inline QString TYPEID() { return QStringLiteral("typeId"); } + + + inline QString ERROR_() { return QStringLiteral("error"); } + + + inline QString ERROR_MESSAGE() { return QStringLiteral("errorMessage"); } + inline QString ERROR_CLASS() { return QStringLiteral("errorClass"); } +} +} diff --git a/Lts/Common/LtsWebSocket/logger.cpp b/Lts/Common/LtsWebSocket/logger.cpp new file mode 100755 index 000000000..61d789a23 --- /dev/null +++ b/Lts/Common/LtsWebSocket/logger.cpp @@ -0,0 +1,32 @@ + + + +#include "logger.h" + +#include + +namespace LtsWebSocket +{ + +LOGGER_T getLogger(const char *name) +{ + static const char ns[] = "Lts.WebSocket."; + std::string loggerName; + const size_t len = strlen(name); + loggerName.reserve(sizeof(ns) + len); + loggerName.append(ns, sizeof(ns) - 1); + loggerName.append(name, len); + + try + { + return NGL::NGLLoggerBootstrap::getNGLRootLogger(). + getNGLLogger(loggerName); + } + catch (const std::exception &ex) + { + fprintf(stderr, "Error retrieving logger: %s", ex.what()); + abort(); + } +} + +} diff --git a/Lts/Common/LtsWebSocket/logger.h b/Lts/Common/LtsWebSocket/logger.h new file mode 100755 index 000000000..0e1cb1771 --- /dev/null +++ b/Lts/Common/LtsWebSocket/logger.h @@ -0,0 +1,18 @@ + + + +#pragma once + +#include +#include + +namespace LtsWebSocket +{ +typedef NGL::INGLLogger &LOGGER_T; + +LTSWEBSOCKETCOMMON_EXPORT LOGGER_T getLogger(const char *name); +inline LOGGER_T getLogger(const QString &loggerName) +{ + return getLogger(loggerName.toUtf8().constData()); +} +} diff --git a/Lts/Common/LtsWebSocket/lts_web_socket_client.cpp b/Lts/Common/LtsWebSocket/lts_web_socket_client.cpp new file mode 100755 index 000000000..31d1d13b6 --- /dev/null +++ b/Lts/Common/LtsWebSocket/lts_web_socket_client.cpp @@ -0,0 +1,202 @@ + + +#include "lts_web_socket_client.h" + +#include +#include +#include + +#include "client_connection.h" + +namespace LtsWebSocket +{ +class WebSocketHelper : public QObject +{ + Q_OBJECT +public: + WebSocketHelper(AbstractClientMessageFactory *messageFactory, + LtsWebSocketClient *webSocketClient, + NGL::INGLLogger &logger); + ~WebSocketHelper(); + +public Q_SLOTS: + void doOpen(const QString &hostname, const unsigned short port); + void doClose(); + void doSendTextMessage(const QString &msg); + void onConnected(); + void onDisconnected(); + void onError(QAbstractSocket::SocketError error); + void parseMessage(const QString &message); +Q_SIGNALS: + void messageParsed(AbstractClientMessage *clientMessage); + void connected(); + void disconnected(); + void dataReceived(); + void error(QAbstractSocket::SocketError); +private: + AbstractClientMessageFactory *m_messageFactory; + LtsWebSocketClient *m_webSocketClient; + QWebSocket *m_socket; + NGL::INGLLogger &m_logger; +}; + +WebSocketHelper::WebSocketHelper(AbstractClientMessageFactory *messageFactory, + LtsWebSocketClient *webSocketClient, + NGL::INGLLogger &logger) + : QObject() + , m_messageFactory(messageFactory) + , m_webSocketClient(webSocketClient) + , m_socket(new QWebSocket(QString(), QWebSocketProtocol::VersionLatest, this)) + , m_logger(logger) +{ + connect(m_socket, &QWebSocket::textMessageReceived, this, &WebSocketHelper::parseMessage); + connect(m_socket, &QWebSocket::textFrameReceived, this, &WebSocketHelper::dataReceived); + connect(m_socket, &QWebSocket::connected, this, &WebSocketHelper::onConnected); + connect(m_socket, &QWebSocket::disconnected, this, &WebSocketHelper::onDisconnected); + connect(m_socket, QOverload::of(&QWebSocket::error), this, &WebSocketHelper::error); +} + +WebSocketHelper::~WebSocketHelper() +{ + m_socket->close(); + delete m_socket; +} + +void WebSocketHelper::doOpen(const QString &hostname, const unsigned short port) +{ + const QUrl url(QStringLiteral("ws://%1:%2/").arg(hostname).arg(port)); + + if (m_socket->state() == QAbstractSocket::SocketState::ConnectedState) + { + if (m_socket->requestUrl() != url) + doClose(); + else + { + INFO_LOG(m_logger) << "Web socket to url: " << url.toDisplayString() << " is already open."; + return; + } + } + + INFO_LOG(m_logger) << "Opening web socket to url: " << url.toDisplayString(); + m_socket->open(url); +} + +void WebSocketHelper::doClose() +{ + INFO_LOG(m_logger) << "Closing web socket to url:" << m_socket->requestUrl().toDisplayString(); + m_socket->close(); +} + +void WebSocketHelper::doSendTextMessage(const QString &msg) +{ + if (!m_socket || m_socket->state() != QAbstractSocket::ConnectedState) + { + ERROR_LOG(m_logger) << "Message could not be send, socket is not connected."; + return; + } + DEBUG_LOG(m_logger) << "Sending message: " << msg; + m_socket->sendTextMessage(msg); +} + +void WebSocketHelper::onConnected() +{ + INFO_LOG(m_logger) << "Connected to server url: " << m_socket->requestUrl().toDisplayString() + << " using local port: " << m_socket->localPort(); + emit connected(); +} + +void WebSocketHelper::onDisconnected() +{ + INFO_LOG(m_logger) << "Disconnected from server url: " << m_socket->requestUrl().toDisplayString(); + emit disconnected(); +} + +void WebSocketHelper::onError(QAbstractSocket::SocketError error) +{ + ERROR_LOG(m_logger) << "Error occured: " << error << " closing socket."; + m_socket->close(); +} + +void WebSocketHelper::parseMessage(const QString &message) +{ + const QString pingMsg(QStringLiteral("ping")); + if (message.startsWith(pingMsg)) + { + doSendTextMessage(PongMessage(message.mid(pingMsg.size())).toJsonString()); + return; + } + + AbstractClientMessage *clientMessage = nullptr; + auto result = m_messageFactory->parseMessage(message); + if (QJsonParseError::NoError == result.error) + clientMessage = m_messageFactory->createMessage(m_webSocketClient, message); + else + { + WARNING_LOG(m_logger) << "Could not parse incoming message: '" << message + << "', error: " << result.errorString(); + clientMessage = new InvalidMessage(m_webSocketClient, QString(), + QStringLiteral("Got message which is not JSON conform (%1)").arg(result.errorString())); + } + if (clientMessage) + emit messageParsed(clientMessage); +} + + + + +LtsWebSocketClient::LtsWebSocketClient(AbstractClientMessageFactory *messageFactory, QObject *parent) + : QObject(parent) + , m_messageFactory(messageFactory) + , m_webSocketHelperThread(new QThread(this)) + , m_logger(getLogger("LtsWebSocketClient")) +{ + m_webSocketHelper = new WebSocketHelper(m_messageFactory, this, m_logger); + m_webSocketHelper->moveToThread(m_webSocketHelperThread); + m_webSocketHelperThread->start(); + + connect(m_webSocketHelper, &WebSocketHelper::connected, this, &LtsWebSocketClient::connected); + connect(m_webSocketHelper, &WebSocketHelper::disconnected, this, &LtsWebSocketClient::disconnected); + connect(m_webSocketHelper, &WebSocketHelper::dataReceived, this, &LtsWebSocketClient::dataReceived); + connect(m_webSocketHelper, &WebSocketHelper::messageParsed, this, &LtsWebSocketClient::onNewClientMessage); + + connect(this, &LtsWebSocketClient::onOpen, m_webSocketHelper, &WebSocketHelper::doOpen); + connect(this, &LtsWebSocketClient::onClose, m_webSocketHelper, &WebSocketHelper::doClose); + connect(this, &LtsWebSocketClient::onSendTextMessage, m_webSocketHelper, &WebSocketHelper::doSendTextMessage); +} + +LtsWebSocketClient::~LtsWebSocketClient() +{ + m_webSocketHelper->disconnect(this); + m_webSocketHelperThread->quit(); + m_webSocketHelperThread->wait(); + delete m_webSocketHelper; +} + +void LtsWebSocketClient::open(const QString &hostname, const unsigned short port) +{ + emit onOpen(hostname, port); +} + +void LtsWebSocketClient::close() +{ + emit onClose(); +} + +void LtsWebSocketClient::addReply(ReplyMessage *message) +{ + const QString msg(message->toJsonString()); + emit onSendTextMessage(msg); + delete message; +} + +void LtsWebSocketClient::onNewClientMessage(AbstractClientMessage *clientMessage) +{ + if (!clientMessage) + return; + clientMessage->process(); + delete clientMessage; +} + +} + +#include "lts_web_socket_client.moc" diff --git a/Lts/Common/LtsWebSocket/lts_web_socket_client.h b/Lts/Common/LtsWebSocket/lts_web_socket_client.h new file mode 100755 index 000000000..e61fa3269 --- /dev/null +++ b/Lts/Common/LtsWebSocket/lts_web_socket_client.h @@ -0,0 +1,67 @@ + + +#pragma once + +#include +#include + +#include +#include "client_message_factory.h" +#include "logger.h" + + +class QThread; +namespace LtsWebSocket +{ +class WebSocketHelper; + + + +class LTSWEBSOCKETCLIENT_EXPORT LtsWebSocketClient + : public QObject + , public ReplyHandler +{ + Q_OBJECT +public: + + + explicit LtsWebSocketClient(AbstractClientMessageFactory *messageFactory, QObject *parent = nullptr); + ~LtsWebSocketClient(); + + + + void open(const QString &hostname, const unsigned short port); + + + + void close(); + + + + void addReply(ReplyMessage *message) override; + +signals: + void connected(); + void disconnected(); + void dataReceived(); + +private slots: + + + void onNewClientMessage(AbstractClientMessage *clientMessage); + +signals: + void onOpen(const QString &hostname, const unsigned short port); + void onClose(); + void onSendTextMessage(const QString &msg); + +private: + AbstractClientMessageFactory *m_messageFactory; + + QThread *m_webSocketHelperThread; + WebSocketHelper *m_webSocketHelper; + + + LOGGER_T m_logger; +}; +} diff --git a/Lts/Common/LtsWebSocket/lts_web_socket_server.cpp b/Lts/Common/LtsWebSocket/lts_web_socket_server.cpp new file mode 100755 index 000000000..ff38eec0a --- /dev/null +++ b/Lts/Common/LtsWebSocket/lts_web_socket_server.cpp @@ -0,0 +1,119 @@ + + +#include "lts_web_socket_server.h" + +#include "connection_handler.h" +#include "client_connection.h" +#include + +namespace LtsWebSocket +{ + +LtsWebSocketServer::LtsWebSocketServer(AbstractClientMessageFactory *messageFactory, QObject *parent) + : QObject(parent) + , m_queueTimer(new QTimer(this)) + , m_messageFactory(messageFactory) + , m_connectionHandler(nullptr) + , m_logger(getLogger("LtsWebSocketServer")) +{ + connect(m_queueTimer, &QTimer::timeout, + this, &LtsWebSocketServer::onQueueTimerTimeout); + + m_queueTimer->setSingleShot(true); +} + +LtsWebSocketServer::~LtsWebSocketServer() = default; + +bool LtsWebSocketServer::init(const QString &serviceName, const unsigned short port) +{ + m_connectionHandler.reset(new ConnectionHandler(this)); + + if (!m_connectionHandler->init(serviceName, port)) + { + ERROR_LOG(m_logger) << "Could not init m_connectionHandler with servicename: " << serviceName << ((port == 0) ? QStringLiteral(" random port") : QStringLiteral(" port: ") + QString::number(port)); + m_connectionHandler.reset(); + return false; + } + + connect(m_connectionHandler.get(), &ConnectionHandler::newTextMessageFromClient, this, &LtsWebSocketServer::onNewTextMessage); + connect(m_connectionHandler.get(), &ConnectionHandler::clientDisconnected, this, &LtsWebSocketServer::clientDisconnected); + connect(m_connectionHandler.get(), &ConnectionHandler::clientConnected, this, &LtsWebSocketServer::clientConnected); + + m_queueTimer->start(m_messageDelay); + + return true; +} + +bool LtsWebSocketServer::init(const QString &serviceName, const QPair &portRange) +{ + if (portRange.first < 1025 || portRange.first > portRange.second) + return init(serviceName); + + for (quint16 currentPort = portRange.first; currentPort <= portRange.second; ++ currentPort) + { + if (init(serviceName, currentPort)) + return true; + } + + return init(serviceName); +} + +void LtsWebSocketServer::sendToAllClients(ReplyMessage *message) +{ + DEBUG_LOG(m_logger) << "Sending message to all clients: " << message->toJsonString(); + m_connectionHandler->sendToAllClients(message); +} + +unsigned short LtsWebSocketServer::webSocketPort() const +{ + return m_connectionHandler->webSocketPort(); +} + +void LtsWebSocketServer::setMessageDelay(qint32 delay) +{ + m_messageDelay = delay; +} + +void LtsWebSocketServer::onNewTextMessage(ReplyHandler *client, const QString &message) +{ + const QString pingMsg(QStringLiteral("ping")); + if (message.startsWith(pingMsg)) + { + client->addReply(new PongMessage(message.mid(pingMsg.size()))); + return; + } + DEBUG_LOG(m_logger) << "Got new message: " << message; + + m_messageQueue.append({client, message}); + + if (!m_queueTimer->isActive()) + m_queueTimer->start(m_messageDelay); +} + +void LtsWebSocketServer::onQueueTimerTimeout() +{ + if (m_messageQueue.isEmpty()) + return; + + MessageCredentials credentials = m_messageQueue.takeFirst(); + AbstractClientMessage *clientMessage = nullptr; + auto result = m_messageFactory->parseMessage(credentials.message); + + if (QJsonParseError::NoError == result.error) + clientMessage = m_messageFactory->createMessage(credentials.client, credentials.message); + else + { + WARNING_LOG(m_logger) << "Could not parse incoming message: '" << credentials.message << "', error: " << result.errorString(); + + clientMessage = new InvalidMessage(credentials.client, QString(), QStringLiteral("Got message which is not JSON conform")); + } + + if (clientMessage) + clientMessage->process(); + + delete clientMessage; + + m_queueTimer->start(m_messageDelay); +} + +} diff --git a/Lts/Common/LtsWebSocket/lts_web_socket_server.h b/Lts/Common/LtsWebSocket/lts_web_socket_server.h new file mode 100755 index 000000000..1534e5143 --- /dev/null +++ b/Lts/Common/LtsWebSocket/lts_web_socket_server.h @@ -0,0 +1,88 @@ + + +#pragma once + +#include + +#include +#include "client_message_factory.h" +#include "logger.h" +#include +#include + +class QTimer; + +namespace LtsWebSocket +{ +class ConnectionHandler; +class ClientConnection; + + + +class LTSWEBSOCKETSERVER_EXPORT LtsWebSocketServer + : public QObject +{ + Q_OBJECT +public: + + + explicit LtsWebSocketServer(AbstractClientMessageFactory *messageFactory, QObject *parent = nullptr); + virtual ~LtsWebSocketServer(); + + + + bool init(const QString &serviceName, const unsigned short port = 0); + + + + bool init(const QString &serviceName, const QPair &portRange); + + + + void sendToAllClients(ReplyMessage *message); + + + + unsigned short webSocketPort() const; + + + + void setMessageDelay(qint32 delay); +signals: + + + void clientDisconnected(ClientConnection *client); + + + + void clientConnected(ClientConnection *client); + +private slots: + + + void onNewTextMessage(ReplyHandler *client, const QString &message); + + + + void onQueueTimerTimeout(); + +private: + struct MessageCredentials + { + ReplyHandler *client {nullptr}; + QString message; + + MessageCredentials(ReplyHandler *client, const QString &message) + : client(client) + , message(message) + {} + }; + + QQueue m_messageQueue; + QTimer *m_queueTimer { nullptr }; + AbstractClientMessageFactory *m_messageFactory { nullptr }; + std::unique_ptr m_connectionHandler; + qint32 m_messageDelay { 0 }; + LOGGER_T m_logger; +}; +} diff --git a/Lts/Common/LtsWebSocket/message_transmitter.h b/Lts/Common/LtsWebSocket/message_transmitter.h new file mode 100755 index 000000000..5c0931cae --- /dev/null +++ b/Lts/Common/LtsWebSocket/message_transmitter.h @@ -0,0 +1,17 @@ + + + +namespace LtsWebSocketServer +{ +class ReplyMessage; + + + +class MessageHandler +{ +public: + + + virtual void addMessage(ReplyMessage *message) = 0; +}; +} diff --git a/Lts/Common/LtsWebSocket/reply_message.cpp b/Lts/Common/LtsWebSocket/reply_message.cpp new file mode 100755 index 000000000..842e9134c --- /dev/null +++ b/Lts/Common/LtsWebSocket/reply_message.cpp @@ -0,0 +1,41 @@ + + + +#include "reply_message.h" + +#include "json_keys.h" + +#include + +namespace LtsWebSocket +{ + + +QString ReplyMessage::toJsonString() const +{ + return QString::fromUtf8(QJsonDocument(m_jsonRepresentation).toJson(QJsonDocument::Compact)); +} + +ReplyMessage::ReplyMessage(const QString &type, const int typeId, const QString &uuid) +{ + m_jsonRepresentation.insert(JsonKeys::TYPE(), type); + m_jsonRepresentation.insert(JsonKeys::TYPEID(), typeId); + + if (!uuid.isEmpty()) + m_jsonRepresentation.insert(JsonKeys::UUID_(), uuid); +} + + + +ErrorMessage::ErrorMessage(const ErrorMessage::ErrorData &data) + : ReplyMessage(JsonKeys::ERROR_(), -1, QUuid::createUuid().toString()) +{ + m_jsonRepresentation.insert(JsonKeys::DATA(), + QJsonObject({{JsonKeys::UUID_(), data.uuid}, + {JsonKeys::ERROR_CLASS(), data.errorClass}, + {JsonKeys::ERROR_MESSAGE(), data.errorMessage} + })); +} + + +} diff --git a/Lts/Common/LtsWebSocket/reply_message.h b/Lts/Common/LtsWebSocket/reply_message.h new file mode 100755 index 000000000..197b993c1 --- /dev/null +++ b/Lts/Common/LtsWebSocket/reply_message.h @@ -0,0 +1,70 @@ + + +#pragma once + +#include + +#include +#include +#include + +namespace LtsWebSocket +{ + + + +class LTSWEBSOCKETCOMMON_EXPORT ReplyMessage +{ +public: + + + virtual ~ReplyMessage() = default; + + + + virtual QString toJsonString() const; + + QJsonObject json() const { return m_jsonRepresentation; } + +protected: + ReplyMessage() = default; + ReplyMessage(const QString &type, const int typeId, const QString &uuid); + +protected: + QJsonObject m_jsonRepresentation; +}; + + + +class LTSWEBSOCKETCOMMON_EXPORT ReplyHandler +{ +public: + virtual ~ReplyHandler() = default; + + + virtual void addReply(ReplyMessage *message) = 0; +}; + + + + + +class LTSWEBSOCKETCOMMON_EXPORT ErrorMessage final + : public ReplyMessage +{ +public: + + + struct ErrorData + { + int errorClass = 0; + QString uuid; + QString errorMessage; + }; + + + + explicit ErrorMessage(const ErrorData &data); +}; + +} diff --git a/Lts/Common/LtsWidgets/CMakeLists.txt b/Lts/Common/LtsWidgets/CMakeLists.txt new file mode 100755 index 000000000..47b50f47e --- /dev/null +++ b/Lts/Common/LtsWidgets/CMakeLists.txt @@ -0,0 +1,210 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +# +# Lts Widgets library +# + +SET(ltswidgets_SRCS + #lts_abstract_table_model.h + #lts_checkable_table_model.h + lts_abstract_sequence_widget.cpp + lts_abstract_sequence_widget.h + lts_background_widget.cpp + lts_background_widget.h + lts_base.h + lts_base_layout_widget.cpp + lts_base_layout_widget.h + lts_column_filter_proxy_model.cpp + lts_column_filter_proxy_model.h + lts_combo_box.cpp + lts_combo_box.h + lts_selection_popup.cpp + lts_selection_popup.h + lts_drag_icon.cpp + lts_drag_icon.h + #lts_drop_handler.h + lts_elided_label.cpp + lts_elided_label.h + lts_events.cpp + lts_events.h + lts_frameless_dialog.cpp + lts_frameless_dialog.h + lts_grid_layout.cpp + lts_grid_layout.h + lts_height_plot_widget.cpp + lts_height_plot_widget.h + lts_message_dialog.cpp + lts_message_dialog.h + lts_message_dialog_helper.cpp + lts_message_dialog_helper.h + lts_profile_button.cpp + lts_profile_button.h + lts_progress_dialog_helper.cpp + lts_progress_dialog_helper.h + lts_splitter.cpp + lts_splitter.h + lts_splitter_handle.cpp + lts_splitter_handle.h + lts_tab_bar.cpp + lts_tab_bar.h + lts_tab_widget.cpp + lts_tab_widget.h + lts_tab_sequence_widget.cpp + lts_tab_sequence_widget.h + lts_tool_bar.cpp + lts_tool_bar.h + lts_tool_box.cpp + lts_tool_box.h + lts_tool_bar_area.cpp + lts_tool_bar_area.h + lts_dialog.cpp + lts_dialog.h + lts_dialog_helper.cpp + lts_dialog_helper.h + lts_dialog_button_box.cpp + lts_dialog_button_box.h + lts_operator_menu.cpp + lts_operator_menu.h + lts_image_editor.cpp + lts_image_editor.h + lts_led_widget.cpp + lts_led_widget.h + ui_requirement.cpp + ui_requirement.h + #lts_widgets_export.h + lts_picture_view.cpp + lts_picture_view.h + lts_global_wait_dialog.cpp + lts_global_wait_dialog.h + lts_wait_dialog.cpp + lts_wait_dialog.h + lts_splash.cpp + lts_splash.h + lts_info_dialog.cpp + lts_info_dialog.h + lts_spinbox.cpp + lts_spinbox.h + lts_multi_range_spinbox.cpp + lts_multi_range_spinbox.h + lts_text_editor.cpp + lts_text_editor.h + lts_text_edit_dialog.h + lts_text_edit_dialog.cpp + lts_range_slider.cpp + lts_range_slider.h + lts_notification_box.cpp + lts_notification_box.h + lts_msec_date_time_edit.cpp + lts_msec_date_time_edit.h + lts_bitfield_widget.cpp + lts_bitfield_widget.h + lts_overlay_loading.cpp + lts_angle_edit.cpp + lts_angle_edit.h + lts_text_editor2.h + lts_text_editor2.cpp + + LtsImageCropper/lts_image_cropper.cpp +) + + +#if (TABLET_MODE) +# LIST(APPEND ltswidgets_SRCS +# lts_date_time_edit_touch.h +# lts_date_time_edit_touch.cpp +# ) +# NGL_WRAP_UI(ltswidgets_SRCS +# lts_date_time_edit_touch.ui +# ) +#else() + LIST(APPEND ltswidgets_SRCS + lts_date_time_edit.h + lts_date_time_edit.cpp + lts_date_time_edit_p.h + lts_date_time_edit_p.cpp + ) +#endif() + + +NGL_WRAP_UI(ltswidgets_SRCS + lts_selection_popup.ui + lts_base_layout.ui + lts_tool_bar_area.ui + lts_overlay_loading.ui + lts_dialog_button_box.ui + lts_operator_menu.ui + lts_dialog_helper.ui + lts_info_dialog.ui + lts_text_editor.ui + lts_notification_box.ui + lts_angle_edit.ui + lts_date_time_edit_touch.ui +) + +NGL_ADD_BIG_RESOURCES(ltswidgets_SRCS resources/lts_resources.qrc) +NGL_ADD_LIBRARY(WS TOOLSWS TOOLSSV LtsWidgets SHARED ${ltswidgets_SRCS}) + +NGL_TARGET_INCLUDE_DIRECTORIES(LtsWidgets + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) + +NGL_TARGET_LINK_LIBRARIES(LtsWidgets + PRIVATE + Qt5::Network # for QHostInfo - for portability + Qt5::Svg + ngltools + ngltranslator + TabBarTabStyle + LtsSessionData + LtsUtilsSettings + PUBLIC + LtsUtilsCore + Qt5::Core + Qt5::Widgets + Qt5::Gui +) + +NGL_INSTALL_TARGET(LtsWidgets) + +# +# Designer-Plugin +# +SET(ltswidgetsplugin_SRCS ltswidgetsplugin.cpp ltswidgetsplugin.json) +NGL_ADD_LIBRARY(WS ltswidgetsplugin MODULE ${ltswidgetsplugin_SRCS}) +NGL_TARGET_LINK_LIBRARIES(ltswidgetsplugin LtsWidgets ltsCoordWidget LtsDataView LtsFileDialog LtsReportDisplay svcFlmCommon) +NGL_SET_TARGET_PROPERTIES(ltswidgetsplugin PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/designer) +NGL_INSTALL_TARGET(ltswidgetsplugin DESTINATION lib/designer COMPONENT Devel) + +IF(BUILD_TESTING) + # picture_editing_test + SET(pet_SRCS + tests/lts_picture_editing.cpp + tests/lts_picture_editing.h + tests/lts_picture_editing_main.cpp + ) + NGL_WRAP_UI(pet_SRCS + tests/lts_picture_editing.ui + ) + NGL_ADD_EXECUTABLE(WS NO_TRANSLATION picture_editing_test ${pet_SRCS}) + NGL_TARGET_LINK_LIBRARIES(picture_editing_test + LtsWidgets + ) + + +# # LtsWidgetsTest +# SET(src_test +# tests/main.cpp +# tests/window.h +# tests/window.cpp +# ) + +# NGL_ADD_EXECUTABLE(WS NO_TRANSLATION LtsWidgetsTest ${src_test}) +# NGL_TARGET_LINK_LIBRARIES(LtsWidgetsTest +# LtsWidgets +# ) +ENDIF() diff --git a/Lts/Common/LtsWidgets/CMakeLists_rc.txt b/Lts/Common/LtsWidgets/CMakeLists_rc.txt new file mode 100755 index 000000000..aa27b36e6 --- /dev/null +++ b/Lts/Common/LtsWidgets/CMakeLists_rc.txt @@ -0,0 +1,208 @@ +# +# $Id$ +# $HeadURL$ +# $Author$ +# + +# +# Lts Widgets library +# + +SET(ltswidgets_SRCS + #lts_abstract_table_model.h + #lts_checkable_table_model.h + lts_abstract_sequence_widget.cpp + lts_abstract_sequence_widget.h + lts_background_widget.cpp + lts_background_widget.h + lts_base.h + lts_base_layout_widget.cpp + lts_base_layout_widget.h + lts_column_filter_proxy_model.cpp + lts_column_filter_proxy_model.h + lts_combo_box.cpp + lts_combo_box.h + lts_selection_popup.cpp + lts_selection_popup.h + lts_drag_icon.cpp + lts_drag_icon.h + #lts_drop_handler.h + lts_elided_label.cpp + lts_elided_label.h + lts_events.cpp + lts_events.h + lts_frameless_dialog.cpp + lts_frameless_dialog.h + lts_grid_layout.cpp + lts_grid_layout.h + lts_height_plot_widget.cpp + lts_height_plot_widget.h + lts_message_dialog.cpp + lts_message_dialog.h + lts_message_dialog_helper.cpp + lts_message_dialog_helper.h + lts_profile_button.cpp + lts_profile_button.h + lts_progress_dialog_helper.cpp + lts_progress_dialog_helper.h + lts_splitter.cpp + lts_splitter.h + lts_splitter_handle.cpp + lts_splitter_handle.h + lts_tab_bar.cpp + lts_tab_bar.h + lts_tab_widget.cpp + lts_tab_widget.h + lts_tab_sequence_widget.cpp + lts_tab_sequence_widget.h + lts_tool_bar.cpp + lts_tool_bar.h + lts_tool_box.cpp + lts_tool_box.h + lts_tool_bar_area.cpp + lts_tool_bar_area.h + lts_dialog.cpp + lts_dialog.h + lts_dialog_helper.cpp + lts_dialog_helper.h + lts_dialog_button_box.cpp + lts_dialog_button_box.h + lts_operator_menu.cpp + lts_operator_menu.h + lts_image_editor.cpp + lts_image_editor.h + lts_led_widget.cpp + lts_led_widget.h + ui_requirement.cpp + ui_requirement.h + #lts_widgets_export.h + lts_picture_view.cpp + lts_picture_view.h + lts_global_wait_dialog.cpp + lts_global_wait_dialog.h + lts_wait_dialog.cpp + lts_wait_dialog.h + lts_splash.cpp + lts_splash.h + lts_info_dialog.cpp + lts_info_dialog.h + lts_spinbox.cpp + lts_spinbox.h + lts_multi_range_spinbox.cpp + lts_multi_range_spinbox.h + lts_text_editor.cpp + lts_text_editor.h + lts_text_edit_dialog.h + lts_text_edit_dialog.cpp + lts_range_slider.cpp + lts_range_slider.h + lts_notification_box.cpp + lts_notification_box.h + lts_msec_date_time_edit.cpp + lts_msec_date_time_edit.h + lts_bitfield_widget.cpp + lts_bitfield_widget.h + lts_overlay_loading.cpp + lts_angle_edit.cpp + lts_angle_edit.h + + LtsImageCropper/lts_image_cropper.cpp +) + + +#if (TABLET_MODE) +# LIST(APPEND ltswidgets_SRCS +# lts_date_time_edit_touch.h +# lts_date_time_edit_touch.cpp +# ) +# NGL_WRAP_UI(ltswidgets_SRCS +# lts_date_time_edit_touch.ui +# ) +#else() + LIST(APPEND ltswidgets_SRCS + lts_date_time_edit.h + lts_date_time_edit.cpp + lts_date_time_edit_p.h + lts_date_time_edit_p.cpp + ) +#endif() + + +NGL_WRAP_UI(ltswidgets_SRCS + lts_selection_popup.ui + lts_base_layout.ui + lts_tool_bar_area.ui + lts_overlay_loading.ui + lts_dialog_button_box.ui + lts_operator_menu.ui + lts_dialog_helper.ui + lts_info_dialog.ui + lts_text_editor.ui + lts_notification_box.ui + lts_angle_edit.ui + lts_date_time_edit_touch.ui +) + +NGL_ADD_BIG_RESOURCES(ltswidgets_SRCS resources/lts_resources.qrc) +NGL_ADD_LIBRARY(WS TOOLSWS TOOLSSV LtsWidgets SHARED ${ltswidgets_SRCS}) + +NGL_TARGET_INCLUDE_DIRECTORIES(LtsWidgets + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../ +) + +NGL_TARGET_LINK_LIBRARIES(LtsWidgets + PRIVATE + Qt5::Network # for QHostInfo - for portability + Qt5::Svg + ngltools + ngltranslator + TabBarTabStyle + LtsSessionData + LtsUtilsSettings + PUBLIC + LtsUtilsCore + Qt5::Core + Qt5::Widgets + Qt5::Gui +) + +NGL_INSTALL_TARGET(LtsWidgets) + +# +# Designer-Plugin +# +SET(ltswidgetsplugin_SRCS ltswidgetsplugin.cpp ltswidgetsplugin.json) +NGL_ADD_LIBRARY(WS ltswidgetsplugin MODULE ${ltswidgetsplugin_SRCS}) +NGL_TARGET_LINK_LIBRARIES(ltswidgetsplugin LtsWidgets ltsCoordWidget LtsDataView LtsFileDialog LtsReportDisplay svcFlmCommon) +NGL_SET_TARGET_PROPERTIES(ltswidgetsplugin PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/designer) +NGL_INSTALL_TARGET(ltswidgetsplugin DESTINATION lib/designer COMPONENT Devel) + +IF(BUILD_TESTING) + # picture_editing_test + SET(pet_SRCS + tests/lts_picture_editing.cpp + tests/lts_picture_editing.h + tests/lts_picture_editing_main.cpp + ) + NGL_WRAP_UI(pet_SRCS + tests/lts_picture_editing.ui + ) + NGL_ADD_EXECUTABLE(WS NO_TRANSLATION picture_editing_test ${pet_SRCS}) + NGL_TARGET_LINK_LIBRARIES(picture_editing_test + LtsWidgets + ) + + +# # LtsWidgetsTest +# SET(src_test +# tests/main.cpp +# tests/window.h +# tests/window.cpp +# ) + +# NGL_ADD_EXECUTABLE(WS NO_TRANSLATION LtsWidgetsTest ${src_test}) +# NGL_TARGET_LINK_LIBRARIES(LtsWidgetsTest +# LtsWidgets +# ) +ENDIF() diff --git a/Lts/Common/LtsWidgets/LtsImageCropper/lts_image_cropper.cpp b/Lts/Common/LtsWidgets/LtsImageCropper/lts_image_cropper.cpp new file mode 100755 index 000000000..5773b96ff --- /dev/null +++ b/Lts/Common/LtsWidgets/LtsImageCropper/lts_image_cropper.cpp @@ -0,0 +1,131 @@ + + + +#include "lts_image_cropper.h" +#include +#include +#include + +namespace LtsWidgets +{ + +LtsImageCropper::LtsImageCropper(QWidget *parent, const QSize &size, const QPixmap &pixmap) + : QGraphicsView(parent) + , m_scene(new QGraphicsScene(this)) + , m_maxScale(2.0f) + , m_multiplier(1.0f) +{ + if(!size.isNull()) + setFixedSize(size); + + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setFrameStyle(QFrame::NoFrame); + setDragMode(QGraphicsView::ScrollHandDrag); + setTransformationAnchor(QGraphicsView::AnchorViewCenter); + + m_scene->setBackgroundBrush(Qt::Dense6Pattern); + setScene(m_scene); + + setPixmap(pixmap); +} + +LtsImageCropper::~LtsImageCropper() +{ + delete m_pixmapItem; +} + +void LtsImageCropper::setPixmap(const QPixmap &pixmap) +{ + if(pixmap.isNull()) + return; + + delete m_pixmapItem; + + m_pixmapItem = new QGraphicsPixmapItem(pixmap); + m_scene->addItem(m_pixmapItem); + + setTransform(QTransform()); + m_currentZoom = 1.0; + + + double sx = static_cast(size().height()) / pixmap.height(); + double sy = static_cast(size().width()) / pixmap.width(); + if(sx > 1 ) + scale(sx,sx); + if(sy > 1) + scale(sy,sy); + + + double ratioWidth = 1.0 * size().width() / pixmap.width(); + double ratioHeight = 1.0 * size().height() / pixmap.height(); + m_minScale = qMax(ratioHeight,ratioWidth); + + calculateScaleStep(); + + + m_scene->setSceneRect(pixmap.rect()); + centerOn(m_scene->sceneRect().center()); +} + +void LtsImageCropper::setImage(const QImage &image) +{ + setPixmap(QPixmap::fromImage(image)); +} + +void LtsImageCropper::setMultiplier(double multiplier) +{ + m_multiplier = multiplier; + calculateScaleStep(); +} + +void LtsImageCropper::calculateScaleStep() +{ + m_stepScale = (m_maxScale - m_minScale) / 100.0; +} + +QImage LtsImageCropper::image() +{ + return grab().toImage(); +} + +void LtsImageCropper::wheelEvent(QWheelEvent *event) +{ + if(!m_pixmapItem) + return; + + auto value = event->pixelDelta(); + if(value.isNull()) + value = event->angleDelta() / 15.0; + + + double scrollZoom = m_stepScale * value.y() * m_multiplier / 8.0; + + m_currentZoom += scrollZoom; + + if(m_currentZoom > m_maxScale) + m_currentZoom = m_maxScale; + else if (m_currentZoom < m_minScale) + m_currentZoom = m_minScale; + + + setTransform(QTransform(m_currentZoom, transform().m12(), transform().m13(), + transform().m21(), m_currentZoom, transform().m23(), + transform().m31(), transform().m32(), 1)); +} + +void LtsImageCropper::mousePressEvent(QMouseEvent *event) +{ + QApplication::setOverrideCursor(Qt::DragMoveCursor); + update(); + QGraphicsView::mousePressEvent(event); +} + +void LtsImageCropper::mouseReleaseEvent(QMouseEvent *event) +{ + QApplication::restoreOverrideCursor(); + update(); + QGraphicsView::mouseReleaseEvent(event); +} + +} diff --git a/Lts/Common/LtsWidgets/LtsImageCropper/lts_image_cropper.h b/Lts/Common/LtsWidgets/LtsImageCropper/lts_image_cropper.h new file mode 100755 index 000000000..a67930d77 --- /dev/null +++ b/Lts/Common/LtsWidgets/LtsImageCropper/lts_image_cropper.h @@ -0,0 +1,80 @@ + + + +#pragma once + +#include "lts_widgets_export.h" +#include + +namespace LtsWidgets +{ + + + +class LTSWIDGETS_EXPORT LtsImageCropper : public QGraphicsView +{ + + Q_OBJECT + + Q_PROPERTY(QPixmap pixMap READ pixmap WRITE setPixmap) + Q_PROPERTY(double multiplier READ multiplier WRITE setMultiplier) + Q_PROPERTY(double maxZoom READ maxZoom WRITE setMaxZoom) + +public: + + + LtsImageCropper(QWidget *parent = nullptr, const QSize &size = {} , const QPixmap &pixmap = {}); + ~LtsImageCropper(); + + + + void setPixmap(const QPixmap &pixmap); + + + + void setImage(const QImage &image); + + + + void setMultiplier(double multiplier); + + + + void setMaxZoom(double maxZoom) { m_maxScale = maxZoom; } + + + + QPixmap pixmap() { return grab(); } + + + + QImage image(); + + + + double multiplier() const { return m_multiplier; } + + + + double maxZoom() const { return m_maxScale; } + +private: + QGraphicsScene *m_scene = nullptr; + QGraphicsPixmapItem *m_pixmapItem = nullptr; + + double m_currentZoom; + double m_minScale; + double m_maxScale; + double m_stepScale; + double m_multiplier; + + void calculateScaleStep(); + +protected: + void wheelEvent(QWheelEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_abstract_sequence_widget.cpp b/Lts/Common/LtsWidgets/lts_abstract_sequence_widget.cpp new file mode 100755 index 000000000..ad50ce074 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_abstract_sequence_widget.cpp @@ -0,0 +1,120 @@ + + +#include "lts_abstract_sequence_widget.h" + +#include + +namespace LtsWidgets +{ + +LtsAbstractSequenceWidget::LtsAbstractSequenceWidget(QWidget *parent, const bool &readOnly) + : QGroupBox(parent) + , m_readOnly(readOnly) + , m_pbAddMember(new QToolButton) +{ + m_pbAddMember->setIcon(QIcon(QLatin1String(":/icons/png/add.png"))); + + connect(m_pbAddMember, &QToolButton::clicked, this, &LtsAbstractSequenceWidget::addMember); +} + +LtsAbstractSequenceWidget::~LtsAbstractSequenceWidget() +{ + +} + +void LtsAbstractSequenceWidget::addWidget(QWidget* widget, bool isReadOnly, bool isButtonVisible) +{ + addMemberWidget(widget, isReadOnly, isButtonVisible); + m_pbAddMember->setDisabled(isReadOnly); + m_pbAddMember->setVisible(isButtonVisible); +} + +int LtsAbstractSequenceWidget::getMemberlistSize(){ + return m_memberList.size(); +} + +void LtsAbstractSequenceWidget::getValue(QWidget *data, + const QString &findObjectName) const +{ + + if(!findChild(findObjectName)) + return; + + data = findChild(findObjectName); +} + +void LtsAbstractSequenceWidget::setMemberCount(int count) +{ + QWidget *child = nullptr; + do + { + child = findChild(getObjectName(++count)); + delete child; + } + while (child); + m_memberList.removeAll(nullptr); +} + +uint32_t LtsAbstractSequenceWidget::getMemberCount() const +{ + int count = 0; + QWidget *child = findChild(getObjectName(count+1)); + while(child) + { + count++; + child = findChild(getObjectName(count+1)); + } + return count; +} + +QToolButton *LtsAbstractSequenceWidget::pbAddMember() const +{ + return m_pbAddMember; +} + +QString LtsAbstractSequenceWidget::getObjectName(int index) const +{ + return QString::number(index); +} + +void LtsAbstractSequenceWidget::addMemberWidget(QWidget *member, bool isReadOnly, bool isButtonVisible) +{ + QToolButton *button = new QToolButton(); + button->setIcon(QIcon(QLatin1String(":/icons/png/remove.png"))); + button->setDisabled(isReadOnly); + + connect(button, &QToolButton::clicked, member, &QWidget::deleteLater); + connect(member, &QWidget::destroyed, this, &LtsAbstractSequenceWidget::deleteMemberWidget); + connect(member, &QWidget::destroyed, button, &QToolButton::deleteLater); + + m_memberList.push_back(member); + setupMemberWidget(member, button, member->objectName(), isButtonVisible); +} + +void LtsAbstractSequenceWidget::deleteMemberWidget(QObject *member) +{ + QWidget *w = qobject_cast(member); + + if(!w) + return; + + for(auto child : w->children()) + child->deleteLater(); + + m_memberList.removeAll(w); + for(int i=0; isetObjectName(getObjectName(i + 1)); + + for(auto child : m_memberList[i]->children()) + { + if(child->objectName().isEmpty()) + continue; + + QString childName = child->objectName(); + QString newId = QString::number(i + 1); + child->setObjectName(childName.replace(QRegExp(QLatin1String(".([0-9]*)")), newId)); + } + } +} +} diff --git a/Lts/Common/LtsWidgets/lts_abstract_sequence_widget.h b/Lts/Common/LtsWidgets/lts_abstract_sequence_widget.h new file mode 100755 index 000000000..c467064c1 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_abstract_sequence_widget.h @@ -0,0 +1,89 @@ +#ifndef LTS_ABSTRACT_SEQUENCE_WIDGET_H +#define LTS_ABSTRACT_SEQUENCE_WIDGET_H + + + + +#pragma once + + +#include +#include +#include "lts_widgets_export.h" + +class QToolButton; +namespace LtsWidgets +{ + + +class LTSWIDGETS_EXPORT LtsAbstractSequenceWidget : public QGroupBox +{ + Q_OBJECT +public: + + + + explicit LtsAbstractSequenceWidget(QWidget *parent = nullptr, const bool &readOnly = false); + + ~LtsAbstractSequenceWidget(); + + + + virtual void addWidget(QWidget* widget, bool isReadOnly, bool isButtonVisible = true); + + + virtual void getValue(QWidget *contentType, + const QString &findObjectName) const; + + + + virtual void setMemberCount(int count); + + + + virtual uint32_t getMemberCount() const; + + + + virtual int getMemberlistSize(); + + +protected: + + + + virtual QToolButton *pbAddMember() const; + + + + + virtual QString getObjectName(int index) const; + + + + virtual void setupMemberWidget(QWidget *child, QToolButton *pbChild, const QString &objectName, bool isButtonVisible = true) = 0; + +signals: + + + void addMember(); + +private slots: + + + virtual void addMemberWidget(QWidget *member, bool isReadOnly, bool isButtonVisible = true); + + + + virtual void deleteMemberWidget(QObject *member); + +private: + bool m_readOnly; + QToolButton *m_pbAddMember = nullptr; + QList m_memberList; + + +}; +} + +#endif diff --git a/Lts/Common/LtsWidgets/lts_abstract_table_model.h b/Lts/Common/LtsWidgets/lts_abstract_table_model.h new file mode 100755 index 000000000..dd0366de3 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_abstract_table_model.h @@ -0,0 +1,286 @@ + + + +#pragma once + +#include "lts_base.h" + +#include + +#include +#include + +#include +#include + + +namespace LtsWidgets +{ + +template +class LtsAbstractTableModel : public QAbstractTableModel +{ +public: + LtsAbstractTableModel(const QVector &header, QObject *parent) + : QAbstractTableModel(parent) + , m_vecColumns(header) + {} + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override + { + if (role == Qt::DisplayRole && orientation == Qt::Horizontal && section>= 0 && section < m_vecColumns.size()) + return m_vecColumns[section]; + + return QAbstractTableModel::headerData(section, orientation, role); + } + + bool isEmpty(const QModelIndex &parent = QModelIndex()) + { + return parent.isValid() ? true : m_vecData.isEmpty(); + } + + int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + return parent.isValid() ? 0 : m_vecData.size(); + } + + int columnCount(const QModelIndex &parent = QModelIndex()) const override + { + return parent.isValid() ? 0 : m_vecColumns.size(); + } + + void clear() + { + beginResetModel(); + m_vecData.clear(); + endResetModel(); + } + + bool indexIsValid(const QModelIndex &idx) const + { + if (!idx.isValid()) + return false; + if (idx.row() < 0 || idx.row() >= m_vecData.size()) + return false; + return true; + } + + virtual bool removeRow(int row) + { + return removeRows(row, 1); + } + + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override + { + if (parent.isValid()) + return false; + + if (row < 0 || row >= m_vecData.size()) + return false; + + int range = std::min(row + count, m_vecData.size()); + + beginRemoveRows(QModelIndex(), row, range - 1); + m_vecData.erase(m_vecData.begin() + row, m_vecData.begin() + range); + endRemoveRows(); + + return true; + } + + virtual bool insertRow(const T &item) + { + if (isFiltered(item, DataModification::eAdded) || isDeletedInternal(item)) + return false; + beginInsertRows(QModelIndex(), m_vecData.size(), m_vecData.size()); + m_vecData.append(item); + endInsertRows(); + return true; + } + + template + void insertRows(const CONT &items) + { + if (items.isEmpty()) + return; + const int cntBefore = m_vecData.size(); + for (const auto &item : items) + { + if (isFiltered(item, DataModification::eAdded) || isDeletedInternal(item)) + continue; + m_vecData += item; + } + const int cntAfter = m_vecData.size(); + if (cntBefore == cntAfter) + return; + beginInsertRows(QModelIndex(), cntBefore, cntAfter - 1); + endInsertRows(); + } + + void setData(const QVector &vecData) + { + clear(); + insertRows(vecData); + } + + + + void update() + { + if (!m_vecData.isEmpty()) + emit dataChanged(index(0, 0), index(m_vecData.size() - 1, columnCount() - 1)); + } + + T getRowData(int row) const + { + if (row < 0 || row >= m_vecData.size()) + return T(); + return m_vecData.at(row); + } + + bool setRowData(int row, const T &data) + { + if (row < 0 || row >= m_vecData.size()) + return false; + + m_vecData[row] = data; + emit dataChanged(index(row, 0), index(row, columnCount() - 1)); + return true; + } + + QModelIndex findIndexForId(int id, int column = 0) const + { + const int row = findRowForId(id); + if (row < 0) + return QModelIndex(); + return index(row, column); + } + + int getId(const QModelIndex &mi) const + { + if (!indexIsValid(mi)) + return -1; + return getIdInternal(m_vecData.at(mi.row())); + } + +protected: + template + class HasId + { + template + static std::true_type testSignature(const int &(CLS::*)() const); + template + static std::true_type testSignature(int (CLS::*)() const); + + template + static decltype(testSignature(&CLS::id)) test(std::nullptr_t); + + template + static std::false_type test(...); + + public: + using type = decltype(test(nullptr)); + static const bool value = type::value; + }; + + + template ::value, int>::type = 0> + int getIdInternal(const F &data) const + { + return data.id(); + } + + + virtual int getId(const T &data) const + { + Q_ASSERT(false); + Q_UNUSED(data); + return -1; + } + + template < typename F = T, typename std::enable_if < !HasId::value, int>::type = 0> + int getIdInternal(const F &data) const + { + return getId(data); + } + + template + class HasIsDeleted + { + template + static std::true_type testSignature(const bool &(CLS::*)() const); + template + static std::true_type testSignature(bool (CLS::*)() const); + + template + static decltype(testSignature(&CLS::isDeleted)) test(std::nullptr_t); + + template + static std::false_type test(...); + + public: + using type = decltype(test(nullptr)); + static const bool value = type::value; + }; + + template ::value, int>::type = 0> + bool isDeletedInternal(const F &data) const + { + return data.isDeleted(); + } + + virtual int isDeleted(const T &data) const + { + Q_UNUSED(data); + return false; + } + template < typename F = T, typename std::enable_if < !HasIsDeleted::value, int>::type = 0> + bool isDeletedInternal(const F &data) const + { + return isDeleted(data); + } + + + virtual bool isFiltered(const T &data, DataModification modType) const + { + Q_UNUSED(data); + Q_UNUSED(modType); + return false; + } + + + virtual bool onDataChanged(const T &data, DataModification modType) + { + if (isFiltered(data, modType)) + return false; + + const int id = getIdInternal(data); + const int row = findRowForId(id); + + + if (modType == DataModification::eRemoved || isDeletedInternal(data)) + { + if (row >= 0) + return removeRow(row); + return false; + } + + if (modType == DataModification::eAdded && row < 0) + return insertRow(data); + + return setRowData(row, data); + } + + int findRowForId(int id) const + { + for (int i = 0; i < m_vecData.size(); ++i) + { + if (getIdInternal(m_vecData.at(i)) == id) + return i; + } + return -1; + } + + QVector m_vecData; + QVector m_vecColumns; +}; +} diff --git a/Lts/Common/LtsWidgets/lts_angle_edit.cpp b/Lts/Common/LtsWidgets/lts_angle_edit.cpp new file mode 100755 index 000000000..489e569cb --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_angle_edit.cpp @@ -0,0 +1,246 @@ + + + +#include "lts_angle_edit.h" +#include "ui_lts_angle_edit.h" + +#include + + +namespace LtsWidgets +{ + +inline constexpr double pi() { return M_PI; } +inline constexpr double deg2rad() { return pi() / 180.; } +inline constexpr double rad2deg() { return 1. / deg2rad(); } + +constexpr int maxAngleDeg = 360; +constexpr int maxAngleMil = 6400; +constexpr std::tuple c_limDeg (0, maxAngleDeg,0, 10); +constexpr std::tuple c_limMil(0, maxAngleMil,0, 50); +constexpr std::tuple c_limRad(-99999, 99999, 3, 1); + +inline constexpr double mil2Deg(const double &mil) +{ + return maxAngleDeg * mil / maxAngleMil; +} +inline constexpr double deg2Mil(const double &angleDeg) +{ + return maxAngleMil * angleDeg / maxAngleDeg; +} +inline constexpr double deg2Rad(const double &angleDeg) +{ + return angleDeg * deg2rad(); +} +inline constexpr double rad2Degree(const double &angleRad) +{ + return angleRad * 180/pi(); +} +inline constexpr double mil2Rad(const double &mil) +{ + return deg2Rad(mil2Deg(mil)); +} +inline constexpr double rad2Mil(const double &angleRad) +{ + return deg2Mil(rad2Degree(angleRad)); +} + + + +LtsAngleEdit::LtsAngleEdit(QWidget *parent) : + QWidget(parent), + m_ui(new Ui_LtsAngleEdit) +{ + m_ui->setupUi(this); + +} + +LtsAngleEdit::~LtsAngleEdit() +{ + delete m_ui; +} + + + +void LtsAngleEdit::initAngleEditWidget(const unsigned int sboxCount) +{ + + m_dboxLimitMap.insert(AngleUnitType::Deg, c_limDeg); + m_dboxLimitMap.insert(AngleUnitType::Mil, c_limMil); + m_dboxLimitMap.insert(AngleUnitType::Rad, c_limRad); + m_hiddenUnits.clear(); + m_hiddenUnits.insert((int)AngleUnitType::Rad); + + + for(unsigned int i=0; i spinbox = QSharedPointer(new QDoubleSpinBox(this) ); + spinbox->setValue(0); + spinbox->setAlignment(Qt::AlignRight); + spinbox->setMinimumWidth(91); + + spinbox.data()->setObjectName(spinBoxName+QString::number(m_sBoxMap.count())); + m_ui->hLayoutSpinbox->addWidget(spinbox.data(), 0); + m_sBoxMap.insert(m_sBoxMap.count(), spinbox); + } + m_ui->hLayoutSpinbox->setContentsMargins(1,1,1,1); + + + + connect(m_ui->cBoxAngleUnit, qOverload(&QComboBox::currentIndexChanged), + this, &LtsAngleEdit::onCBoxAngleUnitChanged); + + setCboxUnit(); + +} + + + +void LtsAngleEdit::setValue(const double value, unsigned int dboxIndex) +{ + if ((int)dboxIndex > m_sBoxMap.count()) + return; + auto spinbox = m_sBoxMap.value(dboxIndex); + spinbox->setValue(value); +} + + + +void LtsAngleEdit::setValue(const double value, AngleUnitType valueUnit, unsigned int dboxIndex) +{ + setValue(getAngleConverted(value, getAngleCurrentUnit(), valueUnit),dboxIndex); +} + +void LtsAngleEdit::hideUnits(const QVector &list) +{ + for(auto u : list) + m_hiddenUnits.insert((int)u); + setCboxUnit(); +} + +void LtsAngleEdit::showUnits(const QVector &list) +{ + for (const auto &u : list) + m_hiddenUnits.remove((int)u); + setCboxUnit(); +} + + + +void LtsAngleEdit::setCboxUnit() +{ + m_ui->cBoxAngleUnit->clear(); + const std::vector allUnits = {AngleUnitType::Deg, AngleUnitType::Mil, AngleUnitType::Rad}; + for(const auto &u : allUnits) + { + if (m_hiddenUnits.contains((int)u)) + continue; + m_ui->cBoxAngleUnit->insertItem(static_cast(u), toString(u),static_cast(u)); + } + + setAngleUnit(AngleUnitType::Deg); +} + + + +void LtsAngleEdit::setValue(const double value, AngleUnitType valueUnit) +{ + + setValue(getAngleConverted(value, getAngleCurrentUnit(), valueUnit),0); +} + + + +void LtsAngleEdit::setAngleUnit(const AngleUnitType &type) +{ + m_ui->cBoxAngleUnit->setCurrentIndex(static_cast(type)); +} + + + +LtsAngleEdit::AngleUnitType LtsAngleEdit::getAngleCurrentUnit() const +{ + return static_cast(m_ui->cBoxAngleUnit->currentIndex()); +} + + + +double LtsAngleEdit::getAngle(unsigned int sboxIndex) const +{ + if ((int)sboxIndex > m_sBoxMap.count()) + return 0; + auto spinbox = m_sBoxMap.value(sboxIndex); + return spinbox->value(); +} + + + +QString LtsAngleEdit::toString(LtsAngleEdit::AngleUnitType t) +{ + switch(t) + { + case AngleUnitType::Deg: + return tr("Degree"); + case AngleUnitType::Mil: + return tr("Mil"); + case AngleUnitType::Rad: + return tr("Radian"); + default: + return tr(""); + } + return tr(""); +} + +void LtsAngleEdit::onCBoxAngleUnitChanged() +{ + AngleUnitType newUnit = static_cast(m_ui->cBoxAngleUnit->currentData().toInt()); + for(auto dbox : qAsConst(m_sBoxMap)) + { + const double value = getAngleConverted(dbox->value(), newUnit, m_currAngleUnit); + auto mapVal = m_dboxLimitMap.value(newUnit); + dbox->setMinimum(std::get(mapVal)); + dbox->setMaximum(std::get(mapVal)); + dbox->setDecimals(std::get(mapVal)); + dbox->setSingleStep(std::get(mapVal)); + dbox->setValue(value); + } + m_currAngleUnit = newUnit; +} + + + +double LtsAngleEdit::getAngleConverted(const double angle, LtsAngleEdit::AngleUnitType newAngleUnit, LtsAngleEdit::AngleUnitType oldAngleUnit) +{ + double convertedAngle = angle; + if (oldAngleUnit == AngleUnitType::Deg && newAngleUnit == AngleUnitType::Mil ) + convertedAngle = deg2Mil(angle); + else if (oldAngleUnit == AngleUnitType::Deg && newAngleUnit == AngleUnitType::Rad ) + convertedAngle = deg2Rad(angle); + else if (oldAngleUnit == AngleUnitType::Mil && newAngleUnit == AngleUnitType::Deg ) + convertedAngle = mil2Deg(angle); + else if (oldAngleUnit == AngleUnitType::Mil && newAngleUnit == AngleUnitType::Rad ) + convertedAngle = mil2Rad(angle); + else if (oldAngleUnit == AngleUnitType::Rad && newAngleUnit == AngleUnitType::Deg ) + convertedAngle = rad2Degree(angle); + else if (oldAngleUnit == AngleUnitType::Rad && newAngleUnit == AngleUnitType::Mil ) + convertedAngle = rad2Mil(angle); + + return convertedAngle; +} + +void LtsAngleEdit::setEnable(int sbIndex, bool enabled) +{ + if ((int)sbIndex > m_sBoxMap.count()) + return; + m_sBoxMap.value(sbIndex)->setEnabled(enabled); +} + +void LtsAngleEdit::showSpinbox(int sbIndex, bool visible) +{ + if ((int)sbIndex > m_sBoxMap.count()) + return; + m_sBoxMap.value(sbIndex)->setVisible(visible); +} + +} + diff --git a/Lts/Common/LtsWidgets/lts_angle_edit.h b/Lts/Common/LtsWidgets/lts_angle_edit.h new file mode 100755 index 000000000..c889c84f9 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_angle_edit.h @@ -0,0 +1,71 @@ + + + +#pragma once + +#include +#include +#include +#include + + +class Ui_LtsAngleEdit; +namespace LtsWidgets +{ + +class LTSWIDGETS_EXPORT LtsAngleEdit : public QWidget +{ + Q_OBJECT + +public: + explicit LtsAngleEdit(QWidget *parent = nullptr); + ~LtsAngleEdit(); + + enum class AngleUnitType : unsigned int + { + Deg = 0, + Mil = 1, + Rad = 2 + }; + Q_ENUM(AngleUnitType) + + enum LimTuple : int + { + AngleMin, + AngleMax, + DecimalPlace, + SingleStep + }; + + const QString spinBoxName = QStringLiteral("sboxAngleValue"); + QString toString(AngleUnitType t); + void setAngleUnit(const AngleUnitType &type); + void setValue(const double value, unsigned int dboxIndex=0); + void setValue(const double value, AngleUnitType valueUnit); + void setValue(const double value, AngleUnitType valueUnit, unsigned int dboxIndex); + + void hideUnits(const QVector &list); + void showUnits(const QVector &list); + LtsAngleEdit::AngleUnitType getAngleCurrentUnit() const; + void initAngleEditWidget(const unsigned int sboxCount); + double getAngle(unsigned int sboxIndex = 0) const; + double getAngleConverted(const double angle, LtsAngleEdit::AngleUnitType newAngleUnit, LtsAngleEdit::AngleUnitType oldAngleUnit); + + void setEnable(int sbIndex, bool enabled); + void showSpinbox(int sbIndex, bool visible); + +protected: + void setCboxUnit(); + +private Q_SLOTS: + void onCBoxAngleUnitChanged(); +private: + Ui_LtsAngleEdit *m_ui; + QMap> m_sBoxMap; + QMap> m_dboxLimitMap; + AngleUnitType m_currAngleUnit; + QSet m_hiddenUnits; +}; + +} + diff --git a/Lts/Common/LtsWidgets/lts_angle_edit.ui b/Lts/Common/LtsWidgets/lts_angle_edit.ui new file mode 100755 index 000000000..8d96c3ea3 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_angle_edit.ui @@ -0,0 +1,49 @@ + + + LtsAngleEdit + + + + 0 + 0 + 107 + 34 + + + + Form + + + + 3 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + + + + + 0 + 0 + + + + + + + + + diff --git a/Lts/Common/LtsWidgets/lts_background_widget.cpp b/Lts/Common/LtsWidgets/lts_background_widget.cpp new file mode 100755 index 000000000..57b25508d --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_background_widget.cpp @@ -0,0 +1,137 @@ + + + +#include "lts_background_widget.h" + +#include +#include +#include + +#ifdef Q_OS_WIN +#include +#endif + + +namespace LtsWidgets +{ + +LtsBackgroundWidget::LtsBackgroundWidget(QWidget *parent) + : QDialog(parent) + , m_isUltraWide(false) +{ + setMouseTracking(true); + setWindowFlags(windowFlags() | Qt::FramelessWindowHint ); + setWindowTitle(QStringLiteral("Splash")); + + setBackground(); +} + + +void LtsBackgroundWidget::setBackground() +{ + static const auto backgroundFiles = + { + QStringLiteral(":/project/background.png"), + QStringLiteral(":/project/background.jpg"), + }; + static const auto backgroundFilesWide = + { + QStringLiteral(":/project/background_wide.png"), + QStringLiteral(":/project/background_wide.jpg"), + }; + + m_pixmap = QPixmap(); + for (const QString &f : m_isUltraWide ? backgroundFilesWide : backgroundFiles) + { + if (!QFile::exists(f)) + continue; + m_pixmap = QPixmap(f); + if (m_pixmap.isNull()) + continue; + break; + } + + if (m_pixmap.isNull()) + { + if (m_isUltraWide) + { + m_isUltraWide = false; + setBackground(); + } + else + { + qWarning() << "BackgroundWidget::c_tor: file missing or is not a valid image ':/project/background.png'"; + } + } +} + + +void LtsBackgroundWidget::setUltraWide(bool set) +{ + if (set == m_isUltraWide) + return; + + m_isUltraWide = set; + setBackground(); + update(); +} + + +void LtsBackgroundWidget::resizeEvent(QResizeEvent *e) +{ + setUltraWide(width() > 3000); + QDialog::resizeEvent(e); +} + + +void LtsBackgroundWidget::paintEvent(QPaintEvent *e) +{ + if (!m_pixmap.isNull()) + { + QPainter painter(this); + painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); + painter.drawPixmap(0, 0, width(), height(), m_pixmap); + } + QDialog::paintEvent(e); +} + + +bool LtsBackgroundWidget::event(QEvent *e) +{ + if (e->type() == QEvent::KeyPress || + e->type() == QEvent::MouseButtonPress || + e->type() == QEvent::MouseButtonRelease || + e->type() == QEvent::MouseMove) + { + + emit signalOperatorAction(); + e->ignore(); + return true; + } + + return QDialog::event(e); +} + + +void LtsBackgroundWidget::showFullScreen() +{ + +#ifdef Q_OS_WIN + winId(); + QWindowsWindowFunctions::setHasBorderInFullScreen(windowHandle(), true); +#endif + QDialog::showFullScreen(); + raise(); +} + + +void LtsBackgroundWidget::closeEvent(QCloseEvent *e) +{ +#ifdef DEVEL + deleteLater(); +#else + e->ignore(); +#endif +} + +} diff --git a/Lts/Common/LtsWidgets/lts_background_widget.h b/Lts/Common/LtsWidgets/lts_background_widget.h new file mode 100755 index 000000000..3ea88021c --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_background_widget.h @@ -0,0 +1,51 @@ + + + +#pragma once + +#include "lts_widgets_export.h" + +#include + + +class QPixmap; +class QEvent; + +namespace LtsWidgets +{ + + + +class LTSWIDGETS_EXPORT LtsBackgroundWidget : public QDialog +{ + Q_OBJECT + +public: + explicit LtsBackgroundWidget(QWidget *parent = nullptr); + +protected: + void paintEvent(QPaintEvent *e) override; + void closeEvent(QCloseEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + bool event(QEvent *e) override; + +private: + void setBackground(); + void setUltraWide(bool set = true); + +signals: + + + void signalOperatorAction(); + +public slots: + + + void showFullScreen(); +private: + QPixmap m_pixmap; + bool m_isUltraWide; + +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_base.h b/Lts/Common/LtsWidgets/lts_base.h new file mode 100755 index 000000000..1a2ce8900 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_base.h @@ -0,0 +1,274 @@ + + + +#pragma once + +#include "lts_widgets_export.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include + + +namespace LtsWidgets +{ +class LtsDialog; + +inline QString readableDuration(qint64 msecs, bool foldDays = true) +{ + if (msecs == 0) + return QObject::tr("---"); + + + if (msecs % 1000 != 0 && msecs < 1000 * 60) + return QObject::tr("%1 seconds").arg(QLocale().toString(msecs / 1000.0, 'f', 3)); + + qint64 secs = msecs / 1000; + qint64 mins = secs / 60; + qint64 hours = mins / 60; + qint64 days = hours / 24; + + if (foldDays) + days = 0; + else + hours = hours % 24; + + mins = mins % 60; + secs = secs % 60; + + + if (!secs && !mins && !hours) + return QObject::tr("%n day(s)", "", days); + if (!secs && !mins && !days) + return QObject::tr("%n hour(s)", "", hours); + if (!secs && !hours && !days) + return QObject::tr("%n minute(s)", "", mins); + if (!mins && !hours && !days) + return QObject::tr("%n second(s)", "", secs); + + QString strSecs = QObject::tr("%1s").arg(secs, 2, 10, QLatin1Char('0')); + QString strMins = QObject::tr("%1m").arg(mins, 2, 10, QLatin1Char('0')); + QString strHours = QObject::tr("%1h").arg(hours, 2, 10, QLatin1Char('0')); + QString strDays = QObject::tr("%1d").arg(days); + + QString retVal; + if (days) + retVal += strDays + QLatin1Char(' '); + if (days || hours) + retVal.append(strHours); + if (mins || hours || days) + retVal.append(strMins); + if (secs) + retVal.append(strSecs); + + return retVal; +} + +inline void addTouchScroll(QWidget *widget) +{ + if (!widget) + return; + widget->setAttribute(Qt::WA_AcceptTouchEvents); + + { + QScrollArea *sca = qobject_cast(widget); + if (sca) + { + QScroller::grabGesture(sca->viewport(), QScroller::LeftMouseButtonGesture); + return; + } + } + + { + QAbstractItemView *aiv = qobject_cast(widget); + if (aiv) + { + QScroller::grabGesture(aiv->viewport(), QScroller::LeftMouseButtonGesture); + aiv->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); + aiv->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + return; + } + } +} + +enum class ButtonSize +{ + eDefault = 40, + + + + + + +}; + + +enum class ButtonStyle +{ + eAll = 1, + eTextOnly, + eIconOnly, + eTextNextToIcon +}; + +enum class LayoutArea +{ + eUnknown, + eInfoTab, + eDialogTab, + eSpecialTab, + eMainTab, +}; + +enum class ExercisePart +{ + ePlanning, + eExecution, + eAfterAction, + eSpecial, +}; + + +namespace ExerciseState +{ + enum State + { + + eUnknown = 0, + eCreated = 1 << 0, + eStarted = 1 << 1, + ePaused = 1 << 2, + eStopped = 1 << 4, + eTerminated = 1 << 5, + + eAll = eCreated | eStarted | ePaused | eStopped | eTerminated + }; + + Q_DECLARE_OPERATORS_FOR_FLAGS(QFlags) +} + +typedef ExerciseState::State ExcState; +typedef QFlags ExerciseStates; + +enum class CreateInstantType +{ + DontCreateInstant, + CreateInstantAndAddAction, + CreateInstantAndDiscardAction, +}; + +struct PluginData +{ + + using createWidgetFuncPtr = std::function; + using directFuncPtr = std::function; + + createWidgetFuncPtr createWidgetFunc = nullptr; + directFuncPtr directFunc = nullptr; + QStringList iconPaths; + QIcon menuIcon; + QString tooltip; + qulonglong dropHandlerKey = 0; + QString menuText; + QString menuGroup; + LtsUtils::LtsTranslatableIdentifier translatableMenuGroup; + QString identifier; + bool closable = true; + LayoutArea layoutArea = LayoutArea::eDialogTab; + int operatorRight = 0; + CreateInstantType createInstant = CreateInstantType::DontCreateInstant; + bool visibleInMenu = true; + ExercisePart exercisePart = ExercisePart::eSpecial; + ExerciseStates exerciseStates = ExerciseState::eAll; + + PluginData() = default; + PluginData(const QString &_menuGroup, + const QString &_menuText, + const QString &_identifier, + const createWidgetFuncPtr &_cwfp, + int _operatorRight, + ExercisePart _exercisePart = ExercisePart::eSpecial, + ExerciseStates _exerciseStates = ExerciseState::eAll) + : PluginData(_menuGroup, _menuText, _identifier, QIcon(), _cwfp, _operatorRight, _exercisePart, _exerciseStates) + {} + + PluginData(const QString &_menuGroup, + const QString &_menuText, + const QString &_identifier, + const QIcon &_icon, + const createWidgetFuncPtr &_cwfp, + int _operatorRight, + ExercisePart _exercisePart = ExercisePart::eSpecial, + ExerciseStates _exerciseStates = ExerciseState::eAll) + : createWidgetFunc(_cwfp) + , menuIcon(_icon) + , menuText(_menuText) + , menuGroup(_menuGroup) + , identifier(_identifier) + , operatorRight(_operatorRight) + , exercisePart(_exercisePart) + , exerciseStates(_exerciseStates) + { + } + + static PluginData createGroup(const QString &menuGroup, + const QString &menuText, + const QString &identifier, + int operatorRight) + { + return PluginData(menuGroup, menuText, identifier, nullptr, operatorRight, ExercisePart::eSpecial); + } + bool isValid() const { return createWidgetFunc != nullptr; } +}; + + + +class LTSWIDGETS_EXPORT Designer +{ + Q_GADGET +public: + enum ButtonSize + { + eDefault = static_cast(LtsWidgets::ButtonSize::eDefault), + + + + + + + }; + Q_ENUM(ButtonSize) + enum ButtonStyle + { + eAll = static_cast(LtsWidgets::ButtonStyle::eAll), + eTextOnly = static_cast(LtsWidgets::ButtonStyle::eTextOnly), + eIconOnly = static_cast(LtsWidgets::ButtonStyle::eIconOnly), + eTextNextToIcon = static_cast(LtsWidgets::ButtonStyle::eTextNextToIcon) + }; + Q_ENUM(ButtonStyle) + enum LayoutArea + { + eUnknownArea = static_cast(LtsWidgets::LayoutArea::eUnknown), + eInfoTab = static_cast(LtsWidgets::LayoutArea::eInfoTab), + eDialogTab = static_cast(LtsWidgets::LayoutArea::eDialogTab), + eSpecialTab = static_cast(LtsWidgets::LayoutArea::eSpecialTab), + eMainTab = static_cast(LtsWidgets::LayoutArea::eMainTab) + }; + Q_ENUM(LayoutArea) +}; + +} +Q_DECLARE_METATYPE(LtsWidgets::Designer::ButtonSize) +Q_DECLARE_METATYPE(LtsWidgets::Designer::ButtonStyle) +Q_DECLARE_METATYPE(LtsWidgets::Designer::LayoutArea) + +Q_DECLARE_METATYPE(LtsWidgets::ButtonSize) +Q_DECLARE_METATYPE(LtsWidgets::ButtonStyle) +Q_DECLARE_METATYPE(LtsWidgets::LayoutArea) diff --git a/Lts/Common/LtsWidgets/lts_base_layout.ui b/Lts/Common/LtsWidgets/lts_base_layout.ui new file mode 100755 index 000000000..30f16a1cd --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_base_layout.ui @@ -0,0 +1,645 @@ + + + LtsBaseLayoutWidget + + + + 0 + 0 + 1024 + 768 + + + + + 1024 + 768 + + + + Operator Application + + + + :/rde_icon_256.png:/rde_icon_256.png + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 5 + + + 0 + + + 0 + + + 5 + + + 0 + + + + + + 0 + 0 + + + + RDE_SPLITTER_LEFT + + + false + + + + + + + + 0 + 0 + + + + <html><head/><body><p>PROJECT_LOGO</p></body></html> + + + + + + + 3 + + + + + + + App name + + + %APP_NAME% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + • + + + + + + + System time + + + %CUR_TIME% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Exercise name + + + %EX_NAME% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + • + + + + + + + Exercise phase + + + %EX_PHASE% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + • + + + + + + + Exercise status + + + %EX_STATUS% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + • + + + + + + + Exercise time + + + %EX_TIME% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + • + + + + + + + Session type (Live or Replay) + + + %PART_TYPE% + + + + + + + • + + + + + + + Session type (Live or Replay) + + + %CUSTOM_TEXT% + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 0 + 0 + + + + + 40 + 40 + + + + + + + + Qt::Vertical + + + + + + + + + + + + + + + [debug] + + + + + + + + 48 + 48 + + + + + 48 + 48 + + + + + + + + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + 100 + 100 + + + + Qt::Horizontal + + + 10 + + + + + 0 + 0 + + + + + 100 + 100 + + + + true + + + + + + 0 + 0 + + + + + 100 + 100 + + + + Qt::Vertical + + + 10 + + + + + 0 + 0 + + + + + 100 + 100 + + + + + + + true + + + + + + 0 + 0 + + + + + 50 + 50 + + + + true + + + + + + + 100 + 100 + + + + Qt::Vertical + + + 10 + + + + + + + + 0 + 0 + + + + Planning + + + true + + + + + + + + 0 + 0 + + + + Execution + + + true + + + + + + + + 0 + 0 + + + + After Action + + + true + + + + + + + + 0 + 0 + + + + Extras + + + true + + + + + + + + 0 + 0 + + + + + 250 + 100 + + + + + + 0 + 0 + + + + + 100 + 100 + + + + + + + 0 + 0 + + + + + 100 + 100 + + + + + + + 0 + 0 + + + + + 100 + 100 + + + + + + + 0 + 0 + + + + + 100 + 100 + + + + + + + + + + + 0 + 0 + + + + + 100 + 100 + + + + true + + + + + + + + + + LtsWidgets::LtsProfileButton + QWidget +
LtsWidgets/lts_profile_button.h
+ 1 +
+ + LtsWidgets::LtsSplitter + QSplitter +
LtsWidgets/lts_splitter.h
+ 1 +
+ + LtsWidgets::LtsTabWidget + QTabWidget +
LtsWidgets/lts_tab_widget.h
+ 1 +
+ + LtsWidgets::LtsToolBarArea + QWidget +
LtsWidgets/lts_tool_bar_area.h
+ 1 +
+ + LtsWidgets::LtsToolBox + QToolBox +
LtsWidgets/lts_tool_box.h
+ 1 +
+
+ + + + +
diff --git a/Lts/Common/LtsWidgets/lts_base_layout_widget.cpp b/Lts/Common/LtsWidgets/lts_base_layout_widget.cpp new file mode 100755 index 000000000..5a6e2a1d7 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_base_layout_widget.cpp @@ -0,0 +1,1828 @@ + + + +#include "lts_base_layout_widget.h" +#include "ui_lts_base_layout.h" + +#include "lts_dialog.h" +#include "lts_dialog_helper.h" +#include "lts_drag_icon.h" +#include "lts_grid_layout.h" +#include "lts_message_dialog.h" +#include "lts_operator_menu.h" +#include "lts_tool_bar_area.h" +#include "lts_splash.h" +#include "lts_events.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace LtsWidgets +{ + +static QVariant splitterInterpolator(const QList &start, const QList &end, qreal progress) +{ + QList ret; + ret = start; + for (int i = 0; i < start.size(); i++) + { + ret[i] = start[i] + qRound((end[i] - start[i]) * progress); + if (ret[i] <= 0) + ret[i] = 1; + } + + return QVariant::fromValue(ret); +} + + +LtsBaseLayoutWidget::LtsBaseLayoutWidget(QWidget *parent, bool splitMenu) + : QWidget(parent) + , m_currentAnimation(nullptr) + , m_ui(new Ui::LtsBaseLayoutWidget) + , m_partitionType(IDL::PartitionType::Global) + , m_domainId(-1) + , m_partitionId(-1) + , m_firstShow(true) + , m_showTerminalButton(false) + , m_exerciseTime(0) + , m_setCurrentTimeTimer(new QTimer(this)) + , m_irreplaceableOnce(false) + , m_bg(nullptr) + , m_screenCount(1) + , m_menuCollapsed(true) + , m_notiBox(nullptr) +{ + m_ui->setupUi(this); + +#ifndef LTS_DEBUG + m_ui->pbDebug->hide(); +#endif + + addDebugMenu(); + + + { + + m_ui->lbCurrentTime->setContextMenuPolicy(Qt::CustomContextMenu); + QMenu *currentTimeMenu = new QMenu(m_ui->lbCurrentTime); + currentTimeMenu->addAction(tr("Copy"), this, [this]() + { + QGuiApplication::clipboard()->setText(m_ui->lbCurrentTime->text()); + }); + currentTimeMenu->addAction(tr("Copy in milliseconds"), this, []() + { + QGuiApplication::clipboard()->setText(QString::number(QDateTime::currentMSecsSinceEpoch())); + }); + connect(m_ui->lbCurrentTime, &QLabel::customContextMenuRequested, + this, [this, currentTimeMenu](const QPoint &pos) { currentTimeMenu->popup(m_ui->lbCurrentTime->mapToGlobal(pos)); }); + } + + + { + + m_ui->lbExerciseTime->setContextMenuPolicy(Qt::CustomContextMenu); + QMenu *exerciseTimeMenu = new QMenu(m_ui->lbExerciseTime); + exerciseTimeMenu->addAction(tr("Copy"), this, [this]() + { + QGuiApplication::clipboard()->setText(m_ui->lbExerciseTime->text()); + }); + exerciseTimeMenu->addAction(tr("Copy in milliseconds"), this, [this]() + { + QGuiApplication::clipboard()->setText(QString::number(m_exerciseTime)); + }); + connect(m_ui->lbExerciseTime, &QLabel::customContextMenuRequested, + this, [this, exerciseTimeMenu](const QPoint &pos) { exerciseTimeMenu->popup(m_ui->lbExerciseTime->mapToGlobal(pos)); }); + } + + + setMinimumSize(1280, 900); + + m_ui->detailWidget->setSubTabsVisible(true); + + + qRegisterAnimationInterpolator >(splitterInterpolator); + qRegisterMetaType("LayoutArea"); + + m_ui->mainWidget->setTabBarAutoHide(true); + + m_ui->infoWidget->setActuatorTabStyle(true); + m_ui->infoWidget->setTabBarAutoHide(false); + m_ui->infoWidget->setTabPosition(QTabWidget::West); + + if (m_ui->infoWidget->count() == 0) + m_ui->infoWidget->hide(); + + if (m_ui->detailWidget->count() == 0) + m_ui->detailWidget->hide(); + + if (m_ui->specialWidget->count() == 0) + m_ui->specialWidget->hide(); + + + m_ui->masterSplitter->setStretchFactor(m_ui->masterSplitter->indexOf(m_ui->infoWidget), 0); + m_ui->masterSplitter->setStretchFactor(m_ui->masterSplitter->indexOf(m_ui->mainSplitter), 20); + + + m_ui->mainSplitter->setStretchFactor(m_ui->mainSplitter->indexOf(m_ui->mainWidget), 20); + m_ui->mainSplitter->setStretchFactor(m_ui->mainSplitter->indexOf(m_ui->detailWidget), 0); + + + m_ui->rightSplitter->setStretchFactor(m_ui->rightSplitter->indexOf(m_ui->stackedWidget), 20); + m_ui->rightSplitter->setStretchFactor(m_ui->rightSplitter->indexOf(m_ui->specialWidget), 0); + + m_isHidden = false; + + + connect(m_ui->profileButton, &LtsProfileButton::signalClicked, this, &LtsBaseLayoutWidget::slotOperatorMenuClicked); + + + + + + m_ui->lbCustomTextBullet->hide(); + m_ui->lbCustomText->hide(); + + m_ui->lbExerciseTime->setText(QStringLiteral(" # ")); + m_ui->lbCurrentTime->setText(QStringLiteral(" # ")); + + connect(m_setCurrentTimeTimer, &QTimer::timeout, this, &LtsBaseLayoutWidget::setCurrentTime); + m_setCurrentTimeTimer->start(200); + + setExerciseData(m_exerciseData); + + if (splitMenu) + { + m_bg = new QButtonGroup(m_ui->naviWidget); + m_bg->addButton(m_ui->pbPlanning, (int) ExercisePart::ePlanning); + m_bg->addButton(m_ui->pbExecution, (int) ExercisePart::eExecution); + m_bg->addButton(m_ui->pbAfterAction, (int) ExercisePart::eAfterAction); + m_bg->addButton(m_ui->pbSpecial, (int) ExercisePart::eSpecial); + + connect(m_bg, QOverload::of(&QButtonGroup::buttonClicked), m_ui->stackedWidget, [this](int id) + { + m_ui->stackedWidget->setCurrentWidget(getToolBox((ExercisePart) id)); + }); + m_ui->pbSpecial->click(); + } + else + { + m_ui->stackedWidget->setCurrentWidget(getToolBox(ExercisePart::eSpecial)); + } + + checkExercisePartButtonVisibility(); +} + + +LtsBaseLayoutWidget::~LtsBaseLayoutWidget() +{ + + + + + if (m_notiBox != nullptr) + m_notiBox->setParent(nullptr); + delete m_notiBox; + + delete m_currentAnimation; + m_currentAnimation = nullptr; + clearMenuActions(); + delete m_ui; + m_ui = nullptr; + +} + + +void LtsBaseLayoutWidget::setCurrentTime() +{ + m_ui->lbCurrentTime->setText(DTG::toString(QDateTime::currentDateTime())); + + if (m_exerciseTime > 0) + m_ui->lbExerciseTime->setText(DTG::toStringMSec(m_exerciseTime)); + else + m_ui->lbExerciseTime->setText(QStringLiteral(" # ")); +} + + +void LtsBaseLayoutWidget::slotScreenCountChanged(int count) +{ + m_screenCount = count; +} + + +const QString LtsBaseLayoutWidget::customText() const +{ + return m_ui->lbCustomText->text(); +} + + +void LtsBaseLayoutWidget::setCustomText(const QString &text) +{ + if (text.isEmpty()) + { + m_ui->lbCustomText->setText(QString()); + m_ui->lbCustomText->hide(); + m_ui->lbCustomTextBullet->hide(); + + return; + } + + m_ui->lbCustomText->show(); + m_ui->lbCustomTextBullet->show(); + m_ui->lbCustomText->setText(text); +} + + +void LtsBaseLayoutWidget::addDebugMenu() +{ +#ifdef LTS_DEBUG + QMenu *menu = new QMenu(this); + + menu->addAction(QStringLiteral("NOTE: all actions are delayed by 1s"))->setEnabled(false); + + menu->addSeparator(); + + menu->addAction(QStringLiteral("logout"), this, [this]() + { + QTimer::singleShot(1000, this, &LtsBaseLayoutWidget::slotLogout); + }); + + menu->addAction(QStringLiteral("forced logout"), this, [this]() + { + QTimer::singleShot(1000, this, &LtsBaseLayoutWidget::signalLogout); + }); + + menu->addAction(QStringLiteral("app exit"), this, [this]() + { + QTimer::singleShot(1000, this, &LtsBaseLayoutWidget::signalClose); + }); + + menu->addSeparator(); + + menu->addAction(QStringLiteral("show test message, unparented"), this, [this]() + { + QTimer::singleShot(1000, this, []() { LtsWidgets::LtsMessageDialog::showMessage(nullptr , QStringLiteral("test"), QStringLiteral("test")); } ); + }); + + menu->addAction(QStringLiteral("show test message"), this, [this]() + { + QTimer::singleShot(1000, this, [this]() { LtsWidgets::LtsMessageDialog::showMessage(this, QStringLiteral("test"), QStringLiteral("test")); } ); + }); + + menu->addSeparator(); + + menu->addAction(QStringLiteral("hide details tab"), this, [this]() + { + QTimer::singleShot(1000, this, [this]() { this->hideWidgets(); } ); + }); + + menu->addAction(QStringLiteral("show details tab"), this, [this]() + { + QTimer::singleShot(1000, this, [this]() { this->showWidgets(); } ); + }); + + menu->addAction(QStringLiteral("hide details tab, animated"), this, [this]() + { + QTimer::singleShot(1000, this, [this]() { this->onHideAnimated(); } ); + }); + + menu->addAction(QStringLiteral("show details tab, animated"), this, [this]() + { + QTimer::singleShot(1000, this, [this]() { this->onReShowAnimated(); } ); + }); + + menu->addSeparator(); + + menu->addAction(QStringLiteral("hide top bar"), this, [this]() + { + QTimer::singleShot(1000, this, [this]() { this->setTopBarVisible(false); } ); + }); + + menu->addSeparator(); + + menu->addAction(QStringLiteral("test NotificationBox with error"), this, [this]() + { + QTimer::singleShot(1000, this, [this]() + { + showMessage(QStringLiteral("Test Title"), QStringLiteral("A long test text that notifies the user about something
that spreads over two lines to fit even more text."), LtsWidgets::LtsNotificationBox::MessageType::Error, + [this](const QString &) + { + new LtsWidgets::LtsMessageDialogNew(this, QStringLiteral("You clicked the link!")); + }, true, QInputDialog::getText(this, QStringLiteral("Identifier"), QStringLiteral("Identifier (keep empty to not set)"))); + }); + }); + + menu->addAction(QStringLiteral("test NotificationBox with success"), this, [this]() + { + QTimer::singleShot(1000, this, [this]() + { + showMessage(QStringLiteral("Test Title"), QStringLiteral("A long test text that notifies the user about something
that spreads over two lines to fit even more text."), LtsWidgets::LtsNotificationBox::MessageType::Success, + [this](const QString &) + { + new LtsWidgets::LtsMessageDialogNew(this, QStringLiteral("You clicked the link!")); + }, true, QInputDialog::getText(this, QStringLiteral("Identifier"), QStringLiteral("Identifier (keep empty to not set)"))); + }); + }); + + menu->addAction(QStringLiteral("test NotificationBox with info"), this, [this]() + { + QTimer::singleShot(1000, this, [this]() + { + showMessage(QStringLiteral("Test Title"), QStringLiteral("A long test text that notifies the user about something
that spreads over two lines to fit even more text."), LtsWidgets::LtsNotificationBox::MessageType::Info, + [this](const QString &) + { + new LtsWidgets::LtsMessageDialogNew(this, QStringLiteral("You clicked the link!")); + }, true, QInputDialog::getText(this, QStringLiteral("Identifier"), QStringLiteral("Identifier (keep empty to not set)"))); + }); + }); + + menu->addAction(QStringLiteral("test NotificationBox with warning"), this, [this]() + { + QTimer::singleShot(1000, this, [this]() + { + showMessage(QStringLiteral("Test Title"), QStringLiteral("A long test text that notifies the user about something
that spreads over two lines to fit even more text."), LtsWidgets::LtsNotificationBox::MessageType::Warning, + [this](const QString &) + { + new LtsWidgets::LtsMessageDialogNew(this, QStringLiteral("You clicked the link!")); + }, true, QInputDialog::getText(this, QStringLiteral("Identifier"), QStringLiteral("Identifier (keep empty to not set)"))); + }); + }); + + m_ui->pbDebug->setMenu(menu); +#endif +} + + +void LtsBaseLayoutWidget::showEvent(QShowEvent *event) +{ + QWidget::showEvent(event); + + if (!m_firstShow) + return; + + m_firstShow = false; + + + QSize rendererSizeHint = m_ui->topLayoutWidget->sizeHint(); + rendererSizeHint.scale(rendererSizeHint.width() * 0.65, rendererSizeHint.height() * 0.65, Qt::IgnoreAspectRatio); + const auto dpr = devicePixelRatioF(); + + { + const QSize s(QSize(rendererSizeHint.height(), rendererSizeHint.height()) * dpr); + + QSvgRenderer svgRenderer; + QPixmap pixmap(s); + pixmap.fill(Qt::transparent); + + QPainter painter(&pixmap); + svgRenderer.load(QLatin1String(":rde_splitter_left.svg")); + svgRenderer.render(&painter); + painter.end(); + + pixmap.setDevicePixelRatio(dpr); + m_ui->lbRdeSplitterLeft->setPixmap(pixmap); + } + + { + + QString projectLogoFilename = QLatin1String(":project/logo.png"); + QPixmap pixmap(projectLogoFilename); + pixmap.setDevicePixelRatio(dpr); + + if (pixmap.isNull()) + { + qWarning() << "LoginWindow::c_tor: (PROJECT LOGO) file missing or is not an valid image '" << projectLogoFilename << "'"; + } + else + { + pixmap = pixmap.scaled(rendererSizeHint * dpr, Qt::KeepAspectRatio, Qt::SmoothTransformation); + m_ui->lbProjectLogo->setPixmap(pixmap); + } + } +#ifdef TABLET_MODE + QList scrollWidgets = findChildren(); + + for (auto i = scrollWidgets.begin(); i != scrollWidgets.end(); ++i) + { + QWidget *widget = *i; + + QVariant isConst = widget->property("const"); + if (isConst.isValid() && isConst.toBool()) + continue; + + addTouchScroll(widget); + } +#endif + + Splash::getInstance()->done(); +} + + +void LtsBaseLayoutWidget::addTouchScroll(QWidget *widget) +{ + if (!widget) + return; + widget->setAttribute(Qt::WA_AcceptTouchEvents); + + QScrollArea *sca = qobject_cast(widget); + if (sca) + { + QScroller::grabGesture(sca->viewport(), QScroller::LeftMouseButtonGesture); + return; + } + + QAbstractItemView *aiv = qobject_cast(widget); + if (aiv) + { + QScroller::grabGesture(aiv->viewport(), QScroller::LeftMouseButtonGesture); + aiv->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); + aiv->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + return; + } +} + + +void LtsBaseLayoutWidget::setAppName(const QString &appName) +{ + m_ui->lbAppName->setText(appName); +} + + +void LtsBaseLayoutWidget::setExerciseData(const ExerciseData &data) +{ + m_exerciseData = data; + + m_ui->lbExerciseName->setText(data.exName.isEmpty() ? tr("no exercise") : data.exName); + + QString exerciseNameToolTip(tr("Exercise name")); + if (!data.exName.isEmpty() && m_partitionId > 0) + { + exerciseNameToolTip += tr("\nPartition Id:"); + exerciseNameToolTip += QStringLiteral(" %1").arg(m_partitionId); + } + + if (!data.exDescription.isEmpty()) + { + exerciseNameToolTip += tr("\nDescription:"); + exerciseNameToolTip += QStringLiteral(" %1").arg(data.exDescription); + } + m_ui->lbExerciseName->setToolTip(exerciseNameToolTip); + + LtsUtils::ProjectSettings *ps = LtsUtils::ProjectSettings::getInstance(); + const bool enableEditPhases = ps->value(QStringLiteral("exm"), + QStringLiteral("enableEditPhases")).toBool(); + if (!enableEditPhases) + { + m_ui->lbExercisePhase->hide(); + m_ui->bullet1->hide(); + } + + m_ui->lbExercisePhase->setText(data.phName.isEmpty() ? tr("no phase") : data.phName); + + QString phaseNameToolTip(tr("Exercise phase")); + if (!data.phDescription.isEmpty()) + { + phaseNameToolTip += tr("\nDescription:"); + phaseNameToolTip += QStringLiteral(" %1").arg(data.phDescription); + } + m_ui->lbExercisePhase->setToolTip(phaseNameToolTip); + + m_ui->lbExerciseStatus->setText(data.exStateString.isEmpty() ? tr("no status") : data.exStateString); + + if (data.exStateCol.isValid() && data.exStateCol != QColor()) + { + QPalette p = m_ui->lbExerciseStatus->palette(); + p.setColor(m_ui->lbExerciseStatus->backgroundRole(), data.exStateCol); + p.setColor(m_ui->lbExerciseStatus->foregroundRole(), data.exStateCol); + m_ui->lbExerciseStatus->setPalette(p); + } + + m_ui->lbPartitionType->setText(data.partitionType); + + applyExerciseStateRestricition(); +} + +void LtsBaseLayoutWidget::applyExerciseStateRestricition() +{ + for (auto it = m_mapButtons.cbegin(); it != m_mapButtons.cend(); ++it) + { + bool visible = it.value().exerciseStates.testFlag(m_exerciseData.exState); + it.key()->setButtonVisible(visible); + } +} + +void LtsBaseLayoutWidget::initilizationFinished() +{ + + + + + + + + +} + +void LtsBaseLayoutWidget::setToolBarActions(const QStringList &actions) +{ + QVector desiredActions; + + m_notFoundYet = actions; + + for (const QString &action : actions) + { + for (auto innerIt = m_mapButtons.cbegin(); innerIt != m_mapButtons.cend(); ++innerIt) + { + if (innerIt.value().identifier.compare(action) == 0) + { + desiredActions << innerIt.key(); + m_notFoundYet.removeAll(action); + } + } + } + + m_ui->toolBar->setMenuEntries(desiredActions); +} + +QStringList LtsBaseLayoutWidget::toolBarActions() const +{ + if (m_ui == nullptr || m_ui->toolBar == nullptr) + return QStringList(); + return m_ui->toolBar->menuEntries(); +} + +void LtsBaseLayoutWidget::setFunctionKeys(const QMap &actions) +{ + for (auto it = actions.cbegin(); it != actions.cend(); ++it) + m_ui->toolBar->setFunctionKey(it.key(), it.value()); +} + +QMap LtsBaseLayoutWidget::functionKeys() const +{ + if (m_ui == nullptr) + return QMap(); + return m_ui->toolBar->functionKeys(); +} + +void LtsBaseLayoutWidget::setSessionInfo(const int domainId, const IDL::PartitionType &partitionType, const int partitionId, const QString &workspaceId) +{ + m_domainId = domainId; + m_partitionType = partitionType; + m_partitionId = partitionId; + m_workspaceId = workspaceId; +} + + + +void LtsBaseLayoutWidget::setOperatorName(const QString &operatorName, bool showTerminalButton) +{ + m_operatorName = operatorName; + if (m_operatorName.isEmpty()) + m_operatorName = tr("Admin"); + + m_ui->profileButton->setToolTip(tr("Operator Name: %1").arg(m_operatorName)); + m_showTerminalButton = showTerminalButton; +} + + +void LtsBaseLayoutWidget::setProfilePicture(const QPixmap &profilePicture) +{ + m_ui->profileButton->setPixmap(profilePicture); +} + + +void LtsBaseLayoutWidget::slotOperatorMenuClicked() +{ + bool debug = false; + +#ifndef LTS_DEBUG + if (LtsSessionData::LtsSessionData::getInstance()->debugFlagValue(QStringLiteral("DeveloperTools.Actions")) == QVariant(true)) +#endif + { + debug = true; + } + + LtsOperatorMenu *operatorMenu = new LtsOperatorMenu(m_operatorName, m_workspaceId, debug, m_screenCount, this); + operatorMenu->setTerminalButton(m_showTerminalButton); + + bool hasOperatorSettings = false; + for (const PluginData &pd : qAsConst(m_mapButtons)) + { + if (pd.identifier == QStringLiteral("_OpmShowOperatorSettings")) + { + hasOperatorSettings = true; + break; + } + } + operatorMenu->setOperatorSettingsButton(hasOperatorSettings); + + connect(operatorMenu, &LtsOperatorMenu::signalLogout, this, &LtsBaseLayoutWidget::slotLogout); + connect(operatorMenu, &LtsOperatorMenu::signalExit, this, &LtsBaseLayoutWidget::slotClose); + connect(operatorMenu, &LtsOperatorMenu::signalShowOperatorSettings, this, &LtsBaseLayoutWidget::signalShowOperatorSettings); + connect(operatorMenu, &LtsOperatorMenu::signalSwap, this, &LtsBaseLayoutWidget::signalSwap); + + operatorMenu->show(); +} + + +bool LtsBaseLayoutWidget::haveUnsavedChanges() +{ + if (LtsTabWidget::hasUnsavedChanges(getTabFromLayoutArea(LayoutArea::eDialogTab))) + return true; + if (LtsTabWidget::hasUnsavedChanges(getTabFromLayoutArea(LayoutArea::eInfoTab))) + return true; + if (LtsTabWidget::hasUnsavedChanges(getTabFromLayoutArea(LayoutArea::eMainTab))) + return true; + if (LtsTabWidget::hasUnsavedChanges(getTabFromLayoutArea(LayoutArea::eSpecialTab))) + return true; + + return false; +} + + + + + + + + + + + + +bool LtsBaseLayoutWidget::notifyUnsavedChanges() +{ + if (isSignalConnected(QMetaMethod::fromSignal(&LtsBaseLayoutWidget::signalNotifyUnsavedChanges))) + { + bool reject = false; + emit signalNotifyUnsavedChanges(reject); + return reject; + } + else + { + if (haveUnsavedChanges()) + { + LtsWidgets::Buttons buttons; + buttons.setFlag(Button::Yes, true); + buttons.setFlag(Button::Cancel, true); + QHash buttonTexts; + buttonTexts.insert(Button::Yes, tr("Discard All")); + const auto ret = LtsMessageDialog::showMessage(this, tr("There are dialogs with unsaved changes.
Please save or discard their changes before closing"), tr("Unsaved changes"),Behaviour::Modal, buttons, buttonTexts); + return ret == Button::Yes; + } + } + return true; +} + + +void LtsBaseLayoutWidget::slotLogout() +{ + emit signalLogout(); +} + + +bool LtsBaseLayoutWidget::slotClose() +{ + if (!notifyUnsavedChanges()) + return false; + + + + + + + + + + + + + + + + + + + + + + + hide(); + + emit signalClose(); + return true; +} + + + + + + + + +void LtsBaseLayoutWidget::closeEvent(QCloseEvent *event) +{ + event->ignore(); + + slotClose(); +} + + +bool LtsBaseLayoutWidget::focusTab(const QString &menuGroup, const QString &identifier, LayoutArea area) +{ + LtsTabWidget *tab = getTabFromLayoutArea(area); + if (!tab) + { + qWarning() << "LtsBaseLayoutWidget::focusTab: tab, by enum type, not found!"; + return false; + } + + return tab->focusTab(menuGroup, identifier); +} + + +bool LtsBaseLayoutWidget::isDialogFocused(const LtsDialog *dialog, const LtsTabWidget *tab) const +{ + QWidget *w = tab->widget(tab->currentIndex()); + + if (w == nullptr) + return false; + + const LtsTabWidget *secondTab = qobject_cast(w); + + if (secondTab != nullptr) + return isDialogFocused(dialog, secondTab); + + const LtsDialogHelper *helper = qobject_cast(w); + + if (helper != nullptr) + return helper->compare(dialog); + + qWarning() << "LtsBaseLayoutWidget::isDialogFocused(LtsDialog *dialog, LtsTabWidget *tab): i found something weird.." << w->metaObject()->className(); + return false; +} + + +bool LtsBaseLayoutWidget::isDialogFocused(const LtsDialog *dialog, const LayoutArea &layoutArea) const +{ + const LtsTabWidget *tab = getTabFromLayoutArea(layoutArea); + if (tab == nullptr) + { + qWarning() << "LtsBaseLayoutWidget::addWidget: tab, by enum type, not found!"; + return false; + } + + return isDialogFocused(dialog, tab); +} + + +bool LtsBaseLayoutWidget::isDialogFocused(const LtsDialog *dialog) const +{ + return (isDialogFocused(dialog, LayoutArea::eDialogTab) || + isDialogFocused(dialog, LayoutArea::eInfoTab) || + isDialogFocused(dialog, LayoutArea::eSpecialTab) || + isDialogFocused(dialog, LayoutArea::eMainTab)); +} + + +bool LtsBaseLayoutWidget::addOrReplaceWidget(LtsDialog *dialog, const QString &menuGroup, const QString &identifier, LayoutArea area, bool setClosable, bool focusNewTab) +{ + LtsTabWidget *tab = getTabFromLayoutArea(area); + if (tab && dialog && (!identifier.isEmpty())) + { + QWidget *existingWidget = tab->getWidget(menuGroup, identifier); + + if (nullptr != existingWidget) + { + if (tab->removeWidget(existingWidget)) + { + existingWidget->deleteLater(); + + } + else + { + qWarning() << __FUNCTION__ << " : Remove existing widget from TabWidget failed."; + } + } + } + return addWidget(dialog, menuGroup, identifier, area, setClosable, focusNewTab); +} + + +bool LtsBaseLayoutWidget::addWidget(LtsDialog *dialog, const QString &menuGroup, const QString &identifier, LayoutArea area, bool setClosable, bool focusNewTab) +{ + if (!dialog) + { + qWarning() << "LtsBaseLayoutWidget::addWidget: cannot add null-widget!"; + return false; + } + + LtsTabWidget *tab = getTabFromLayoutArea(area); + if (!tab) + { + qWarning() << "LtsBaseLayoutWidget::addWidget: tab, by enum type, not found!"; + return false; + } + + dialog->setMenuGroup(menuGroup); + dialog->setIdentifier(identifier); + QWidget *helper = dialog->getHelper(); + if (!helper) + { + helper = dialog; + dialog->setAttribute(Qt::WA_DeleteOnClose); + } + + if (!identifier.isEmpty()) + { + QWidget *existingWidget = tab->getWidget(menuGroup, identifier); + if (existingWidget != nullptr) + { + helper->deleteLater(); + + if (focusNewTab) + tab->focusTab(menuGroup, identifier); + + + int indexOfSplitter = getIndexOfTab(tab); + + if (area == LayoutArea::eDialogTab && !(m_ui->mainSplitter->sizes().at(indexOfSplitter))) + m_ui->mainSplitter->animate(1); + else if (area == LayoutArea::eInfoTab && !(m_ui->masterSplitter->sizes().at(indexOfSplitter))) + m_ui->masterSplitter->animate(1); + + + return false; + } + } + + dialog->setBaseLayoutWidget(this); + + connect(dialog, &LtsDialog::startMainSelect, + this, &LtsBaseLayoutWidget::onHideAnimated); + connect(dialog, &LtsDialog::endMainSelect, + this, &LtsBaseLayoutWidget::onReShowAnimated); + + + emit signalStapleTopicLockHandler(dialog); + dialog->triggerTopicLock(); + + + + + tab->addTab(menuGroup, identifier, helper, setClosable, focusNewTab); + + if (tab->count() > 1) + { + int indexOfSplitter = getIndexOfTab(tab); + if (area == LayoutArea::eDialogTab && !(m_ui->mainSplitter->sizes().at(indexOfSplitter))) + { + qDebug()<<"animate 1a: "; + m_ui->mainSplitter->animate(1); + } + else if (area == LayoutArea::eInfoTab && !(m_ui->masterSplitter->sizes().at(indexOfSplitter))) + { + qDebug()<<"animate 1b: "; + m_ui->masterSplitter->animate(1); + } + } + +#ifdef ADJUST_SPLITTER_SIZES_DIALOGTAB + + + if (LayoutArea::eDialogTab == area) + { + const QList & sizes = m_ui->mainSplitter->sizes(); + int requestedTabHeight = tab->sizeHint().height(); + int mainHeight = sizes[0]+sizes[1]-requestedTabHeight; + + m_ui->mainSplitter->setSizes( QList() << mainHeight << requestedTabHeight ); + } +#endif + + return true; +} + + +LtsDialog *LtsBaseLayoutWidget::getWidget(const QString &menuGroup, const QString &identifier, LayoutArea area) +{ + LtsTabWidget *tab = getTabFromLayoutArea(area); + + if (tab == nullptr) + return nullptr; + + QWidget *w = tab->getWidget(menuGroup, identifier); + + if (w == nullptr) + return nullptr; + + LtsDialogHelper *helper = qobject_cast(w); + + if (helper == nullptr) + return nullptr; + + LtsDialog *dialog = qobject_cast(helper->getDialog()); + return dialog; +} + + +void LtsBaseLayoutWidget::clearMenuActions() +{ + + while (m_ui->naviActionsPlanning->count()) + m_ui->naviActionsPlanning->removeItem(0); + while (m_ui->naviActionsExecution->count()) + m_ui->naviActionsExecution->removeItem(0); + while (m_ui->naviActionsAfterAction->count()) + m_ui->naviActionsAfterAction->removeItem(0); + while (m_ui->naviActionsSpecial->count()) + m_ui->naviActionsSpecial->removeItem(0); + + checkExercisePartButtonVisibility(); + + + for(auto it = m_mapButtons.begin(); it != m_mapButtons.end(); ++it) + delete it.key(); + + m_mapButtons.clear(); +} + + +void LtsBaseLayoutWidget::executeCreateInstantFunctions(const PluginData &plData) +{ + if (plData.createWidgetFunc) + { + LtsDialog *w = plData.createWidgetFunc(this); + + if (w) + addWidget(w, plData.menuGroup, plData.identifier, plData.layoutArea, plData.closable); + } + + if (plData.directFunc) + { + plData.directFunc(plData); + } +} + + +void LtsBaseLayoutWidget::setExerciseTime(qint64 mSecsSinceEpoch) +{ + m_exerciseTime = mSecsSinceEpoch; +} + +LtsDragIcon *LtsBaseLayoutWidget::addMenuAction(const PluginData &plData, bool isFirstRun) +{ + if (!LtsSessionData::LtsSessionData::getInstance()->hasOperatorRight(plData.operatorRight)) + { + qDebug() << " > Action" << plData.menuGroup << ":" << plData.menuText << "was not added due to missing operator right 0x" << QString::number(plData.operatorRight, 16); + return nullptr; + } + + if (isFirstRun) + { + switch (plData.createInstant) + { + case CreateInstantType::CreateInstantAndAddAction: + { + executeCreateInstantFunctions(plData); + break; + } + + case CreateInstantType::CreateInstantAndDiscardAction: + { + executeCreateInstantFunctions(plData); + return nullptr; + } + + default: + { + break; + } + } + } + else + { + if (plData.createInstant == CreateInstantType::CreateInstantAndDiscardAction) + return nullptr; + } + + + + bool err = false; + if (plData.identifier.isEmpty()) + { +#ifdef LTS_DEBUG + LtsWidgets::LtsMessageDialog::showMessage(this, QStringLiteral("STOP!\n" + "Hier wurde ein Menu Entry hinzugefuegt, welcher ueber keinen Identifier verfuegt!\n" + "Bitte aendern!\n" + "Jetzt Debugger attachen und schauen woher der aufruf kommt.\n" + "Da der Identifier fehlt kann ich Dir hier nicht sagen um welchen Menu Entry es sich handelt =)")); +#endif + err = true; + } + + if (plData.menuGroup.isEmpty()) + { +#ifdef LTS_DEBUG + LtsWidgets::LtsMessageDialog::showMessage(this, QStringLiteral("STOP!\n" + "Hier wurde ein Menu Entry hinzugefuegt, welcher ueber keine Menu Group verfuegt!\n" + "Bitte aendern!\n" + "Jetzt Debugger attachen und schauen woher der aufruf kommt.\n" + "Da der Identifier fehlt kann ich Dir hier nicht sagen um welchen Menu Entry es sich handelt =)")); +#endif + err = true; + } + + if (err) + return nullptr; + + + + bool found = false; + + if (!isFirstRun) + { + for (auto it = m_mapButtons.begin(); it != m_mapButtons.end(); ++it) + { + LtsDragIcon *thatIcon = it.key(); + + if (thatIcon->objectName() == plData.identifier) + { + thatIcon->setText(plData.menuText); + thatIcon->setContext(plData.menuGroup); + + bool groupFound = false; + + for (int i = 0; i < m_ui->stackedWidget->count(); ++i) + { + LtsWidgets::LtsToolBox *tb = qobject_cast(m_ui->stackedWidget->widget(i)); + int groupWidgetIndex = tb->indexOf(thatIcon->parentWidget() ); + if (groupWidgetIndex >= 0) + { + tb->setItemText(groupWidgetIndex, plData.menuGroup); + groupFound = true; + break; + } + } + if (!groupFound) + { + qWarning() << "group widget not found"; + } + found = true; + break; + } + } + + if (!found) + { + + } + else + { + return nullptr; + } + } + + LtsWidgets::LtsToolBox *tb = getToolBox(m_bg ? plData.exercisePart : ExercisePart::eSpecial); + + + QWidget *tmpW = tb->findChild(plData.menuGroup); + if (!tmpW) + { + + if (plData.createWidgetFunc == nullptr && plData.directFunc == nullptr) + return nullptr; + + + tmpW = new QWidget; + tmpW->setObjectName(plData.menuGroup); + + LtsGridLayout *ltsGridLayout = new LtsGridLayout(LtsWidgets::ButtonSize::eDefault, tmpW); + tmpW->setLayout(ltsGridLayout); + + tb->addItem(tmpW, plData.menuGroup); + connect(tb, &LtsToolBox::resized, + ltsGridLayout, &LtsGridLayout::onParentResize); + } + + + QLayout *widgetLayout = tmpW->layout(); + LtsGridLayout *ltsGridLayout = qobject_cast(widgetLayout); + if (!ltsGridLayout) + { + + qWarning() << "temp widget has no layout! cannot add action" << plData.menuText << "to menu! (the bad widget was of type" << tmpW->metaObject()->className() << ") [did you forget to add the menuGroup to that action?]"; + return nullptr; + } + + + if (widgetLayout->count() > 0) + { + QSpacerItem *spacer = dynamic_cast(widgetLayout->itemAt(widgetLayout->count() - 1)); + if (spacer) + { + ltsGridLayout->removeItem(spacer); + delete spacer; + } + } + + + LtsDragIcon *dragIcon = nullptr; + if (plData.createWidgetFunc == nullptr && plData.directFunc == nullptr) + { + QFrame *line = new QFrame(tmpW); + line->setLineWidth(2); + line->setFrameStyle(QFrame::HLine); + + ltsGridLayout->add_widget(line); + } + else + { + if (isFirstRun || !found) + { + dragIcon = createButton(plData); + + ltsGridLayout->add_widget(dragIcon); + m_mapButtons.insert(dragIcon, plData); + + auto it = std::find_if(m_notFoundYet.begin(), m_notFoundYet.end(), [plData](const QString &s) + { + return s == plData.identifier; + }); + + if (it != m_notFoundYet.end()) + { + m_ui->toolBar->addMenuEntry(dragIcon); + m_notFoundYet.erase(it); + } + } + } + + checkExercisePartButtonVisibility(); + + QSpacerItem *spacer = new QSpacerItem(10, 1, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); + ltsGridLayout->addItem(spacer, ltsGridLayout->rowCount(), 0); + + return dragIcon; +} + + +void LtsBaseLayoutWidget::addNotification(QToolButton *noti) +{ + m_ui->notificationLayoutWidget->layout()->addWidget(noti); +} + + +bool LtsBaseLayoutWidget::notificationExists(const QString &identifier) const +{ + return (m_ui->notificationLayoutWidget->findChild(identifier, Qt::FindDirectChildrenOnly) != nullptr); +} + + +bool LtsBaseLayoutWidget::removeMenuAction(const QString &byIdentifier) +{ + for (auto it = m_mapButtons.begin(); it != m_mapButtons.end(); ++it) + { + if (it.value().identifier == byIdentifier) + return removeMenuAction(it.key()); + } + + return false; +} + + +bool LtsBaseLayoutWidget::removeMenuAction(const PluginData &plData) +{ + for (auto it = m_mapButtons.begin(); it != m_mapButtons.end(); ++it) + { + if (it.value().identifier == plData.identifier) + return removeMenuAction(it.key()); + } + + return false; +} + + +bool LtsBaseLayoutWidget::removeMenuAction(LtsDragIcon *icon) +{ + auto it = m_mapButtons.find(icon); + if (it == m_mapButtons.end()) + return false; + + const QString menuGroup = it->menuGroup; + const ExercisePart ep = it->exercisePart; + m_mapButtons.erase(it); + + icon->setParent(nullptr); + icon->deleteLater(); + + checkMenuGroup(menuGroup, ep); + + return true; +} + +void LtsBaseLayoutWidget::executeAction(const QString &actionIdentifier) +{ + const QList plDatas(m_mapButtons.values()); + auto it = std::find_if(plDatas.constBegin(), plDatas.constEnd(), [actionIdentifier](const PluginData &plData) + { + return (plData.identifier == actionIdentifier); + }); + + if (it == plDatas.constEnd()) + return; + executePluginData(*it); +} + + +void LtsBaseLayoutWidget::executePluginData(const PluginData &plData) +{ + const QString &identifier = plData.identifier; + const QString &menuGroup = plData.menuGroup; + + LtsTabWidget *tab = getTabFromLayoutArea(plData.layoutArea); + + if (!tab->focusTab(menuGroup, identifier)) + { + + + if (plData.createWidgetFunc) + { + LtsDialog *w = plData.createWidgetFunc(this); + if (w) + { + + addWidget(w, menuGroup, identifier, plData.layoutArea, plData.closable); + } + else + { + + } + } + else + { + if (plData.directFunc) + plData.directFunc(plData); + } + } + else + { + + } +} + + +void LtsBaseLayoutWidget::onMenuEntryClicked() +{ + LtsDragIcon *btn = qobject_cast(sender()); + if (!btn) + return; + + if (!m_mapButtons.contains(btn)) + return; + + executePluginData(m_mapButtons.value(btn)); +} + + +void LtsBaseLayoutWidget::resetLayoutVisibility() +{ + for (auto i = m_oldLayoutVisibilityStates.cbegin(); i != m_oldLayoutVisibilityStates.cend(); ++i) + { + const auto &tabPtr = i.key(); + if (tabPtr) + tabPtr->setVisible(i.value()); + } + + m_oldLayoutVisibilityStates.clear(); +} + + +void LtsBaseLayoutWidget::hideVisibleLayouts() +{ + if (!m_oldLayoutVisibilityStates.isEmpty()) + { + qWarning() << "LtsBaseLayoutWidget::hideVisibleLayouts: old states available - ignoring"; + return; + } + + LtsWidgets::LtsTabWidget *infoTab = getTabFromLayoutArea(LtsWidgets::LayoutArea::eInfoTab); + LtsWidgets::LtsTabWidget *specialTab = getTabFromLayoutArea(LtsWidgets::LayoutArea::eSpecialTab); + LtsWidgets::LtsTabWidget *dialogTab = getTabFromLayoutArea(LtsWidgets::LayoutArea::eDialogTab); + + m_oldLayoutVisibilityStates.insert(infoTab, infoTab->isVisible()); + m_oldLayoutVisibilityStates.insert(specialTab, specialTab->isVisible()); + m_oldLayoutVisibilityStates.insert(dialogTab, dialogTab->isVisible()); + + + if (infoTab->isVisible()) + infoTab->setVisible(false); + if (specialTab->isVisible()) + specialTab->setVisible(false); + if (dialogTab->isVisible()) + dialogTab->setVisible(false); +} + + +void LtsBaseLayoutWidget::setTopBarVisible(bool visible) +{ + m_ui->topLayoutWidget->setVisible(visible); +} + + +bool LtsBaseLayoutWidget::isTopBarVisible() const +{ + return m_ui->topLayoutWidget->isVisible(); +} + + +bool LtsBaseLayoutWidget::isMenuCollapsed() const +{ + return m_menuCollapsed; +} + + +void LtsBaseLayoutWidget::setMenuVisible(bool visible) +{ + m_ui->rightSplitter->setVisible(visible); +} + + +bool LtsBaseLayoutWidget::isMenuVisible() const +{ + return m_ui->rightSplitter->isVisible(); +} + + +void LtsBaseLayoutWidget::collapseMenu(bool collapse) +{ + m_menuCollapsed = collapse; + if (collapse) + { + m_ui->masterSplitter->setSizes(m_menuSplitterSizes); + } + else + { + m_menuSplitterSizes = m_ui->masterSplitter->sizes(); + m_ui->masterSplitter->setSizes(QList() << 1 << 0 ); + } +} + + +void LtsBaseLayoutWidget::onReShowAnimated() +{ + QPropertyAnimation *animation = new QPropertyAnimation(m_ui->mainSplitter, "sizes", this); + animation->setDuration(500); + + + QList start = m_mainSplitterSizes; + start[getIndexOfTab(getTabFromLayoutArea(LayoutArea::eDialogTab))] = 0; + + QList end = m_mainSplitterSizes; + + animation->setStartValue(QVariant::fromValue(start)); + animation->setEndValue(QVariant::fromValue(end)); + + connect(animation, &QPropertyAnimation::finished, this, [this]() { m_currentAnimation = nullptr; }); + + m_ui->detailWidget->show(); + animation->start(QAbstractAnimation::DeleteWhenStopped); + m_isHidden = false; + + delete m_currentAnimation; + m_currentAnimation = animation; +} + + +void LtsBaseLayoutWidget::onHideAnimated() +{ + if (m_currentAnimation) + { + delete m_currentAnimation; + m_currentAnimation = nullptr; + } + + QPropertyAnimation *animation = new QPropertyAnimation(m_ui->mainSplitter, "sizes", this); + animation->setDuration(500); + + m_mainSplitterSizes = m_ui->mainSplitter->sizes(); + QList start = m_ui->mainSplitter->sizes(); + + QList end = start; + end[getIndexOfTab(getTabFromLayoutArea(LayoutArea::eDialogTab))] = 1; + + animation->setStartValue(QVariant::fromValue(start)); + animation->setEndValue(QVariant::fromValue(end)); + connect(animation, &QPropertyAnimation::finished, this, &LtsBaseLayoutWidget::hideWidgets); + connect(animation, &QPropertyAnimation::finished, this, [this]() { m_currentAnimation = nullptr; }); + + animation->start(QAbstractAnimation::DeleteWhenStopped); + m_isHidden = true; + m_currentAnimation = animation; +} + + +void LtsBaseLayoutWidget::retranslate() +{ + emit signalRetranslateActions(); + setExerciseData(m_exerciseData); + m_ui->retranslateUi(this); + +} + + +void LtsBaseLayoutWidget::changeEvent(QEvent *e) +{ + QWidget::changeEvent(e); + if (e->type() == QEvent::LanguageChange) + retranslate(); +} + + +int LtsBaseLayoutWidget::getIndexOfTab(LtsTabWidget *tab) +{ + int result = 0; + + result = m_ui->masterSplitter->indexOf(tab); + + if (result > -1) + return result; + + + + result = m_ui->mainSplitter->indexOf(tab); + + if (result > -1) + return result; + + + + result = m_ui->rightSplitter->indexOf(tab); + + if (result > -1) + return result; + + + qWarning() << "LtsBaseLayoutWidget::getIndexOfTab: tab not found in any splitter.."; + return result; +} + + +LtsToolBox* LtsBaseLayoutWidget::getToolBox(const ExercisePart &exercisePart) const +{ + switch (exercisePart) + { + case ExercisePart::eAfterAction: + return m_ui->naviActionsAfterAction; + case ExercisePart::eExecution: + return m_ui->naviActionsExecution; + case ExercisePart::ePlanning: + return m_ui->naviActionsPlanning; + case ExercisePart::eSpecial: + return m_ui->naviActionsSpecial; + } + Q_ASSERT(false); + return nullptr; +} + + +QPushButton* LtsBaseLayoutWidget::getExercisePartButton(const ExercisePart &exercisePart) const +{ + switch (exercisePart) + { + case ExercisePart::eAfterAction: + return m_ui->pbAfterAction; + case ExercisePart::eExecution: + return m_ui->pbExecution; + case ExercisePart::ePlanning: + return m_ui->pbPlanning; + case ExercisePart::eSpecial: + return m_ui->pbSpecial; + } + Q_ASSERT(false); + return nullptr; +} + + +void LtsBaseLayoutWidget::checkMenuGroup(const QString &menuGroup, const ExercisePart &exercisePart) +{ + QWidget *tmpW = getToolBox(exercisePart)->findChild(menuGroup); + if (!tmpW) + return; + +#ifdef NEVER_PRINT_DEBUG + for (int i = 0; i < tmpW->children().size(); i++) + { + QObject * whatEver = tmpW->children().at( i ); + const QString & name = whatEver->objectName(); + qCritical() << "MenuGroup child " << i << " : " << name; + } +#endif + + + + + + if (tmpW->children().size() <= 1) + delete tmpW; + checkExercisePartButtonVisibility(); +} + + +void LtsBaseLayoutWidget::checkExercisePartButtonVisibility() +{ + if (nullptr == m_bg) + { + for (int ep = (int)ExercisePart::ePlanning; ep <= (int)ExercisePart::eSpecial; ++ep) + { + getExercisePartButton(static_cast(ep))->setVisible(false); + } + return; + } + + int visibleButtons = 0; + ExercisePart lastVisibleButton = ExercisePart::ePlanning; + for (int ep = (int) ExercisePart::ePlanning; ep <= (int) ExercisePart::eSpecial; ++ep) + { + bool visible = getToolBox((ExercisePart) ep)->children().count() > 1; + getExercisePartButton((ExercisePart) ep)->setVisible(visible); + if (visible) + { + ++visibleButtons; + lastVisibleButton = (ExercisePart) ep; + } + } + if (visibleButtons == 0) + lastVisibleButton = ExercisePart::eSpecial; + getExercisePartButton(lastVisibleButton)->setChecked(true); + getExercisePartButton(lastVisibleButton)->setVisible(visibleButtons > 1); +} + + +bool LtsBaseLayoutWidget::isWidgetsHidden() const +{ + return m_ui->detailWidget->isVisible(); +} + + +void LtsBaseLayoutWidget::showWidgets() +{ + m_ui->detailWidget->show(); +} + + +void LtsBaseLayoutWidget::hideWidgets() +{ + m_ui->detailWidget->hide(); +} + + +LtsDragIcon *LtsBaseLayoutWidget::createButton(const PluginData &plData) +{ + + LtsDragIcon *tmpBtn = new LtsDragIcon(nullptr); + tmpBtn->setObjectName(plData.identifier); + tmpBtn->setButtonStyle(LtsWidgets::ButtonStyle::eTextNextToIcon); + tmpBtn->setIcon(plData.menuIcon); + tmpBtn->setContext(plData.menuGroup); + tmpBtn->setText(plData.menuText); + if (!plData.tooltip.isEmpty()) + tmpBtn->setToolTip(plData.tooltip); + tmpBtn->setDropHandlerKey(plData.dropHandlerKey); + tmpBtn->setIdentifier(plData.identifier); + + connect(tmpBtn, &QWidget::destroyed, + this, []() + { + qDebug() << "LtsDragIcon was destroyed"; + }); + + connect(tmpBtn, &LtsDragIcon::clicked, this, &LtsBaseLayoutWidget::onMenuEntryClicked); + return tmpBtn; +} + + +LtsToolBarArea * LtsBaseLayoutWidget::getToolbar() const +{ + return m_ui->toolBar; +} + + +QWidget *LtsBaseLayoutWidget::getMenu() const +{ + return m_ui->naviWidget; +} + + +LtsTabWidget *LtsBaseLayoutWidget::getTabFromLayoutArea(LayoutArea area) const +{ + switch (area) + { + case LayoutArea::eDialogTab: + return m_ui->detailWidget; + case LayoutArea::eInfoTab: + return m_ui->infoWidget; + case LayoutArea::eSpecialTab: + return m_ui->specialWidget; + case LayoutArea::eMainTab: + return m_ui->mainWidget; + case LayoutArea::eUnknown: + return m_ui->detailWidget; + } + return nullptr; +} + + +void LtsBaseLayoutWidget::showNextQueuedMessage() +{ + if (m_messageQueue.isEmpty()) + { + return; + } + + const Message m = m_messageQueue.takeLast(); + + showMessage(m.title, m.text, m.type, m.clickedLinkCallback, m.closeOnLinkClicked, m.identifier); +} + + +void LtsBaseLayoutWidget::removeQueuedMessage(const QString &messageIdentifier) +{ + if (messageIdentifier.isEmpty()) + { + return; + } + + m_messageQueue.erase(std::remove_if(m_messageQueue.begin(), m_messageQueue.end(), [messageIdentifier](const Message &m) + { + return (m.identifier == messageIdentifier); + }), m_messageQueue.end()); + + if (!m_currentMessage.isEmpty()) + { + if (m_currentMessage.first().identifier == messageIdentifier) + { + + m_currentMessage.clear(); + m_notiBox->hide(); + showNextQueuedMessage(); + } + } +} + + +void LtsBaseLayoutWidget::showMessage(const QString &title, const QString &text, LtsNotificationBox::MessageType type, const std::function &clickedLinkCallback, bool closeOnLinkClicked, const QString &identifier) +{ + if (m_notiBox == nullptr) + { + m_notiBox = new LtsNotificationBox; + + connect(m_notiBox, &LtsNotificationBox::signalLinkClicked, + this, [this, closeOnLinkClicked, clickedLinkCallback](const QString &link) + { + + + auto *event = new LtsLinkEvent(QUrl(link), this); + event->setAccepted(false); + QApplication::postEvent(qApp, event); + + if (clickedLinkCallback) + clickedLinkCallback(link); + + if (closeOnLinkClicked) + { + m_notiBox->hide(); + m_currentMessage.clear(); + showNextQueuedMessage(); + } + }); + + connect(m_notiBox, &QObject::destroyed, + this, [this]() + { + if (qobject_cast(sender()) == m_notiBox) + m_notiBox = nullptr; + }); + + connect(m_notiBox, &LtsNotificationBox::signalClose, + this, [this]() + { + m_notiBox->hide(); + m_currentMessage.clear(); + showNextQueuedMessage(); + }); + + connect(m_notiBox, &LtsNotificationBox::signalAck, + this, [this]() + { + auto *event = new LtsLinkEvent(QUrl(QStringLiteral("lts://NotificationAck/%1").arg(m_currentMessage.first().identifier)), this); + event->setAccepted(false); + QApplication::postEvent(qApp, event); + + m_notiBox->hide(); + m_currentMessage.clear(); + showNextQueuedMessage(); + }); + + m_notiBox->setParent(this); + m_notiBox->setGeometry(0, 0, m_notiBox->width(), m_notiBox->height()); + } + + + m_messageQueue.erase(std::remove_if(m_messageQueue.begin(), m_messageQueue.end(), [identifier](const Message &m) + { + return (!identifier.isEmpty() && m.identifier == identifier); + }), m_messageQueue.end()); + + if (!m_currentMessage.isEmpty()) + { + const auto m = m_currentMessage.takeFirst(); + if (identifier.isEmpty() || m.identifier != identifier) + { + m_messageQueue += m; + } + } + + m_currentMessage += Message({ title, text, type, clickedLinkCallback, closeOnLinkClicked, identifier }); + + m_notiBox->message(title, text, type); + + + + auto *animation = new QPropertyAnimation(m_notiBox, "geometry"); + connect(animation, &QPropertyAnimation::finished, animation, &QObject::deleteLater); + animation->setDuration(700); + animation->setStartValue(QRect(width() + 10, height() + 10, m_notiBox->width(), m_notiBox->height())); + animation->setEndValue(QRect(width() - m_notiBox->width() - 15, height() - m_notiBox->height() - 15, m_notiBox->width(), m_notiBox->height())); + animation->setEasingCurve(QEasingCurve::OutElastic); + animation->start(QAbstractAnimation::DeleteWhenStopped); +} + + +bool LtsBaseLayoutWidget::messageIdentifierExists(const QString &identifier) const +{ + if (identifier.isEmpty()) + { + return false; + } + + if (!m_currentMessage.isEmpty() && m_currentMessage.first().identifier == identifier) + { + return true; + } + + for (const Message &m : qAsConst(m_messageQueue)) + { + if (m.identifier == identifier) + { + return true; + } + } + + return false; +} + +} + + + + + + + + + + + + + + + + + + + + diff --git a/Lts/Common/LtsWidgets/lts_base_layout_widget.h b/Lts/Common/LtsWidgets/lts_base_layout_widget.h new file mode 100755 index 000000000..937e3d307 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_base_layout_widget.h @@ -0,0 +1,346 @@ + + + +#pragma once + +#include "lts_base.h" +#include "lts_widgets_export.h" +#include "lts_notification_box.h" +#include "qevent.h" + +#include +#include +#include +#include + + +class QButtonGroup; +class QPropertyAnimation; +class QPushButton; +class QTimer; +class QToolButton; + +namespace Ui +{ +class LtsBaseLayoutWidget; +} + +namespace IDL +{ +enum class PartitionType; +} + +namespace LtsUtils { class Promise; } + +namespace LtsWidgets +{ +class LtsDragIcon; +class LtsProgressDialogHelper; +class LtsTabWidget; +class LtsToolBarArea; +class LtsToolBox; + +struct ExerciseData +{ + QString exName; + QString exDescription; + int phId = -1; + QString phName; + QString phDescription; + QString exStateString; + ExcState exState = ExerciseState::eUnknown; + QColor exStateCol; + QString partitionType; + + bool operator==(const ExerciseData &o) const + { + return exName == o.exName && + exDescription == o.exDescription && + phId == o.phId && + phName == o.phName && + phDescription == o.phDescription && + exStateString == o.exStateString && + exState == o.exState && + exStateCol == o.exStateCol && + partitionType == o.partitionType; + } + bool operator!=(const ExerciseData &o) const + { + return !operator ==(o); + } + +}; + + + + +class LTSWIDGETS_EXPORT LtsBaseLayoutWidget : public QWidget +{ + Q_OBJECT + +public: + + explicit LtsBaseLayoutWidget(QWidget *parent = nullptr, bool splitMenu = true); + ~LtsBaseLayoutWidget(); + + + + void hideVisibleLayouts(); + + + + void resetLayoutVisibility(); + + + + void setMenuVisible(bool visible); + + + + bool isMenuVisible() const; + + bool isTopBarVisible() const; + void setTopBarVisible(bool visible); + + + + bool isMenuCollapsed() const; + + + + void collapseMenu(bool collapse = true); + + + + void setSessionInfo(const int domainId, const IDL::PartitionType &partitionType, const int partitionId, const QString &workspaceId); + + + + LtsDragIcon *addMenuAction(const PluginData &plData, bool isFirstRun = false); + + + + bool removeMenuAction(const PluginData &plData); + + + + bool removeMenuAction(LtsDragIcon *icon); + + bool removeMenuAction(const QString &byIdentifier); + + + + void clearMenuActions(); + + void setAppName(const QString &appName); + bool isDialogFocused(const LtsDialog *dialog) const; + + + + void setCustomText(const QString &text); + const QString customText() const; + + + + LtsTabWidget *getTabFromLayoutArea(LayoutArea area) const; + + + + LtsDialog *getWidget(const QString &menuGroup, const QString &identifier, LayoutArea area = LayoutArea::eUnknown); + + + + QWidget *getMenu() const; + + + + LtsToolBarArea *getToolbar() const; + + + + void setOperatorName(const QString &operatorName, bool showTerminalButton); + + + + void setProfilePicture(const QPixmap &profilePicture); + + + + void setExerciseData(const ExerciseData &data); + + + + void initilizationFinished(); + + + + void addNotification(QToolButton *noti); + + + + bool isWidgetsHidden() const; + + bool notificationExists(const QString &identifier) const; + const QMap &mapButtons() const { return m_mapButtons; } + void setToolBarActions(const QStringList &actions); + QStringList toolBarActions() const; + void setFunctionKeys(const QMap &actions); + QMap functionKeys() const; + void setWorkspaceId(const QString &workspaceId) { m_workspaceId = workspaceId; } + + + + void showMessage(const QString &title, const QString &text, LtsNotificationBox::MessageType type = LtsNotificationBox::MessageType::Info, const std::function &clickedLinkCallback = nullptr, bool closeOnLinkClicked = true, const QString &identifier = {}); + bool messageIdentifierExists(const QString &identifier) const; + + void executeAction(const QString &actionIdentifier); + bool isSecondaryWidget() const { return m_isSecondaryWidget; } + void setSecondaryWidget() { m_isSecondaryWidget = true; } + + + + void removeQueuedMessage(const QString &messageIdentifier); + + +public slots: + bool slotClose(); + void slotLogout(); + + void slotScreenCountChanged(int count); + + + + void onMenuEntryClicked(); + + void onHideAnimated(); + void onReShowAnimated(); + + + + bool addWidget(LtsWidgets::LtsDialog *dialog, const QString &menuGroup, const QString &identifier, LtsWidgets::LayoutArea area = LayoutArea::eUnknown, bool setClosable = true, bool focusNewTab = true); + + + + bool addOrReplaceWidget(LtsDialog *dialog, const QString &menuGroup, const QString &identifier, LayoutArea area = LayoutArea::eUnknown, bool setClosable = true, bool focusNewTab = true ); + + bool focusTab(const QString &menuGroup, const QString &identifier, LtsWidgets::LayoutArea area); + + + + void executeCreateInstantFunctions(const LtsWidgets::PluginData &plData); + + void setExerciseTime(qint64 mSecsSinceEpoch); + bool haveUnsavedChanges(); + + + + + + bool notifyUnsavedChanges(); + + + + void setIrreplaceableOnce() { m_irreplaceableOnce = true; } + bool irreplaceableReset() { bool r = m_irreplaceableOnce; m_irreplaceableOnce = false; return r; } + bool isIrreplaceable() const { return m_irreplaceableOnce; } + + + + void showWidgets(); + + + + void hideWidgets(); + + +private slots: + + + void slotOperatorMenuClicked(); + + void setCurrentTime(); + + +signals: + + + + + void signalClose(); + void signalLogout(); + void signalShowOperatorSettings(); + void signalRetranslateActions(); + void signalSwap(); + void signalStapleTopicLockHandler(LtsWidgets::LtsDialog *widget); + void signalSuccessfullyPlaced(); + + + + void signalNotifyUnsavedChanges(bool &reject); + +protected: + + + void closeEvent(QCloseEvent *event) override; + + void showEvent(QShowEvent *event) override; + void changeEvent(QEvent *e) override; + + +private: + void addDebugMenu(); + void retranslate(); + void addTouchScroll(QWidget *widget); + LtsDragIcon *createButton(const PluginData &plData); + int getIndexOfTab(LtsTabWidget *tab); + LtsToolBox *getToolBox(const ExercisePart &exercisePart) const; + QPushButton *getExercisePartButton(const ExercisePart &exercisePart) const; + void checkExercisePartButtonVisibility(); + void checkMenuGroup(const QString &menuGroup, const ExercisePart &exercisePart); + bool isDialogFocused(const LtsDialog *dialog, const LayoutArea &layoutArea) const; + bool isDialogFocused(const LtsDialog *dialog, const LtsTabWidget *tab) const; + void applyExerciseStateRestricition(); + void executePluginData(const PluginData &plData); + void showNextQueuedMessage(); + + + struct Message + { + public: + QString title; + QString text; + LtsNotificationBox::MessageType type; + std::function clickedLinkCallback; + bool closeOnLinkClicked; + QString identifier; + }; + + ExerciseData m_exerciseData; + QMap m_mapButtons; + QList m_mainSplitterSizes; + QList m_menuSplitterSizes; + bool m_isHidden; + QPropertyAnimation *m_currentAnimation{nullptr}; + Ui::LtsBaseLayoutWidget *m_ui{nullptr}; + IDL::PartitionType m_partitionType; + int m_domainId; + int m_partitionId; + QString m_operatorName; + bool m_firstShow; + bool m_showTerminalButton; + qint64 m_exerciseTime; + QTimer *m_setCurrentTimeTimer{nullptr}; + bool m_irreplaceableOnce; + QButtonGroup *m_bg{nullptr}; + QString m_workspaceId; + int m_screenCount; + bool m_menuCollapsed; + QMap, bool> m_oldLayoutVisibilityStates; + LtsNotificationBox *m_notiBox{nullptr}; + QStringList m_notFoundYet; + bool m_isSecondaryWidget { false }; + QVector m_currentMessage; + QVector m_messageQueue; +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_bitfield_widget.cpp b/Lts/Common/LtsWidgets/lts_bitfield_widget.cpp new file mode 100755 index 000000000..be35119b8 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_bitfield_widget.cpp @@ -0,0 +1,129 @@ + + + +#include "lts_bitfield_widget.h" + +#include + +namespace LtsWidgets +{ + +LtsBitfieldWidget::LtsBitfieldWidget(QWidget *parent, quint8 value) + : QWidget(parent) + , m_value(value) +{ + setObjectName(QString::fromUtf8("LtsBitfieldWidget")); +} + +void LtsBitfieldWidget::paintEvent(QPaintEvent *pe) +{ + QWidget::paintEvent(pe); + + QPainter painter; + painter.begin(this); + painter.setPen(Qt::SolidLine); + + if (m_hasCaption) + { + QFont font = painter.font(); + font.setPointSizeF( (height()/2.0 > width()/8.0) ? width()/8.0 : height()/2.0 ); + painter.setFont(font); + } + + for (int i = 0; i < 8; ++i) + { + QRectF bitGraphic; + + bitGraphic.setLeft(width() / 8.0 * i); + bitGraphic.setWidth((width() / 8.0) - 1); + bitGraphic.setHeight(height() - 1); + + bool bitVal = (m_value & (1 << (7 - i))) > 0; + + painter.fillRect(bitGraphic, QBrush(bitVal ? m_onColor : m_offColor, Qt::SolidPattern)); + + if (m_hasCaption) + { + painter.setPen(bitVal ? m_offColor : m_onColor); + painter.drawText(bitGraphic, Qt::AlignCenter, bitVal ? QStringLiteral("1") : QStringLiteral("0")); + } + + painter.setPen(Qt::gray); + painter.drawRect(bitGraphic); + } + + painter.end(); +} + +void LtsBitfieldWidget::setHasCaption(bool hasCaption) +{ + if (m_hasCaption == hasCaption) + return; + + m_hasCaption = hasCaption; + repaint(); + + emit hasCaptionChanged(hasCaption); +} + +void LtsBitfieldWidget::setOnColor(const QColor &onColor) +{ + if (m_onColor == onColor) + return; + + m_onColor = onColor; + repaint(); +} + +void LtsBitfieldWidget::setOffColor(const QColor &offColor) +{ + if (m_offColor == offColor) + return; + + m_offColor = offColor; + repaint(); +} + +void LtsBitfieldWidget::mousePressEvent(QMouseEvent *event) +{ + if (!m_isEditable) + { + QWidget::mousePressEvent(event); + return; + } + + for (int i = 0; i < 8; ++i) + { + if (event->pos().x() > width()/8 * i && + event->pos().x() < width()/8 * (i + 1)) + { + m_value ^= (1 << (7 - i)); + emit valueChanged(m_value); + break; + } + } + repaint(); +} + +void LtsBitfieldWidget::setEditable(bool editable) +{ + if (m_isEditable == editable) + return; + + m_isEditable = editable; + + emit isEditableChanged(m_isEditable); +} + +void LtsBitfieldWidget::setValue(quint8 value) +{ + if (m_value == value) + return; + + m_value = value; + repaint(); + + emit valueChanged(m_value); +} + +} diff --git a/Lts/Common/LtsWidgets/lts_bitfield_widget.h b/Lts/Common/LtsWidgets/lts_bitfield_widget.h new file mode 100755 index 000000000..889fb79cf --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_bitfield_widget.h @@ -0,0 +1,95 @@ + + + +#pragma once + +#include +#include + +#include "lts_widgets_export.h" + + +namespace LtsWidgets +{ + +class LTSWIDGETS_EXPORT LtsBitfieldWidget : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(quint8 value READ value WRITE setValue NOTIFY valueChanged) + Q_PROPERTY(bool isEditable READ isEditable WRITE setEditable NOTIFY isEditableChanged) + Q_PROPERTY(bool hasCaption READ hasCaption WRITE setHasCaption NOTIFY hasCaptionChanged) + Q_PROPERTY(QColor onColor READ onColor WRITE setOnColor) + Q_PROPERTY(QColor offColor READ offColor WRITE setOffColor) + +public: + explicit LtsBitfieldWidget(QWidget *parent = nullptr, quint8 value = 0); + +signals: + void valueChanged(quint8 newValue); + void isEditableChanged(bool option); + void hasCaptionChanged(bool option); + +public slots: + + + void setValue(quint8 value); + + + + void setEditable(bool editable); + + + + void setHasCaption(bool hasCaption); + + + + void setOnColor(const QColor &onColor); + + + + void setOffColor(const QColor &offColor); + + +public: + + + quint8 value() const { return m_value; } + + + + bool isEditable() const { return m_isEditable; } + + + + bool hasCaption() const { return m_hasCaption; } + + + + QColor onColor() const {return m_onColor;} + + + + QColor offColor() const {return m_offColor;} + +private: + + + void mousePressEvent(QMouseEvent *event) override; + + + + void paintEvent(QPaintEvent *pe) override; + +private: + quint8 m_value = 0; + + bool m_isEditable = true; + bool m_hasCaption = true; + + QColor m_onColor = Qt::black; + QColor m_offColor = Qt::white; +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_checkable_table_model.h b/Lts/Common/LtsWidgets/lts_checkable_table_model.h new file mode 100755 index 000000000..93d22ecf4 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_checkable_table_model.h @@ -0,0 +1,131 @@ + + + +#pragma once + +#include "lts_abstract_table_model.h" + + +namespace LtsWidgets +{ + +template +class LtsCheckableTableModel : public LtsAbstractTableModel +{ +public: + LtsCheckableTableModel(const QVector &header, int checkableColumn, QObject *parent) + : LtsAbstractTableModel(header, parent) + , m_checkableColumn(checkableColumn) + { + } + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override + { + if (role == Qt::CheckStateRole && index.column() == m_checkableColumn && LtsAbstractTableModel::indexIsValid(index)) + return LtsAbstractTableModel::m_vecData.at(index.row()).first ? Qt::Checked : Qt::Unchecked; + return QVariant(); + } + + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override + { + if (role == Qt::CheckStateRole) + { + switchCheckbox(index); + return true; + } + return QAbstractItemModel::setData(index, value, role); + } + + Qt::ItemFlags flags(const QModelIndex &index) const override + { + Qt::ItemFlags flags(LtsWidgets::LtsAbstractTableModel::flags(index) & ~Qt::ItemIsSelectable); + if (index.column() == m_checkableColumn) + return flags.setFlag(Qt::ItemIsUserCheckable); + + return flags; + } + + QVector getCheckedIds() const + { + QVector ids; + + for (int row = 0; row < LtsAbstractTableModel::rowCount(); ++row) + { + const T &entry = LtsAbstractTableModel::m_vecData.at(row); + if (entry.first) + ids << entry.second.id(); + } + + return ids; + } + + QVector getUncheckedIds() const + { + QVector ids; + + for (int row = 0; row < LtsAbstractTableModel::rowCount(); ++row) + { + const T &entry = LtsAbstractTableModel::m_vecData.at(row); + if (!entry.first) + ids << entry.second.id(); + } + + return ids; + } + + bool setChecked(const QVector &vecCheckedIds) + { + for (int row = 0; row < LtsAbstractTableModel::rowCount(); ++row) + { + T &entry = LtsAbstractTableModel::m_vecData[row]; + bool bContains = vecCheckedIds.contains(entry.second.id()); + if (entry.first != bContains) + { + entry.first = bContains; + const QModelIndex idx(LtsAbstractTableModel::index(row, m_checkableColumn, QModelIndex())); + emit LtsAbstractTableModel::dataChanged(idx, idx, {Qt::CheckStateRole}); + } + } + + return true; + } + bool isFiltered(const T &data, DataModification modType) const override + { + Q_UNUSED(data); + Q_UNUSED(modType); + return false; + } + +public slots: + void switchCheckbox(const QModelIndex &index) + { + auto entry = LtsAbstractTableModel::getRowData(index.row()); + entry.first = !entry.first; + LtsAbstractTableModel::setRowData(index.row(), entry); + } + + void selectAll() + { + for (int row = 0; row < LtsAbstractTableModel::rowCount(); ++row) + { + T entry = LtsAbstractTableModel::m_vecData.at(row); + entry.first = true; + LtsAbstractTableModel::setRowData(row, entry); + } + } + + void unselectAll() + { + for (int row = 0; row < LtsAbstractTableModel::rowCount(); ++row) + { + T entry = LtsAbstractTableModel::m_vecData.at(row); + entry.first = false; + LtsAbstractTableModel::setRowData(row, entry); + } + } + +private: + int m_checkableColumn; + +}; +} diff --git a/Lts/Common/LtsWidgets/lts_column_filter_proxy_model.cpp b/Lts/Common/LtsWidgets/lts_column_filter_proxy_model.cpp new file mode 100755 index 000000000..2a9efa15b --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_column_filter_proxy_model.cpp @@ -0,0 +1,156 @@ + + + +#include "lts_column_filter_proxy_model.h" + +#include + +#include + + +namespace LtsWidgets +{ +LtsColumnFilterProxyModel::LtsColumnFilterProxyModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ + setSortCaseSensitivity(Qt::CaseInsensitive); +} + +bool LtsColumnFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + return LtsUtils::lessThan(sourceModel()->data(left), sourceModel()->data(right)); +} + +void LtsColumnFilterProxyModel::onSetColumnStringFilter(const QString &newFilterString, int column) +{ + setColumnStringFilter(newFilterString, column); +} + +void LtsColumnFilterProxyModel::onSetColumnDataFilter(const QVariant &newFilterData, int column) +{ + setColumnDataFilter(newFilterData, column); +} + +void LtsColumnFilterProxyModel::setColumnStringFilter(const QString &newFilterString, int column) +{ + if (column < 0) + return; + auto it = m_columnStringFilters.find(column); + if (newFilterString.isEmpty()) + { + if (it == m_columnStringFilters.end()) + return; + m_columnStringFilters.erase(it); + } + else + { + auto rx = QRegularExpression(newFilterString, + QRegularExpression::CaseInsensitiveOption); + if (it == m_columnStringFilters.end()) + { + m_columnStringFilters.insert(column, rx); + } + else + { + if (rx == it.value()) + return; + it.value() = rx; + } + } + invalidateFilter(); +} + +void LtsColumnFilterProxyModel::setColumnDataFilter(const QVariant &newFilterData, int column) +{ + if (column < 0) + return; + auto it = m_columnDataFilters.find(column); + if (newFilterData.isNull()) + { + if (it == m_columnDataFilters.end()) + return; + m_columnDataFilters.erase(it); + } + else + { + if (it == m_columnDataFilters.end()) + { + m_columnDataFilters[column] = newFilterData; + } + else + { + if (newFilterData == it.value()) + return; + it.value() = newFilterData; + } + } + invalidateFilter(); +} + + +bool LtsColumnFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + + const auto sm = sourceModel(); + bool bRet = true; + for (auto it = m_columnDataFilters.begin(); bRet && it != m_columnDataFilters.end(); ++it) + { + const QModelIndex idx = sm->index(source_row, it.key(), source_parent); + const QVariant _data = sm->data(idx, LtsWidgets::LtsColumnFilterProxyModel::ComboboxFilterRole).toString(); + bRet = _data == it.value(); + if (!bRet) + break; + } + + for (auto it = m_columnStringFilters.begin(); bRet && it != m_columnStringFilters.end(); ++it) + { + const QRegularExpression &rx = it.value(); + if (!rx.isValid()) + continue; + const QModelIndex idx = sm->index(source_row, it.key(), source_parent); + const QString _data = sm->data(idx, Qt::DisplayRole).toString(); + bRet = _data.contains(rx); + if (!bRet) + break; + } + +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) + if (!bRet && m_bRecursiveFiltering) + { + const QModelIndex idx = sm->index(source_row, 0, source_parent); + if (checkIfOneChildIsVisible(idx)) + return true; + } +#endif + + return bRet; +} + +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) +void LtsColumnFilterProxyModel::setRecursiveFilteringEnabled(bool recursive) +{ + if (m_bRecursiveFiltering == recursive) + return; + m_bRecursiveFiltering = recursive; + invalidateFilter(); +} + +bool LtsColumnFilterProxyModel::recursiveFilteringEnabled() const +{ + return m_bRecursiveFiltering; +} + +bool LtsColumnFilterProxyModel::checkIfOneChildIsVisible(const QModelIndex &parent) const +{ + const auto sm = sourceModel(); + const int childCount = sm->rowCount(parent); + for (int i = 0; i < childCount; ++i) + { + if (filterAcceptsRow(i, parent)) + return true; + } + return false; +} +#endif + +} diff --git a/Lts/Common/LtsWidgets/lts_column_filter_proxy_model.h b/Lts/Common/LtsWidgets/lts_column_filter_proxy_model.h new file mode 100755 index 000000000..90f841dc4 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_column_filter_proxy_model.h @@ -0,0 +1,122 @@ + + + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + + +namespace LtsWidgets +{ +struct ComboboxFilterData +{ + QString text; + QVariant data; + QIcon icon; + + inline bool operator<(const ComboboxFilterData &cfd) + { + return LtsUtils::lessThan(data, cfd.data); + } + inline friend QDataStream &operator<<(QDataStream &ds, const ComboboxFilterData &cfd) + { + ds << cfd.text << cfd.data << cfd.icon; + return ds; + } + inline friend QDataStream &operator>>(QDataStream &ds, ComboboxFilterData &cfd) + { + ds >> cfd.text >> cfd.data >> cfd.icon; + return ds; + } +}; + + +struct ComboboxFilterContent +{ + QVector data; + bool sortItems = false; + bool isValid() const { return m_isValid; } + + QVariant toVariant() const + { + if (!isValid()) + return QVariant(); + QByteArray ba; + QDataStream ds(&ba, QIODevice::WriteOnly); + ds << (*this); + return ba; + } + + static ComboboxFilterContent fromVariant(const QVariant &v) + { + ComboboxFilterContent ret; + QDataStream ds(v.toByteArray()); + ds.startTransaction(); + ds >> ret; + ds.commitTransaction(); + ret.m_isValid = ds.status() == QDataStream::Ok; + + return ret; + } + + inline friend QDataStream &operator<<(QDataStream &ds, const ComboboxFilterContent &cfc) + { + ds << cfc.data << cfc.sortItems; + return ds; + } + inline friend QDataStream &operator>>(QDataStream &ds, ComboboxFilterContent &cfc) + { + ds >> cfc.data >> cfc.sortItems; + return ds; + } + +private: + bool m_isValid = true; +}; + + +class LTSWIDGETS_EXPORT LtsColumnFilterProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + static constexpr int ComboboxFilterRole = Qt::UserRole + 17; + + explicit LtsColumnFilterProxyModel(QObject *parent = nullptr); + +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) + void setRecursiveFilteringEnabled(bool recursive); + bool recursiveFilteringEnabled() const; +#endif + + const QHash &columnStringFilters() const { return m_columnStringFilters; } + const QHash &columnDataFilters() const { return m_columnDataFilters; } + + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + + virtual void setColumnStringFilter(const QString &newFilterString, int column); + virtual void setColumnDataFilter(const QVariant &newFilterData, int column); + +public slots: + void onSetColumnStringFilter(const QString &newFilterString, int column); + void onSetColumnDataFilter(const QVariant &newFilterData, int column); + +protected: + virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) + bool checkIfOneChildIsVisible(const QModelIndex &parent) const; +#endif +private: + QHash m_columnStringFilters; + QHash m_columnDataFilters; +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) + bool m_bRecursiveFiltering = false; +#endif +}; +} diff --git a/Lts/Common/LtsWidgets/lts_column_organiser_widget.ui b/Lts/Common/LtsWidgets/lts_column_organiser_widget.ui new file mode 100755 index 000000000..e51415e80 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_column_organiser_widget.ui @@ -0,0 +1,75 @@ + + + LtsColumnOrganiserWidget + + + + 0 + 0 + 419 + 402 + + + + Form + + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + false + + + + + + + Qt::Horizontal + + + + 178 + 20 + + + + + + + + + + Reset + + + + + + + Apply + + + + + + + Cancel + + + + + + + + + + diff --git a/Lts/Common/LtsWidgets/lts_combo_box.cpp b/Lts/Common/LtsWidgets/lts_combo_box.cpp new file mode 100755 index 000000000..ccbd3bea6 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_combo_box.cpp @@ -0,0 +1,118 @@ + + + +#include "lts_combo_box.h" +#include "lts_selection_popup.h" +#include "lts_message_dialog.h" + +#include +#include + + +#ifdef NEVER +# include +#endif + +namespace LtsWidgets +{ + +bool LtsComboBox::s_useSelectionDialog = false; + +LtsComboBox::LtsComboBox(QWidget *parent) + : QComboBox(parent) + , m_currentIndex(-1) +{ + connect(this, &LtsComboBox::signalExternallyChanged, + this, &LtsComboBox::signalRefill); +} + +void LtsComboBox::setUseSelectionDialog(bool bUseSelectionDialog) +{ + s_useSelectionDialog = bUseSelectionDialog; +} + +void LtsComboBox::showEvent(QShowEvent *e) +{ + QComboBox::showEvent(e); + + m_currentIndex = currentIndex(); +} + +void LtsComboBox::enterEvent(QEvent *e) +{ + BaseClass::enterEvent(e); + emit signalEnterEvent(); +} + +void LtsComboBox::leaveEvent(QEvent *e) +{ + BaseClass::leaveEvent(e); + emit signalLeaveEvent(); +} + +void LtsComboBox::showPopup() +{ + bool bUseSelectionDialog = s_useSelectionDialog; + if (QGuiApplication::queryKeyboardModifiers() == Qt::ControlModifier) + bUseSelectionDialog = !bUseSelectionDialog; + if (!bUseSelectionDialog) + { + QComboBox::showPopup(); + return; + } + + if (!model()->rowCount()) + { + LtsWidgets::LtsMessageDialog::showMessage(this, tr("There are no items available!")); + return; + } + + + LtsSelectionPopup* popup = new LtsSelectionPopup(model(), currentIndex(), this); + popup->setAttribute(Qt::WA_DeleteOnClose); + +#ifdef NEVER + + + + + + + + connect(popup, static_cast(&LtsSelectionPopup::currentIndexChanged), this, [](int idx){ + qCritical() << " LtsComboBox: called: currentIndexChanged(int), idx=" << idx; + }); + + + connect(popup, static_cast(&LtsSelectionPopup::activated), this, [](int idx){ + qCritical() << " LtsComboBox: called: activated(int), idx=" << idx; + }); + +#endif + + + + connect(popup, &LtsSelectionPopup::currentIndexChanged, + this, &LtsComboBox::userChangedIndex); + connect(popup, &LtsSelectionPopup::activated, + this, static_cast(&LtsComboBox::activated)); + + popup->show(); +} + + +void LtsComboBox::userChangedIndex(int index) +{ + m_currentIndex = index; + setCurrentIndex(index); +} + + +bool LtsComboBox::hasValueChanged() const +{ + if (m_currentIndex == -1) + return false; + return !(currentIndex() == m_currentIndex); +} + +} diff --git a/Lts/Common/LtsWidgets/lts_combo_box.h b/Lts/Common/LtsWidgets/lts_combo_box.h new file mode 100755 index 000000000..5264ce5c4 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_combo_box.h @@ -0,0 +1,59 @@ + + + +#pragma once + +#include "lts_widgets_export.h" + +#include + + +namespace LtsWidgets +{ + + + +class LTSWIDGETS_EXPORT LtsComboBox : public QComboBox +{ + Q_OBJECT + using BaseClass = QComboBox; + +public: + explicit LtsComboBox(QWidget *parent = nullptr); + ~LtsComboBox() = default; + + + + static void setUseSelectionDialog(bool bUseSelectionDialog); + + + + bool hasValueChanged() const; + + void setExternalContentType(int ect) { m_externalContentType = ect; } + int externalContentType() const { return m_externalContentType; } + +protected: + + + void showPopup() override; + + void showEvent(QShowEvent *e) override; + void enterEvent(QEvent *e) override; + void leaveEvent(QEvent *) override; + +protected slots: + void userChangedIndex(int index); + +signals: + void signalExternallyChanged(); + void signalRefill(); + void signalEnterEvent(); + void signalLeaveEvent(); + +private: + int m_currentIndex; + int m_externalContentType; + static bool s_useSelectionDialog; +}; +} diff --git a/Lts/Common/LtsWidgets/lts_date_time_edit.cpp b/Lts/Common/LtsWidgets/lts_date_time_edit.cpp new file mode 100755 index 000000000..f2a316504 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_date_time_edit.cpp @@ -0,0 +1,1785 @@ + + + +#include "lts_date_time_edit.h" +#include "lts_date_time_edit_p.h" + +#include "ui_lts_date_time_edit_touch.h" + + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static constexpr qint64 s_oneSecondMSec = 1000LL; +static constexpr qint64 s_oneMinuteMSec = 60 * s_oneSecondMSec; +static constexpr qint64 s_oneHourMSec = 60 * s_oneMinuteMSec; +static constexpr qint64 s_oneDayMSec = 24 * s_oneHourMSec; + +namespace Ui +{ +class LtsDateTimeEditLe +{ +public: + LtsWidgets::DigitsLineEdit *leLongDay = nullptr; + LtsWidgets::DigitsLineEdit *leLongHHMMSS = nullptr; + LtsWidgets::LineEdit *leLongTZ = nullptr; + LtsWidgets::DigitsLineEdit *leLongMonth = nullptr; + LtsWidgets::DigitsLineEdit *leLongYear = nullptr; + QCheckBox *checkBox = nullptr; + QGridLayout *gridLayout = nullptr; + QPushButton *pbBBB = nullptr; + QPushButton *pbBB = nullptr; + QPushButton *pbB = nullptr; + QPushButton *pbF = nullptr; + QPushButton *pbFF = nullptr; + QPushButton *pbFFF = nullptr; + QPushButton *pbNow = nullptr; + QSlider *slTime = nullptr; + + static void setFixedSize(QLineEdit *le, int charCount) + { + QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + sizePolicy.setVerticalStretch(0); + sizePolicy.setHeightForWidth(false); + + const QFontMetricsF fm(le->font()); + const auto textMargins = le->textMargins(); + le->setSizePolicy(sizePolicy); + charCount += 3; +#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0) + const qreal w = fm.width(QLatin1Char('x')) * charCount + textMargins.left() + textMargins.right(); +#else + const qreal w = fm.horizontalAdvance(QLatin1Char('x')) * charCount + textMargins.left() + textMargins.right(); +#endif + le->setMinimumWidth(w); + le->setMaximumWidth(w); + } + + void setupUi(QWidget *parent) + { + gridLayout = new QGridLayout(parent); + gridLayout->setMargin(0); + + int iStart = 3; + + checkBox = new QCheckBox; + gridLayout->addWidget(checkBox, 0, iStart++, 1, 1); + + auto horizontalLayout = new QHBoxLayout; + horizontalLayout->setSpacing(0); + horizontalLayout->setContentsMargins(0, 0, 0, 0); + + leLongDay = new LtsWidgets::DigitsLineEdit(parent); + setFixedSize(leLongDay, 2); + leLongDay->setObjectName(QStringLiteral("leLongDay")); + horizontalLayout->addWidget(leLongDay); + + leLongHHMMSS = new LtsWidgets::DigitsLineEdit(parent); + leLongHHMMSS->setExactNumDigits(6); + setFixedSize(leLongHHMMSS, 6); + leLongHHMMSS->setObjectName(QStringLiteral("leLongHHMMSS")); + horizontalLayout->addWidget(leLongHHMMSS); + + leLongTZ = new LtsWidgets::LineEdit(parent); + leLongTZ->setAlignment(Qt::AlignCenter); + setFixedSize(leLongTZ, 1); + leLongTZ->setObjectName(QStringLiteral("leLongTZ")); + horizontalLayout->addWidget(leLongTZ); + + leLongMonth = new LtsWidgets::DigitsLineEdit(parent); + leLongMonth->setAlignment(Qt::AlignCenter); + setFixedSize(leLongMonth, 3); + leLongMonth->setObjectName(QStringLiteral("leLongMonth")); + horizontalLayout->addWidget(leLongMonth); + + leLongYear = new LtsWidgets::DigitsLineEdit(parent); + leLongYear->setExactNumDigits(2); + setFixedSize(leLongYear, 2); + leLongYear->setObjectName(QStringLiteral("leLongYear")); + horizontalLayout->addWidget(leLongYear); + + gridLayout->addLayout(horizontalLayout, 0, iStart++, 1, 1); + + pbNow = new QPushButton(QApplication::translate("LtsDateTimeEdit", "Now", nullptr)); + pbNow->setObjectName(QStringLiteral("pbNow")); + gridLayout->addWidget(pbNow, 0, iStart++, 1, 1); + + QWidget::setTabOrder(checkBox, leLongDay); + QWidget::setTabOrder(leLongDay, leLongHHMMSS); + QWidget::setTabOrder(leLongHHMMSS, leLongTZ); + QWidget::setTabOrder(leLongTZ, leLongMonth); + QWidget::setTabOrder(leLongMonth, leLongYear); + QWidget::setTabOrder(leLongYear, pbNow); + + parent->setFocusProxy(leLongDay); + } + + static QPushButton *addButton(const QString &text, QWidget *parent) + { + QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + auto pb = new QPushButton(parent); + pb->setMinimumWidth(27); + pb->setMaximumWidth(27); + pb->setText(text); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + sizePolicy.setHeightForWidth(pb->sizePolicy().hasHeightForWidth()); + pb->setSizePolicy(sizePolicy); + return pb; + } + + void addButtons(QWidget *parent) + { + pbBBB = addButton(QLatin1String("<<<"), parent); + pbBB = addButton(QLatin1String("<<"), parent); + pbB = addButton(QLatin1String("<"), parent); + gridLayout->addWidget(pbBBB, 0, 0, 1, 1); + gridLayout->addWidget(pbBB, 0, 1, 1, 1); + gridLayout->addWidget(pbB, 0, 2, 1, 1); + + int iStart = 5; + pbF = addButton(QLatin1String(">"), parent); + pbFF = addButton(QLatin1String(">>"), parent); + pbFFF = addButton(QLatin1String(">>>"), parent); + gridLayout->addWidget(pbF, 0, iStart++, 1, 1); + gridLayout->addWidget(pbFF, 0, iStart++, 1, 1); + gridLayout->addWidget(pbFFF, 0, iStart++, 1, 1); + } + + void addSlider(QWidget *parent) + { + slTime = new QSlider(parent); + slTime->setOrientation(Qt::Horizontal); + gridLayout->addWidget(slTime, 1, 0, 1, 7); + } +}; +} + + +namespace LtsWidgets +{ + + + + +void setItemText(LtsWidgets::LineEdit *lineEdit, const QString &text) +{ + if (!lineEdit) + return; + lineEdit->setText(text); +} +void setItemText(QComboBox *comboxBox, const QString &text) +{ + if (!comboxBox) + return; + comboxBox->setCurrentIndex(comboxBox->findText(text)); +} +void setItemData(QComboBox *comboxBox, int data) +{ + if (!comboxBox) + return; + comboxBox->setCurrentIndex(comboxBox->findData(data)); +} +void setItemValue(QSpinBox *spinBox, int value) +{ + if (!spinBox) + return; + spinBox->setValue(value); +} + + +QString getItemText(LtsWidgets::LineEdit *lineEdit) +{ + if (!lineEdit) + return {}; + return lineEdit->text(); +} +QString getItemText(QComboBox *comboxBox) +{ + if (!comboxBox || (comboxBox->count() <= 0)) + return {}; + const QString &curTest = comboxBox->currentText(); + if (curTest.isEmpty()) + return {}; + return curTest.at(0).toUpper(); +} +int getItemData(QComboBox *comboxBox) +{ + if (!comboxBox || (comboxBox->count() <= 0)) + return {}; + int data = comboxBox->currentData().toInt(); + return data; +} +int getItemValue(QSpinBox *spinBox) +{ + if (!spinBox) + return 0; + return spinBox->value(); +} + + + + + + +LtsDateTimeEdit::LtsDateTimeEdit(QWidget *parent, bool withNowButton, bool checkable) + : QWidget(parent) + + , m_scFixed(new QShortcut(QKeySequence(Qt::ShiftModifier + Qt::Key_F2), this, SLOT(setFixedDateTime()), nullptr, Qt::WidgetWithChildrenShortcut)) + , m_scOffset(new QShortcut(QKeySequence(Qt::Key_F2), this, SLOT(setOffsetDateTime()), nullptr, Qt::WidgetWithChildrenShortcut)) + , m_checkable(checkable) + , m_withNowButton(withNowButton) +{ + +#ifdef TABLET_MODE + + setupUiTouch(); + return; +#else + + m_uiLe = new Ui::LtsDateTimeEditLe; + m_uiLe->setupUi(this); + + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + setFocusPolicy(Qt::StrongFocus); + setContentsMargins(QMargins()); + + QCompleter *completer = new QCompleter(DTG::months(), this); + completer->setCaseSensitivity(Qt::CaseInsensitive); + completer->setCompletionMode(QCompleter::InlineCompletion); + + QRegularExpression rx(DTG::months().join(QLatin1Char('|'))); + QRegularExpressionValidator *val = new QRegularExpressionValidator(rx, this); + m_uiLe->leLongMonth->setCompleter(completer); + m_uiLe->leLongMonth->setValidator(val); + + connect(m_uiLe->leLongDay, &DigitsLineEdit::textEdited, this, &LtsDateTimeEdit::valueDayChanged); + connect(m_uiLe->leLongMonth, &LineEdit::textEdited, this, &LtsDateTimeEdit::valueMonthChanged2); + connect(m_uiLe->leLongTZ, &LineEdit::textEdited, this, &LtsDateTimeEdit::valueTimezoneChangedStr); + connect(m_uiLe->leLongYear, &DigitsLineEdit::textEdited, this, &LtsDateTimeEdit::valueYearChanged); + + connect(m_uiLe->leLongHHMMSS, &DigitsLineEdit::focusOut, this, &LtsDateTimeEdit::onFocusOutHHMMSS); + connect(m_uiLe->leLongDay, &DigitsLineEdit::focusOut, this, &LtsDateTimeEdit::onFocusOutDay); + connect(m_uiLe->leLongMonth, &LineEdit::focusOut, this, &LtsDateTimeEdit::onFocusOutMonth); + connect(m_uiLe->leLongTZ, &LineEdit::focusOut, this, &LtsDateTimeEdit::onFocusOutTZ); + connect(m_uiLe->leLongYear, &DigitsLineEdit::focusOut, this, &LtsDateTimeEdit::onFocusOutYear); + + connect(m_uiLe->leLongHHMMSS, &DigitsLineEdit::focusOutBackspace, this, &LtsDateTimeEdit::onFocusOutBackspace); + connect(m_uiLe->leLongDay, &DigitsLineEdit::focusOutBackspace, this, &LtsDateTimeEdit::onFocusOutBackspace); + connect(m_uiLe->leLongMonth, &LineEdit::focusOutBackspace, this, &LtsDateTimeEdit::onFocusOutBackspace); + connect(m_uiLe->leLongTZ, &LineEdit::focusOutBackspace, this, &LtsDateTimeEdit::onFocusOutBackspace); + connect(m_uiLe->leLongYear, &DigitsLineEdit::focusOutBackspace, this, &LtsDateTimeEdit::onFocusOutBackspace); + + connect(m_uiLe->leLongHHMMSS, &DigitsLineEdit::wheelEventReceived, this, &LtsDateTimeEdit::onHmsWheelEvent); + connect(m_uiLe->leLongDay, &DigitsLineEdit::wheelEventReceived, this, &LtsDateTimeEdit::onDayWheelEvent); + connect(m_uiLe->leLongMonth, &DigitsLineEdit::wheelEventReceived, this, &LtsDateTimeEdit::onMonthWheelEvent); + connect(m_uiLe->leLongYear, &DigitsLineEdit::wheelEventReceived, this, &LtsDateTimeEdit::onYearWheelEvent); + + connect(m_uiLe->checkBox, &QCheckBox::toggled, this, &LtsDateTimeEdit::onCheckBoxChanged); + connect(m_uiLe->pbNow, &QPushButton::clicked, this, &LtsDateTimeEdit::setOffsetDateTime); + connect(checkBox(), &QCheckBox::toggled, this, &LtsDateTimeEdit::toggled); + + setTimezone(DTG::timezone()); + + setCheckable(m_checkable); + + setWithNowButton(m_withNowButton); + + onCheckBoxChanged(); +#endif +} + +LtsDateTimeEdit::~LtsDateTimeEdit() +{ + delete m_uiLe; +} + +bool LtsDateTimeEdit::setupUiTouch() +{ + m_uiTouch = new Ui::LtsDateTimeEdit; + m_uiTouch->setupUi(this); + setFocusPolicy(Qt::StrongFocus); + + for (int i = 65; i < 91; i++ ) + { + if (QChar(i).toUpper() == QChar::fromLatin1('I')) + continue; + m_uiTouch->cbxTimeZone->addItem(QChar(i), QChar(i)); + } + + connect(m_uiTouch->pbNow, &QPushButton::clicked, + this, &LtsDateTimeEdit::setOffsetDateTime); + connect(m_uiTouch->cbxMonth, static_cast(&QComboBox::currentIndexChanged), + this, &LtsDateTimeEdit::dateTimeChanged); + connect(m_uiTouch->spDay, &QSpinBox::editingFinished, + this, &LtsDateTimeEdit::dateTimeChanged); + connect(m_uiTouch->spHour, &QSpinBox::editingFinished, + this, &LtsDateTimeEdit::dateTimeChanged); + connect(m_uiTouch->spMinutes, &QSpinBox::editingFinished, + this, &LtsDateTimeEdit::dateTimeChanged); + connect(m_uiTouch->spSeconds, &QSpinBox::editingFinished, + this, &LtsDateTimeEdit::dateTimeChanged); + connect(m_uiTouch->spYear, &QSpinBox::editingFinished, + this, &LtsDateTimeEdit::dateTimeChanged); + + connect(m_uiTouch->checkBox, &QCheckBox::toggled, + this, &LtsDateTimeEdit::onCheckBoxChanged); + connect(m_uiTouch->cbxTimeZone, static_cast(&QComboBox::currentIndexChanged), + this, &LtsDateTimeEdit::valueTimezoneChangedInt); + + + + connect(m_uiTouch->cbxMonth, &QComboBox::currentTextChanged, + this, &LtsDateTimeEdit::valueMonthChanged); + + connect(checkBox(), &QCheckBox::toggled, this, &LtsDateTimeEdit::toggled); + + setMinMaxMonth(1,12); + + setTimezone(DTG::timezone()); + + + setCheckable(m_checkable); + + setWithNowButton(m_withNowButton); + + onCheckBoxChanged(); + + return true; +} + + +void LtsDateTimeEdit::setRetrieveCurrentTimeCallback(const CurrentTimeCallback &cb) +{ + m_getCurrentTimeCallback = cb; +} + +void LtsDateTimeEdit::setEditTimezone(const bool edit) +{ + m_editTimezone = edit; + if (m_uiLe) + m_uiLe->leLongTZ->setEnabled(m_editTimezone); + if (m_uiTouch) + m_uiTouch->cbxTimeZone->setEnabled(m_editTimezone); +} + +void LtsDateTimeEdit::setWithButtons(const bool buttons) +{ + m_withButtons = buttons; + if (buttons) + { + if (m_uiLe && !m_uiLe->pbBBB) + { + m_uiLe->addButtons(this); + connect(m_uiLe->pbBBB, &QPushButton::clicked, this, &LtsDateTimeEdit::onBackHourClicked); + connect(m_uiLe->pbBB, &QPushButton::clicked, this, &LtsDateTimeEdit::onBackMinuteClicked); + connect(m_uiLe->pbB, &QPushButton::clicked, this, &LtsDateTimeEdit::onBackSecondClicked); + connect(m_uiLe->pbF, &QPushButton::clicked, this, &LtsDateTimeEdit::onForwardSecondClicked); + connect(m_uiLe->pbFF, &QPushButton::clicked, this, &LtsDateTimeEdit::onForwardMinuteClicked); + connect(m_uiLe->pbFFF, &QPushButton::clicked, this, &LtsDateTimeEdit::onForwardHourClicked); + disableControls(); + } + } + else + { + if (m_uiLe) + { + delete m_uiLe->pbBBB; + delete m_uiLe->pbBB; + delete m_uiLe->pbB; + delete m_uiLe->pbFFF; + delete m_uiLe->pbFF; + delete m_uiLe->pbF; + + m_uiLe->pbBBB = nullptr; + m_uiLe->pbBB = nullptr; + m_uiLe->pbB = nullptr; + m_uiLe->pbFFF = nullptr; + m_uiLe->pbFF = nullptr; + m_uiLe->pbF = nullptr; + } + } +} + +void LtsDateTimeEdit::setWithSlider(const bool slider) +{ + m_withSlider = slider; + + if (!m_uiLe) + return; + + if (slider) + { + m_uiLe->addSlider(this); + m_uiLe->slTime->setMinimum(m_minTimeMSec / 1000); + m_uiLe->slTime->setMaximum(m_maxTimeMSec > 0 ? m_maxTimeMSec / 1000 : time(nullptr)); + connect(m_uiLe->slTime, &QSlider::sliderMoved, this, &LtsDateTimeEdit::setSliderValue); + disableControls(); + } + else + { + delete m_uiLe->slTime; + m_uiLe->slTime = nullptr; + } +} + +void LtsDateTimeEdit::setReadOnly(const bool readOnly) +{ + if (m_readOnly == readOnly) + return; + m_readOnly = readOnly; + + if (m_uiLe) + { + m_uiLe->leLongDay->setReadOnly(m_readOnly); + m_uiLe->leLongHHMMSS->setReadOnly(m_readOnly); + m_uiLe->leLongMonth->setReadOnly(m_readOnly); + m_uiLe->leLongTZ->setReadOnly(m_readOnly); + m_uiLe->leLongYear->setReadOnly(m_readOnly); + } + if (m_uiTouch) + { + m_uiTouch->spDay->setReadOnly(m_readOnly); + m_uiTouch->spHour->setReadOnly(m_readOnly); + m_uiTouch->spMinutes->setReadOnly(m_readOnly); + m_uiTouch->spSeconds->setReadOnly(m_readOnly); + m_uiTouch->spYear->setReadOnly(m_readOnly); + m_uiTouch->cbxTimeZone->setEnabled(m_readOnly); + } +} + +QCheckBox *LtsDateTimeEdit::checkBox() const +{ + return m_uiLe ? m_uiLe->checkBox : m_uiTouch ? m_uiTouch->checkBox : nullptr; +} + +void LtsDateTimeEdit::setCheckable(bool checkable) +{ + if (!checkBox() || (m_checkable == checkable)) + return; + + m_checkable = checkable; + checkBox()->setVisible(checkable); + if (!checkable) + checkBox()->setChecked(true); +} + +void LtsDateTimeEdit::setChecked(bool bChecked) +{ + if (!checkBox()) + return; + checkBox()->setChecked(bChecked); +} + +bool LtsDateTimeEdit::isChecked() const +{ + if (!checkBox()) + return false; + return checkBox()->isChecked(); +} + +QPushButton *LtsDateTimeEdit::pbNow() const +{ + return m_uiLe ? m_uiLe->pbNow : m_uiTouch ? m_uiTouch->pbNow : nullptr; +} + +void LtsDateTimeEdit::setWithNowButton(bool nowButton) +{ + if (!pbNow()) + return; + + pbNow()->setVisible(nowButton); +} + +bool LtsDateTimeEdit::setMsecsSinceEpoch(qint64 msec, bool setToNearestPossible) +{ + if (msec == 0 && isCheckable()) + { + if (m_uiLe) + m_uiLe->checkBox->setChecked(false); + m_dateIsAcceptable = true; + return true; + } + if (m_uiLe) + m_uiLe->checkBox->setChecked(true); + + m_dateIsAcceptable = false; + if (msec == 0) + return false; + + + + + + + if (!setToNearestPossible) + { + if (!isDataAcceptable()) + return false; + } + else + { + if (msec < m_minTimeMSec && m_minTimeMSec > 0) + msec = m_minTimeMSec; + if (msec > m_maxTimeMSec && m_maxTimeMSec > 0) + msec = m_maxTimeMSec; + } + + m_curTimeMSec = msec; + + const qint64 offsetTimeZoneMsec = getTimezoneAsSecs() * 1000; + const QDateTime datetime(QDateTime::fromMSecsSinceEpoch(m_curTimeMSec + offsetTimeZoneMsec, Qt::UTC)); + const QDate date(datetime.date()); + const QTime time(datetime.time()); + + const int day = date.day(); + const int month = date.month(); + const int year = date.year(); + const int hour = time.hour(); + const int minute = time.minute(); + const int second = time.second(); + + + if (!setYear(year) || + !setMonth(month) || + !setDay(day) || + !setHHMMSS(hour, minute, second)) + return false; + + if (!isDataAcceptable()) + return false; + + enableControls(); + + emit dateTimeChanged(); + + return true; +} + + +qint64 LtsDateTimeEdit::msecsSinceEpoch() const +{ + if (isEmpty()) + return 0; + + if (m_uiLe && !m_uiLe->checkBox->isChecked()) + return 0; + if (m_uiTouch && !m_uiTouch->checkBox->isChecked()) + return 0; + + auto that = const_cast(this); + if (!that->isDataAcceptable()) + return 0; + + return m_curTimeMSec; +} + +bool LtsDateTimeEdit::setTimezone(char tz) +{ + tz = toupper(tz); + if (!DTG::isTimezoneValid(tz)) + return false; + + if (m_uiLe) + setItemText(m_uiLe->leLongTZ, QString(QLatin1Char(tz))); + if (m_uiTouch) + setItemText(m_uiTouch->cbxTimeZone, QString(QLatin1Char(tz))); + + setMsecsSinceEpoch(m_curTimeMSec); + + return true; +} + +char LtsDateTimeEdit::getTimezone() const +{ + QString str; + if (m_uiLe) + str = getItemText(m_uiLe->leLongTZ); + if (m_uiTouch) + str = getItemText(m_uiTouch->cbxTimeZone); + + if (str.isEmpty()) + return DTG::timezone(); + return str[0].toUpper().toLatin1(); +} + +qint64 LtsDateTimeEdit::getTimezoneAsSecs() const +{ + const char tzGUI = getTimezone(); + return DTG::timezone2Secs(tzGUI); +} + +void LtsDateTimeEdit::clearTimezone() +{ + if (m_uiLe) + m_uiLe->leLongTZ->clear(); + if (m_uiTouch) + m_uiTouch->cbxTimeZone->setCurrentIndex(0); +} + +void LtsDateTimeEdit::setMinMaxMonth(int minMonth, int maxMonth) +{ + if (!m_uiTouch) + return; + + m_minMonth = minMonth; + m_maxMonth = maxMonth; + + const int currentMonth = m_uiTouch->cbxMonth->currentData().toInt(); + m_uiTouch->cbxMonth->blockSignals(true); + m_uiTouch->cbxMonth->clear(); + for (int i = 1; i <= 12; ++i) + { + if (i < m_minMonth) + continue; + if (i > m_maxMonth) + break; + + + + const QStringList &monthSl = DTG::months(); + m_uiTouch->cbxMonth->addItem(monthSl[i-1], i); + + + + + } + + + m_uiTouch->cbxMonth->setCurrentIndex(m_uiTouch->cbxMonth->findData(currentMonth)); + m_uiTouch->cbxMonth->blockSignals(false); +} + + +void LtsDateTimeEdit::setSubWidgetsProperties(const QString &name, const QVariant &value) +{ + if (m_uiLe) + { + m_uiLe->leLongDay->setProperty(name.toStdString().c_str(), value); + m_uiLe->leLongHHMMSS->setProperty(name.toStdString().c_str(), value); + m_uiLe->leLongMonth->setProperty(name.toStdString().c_str(), value); + m_uiLe->leLongYear->setProperty(name.toStdString().c_str(), value); + m_uiLe->leLongTZ->setProperty(name.toStdString().c_str(), value); + } + if (m_uiTouch) + { + m_uiTouch->spDay->setProperty(name.toStdString().c_str(), value); + m_uiTouch->spHour->setProperty(name.toStdString().c_str(), value); + m_uiTouch->spMinutes->setProperty(name.toStdString().c_str(), value); + m_uiTouch->spSeconds->setProperty(name.toStdString().c_str(), value); + m_uiTouch->spYear->setProperty(name.toStdString().c_str(), value); + } +} + +bool LtsDateTimeEdit::setMonth(int month) +{ + const auto months(DTG::months()); + if (month < 1 || month > months.size()) + return false; + + if (m_uiLe) + setItemText(m_uiLe->leLongMonth, months.at(month - 1)); + if (m_uiTouch) + setItemData(m_uiTouch->cbxMonth, month); + return true; +} + +int LtsDateTimeEdit::getMonth() const +{ + if (m_uiLe) + { + const QString str = getItemText(m_uiLe->leLongMonth); + if (str.length() != 3) + return -1; + + int idx = DTG::months().indexOf(str); + if (idx < 0) + return -1; + return idx + 1; + } + if (m_uiTouch) + { + int month = getItemData(m_uiTouch->cbxMonth); + return month; + } + return 0; +} + +bool LtsDateTimeEdit::setDay(int day) +{ + if (day < 1 || day > 31) + return false; + + QString oldDay; + if (m_uiLe) + oldDay = m_uiLe->leLongDay->text(); + int oldDayInt = 0; + if (m_uiTouch) + oldDayInt = getItemValue(m_uiTouch->spDay); + + if (m_uiLe) + setItemText(m_uiLe->leLongDay, QStringLiteral("%1").arg(day, 2, 10, QLatin1Char('0'))); + if (m_uiTouch) + setItemValue(m_uiTouch->spDay, day); + + if (!isDayAcceptable()) + { + if (m_uiLe) + setItemText(m_uiLe->leLongDay, oldDay); + if (m_uiTouch) + setItemValue(m_uiTouch->spDay, oldDayInt); + return false; + } + + return true; +} + +int LtsDateTimeEdit::getDay() const +{ + if (m_uiLe) + return m_uiLe->leLongDay->getValue(); + if (m_uiTouch) + return getItemValue(m_uiTouch->spDay); + return 0; +} + +bool LtsDateTimeEdit::setHHMMSS(int hour, int minute, int second) +{ + if (hour < 0 || hour > 23 || + minute < 0 || minute > 59 || + second < 0 || second > 59) + return false; + + if (m_uiLe) + m_uiLe->leLongHHMMSS->setValue(hour * 10000 + minute * 100 + second); + if (m_uiTouch) + { + setItemValue(m_uiTouch->spHour , hour); + setItemValue(m_uiTouch->spMinutes, minute); + setItemValue(m_uiTouch->spSeconds, second); + } + + return true; +} + +int LtsDateTimeEdit::getHour() const +{ + if (m_uiLe) + { + const int time = m_uiLe->leLongHHMMSS->getValue(); + return time / 10000; + } + if (m_uiTouch) + { + return getItemValue(m_uiTouch->spHour); + } + return 0; +} + +int LtsDateTimeEdit::getMinute() const +{ + if (m_uiLe) + { + const int time = m_uiLe->leLongHHMMSS->getValue(); + return (time / 100) % 100; + } + if (m_uiTouch) + { + return getItemValue(m_uiTouch->spMinutes); + } + return 0; +} + +int LtsDateTimeEdit::getSecond() const +{ + if (m_uiLe) + { + const int time = m_uiLe->leLongHHMMSS->getValue(); + return time % 100; + } + if (m_uiTouch) + { + return getItemValue(m_uiTouch->spSeconds); + } + return 0; +} + +bool LtsDateTimeEdit::setYear(int year) +{ + int finalYear = -1; + if (year >= 0 && year <= 99) + finalYear = year; + else if (year >= 2000 && year <= 2099) + finalYear = year - 2000; + + if (finalYear >= 0) + { + if (m_uiLe) + m_uiLe->leLongYear->setValue(finalYear); + if (m_uiTouch) + setItemValue(m_uiTouch->spYear, finalYear); + return true; + } + + return false; +} + +int LtsDateTimeEdit::getYear() const +{ + if (m_uiLe) + return 2000 + m_uiLe->leLongYear->getValue(); + if (m_uiTouch) + return 2000 + m_uiTouch->spYear->value(); + return 2000; +} + +bool LtsDateTimeEdit::isEmpty() const +{ + return (isEmptyDay() || + isEmptyHHMMSS() || + isEmptyMonth() || + isEmptyTimezone() || + isEmptyYear()); +} + +bool LtsDateTimeEdit::isValidDate() const +{ + return m_dateIsAcceptable; +} + + +bool LtsDateTimeEdit::isEmptyDay() const +{ + if (m_uiLe) + return m_uiLe->leLongDay->isEmpty(); + return false; +} + +bool LtsDateTimeEdit::isEmptyHHMMSS() const +{ + if (m_uiLe) + return m_uiLe->leLongHHMMSS->isEmpty(); + return false; +} + +bool LtsDateTimeEdit::isEmptyMonth() const +{ + if (m_uiLe) + return m_uiLe->leLongMonth->text().isEmpty(); + return false; +} + +bool LtsDateTimeEdit::isEmptyTimezone() const +{ + if (m_uiLe) + return m_uiLe->leLongTZ->text().isEmpty(); + return false; +} + +bool LtsDateTimeEdit::isEmptyYear() const +{ + if (m_uiLe) + return m_uiLe->leLongYear->isEmpty(); + return false; +} + +bool LtsDateTimeEdit::hasFocus() const +{ + if (!m_uiLe) + return false; + return (m_uiLe->leLongDay->hasFocus() || + m_uiLe->leLongHHMMSS->hasFocus() || + m_uiLe->leLongMonth->hasFocus() || + m_uiLe->leLongTZ->hasFocus() || + m_uiLe->leLongYear->hasFocus()); +} + +bool LtsDateTimeEdit::isDataAcceptable() +{ + m_dateIsAcceptable = false; + + bool state = + isDayAcceptable() && + isHHMMSSAcceptable() && + isMonthAcceptable() && + isYearAcceptable() && + QDate::isValid(getYear(), getMonth(), getDay()) && + QTime::isValid(getHour(), getMinute(), getSecond()); + + if (m_editTimezone && state) + state = isTzAcceptable(); + + showInvalidData(false); + + if (!state) + { + disableControls(); + return false; + } + + qint64 msec = QDateTime(QDate(getYear(), getMonth(), getDay()), QTime(getHour(), getMinute(), getSecond()), Qt::UTC).toMSecsSinceEpoch(); + msec -= DTG::timezone2Secs(getTimezone()) * 1000LL; + if (m_minTimeMSec > 0 && m_maxTimeMSec > 0 && m_minTimeMSec < m_maxTimeMSec) + { + if (msec < m_minTimeMSec || msec > m_maxTimeMSec) + return false; + } + + m_curTimeMSec = msec; + enableControls(); + m_dateIsAcceptable = true; + return true; +} + +bool LtsDateTimeEdit::isDayAcceptable() const +{ + int value = getDay(); + int max = 31; + if (isMonthAcceptable()) + { + const int month = getMonth(); + switch (month) + { + case 2: + { + max = 28; + if (isYearAcceptable()) + { + if (getYear() % 4 == 0 && (getYear() % 100 != 0 || getYear() % 400 == 0)) + + max = 29; + } + break; + } + case 4: + case 6: + case 9: + case 11: + max = 30; + break; + default: + break; + } + } + return (value > 0 && value <= max); +} + +bool LtsDateTimeEdit::isHHMMSSAcceptable() const +{ + int hh = getHour(); + if (hh < 0 || hh > 23) + return false; + + int mm = getMinute(); + if (mm < 0 || mm > 59) + return false; + + int ss = getSecond(); + if (ss < 0 || ss > 59) + return false; + + return true; +} + +bool LtsDateTimeEdit::isMonthAcceptable() const +{ + int value = getMonth(); + return (value > 0 && value <= 12); +} + +bool LtsDateTimeEdit::isYearAcceptable() const +{ + if (m_uiLe) + return isLineEditDataAcceptable(m_uiLe->leLongYear); + return true; +} + +bool LtsDateTimeEdit::isTzAcceptable() const +{ + if (m_uiLe) + return isLineEditDataAcceptable(m_uiLe->leLongTZ); + return true; +} + +void LtsDateTimeEdit::showInvalidData(bool setFocus) +{ + if (!m_uiLe) + return; + + bool focus = setFocus; + + auto check = [&](QLineEdit * le, bool isValid) + { + if (isValid) + le->setStyleSheet(QString()); + else + { + le->setStyleSheet(QLatin1String("background-color: yellow;")); + if (focus) + { + le->setFocus(); + focus = false; + } + } + }; + + check(m_uiLe->leLongDay, isDayAcceptable()); + check(m_uiLe->leLongHHMMSS, isHHMMSSAcceptable()); + if (m_editTimezone) + check(m_uiLe->leLongTZ, isTzAcceptable()); + check(m_uiLe->leLongMonth, isMonthAcceptable()); + check(m_uiLe->leLongYear, isYearAcceptable()); +} + +bool LtsDateTimeEdit::isLineEditDataAcceptable(QLineEdit *le) const +{ + const QValidator *pValidator = le->validator(); + + if (!pValidator) + return true; + + QString text = le->text(); + + int pos; + QValidator::State state = pValidator->validate(text, pos); + + if (state == QValidator::Acceptable || state == QValidator::Intermediate) + return true; + + return false; +} + +void LtsDateTimeEdit::setFixedDateTime() +{ + setMsecsSinceEpoch(LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("TimeSettings"), QStringLiteral("FixedTime"), 0).toLongLong()); +} + +void LtsDateTimeEdit::setOffsetDateTime() +{ + if (!m_getCurrentTimeCallback) + return; + + qint64 offset = LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("TimeSettings"), QStringLiteral("Offset"), 0).toLongLong(); + + setMsecsSinceEpoch((m_getCurrentTimeCallback() / 1000) * 1000 + offset); +} + +void LtsDateTimeEdit::setSliderValue(int val) +{ + setMsecsSinceEpoch(val * 1000LL); +} + +void LtsDateTimeEdit::valueDayChanged(const QString &str) +{ + if (!m_uiLe) + return; + int curpos = m_uiLe->leLongDay->cursorPosition(); + int cp = curpos; + + bool changed = false; + + QString tag(str); + + if (tag.length() > 2) + { + tag = tag.left(2); + changed = true; + } + + bool ok; + int t = tag.toInt(&ok, 10); + + if (ok) + { + if (t > 31) + { + tag.remove(curpos - 1, 1); + if (cp >= curpos) + cp--; + changed = true; + } + } + + if (changed) + { + m_uiLe->leLongDay->setText(tag); + m_uiLe->leLongDay->setCursorPosition(cp); + } + + if (tag.length() == 2) + { + showInvalidData(false); + + if (m_autoForward) + focusNextPrevChild(true); + } +} + +void LtsDateTimeEdit::fixupHHMMSS() +{ + if (!m_uiLe) + return; + + int curpos = m_uiLe->leLongHHMMSS->cursorPosition(); + int cp = curpos; + + bool changed = false; + + QString uhrzeit(m_uiLe->leLongHHMMSS->text()); + + + + + + + QString hh; + QString mm; + QString ss; + + if (uhrzeit.length() > 1) + hh = uhrzeit.left(2); + else + hh = uhrzeit + QLatin1Char('0'); + + if (uhrzeit.length() > 0) + { + if (uhrzeit.length() == 1) + hh = uhrzeit.mid(0, 1) + QLatin1Char('0'); + else if (uhrzeit.length() >= 2) + { + hh = uhrzeit.mid(0, 2); + if (uhrzeit.length() > 2) + { + if (uhrzeit.length() == 3) + mm = uhrzeit.mid(2, 1) + QLatin1Char('0'); + else if (uhrzeit.length() >= 4) + { + mm = uhrzeit.mid(2, 2); + if (uhrzeit.length() == 5) + ss = uhrzeit.mid(4, 1) + QLatin1Char('0'); + else if (uhrzeit.length() == 6) + ss = uhrzeit.mid(4, 2); + } + } + } + } + + bool ok; + + int h = hh.toInt(&ok, 10); + if (ok) + { + if (h > 23) + { + uhrzeit.remove(curpos - 1, 1); + if (cp >= curpos) + cp--; + changed = true; + } + } + + int m = mm.toInt(&ok, 10); + + if (ok) + { + if ((hh.length() == 2) && (m > 59)) + { + uhrzeit.remove(curpos - 1, 1); + if (cp >= curpos) + cp--; + changed = true; + } + } + + int s = ss.toInt(&ok, 10); + + if (ok) + { + if ((hh.length() == 2 && mm.length() == 2) && (s > 59)) + { + uhrzeit.remove(curpos - 1, 1); + if (cp >= curpos) + cp--; + changed = true; + } + } + + if (changed) + { + m_uiLe->leLongHHMMSS->setText(uhrzeit); + m_uiLe->leLongHHMMSS->setCursorPosition(cp); + } + + isDataAcceptable(); + emit dateTimeChanged(); +} + +void LtsDateTimeEdit::valueMonthChanged2(const QString &str) +{ + if (!m_uiLe) + return; + if (str.length() == 3 && m_uiLe->leLongMonth->selectedText().isEmpty()) + focusNextPrevChild(true); +} + +void LtsDateTimeEdit::valueMonthChanged(const QString &str) +{ + if (!m_uiLe) + return; + int curpos = m_uiLe->leLongMonth->cursorPosition(); + int cp = curpos; + + bool changed = false; + + QString monStr = str; + + const auto months = DTG::months(); + for (int i = 0; i < months.size(); ++i) + { + const QString &mon = months.at(i); + if (mon.startsWith(monStr, Qt::CaseInsensitive)) + { + monStr = mon; + changed = true; + break; + } + } + + if (!changed) + { + monStr.remove(curpos - 1, 1); + if (cp >= curpos) + cp--; + changed = true; + } + + if (changed) + { + m_uiLe->leLongMonth->setText(monStr); + + + if (cp > 0) + m_uiLe->leLongMonth->setCursorPosition(cp); + else + m_uiLe->leLongMonth->setCursorPosition(1); + } + + if (monStr.size() == 3) + { + showInvalidData(false); + if (m_autoForward) + focusNextPrevChild(true); + } +} + +void LtsDateTimeEdit::valueTimezoneChangedStr(const QString &str) +{ + if (str.length() > 1) + { + clearTimezone(); + return; + } + if (str.length() == 0) + return; + QChar c = str.at(0); + if (!c.isLetter() || c.toUpper() == QLatin1Char('J')) + { + clearTimezone(); + return; + } + if (c.isLower()) + setTimezone(c.toUpper().toLatin1()); + + showInvalidData(false); + if (m_autoForward) + focusNextPrevChild(true); +} + +void LtsDateTimeEdit::valueTimezoneChangedInt(int newTimezone) +{ + Q_UNUSED(newTimezone); + if (!m_uiTouch) + return; + const QString &curText = m_uiTouch->cbxTimeZone->currentText(); + if (curText.isEmpty()) + return; + + const QChar tz = curText.at(0); + setTimezone(tz.toUpper().toLatin1()); +} + +void LtsDateTimeEdit::valueYearChanged(const QString &str) +{ + if (!m_uiLe) + return; + int curpos = m_uiLe->leLongYear->cursorPosition(); + int cp = curpos; + + bool changed = false; + + const QString &year = str; + + QString yy; + if (year.length() > 2) + { + yy = year.left(2); + changed = true; + } + + if (changed) + { + m_uiLe->leLongYear->setText(yy); + m_uiLe->leLongYear->setCursorPosition(cp); + } + + if (year.length() == 2) + { + showInvalidData(false); + if (m_autoForward) + focusNextPrevChild(true); + } +} + +void LtsDateTimeEdit::onBackHourClicked() +{ + if (!setMsecsSinceEpoch(m_curTimeMSec - s_oneHourMSec)) + setMsecsSinceEpoch(m_minTimeMSec); +} + +void LtsDateTimeEdit::onBackMinuteClicked() +{ + const int ofs = QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) ? 5 : 1; + if (!setMsecsSinceEpoch(m_curTimeMSec - ofs * s_oneMinuteMSec)) + setMsecsSinceEpoch(m_minTimeMSec); +} + +void LtsDateTimeEdit::onBackSecondClicked() +{ + const int ofs = QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) ? 5 : 1; + if (!setMsecsSinceEpoch(m_curTimeMSec - ofs * s_oneSecondMSec)) + setMsecsSinceEpoch(m_minTimeMSec); +} + +void LtsDateTimeEdit::onForwardSecondClicked() +{ + const int ofs = QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) ? 5 : 1; + if (!setMsecsSinceEpoch(m_curTimeMSec + ofs * s_oneSecondMSec)) + setMsecsSinceEpoch(m_maxTimeMSec); +} + +void LtsDateTimeEdit::onForwardMinuteClicked() +{ + const int ofs = QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) ? 5 : 1; + if (!setMsecsSinceEpoch(m_curTimeMSec + ofs * s_oneMinuteMSec)) + setMsecsSinceEpoch(m_maxTimeMSec); +} + +void LtsDateTimeEdit::onForwardHourClicked() +{ + if (!setMsecsSinceEpoch(m_curTimeMSec + s_oneHourMSec)) + setMsecsSinceEpoch(m_maxTimeMSec); +} + +void LtsDateTimeEdit::onCheckBoxChanged() +{ + QVector widgets; + bool bEnable = false; + + if (m_uiLe) + { + widgets = {m_uiLe->leLongDay, m_uiLe->leLongHHMMSS, m_uiLe->leLongTZ, + m_uiLe->leLongMonth, m_uiLe->leLongYear}; + } + + if (m_uiTouch) + { + widgets = {m_uiTouch->spDay, m_uiTouch->spHour, m_uiTouch->spMinutes, m_uiTouch->spSeconds, m_uiTouch->spYear, m_uiTouch->cbxMonth, m_uiTouch->cbxTimeZone, m_uiTouch->pbNow}; + } + + bEnable = checkBox()->isChecked(); + for (auto w : qAsConst(widgets)) + { + if (w) + w->setEnabled(bEnable); + } + enaDisaControls(bEnable); +} + +void LtsDateTimeEdit::disableControls() +{ + enaDisaControls(false); +} + +void LtsDateTimeEdit::enableControls() +{ + if (!m_uiLe) + return; + + enaDisaControls(true); + if (m_withSlider && + m_minTimeMSec != 0 && + m_maxTimeMSec > 0 && + m_maxTimeMSec > m_minTimeMSec) + { + m_uiLe->slTime->setEnabled(true); + m_uiLe->slTime->setValue(m_curTimeMSec / 1000LL); + } +} + +void LtsDateTimeEdit::enaDisaControls(bool bEnable) +{ + if (!m_uiLe) + return; + const QVector widgets({m_uiLe->pbBBB, m_uiLe->pbBB, m_uiLe->pbB, + m_uiLe->pbF, m_uiLe->pbFF, m_uiLe->pbFFF, + m_uiLe->slTime}); + for (auto w : widgets) + { + if (w) + w->setEnabled(bEnable); + } +} + +void LtsDateTimeEdit::setMinimumTimeMSec(qint64 msec) +{ + if (m_minTimeMSec == msec) + return; + m_minTimeMSec = msec; + if (m_minTimeMSec > 0 && m_uiLe && m_uiLe->slTime) + { + m_uiLe->slTime->setMinimum(m_minTimeMSec / 1000LL); + if (m_maxTimeMSec > m_minTimeMSec) + m_uiLe->slTime->setEnabled(true); + } + if (m_curTimeMSec < m_minTimeMSec) + setMsecsSinceEpoch(m_minTimeMSec); +} + +void LtsDateTimeEdit::setMaximumTimeMSec(qint64 msec) +{ + if (m_maxTimeMSec == msec) + return; + m_maxTimeMSec = msec; + if (!m_uiLe || !m_uiLe->slTime) + return; + + m_uiLe->slTime->setMaximum(m_maxTimeMSec / 1000LL); + if (m_minTimeMSec > 0 && m_maxTimeMSec > m_minTimeMSec) + m_uiLe->slTime->setEnabled(true); + if (m_maxTimeMSec > 0 && m_curTimeMSec > m_maxTimeMSec) + setMsecsSinceEpoch(m_maxTimeMSec); +} + +void LtsDateTimeEdit::clear() +{ + if (m_uiLe) + { + m_uiLe->leLongDay->clear(); + m_uiLe->leLongHHMMSS->clear(); + m_uiLe->leLongMonth->clear(); + m_uiLe->leLongYear->clear(); + m_uiLe->leLongTZ->clear(); + m_curTimeMSec = 0; + } + if (m_uiTouch) + { + m_uiTouch->spDay->clear(); + m_uiTouch->spHour->clear(); + m_uiTouch->spMinutes->clear(); + m_uiTouch->spSeconds->clear(); + m_uiTouch->spYear->clear(); + } + emit dateTimeChanged(); + +} + +void LtsDateTimeEdit::onFocusOutDay() +{ + if (!m_uiLe) + return; + if (m_backspacePressed) + { + m_backspacePressed = false; + return; + } + if (!m_recursionAvoid && !hasFocus()) + onFocusOut(); + + QString value = m_uiLe->leLongDay->text(); + + if (value.length() == 0 && !m_backspacePressed) + { + if (!isEmptyHHMMSS() || + !isEmptyMonth() || + (!isEmptyTimezone() && m_uiLe->leLongTZ->isEnabled()) || + !isEmptyYear()) + { + if (m_getCurrentTimeCallback) + { + const QDateTime dt(QDateTime::fromMSecsSinceEpoch(m_getCurrentTimeCallback(), Qt::UTC)); + setDay(dt.date().day()); + } + } + } + isDataAcceptable(); + emit dateTimeChanged(); +} + +void LtsDateTimeEdit::onFocusOutHHMMSS() +{ + if (!m_uiLe) + return; + if (m_backspacePressed) + { + m_backspacePressed = false; + return; + } + if (!m_recursionAvoid && !hasFocus()) + onFocusOut(); + + QString value = m_uiLe->leLongHHMMSS->text(); + + if (value.length() == 0 && !m_backspacePressed) + { + if (!isEmptyDay() || + !isEmptyMonth() || + !(isEmptyTimezone() && m_uiLe->leLongTZ->isEnabled()) || + !isEmptyYear()) + { + if (m_getCurrentTimeCallback) + { + const QDateTime dt(QDateTime::fromMSecsSinceEpoch(m_getCurrentTimeCallback(), Qt::UTC)); + setHHMMSS(dt.time().hour(), dt.time().minute(), dt.time().second()); + } + } + } + value += QString(6 - value.length(), QLatin1Char('0')); + m_uiLe->leLongHHMMSS->setText(value); + isDataAcceptable(); + emit dateTimeChanged(); +} + +void LtsDateTimeEdit::onFocusOutTZ() +{ + if (!m_uiLe) + return; + if (m_backspacePressed) + { + m_backspacePressed = false; + return; + } + if (!m_recursionAvoid && !hasFocus()) + onFocusOut(); + + QString value = m_uiLe->leLongTZ->text(); + + if (value.length() == 0 && !m_backspacePressed) + { + if (!isEmptyHHMMSS() || + !isEmptyMonth() || + !isEmptyDay() || + !isEmptyYear()) + setTimezone(DTG::timezone()); + } + isDataAcceptable(); + emit dateTimeChanged(); +} + +void LtsDateTimeEdit::onFocusOutMonth() +{ + if (!m_uiLe) + return; + if (m_backspacePressed) + { + m_backspacePressed = false; + return; + } + + if (!m_recursionAvoid && !hasFocus()) + onFocusOut(); + + QString value = m_uiLe->leLongMonth->text(); + + if (value.length() == 0 && !m_backspacePressed) + { + if (!isEmptyHHMMSS() || + !isEmptyDay() || + (!isEmptyTimezone() && m_uiLe->leLongTZ->isEnabled()) || + !isEmptyYear()) + { + if (m_getCurrentTimeCallback) + { + const QDateTime dt(QDateTime::fromMSecsSinceEpoch(m_getCurrentTimeCallback(), Qt::UTC)); + setMonth(dt.date().month()); + } + } + } + isDataAcceptable(); + emit dateTimeChanged(); +} + +void LtsDateTimeEdit::onFocusOutYear() +{ + if (!m_uiLe) + return; + if (m_backspacePressed) + { + m_backspacePressed = false; + return; + } + + if (!m_recursionAvoid && !hasFocus()) + onFocusOut(); + + QString value = m_uiLe->leLongYear->text(); + + if (value.length() == 1) + { + value = QLatin1Char('0') + value; + m_uiLe->leLongYear->setText(value); + } + + if (value.length() == 0 && !m_backspacePressed) + { + if (!isEmptyHHMMSS() || + !isEmptyMonth() || + (!isEmptyTimezone() && m_uiLe->leLongTZ->isEnabled()) || + !isEmptyDay()) + { + if (m_getCurrentTimeCallback) + { + const QDateTime dt(QDateTime::fromMSecsSinceEpoch(m_getCurrentTimeCallback(), Qt::UTC)); + setYear(dt.date().year()); + } + } + } + isDataAcceptable(); + emit dateTimeChanged(); +} + +void LtsDateTimeEdit::onFocusOutBackspace() +{ + m_backspacePressed = true; +} + +void LtsDateTimeEdit::onFocusOut() +{ + if (!m_uiLe) + return; + if (m_backspacePressed) + { + m_backspacePressed = false; + return; + } + m_recursionAvoid = true; + onFocusOutDay(); + onFocusOutHHMMSS(); + onFocusOutMonth(); + onFocusOutTZ(); + onFocusOutYear(); + m_recursionAvoid = false; +} + +void LtsDateTimeEdit::onHmsWheelEvent(bool bForward, int cursorPos) +{ + if (!m_uiLe) + return; + if (m_uiLe->leLongHHMMSS->text().size() != 6) + return; + + fixupHHMMSS(); + + int selStart = 0; + switch (cursorPos) + { + case 0: + case 1: + case 2: + if (bForward) + onForwardHourClicked(); + else + onBackHourClicked(); + selStart = 0; + break; + case 3: + case 4: + if (bForward) + onForwardMinuteClicked(); + else + onBackMinuteClicked(); + selStart = 2; + break; + case 5: + case 6: + if (bForward) + onForwardSecondClicked(); + else + onBackSecondClicked(); + selStart = 4; + break; + default: + return; + } + m_uiLe->leLongHHMMSS->setCursorPosition(cursorPos); + m_uiLe->leLongHHMMSS->setSelection(selStart, 2); + emit dateTimeChanged(); +} + +void LtsDateTimeEdit::onDayWheelEvent(bool bForward, int cursorPos) +{ + if (!m_uiLe) + return; + if (!isDayAcceptable()) + return; + const int ofs = bForward ? 1 : -1; + setMsecsSinceEpoch(m_curTimeMSec + ofs * s_oneDayMSec, true); + m_uiLe->leLongDay->setCursorPosition(cursorPos); + m_uiLe->leLongDay->setSelection(0, 2); + emit dateTimeChanged(); +} + +void LtsDateTimeEdit::onMonthWheelEvent(bool bForward, int cursorPos) +{ + if (!m_uiLe) + return; + if (!isMonthAcceptable()) + return; + const int ofs = bForward ? 1 : -1; + const QDateTime dt(QDateTime::fromMSecsSinceEpoch(m_curTimeMSec, Qt::UTC).addMonths(ofs)); + setMsecsSinceEpoch(dt.toMSecsSinceEpoch(), true); + m_uiLe->leLongMonth->setCursorPosition(cursorPos); + m_uiLe->leLongMonth->setSelection(0, 3); + emit dateTimeChanged(); +} + +void LtsDateTimeEdit::onYearWheelEvent(bool bForward, int cursorPos) +{ + if (!m_uiLe) + return; + if (!isYearAcceptable()) + return; + const int ofs = bForward ? 1 : -1; + const QDateTime dt(QDateTime::fromMSecsSinceEpoch(m_curTimeMSec, Qt::UTC).addYears(ofs)); + setMsecsSinceEpoch(dt.toMSecsSinceEpoch(), true); + m_uiLe->leLongYear->setCursorPosition(cursorPos); + m_uiLe->leLongYear->setSelection(0, 2); + emit dateTimeChanged(); +} + +} diff --git a/Lts/Common/LtsWidgets/lts_date_time_edit.h b/Lts/Common/LtsWidgets/lts_date_time_edit.h new file mode 100755 index 000000000..13047d3f5 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_date_time_edit.h @@ -0,0 +1,213 @@ + + + +#pragma once + + +#include "lts_widgets_export.h" +#include "qcheckbox.h" +#include "qpushbutton.h" + +#include +#include + + +namespace Ui +{ +class LtsDateTimeEditLe; +class LtsDateTimeEdit; +} + +class QLineEdit; +class QShortcut; + +namespace LtsWidgets +{ +class LTSWIDGETS_EXPORT LtsDateTimeEdit : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(bool autoForward READ getAutoForward WRITE setAutoForward) + Q_PROPERTY(bool editTimezone READ getEditTimezone WRITE setEditTimezone) + Q_PROPERTY(bool withButtons READ getWithButtons WRITE setWithButtons) + Q_PROPERTY(bool withSlider READ getWithSlider WRITE setWithSlider) + Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly) + Q_PROPERTY(bool withNowButton READ withNowButton WRITE setWithNowButton) + Q_PROPERTY(bool checkable READ isCheckable WRITE setCheckable) + Q_PROPERTY(bool checked READ isChecked WRITE setChecked NOTIFY toggled) + +public: + + using CurrentTimeCallback = std::function; + + explicit LtsDateTimeEdit(QWidget *parent = nullptr, bool withNowButton = true, bool checkable = false); + ~LtsDateTimeEdit(); + + bool setupUiTouch(); + + + qint64 msecsSinceEpoch() const; + bool isEmpty() const; + bool isValidDate() const; + + void setAutoForward(const bool af) { m_autoForward = af; } + bool getAutoForward() const { return m_autoForward; } + + void setEditTimezone(const bool edit); + bool getEditTimezone() const { return m_editTimezone; } + + void setWithButtons(const bool buttons); + bool getWithButtons() const { return m_withButtons; } + + void setWithSlider(const bool slider); + bool getWithSlider() const { return m_withSlider; } + + void setReadOnly(const bool readOnly); + bool isReadOnly() const { return m_readOnly; } + + void setCheckable(bool checkable); + bool isCheckable() const { return m_checkable; } + void setChecked(bool bChecked); + bool isChecked() const; + + void setWithNowButton(bool nowButton); + bool withNowButton() const { return m_withNowButton; } + + + void setMinimumTimeMSec(qint64 msec); + qint64 minimumTimeMSec() const { return m_minTimeMSec; } + + + void setMaximumTimeMSec(qint64 msec); + qint64 maximumTimeMSec() const { return m_maxTimeMSec; } + + void setMinMaxTimeMSec(qint64 min, qint64 max) { setMinimumTimeMSec(min); setMaximumTimeMSec(max); } + + void setRetrieveCurrentTimeCallback(const CurrentTimeCallback &cb); + + bool setTimezone(char tz); + char getTimezone() const; + qint64 getTimezoneAsSecs() const; + + void setSubWidgetsProperties(const QString &name, const QVariant &value); + +Q_SIGNALS: + void dateTimeChanged(); + void toggled(bool on); + +public Q_SLOTS: + bool setMsecsSinceEpoch(qint64 msec, bool setToNearestPossible = true); + void clear(); + +private: + void clearTimezone(); + + void setMinMaxMonth(int minMonth, int maxMonth); + + bool setMonth(int month); + int getMonth() const; + + bool setDay(int day); + int getDay() const; + + bool setHHMMSS(int hour, int minute, int second); + int getHour() const; + int getMinute() const; + int getSecond() const; + + bool setYear(int year); + int getYear() const; + + bool isEmptyDay() const; + bool isEmptyHHMMSS() const; + bool isEmptyTimezone() const; + bool isEmptyMonth() const; + bool isEmptyYear() const; + + bool hasFocus() const; + + bool isDataAcceptable(); + + void showInvalidData(bool setFocus); + + bool isLineEditDataAcceptable(QLineEdit *le) const; + + bool isDayAcceptable() const; + bool isHHMMSSAcceptable() const; + bool isMonthAcceptable() const; + bool isYearAcceptable() const; + bool isTzAcceptable() const; + + QCheckBox* checkBox() const; + QPushButton* pbNow() const; + +private Q_SLOTS: + void setFixedDateTime(); + void setOffsetDateTime(); + + void setSliderValue(int val); + void valueDayChanged(const QString &str); + void fixupHHMMSS(); + void valueMonthChanged(const QString &str); + void valueMonthChanged2(const QString &str); + void valueTimezoneChangedStr(const QString &str); + void valueTimezoneChangedInt(int newTimezone); + void valueYearChanged(const QString &str); + + void onBackHourClicked(); + void onBackMinuteClicked(); + void onBackSecondClicked(); + void onForwardSecondClicked(); + void onForwardMinuteClicked(); + void onForwardHourClicked(); + + void onCheckBoxChanged(); + void disableControls(); + void enableControls(); + void enaDisaControls(bool bEnable); + + void onFocusOutHHMMSS(); + void onFocusOutTZ(); + void onFocusOutDay(); + void onFocusOutMonth(); + void onFocusOutYear(); + + void onFocusOutBackspace(); + void onFocusOut(); + + void onHmsWheelEvent(bool bForward, int cursorPos); + void onDayWheelEvent(bool bForward, int cursorPos); + void onMonthWheelEvent(bool bForward, int cursorPos); + void onYearWheelEvent(bool bForward, int cursorPos); + +private: + Ui::LtsDateTimeEditLe *m_uiLe{nullptr}; + Ui::LtsDateTimeEdit *m_uiTouch{nullptr}; + QShortcut *m_scFixed = nullptr; + QShortcut *m_scOffset = nullptr; + + CurrentTimeCallback m_getCurrentTimeCallback = nullptr; + + qint64 m_minTimeMSec = 0; + qint64 m_maxTimeMSec = 0; + + qint64 m_curTimeMSec = -1; + + bool m_editTimezone = true; + bool m_autoForward = true; + bool m_withButtons = false; + bool m_withSlider = false; + bool m_readOnly = false; + bool m_checkable = false; + bool m_withNowButton = false; + + int m_minMonth{0}; + int m_maxMonth{0}; + + + bool m_backspacePressed = false; + bool m_recursionAvoid = false; + bool m_dateIsAcceptable = false; +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_date_time_edit_p.cpp b/Lts/Common/LtsWidgets/lts_date_time_edit_p.cpp new file mode 100755 index 000000000..2bdf1bed6 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_date_time_edit_p.cpp @@ -0,0 +1,118 @@ +#include "lts_date_time_edit_p.h" + +#include +#include +#include +#include + + +namespace LtsWidgets +{ + + + + +void LineEdit::clear() +{ + setStyleSheet(QString()); + QLineEdit::clear(); +} + +bool LineEdit::isEmpty() const +{ + return text().isEmpty(); +} + +void LineEdit::keyPressEvent(QKeyEvent *event) +{ + const bool wasEmpty = isEmpty(); + QLineEdit::keyPressEvent(event); + int keyCode = event->key(); + if (keyCode == Qt::Key_Backspace && wasEmpty) + { + emit focusOutBackspace(); + focusPreviousChild(); + event->accept(); + } +} + +void LineEdit::contextMenuEvent(QContextMenuEvent *e) +{ + Q_UNUSED(e); + +} + +void LineEdit::focusOutEvent(QFocusEvent *event) +{ + QLineEdit::focusOutEvent(event); + emit focusOut(); +} + + + + +DigitsLineEdit::DigitsLineEdit(QWidget *parent) + : LineEdit(parent) +{ + setAlignment(Qt::AlignRight); + QRegularExpression rx(QLatin1String("[-]{0,1}[0-9 ]*")); + setValidator(new QRegularExpressionValidator(rx, this)); +} + +void DigitsLineEdit::setExactNumDigits(int num) +{ + m_exactNumDigits = num; + + QRegularExpression rx(QString::asprintf("^[0-9 ]{%d}$", num)); + setValidator(new QRegularExpressionValidator(rx, this)); +} + +void DigitsLineEdit::setValue(int val) +{ + QString str; + + if (m_exactNumDigits == -1) + str.setNum(val); + else + str = QStringLiteral("%1").arg(val, m_exactNumDigits, 10, QLatin1Char('0')); + + setText(str); +} + +int DigitsLineEdit::getValue() const +{ + if (text().isEmpty()) + return 0; + return text().remove(QLatin1Char(' ')).toInt(); +} + +void DigitsLineEdit::setCursorPosition(int curPos) +{ + + curPos = curPos < 0 ? 1 : curPos; + QLineEdit::setCursorPosition(curPos); +} + +void DigitsLineEdit::wheelEvent(QWheelEvent *event) +{ + LineEdit::wheelEvent(event); + const QString txt = text(); + if (m_exactNumDigits > 0 && txt.length() != m_exactNumDigits) + return; + event->accept(); + int cursorPos = cursorPosition(); + emit wheelEventReceived(event->delta() > 0, cursorPos); +} + +void DigitsLineEdit::keyPressEvent(QKeyEvent *event) +{ + LineEdit::keyPressEvent(event); + if (event->key() == Qt::Key_Up || event->key() == Qt::Key_Down) + { + const bool bUp = event->key() == Qt::Key_Up; + const int cursorPos = cursorPosition(); + emit wheelEventReceived(bUp, cursorPos); + } +} + +} diff --git a/Lts/Common/LtsWidgets/lts_date_time_edit_p.h b/Lts/Common/LtsWidgets/lts_date_time_edit_p.h new file mode 100755 index 000000000..daf8bcf4a --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_date_time_edit_p.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +namespace LtsWidgets +{ + +class LineEdit : public QLineEdit +{ + Q_OBJECT +public: + using QLineEdit::QLineEdit; + + void clear(); + bool isEmpty() const; +protected: + void contextMenuEvent(QContextMenuEvent *e) override; + void focusOutEvent(QFocusEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; +Q_SIGNALS: + void focusOutBackspace(); + void focusOut(); +}; + +class DigitsLineEdit final : public LineEdit +{ + Q_OBJECT +public: + DigitsLineEdit(QWidget *parent = nullptr); + + void setExactNumDigits(int num); + void setCursorPosition(int curPos); + void setValue(int val); + int getValue() const; +Q_SIGNALS: + void wheelEventReceived(bool bForward, int cursorPos); +protected: + void wheelEvent(QWheelEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; +private: + int m_exactNumDigits = -1; +}; +} diff --git a/Lts/Common/LtsWidgets/lts_date_time_edit_rc.cpp b/Lts/Common/LtsWidgets/lts_date_time_edit_rc.cpp new file mode 100755 index 000000000..d34b1fbea --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_date_time_edit_rc.cpp @@ -0,0 +1,1792 @@ + + + +#include "lts_date_time_edit.h" +#include "lts_date_time_edit_p.h" + +#include "ui_lts_date_time_edit_touch.h" + + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static constexpr qint64 s_oneSecondMSec = 1000LL; +static constexpr qint64 s_oneMinuteMSec = 60 * s_oneSecondMSec; +static constexpr qint64 s_oneHourMSec = 60 * s_oneMinuteMSec; +static constexpr qint64 s_oneDayMSec = 24 * s_oneHourMSec; + +namespace Ui +{ +class LtsDateTimeEditLe +{ +public: + LtsWidgets::DigitsLineEdit *leLongDay = nullptr; + LtsWidgets::DigitsLineEdit *leLongHHMMSS = nullptr; + LtsWidgets::LineEdit *leLongTZ = nullptr; + LtsWidgets::DigitsLineEdit *leLongMonth = nullptr; + LtsWidgets::DigitsLineEdit *leLongYear = nullptr; + QCheckBox *checkBox = nullptr; + QGridLayout *gridLayout = nullptr; + QPushButton *pbBBB = nullptr; + QPushButton *pbBB = nullptr; + QPushButton *pbB = nullptr; + QPushButton *pbF = nullptr; + QPushButton *pbFF = nullptr; + QPushButton *pbFFF = nullptr; + QPushButton *pbNow = nullptr; + QSlider *slTime = nullptr; + + static void setFixedSize(QLineEdit *le, int charCount) + { + QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + sizePolicy.setVerticalStretch(0); + sizePolicy.setHeightForWidth(false); + + const QFontMetricsF fm(le->font()); + const auto textMargins = le->textMargins(); + le->setSizePolicy(sizePolicy); + charCount += 3; +#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0) + const qreal w = fm.width(QLatin1Char('x')) * charCount + textMargins.left() + textMargins.right(); +#else + const qreal w = fm.horizontalAdvance(QLatin1Char('x')) * charCount + textMargins.left() + textMargins.right(); +#endif + le->setMinimumWidth(w); + le->setMaximumWidth(w); + } + + void setupUi(QWidget *parent) + { + gridLayout = new QGridLayout(parent); + gridLayout->setMargin(0); + + int iStart = 3; + + checkBox = new QCheckBox; + gridLayout->addWidget(checkBox, 0, iStart++, 1, 1); + + auto horizontalLayout = new QHBoxLayout; + horizontalLayout->setSpacing(0); + horizontalLayout->setContentsMargins(0, 0, 0, 0); + + leLongDay = new LtsWidgets::DigitsLineEdit(parent); + setFixedSize(leLongDay, 2); + leLongDay->setObjectName(QStringLiteral("leLongDay")); + horizontalLayout->addWidget(leLongDay); + + leLongHHMMSS = new LtsWidgets::DigitsLineEdit(parent); + leLongHHMMSS->setExactNumDigits(6); + setFixedSize(leLongHHMMSS, 6); + leLongHHMMSS->setObjectName(QStringLiteral("leLongHHMMSS")); + horizontalLayout->addWidget(leLongHHMMSS); + + leLongTZ = new LtsWidgets::LineEdit(parent); + leLongTZ->setAlignment(Qt::AlignCenter); + setFixedSize(leLongTZ, 1); + leLongTZ->setObjectName(QStringLiteral("leLongTZ")); + horizontalLayout->addWidget(leLongTZ); + + leLongMonth = new LtsWidgets::DigitsLineEdit(parent); + leLongMonth->setAlignment(Qt::AlignCenter); + setFixedSize(leLongMonth, 3); + leLongMonth->setObjectName(QStringLiteral("leLongMonth")); + horizontalLayout->addWidget(leLongMonth); + + leLongYear = new LtsWidgets::DigitsLineEdit(parent); + leLongYear->setExactNumDigits(2); + setFixedSize(leLongYear, 2); + leLongYear->setObjectName(QStringLiteral("leLongYear")); + horizontalLayout->addWidget(leLongYear); + + gridLayout->addLayout(horizontalLayout, 0, iStart++, 1, 1); + + pbNow = new QPushButton(QApplication::translate("LtsDateTimeEdit", "Now", nullptr)); + pbNow->setObjectName(QStringLiteral("pbNow")); + gridLayout->addWidget(pbNow, 0, iStart++, 1, 1); + + QWidget::setTabOrder(checkBox, leLongDay); + QWidget::setTabOrder(leLongDay, leLongHHMMSS); + QWidget::setTabOrder(leLongHHMMSS, leLongTZ); + QWidget::setTabOrder(leLongTZ, leLongMonth); + QWidget::setTabOrder(leLongMonth, leLongYear); + QWidget::setTabOrder(leLongYear, pbNow); + + parent->setFocusProxy(leLongDay); + } + + static QPushButton *addButton(const QString &text, QWidget *parent) + { + QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + auto pb = new QPushButton(parent); + pb->setMinimumWidth(27); + pb->setMaximumWidth(27); + pb->setText(text); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + sizePolicy.setHeightForWidth(pb->sizePolicy().hasHeightForWidth()); + pb->setSizePolicy(sizePolicy); + return pb; + } + + void addButtons(QWidget *parent) + { + pbBBB = addButton(QLatin1String("<<<"), parent); + pbBB = addButton(QLatin1String("<<"), parent); + pbB = addButton(QLatin1String("<"), parent); + gridLayout->addWidget(pbBBB, 0, 0, 1, 1); + gridLayout->addWidget(pbBB, 0, 1, 1, 1); + gridLayout->addWidget(pbB, 0, 2, 1, 1); + + int iStart = 5; + pbF = addButton(QLatin1String(">"), parent); + pbFF = addButton(QLatin1String(">>"), parent); + pbFFF = addButton(QLatin1String(">>>"), parent); + gridLayout->addWidget(pbF, 0, iStart++, 1, 1); + gridLayout->addWidget(pbFF, 0, iStart++, 1, 1); + gridLayout->addWidget(pbFFF, 0, iStart++, 1, 1); + } + + void addSlider(QWidget *parent) + { + slTime = new QSlider(parent); + slTime->setOrientation(Qt::Horizontal); + gridLayout->addWidget(slTime, 1, 0, 1, 7); + } +}; +} + + +namespace LtsWidgets +{ + + + + +void setItemText(LtsWidgets::LineEdit *lineEdit, const QString &text) +{ + if (!lineEdit) + return; + lineEdit->setText(text); +} +void setItemText(QComboBox *comboxBox, const QString &text) +{ + if (!comboxBox) + return; + comboxBox->setCurrentIndex(comboxBox->findText(text)); +} +void setItemData(QComboBox *comboxBox, int data) +{ + if (!comboxBox) + return; + comboxBox->setCurrentIndex(comboxBox->findData(data)); +} +void setItemValue(QSpinBox *spinBox, int value) +{ + if (!spinBox) + return; + spinBox->setValue(value); +} + + +QString getItemText(LtsWidgets::LineEdit *lineEdit) +{ + if (!lineEdit) + return {}; + return lineEdit->text(); +} +QString getItemText(QComboBox *comboxBox) +{ + if (!comboxBox || (comboxBox->count() <= 0)) + return {}; + const QString &curTest = comboxBox->currentText(); + if (curTest.isEmpty()) + return {}; + return curTest.at(0).toUpper(); +} +int getItemData(QComboBox *comboxBox) +{ + if (!comboxBox || (comboxBox->count() <= 0)) + return {}; + int data = comboxBox->currentData().toInt(); + return data; +} +int getItemValue(QSpinBox *spinBox) +{ + if (!spinBox) + return 0; + return spinBox->value(); +} + + + + + + +LtsDateTimeEdit::LtsDateTimeEdit(QWidget *parent, bool withNowButton, bool withCheckBox) + : QWidget(parent) + + , m_scFixed(new QShortcut(QKeySequence(Qt::ShiftModifier + Qt::Key_F2), this, SLOT(setFixedDateTime()), nullptr, Qt::WidgetWithChildrenShortcut)) + , m_scOffset(new QShortcut(QKeySequence(Qt::Key_F2), this, SLOT(setOffsetDateTime()), nullptr, Qt::WidgetWithChildrenShortcut)) + , m_withCheckBox(withCheckBox) + , m_withNowButton(withNowButton) +{ + +#ifdef TABLET_MODE + + setupUiTouch(); + return; +#else + + m_uiLe = new Ui::LtsDateTimeEditLe; + m_uiLe->setupUi(this); + + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + setFocusPolicy(Qt::StrongFocus); + setContentsMargins(QMargins()); + + QCompleter *completer = new QCompleter(DTG::months(), this); + completer->setCaseSensitivity(Qt::CaseInsensitive); + completer->setCompletionMode(QCompleter::InlineCompletion); + + QRegularExpression rx(DTG::months().join(QLatin1Char('|'))); + QRegularExpressionValidator *val = new QRegularExpressionValidator(rx, this); + m_uiLe->leLongMonth->setCompleter(completer); + m_uiLe->leLongMonth->setValidator(val); + + connect(m_uiLe->leLongDay, &DigitsLineEdit::textEdited, this, &LtsDateTimeEdit::valueDayChanged); + connect(m_uiLe->leLongMonth, &LineEdit::textEdited, this, &LtsDateTimeEdit::valueMonthChanged2); + connect(m_uiLe->leLongTZ, &LineEdit::textEdited, this, &LtsDateTimeEdit::valueTimezoneChangedStr); + connect(m_uiLe->leLongYear, &DigitsLineEdit::textEdited, this, &LtsDateTimeEdit::valueYearChanged); + + connect(m_uiLe->leLongHHMMSS, &DigitsLineEdit::focusOut, this, &LtsDateTimeEdit::onFocusOutHHMMSS); + connect(m_uiLe->leLongDay, &DigitsLineEdit::focusOut, this, &LtsDateTimeEdit::onFocusOutDay); + connect(m_uiLe->leLongMonth, &LineEdit::focusOut, this, &LtsDateTimeEdit::onFocusOutMonth); + connect(m_uiLe->leLongTZ, &LineEdit::focusOut, this, &LtsDateTimeEdit::onFocusOutTZ); + connect(m_uiLe->leLongYear, &DigitsLineEdit::focusOut, this, &LtsDateTimeEdit::onFocusOutYear); + + connect(m_uiLe->leLongHHMMSS, &DigitsLineEdit::focusOutBackspace, this, &LtsDateTimeEdit::onFocusOutBackspace); + connect(m_uiLe->leLongDay, &DigitsLineEdit::focusOutBackspace, this, &LtsDateTimeEdit::onFocusOutBackspace); + connect(m_uiLe->leLongMonth, &LineEdit::focusOutBackspace, this, &LtsDateTimeEdit::onFocusOutBackspace); + connect(m_uiLe->leLongTZ, &LineEdit::focusOutBackspace, this, &LtsDateTimeEdit::onFocusOutBackspace); + connect(m_uiLe->leLongYear, &DigitsLineEdit::focusOutBackspace, this, &LtsDateTimeEdit::onFocusOutBackspace); + + connect(m_uiLe->leLongHHMMSS, &DigitsLineEdit::wheelEventReceived, this, &LtsDateTimeEdit::onHmsWheelEvent); + connect(m_uiLe->leLongDay, &DigitsLineEdit::wheelEventReceived, this, &LtsDateTimeEdit::onDayWheelEvent); + connect(m_uiLe->leLongMonth, &DigitsLineEdit::wheelEventReceived, this, &LtsDateTimeEdit::onMonthWheelEvent); + connect(m_uiLe->leLongYear, &DigitsLineEdit::wheelEventReceived, this, &LtsDateTimeEdit::onYearWheelEvent); + + connect(m_uiLe->checkBox, &QCheckBox::toggled, this, &LtsDateTimeEdit::onCheckBoxChanged); + connect(m_uiLe->pbNow, &QPushButton::clicked, this, &LtsDateTimeEdit::setOffsetDateTime); + + setTimezone(DTG::timezone()); + + bool temp = m_withCheckBox; + m_withCheckBox = !m_withCheckBox; + setWithCheckBox(temp); + + temp = m_withNowButton; + m_withNowButton = !m_withNowButton; + setWithNowButton(temp); + + onCheckBoxChanged(); +#endif +} + +LtsDateTimeEdit::~LtsDateTimeEdit() +{ + delete m_uiLe; +} + +bool LtsDateTimeEdit::setupUiTouch() +{ + m_uiTouch = new Ui::LtsDateTimeEdit; + m_uiTouch->setupUi(this); + setFocusPolicy(Qt::StrongFocus); + + for (int i = 65; i < 91; i++ ) + { + if (QChar(i).toUpper() == QChar::fromLatin1('I')) + continue; + m_uiTouch->cbxTimeZone->addItem(QChar(i), QChar(i)); + } + + connect(m_uiTouch->pbNow, &QPushButton::clicked, + this, &LtsDateTimeEdit::setOffsetDateTime); + connect(m_uiTouch->cbxMonth, static_cast(&QComboBox::currentIndexChanged), + this, &LtsDateTimeEdit::dateTimeChanged); + connect(m_uiTouch->spDay, &QSpinBox::editingFinished, + this, &LtsDateTimeEdit::dateTimeChanged); + connect(m_uiTouch->spHour, &QSpinBox::editingFinished, + this, &LtsDateTimeEdit::dateTimeChanged); + connect(m_uiTouch->spMinutes, &QSpinBox::editingFinished, + this, &LtsDateTimeEdit::dateTimeChanged); + connect(m_uiTouch->spSeconds, &QSpinBox::editingFinished, + this, &LtsDateTimeEdit::dateTimeChanged); + connect(m_uiTouch->spYear, &QSpinBox::editingFinished, + this, &LtsDateTimeEdit::dateTimeChanged); + + connect(m_uiTouch->checkBox, &QCheckBox::toggled, + this, &LtsDateTimeEdit::onCheckBoxChanged); + connect(m_uiTouch->cbxTimeZone, static_cast(&QComboBox::currentIndexChanged), + this, &LtsDateTimeEdit::valueTimezoneChangedInt); + + + + connect(m_uiTouch->cbxMonth, &QComboBox::currentTextChanged, + this, &LtsDateTimeEdit::valueMonthChanged); + + + setMinMaxMonth(1,12); + + setTimezone(DTG::timezone()); + + + bool temp = m_withCheckBox; + m_withCheckBox = !m_withCheckBox; + setWithCheckBox(temp); + + temp = m_withNowButton; + m_withNowButton = !m_withNowButton; + setWithNowButton(temp); + + onCheckBoxChanged(); + + + return true; +} + + +void LtsDateTimeEdit::setRetrieveCurrentTimeCallback(const CurrentTimeCallback &cb) +{ + m_getCurrentTimeCallback = cb; +} + +void LtsDateTimeEdit::setEditTimezone(const bool edit) +{ + m_editTimezone = edit; + if (m_uiLe) + m_uiLe->leLongTZ->setEnabled(m_editTimezone); + if (m_uiTouch) + m_uiTouch->cbxTimeZone->setEnabled(m_editTimezone); +} + +void LtsDateTimeEdit::setWithButtons(const bool buttons) +{ + m_withButtons = buttons; + if (buttons) + { + if (!m_uiLe->pbBBB) + { + m_uiLe->addButtons(this); + connect(m_uiLe->pbBBB, &QPushButton::clicked, this, &LtsDateTimeEdit::onBackHourClicked); + connect(m_uiLe->pbBB, &QPushButton::clicked, this, &LtsDateTimeEdit::onBackMinuteClicked); + connect(m_uiLe->pbB, &QPushButton::clicked, this, &LtsDateTimeEdit::onBackSecondClicked); + connect(m_uiLe->pbF, &QPushButton::clicked, this, &LtsDateTimeEdit::onForwardSecondClicked); + connect(m_uiLe->pbFF, &QPushButton::clicked, this, &LtsDateTimeEdit::onForwardMinuteClicked); + connect(m_uiLe->pbFFF, &QPushButton::clicked, this, &LtsDateTimeEdit::onForwardHourClicked); + disableControls(); + } + } + else + { + delete m_uiLe->pbBBB; + delete m_uiLe->pbBB; + delete m_uiLe->pbB; + delete m_uiLe->pbFFF; + delete m_uiLe->pbFF; + delete m_uiLe->pbF; + + m_uiLe->pbBBB = nullptr; + m_uiLe->pbBB = nullptr; + m_uiLe->pbB = nullptr; + m_uiLe->pbFFF = nullptr; + m_uiLe->pbFF = nullptr; + m_uiLe->pbF = nullptr; + } +} + +void LtsDateTimeEdit::setWithSlider(const bool slider) +{ + m_withSlider = slider; + + if (!m_uiLe) + return; + + if (slider) + { + m_uiLe->addSlider(this); + m_uiLe->slTime->setMinimum(m_minTimeMSec / 1000); + m_uiLe->slTime->setMaximum(m_maxTimeMSec > 0 ? m_maxTimeMSec / 1000 : time(nullptr)); + connect(m_uiLe->slTime, &QSlider::sliderMoved, this, &LtsDateTimeEdit::setSliderValue); + disableControls(); + } + else + { + delete m_uiLe->slTime; + m_uiLe->slTime = nullptr; + } +} + +void LtsDateTimeEdit::setReadOnly(const bool readOnly) +{ + if (m_readOnly == readOnly) + return; + m_readOnly = readOnly; + + if (m_uiLe) + { + m_uiLe->leLongDay->setReadOnly(m_readOnly); + m_uiLe->leLongHHMMSS->setReadOnly(m_readOnly); + m_uiLe->leLongMonth->setReadOnly(m_readOnly); + m_uiLe->leLongTZ->setReadOnly(m_readOnly); + m_uiLe->leLongYear->setReadOnly(m_readOnly); + } + if (m_uiTouch) + { + m_uiTouch->spDay->setReadOnly(m_readOnly); + m_uiTouch->spHour->setReadOnly(m_readOnly); + m_uiTouch->spMinutes->setReadOnly(m_readOnly); + m_uiTouch->spSeconds->setReadOnly(m_readOnly); + m_uiTouch->spYear->setReadOnly(m_readOnly); + m_uiTouch->cbxTimeZone->setEnabled(m_readOnly); + } +} + +void LtsDateTimeEdit::setWithCheckBox(bool checkBoxEnabled) +{ + if (m_withCheckBox == checkBoxEnabled) + return; + + m_withCheckBox = checkBoxEnabled; + if (m_uiLe) + { + m_uiLe->checkBox->setVisible(m_withCheckBox); + if (!m_withCheckBox) + m_uiLe->checkBox->setChecked(true); + } + if (m_uiTouch) + { + m_uiTouch->checkBox->setVisible(m_withCheckBox); + if (!m_withCheckBox) + m_uiTouch->checkBox->setChecked(true); + } +} + +void LtsDateTimeEdit::setCheckBoxChecked(bool bChecked) +{ + if (!m_uiLe) + return; + m_uiLe->checkBox->setChecked(bChecked); +} + +bool LtsDateTimeEdit::isCheckBoxChecked() const +{ + if (!m_uiLe) + return false; + return m_uiLe->checkBox->isChecked(); +} + +void LtsDateTimeEdit::setWithNowButton(bool nowButton) +{ + if (m_withNowButton == nowButton) + return; + + m_withNowButton = nowButton; + if (m_uiLe) + m_uiLe->pbNow->setVisible(m_withNowButton); + if (m_uiTouch) + m_uiTouch->pbNow->setVisible(m_withNowButton); +} + +bool LtsDateTimeEdit::setMsecsSinceEpoch(qint64 msec, bool setToNearestPossible) +{ + if (msec == 0 && m_withCheckBox) + { + if (m_uiLe) + m_uiLe->checkBox->setChecked(false); + m_dateIsAcceptable = true; + return true; + } + if (m_uiLe) + m_uiLe->checkBox->setChecked(true); + + m_dateIsAcceptable = false; + if (msec == 0) + return false; + + + + + + + if (!setToNearestPossible) + { + if (!isDataAcceptable()) + return false; + } + else + { + if (msec < m_minTimeMSec && m_minTimeMSec > 0) + msec = m_minTimeMSec; + if (msec > m_maxTimeMSec && m_maxTimeMSec > 0) + msec = m_maxTimeMSec; + } + + m_curTimeMSec = msec; + + const qint64 offsetTimeZoneMsec = getTimezoneAsSecs() * 1000; + const QDateTime datetime(QDateTime::fromMSecsSinceEpoch(m_curTimeMSec + offsetTimeZoneMsec, Qt::UTC)); + const QDate date(datetime.date()); + const QTime time(datetime.time()); + + const int day = date.day(); + const int month = date.month(); + const int year = date.year(); + const int hour = time.hour(); + const int minute = time.minute(); + const int second = time.second(); + + + if (!setYear(year) || + !setMonth(month) || + !setDay(day) || + !setHHMMSS(hour, minute, second)) + return false; + + if (!isDataAcceptable()) + return false; + + enableControls(); + + emit dateTimeChanged(); + + return true; +} + + +qint64 LtsDateTimeEdit::msecsSinceEpoch() const +{ + if (isEmpty()) + return 0; + + if (m_uiLe && !m_uiLe->checkBox->isChecked()) + return 0; + if (m_uiTouch && !m_uiTouch->checkBox->isChecked()) + return 0; + + auto that = const_cast(this); + if (!that->isDataAcceptable()) + return 0; + + return m_curTimeMSec; +} + +bool LtsDateTimeEdit::setTimezone(char tz) +{ + tz = toupper(tz); + if (!DTG::isTimezoneValid(tz)) + return false; + + if (m_uiLe) + setItemText(m_uiLe->leLongTZ, QString(QLatin1Char(tz))); + if (m_uiTouch) + setItemText(m_uiTouch->cbxTimeZone, QString(QLatin1Char(tz))); + + setMsecsSinceEpoch(m_curTimeMSec); + + return true; +} + +char LtsDateTimeEdit::getTimezone() const +{ + QString str; + if (m_uiLe) + str = getItemText(m_uiLe->leLongTZ); + if (m_uiTouch) + str = getItemText(m_uiTouch->cbxTimeZone); + + if (str.isEmpty()) + return DTG::timezone(); + return str[0].toUpper().toLatin1(); +} + +qint64 LtsDateTimeEdit::getTimezoneAsSecs() const +{ + const char tzGUI = getTimezone(); + return DTG::timezone2Secs(tzGUI); +} + +void LtsDateTimeEdit::clearTimezone() +{ + if (m_uiLe) + m_uiLe->leLongTZ->clear(); + if (m_uiTouch) + m_uiTouch->cbxTimeZone->setCurrentIndex(0); +} + +void LtsDateTimeEdit::setMinMaxMonth(int minMonth, int maxMonth) +{ + if (!m_uiTouch) + return; + + if (m_minMonth != minMonth) + m_minMonth = minMonth; + if (m_maxMonth != maxMonth) + m_maxMonth = maxMonth; + + int currentMonth = m_uiTouch->cbxMonth->currentData().toInt(); + m_uiTouch->cbxMonth->blockSignals(true); + m_uiTouch->cbxMonth->clear(); + for (int i =1; i<= 12 ; ++i) + { + if (i < m_minMonth) + continue; + if (i > m_maxMonth) + break; + + const QStringList monthSl = DTG::months(); + m_uiTouch->cbxMonth->addItem(monthSl[i-1], i); + + + + } + + + m_uiTouch->cbxMonth->setCurrentIndex(m_uiTouch->cbxMonth->findData(currentMonth)); + m_uiTouch->cbxMonth->blockSignals(false); +} + + +void LtsDateTimeEdit::setSubWidgetsProperties(const QString &name, const QVariant &value) +{ + if (m_uiLe) + { + m_uiLe->leLongDay->setProperty(name.toStdString().c_str(), value); + m_uiLe->leLongHHMMSS->setProperty(name.toStdString().c_str(), value); + m_uiLe->leLongMonth->setProperty(name.toStdString().c_str(), value); + m_uiLe->leLongYear->setProperty(name.toStdString().c_str(), value); + m_uiLe->leLongTZ->setProperty(name.toStdString().c_str(), value); + } + if (m_uiTouch) + { + m_uiTouch->spDay->setProperty(name.toStdString().c_str(), value); + m_uiTouch->spHour->setProperty(name.toStdString().c_str(), value); + m_uiTouch->spMinutes->setProperty(name.toStdString().c_str(), value); + m_uiTouch->spSeconds->setProperty(name.toStdString().c_str(), value); + m_uiTouch->spYear->setProperty(name.toStdString().c_str(), value); + } +} + +bool LtsDateTimeEdit::setMonth(int month) +{ + const auto months(DTG::months()); + if (month < 1 || month > months.size()) + return false; + + if (m_uiLe) + setItemText(m_uiLe->leLongMonth, months.at(month - 1)); + if (m_uiTouch) + setItemData(m_uiTouch->cbxMonth, month); + return true; +} + +int LtsDateTimeEdit::getMonth() const +{ + if (m_uiLe) + { + const QString str = getItemText(m_uiLe->leLongMonth); + if (str.length() != 3) + return -1; + + int idx = DTG::months().indexOf(str); + if (idx < 0) + return -1; + return idx + 1; + } + if (m_uiTouch) + { + int month = getItemData(m_uiTouch->cbxMonth); + return month; + } + return 0; +} + +bool LtsDateTimeEdit::setDay(int day) +{ + if (day < 1 || day > 31) + return false; + + QString oldDay; + if (m_uiLe) + oldDay = m_uiLe->leLongDay->text(); + int oldDayInt = 0; + if (m_uiTouch) + oldDayInt = getItemValue(m_uiTouch->spDay); + + if (m_uiLe) + setItemText(m_uiLe->leLongDay, QStringLiteral("%1").arg(day, 2, 10, QLatin1Char('0'))); + if (m_uiTouch) + setItemValue(m_uiTouch->spDay, day); + + if (!isDayAcceptable()) + { + if (m_uiLe) + setItemText(m_uiLe->leLongDay, oldDay); + if (m_uiTouch) + setItemValue(m_uiTouch->spDay, oldDayInt); + return false; + } + + return true; +} + +int LtsDateTimeEdit::getDay() const +{ + if (m_uiLe) + return m_uiLe->leLongDay->getValue(); + if (m_uiTouch) + return getItemValue(m_uiTouch->spDay); + return 0; +} + +bool LtsDateTimeEdit::setHHMMSS(int hour, int minute, int second) +{ + if (hour < 0 || hour > 23 || + minute < 0 || minute > 59 || + second < 0 || second > 59) + return false; + + if (m_uiLe) + m_uiLe->leLongHHMMSS->setValue(hour * 10000 + minute * 100 + second); + if (m_uiTouch) + { + setItemValue(m_uiTouch->spHour , hour); + setItemValue(m_uiTouch->spMinutes, minute); + setItemValue(m_uiTouch->spSeconds, second); + } + + return true; +} + +int LtsDateTimeEdit::getHour() const +{ + if (m_uiLe) + { + const int time = m_uiLe->leLongHHMMSS->getValue(); + return time / 10000; + } + if (m_uiTouch) + { + return getItemValue(m_uiTouch->spHour); + } + return 0; +} + +int LtsDateTimeEdit::getMinute() const +{ + if (m_uiLe) + { + const int time = m_uiLe->leLongHHMMSS->getValue(); + return (time / 100) % 100; + } + if (m_uiTouch) + { + return getItemValue(m_uiTouch->spMinutes); + } + return 0; +} + +int LtsDateTimeEdit::getSecond() const +{ + if (m_uiLe) + { + const int time = m_uiLe->leLongHHMMSS->getValue(); + return time % 100; + } + if (m_uiTouch) + { + return getItemValue(m_uiTouch->spSeconds); + } + return 0; +} + +bool LtsDateTimeEdit::setYear(int year) +{ + int finalYear = -1; + if (year >= 0 && year <= 99) + finalYear = year; + else if (year >= 2000 && year <= 2099) + finalYear = year - 2000; + + if (finalYear >= 0) + { + if (m_uiLe) + m_uiLe->leLongYear->setValue(finalYear); + if (m_uiTouch) + setItemValue(m_uiTouch->spYear, finalYear); + return true; + } + + return false; +} + +int LtsDateTimeEdit::getYear() const +{ + if (m_uiLe) + return 2000 + m_uiLe->leLongYear->getValue(); + if (m_uiTouch) + return 2000 + m_uiTouch->spYear->value(); + return 2000; +} + +bool LtsDateTimeEdit::isEmpty() const +{ + return (isEmptyDay() || + isEmptyHHMMSS() || + isEmptyMonth() || + isEmptyTimezone() || + isEmptyYear()); +} + +bool LtsDateTimeEdit::isValidDate() const +{ + return m_dateIsAcceptable; +} + + +bool LtsDateTimeEdit::isEmptyDay() const +{ + if (m_uiLe) + return m_uiLe->leLongDay->isEmpty(); + return false; +} + +bool LtsDateTimeEdit::isEmptyHHMMSS() const +{ + if (m_uiLe) + return m_uiLe->leLongHHMMSS->isEmpty(); + return false; +} + +bool LtsDateTimeEdit::isEmptyMonth() const +{ + if (m_uiLe) + return m_uiLe->leLongMonth->text().isEmpty(); + return false; +} + +bool LtsDateTimeEdit::isEmptyTimezone() const +{ + if (m_uiLe) + return m_uiLe->leLongTZ->text().isEmpty(); + return false; +} + +bool LtsDateTimeEdit::isEmptyYear() const +{ + if (m_uiLe) + return m_uiLe->leLongYear->isEmpty(); + return false; +} + +bool LtsDateTimeEdit::hasFocus() const +{ + if (!m_uiLe) + return false; + return (m_uiLe->leLongDay->hasFocus() || + m_uiLe->leLongHHMMSS->hasFocus() || + m_uiLe->leLongMonth->hasFocus() || + m_uiLe->leLongTZ->hasFocus() || + m_uiLe->leLongYear->hasFocus()); +} + +bool LtsDateTimeEdit::isDataAcceptable() +{ + m_dateIsAcceptable = false; + + bool state = + isDayAcceptable() && + isHHMMSSAcceptable() && + isMonthAcceptable() && + isYearAcceptable() && + QDate::isValid(getYear(), getMonth(), getDay()) && + QTime::isValid(getHour(), getMinute(), getSecond()); + + if (m_editTimezone && state) + state = isTzAcceptable(); + + showInvalidData(false); + + if (!state) + { + disableControls(); + return false; + } + + qint64 msec = QDateTime(QDate(getYear(), getMonth(), getDay()), QTime(getHour(), getMinute(), getSecond()), Qt::UTC).toMSecsSinceEpoch(); + msec -= DTG::timezone2Secs(getTimezone()) * 1000LL; + if (m_minTimeMSec > 0 && m_maxTimeMSec > 0 && m_minTimeMSec < m_maxTimeMSec) + { + if (msec < m_minTimeMSec || msec > m_maxTimeMSec) + return false; + } + + m_curTimeMSec = msec; + enableControls(); + m_dateIsAcceptable = true; + return true; +} + +bool LtsDateTimeEdit::isDayAcceptable() const +{ + int value = getDay(); + int max = 31; + if (isMonthAcceptable()) + { + const int month = getMonth(); + switch (month) + { + case 2: + { + max = 28; + if (isYearAcceptable()) + { + if (getYear() % 4 == 0 && (getYear() % 100 != 0 || getYear() % 400 == 0)) + + max = 29; + } + break; + } + case 4: + case 6: + case 9: + case 11: + max = 30; + break; + default: + break; + } + } + return (value > 0 && value <= max); +} + +bool LtsDateTimeEdit::isHHMMSSAcceptable() const +{ + int hh = getHour(); + if (hh < 0 || hh > 23) + return false; + + int mm = getMinute(); + if (mm < 0 || mm > 59) + return false; + + int ss = getSecond(); + if (ss < 0 || ss > 59) + return false; + + return true; +} + +bool LtsDateTimeEdit::isMonthAcceptable() const +{ + int value = getMonth(); + return (value > 0 && value <= 12); +} + +bool LtsDateTimeEdit::isYearAcceptable() const +{ + if (m_uiLe) + return isLineEditDataAcceptable(m_uiLe->leLongYear); + return true; +} + +bool LtsDateTimeEdit::isTzAcceptable() const +{ + if (m_uiLe) + return isLineEditDataAcceptable(m_uiLe->leLongTZ); + return true; +} + +void LtsDateTimeEdit::showInvalidData(bool setFocus) +{ + if (!m_uiLe) + return; + + bool focus = setFocus; + + auto check = [&](QLineEdit * le, bool isValid) + { + if (isValid) + le->setStyleSheet(QString()); + else + { + le->setStyleSheet(QLatin1String("background-color: yellow;")); + if (focus) + { + le->setFocus(); + focus = false; + } + } + }; + + check(m_uiLe->leLongDay, isDayAcceptable()); + check(m_uiLe->leLongHHMMSS, isHHMMSSAcceptable()); + if (m_editTimezone) + check(m_uiLe->leLongTZ, isTzAcceptable()); + check(m_uiLe->leLongMonth, isMonthAcceptable()); + check(m_uiLe->leLongYear, isYearAcceptable()); +} + +bool LtsDateTimeEdit::isLineEditDataAcceptable(QLineEdit *le) const +{ + const QValidator *pValidator = le->validator(); + + if (!pValidator) + return true; + + QString text = le->text(); + + int pos; + QValidator::State state = pValidator->validate(text, pos); + + if (state == QValidator::Acceptable || state == QValidator::Intermediate) + return true; + + return false; +} + +void LtsDateTimeEdit::setFixedDateTime() +{ + setMsecsSinceEpoch(LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("TimeSettings"), QStringLiteral("FixedTime"), 0).toLongLong()); +} + +void LtsDateTimeEdit::setOffsetDateTime() +{ + if (!m_getCurrentTimeCallback) + return; + + qint64 offset = LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("TimeSettings"), QStringLiteral("Offset"), 0).toLongLong(); + + setMsecsSinceEpoch((m_getCurrentTimeCallback() / 1000) * 1000 + offset); +} + +void LtsDateTimeEdit::setSliderValue(int val) +{ + setMsecsSinceEpoch(val * 1000LL); +} + +void LtsDateTimeEdit::valueDayChanged(const QString &str) +{ + if (!m_uiLe) + return; + int curpos = m_uiLe->leLongDay->cursorPosition(); + int cp = curpos; + + bool changed = false; + + QString tag(str); + + if (tag.length() > 2) + { + tag = tag.left(2); + changed = true; + } + + bool ok; + int t = tag.toInt(&ok, 10); + + if (ok) + { + if (t > 31) + { + tag.remove(curpos - 1, 1); + if (cp >= curpos) + cp--; + changed = true; + } + } + + if (changed) + { + m_uiLe->leLongDay->setText(tag); + m_uiLe->leLongDay->setCursorPosition(cp); + } + + if (tag.length() == 2) + { + showInvalidData(false); + + if (m_autoForward) + focusNextPrevChild(true); + } +} + +void LtsDateTimeEdit::fixupHHMMSS() +{ + if (!m_uiLe) + return; + + int curpos = m_uiLe->leLongHHMMSS->cursorPosition(); + int cp = curpos; + + bool changed = false; + + QString uhrzeit(m_uiLe->leLongHHMMSS->text()); + + + + + + + QString hh; + QString mm; + QString ss; + + if (uhrzeit.length() > 1) + hh = uhrzeit.left(2); + else + hh = uhrzeit + QLatin1Char('0'); + + if (uhrzeit.length() > 0) + { + if (uhrzeit.length() == 1) + hh = uhrzeit.mid(0, 1) + QLatin1Char('0'); + else if (uhrzeit.length() >= 2) + { + hh = uhrzeit.mid(0, 2); + if (uhrzeit.length() > 2) + { + if (uhrzeit.length() == 3) + mm = uhrzeit.mid(2, 1) + QLatin1Char('0'); + else if (uhrzeit.length() >= 4) + { + mm = uhrzeit.mid(2, 2); + if (uhrzeit.length() == 5) + ss = uhrzeit.mid(4, 1) + QLatin1Char('0'); + else if (uhrzeit.length() == 6) + ss = uhrzeit.mid(4, 2); + } + } + } + } + + bool ok; + + int h = hh.toInt(&ok, 10); + if (ok) + { + if (h > 23) + { + uhrzeit.remove(curpos - 1, 1); + if (cp >= curpos) + cp--; + changed = true; + } + } + + int m = mm.toInt(&ok, 10); + + if (ok) + { + if ((hh.length() == 2) && (m > 59)) + { + uhrzeit.remove(curpos - 1, 1); + if (cp >= curpos) + cp--; + changed = true; + } + } + + int s = ss.toInt(&ok, 10); + + if (ok) + { + if ((hh.length() == 2 && mm.length() == 2) && (s > 59)) + { + uhrzeit.remove(curpos - 1, 1); + if (cp >= curpos) + cp--; + changed = true; + } + } + + if (changed) + { + m_uiLe->leLongHHMMSS->setText(uhrzeit); + m_uiLe->leLongHHMMSS->setCursorPosition(cp); + } + + isDataAcceptable(); + emit dateTimeChanged(); +} + +void LtsDateTimeEdit::valueMonthChanged2(const QString &str) +{ + if (!m_uiLe) + return; + if (str.length() == 3 && m_uiLe->leLongMonth->selectedText().isEmpty()) + focusNextPrevChild(true); +} + +void LtsDateTimeEdit::valueMonthChanged(const QString &str) +{ + if (!m_uiLe) + return; + int curpos = m_uiLe->leLongMonth->cursorPosition(); + int cp = curpos; + + bool changed = false; + + QString monStr = str; + + const auto months = DTG::months(); + for (int i = 0; i < months.size(); ++i) + { + const QString &mon = months.at(i); + if (mon.startsWith(monStr, Qt::CaseInsensitive)) + { + monStr = mon; + changed = true; + break; + } + } + + if (!changed) + { + monStr.remove(curpos - 1, 1); + if (cp >= curpos) + cp--; + changed = true; + } + + if (changed) + { + m_uiLe->leLongMonth->setText(monStr); + + + if (cp > 0) + m_uiLe->leLongMonth->setCursorPosition(cp); + else + m_uiLe->leLongMonth->setCursorPosition(1); + } + + if (monStr.size() == 3) + { + showInvalidData(false); + if (m_autoForward) + focusNextPrevChild(true); + } +} + +void LtsDateTimeEdit::valueTimezoneChangedStr(const QString &str) +{ + if (str.length() > 1) + { + clearTimezone(); + return; + } + if (str.length() == 0) + return; + QChar c = str.at(0); + if (!c.isLetter() || c.toUpper() == QLatin1Char('J')) + { + clearTimezone(); + return; + } + if (c.isLower()) + setTimezone(c.toUpper().toLatin1()); + + showInvalidData(false); + if (m_autoForward) + focusNextPrevChild(true); +} + +void LtsDateTimeEdit::valueTimezoneChangedInt(int newTimezone) +{ + Q_UNUSED(newTimezone); + if (!m_uiTouch) + return; + const QString &curText = m_uiTouch->cbxTimeZone->currentText(); + if (curText.isEmpty()) + return; + + const QChar tz = curText.at(0); + setTimezone(tz.toUpper().toLatin1()); +} + +void LtsDateTimeEdit::valueYearChanged(const QString &str) +{ + if (!m_uiLe) + return; + int curpos = m_uiLe->leLongYear->cursorPosition(); + int cp = curpos; + + bool changed = false; + + const QString &year = str; + + QString yy; + if (year.length() > 2) + { + yy = year.left(2); + changed = true; + } + + if (changed) + { + m_uiLe->leLongYear->setText(yy); + m_uiLe->leLongYear->setCursorPosition(cp); + } + + if (year.length() == 2) + { + showInvalidData(false); + if (m_autoForward) + focusNextPrevChild(true); + } +} + +void LtsDateTimeEdit::onBackHourClicked() +{ + if (!setMsecsSinceEpoch(m_curTimeMSec - s_oneHourMSec)) + setMsecsSinceEpoch(m_minTimeMSec); +} + +void LtsDateTimeEdit::onBackMinuteClicked() +{ + const int ofs = QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) ? 5 : 1; + if (!setMsecsSinceEpoch(m_curTimeMSec - ofs * s_oneMinuteMSec)) + setMsecsSinceEpoch(m_minTimeMSec); +} + +void LtsDateTimeEdit::onBackSecondClicked() +{ + const int ofs = QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) ? 5 : 1; + if (!setMsecsSinceEpoch(m_curTimeMSec - ofs * s_oneSecondMSec)) + setMsecsSinceEpoch(m_minTimeMSec); +} + +void LtsDateTimeEdit::onForwardSecondClicked() +{ + const int ofs = QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) ? 5 : 1; + if (!setMsecsSinceEpoch(m_curTimeMSec + ofs * s_oneSecondMSec)) + setMsecsSinceEpoch(m_maxTimeMSec); +} + +void LtsDateTimeEdit::onForwardMinuteClicked() +{ + const int ofs = QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier) ? 5 : 1; + if (!setMsecsSinceEpoch(m_curTimeMSec + ofs * s_oneMinuteMSec)) + setMsecsSinceEpoch(m_maxTimeMSec); +} + +void LtsDateTimeEdit::onForwardHourClicked() +{ + if (!setMsecsSinceEpoch(m_curTimeMSec + s_oneHourMSec)) + setMsecsSinceEpoch(m_maxTimeMSec); +} + +void LtsDateTimeEdit::onCheckBoxChanged() +{ + QVector widgets; + bool bEnable = false; + + if (m_uiLe) + { + widgets = {m_uiLe->leLongDay, m_uiLe->leLongHHMMSS, m_uiLe->leLongTZ, + m_uiLe->leLongMonth, m_uiLe->leLongYear}; + bEnable = m_uiLe->checkBox->isChecked(); + } + + if (m_uiTouch) + { + widgets = {m_uiTouch->spDay, m_uiTouch->spHour, m_uiTouch->spMinutes, m_uiTouch->spSeconds, m_uiTouch->spYear, m_uiTouch->cbxMonth, m_uiTouch->cbxTimeZone, m_uiTouch->pbNow}; + bEnable = m_uiTouch->checkBox->isChecked(); + } + + for (auto w : widgets) + { + if (w) + w->setEnabled(bEnable); + } + enaDisaControls(bEnable); +} + +void LtsDateTimeEdit::disableControls() +{ + enaDisaControls(false); +} + +void LtsDateTimeEdit::enableControls() +{ + if (!m_uiLe) + return; + + enaDisaControls(true); + if (m_withSlider && + m_minTimeMSec != 0 && + m_maxTimeMSec > 0 && + m_maxTimeMSec > m_minTimeMSec) + { + m_uiLe->slTime->setEnabled(true); + m_uiLe->slTime->setValue(m_curTimeMSec / 1000LL); + } +} + +void LtsDateTimeEdit::enaDisaControls(bool bEnable) +{ + if (!m_uiLe) + return; + const QVector widgets({m_uiLe->pbBBB, m_uiLe->pbBB, m_uiLe->pbB, + m_uiLe->pbF, m_uiLe->pbFF, m_uiLe->pbFFF, + m_uiLe->slTime}); + for (auto w : widgets) + { + if (w) + w->setEnabled(bEnable); + } +} + +void LtsDateTimeEdit::setMinimumTimeMSec(qint64 msec) +{ + if (m_minTimeMSec == msec) + return; + m_minTimeMSec = msec; + if (m_minTimeMSec > 0 && m_uiLe && m_uiLe->slTime) + { + m_uiLe->slTime->setMinimum(m_minTimeMSec / 1000LL); + if (m_maxTimeMSec > m_minTimeMSec) + m_uiLe->slTime->setEnabled(true); + } + if (m_curTimeMSec < m_minTimeMSec) + setMsecsSinceEpoch(m_minTimeMSec); +} + +void LtsDateTimeEdit::setMaximumTimeMSec(qint64 msec) +{ + if (m_maxTimeMSec == msec) + return; + m_maxTimeMSec = msec; + if (!m_uiLe || !m_uiLe->slTime) + return; + + m_uiLe->slTime->setMaximum(m_maxTimeMSec / 1000LL); + if (m_minTimeMSec > 0 && m_maxTimeMSec > m_minTimeMSec) + m_uiLe->slTime->setEnabled(true); + if (m_maxTimeMSec > 0 && m_curTimeMSec > m_maxTimeMSec) + setMsecsSinceEpoch(m_maxTimeMSec); +} + +void LtsDateTimeEdit::clear() +{ + if (m_uiLe) + { + m_uiLe->leLongDay->clear(); + m_uiLe->leLongHHMMSS->clear(); + m_uiLe->leLongMonth->clear(); + m_uiLe->leLongYear->clear(); + m_uiLe->leLongTZ->clear(); + m_curTimeMSec = 0; + } + if (m_uiTouch) + { + m_uiTouch->spDay->clear(); + m_uiTouch->spHour->clear(); + m_uiTouch->spMinutes->clear(); + m_uiTouch->spSeconds->clear(); + m_uiTouch->spYear->clear(); + } + emit dateTimeChanged(); + +} + +void LtsDateTimeEdit::onFocusOutDay() +{ + if (!m_uiLe) + return; + if (m_backspacePressed) + { + m_backspacePressed = false; + return; + } + if (!m_recursionAvoid && !hasFocus()) + onFocusOut(); + + QString value = m_uiLe->leLongDay->text(); + + if (value.length() == 0 && !m_backspacePressed) + { + if (!isEmptyHHMMSS() || + !isEmptyMonth() || + (!isEmptyTimezone() && m_uiLe->leLongTZ->isEnabled()) || + !isEmptyYear()) + { + if (m_getCurrentTimeCallback) + { + const QDateTime dt(QDateTime::fromMSecsSinceEpoch(m_getCurrentTimeCallback(), Qt::UTC)); + setDay(dt.date().day()); + } + } + } + isDataAcceptable(); + emit dateTimeChanged(); +} + +void LtsDateTimeEdit::onFocusOutHHMMSS() +{ + if (!m_uiLe) + return; + if (m_backspacePressed) + { + m_backspacePressed = false; + return; + } + if (!m_recursionAvoid && !hasFocus()) + onFocusOut(); + + QString value = m_uiLe->leLongHHMMSS->text(); + + if (value.length() == 0 && !m_backspacePressed) + { + if (!isEmptyDay() || + !isEmptyMonth() || + !(isEmptyTimezone() && m_uiLe->leLongTZ->isEnabled()) || + !isEmptyYear()) + { + if (m_getCurrentTimeCallback) + { + const QDateTime dt(QDateTime::fromMSecsSinceEpoch(m_getCurrentTimeCallback(), Qt::UTC)); + setHHMMSS(dt.time().hour(), dt.time().minute(), dt.time().second()); + } + } + } + value += QString(6 - value.length(), QLatin1Char('0')); + m_uiLe->leLongHHMMSS->setText(value); + isDataAcceptable(); + emit dateTimeChanged(); +} + +void LtsDateTimeEdit::onFocusOutTZ() +{ + if (!m_uiLe) + return; + if (m_backspacePressed) + { + m_backspacePressed = false; + return; + } + if (!m_recursionAvoid && !hasFocus()) + onFocusOut(); + + QString value = m_uiLe->leLongTZ->text(); + + if (value.length() == 0 && !m_backspacePressed) + { + if (!isEmptyHHMMSS() || + !isEmptyMonth() || + !isEmptyDay() || + !isEmptyYear()) + setTimezone(DTG::timezone()); + } + isDataAcceptable(); + emit dateTimeChanged(); +} + +void LtsDateTimeEdit::onFocusOutMonth() +{ + if (!m_uiLe) + return; + if (m_backspacePressed) + { + m_backspacePressed = false; + return; + } + + if (!m_recursionAvoid && !hasFocus()) + onFocusOut(); + + QString value = m_uiLe->leLongMonth->text(); + + if (value.length() == 0 && !m_backspacePressed) + { + if (!isEmptyHHMMSS() || + !isEmptyDay() || + (!isEmptyTimezone() && m_uiLe->leLongTZ->isEnabled()) || + !isEmptyYear()) + { + if (m_getCurrentTimeCallback) + { + const QDateTime dt(QDateTime::fromMSecsSinceEpoch(m_getCurrentTimeCallback(), Qt::UTC)); + setMonth(dt.date().month()); + } + } + } + isDataAcceptable(); + emit dateTimeChanged(); +} + +void LtsDateTimeEdit::onFocusOutYear() +{ + if (!m_uiLe) + return; + if (m_backspacePressed) + { + m_backspacePressed = false; + return; + } + + if (!m_recursionAvoid && !hasFocus()) + onFocusOut(); + + QString value = m_uiLe->leLongYear->text(); + + if (value.length() == 1) + { + value = QLatin1Char('0') + value; + m_uiLe->leLongYear->setText(value); + } + + if (value.length() == 0 && !m_backspacePressed) + { + if (!isEmptyHHMMSS() || + !isEmptyMonth() || + (!isEmptyTimezone() && m_uiLe->leLongTZ->isEnabled()) || + !isEmptyDay()) + { + if (m_getCurrentTimeCallback) + { + const QDateTime dt(QDateTime::fromMSecsSinceEpoch(m_getCurrentTimeCallback(), Qt::UTC)); + setYear(dt.date().year()); + } + } + } + isDataAcceptable(); + emit dateTimeChanged(); +} + +void LtsDateTimeEdit::onFocusOutBackspace() +{ + m_backspacePressed = true; +} + +void LtsDateTimeEdit::onFocusOut() +{ + if (!m_uiLe) + return; + if (m_backspacePressed) + { + m_backspacePressed = false; + return; + } + m_recursionAvoid = true; + onFocusOutDay(); + onFocusOutHHMMSS(); + onFocusOutMonth(); + onFocusOutTZ(); + onFocusOutYear(); + m_recursionAvoid = false; +} + +void LtsDateTimeEdit::onHmsWheelEvent(bool bForward, int cursorPos) +{ + if (!m_uiLe) + return; + if (m_uiLe->leLongHHMMSS->text().size() != 6) + return; + + fixupHHMMSS(); + + int selStart = 0; + switch (cursorPos) + { + case 0: + case 1: + case 2: + if (bForward) + onForwardHourClicked(); + else + onBackHourClicked(); + selStart = 0; + break; + case 3: + case 4: + if (bForward) + onForwardMinuteClicked(); + else + onBackMinuteClicked(); + selStart = 2; + break; + case 5: + case 6: + if (bForward) + onForwardSecondClicked(); + else + onBackSecondClicked(); + selStart = 4; + break; + default: + return; + } + m_uiLe->leLongHHMMSS->setCursorPosition(cursorPos); + m_uiLe->leLongHHMMSS->setSelection(selStart, 2); + emit dateTimeChanged(); +} + +void LtsDateTimeEdit::onDayWheelEvent(bool bForward, int cursorPos) +{ + if (!m_uiLe) + return; + if (!isDayAcceptable()) + return; + const int ofs = bForward ? 1 : -1; + setMsecsSinceEpoch(m_curTimeMSec + ofs * s_oneDayMSec, true); + m_uiLe->leLongDay->setCursorPosition(cursorPos); + m_uiLe->leLongDay->setSelection(0, 2); + emit dateTimeChanged(); +} + +void LtsDateTimeEdit::onMonthWheelEvent(bool bForward, int cursorPos) +{ + if (!m_uiLe) + return; + if (!isMonthAcceptable()) + return; + const int ofs = bForward ? 1 : -1; + const QDateTime dt(QDateTime::fromMSecsSinceEpoch(m_curTimeMSec, Qt::UTC).addMonths(ofs)); + setMsecsSinceEpoch(dt.toMSecsSinceEpoch(), true); + m_uiLe->leLongMonth->setCursorPosition(cursorPos); + m_uiLe->leLongMonth->setSelection(0, 3); + emit dateTimeChanged(); +} + +void LtsDateTimeEdit::onYearWheelEvent(bool bForward, int cursorPos) +{ + if (!m_uiLe) + return; + if (!isYearAcceptable()) + return; + const int ofs = bForward ? 1 : -1; + const QDateTime dt(QDateTime::fromMSecsSinceEpoch(m_curTimeMSec, Qt::UTC).addYears(ofs)); + setMsecsSinceEpoch(dt.toMSecsSinceEpoch(), true); + m_uiLe->leLongYear->setCursorPosition(cursorPos); + m_uiLe->leLongYear->setSelection(0, 2); + emit dateTimeChanged(); +} + +} diff --git a/Lts/Common/LtsWidgets/lts_date_time_edit_rc.h b/Lts/Common/LtsWidgets/lts_date_time_edit_rc.h new file mode 100755 index 000000000..5965d37f9 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_date_time_edit_rc.h @@ -0,0 +1,206 @@ + + + +#pragma once + + +#include "lts_widgets_export.h" + +#include +#include + + +namespace Ui +{ +class LtsDateTimeEditLe; +class LtsDateTimeEdit; +} + +class QLineEdit; +class QShortcut; + +namespace LtsWidgets +{ +class LTSWIDGETS_EXPORT LtsDateTimeEdit : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(bool autoForward READ getAutoForward WRITE setAutoForward) + Q_PROPERTY(bool editTimezone READ getEditTimezone WRITE setEditTimezone) + Q_PROPERTY(bool withButtons READ getWithButtons WRITE setWithButtons) + Q_PROPERTY(bool withSlider READ getWithSlider WRITE setWithSlider) + Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly) + Q_PROPERTY(bool withNowButton READ withNowButton WRITE setWithNowButton) + Q_PROPERTY(bool withCheckBox READ withCheckBox WRITE setWithCheckBox) + +public: + + using CurrentTimeCallback = std::function; + + explicit LtsDateTimeEdit(QWidget *parent = nullptr, bool withNowButton = true, bool withCheckBox = false); + ~LtsDateTimeEdit(); + + bool setupUiTouch(); + + + qint64 msecsSinceEpoch() const; + bool isEmpty() const; + bool isValidDate() const; + + void setAutoForward(const bool af) { m_autoForward = af; } + bool getAutoForward() const { return m_autoForward; } + + void setEditTimezone(const bool edit); + bool getEditTimezone() const { return m_editTimezone; } + + void setWithButtons(const bool buttons); + bool getWithButtons() const { return m_withButtons; } + + void setWithSlider(const bool slider); + bool getWithSlider() const { return m_withSlider; } + + void setReadOnly(const bool readOnly); + bool isReadOnly() const { return m_readOnly; } + + void setWithCheckBox(bool checkBoxEnabled); + bool withCheckBox() const { return m_withCheckBox; } + void setCheckBoxChecked(bool bChecked); + bool isCheckBoxChecked() const; + + void setWithNowButton(bool nowButton); + bool withNowButton() const { return m_withNowButton; } + + + void setMinimumTimeMSec(qint64 msec); + qint64 minimumTimeMSec() const { return m_minTimeMSec; } + + + void setMaximumTimeMSec(qint64 msec); + qint64 maximumTimeMSec() const { return m_maxTimeMSec; } + + void setMinMaxTimeMSec(qint64 min, qint64 max) { setMinimumTimeMSec(min); setMaximumTimeMSec(max); } + + void setRetrieveCurrentTimeCallback(const CurrentTimeCallback &cb); + + bool setTimezone(char tz); + char getTimezone() const; + qint64 getTimezoneAsSecs() const; + + void setSubWidgetsProperties(const QString &name, const QVariant &value); + +Q_SIGNALS: + void dateTimeChanged(); + +public Q_SLOTS: + bool setMsecsSinceEpoch(qint64 msec, bool setToNearestPossible = true); + void clear(); + +private: + void clearTimezone(); + + void setMinMaxMonth(int minMonth, int maxMonth); + + bool setMonth(int month); + int getMonth() const; + + bool setDay(int day); + int getDay() const; + + bool setHHMMSS(int hour, int minute, int second); + int getHour() const; + int getMinute() const; + int getSecond() const; + + bool setYear(int year); + int getYear() const; + + bool isEmptyDay() const; + bool isEmptyHHMMSS() const; + bool isEmptyTimezone() const; + bool isEmptyMonth() const; + bool isEmptyYear() const; + + bool hasFocus() const; + + bool isDataAcceptable(); + + void showInvalidData(bool setFocus); + + bool isLineEditDataAcceptable(QLineEdit *le) const; + + bool isDayAcceptable() const; + bool isHHMMSSAcceptable() const; + bool isMonthAcceptable() const; + bool isYearAcceptable() const; + bool isTzAcceptable() const; + +private Q_SLOTS: + void setFixedDateTime(); + void setOffsetDateTime(); + + void setSliderValue(int val); + void valueDayChanged(const QString &str); + void fixupHHMMSS(); + void valueMonthChanged(const QString &str); + void valueMonthChanged2(const QString &str); + void valueTimezoneChangedStr(const QString &str); + void valueTimezoneChangedInt(int newTimezone); + void valueYearChanged(const QString &str); + + void onBackHourClicked(); + void onBackMinuteClicked(); + void onBackSecondClicked(); + void onForwardSecondClicked(); + void onForwardMinuteClicked(); + void onForwardHourClicked(); + + void onCheckBoxChanged(); + void disableControls(); + void enableControls(); + void enaDisaControls(bool bEnable); + + void onFocusOutHHMMSS(); + void onFocusOutTZ(); + void onFocusOutDay(); + void onFocusOutMonth(); + void onFocusOutYear(); + + void onFocusOutBackspace(); + void onFocusOut(); + + void onHmsWheelEvent(bool bForward, int cursorPos); + void onDayWheelEvent(bool bForward, int cursorPos); + void onMonthWheelEvent(bool bForward, int cursorPos); + void onYearWheelEvent(bool bForward, int cursorPos); + +private: + Ui::LtsDateTimeEditLe *m_uiLe{nullptr}; + Ui::LtsDateTimeEdit *m_uiTouch{nullptr}; + QShortcut *m_scFixed = nullptr; + QShortcut *m_scOffset = nullptr; + + CurrentTimeCallback m_getCurrentTimeCallback = nullptr; + + qint64 m_minTimeMSec = 0; + qint64 m_maxTimeMSec = 0; + + qint64 m_curTimeMSec = -1; + + bool m_editTimezone = true; + bool m_autoForward = true; + bool m_withButtons = false; + bool m_withSlider = false; + bool m_readOnly = false; + bool m_withCheckBox = false; + bool m_withNowButton = false; + + int m_minMonth{0}; + int m_maxMonth{0}; + + + bool m_backspacePressed = false; + bool m_recursionAvoid = false; + bool m_dateIsAcceptable = false; +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_date_time_edit_touch.cpp b/Lts/Common/LtsWidgets/lts_date_time_edit_touch.cpp new file mode 100755 index 000000000..0f143f714 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_date_time_edit_touch.cpp @@ -0,0 +1,389 @@ + + + +#include "lts_date_time_edit_touch.h" +#include "ui_lts_date_time_edit_touch.h" + +#include +#include +#include +#include + +namespace LtsWidgets +{ + +LtsDateTimeEdit::LtsDateTimeEdit(QWidget *parent, bool withNowButton, bool withCheckBox) + : QWidget(parent) + , m_withCheckBox(withCheckBox) + , m_withNowButton(withNowButton) + , m_tzOffset(0) + , m_minMonth(1) + , m_maxMonth(12) + , m_scFixed(new QShortcut(QKeySequence(Qt::ShiftModifier + Qt::Key_F2), this, SLOT(onSetFixedDateTime()), nullptr, Qt::WidgetWithChildrenShortcut)) + , m_scOffset(new QShortcut(QKeySequence(Qt::Key_F2), this, SLOT(onSetOffsetDateTime()), nullptr, Qt::WidgetWithChildrenShortcut)) + , m_getCurrentTimeCallback(nullptr) + , m_ui(new Ui::LtsDateTimeEdit) +{ + m_ui->setupUi(this); + setFocusPolicy(Qt::StrongFocus); + + + for (int i = 65; i < 91; i++ ) + { + if (QChar(i).toUpper() == QChar::fromLatin1('I')) + continue; + m_ui->cbxTimeZone->addItem(QChar(i), QChar(i)); + + } + + connect(m_ui->pbNow, &QPushButton::clicked, + this, &LtsDateTimeEdit::onSetOffsetDateTime); + connect(m_ui->cbxMonth, static_cast(&QComboBox::currentIndexChanged), + this, &LtsDateTimeEdit::onDateTimeChanged); + connect(m_ui->spDay, &QSpinBox::editingFinished, + this, &LtsDateTimeEdit::onDateTimeChanged); + connect(m_ui->spHour, &QSpinBox::editingFinished, + this, &LtsDateTimeEdit::onDateTimeChanged); + connect(m_ui->spMinutes, &QSpinBox::editingFinished, + this, &LtsDateTimeEdit::onDateTimeChanged); + connect(m_ui->spSeconds, &QSpinBox::editingFinished, + this, &LtsDateTimeEdit::onDateTimeChanged); + connect(m_ui->spYear, &QSpinBox::editingFinished, + this, &LtsDateTimeEdit::onDateTimeChanged); + + connect(m_ui->checkBox, &QCheckBox::toggled, + this, &LtsDateTimeEdit::onCheckBoxChanged); + connect(m_ui->cbxTimeZone, static_cast(&QComboBox::currentIndexChanged), + this, &LtsDateTimeEdit::onTimeZoneChanged); + + connect(m_ui->cbxMonth, static_cast(&QComboBox::currentIndexChanged), + this, &LtsDateTimeEdit::onMonthChanged); + + m_tzOffset = DTG::timezone2Secs(QLatin1Char(DTG::timezone()).toLatin1()); + + + m_ui->cbxTimeZone->setCurrentIndex(m_ui->cbxTimeZone->findText(QString(QLatin1Char(DTG::timezone())))); + onTimeZoneChanged(); + setMinMaxMonth(1,12); + + + int offset = LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("TimeSettings"), QStringLiteral("Offset"), 0).toLongLong()/1000; + m_ui->pbNow->setToolTip(tr("Set to current time plus %n second(s) offset", "", offset)); + + updateDateTimeType(); + + m_ui->checkBox->setVisible(m_withCheckBox); + m_ui->pbNow->setVisible(m_withNowButton); +} + +LtsDateTimeEdit::~LtsDateTimeEdit() +{ + delete m_ui; +} + +void LtsDateTimeEdit::updateDateTimeType() +{ + switch (DTG::dateTimeType()) + { + case DTG::DateTimeType::DTG: + break; + case DTG::DateTimeType::DDMMYYYY_HHMMSS_Z: + break; + case DTG::DateTimeType::DDMMYYYY_Z_HHMMSS: + break; + default: + break; + } +} + +bool LtsDateTimeEdit::setTimezone(char tz) +{ + QChar tzLocal = QChar(QLatin1Char(tz)).toUpper(); + if (tzLocal.toLatin1() == 'J') + tzLocal = QLatin1Char('Z'); + m_ui->cbxTimeZone->setCurrentIndex(m_ui->cbxTimeZone->findData(tzLocal)); + int offset = DTG::timezone2Secs(tzLocal.toLatin1()); + if (offset != m_tzOffset) + { + const auto ms = msecsSinceEpoch(); + m_tzOffset = offset; + setMsecsSinceEpoch(ms+m_tzOffset); + } + return true; +} + +char LtsDateTimeEdit::getTimezone() const +{ + QChar tz = m_ui->cbxTimeZone->currentText().at(0).toUpper(); + + + if (tz.toLatin1() == 'J') + tz = QLatin1Char('Z'); + + if (tz.isNull()) + return DTG::timezone(); + return tz.toLatin1(); +} + + +void LtsDateTimeEdit::setMinMaxMonth(int minMonth, int maxMonth ) +{ + if (m_minMonth != minMonth) + m_minMonth = minMonth; + if (m_maxMonth != maxMonth) + m_maxMonth = maxMonth; + + int currentMonth = m_ui->cbxMonth->currentData().toInt(); + m_ui->cbxMonth->blockSignals(true); + m_ui->cbxMonth->clear(); + for (int i =1; i<= 12 ; i++) + { + if (i < m_minMonth) + continue; + if (i > m_maxMonth) + break; + QDate date(1777,i,1); + m_ui->cbxMonth->addItem(date.toString(QStringLiteral("MMM")),i); + + } + m_ui->cbxMonth->setCurrentIndex(m_ui->cbxMonth->findData(currentMonth)); + m_ui->cbxMonth->blockSignals(false); +} + + +void LtsDateTimeEdit::setDateTime(const QDateTime &datetime) +{ + if (!datetime.isValid() && m_withCheckBox) + { + m_ui->checkBox->setChecked(false); + return; + } + + QDateTime dt = datetime.toUTC().addSecs(m_tzOffset); + dt.setOffsetFromUtc(m_tzOffset); + + + QDateTime oldDateTime = dateTime(); + if (dt == oldDateTime) + return; + + blockSignals(true); + + m_ui->spDay->setValue(dt.date().day()); + m_ui->cbxMonth->setCurrentIndex(m_ui->cbxMonth->findData(dt.date().month())); + + + + + m_ui->spYear->setValue(dt.date().year()-2000); + + m_ui->spHour->setValue(dt.time().hour()); + m_ui->spMinutes->setValue(dt.time().minute()); + m_ui->spSeconds->setValue(dt.time().second()); + + blockSignals(false); + + const QDateTime dtNew = dateTime(); + if (dtNew != oldDateTime) + emit dateTimeChanged(); +} + + +void LtsDateTimeEdit::onDateTimeChanged() +{ + emit dateTimeChanged(); + + QDateTime dateTime; + dateTime = dateTime.addYears(m_ui->spYear->value()); + dateTime = dateTime.addMonths(m_ui->cbxMonth->currentData().toInt()); + dateTime = dateTime.addDays(m_ui->spDay->value()); + dateTime = dateTime.addSecs(m_ui->spHour->value()*60*60 + m_ui->spMinutes->value()*60 + m_ui->spSeconds->value()); + if (dateTime.isValid()) + { + m_dateTime = dateTime; + + } +} + +void LtsDateTimeEdit::onTimeZoneChanged() +{ + if (m_ui->cbxMonth->currentText().isEmpty()) + { + return; + } + QChar tz = m_ui->cbxTimeZone->currentText().at(0); + if (tz.isLower()) + tz = tz.toUpper(); + if (tz.toLatin1() == 'J') + tz = QLatin1Char('Z'); + m_ui->cbxTimeZone->setCurrentIndex(m_ui->cbxTimeZone->findData(tz)); + int offset = DTG::timezone2Secs(tz.toLatin1()); + if (offset != m_tzOffset) + { + const auto ms = msecsSinceEpoch(); + m_tzOffset = offset; + setMsecsSinceEpoch(ms+m_tzOffset); + } +} + +void LtsDateTimeEdit::onMonthChanged(int month) +{ + + Q_UNUSED(month); +} + +void LtsDateTimeEdit::setWithCheckBox(bool checkBoxEnabled) +{ + if (m_withCheckBox == checkBoxEnabled) + return; + + m_withCheckBox = checkBoxEnabled; + m_ui->checkBox->setVisible(m_withCheckBox); + if (!m_withCheckBox) + m_ui->checkBox->setChecked(true); + emit withCheckBoxChanged(m_withCheckBox); +} + +bool LtsDateTimeEdit::isCheckBoxChecked() const +{ + return m_ui->checkBox->isChecked(); +} + +void LtsDateTimeEdit::setWithNowButton(bool nowButton) +{ + if (m_withNowButton == nowButton) + return; + + m_withNowButton = nowButton; + m_ui->pbNow->setVisible(m_withNowButton); + emit withNowButtonChanged(m_withNowButton); +} + +void LtsDateTimeEdit::onCheckBoxChanged(bool checked) +{ + m_ui->spDay->setEnabled(checked); + m_ui->spHour->setEnabled(checked); + m_ui->spMinutes->setEnabled(checked); + m_ui->spSeconds->setEnabled(checked); + m_ui->spYear->setEnabled(checked); + m_ui->cbxMonth->setEnabled(checked); + m_ui->cbxTimeZone->setEnabled(checked); + m_ui->pbNow->setEnabled(checked); + emit dateTimeChanged(); +} + +void LtsDateTimeEdit::setMsecsSinceEpoch(qint64 mSecsSinceEpoch) +{ + if (mSecsSinceEpoch == 0 && m_withCheckBox) + { + m_ui->checkBox->setChecked(false); + return; + } + m_ui->checkBox->setChecked(true); + setDateTime(QDateTime::fromMSecsSinceEpoch(mSecsSinceEpoch).toUTC()); +} + +qint64 LtsDateTimeEdit::msecsSinceEpoch() const +{ + const QDateTime dt(dateTime()); + if (dt.isNull()) + return 0; + return dt.toMSecsSinceEpoch(); +} + +void LtsDateTimeEdit::clear() +{ + m_ui->spDay->clear(); + m_ui->spHour->clear(); + m_ui->spMinutes->clear(); + m_ui->spSeconds->clear(); + m_ui->spYear->clear(); + emit dateTimeChanged(); +} + +void LtsDateTimeEdit::setCheckBoxChecked(bool bChecked) +{ + m_ui->checkBox->setChecked(bChecked); +} + +QDateTime LtsDateTimeEdit::dateTime() const +{ + if (!m_ui->checkBox->isChecked()) + return QDateTime(); + + QDateTime final; + int year = 0; + if (m_ui->spYear->value() < 1000) + { + year=m_ui->spYear->value()+2000; + } + else + { + year=m_ui->spYear->value(); + } + QDate date(year,m_ui->cbxMonth->currentData().toInt(), m_ui->spDay->value()); + final.setDate(date); + final = final.addSecs(m_ui->spHour->value()*60*60 + m_ui->spMinutes->value()*60 + m_ui->spSeconds->value()); + + if (!final.isValid()) + return QDateTime(); + + + return QDateTime(final.date(), final.time(), Qt::OffsetFromUTC, m_tzOffset).toUTC(); +} + +void LtsDateTimeEdit::setMaximumTimeMSec(qint64 msec) +{ + const auto maximumDate(QDateTime::fromMSecsSinceEpoch(msec).date()); + m_ui->spYear->setMaximum(maximumDate.year()-2000); + + + + +} + +qint64 LtsDateTimeEdit::minimumTimeMSec() const +{ + QDate date; + date.setDate(m_ui->spYear->minimum(), m_maxMonth, m_ui->spMinutes->minimum() ); + return QDateTime(date).toMSecsSinceEpoch(); +} + +void LtsDateTimeEdit::setMinimumTimeMSec(qint64 msec) +{ + const auto minimumDate(QDateTime::fromMSecsSinceEpoch(msec).date()); + + m_ui->spYear->setMinimum(minimumDate.year()-2000); + +} + +void LtsDateTimeEdit::setRetrieveCurrentTimeCallback(const CurrentTimeCallback &cb) +{ + m_getCurrentTimeCallback = cb; +} + +void LtsDateTimeEdit::onSetFixedDateTime() +{ + setMsecsSinceEpoch(LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("TimeSettings"), QStringLiteral("FixedTime"), 0).toLongLong()); +} + +void LtsDateTimeEdit::onSetOffsetDateTime() +{ + if (!m_getCurrentTimeCallback) + return; + qint64 offset = LtsUtils::OperatorSettings::getInstance()->value(QStringLiteral("TimeSettings"), QStringLiteral("Offset"), 0).toLongLong(); + + setMsecsSinceEpoch((m_getCurrentTimeCallback() / 1000) * 1000 + offset); +} + + +void LtsDateTimeEdit::setSubWidgetsProperties(QString name, QVariant value) +{ + m_ui->spDay->setProperty(name.toStdString().c_str(), value); + m_ui->spHour->setProperty(name.toStdString().c_str(), value); + m_ui->spMinutes->setProperty(name.toStdString().c_str(), value); + m_ui->spSeconds->setProperty(name.toStdString().c_str(), value); + m_ui->spYear->setProperty(name.toStdString().c_str(), value); + +} +} diff --git a/Lts/Common/LtsWidgets/lts_date_time_edit_touch.h b/Lts/Common/LtsWidgets/lts_date_time_edit_touch.h new file mode 100755 index 000000000..9ec977edb --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_date_time_edit_touch.h @@ -0,0 +1,140 @@ + + + +#pragma once + +#include "lts_widgets_export.h" + +#include +#include +#include + +class QDateTime; + +namespace Ui +{ +class LtsDateTimeEdit; +} + +class QShortcut; + +namespace LtsWidgets +{ +class LTSWIDGETS_EXPORT LtsDateTimeEdit : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(bool autoForward READ getAutoForward WRITE setAutoForward) + Q_PROPERTY(bool editTimezone READ getEditTimezone WRITE setEditTimezone) + Q_PROPERTY(bool withButtons READ getWithButtons WRITE setWithButtons) + Q_PROPERTY(bool withSlider READ getWithSlider WRITE setWithSlider) + Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly) + Q_PROPERTY(bool withNowButton READ withNowButton WRITE setWithNowButton) + Q_PROPERTY(bool withCheckBox READ withCheckBox WRITE setWithCheckBox) + +public: + using CurrentTimeCallback = std::function; + explicit LtsDateTimeEdit(QWidget *parent = nullptr, bool withNowButton = true, bool withCheckBox = false); + ~LtsDateTimeEdit(); + + qint64 msecsSinceEpoch() const; + bool isEmpty() const { return msecsSinceEpoch() == 0; } + + void setAutoForward(const bool af) { m_autoForward = af; } + bool getAutoForward() const { return m_autoForward; } + + void setEditTimezone(const bool edit) { m_editTimezone = edit; } + bool getEditTimezone() const { return m_editTimezone; } + + void setWithButtons(const bool buttons) { m_withButtons = buttons; } + bool getWithButtons() const { return m_withButtons; } + + void setWithSlider(const bool slider) { m_withSlider = slider; } + bool getWithSlider() const { return m_withSlider; } + + void setReadOnly(const bool readOnly) { m_readOnly = readOnly; } + bool isReadOnly() const { return m_readOnly; } + + bool withCheckBox() const { return m_withCheckBox; } + void setCheckBoxChecked(bool bChecked); + bool isCheckBoxChecked() const; + + bool withNowButton() const { return m_withNowButton; } + void setDateTime(const QDateTime &datetime); + + + void setMinimumTimeMSec(qint64 msec); + qint64 minimumTimeMSec() const; + + + void setMaximumTimeMSec(qint64 msec); + qint64 maximumTimeMSec() const { return m_maxTimeMSec; }; + + void setMinMaxTimeMSec(qint64 min, qint64 max) { setMinimumTimeMSec(min); setMaximumTimeMSec(max); } + + void setRetrieveCurrentTimeCallback(const CurrentTimeCallback &cb); + void updateDateTimeType(); + + bool setTimezone(char tz); + char getTimezone() const; + + + void setSubWidgetsProperties(QString name, QVariant value); + + bool isValidDate() const + { + + return m_dateTime.isValid(); + } + +private: + QDateTime dateTime() const; + void setMinMaxMonth(int minMonth, int maxMonth); + +public slots: + void setWithCheckBox(bool checkBoxEnabled); + void setWithNowButton(bool nowButton); + void setMsecsSinceEpoch(qint64 mSecsSinceEpoch); + void clear(); + + void onSetFixedDateTime(); + void onSetOffsetDateTime(); + +public slots: + void onDateTimeChanged(); + +signals: + void dateTimeChanged(); + void withCheckBoxChanged(bool checkBoxEnabled); + void withNowButtonChanged(bool nowButton); + +private slots: + void onCheckBoxChanged(bool checked); + void onTimeZoneChanged(); + void onMonthChanged(int month); + +private: + + qint64 m_minTimeMSec = 0; + qint64 m_maxTimeMSec = 0; + qint64 m_curTimeMSec = -1; + + bool m_editTimezone = true; + bool m_autoForward = true; + bool m_withButtons = false; + bool m_withSlider = false; + bool m_readOnly = false; + bool m_withCheckBox = false; + bool m_withNowButton = false; + + int m_tzOffset; + int m_minMonth; + int m_maxMonth; + QShortcut *m_scFixed; + QShortcut *m_scOffset; + QDateTime m_dateTime; + CurrentTimeCallback m_getCurrentTimeCallback; + Ui::LtsDateTimeEdit *m_ui; +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_date_time_edit_touch.ui b/Lts/Common/LtsWidgets/lts_date_time_edit_touch.ui new file mode 100755 index 000000000..fe28a140c --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_date_time_edit_touch.ui @@ -0,0 +1,181 @@ + + + LtsDateTimeEdit + + + + 0 + 0 + 558 + 31 + + + + + 0 + 0 + + + + + 558 + 31 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + true + + + true + + + true + + + + + + + 1 + + + 31 + + + + + + + + 5 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + + + + 23 + + + + + + + 59 + + + + + + + 59 + + + + + + + + 5 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + + + + + 50 + 16777215 + + + + + + + + + 5 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + + + + + 100 + 16777215 + + + + + + + + 0 + + + 99 + + + + + + + Now + + + + + + + + + + diff --git a/Lts/Common/LtsWidgets/lts_dialog.cpp b/Lts/Common/LtsWidgets/lts_dialog.cpp new file mode 100755 index 000000000..a03c1085e --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_dialog.cpp @@ -0,0 +1,810 @@ + + + +#include "lts_dialog.h" +#include "lts_progress_dialog_helper.h" +#include "lts_message_dialog.h" +#include "lts_dialog_helper.h" +#include "lts_dialog_button_box.h" +#include "lts_base_layout_widget.h" +#include "lts_date_time_edit.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + + +namespace LtsWidgets +{ +int LtsDialog::s_object_count = 0; + + +class OverlayContainer : public QWidget +{ + Q_OBJECT +public: + OverlayContainer(QWidget *parent); + +protected: + void paintEvent(QPaintEvent *e) override; +}; + + +OverlayContainer::OverlayContainer(QWidget *parent) + : QWidget(parent) +{ + setAttribute(Qt::WA_TranslucentBackground); +} + + +void OverlayContainer::paintEvent(QPaintEvent *e) +{ + QPainter p(this); + p.fillRect(rect(), { 80, 80, 80, 200 }); + QWidget::paintEvent(e); +} + + + + +LtsDialog::LtsDialog(QWidget *parent, ButtonCombination buttonCombination, bool closeable) + : QWidget() + , m_changesMade(false) + , m_firstShow(true) + , m_baseLayoutWidget(nullptr) + , m_isClosable(closeable) + , m_noLockTopic(false) + , m_unchangeableDialog(false) + , m_dialogHelper(new LtsDialogHelper(nullptr)) + + + + , m_buttonBox(nullptr) + , m_uiRequirements(new UiRequirementList()) + , m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("LtsWidget.LtsDialog")) +{ + Q_UNUSED(parent); + + s_object_count++; + + m_dialogHelper->setDialog(this); + m_buttonBox = m_dialogHelper->getButtonBox(); + + + + + + + + + + + + + + + + addDebugMenuButton(); + + + + + + connect(m_buttonBox->getButton(DialogButton::Close), &QPushButton::clicked, this, &LtsDialog::slotClose); + + switch(buttonCombination) + { + case ButtonCombination::OkCancel: + { + m_buttonBox->getButton(DialogButton::Close)->setText(tr("Cancel")); + connect(m_buttonBox->getButton(DialogButton::Ok), &QPushButton::clicked, this, &LtsDialog::slotOkay); + break; + } + + case ButtonCombination::OkApplyCancel: + { + m_buttonBox->getButton(DialogButton::Close)->setText(tr("Cancel")); + connect(m_buttonBox->getButton(DialogButton::Ok), &QPushButton::clicked, this, &LtsDialog::slotOkay); + connect(m_buttonBox->getButton(DialogButton::Apply), &QPushButton::clicked, this, &LtsDialog::slotApply); + m_buttonBox->getButton(DialogButton::Apply)->setVisible(true); + break; + } + + case ButtonCombination::CloseOnly: + { + m_buttonBox->getButton(DialogButton::Ok)->setVisible(false); + setUnchangeable(); + break; + } + + default: + case ButtonCombination::SeamlessDialog: + { + m_buttonBox->setVisible(false); + setContentsMargins(0,0,0,0); + m_dialogHelper->setContentsMargins(0,0,0,0); + + QLayout *l = m_dialogHelper->layout(); + + if (l) + { + l->setContentsMargins(0,0,0,0); + l->setMargin(0); + } + else + { + qWarning() << "LtsDialog::c_tor: Dialog Helper has no layout to set margins"; + } + + break; + } + } + + if (!closeable) + { + m_buttonBox->getButton(LtsWidgets::DialogButton::Ok)->setVisible(false); + m_buttonBox->getButton(LtsWidgets::DialogButton::Close)->setVisible(false); + } +} + + +LtsDialog::~LtsDialog() +{ + delete m_uiRequirements; + + if (m_overlayWidget) + m_overlayWidget->setParent(nullptr); + + + + + s_object_count--; +} + + +void LtsDialog::setOverlay(QWidget *widget) +{ + if (m_overlayContainer) + { + if (m_overlayWidget) + { + m_overlayWidget->hide(); + m_overlayWidget->setParent(nullptr); + m_overlayWidget->hide(); + m_overlayWidget = nullptr; + + } + + m_overlayContainer->hide(); + m_overlayContainer->setParent(nullptr); + m_overlayContainer->hide(); + m_overlayContainer->deleteLater(); + m_overlayContainer = nullptr; + } + + + OverlayContainer *overlayContainer = new OverlayContainer(m_dialogHelper); + + connect(m_dialogHelper.data(), &LtsDialogHelper::signalSizeChanged, + overlayContainer, [overlayContainer](const QSize &size) + { + overlayContainer->setGeometry(QRect(QPoint(0, 0), size)); + }); + + overlayContainer->setGeometry(QRect(QPoint(0, 0), m_dialogHelper->size())); + m_overlayContainer = overlayContainer; + + + QHBoxLayout *l = new QHBoxLayout; + m_overlayContainer->setLayout(l); + + widget->setAutoFillBackground(true); + l->addWidget(widget, 0, Qt::AlignCenter); + widget->show(); + + connect(widget, &QWidget::destroyed, + m_overlayContainer.data(), &QObject::deleteLater); + + m_overlayWidget = widget; + + m_overlayContainer->show(); + m_overlayContainer->raise(); +} + + +void LtsDialog::unsetOverlay() +{ + if (m_overlayWidget) + m_overlayWidget->setParent(nullptr); + + if (!m_overlayContainer) + return; + + m_overlayContainer->setParent(nullptr); + m_overlayContainer->deleteLater(); +} + + +LtsDialogHelper *LtsDialog::getHelper() +{ + return m_dialogHelper; +} + + +LtsBaseLayoutWidget* LtsDialog::baseLayoutWidget() +{ + return m_baseLayoutWidget; +} + + +void LtsDialog::setOnlineHelpFilename(const QString &fileName) +{ + m_onlineHelpFileName = fileName; + QPushButton *btn = m_buttonBox->getButton(DialogButton::Help); + + if (btn == nullptr) + return; + + if (LtsSessionData::LtsSessionData::getInstance()->isTablet() && strcmp(PROJECTBUILD, "guez") == 0) + return; + + connect(btn, &QPushButton::clicked, + this, [this]() + { + emit signalShowOnlineHelp(m_onlineHelpFileName); + }); + + btn->setVisible(true); +} + + +void LtsDialog::addDebugMenuButton() +{ +#ifdef LTS_DEBUG + QMenu *menu = new QMenu(this); + + menu->addAction(QStringLiteral("simulate changes"), this, &LtsDialog::slotChangesMade); + menu->addAction(QStringLiteral("unchange"), this, &LtsDialog::__unchange__); + menu->addAction(QStringLiteral("retranslate"), this, &LtsDialog::retranslate); + menu->addAction(QStringLiteral("force unlock"), this, [this]() { setUnchangeable(false); }); + menu->addAction(QStringLiteral("set unchangeable"), this, [this]() { setUnchangeable(); }); + menu->addAction(QStringLiteral("try lock"), this, &LtsDialog::tryLockTopic); + menu->addAction(QStringLiteral("set closable"), this, [this]() { m_isClosable = true; }); + menu->addAction(QStringLiteral("set unclosable"), this, [this]() { m_isClosable = false; }); + menu->addAction(QStringLiteral("clear requirements"), this, [this] { m_uiRequirements->clear(); }); + menu->addAction(QStringLiteral("enable all button box buttons"), this, [this]() + { + const auto children = m_buttonBox->findChildren(); + for (QPushButton *pb : children) + pb->setEnabled(true); + }); + menu->addAction(QStringLiteral("show button box button .."), this, [this]() + { + QString buttonObjectName = QInputDialog::getText(this, QString(), QStringLiteral("Enter button object name")); + if (buttonObjectName.isEmpty()) + return; + + const QList buttons = m_buttonBox->findChildren(buttonObjectName); + + int btnCount = buttons.count(); + if (btnCount == 0) + { + LtsMessageDialog::showMessage(this, QStringLiteral("button not found")); + return; + } + + if (btnCount > 1) + { + QStringList buttonStringList; + buttonStringList.reserve(buttons.size()); + for (QPushButton *pb : buttons) + buttonStringList += pb->objectName(); + + LtsMessageDialog::showMessage(this, QStringLiteral("button ambiguous: %1").arg(buttonStringList.join(QStringLiteral(", ")))); + return; + } + + buttons.at(0)->setVisible(true); + }); + + QPushButton *debugBtn = m_buttonBox->getButton(DialogButton::_debug_); + debugBtn->setMenu(menu); + debugBtn->setVisible(true); +#endif +} + + +void LtsDialog::updateWindowTitle() +{ + QString completeTitle = m_title; + + if (m_changesMade) + completeTitle.insert(0, QStringLiteral("\u2022")); + + if (!m_topicLockedBy.isEmpty()) + completeTitle.push_back(QString(QStringLiteral(" - READ-ONLY (%1)")).arg(m_topicLockedBy)); + + QWidget::setWindowTitle(completeTitle); + if (m_dialogHelper != nullptr) + m_dialogHelper->setWindowTitle(completeTitle); +} + + +void LtsDialog::setWindowTitle(const QString &title) +{ + m_title = title; + updateWindowTitle(); +} + + +void LtsDialog::setBaseLayoutWidget(LtsBaseLayoutWidget *baseLayoutWidget) +{ + m_baseLayoutWidget = baseLayoutWidget; +} + +void LtsDialog::setIdentifier(const QString &identifier) +{ + m_identifier = identifier; + tableColumnOrganiserSetting(); +} + + +void LtsDialog::addConnections(QWidget *widget) +{ + if (addChangesMadeSignal(widget, &QLineEdit::textChanged)) return; + if (addChangesMadeSignal(widget, &QDateTimeEdit::dateTimeChanged)) return; + if (addChangesMadeSignal(widget, &QComboBox::currentTextChanged)) return; + if (addChangesMadeSignal(widget, &QCheckBox::stateChanged)) return; + if (addChangesMadeSignal(widget, static_cast(&QSpinBox::valueChanged))) return; + if (addChangesMadeSignal(widget, static_cast(&QDoubleSpinBox::valueChanged))) return; + if (addChangesMadeSignal(widget, &QSlider::valueChanged)) return; + if (addChangesMadeSignal(widget, &QPlainTextEdit::textChanged)) return; + if (addChangesMadeSignal(widget, &QTextEdit::textChanged)) return; + if (addChangesMadeSignal(widget, &QAbstractButton::toggled)) return; + if (addChangesMadeSignal(widget, &LtsDateTimeEdit::dateTimeChanged)) return; +} + + +void LtsDialog::setUnchangeable(bool unchangeable) +{ + m_unchangeableDialog = unchangeable; +} + + +bool LtsDialog::addDialogFixed(LtsDialog *dialog, const char *menuGroupUntranslated, const QString &identifier, const LayoutArea &layoutArea, bool setClosable, bool focusNewTab) +{ + return addDialog(dialog, tr(menuGroupUntranslated) , identifier, layoutArea, setClosable, focusNewTab); +} + + +bool LtsDialog::addDialog(LtsDialog *dialog, const QString &menuGroup, const QString &identifier, const LayoutArea &layoutArea, bool setClosable, bool focusNewTab) +{ + if (!m_baseLayoutWidget) + { + qCritical() << "LtsDialog: addDialog: no base layout set - cannot add Dialog !"; + return false; + } + + return m_baseLayoutWidget->addWidget(dialog, menuGroup, identifier, layoutArea, setClosable, focusNewTab); +} + + +bool LtsDialog::amIFocused() const +{ + if (!m_baseLayoutWidget) + { + qCritical() << "LtsDialog: addDialog: no base layout set - cannot check for focus!"; + return false; + } + + return m_baseLayoutWidget->isDialogFocused(this); +} + + +bool LtsDialog::focusDialog(const QString &menuGroup, const QString &identifier, const LayoutArea &layoutArea) +{ + if (!m_baseLayoutWidget) + { + qCritical() << "LtsDialog: addDialog: no base layout set - cannot focus Dialog !"; + return false; + } + + return m_baseLayoutWidget->focusTab(menuGroup, identifier, layoutArea); +} + + +void LtsDialog::showEvent(QShowEvent *event) +{ + emit visibilityChanged(true); + + bool tmp = m_unchangeableDialog; + + if (!m_firstShow) + { + QWidget::showEvent(event); + return; + } + + m_unchangeableDialog = true; + m_firstShow = false; + + + + + + QList listenWidgets = findChildren(); + + for (auto i = listenWidgets.begin(); i != listenWidgets.end(); ++i) + { + + + + QWidget *widget = *i; + + QVariant isConst = widget->property("const"); + if (isConst.isValid() && isConst.toBool()) + continue; + + if (!tmp) + addConnections(widget); + +#ifdef TABLET_MODE + LtsWidgets::addTouchScroll(widget); +#endif + } + + QWidget::showEvent(event); + m_unchangeableDialog = tmp; +} + + +void LtsDialog::tryLockTopic() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + if (!m_topicInfo.isValid()) + return; + + if (m_topicInfo.isLocked()) + { + WARNING_LOG(m_logger) << LOGFUNCTION << " tried to lock with an already locked topic info, return"; + return; + } + + if (m_noLockTopic) + return; + + + + + + + + + bool bIsConnected = isSignalConnected(QMetaMethod::fromSignal(&LtsDialog::signalLockTopic)); + + DEBUG_LOG(m_logger) << LOGFUNCTION + << " signalLockTopic(): " + << ", TopicName =" << m_topicInfo.topicName() + << ", entity =" << m_topicInfo.entityId() + << ", signalLockTopic isConnected=" << bIsConnected; + + if (bIsConnected) + { + emit signalLockTopic(m_topicInfo); + + m_topicInfo.setLocked(); + } + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void LtsDialog::initTopicLockHandler(const QString &topicName, int entityId) +{ + + + m_topicInfo.setTopicName(topicName); + m_topicInfo.setEntity(entityId); + m_topicInfo.setValid(); +} + + +void LtsDialog::tryLockTopicNew() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START"; + tryLockTopic(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + +void LtsDialog::tryLockTopicNew(const QString &topicName, int entityId) +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" + << ", topicName='" << topicName << "'" + << ", entityId=" << entityId; + + + + if (m_topicInfo.entityId() != entityId) + { + DEBUG_LOG(m_logger) << LOGFUNCTION << " topicName='" << topicName << "'" + << " change from " << m_topicInfo.entityId() << " to " << entityId; + m_topicInfo.setLocked(false); + } + + + m_topicInfo.setTopicName(topicName); + m_topicInfo.setEntity(entityId); + m_topicInfo.setValid(); + + tryLockTopic(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END" + << ", topicName='" << topicName << "'" + << ", entityId=" << entityId; +} + +void LtsDialog::triggerTopicLock() +{ + TRACE_LOG(m_logger) << LOGFUNCTION << " START" << " !!trigger!!"; + tryLockTopic(); + TRACE_LOG(m_logger) << LOGFUNCTION << " END"; +} + + +void LtsDialog::translate(const QString &title) +{ + bool isAlreadyUnchangeable = unchangeable(); + + if (!isAlreadyUnchangeable) + setUnchangeable(true); + + setWindowTitle(title); + + + + + + if (!isAlreadyUnchangeable) + setUnchangeable(false); + + { + bool isNowAlreadyUnchangeable = unchangeable(); + + QPushButton *b = m_buttonBox->getButton(DialogButton::ReadOnly); + if (b) + { + connect(b, &QPushButton::clicked, this, &LtsDialog::onReadOnlyButtonClicked, Qt::UniqueConnection); + + + b->setVisible(isNowAlreadyUnchangeable && !m_topicLockedBy.isEmpty()); + if (b->isVisible()) + b->setToolTip(tr("Locked by: %1").arg(m_topicLockedBy)); + else + b->setToolTip({}); + } + } +} + + + +void LtsDialog::slotLock(const QString &lockedBy) +{ + setUnchangeable(true); + + m_topicLockedBy = lockedBy; + + lock(); + retranslate(); +} + + +void LtsDialog::slotUnlock() +{ + setUnchangeable(false); + + m_topicLockedBy.clear(); + + unlock(); + retranslate(); +} + + +void LtsDialog::lock() +{ + setWidgetsReadonly(true); + setWidgetsReadonly(true); + + setWidgetsReadonly(true); + setWidgetsReadonly(true); + setWidgetsReadonly(true); + setWidgetsReadonly(true); + + setWidgetsDisabled(true); + setWidgetsDisabled(true); + setWidgetsDisabled(true); + setWidgetsDisabled(true); + setWidgetsDisabled(true); + setWidgetsDisabled(true); + setWidgetsDisabled(true); + setWidgetsDisabled(true); + setWidgetsDisabled(true); +} + + +void LtsDialog::unlock() +{ + restoreReadonly(); + restoreReadonly(); + + restoreReadonly(); + restoreReadonly(); + restoreReadonly(); + restoreReadonly(); + + restoreEnabled(); + restoreEnabled(); + restoreEnabled(); + restoreEnabled(); + restoreEnabled(); + restoreEnabled(); + restoreEnabled(); + restoreEnabled(); +} + + +void LtsDialog::slotChangesMade() +{ + if (m_unchangeableDialog) + return; + + m_changesMade = true; + updateWindowTitle(); + + m_buttonBox->getButton(DialogButton::Ok)->setEnabled(true); + m_buttonBox->getButton(DialogButton::Apply)->setEnabled(true); + + emit signalChangesMade(); +} + + +void LtsDialog::resetChanges() +{ + m_changesMade = false; + + updateWindowTitle(); + + + + m_buttonBox->getButton(DialogButton::Ok)->setEnabled(false); + m_buttonBox->getButton(DialogButton::Apply)->setEnabled(false); + emit signalChangesReset(); +} + + +bool LtsDialog::_apply() +{ + if (m_uiRequirements->checkRequirements(this) && apply()) + { + resetChanges(); + return true; + } + + return false; +} + + +Button LtsDialog::askApplyDiscardCancel() +{ + return LtsMessageDialog::askApplyDiscardCancel(this); +} + + +void LtsDialog::slotApply() +{ + _apply(); +} + + +void LtsDialog::slotOkay() +{ + if (_apply()) + _close(); +} + + +void LtsDialog::slotHide() +{ + _hide(); +} + + +void LtsDialog::slotClose() +{ + + if ((m_changesMade) && (!m_unchangeableDialog)) + { + switch (askApplyDiscardCancel()) + { + case Button::Apply: + slotOkay(); + break; + case Button::Discard: + _discard(); + break; + default: + break; + } + } + else + { + _close(); + } +} + +void LtsDialog::onReadOnlyButtonClicked() +{ + if (m_topicLockedBy.isEmpty()) + return; + LtsWidgets::LtsMessageDialog::showMessage(this, tr("Locked by: %1").arg(m_topicLockedBy)); +} + + +void LtsDialog::closeRequest() +{ + slotClose(); +} + + +void LtsDialog::_hide() +{ + if (m_dialogHelper != nullptr) + { + emit signalHide(m_dialogHelper); + + } +} + + +void LtsDialog::_close() +{ + resetChanges(); + aboutToClose(); + if (m_dialogHelper != nullptr) + m_dialogHelper->close(); +} + +void LtsDialog::_discard() +{ + dialogCanceled(); + _close(); +} + +} + + + + +#include "lts_dialog.moc" diff --git a/Lts/Common/LtsWidgets/lts_dialog.h b/Lts/Common/LtsWidgets/lts_dialog.h new file mode 100755 index 000000000..193642869 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_dialog.h @@ -0,0 +1,473 @@ + + + +#pragma once + +#include "lts_base.h" +#include "lts_widgets_export.h" +#include "ui_requirement.h" +#include "lts_dialog_button_box.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +namespace LtsWidgets +{ +class TopicInfo +{ +public: + int entityId() const { return m_entityId; } + const QString &topicName() const { return m_topicName; } + bool isLocked() const { return m_locked; } + bool isValid() const { return m_valid; } + + void setEntity(int entityId) { m_entityId = entityId; } + void setTopicName(const QString &topicName) { m_topicName = topicName; } + void setLocked(bool locked = true) { m_locked = locked; } + void setValid() { m_valid = true; } + +private: + QString m_topicName; + int m_entityId{0}; + bool m_locked{false}; + bool m_valid{false}; +}; + + +class LtsBaseLayoutWidget; +class LtsDialogHelper; +class LtsProgressDialogHelper; +class LtsDialogButtonBox; +class LtsToolBarArea; +class OverlayContainer; + + +enum class ButtonCombination +{ + OkApplyCancel, + + OkCancel, + + + + CloseOnly, + SeamlessDialog, +}; + + + + +class LTSWIDGETS_EXPORT LtsDialog : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(bool unchangeable READ unchangeable WRITE setUnchangeable) +public: + static int s_object_count; + + + + explicit LtsDialog(QWidget *parent = nullptr, ButtonCombination buttonCombination = ButtonCombination::CloseOnly, bool closeable = true); + ~LtsDialog(); + + + + LtsDialogHelper *getHelper(); + + bool hasUnsavedChanges() const { return m_changesMade || m_currentlyBlocking; } + + + + virtual void retranslate() = 0; + + void closeRequest(); + void setUnchangeable(bool unchangeable = true); + bool unchangeable() const { return m_unchangeableDialog; } + + + + bool addDialog(LtsDialog *dialog, const QString &menuGroup, const QString &identifier, const LayoutArea &layoutArea = LayoutArea::eDialogTab, bool setClosable = true, bool focusNewTab = true); + + + Q_DECL_DEPRECATED bool addDialogFixed(LtsDialog *dialog, const char *menuGroupUntranslated, const QString &identifier, const LayoutArea &layoutArea = LayoutArea::eDialogTab, bool setClosable = true, bool focusNewTab = true); + bool focusDialog(const QString &menuGroup, const QString &identifier, const LayoutArea &layoutArea); + bool amIFocused() const; + void setBaseLayoutWidget(LtsBaseLayoutWidget *baseLayoutWidget); + void __unchange__() { resetChanges(); } + void setClosable(bool closable) { m_isClosable = closable; } + bool isClosable() const { return m_isClosable; } + virtual void aboutToClose() {} + void setMenuGroup(const QString &menuGroup) { m_menuGroup = menuGroup; } + const QString &menuGroup() const { return m_menuGroup; } + void setIdentifier(const QString &identifier); + virtual void tableColumnOrganiserSetting(){}; + const QString &identifier() const { return m_identifier; } + + void setOverlay(QWidget *widget); + void unsetOverlay(); + + + + static inline void setMinWidthForWidgetHelper(QWidget *w) + { + int width = w->sizeHint().width(); + w->setMinimumWidth(width); + } + + QString windowTitle() const + { + return m_title; + } + + + + + + + + + + + + + + + void tryLockTopicNew(); + + + + virtual void tryLockTopicNew (const QString &topicName, int entityId = -1); + + + void triggerTopicLock(); + + void setOnlineHelpFilename(const QString &fileName); + void setCurrentlyBlocking(bool currentlyBlocking = true) { m_currentlyBlocking = currentlyBlocking; } + +signals: + + + void startMainSelect(LtsWidgets::LayoutArea); + + + + void endMainSelect(LtsWidgets::LayoutArea); + + + + void signalChangesMade(); + + void signalHide(QObject *object); + + + + void signalChangesReset(); + + void signalFocused(); + void visibilityChanged(bool visible); + void signalLockTopic(const LtsWidgets::TopicInfo &topicInfo); + void signalShowOnlineHelp(const QString &fileName); + +protected: + + + void translate(const QString &title); + + + + template + + + void translate(T *ui, const QString &title) + { + translate(title); + if (ui) + ui->retranslateUi(this); + + m_uiRequirements->retranslate(); + } + + void hideEvent(QHideEvent *event) override + { + QWidget::hideEvent(event); + emit visibilityChanged(false); + } + + template + + + void translate(T *ui, const QString &custom, bool isNew, const QString &title) + { + QString newTitle; + if (!isNew) + { + if (unchangeable()) + { + if (custom.isEmpty()) + newTitle = (tr("Show %1").arg(title)); + else + newTitle = (tr("Show %1: %2").arg(title, custom)); + } + else + { + if (custom.isEmpty()) + newTitle = (tr("Edit %1").arg(title)); + else + newTitle = (tr("Edit %1: %2").arg(title, custom)); + + + tryLockTopicNew(); + } + } + else + { + newTitle = (tr("New %1").arg(title)); + m_buttonBox->getButton(LtsWidgets::DialogButton::Apply)->hide(); + m_buttonBox->getButton(LtsWidgets::DialogButton::Ok)->setText(tr("Create")); + } + + translate(ui, newTitle); + } + + + template + + + void translate(T* ui, const QString &customUniqueTopicName, int topicId, const QString &custom, const QString &title) + { + Q_UNUSED(customUniqueTopicName); + + initTopicLockHandler(customUniqueTopicName, topicId); + + translate(ui, custom , + (topicId < 1 ), + title); + } + + + template + + + void translate(T *ui, const S &data, const QString &custom, const QString &title) + { + translate(ui, QString::fromUtf8(typeid(data).name()), data.id(), custom , title); + } + + + template + + + void translate(T *ui, const S &data, const QString &title) + { + translate(ui, data, QString::fromStdString(data.name()), title); + } + + + + virtual void dialogCanceled() {} + LtsBaseLayoutWidget* baseLayoutWidget(); + void setNoTopicLock() { m_noLockTopic = true; } + +private: + friend class LtsDialogHelper; + + bool m_changesMade; + bool m_currentlyBlocking { false }; + bool m_firstShow; + QPointer m_baseLayoutWidget; + bool m_isClosable; + TopicInfo m_topicInfo; + +public: + QString m_topicLockedBy; + +private: + bool m_noLockTopic; + bool m_unchangeableDialog; + QPointer m_dialogHelper; + + QString m_title; + QString m_onlineHelpFileName; + QString m_menuGroup; + QString m_identifier; + + + QPointer m_overlayContainer; + QPointer m_overlayWidget; + + bool _apply(); + void _close(); + void _discard(); + void _hide(); + void resetChanges(); + void addDebugMenuButton(); + void tryLockTopic(); + void initTopicLockHandler(const QString &topicName, int entityId); + + + + void setWindowTitle(const QString &title); + void updateWindowTitle(); + void addConnections(QWidget *widget); + + template + bool addChangesMadeSignal(QWidget *widget, SignalFunc signal) + { + WidgetType *concreteWidget = qobject_cast(widget); + + if (!concreteWidget) + return false; + + connect(concreteWidget, signal, this, &LtsDialog::slotChangesMade); + return true; + } + + + template + void setWidgetsReadonly(bool readonly) + { + const auto children = findChildren(); + for (WidgetType *widget : children ) + { + widget->setProperty("lastReadOnlyState", widget->isReadOnly()); + widget->setReadOnly(readonly); + } + } + + + template + void setWidgetsDisabled(bool disabled) + { + const auto children = findChildren(); + for (WidgetType *widget : children) + { + widget->setProperty("lastEnabledState", widget->isEnabled()); + widget->setEnabled(!disabled); + } + } + + template + void restoreReadonly() + { + const auto children = findChildren(); + for (WidgetType *widget : children) + { + QVariant lastState = widget->property("lastReadOnlyState"); + if (lastState.isValid() && !lastState.isNull() && lastState.canConvert(QMetaType::Bool)) + widget->setReadOnly(lastState.toBool()); + } + } + + template + void restoreEnabled() + { + const auto children = findChildren(); + for (WidgetType *widget : children) + { + QVariant lastState = widget->property("lastEnabledState"); + if (lastState.isValid() && !lastState.isNull() && lastState.canConvert(QMetaType::Bool)) + widget->setEnabled(lastState.toBool()); + } + } + +public slots: + + + void slotChangesMade(); + + void slotHide(); + void slotLock(const QString &lockedBy); + void slotUnlock(); + +private slots: + void slotApply(); + void slotOkay(); + void slotClose(); + + void onReadOnlyButtonClicked(); + +protected: + virtual void lock(); + virtual void unlock(); + + virtual bool apply() { return true; } + void showEvent(QShowEvent *event) override; + Button askApplyDiscardCancel(); + + LtsDialogButtonBox *m_buttonBox; + UiRequirementList *m_uiRequirements; + + NGL::INGLLogger &m_logger; +}; + + + +template +T *initDialog(T *dialog) +{ + if (!dialog->init()) + { + dialog->deleteLater(); + return nullptr; + } + return dialog; +} + + +template +T *initDialog(T *dialog, F&&... f) +{ + if (!dialog->init(std::forward(f)...)) + { + dialog->deleteLater(); + return nullptr; + } + return dialog; +} + + +template +T *createThrowableDialog(F&&... f) +{ + T *dlg = nullptr; + + try + { + dlg = new T(std::forward(f)...); + } + catch (const char *s) + { + fprintf(stderr, "Dialog could not be created: %s\n", s); + return nullptr; + } + catch (const QString &msg) + { + fprintf(stderr, "Dialog could not be created: %s\n", msg.toStdString().c_str()); + return nullptr; + } + catch (...) + { + fprintf(stderr, "Dialog could not be created: unknown error\n"); + return nullptr; + } + + return dlg; +} + + +} diff --git a/Lts/Common/LtsWidgets/lts_dialog_button_box.cpp b/Lts/Common/LtsWidgets/lts_dialog_button_box.cpp new file mode 100755 index 000000000..1bd215710 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_dialog_button_box.cpp @@ -0,0 +1,217 @@ + + + +#include "lts_dialog_button_box.h" +#include "ui_lts_dialog_button_box.h" + +#include "lts_message_dialog.h" +#include +#include + +namespace LtsWidgets +{ +LtsDialogButtonBox::LtsDialogButtonBox(QWidget *parent) + : QWidget(parent) + , m_isTablet(LtsSessionData::LtsSessionData::getInstance()->isTablet()) + , m_ui(new Ui::LtsDialogButtonBox) +{ + m_ui->setupUi(this); + _setVisibilityForAll(false); + + + QMenu *libraryMenu = new QMenu(m_ui->pbLibrary); + libraryMenu->addAction(QIcon(QLatin1String(":/style/library_load.svg")), + tr("Load"), this, &LtsDialogButtonBox::signalLibraryLoadClicked); + libraryMenu->addAction(QIcon(QLatin1String(":/style/library_save.svg")), + tr("Save"), this, &LtsDialogButtonBox::signalLibrarySaveClicked); + m_ui->pbLibrary->setMenu(libraryMenu); + + + m_ui->pbOk->setVisible(true); + m_ui->pbClose->setVisible(true); + +} + + +LtsDialogButtonBox::~LtsDialogButtonBox() +{ + delete m_ui; +} + + +void LtsDialogButtonBox::_setVisibilityForAll(bool visible) +{ + const auto buttons = findChildren(); + for (QPushButton *button : buttons) + button->setVisible(visible); +} + + +void LtsDialogButtonBox::setText(DialogButton button, const QString &text) +{ + + + QPushButton *btn = getButton(button); + + if (btn) + btn->setText(text); +} + + +void LtsDialogButtonBox::setToolTip(DialogButton button, const QString &text) +{ + QPushButton *btn = getButton(button); + + if (btn) + btn->setToolTip(text); +} + + +void LtsDialogButtonBox::setIcon(DialogButton button, const QIcon &icon) +{ + QPushButton *btn = getButton(button); + + if (btn) + btn->setIcon(icon); +} + + +void LtsDialogButtonBox::setMenu(DialogButton button, QMenu *menu) +{ + QPushButton *btn = getButton(button); + + if (btn) + btn->setMenu(menu); +} + + +QPushButton *LtsDialogButtonBox::getButton(DialogButton button) const +{ + switch (button) + { + case DialogButton::ReadOnly: + return m_ui->pbReadOnly; + case DialogButton::Ok: + return m_ui->pbOk; + case DialogButton::Apply: + return m_ui->pbApply; + case DialogButton::Close: + return m_ui->pbClose; + case DialogButton::Yes: + return m_ui->pbYes; + case DialogButton::No: + return m_ui->pbNo; + case DialogButton::Remove: + return m_ui->pbRemove; + case DialogButton::Hide: + return m_ui->pbHide; + case DialogButton::Add: + return m_ui->pbAdd; + case DialogButton::Help: + return m_ui->pbHelp; + case DialogButton::Edit: + return m_ui->pbEdit; + case DialogButton::Save: + return m_ui->pbSave; + case DialogButton::Load: + return m_ui->pbLoad; + case DialogButton::Show: + return m_ui->pbShow; + case DialogButton::Import: + return m_ui->pbImport; + case DialogButton::Export: + return m_ui->pbExport; + case DialogButton::Library: + return m_ui->pbLibrary; + case DialogButton::CenterMap: + return m_ui->pbCenterMap; + case DialogButton::ZoomMapTo: + return m_ui->pbZoomMapTo; + case DialogButton::Print: + return m_ui->pbPrint; +#ifdef LTS_DEBUG + case DialogButton::_debug_: + return m_ui->_pbChanges; +#endif + } + Q_ASSERT(false); + return nullptr; +} + + +void LtsDialogButtonBox::slotRemoveClicked() +{ + if (LtsMessageDialog::showMessage(this, + tr("Are you sure you want to delete the selected entry(ies)?"), + tr("Remove selected entry(ies)"), + Behaviour::Modal, + Button::Yes | Button::No) + == Button::Yes) + emit signalRemoveClicked(); +} + + +void LtsDialogButtonBox::setButtonsVisible(QPushButton *pb) +{ + if (!pb || (pb == m_ui->pbPrint && m_isTablet)) + return; + m_ui->line->setVisible(true); + pb->setVisible(true); +} + + +void LtsDialogButtonBox::setButtonVisible(DialogButton button, bool visible) +{ + QPushButton *pb = getButton(button); + if (!pb) + return; + if (m_isTablet && pb == m_ui->pbPrint) + { + pb->setVisible(false); + return; + } + pb->setVisible(visible); +} + + +bool LtsDialogButtonBox::isButtonVisible(DialogButton button) const +{ + const QPushButton *pb = getButton(button); + return (pb ? pb->isVisible() : false); +} + + +void LtsDialogButtonBox::setButtonEnabled(DialogButton button, bool enabled) +{ + QPushButton *pb = getButton(button); + + switch (button) + { + case DialogButton::Close: + case DialogButton::Ok: + case DialogButton::Apply: + + + { +#ifdef LTS_DEBUG + LtsMessageDialog::showMessage(this, QStringLiteral("Hier stimmt etwas nicht. Bitte MIKE bescheid geben WAS geklickt oder gemacht wurde als dieser Dialog auftauchte, damit ich es fixen kann. Danke."), QStringLiteral(">>> STOP <<<")); +#endif + break; + } + + default: + break; + } + + if (pb) + pb->setEnabled(enabled); +} + + +bool LtsDialogButtonBox::isButtonEnabled(DialogButton button) const +{ + auto *that = const_cast(this); + const QPushButton *pb = that->getButton(button); + return (pb ? pb->isEnabled() : false); +} +} diff --git a/Lts/Common/LtsWidgets/lts_dialog_button_box.h b/Lts/Common/LtsWidgets/lts_dialog_button_box.h new file mode 100755 index 000000000..d56da40ac --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_dialog_button_box.h @@ -0,0 +1,196 @@ + + + +#pragma once + +#include "lts_widgets_export.h" +#include "lts_message_dialog.h" + +#include +#include + +namespace Ui +{ +class LtsDialogButtonBox; +} + +namespace LtsWidgets +{ +enum class DialogButton +{ + + ReadOnly, + Ok, + Apply, + Close, + Yes, + No, + + + Remove, + Hide, + Add, + Help, + Edit, + Save, + Load, + Show, + Import, + Export, + CenterMap, + ZoomMapTo, + Library, + Print, + +#ifdef LTS_DEBUG + _debug_, +#endif +}; + + + + + + + +class LTSWIDGETS_EXPORT LtsDialogButtonBox : public QWidget +{ + Q_OBJECT + +public: + explicit LtsDialogButtonBox(QWidget *parent); + ~LtsDialogButtonBox(); + + + + template + void connectButton(DialogButton button, const typename QtPrivate::FunctionPointer::Object *receiver, + Func2 slot, Qt::ConnectionType type = Qt::AutoConnection) + { + + { + switch (button) + { +#ifdef LTS_DEBUG + case DialogButton::_debug_: +#endif + case DialogButton::Close: + case DialogButton::Ok: + case DialogButton::ReadOnly: + case DialogButton::Apply: + case DialogButton::Help: + { + LtsMessageDialog::showMessage(this, + QStringLiteral("Bitte den fuer das Plugin verantwortlichen kontaktieren:
" + "m_buttonBox nicht mit Ok, Apply, Help, _debug_ oder Close verbinden
" + "Note: Bitte nur m_buttonBox verwenden, keine LtsDialogButtonBox selber erstellen" + "Ok & Apply werden automatisch zur virtual Methode 'apply()' im LtsDialog weitergeleitet.
" + "Close schließt einfach den Dialog - im Destruktor oder in 'aboutToClose()' ist der 'Close Code' gut aufgehoben.
" + "
" + "FIXEN !!!!!"), + QStringLiteral("--- STOP --- !!! FIXEN !!! ---")); + break; + } + + default: + break; + } + } + + + QPushButton *theSender = getButton(button); + + if (!theSender) + return; + + + switch (button) + { + case DialogButton::Remove: + { + connect(this, &LtsDialogButtonBox::signalRemoveClicked, receiver, slot, type); + connect(theSender, &QPushButton::clicked, this, &LtsDialogButtonBox::slotRemoveClicked); + + break; + } + case DialogButton::Export: + case DialogButton::Import: + { + if (m_isTablet) + { + theSender->setVisible(false); + return; + } + Q_FALLTHROUGH(); + } + default: + connect(theSender, &QPushButton::clicked, receiver, slot, type); + break; + } + + setButtonsVisible(theSender); + } + template + bool connectLibraryButton(const typename QtPrivate::FunctionPointer::Object *receiver, + Func1 slotLoad, + Func2 slotSave, + Qt::ConnectionType type = Qt::AutoConnection) + { + QPushButton *theSender = getButton(DialogButton::Library); + if (!theSender) + return false; + if (m_isTablet) + { + theSender->setVisible(false); + return true; + } + setButtonsVisible(theSender); + type = static_cast(type | Qt::UniqueConnection); + return (connect(this, &LtsDialogButtonBox::signalLibraryLoadClicked, receiver, slotLoad, type) && + connect(this, &LtsDialogButtonBox::signalLibrarySaveClicked, receiver, slotSave, type)); + } + + + + void setText(DialogButton button, const QString &text); + + void setToolTip(DialogButton button, const QString &text); + void setIcon(DialogButton button, const QIcon &icon); + void setMenu(DialogButton button, QMenu *menu); + + + + void setButtonEnabled(DialogButton button, bool enabled = true); + bool isButtonEnabled(DialogButton button) const; + + void setButtonVisible(DialogButton button, bool visible = true); + bool isButtonVisible(DialogButton button) const; + + + + QPushButton *getButton(DialogButton button) const; + + + + void _setVisibilityForAll(bool visible); + +signals: + void signalRemoveClicked(); + void signalHelpClicked(); + void signalLibraryLoadClicked(); + void signalLibrarySaveClicked(); + +private slots: + void slotRemoveClicked(); + +private: + const bool m_isTablet; + + + + void setButtonsVisible(QPushButton *pb); + + Ui::LtsDialogButtonBox *m_ui; +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_dialog_button_box.ui b/Lts/Common/LtsWidgets/lts_dialog_button_box.ui new file mode 100755 index 000000000..32152ae7f --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_dialog_button_box.ui @@ -0,0 +1,326 @@ + + + LtsDialogButtonBox + + + + 0 + 0 + 1706 + 46 + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + [debug] + + + + + + + + + + + :/style/help_index.svg:/style/help_index.svg + + + + + + + Print + + + + :/style/freq_plan.svg:/style/freq_plan.svg + + + + + + + Library + + + + :/style/library.svg:/style/library.svg + + + + + + + Import + + + + :/style/export_documents.svg:/style/export_documents.svg + + + + + + + Export + + + + :/style/import_documents.svg:/style/import_documents.svg + + + + + + + Center Map + + + + :/style/center_to_object.svg:/style/center_to_object.svg + + + + + + + Zoom to + + + + :/style/zoom_map_to.svg:/style/zoom_map_to.svg + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + ReadOnly + + + + :/style/warning.svg:/style/warning.svg + + + + + + + OK + + + + :/style/dialog-ok.svg:/style/dialog-ok.svg + + + + + + + Apply + + + + :/style/dialog-ok.svg:/style/dialog-ok.svg + + + + + + + Load + + + + :/style/export_documents.svg:/style/export_documents.svg + + + + + + + Save + + + + :/style/import_documents.svg:/style/import_documents.svg + + + + + + + Add + + + + :/style/document-new.svg:/style/document-new.svg + + + + + + + Edit + + + + :/style/document-edit.svg:/style/document-edit.svg + + + + + + + Show + + + + :/style/filter_map_settings.svg:/style/filter_map_settings.svg + + + + + + + Yes + + + + :/style/dialog-ok.svg:/style/dialog-ok.svg + + + + + + + No + + + + :/style/dialog-close.svg:/style/dialog-close.svg + + + + + + + Remove + + + + :/style/media-vcr-nopause.svg:/style/media-vcr-nopause.svg + + + + + + + Hide + + + + :/style/show_building_configuration.svg:/style/show_building_configuration.svg + + + + + + + Close + + + + :/style/dialog-close.svg:/style/dialog-close.svg + + + Ctrl+W + + + + + + + + + + + + + diff --git a/Lts/Common/LtsWidgets/lts_dialog_helper.cpp b/Lts/Common/LtsWidgets/lts_dialog_helper.cpp new file mode 100755 index 000000000..c9c6820eb --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_dialog_helper.cpp @@ -0,0 +1,172 @@ + + + +#include "lts_dialog_helper.h" +#include "lts_dialog.h" +#include "ui_lts_dialog_helper.h" + +#include +#include +#include + + +namespace LtsWidgets +{ +class LDHScrollBar : public QScrollBar +{ + Q_OBJECT +public: + using QScrollBar::QScrollBar; + + void showEvent(QShowEvent *e) override + { + QScrollBar::showEvent(e); + emit scrollbarVisiblityChanged(true); + } + void hideEvent(QHideEvent *e) override + { + QScrollBar::hideEvent(e); + emit scrollbarVisiblityChanged(false); + } +Q_SIGNALS: + void scrollbarVisiblityChanged(bool visible); +}; + + +LtsDialogHelper::LtsDialogHelper(QWidget *parent) + : QWidget(parent) + , m_ui(new Ui::LtsDialogHelper()) + , m_dialog(nullptr) +{ + m_ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + auto hScroll = new LDHScrollBar(this); + auto vScroll = new LDHScrollBar(this); + m_ui->scrollArea->setHorizontalScrollBar(hScroll); + m_ui->scrollArea->setVerticalScrollBar(vScroll); + connect(hScroll, &LDHScrollBar::scrollbarVisiblityChanged, + this, &LtsDialogHelper::onHScrollBarVisibilityChanged); + connect(vScroll, &LDHScrollBar::scrollbarVisiblityChanged, + this, &LtsDialogHelper::onVScrollBarVisibilityChanged); + +#ifdef TABLET_MODE + QList scrollWidgets = findChildren(); + + for (auto i = scrollWidgets.begin(); i != scrollWidgets.end(); ++i) + { + QWidget *widget = *i; + + QVariant isConst = widget->property("const"); + if (isConst.isValid() && isConst.toBool()) + continue; + + LtsWidgets::addTouchScroll(widget); + } +#endif + m_ui->buttonBox->setMinimumHeight(m_ui->buttonBox->sizeHint().height()); +} + + +LtsDialogHelper::~LtsDialogHelper() +{ + m_ui->scrollArea->horizontalScrollBar()->disconnect(this); + m_ui->scrollArea->verticalScrollBar()->disconnect(this); + delete m_ui; +} + + +LtsDialogButtonBox *LtsDialogHelper::getButtonBox() +{ + return m_ui->buttonBox; +} + + +void LtsDialogHelper::setDialog(LtsDialog *dialog) +{ + if (m_dialog != nullptr) + return; + + m_dialog = dialog; + m_dialog->setContentsMargins(QMargins()); + if (m_dialog->layout()) + m_dialog->layout()->setContentsMargins(QMargins()); + m_ui->scrollArea->setWidget(m_dialog); + + + { + connect(dialog, &LtsDialog::destroyed, this, [this]() + { + m_dialog = nullptr; + deleteLater(); + }); + } + + + +} + +LtsDialog *LtsDialogHelper::getDialog() +{ + return m_dialog; +} + +void LtsDialogHelper::retranslate() +{ + if (m_dialog == nullptr) + return; + + m_dialog->setContentsMargins(QMargins()); + if (m_dialog->layout()) + m_dialog->layout()->setContentsMargins(QMargins()); + + bool oldChangesFlag = m_dialog->m_changesMade; + + + + m_dialog->retranslate(); + + if (oldChangesFlag) + m_dialog->slotChangesMade(); + else + m_dialog->resetChanges(); +} + + +void LtsDialogHelper::resizeEvent(QResizeEvent *e) +{ + emit signalSizeChanged(e->size()); +} + + +void LtsDialogHelper::changeEvent(QEvent *e) +{ + QWidget::changeEvent(e); + if (e->type() == QEvent::LanguageChange) + retranslate(); +} + + +void LtsDialogHelper::onHScrollBarVisibilityChanged(bool visible) +{ + if (m_dialog == nullptr || !m_dialog->layout()) + return; + + QMargins m = m_dialog->layout()->contentsMargins(); + m.setBottom(visible ? -1 : 0); + m_dialog->layout()->setContentsMargins(m); +} + + +void LtsDialogHelper::onVScrollBarVisibilityChanged(bool visible) +{ + if (m_dialog == nullptr || !m_dialog->layout()) + return; + + QMargins m = m_dialog->layout()->contentsMargins(); + m.setRight(visible ? -1 : 0); + m_dialog->layout()->setContentsMargins(m); +} + +} + +#include "lts_dialog_helper.moc" diff --git a/Lts/Common/LtsWidgets/lts_dialog_helper.h b/Lts/Common/LtsWidgets/lts_dialog_helper.h new file mode 100755 index 000000000..c171e3532 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_dialog_helper.h @@ -0,0 +1,51 @@ + + + +#pragma once + +#include +#include + + +namespace Ui +{ +class LtsDialogHelper; +} + + +namespace LtsWidgets +{ +class LtsDialogButtonBox; +class LtsDialog; + + + +class LtsDialogHelper : public QWidget +{ + Q_OBJECT + +public: + explicit LtsDialogHelper(QWidget *parent = nullptr); + ~LtsDialogHelper(); + + LtsDialogButtonBox *getButtonBox(); + void setDialog(LtsDialog *dialog); + LtsDialog *getDialog(); + bool compare(const LtsDialog *dialog) const { return (dialog == m_dialog); } + void retranslate(); + +signals: + void signalSizeChanged(const QSize &size); + +private: + void resizeEvent(QResizeEvent *e) override; + void changeEvent(QEvent *e) override; + void onHScrollBarVisibilityChanged(bool visible); + void onVScrollBarVisibilityChanged(bool visible); + + Ui::LtsDialogHelper *m_ui; + LtsDialog *m_dialog; + +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_dialog_helper.ui b/Lts/Common/LtsWidgets/lts_dialog_helper.ui new file mode 100755 index 000000000..ef1550ab4 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_dialog_helper.ui @@ -0,0 +1,70 @@ + + + LtsDialogHelper + + + + 0 + 0 + 456 + 306 + + + + + 0 + 0 + + + + + + + + + + QFrame::NoFrame + + + Qt::ScrollBarAsNeeded + + + true + + + + + + + + 0 + 0 + + + + + 0 + 40 + + + + + 16777215 + 150 + + + + + + + + + LtsWidgets::LtsDialogButtonBox + QWidget +
LtsWidgets/lts_dialog_button_box.h
+ 1 +
+
+ + +
diff --git a/Lts/Common/LtsWidgets/lts_drag_icon.cpp b/Lts/Common/LtsWidgets/lts_drag_icon.cpp new file mode 100755 index 000000000..9dad4ce61 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_drag_icon.cpp @@ -0,0 +1,489 @@ + + + +#include "lts_drag_icon.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace LtsWidgets +{ + +class LtsToolButton : public QToolButton +{ + Q_OBJECT +public: + explicit LtsToolButton(QWidget *parent) + : QToolButton(parent) + , m_sameWidgetDrop(false) + , m_isDraggable(true) + , m_startPos(QPoint()) + { + setAutoFillBackground(false); + setToolButtonStyle(Qt::ToolButtonTextUnderIcon); + } + + virtual ~LtsToolButton() = default; + + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void dragEnterEvent(QDragEnterEvent *event) override; + void setSameWidgetDrop(bool drop) { m_sameWidgetDrop = drop; } + bool sameWidgetDrop() const { return m_sameWidgetDrop; } + void setDraggable(bool isDraggable) { m_isDraggable = isDraggable; } + void dropEvent(QDropEvent *event) override + { + emit newDropEvent(event); + QToolButton::dropEvent(event); + } + void addMimeData(const QString &mimeType, const QByteArray &data); + bool removeMimeData(const QString &mimeType); + void removeAllMimeData() { m_mimeData.clear(); } + +protected: + void initButton(); + +signals: + void newDropEvent(QDropEvent *e); + void dropSuc(); + +private: + void performDrag(QMouseEvent *event); + bool m_sameWidgetDrop; + bool m_isDraggable; + QPoint m_startPos; + QMap m_mimeData; + +}; + + + +LtsDragIcon::LtsDragIcon(QWidget *parent) + : QWidget(parent) + , m_lay(nullptr) + , m_toolButton(nullptr) + , m_icon() + , m_dragLabel(new QLabel(this)) + , m_text() + , m_style(ButtonStyle::eAll) + , m_forceFixedSize(false) + , m_isDraggable(true) + , m_dropHandlerKey(0L) + , m_buttonSize(ButtonSize::eDefault) + , m_showDragIcon(true) +{ + setAutoFillBackground(false); + initButton(); +} + + +void LtsDragIcon::setStyleToButton() +{ + if (nullptr == m_toolButton) return; + + switch (m_style) + { + case ButtonStyle::eIconOnly: + { + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + m_toolButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + setFixedSize(QSize(static_cast(m_buttonSize), static_cast(m_buttonSize))); + m_toolButton->setToolButtonStyle(Qt::ToolButtonIconOnly); + } break; + + case ButtonStyle::eTextOnly: + { + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + m_toolButton->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); + setFixedSize( QSize(QWIDGETSIZE_MAX,QWIDGETSIZE_MAX) ); + m_toolButton->setToolButtonStyle(Qt::ToolButtonTextOnly); + layout()->setSizeConstraint( QLayout::SetDefaultConstraint ); + } break; + + case ButtonStyle::eTextNextToIcon: + { + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + m_toolButton->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); + setFixedSize( QSize(QWIDGETSIZE_MAX,QWIDGETSIZE_MAX) ); + m_toolButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); + layout()->setSizeConstraint( QLayout::SetDefaultConstraint ); + } break; + + default: break; + } + +} + + +void LtsDragIcon::initButton() +{ + setMinimumSize(static_cast(m_buttonSize), static_cast(m_buttonSize)); + + delete m_toolButton; + delete m_lay; + delete layout(); + + m_toolButton = new LtsToolButton(this); + m_toolButton->setDraggable(m_isDraggable); + connect(m_toolButton, &LtsToolButton::clicked, this, &LtsDragIcon::clicked); + connect(m_toolButton, &LtsToolButton::toggled, this, &LtsDragIcon::toggled); + connect(m_toolButton, &LtsToolButton::newDropEvent, this, &LtsDragIcon::newDropEvent); + connect(m_toolButton, &LtsToolButton::dropSuc, this, &LtsDragIcon::dropSuc); + + m_toolButton->setIcon(modifyIcon(m_icon)); + m_toolButton->setText(m_text); + generateToolTip(); + m_toolButton->setIconSize(QSize(static_cast(m_buttonSize), static_cast(m_buttonSize))); + + + QVBoxLayout *box = new QVBoxLayout(this); + box->setMargin(0); + box->setSpacing(0); + box->addWidget(m_toolButton); + + this->setLayout(box); + + m_toolButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); + + if (m_forceFixedSize) + { + m_toolButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + setFixedSize(QSize(static_cast(m_buttonSize), static_cast(m_buttonSize))); + } + else + { + m_toolButton->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); + } + + setStyleToButton(); +} + +void LtsDragIcon::addToButtonGroup(QButtonGroup *btnGrp, int btnGrpId) +{ + btnGrp->addButton(m_toolButton, btnGrpId); +} + + +void LtsDragIcon::setIcon(const QIcon &icon) +{ + m_icon = icon; + (m_toolButton) ? m_toolButton->setIcon(modifyIcon(m_icon)) : initButton(); +} + + +void LtsDragIcon::setMenu(QMenu *menu) +{ + if (nullptr == m_toolButton) + initButton(); + m_toolButton->setMenu(menu); +} + +QMenu *LtsDragIcon::menu() const +{ + if (nullptr == m_toolButton) + return nullptr; + + return m_toolButton->menu(); +} + + +void LtsDragIcon::setText(const QString &text) +{ + m_text = text; + (m_toolButton) ? generateToolTip() : initButton(); +} + +void LtsDragIcon::setContext(const QString &context) +{ + m_context = context; + generateToolTip(); +} + +void LtsDragIcon::generateToolTip() +{ + if (!m_toolButton || m_text.isEmpty()) + return; + + QString text(m_text); + if (!m_context.isEmpty()) + text.prepend(m_context + QLatin1String(" - ")); + + m_toolButton->setText(m_text); + m_toolButton->setToolTip(text); +} + +bool LtsDragIcon::isCheckable() +{ + return (m_toolButton) ? m_toolButton->isCheckable() : false; +} + +void LtsDragIcon::setDraggable(bool draggable) +{ + m_isDraggable = draggable; + m_toolButton->setDraggable(m_isDraggable); +} + +void LtsDragIcon::setCheckable(bool checkable) +{ + m_toolButton->setCheckable(checkable); +} + +bool LtsDragIcon::isChecked() const +{ + return (m_toolButton) ? m_toolButton->isChecked() : false; +} + +void LtsDragIcon::setChecked(bool checked) +{ + if (nullptr == m_toolButton) + initButton(); + m_toolButton->setChecked(checked); +} + +void LtsDragIcon::setSameWidgetDrop(bool drop) +{ + if (nullptr == m_toolButton) + initButton(); + m_toolButton->setSameWidgetDrop(drop); +} + +bool LtsDragIcon::sameWidgetDrop() const +{ + if (nullptr == m_toolButton) + return false; + return m_toolButton->sameWidgetDrop(); +} + +void LtsDragIcon::addMimeData(const QString &mimeType, const QByteArray &data) +{ + if (nullptr == m_toolButton) + initButton(); + m_toolButton->addMimeData(mimeType, data); +} + +bool LtsDragIcon::removeMimeData(const QString &mimeType) +{ + if (nullptr == m_toolButton) + initButton(); + return m_toolButton->removeMimeData(mimeType); +} + +void LtsDragIcon::removeAllMimeData() +{ + if (nullptr == m_toolButton) + initButton(); + m_toolButton->removeAllMimeData(); +} + +void LtsDragIcon::setAcceptDrops(bool on) +{ + if (nullptr == m_toolButton) + initButton(); + m_toolButton->setAcceptDrops(on); +} + +void LtsDragIcon::dragEnterEvent(QDragEnterEvent *e) +{ + if (m_toolButton) + m_toolButton->dragEnterEvent(e); +} +void LtsDragIcon::dropEvent(QDropEvent *e) +{ + QWidget::dropEvent(e); +} + +void LtsDragIcon::setButtonStyle(ButtonStyle style) +{ + m_style = style; + setStyleToButton(); +} + +void LtsDragIcon::setIconSize(ButtonSize buttonSize) +{ + m_buttonSize = buttonSize; + (m_toolButton) ? m_toolButton->setIconSize(QSize(static_cast(m_buttonSize), static_cast(m_buttonSize))) : initButton(); +} + +void LtsDragIcon::setForceFixedSize(bool force) +{ + m_forceFixedSize = force; + initButton(); +} + +void LtsDragIcon::setButtonVisible(bool visible) +{ + if (nullptr == m_toolButton) + initButton(); + m_toolButton->setVisible(visible); +} + +bool LtsDragIcon::buttonVisible() const +{ + if (nullptr == m_toolButton) + return false; + return m_toolButton->isVisible(); +} + +void LtsDragIcon::setAction(QAction *action) +{ + QIcon icon = action->icon(); + if(!icon.isNull()) + m_icon = icon; + m_toolButton->setDefaultAction(action); + this->setObjectName(action->objectName()); +} + +void LtsDragIcon::changeEvent(QEvent *event) +{ + if (event->type() == QEvent::EnabledChange) + emit enabledChanged(isEnabled()); +} + + + +void LtsToolButton::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) + m_startPos = event->pos(); + QToolButton::mousePressEvent(event); +} + + +void LtsToolButton::mouseReleaseEvent(QMouseEvent *event) +{ + QToolButton::mouseReleaseEvent(event); +} + +void LtsToolButton::mouseMoveEvent(QMouseEvent *event) +{ + if (!m_isDraggable) + return; + if (event->buttons() & Qt::LeftButton) { + int distance = (event->pos() - m_startPos).manhattanLength(); + if (distance >= 5) + { + this->setDown(false); + performDrag(event); + return; + } + } + QToolButton::mouseMoveEvent(event); +} + +void LtsToolButton::performDrag(QMouseEvent *) +{ + QWidget *item = this; + if (item) + { + + LtsDragIcon *dragIcon = nullptr; + QString name; + + while (dragIcon == nullptr) + { + if ((!item->objectName().isEmpty()) && (name.isNull())) + { + name = item->objectName(); + } + item = item->parentWidget(); + if (nullptr == item) return; + dragIcon = dynamic_cast(item); + } + QMimeData *mimeData = new QMimeData(); + mimeData->setText(name); + for(auto it = m_mimeData.cbegin(); it != m_mimeData.cend(); ++it) + mimeData->setData(it.key(), it.value()); + + + if (dragIcon->dropHandlerKey() != 0L) + { + QByteArray data = QByteArray(QByteArray::number(dragIcon->dropHandlerKey())); + mimeData->setData(QLatin1String("DropHandlerPtr"),data); + } + + QDrag *drag = new QDrag(item); + + drag->setMimeData(mimeData); + + QAction *action = defaultAction(); + + if ((action) && (dragIcon->showDragIcon())) + { + drag->setPixmap(action->icon().pixmap(16,16) ); + } + else + { + drag->setPixmap(dragIcon->icon().pixmap(16, 16)); + } + Qt::DropAction drop = drag->exec(Qt::CopyAction | Qt::MoveAction); + if (drop == Qt::MoveAction || drop == Qt::CopyAction ) + emit dropSuc(); + } + +} + +void LtsToolButton::dragEnterEvent(QDragEnterEvent *event) +{ + QWidget *source = + qobject_cast(event->source()); + if (source && source != this) { + event->setDropAction(Qt::MoveAction); + event->accept(); + } + QWidget::dragEnterEvent(event); +} + +void LtsToolButton::addMimeData(const QString &mimeType, const QByteArray &data) +{ + m_mimeData.insert(mimeType,data); +} + +bool LtsToolButton::removeMimeData(const QString &mimeType) +{ + return (m_mimeData.remove(mimeType) > 0); +} + +#define SIZE 40 + +QIcon LtsDragIcon::modifyIcon(const QIcon &icon) +{ + if (m_modification.isEmpty()) + return icon; + + QPixmap iconImage(icon.pixmap(SIZE*0.8, SIZE*0.8)); + QPainter painter(&iconImage); + + painter.save(); + painter.setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing | QPainter::HighQualityAntialiasing | QPainter::SmoothPixmapTransform); + painter.setBrush(QColor("red")); + painter.setPen(QPen(QColor("red"), 3)); + + QFont f; + f.setFamily(QStringLiteral("Monospace")); + f.setPointSize(12); + f.setBold(true); + painter.setFont(f); + + painter.drawText(QRect(0, 0, 28, 28), m_modification); + painter.restore(); + + return iconImage; +} + +} + + +#include "lts_drag_icon.moc" + diff --git a/Lts/Common/LtsWidgets/lts_drag_icon.h b/Lts/Common/LtsWidgets/lts_drag_icon.h new file mode 100755 index 000000000..c4b99c892 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_drag_icon.h @@ -0,0 +1,246 @@ + + + +#pragma once + +#include "lts_base.h" +#include "lts_drop_handler.h" +#include "lts_widgets_export.h" + +#include +#include +#include + + +class QButtonGroup; +class QHBoxLayout; +class QLabel; +class QMenu; + +namespace LtsWidgets { + +class LtsToolButton; + + + +class LTSWIDGETS_EXPORT LtsDragIcon : public QWidget +{ +public: + Q_OBJECT + Q_PROPERTY(QString text READ text WRITE setText) + Q_PROPERTY(QString context READ context WRITE setContext) + Q_PROPERTY(QString identifier READ identifier WRITE setIdentifier) + Q_PROPERTY(QIcon icon READ icon WRITE setIcon) + Q_PROPERTY(LtsWidgets::Designer::ButtonSize eButtonSize READ iconSizeDesigner WRITE setIconSizeDesigner) + Q_PROPERTY(bool eForceFixedSize READ isForceFixedSize WRITE setForceFixedSize) + Q_PROPERTY(bool isCheckable READ isCheckable WRITE setCheckable) + Q_PROPERTY(bool isChecked READ isChecked WRITE setChecked) + Q_PROPERTY(bool showDragIcon READ showDragIcon WRITE setShowDragIcon) + Q_PROPERTY(bool isDraggable READ isDraggable WRITE setDraggable) + + + + Q_PROPERTY(LtsWidgets::Designer::ButtonStyle eStyle READ buttonStyleDesigner WRITE setButtonStyleDesigner) + + + + + void setButtonStyleDesigner(Designer::ButtonStyle t) { setButtonStyle(static_cast(t)); } + + + + Designer::ButtonStyle buttonStyleDesigner() const { return static_cast(buttonStyle()); } + + + + void setIconSizeDesigner(Designer::ButtonSize t) { setIconSize(static_cast(t)); } + + + + Designer::ButtonSize iconSizeDesigner() const { return static_cast(iconSize()); } + +public: + + + explicit LtsDragIcon(QWidget *parent); + + virtual ~LtsDragIcon() = default; + + + + void setMenu(QMenu *menu); + + + + QMenu* menu() const; + + + + void setText(const QString &text); + + + + QString text() const { return m_text; } + + + + void setContext(const QString &context); + + + + QString context() const { return m_context; } + + + + void setCheckable(bool checkable); + + + + bool isCheckable(); + + + + + void setDraggable(bool draggable); + + + + bool isDraggable() const { return m_isDraggable; } + + + + void setChecked(bool checked); + + + + bool isChecked() const; + + + + void setShowDragIcon(bool showDragIcon) { m_showDragIcon = showDragIcon; } + bool showDragIcon() const { return m_showDragIcon; } + + + + void setSameWidgetDrop(bool drop); + + + + bool sameWidgetDrop() const; + + + + void setButtonStyle(ButtonStyle style); + + + + ButtonStyle buttonStyle() const { return m_style; } + + + + void setIconSize(ButtonSize buttonSize); + + + + ButtonSize iconSize() const { return m_buttonSize; } + + + + void setButtonVisible(bool visible); + + + + bool buttonVisible() const; + + + + void setIcon(const QIcon &icon); + + + + QIcon icon() const { return m_icon; } + + + + void setForceFixedSize(bool force); + + + + bool isForceFixedSize() const { return m_forceFixedSize; } + + + + void setAction(QAction *action); + + + + void addToButtonGroup(QButtonGroup *btnGrp, int btnGrpId); + + + + void setDropHandlerKey(qulonglong key) { m_dropHandlerKey = key; } + + + + void addMimeData(const QString &mimeType, const QByteArray &data); + bool removeMimeData(const QString &mimeType); + void removeAllMimeData(); + + + + void setAcceptDrops(bool on); + + qulonglong dropHandlerKey() const { return m_dropHandlerKey; } + + + void setIdentifier(const QString &id) { m_identifier = id; } + QString identifier() const { return m_identifier; } + + void setModification(const QString &id) { m_modification = id; setIcon(m_icon); } + QString modification() const { return m_modification; } + void removeModification() { m_modification = QString(); setIcon(m_icon);} + +signals: + + + void clicked(); + + + + void toggled(bool); + void newDropEvent(QDropEvent *e); + void dropSuc(); + + void enabledChanged(bool); + +protected: + void dragEnterEvent(QDragEnterEvent *e) override; + void dropEvent(QDropEvent *e) override; + void initButton(); + +private: + void setStyleToButton(); + QIcon modifyIcon(const QIcon &icon); + void generateToolTip(); + +protected slots: + void changeEvent(QEvent *event) override; + +private: + QHBoxLayout *m_lay; + LtsToolButton *m_toolButton; + QIcon m_icon; + QLabel *m_dragLabel; + QString m_text; + QString m_identifier; + QString m_modification; + ButtonStyle m_style; + bool m_forceFixedSize; + bool m_isDraggable; + qulonglong m_dropHandlerKey; + ButtonSize m_buttonSize; + bool m_showDragIcon; + QString m_context; +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_drop_handler.h b/Lts/Common/LtsWidgets/lts_drop_handler.h new file mode 100755 index 000000000..e1df572b2 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_drop_handler.h @@ -0,0 +1,31 @@ + + + +#pragma once + + +class QMimeData; + + +namespace LtsWidgets +{ + + + class LtsDropHandler + { + public: + + LtsDropHandler() = default; + virtual ~LtsDropHandler() = default; + + LtsDropHandler( const LtsDropHandler & ) = delete; + LtsDropHandler & operator = ( const LtsDropHandler & ) = delete; + + + + virtual void doDrop( int x, int y, const QMimeData * mimeData ) const = 0; + + }; + + +} diff --git a/Lts/Common/LtsWidgets/lts_elided_label.cpp b/Lts/Common/LtsWidgets/lts_elided_label.cpp new file mode 100755 index 000000000..738d94886 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_elided_label.cpp @@ -0,0 +1,60 @@ + + + +#include "lts_elided_label.h" + +#include +#include + + +namespace LtsWidgets +{ +void LtsElidedLabel::setElideMode(Qt::TextElideMode elideMode) +{ + m_elideMode = elideMode; + m_cachedText.clear(); + update(); +} + +void LtsElidedLabel::resizeEvent(QResizeEvent *e) +{ + QLabel::resizeEvent(e); + m_cachedText.clear(); +} + +void LtsElidedLabel::paintEvent(QPaintEvent *e) +{ + if (m_elideMode == Qt::ElideNone) + return QLabel::paintEvent(e); + + updateCachedTexts(); + QLabel::setText(m_cachedElidedText); + QLabel::paintEvent(e); + QLabel::setText(m_cachedText); +} + +void LtsElidedLabel::updateCachedTexts() +{ + + const auto txt = text(); + if (m_cachedText == txt) + return; + m_cachedText = txt; + const QFontMetrics fm(fontMetrics()); + m_cachedElidedText = fm.elidedText(text(), + m_elideMode, + width(), + Qt::TextShowMnemonic); + if (!m_cachedText.isEmpty()) + { + const QString showFirstCharacter = m_cachedText.at(0) + QStringLiteral("..."); +#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0) + const int width = fm.width(showFirstCharacter); +#else + const int width = fm.horizontalAdvance(showFirstCharacter); +#endif + setMinimumWidth(width + 1); + } +} + +} diff --git a/Lts/Common/LtsWidgets/lts_elided_label.h b/Lts/Common/LtsWidgets/lts_elided_label.h new file mode 100755 index 000000000..a106c2b2a --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_elided_label.h @@ -0,0 +1,36 @@ + + + +#pragma once + +#include +#include + +namespace LtsWidgets +{ + +class LTSWIDGETS_EXPORT LtsElidedLabel : public QLabel +{ + Q_OBJECT +public: + Q_PROPERTY(Qt::TextElideMode ElideMode READ elideMode WRITE setElideMode) + + using QLabel::QLabel; + + void setElideMode(Qt::TextElideMode elideMode); + Qt::TextElideMode elideMode() const { return m_elideMode; } + +protected: + void paintEvent(QPaintEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + +private: + void updateCachedTexts(); + +private: + Qt::TextElideMode m_elideMode = Qt::ElideRight; + QString m_cachedElidedText; + QString m_cachedText; +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_events.cpp b/Lts/Common/LtsWidgets/lts_events.cpp new file mode 100755 index 000000000..9f5372bb0 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_events.cpp @@ -0,0 +1,58 @@ + + + +#include "lts_events.h" + +namespace LtsWidgets +{ +QEvent::Type LtsLinkEvent::eventTypeId() +{ + static QEvent::Type sLinkEventId = QEvent::None; + if (sLinkEventId == QEvent::None) + sLinkEventId = static_cast(QEvent::registerEventType()); + return sLinkEventId; +} + +QEvent::Type SaveImageEvent::saveImageEventId() +{ + static QEvent::Type sSaveImageEventId = QEvent::None; + if (sSaveImageEventId == QEvent::None) + sSaveImageEventId = static_cast(QEvent::registerEventType()); + return sSaveImageEventId; +} + +QEvent::Type SetMarkerEvent::setMarkerEventId() +{ + static QEvent::Type sSetMarkerEventId = QEvent::None; + if (sSetMarkerEventId == QEvent::None) + sSetMarkerEventId = static_cast(QEvent::registerEventType()); + return sSetMarkerEventId; +} + +QEvent::Type AddMarkerEvent::addMarkerEventId() +{ + static QEvent::Type sAddMarkerEventId = QEvent::None; + if (sAddMarkerEventId == QEvent::None) + sAddMarkerEventId = static_cast(QEvent::registerEventType()); + return sAddMarkerEventId; +} + +QEvent::Type AddSnapshotEvent::addSnapshotEventId() +{ + static QEvent::Type eventId = QEvent::None; + if (eventId == QEvent::None) + eventId = static_cast(QEvent::registerEventType()); + return eventId; +} + +QEvent::Type SaveSnapshotEvent::saveSnapshotEventId() +{ + static QEvent::Type eventId = QEvent::None; + if (eventId == QEvent::None) + eventId = static_cast(QEvent::registerEventType()); + return eventId; +} + + + +} diff --git a/Lts/Common/LtsWidgets/lts_events.h b/Lts/Common/LtsWidgets/lts_events.h new file mode 100755 index 000000000..60cd3cd58 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_events.h @@ -0,0 +1,94 @@ + + + +#pragma once + +#include "lts_widgets_export.h" + +#include +#include +#include +#include +#include + + +namespace LtsWidgets +{ +class LTSWIDGETS_EXPORT LtsLinkEvent : public QEvent +{ +public: + explicit LtsLinkEvent(QUrl url, QWidget *sourceBaseLayoutWidget) + : QEvent(eventTypeId()) + , m_url(std::move(url)) + , m_sourceBaseLayoutWidget(sourceBaseLayoutWidget) + {} + + static QEvent::Type eventTypeId(); + QWidget *sourceBaseLayoutWidget() const { return m_sourceBaseLayoutWidget.data(); } + QUrl url() const { return m_url; } +private: + QUrl m_url; + QPointer m_sourceBaseLayoutWidget; +}; + +class LTSWIDGETS_EXPORT SaveImageEvent : public QEvent +{ +public: + SaveImageEvent(QWidget *w, QPixmap &&pm) + : QEvent(saveImageEventId()), m_widget(w), m_pix(std::move(pm)) + {} + + const QPixmap &pixmap() const { return m_pix; } + QWidget *widget() const { return m_widget.data(); } + static QEvent::Type saveImageEventId(); +private: + QPointer m_widget; + QPixmap m_pix; +}; + +class LTSWIDGETS_EXPORT AddMarkerEvent : public QEvent +{ +public: + AddMarkerEvent() + : QEvent(addMarkerEventId()) + {} + static QEvent::Type addMarkerEventId(); +}; + +class LTSWIDGETS_EXPORT SetMarkerEvent : public QEvent +{ +public: + SetMarkerEvent(const QString &description) + : QEvent(setMarkerEventId()), m_description(description) + {} + const QString &getDescription() const { return m_description; } + void setDescription(const QString &description) { m_description = description; } + static QEvent::Type setMarkerEventId(); +private: + QString m_description; +}; + +class LTSWIDGETS_EXPORT AddSnapshotEvent : public QEvent +{ +public: + AddSnapshotEvent() + : QEvent(addSnapshotEventId()) + {} + static QEvent::Type addSnapshotEventId(); +}; + +class LTSWIDGETS_EXPORT SaveSnapshotEvent : public QEvent +{ +public: + SaveSnapshotEvent(const QString &description) + : QEvent(saveSnapshotEventId()), m_description(description) + {} + const QString &getDescription() const { return m_description; } + void setDescription(const QString &description) { m_description = description; } + static QEvent::Type saveSnapshotEventId(); +private: + QString m_description; +}; + + +} diff --git a/Lts/Common/LtsWidgets/lts_frameless_dialog.cpp b/Lts/Common/LtsWidgets/lts_frameless_dialog.cpp new file mode 100755 index 000000000..e26e9451d --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_frameless_dialog.cpp @@ -0,0 +1,153 @@ + + + +#include "lts_frameless_dialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace LtsWidgets +{ +LtsFramelessDialog::LtsFramelessDialog(QWidget *parent, const Behaviour &behaviour) + : QDialog(parent) +{ + + + Qt::WindowFlags wf; + + + switch (behaviour) + { + case Behaviour::Modal: + { + +#ifndef TABLET_MODE + setModal(true); + wf |= Qt::Dialog; +#else + wf |= Qt::Window; +#endif + wf |= Qt::WindowStaysOnTopHint; + break; + } + + case Behaviour::Popup: + wf |= Qt::Popup; + break; + } + + setWindowFlags(wf); + + + QTimer *t = new QTimer(this); + t->setInterval(250); + connect(t, &QTimer::timeout, + this, [this]() + { + + + + QSize s = size(); + setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, s, screenRectFromPoint(QCursor::pos()))); + }); + +} + + +LtsFramelessDialog::~LtsFramelessDialog() +{ + +} + + +QRect LtsFramelessDialog::screenRectFromPoint(const QPoint &pos) const +{ + const auto screens = QGuiApplication::screens(); + for (QScreen *screen : screens) + { + QRect s = screen->geometry().adjusted(-50, -50, 50, 50); + + bool contains = s.contains(pos); + + + + + + if (contains) + return screen->geometry(); + } + + return QRect(); +} + + +void LtsFramelessDialog::showEvent(QShowEvent *event) +{ + QDialog::showEvent(event); + QTimer::singleShot(0, this, &LtsFramelessDialog::onUpdateGeometry); + + +#ifdef TABLET_MODE + QList scrollWidgets = findChildren(); + + for (auto i = scrollWidgets.begin(); i != scrollWidgets.end(); ++i) + { + QWidget *widget = *i; + + QVariant isConst = widget->property("const"); + if (isConst.isValid() && isConst.toBool()) + continue; + + addTouchScroll(widget); + } +#endif +} + + +void LtsFramelessDialog::addTouchScroll(QWidget *widget) +{ + if (!widget) + return; + widget->setAttribute(Qt::WA_AcceptTouchEvents); + + QScrollArea *sca = qobject_cast(widget); + if (sca) + { + QScroller::grabGesture(sca->viewport(), QScroller::LeftMouseButtonGesture); + return; + } +} + + +void LtsFramelessDialog::onUpdateGeometry() +{ + QRect r; + QWidget *p = parentWidget(); + + if (p) + { + QWidget *window = p->window(); + QPoint global = window->mapToGlobal(QPoint(0,0)); + QSize size = window->size(); + + r = QRect(global, size); + } + else + { + r = QGuiApplication::primaryScreen()->geometry(); + } + + QSize s = size(); + setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, s, r)); +} + +} diff --git a/Lts/Common/LtsWidgets/lts_frameless_dialog.h b/Lts/Common/LtsWidgets/lts_frameless_dialog.h new file mode 100755 index 000000000..39e7dff86 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_frameless_dialog.h @@ -0,0 +1,46 @@ + + + +#pragma once + +#include + +#include "lts_base.h" +#include "lts_widgets_export.h" + + +namespace LtsWidgets +{ + + +enum class Behaviour +{ + Popup = 0, + Modal, +}; + + + + +class LTSWIDGETS_EXPORT LtsFramelessDialog : public QDialog +{ + Q_OBJECT + +public: + + + explicit LtsFramelessDialog(QWidget *parent = nullptr, const Behaviour &behaviour = Behaviour::Popup); + virtual ~LtsFramelessDialog(); + +public Q_SLOTS: + void onUpdateGeometry(); +private: + QRect screenRectFromPoint(const QPoint &pos) const; + void addTouchScroll(QWidget *widget); + +protected: + void showEvent(QShowEvent *event) override; + +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_global_wait_dialog.cpp b/Lts/Common/LtsWidgets/lts_global_wait_dialog.cpp new file mode 100755 index 000000000..b64f6b50e --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_global_wait_dialog.cpp @@ -0,0 +1,225 @@ + + +#include "lts_global_wait_dialog.h" +#include "lts_progress_dialog_helper.h" +#include "lts_overlay_loading.h" +#include "lts_dialog.h" +#include "lts_frameless_dialog.h" + +#include +#include + + +#ifdef Q_OS_UNIX +#include +#include +#include +#endif + + +namespace LtsWidgets +{ + +LtsGlobalWaitDialog::~LtsGlobalWaitDialog() +{ + delete m_progressDialog; +} + + +LtsGlobalWaitDialog::LtsGlobalWaitDialog() + : m_waitingCount(0) + , m_visible(true) + , m_progressDialog(new LtsProgressDialogHelper(nullptr)) + , m_hideTimer(new QTimer(this)) +{ + m_hideTimer->setSingleShot(true); + m_hideTimer->setInterval(500); + connect(m_hideTimer, &QTimer::timeout, + this, &LtsGlobalWaitDialog::hideIt); +} + + +void LtsGlobalWaitDialog::unarm() +{ + m_armed = false; + hideIt(); +} + + +void LtsGlobalWaitDialog::rearm() +{ + m_armed = true; + setVisible(true); +} + + +void LtsGlobalWaitDialog::setVisible(bool visible) +{ + if (m_waitingCount > 0 && visible) + showIt(); + + if (m_waitingCount < 1 && !visible) + hideIt(); +} + + +void LtsGlobalWaitDialog::showIt() +{ + if (m_progressDialog->isShown()) + return; + + m_hideTimer->stop(); + if (m_armed) + { + m_progressDialog->slotShowWaitForReplyWindow(); + } +} + + +void LtsGlobalWaitDialog::hideIt() +{ + if (!m_progressDialog->isShown()) + return; + + m_hideTimer->stop(); + m_progressDialog->slotStopWaitForReplyWindow(); +} + + +void bt() +{ +#ifdef Q_OS_UNIX + void *callstack[128]; + const int nMaxFrames = sizeof(callstack) / sizeof(callstack[0]); + int nFrames = backtrace(callstack, nMaxFrames); + char **symbols = backtrace_symbols(callstack, nFrames); + + for (int i = 1; i < nFrames; i++) + { + QString address(QStringLiteral("0x%1").arg(QString::number(*(quintptr *)callstack[i], 16))); + QString function; + int offset = -1; + + Dl_info info; + if (dladdr(callstack[i], &info) && info.dli_sname) + { + char *demangled = nullptr; + int status = -1; + if (info.dli_sname[0] == '_') + demangled = abi::__cxa_demangle(info.dli_sname, nullptr, 0, &status); + + function = QString::fromUtf8(status == 0 ? demangled : info.dli_sname == 0 ? symbols[i] : info.dli_sname); + offset = (char *)callstack[i] - (char *)info.dli_saddr; + + free(demangled); + } + else + { + function = QString::fromUtf8(symbols[i]); + } + + + } + + free(symbols); + + + +#endif +} + + +void LtsGlobalWaitDialog::wait(QWidget *parent, const QString &customText) +{ + m_progressDialog->setParentWidget(parent); + if (m_waitingCount < 1 && m_visible) + showIt(); + + setCustomText(customText); + + m_waitingCount++; + + + + +} + + +void LtsGlobalWaitDialog::setCustomText(const QString &customText) +{ + m_progressDialog->setCustomText(customText); +} + + +void LtsGlobalWaitDialog::done() +{ + if (m_waitingCount == 0) + { + + m_hideTimer->start(); + return; + } + + Q_ASSERT(m_waitingCount > 0); + + m_waitingCount--; + + + if (m_waitingCount == 0) + m_hideTimer->start(); +} + +static LtsGlobalWaitDialog *s_gwdInstance = nullptr; +void LtsGlobalWaitDialog::cleanupGwdInstance() +{ + delete s_gwdInstance; + s_gwdInstance = nullptr; +} + +LtsGlobalWaitDialog *LtsGlobalWaitDialog::getInstance() +{ + if (!s_gwdInstance) + { + s_gwdInstance = new LtsGlobalWaitDialog; + qAddPostRoutine(&LtsGlobalWaitDialog::cleanupGwdInstance); + } + return s_gwdInstance; +} +} + + + +Waiter::Waiter(const QString &customText) +{ + LtsWidgets::LtsGlobalWaitDialog::getInstance()->wait(nullptr, customText); +} + + +Waiter::Waiter(LtsWidgets::LtsDialog *parent, const QString &customText) + : m_parent(parent) +{ + m_oldClosableState = m_parent->isClosable(); + m_parent->setClosable(false); + m_parent->setCurrentlyBlocking(true); + auto *wait = new LtsWidgets::LtsOverlayLoading; + wait->setText(customText.isEmpty() ? QObject::tr("Waiting for server reply...") : customText); + m_parent->setOverlay(wait); + m_loadingOverlay = wait; +} + + +Waiter::~Waiter() +{ + if (m_loadingOverlay == nullptr) + { + LtsWidgets::LtsGlobalWaitDialog::getInstance()->done(); + return; + } + + m_parent->setClosable(m_oldClosableState); + m_parent->setCurrentlyBlocking(false); + m_loadingOverlay->deleteLater(); +} + + + diff --git a/Lts/Common/LtsWidgets/lts_global_wait_dialog.h b/Lts/Common/LtsWidgets/lts_global_wait_dialog.h new file mode 100755 index 000000000..67b689981 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_global_wait_dialog.h @@ -0,0 +1,69 @@ + + + +#pragma once + +#include "lts_widgets_export.h" + +#include + + +class QTimer; +class QWidget; + + +namespace LtsWidgets +{ +class LtsDialog; +class LtsOverlayLoading; +class LtsProgressDialogHelper; + + +class LTSWIDGETS_EXPORT LtsGlobalWaitDialog : public QObject +{ + Q_OBJECT + +public: + static LtsGlobalWaitDialog* getInstance(); + void wait(QWidget *parent, const QString &customText = QString()); + void wait(const QString &customText = QString()) { wait(nullptr, customText); } + void done(); + void setVisible(bool visible = true); + void setCustomText(const QString &customText = QString()); + void unarm(); + void rearm(); + +private: + friend struct LtsGlobalWaitDialogHandler; + + LtsGlobalWaitDialog(); + ~LtsGlobalWaitDialog(); + static void cleanupGwdInstance(); + + void showIt(); + void hideIt(); + + int m_waitingCount; + bool m_visible; + bool m_armed { true }; + LtsProgressDialogHelper *m_progressDialog; + QTimer *m_hideTimer; +}; + +} + + + + +class LTSWIDGETS_EXPORT Waiter +{ +public: + explicit Waiter(const QString &customText = {}); + explicit Waiter(LtsWidgets::LtsDialog *parent, const QString &customText = {}); + ~Waiter(); + +private: + LtsWidgets::LtsOverlayLoading *m_loadingOverlay { nullptr }; + LtsWidgets::LtsDialog *m_parent { nullptr }; + bool m_oldClosableState { false }; +}; diff --git a/Lts/Common/LtsWidgets/lts_grid_layout.cpp b/Lts/Common/LtsWidgets/lts_grid_layout.cpp new file mode 100755 index 000000000..f4fd7a88f --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_grid_layout.cpp @@ -0,0 +1,128 @@ + + + +#include "lts_grid_layout.h" +#include + +namespace LtsWidgets +{ + +LtsGridLayout::LtsGridLayout(LtsWidgets::ButtonSize buttonSize, QWidget *parent, int maxColumn) + : QGridLayout(parent) + , m_buttonSize(buttonSize) + , m_defaultMaxColumn(maxColumn) + , m_maxColumn(maxColumn) + , m_colSize(0) + , m_autoSize(maxColumn < 0) +{ + setSizeConstraint(SizeConstraint::SetNoConstraint); + + setVerticalSpacing(1); + setMargin(0); +} + +void LtsGridLayout::add_widget(QWidget *widget) +{ + for (auto it = m_widgetList.cbegin(); it != m_widgetList.cend(); ++it) + { + if (*it == widget) + return; + } + connect(widget, &QWidget::destroyed, this, &LtsGridLayout::onWidgetDestroyed); + addWidgetInternal(widget); + m_widgetList.push_back(widget); +} + +void LtsGridLayout::setAutoSize(bool enable) +{ + m_autoSize = enable; + if (m_autoSize == false) + setMaxColumn(1); + else + { + m_maxColumn = -1; + m_oldSize = QSize(); + m_oldGeo = QRect(); + QResizeEvent e(sizeHint(), sizeHint()); + onParentResize(&e); + setGeometry(QRect(0, 0, sizeHint().width(), sizeHint().height())); + } +} + +void LtsGridLayout::setMaxColumn(int max) +{ + m_maxColumn = max; + m_defaultMaxColumn = max; + + + m_colSize = 0; + + for (QWidget *wid : qAsConst(m_widgetList)) + removeWidget(wid); + + for (QWidget *wid : qAsConst(m_widgetList)) + addWidgetInternal(wid); + +} + +void LtsGridLayout::addWidgetInternal(QWidget *widget) +{ + int current_row = 0; + int current_column = 0; + + while (QLayoutItem *item = itemAtPosition(current_row, current_column)) + { + if (!item->widget()) + { + removeItem(item); + continue; + } + + if (current_column == (m_maxColumn - 1)) + { + current_column = 0; + ++current_row; + } + else + ++current_column; + } + + if (current_row == 0) + m_colSize += 1; + + QGridLayout::addWidget(widget, current_row, current_column); +} + + +void LtsGridLayout::onParentResize(QResizeEvent *e) +{ + if (m_autoSize && m_oldSize != e->size()) + { + bool bigger = (e->size().width() > m_oldSize.width()); + m_oldSize = e->size(); + int newWidth = m_oldSize.width(); + int maxIconsInRow = newWidth / (static_cast(m_buttonSize) + 4); + + if (!bigger && newWidth <= (static_cast(m_buttonSize)*m_colSize - static_cast(m_buttonSize) / 2)) + maxIconsInRow--; + if (m_maxColumn != maxIconsInRow) + setMaxColumn(maxIconsInRow); + } +} + +void LtsGridLayout::onWidgetDestroyed() +{ + QWidget *s = qobject_cast(sender()); + for (auto it = m_widgetList.begin(); it != m_widgetList.end(); ++it) + { + if (*it == s) + { + QGridLayout::removeWidget(s); + m_widgetList.erase(it); + setMaxColumn(m_maxColumn); + break; + } + } +} + +} diff --git a/Lts/Common/LtsWidgets/lts_grid_layout.h b/Lts/Common/LtsWidgets/lts_grid_layout.h new file mode 100755 index 000000000..70949b856 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_grid_layout.h @@ -0,0 +1,61 @@ + + + +#pragma once +#include +#include + +#include "lts_widgets_export.h" +#include "lts_base.h" + +class QWidget; + +namespace LtsWidgets +{ + + +class LTSWIDGETS_EXPORT LtsGridLayout : public QGridLayout +{ + Q_OBJECT +public: + + + explicit LtsGridLayout(LtsWidgets::ButtonSize buttonSize, QWidget *parent = nullptr, int maxColumn = 1); + + + + void add_widget(QWidget *widget); + + + + void setAutoSize(bool enable); + + + + void setMaxColumn(int max); + + +private: + void addWidgetInternal(QWidget *widget); + +public Q_SLOTS: + void onParentResize(QResizeEvent *e); + + +private Q_SLOTS: + void onWidgetDestroyed(); +private: + + LtsWidgets::ButtonSize m_buttonSize; + int m_defaultMaxColumn; + int m_maxColumn; + int m_colSize; + QVector m_widgetList; + QRect m_oldGeo; + QSize m_oldSize; + bool m_autoSize; + + +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_height_plot_widget.cpp b/Lts/Common/LtsWidgets/lts_height_plot_widget.cpp new file mode 100755 index 000000000..2c6f882ec --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_height_plot_widget.cpp @@ -0,0 +1,621 @@ + + + +#include "lts_height_plot_widget.h" + +#include +#include +#include +#include +#include + +#include + + +static inline double getLength(const QPointF &p1, const QPointF &p2) +{ + const double x = p1.x() - p2.x(); + const double y = p1.y() - p2.y(); + return sqrt(x * x + y * y); +} + +class LtsHeightPlotWidget::Private +{ +public: + Private() + : m_length(0) + , m_minElevation(0) + , m_maxElevation(0) + , m_nullX(0) + , m_nullY(0) + , m_scaleX(0) + , m_scaleY(0) + , m_fillBrush(Qt::gray) + , m_axisPen(Qt::black) + , m_mousePosPen(Qt::blue) + , m_markerPen(Qt::magenta) + , iPositionHint(1) + { + m_axisPen.setWidth(1); + m_viewLinePen.setWidth(1); + m_textFont.setPixelSize(10); + m_markerPen.setWidth(2); + m_markerPen.setCapStyle(Qt::RoundCap); + m_mousePosPen.setStyle(Qt::DashDotDotLine); + m_viewLinePen.setStyle(Qt::DashLine); + } + void clearPixmap(); + + double getDataHeight(double pos) const; + QPointF getDataPos(double pos) const; + int findDataPointIdx(const QPointF &point) const; + void clear(); + void recalcData(); + void setData(const QVector > &data); + void setMarks(const QVector &data); + void resize(const QSize &newSize); + void repaint(); + void paintLines(QPainter &p) const; + + QVector< QPair< QPointF, double > > m_data; + QVector< QPointF > m_marks; + QVector< QPair< double, double > > m_heightList; + + double m_length; + double m_minElevation; + double m_maxElevation; + double m_nullX; + double m_nullY; + double m_scaleX; + double m_scaleY; + QPainterPath m_drawPoints; + QBrush m_fillBrush; + QPen m_axisPen; + QPen m_viewLinePen; + QPen m_mousePosPen; + QPen m_markerPen; + QFont m_textFont; + QPointF m_mousePos; + QPointF m_mouseLeftClickPos; + + QSize m_curSize; + QTransform m_trans1; + QTransform m_trans2; + QTransform m_transInverted; + QTransform m_transCombined; + + QPixmap m_pixmap; + + mutable int iPositionHint; +}; + +double LtsHeightPlotWidget::Private::getDataHeight(double pos) const +{ + if (m_heightList.count() <= 1) + return 0; + + if (pos <= 0.) + return m_heightList[0].second; + if (pos >= m_heightList[m_heightList.count() - 1].first) + return m_heightList[m_heightList.count() - 1].second; + + if (iPositionHint >= m_heightList.size()) + iPositionHint = 1; + if (pos <= m_heightList[iPositionHint].first) + { + while (iPositionHint > 1 && pos <= m_heightList[iPositionHint - 1].first) + --iPositionHint; + } + else + { + while (iPositionHint < m_heightList.count() - 1 && pos > m_heightList[iPositionHint].first) + ++iPositionHint; + } + const int iFound = iPositionHint; + + const double x = m_heightList[iFound].first - m_heightList[iFound - 1].first; + const double y = m_heightList[iFound].second - m_heightList[iFound - 1].second; + const double m = y / x; + const double n = m_heightList[iFound].second - m * m_heightList[iFound].first; + return m * pos + n; +} + +QPointF LtsHeightPlotWidget::Private::getDataPos(double pos) const +{ + if (m_heightList.count() <= 1) + return QPointF(); + + if (pos <= 0.) + return m_data[0].first; + if (pos >= m_heightList[m_heightList.count() - 1].first) + return m_data[m_heightList.count() - 1].first; + + if (pos <= m_heightList[iPositionHint].first) + { + while (iPositionHint > 1 && pos <= m_heightList[iPositionHint - 1].first) + --iPositionHint; + } + else + { + while (iPositionHint < m_heightList.count() - 1 && pos > m_heightList[iPositionHint].first) + ++iPositionHint; + } + const int iFound = iPositionHint; + + const double diff = m_heightList[iFound].first - m_heightList[iFound - 1].first; + double percentile = (pos - m_heightList[iFound - 1].first) / diff; + const QPointF &p1 = m_data[iFound - 1].first; + const QPointF p2 = m_data[iFound].first - m_data[iFound - 1].first; + return QPointF(p1.x() + p2.x() * percentile, p1.y() + p2.y() * percentile); +} + +int LtsHeightPlotWidget::Private::findDataPointIdx(const QPointF &point) const +{ + for (int i = 0; i < m_heightList.count() - 1; ++i) + { + + const double x = m_data[i + 1].first.x() - m_data[i].first.x(); + const double y = m_data[i + 1].first.y() - m_data[i].first.y(); + const double m = y / x; + const double n = m_data[i].first.y() - m * m_data[i].first.x(); + + + const double yRes = m * point.x() + n; + if (fabs(yRes - point.y()) < 1.) + { + + const double len = getLength(m_data[ i ].first, point); + if (len <= m_heightList[i].first) + return i; + } + } + return -1; +} + +void LtsHeightPlotWidget::Private::clear() +{ + m_data.clear(); + m_marks.clear(); + m_length = 0; + m_minElevation = 0; + m_maxElevation = 0; + m_scaleX = 0; + m_scaleY = 0; + m_drawPoints = QPainterPath(); + m_heightList.clear(); + m_curSize = QSize(); + + iPositionHint = 1; +} + +void LtsHeightPlotWidget::Private::clearPixmap() +{ + m_pixmap = QPixmap(); +} + +inline double log5(double x) +{ + return log10(x) / log10(5.); +} + +void LtsHeightPlotWidget::Private::recalcData() +{ + m_heightList.clear(); + m_drawPoints = QPainterPath(); + + if (m_data.isEmpty()) + { + clear(); + return; + } + + double length = 0; + double minElevation = m_data.at(0).second; + double maxElevation = minElevation; + + for (int i = 1; i < m_data.count(); ++i) + { + length += getLength(m_data.at(i - 1).first, m_data.at(i).first); + minElevation = std::min(minElevation, m_data.at(i).second); + maxElevation = std::max(maxElevation, m_data.at(i).second); + } + + if (minElevation < -10000.0) minElevation = -10000.0; + if (maxElevation < -10000.0) maxElevation = -10000.0; + + if (minElevation > 10000.0) minElevation = 10000.0; + if (maxElevation > 10000.0) maxElevation = 10000.0; + + if (qFuzzyCompare(minElevation, maxElevation)) + { + minElevation -= 1.0; + maxElevation += 1.0; + } + + + double n2 = pow(5, floor(log5(fabs(minElevation))) - 1); + if (qFuzzyIsNull(n2)) + n2 = 0.1; + + m_minElevation = lrint(floor(minElevation / n2) * n2); + m_maxElevation = lrint(ceil(maxElevation / n2) * n2); + m_scaleY = pow(10, floor(log10(m_maxElevation - m_minElevation)) - 1); + if (m_scaleY < 0.1) + m_scaleY = 0.1; + m_nullY = m_minElevation; + if (m_minElevation < 0 && m_maxElevation > 0) + m_nullY = 0; + + + const double n = pow(10, floor(log10(length))) / 2; + m_length = lrint(ceil(length / n) * n); + m_scaleX = pow(10, floor(log10(length)) - 1); + if (m_scaleX < 0.1) + m_scaleX = 0.1; + m_nullX = 0; + + double len = 0; + m_drawPoints.moveTo(len, m_data.at(0).second); + m_heightList.append(QPair(len, m_data.at(0).second)); + for (int i = 1; i < m_data.count(); ++i) + { + len += getLength(m_data.at(i - 1).first, m_data.at(i).first); + m_drawPoints.lineTo(len, m_data.at(i).second); + + m_heightList.append(QPair(len, m_data.at(i).second)); + + } + m_drawPoints.lineTo(len, m_minElevation); + m_drawPoints.lineTo(0, m_minElevation); + m_drawPoints.setFillRule(Qt::WindingFill); +} + +void LtsHeightPlotWidget::Private::setData(const QVector< QPair< QPointF, double > > &data) +{ + if (data.isEmpty()) + { + clear(); + return; + } + QSize tmp = m_curSize; + clear(); + + m_data = data; + + recalcData(); + resize(tmp); +} + + +void LtsHeightPlotWidget::Private::setMarks(const QVector< QPointF > &data) +{ + m_marks = data; + + recalcData(); + repaint(); +} + +void LtsHeightPlotWidget::Private::resize(const QSize &newSize) +{ + if (newSize == m_curSize) + return; + + m_curSize = newSize; + + const int border = 25; + const double width = m_curSize.width(); + const double height = m_curSize.height(); + + const double minY = m_minElevation - m_scaleY; + const double maxY = m_maxElevation + m_scaleY; + const double wertebereichY = maxY - minY; + + const double minX = 0 - m_scaleX; + const double maxX = m_length + m_scaleX; + const double wertebereichX = maxX - minX; + + m_trans1.reset(); + m_trans2.reset(); + + + m_trans1.translate(10, height); + + m_trans1.scale(1, -1); + + m_trans1.translate(border, border); + + m_trans1.scale(1, -1); + + + m_trans2.scale((width - 2 * border) / (wertebereichX), + (height - 2 * border) / (wertebereichY)); + + m_trans2.translate(0, minY); + + m_trans2.scale(1, -1); + + m_transInverted = m_trans1.inverted() * m_trans2.inverted(); + m_transCombined = m_trans2 * m_trans1; + + repaint(); +} + +void LtsHeightPlotWidget::Private::repaint() +{ + if (!m_curSize.isValid()) + return; + if (!m_heightList.count()) + return; + + QImage img(m_curSize, QImage::Format_ARGB32); + img.fill(Qt::transparent); + QPainter p(&img); + const QPainter::RenderHints hints = QPainter::HighQualityAntialiasing | + QPainter::SmoothPixmapTransform | + QPainter::TextAntialiasing | + QPainter::Antialiasing; + p.setRenderHints(hints); + p.setTransform(m_trans1); + + + p.setPen(QPen(m_fillBrush, 0.1)); + p.setBrush(m_fillBrush); + p.drawPath(m_trans2.map(m_drawPoints)); + p.setRenderHint(QPainter::Antialiasing, false); + + + p.setPen(m_markerPen); + for (int i = 0; i < m_marks.count(); ++i) + { + int posIdx = findDataPointIdx(m_marks.at(i)); + if (posIdx < 0) + continue; + double len = getLength(m_data.at(posIdx).first, m_marks.at(i)); + len += m_heightList.at(posIdx).first; + const double height = getDataHeight(len); + const QLineF l(len, m_minElevation, len, height); + + + p.drawLine(m_trans2.map(l)); + } + + + p.setPen(m_axisPen); + p.setFont(m_textFont); + + + p.drawLine(m_trans2.map(QPointF(m_nullX, m_minElevation - m_scaleY)), + m_trans2.map(QPointF(m_nullX, m_maxElevation + m_scaleY))); + + + for (double i = m_minElevation; i <= m_maxElevation; i += m_scaleY) + { + const qreal y = m_trans2.map(QPointF(0, i)).y(); + p.drawLine(QLineF(-2, y, 2, y)); + } + + + for (double i = m_minElevation; i <= m_maxElevation; i += m_scaleY * 5) + { + const qreal y = m_trans2.map(QPointF(0, i)).y(); + p.drawLine(QLineF(-4, y, 4, y)); + } + + QFontMetricsF fm(m_textFont); + for (double i = m_minElevation; i <= m_maxElevation; i += m_scaleY * 5) + { + if (qFuzzyCompare(i, m_nullY)) + continue; + const qreal y = m_trans2.map(QPointF(m_nullX, i)).y(); + const QPointF p1(-20, y); + const QString str = QString::number(i); + QRectF r = fm.boundingRect(str); + r.setWidth(fm.width(str)); + r.moveTo(p1.x() - r.width() / 2., p1.y() - r.height() / 2.); + p.drawText(r, str, Qt::AlignHCenter | Qt::AlignVCenter); + } + + + p.drawLine(m_trans2.map(QPointF(-m_scaleX, m_nullY)), + m_trans2.map(QPointF(m_scaleX + m_length, m_nullY))); + + + for (double i = m_scaleX; i <= m_length; i += m_scaleX) + { + const QPointF point = m_trans2.map(QPointF(i, m_nullY)); + p.drawLine(QLineF(point.x(), point.y() - 2, point.x(), point.y() + 2)); + } + + + for (double i = 0; i <= m_length; i += m_scaleX * 5) + { + const QPointF point = m_trans2.map(QPointF(i, m_nullY)); + p.drawLine(QLineF(point.x(), point.y() - 4, point.x(), point.y() + 4)); + } + + for (double i = 0; i <= m_length; i += m_scaleX * 5) + { + if (qFuzzyCompare(i, m_nullX)) + continue; + const QPointF point = m_trans2.map(QPointF(i, m_nullY)); + const QPointF p1(point.x(), point.y() + 15); + const QString str = QString::number(i); + QRectF r = fm.boundingRect(str); + r.setWidth(fm.width(str)); + r.moveTo(p1.x() - r.width() / 2., p1.y() - r.height() / 2.); + p.drawText(r, str, Qt::AlignHCenter | Qt::AlignVCenter); + } + p.end(); + m_pixmap = QPixmap::fromImage(img); +} + +void LtsHeightPlotWidget::Private::paintLines(QPainter &p) const +{ + if (m_heightList.count() > 0) + { + + QPointF pos = m_mousePos; + if (pos.x() < 0) + pos.setX(0); + if (pos.x() > m_heightList.at(m_heightList.count() - 1).first) + pos.setX(m_heightList.at(m_heightList.count() - 1).first); + + + + const double curHeight = getDataHeight(pos.x()); + p.setPen(m_mousePosPen); + p.drawLine(m_transCombined.map(QLineF(m_nullX, curHeight, pos.x(), curHeight))); + p.drawLine(m_transCombined.map(QLineF(pos.x(), m_nullY, pos.x(), curHeight))); + + + if (!m_mouseLeftClickPos.isNull()) + { + const double y = curHeight > pos.y() ? curHeight : pos.y(); + p.drawLine(m_transCombined.map(QLineF(m_nullX, y, pos.x(), y))); + p.drawLine(m_transCombined.map(QLineF(pos.x(), m_nullY, pos.x(), y))); + + p.setPen(m_viewLinePen); + const QPointF &c = m_mouseLeftClickPos; + p.drawLine(m_transCombined.map(QLineF(m_nullX, c.y(), c.x(), c.y()))); + p.drawLine(m_transCombined.map(QLineF(c.x(), m_nullY, c.x(), c.y()))); + + + const QLineF line(m_mouseLeftClickPos, QPointF(pos.x(), y)); + p.drawLine(m_transCombined.map(line)); + } + } +} + +LtsHeightPlotWidget::LtsHeightPlotWidget(QWidget *parent) + : QWidget(parent) + , d(new Private) +{ + setMinimumSize(300, 300); + resize(size()); + setMouseTracking(true); +} + +LtsHeightPlotWidget::~LtsHeightPlotWidget() +{ + delete d; +} + +void LtsHeightPlotWidget::clearView() +{ + d->clearPixmap(); + update(); +} + +void LtsHeightPlotWidget::setData(const QVector< QPair< QPointF, double > > &data) +{ + d->setData(data); + update(); +} + +void LtsHeightPlotWidget::setMarks(const QVector &data) +{ + d->setMarks(data); +} + +void LtsHeightPlotWidget::setBackgroundBrush(const QBrush &brush) +{ + QPalette pal = palette(); + pal.setBrush(QPalette::Background, brush); + setPalette(pal); +} + +void LtsHeightPlotWidget::setTextFont(const QFont &f) +{ + d->m_textFont = f; + d->repaint(); +} + +void LtsHeightPlotWidget::setAxisPen(const QPen &pen) +{ + d->m_axisPen = pen; + d->repaint(); +} + +void LtsHeightPlotWidget::setPositionPen(const QPen &pen) +{ + d->m_mousePosPen = pen; +} + +void LtsHeightPlotWidget::setViewLinePen(const QPen &pen) +{ + d->m_viewLinePen = pen; +} + +void LtsHeightPlotWidget::setContourBrush(const QBrush &brush) +{ + d->m_fillBrush = brush; + d->repaint(); +} + +void LtsHeightPlotWidget::setMarkerPen(const QPen &pen) +{ + d->m_markerPen = pen; + d->repaint(); +} + +void LtsHeightPlotWidget::resizeEvent(QResizeEvent *e) +{ + d->resize(size()); + QWidget::resizeEvent(e); +} + +void LtsHeightPlotWidget::paintEvent(QPaintEvent *e) +{ + Q_UNUSED(e); + QPainter p(this); + + const QPainter::RenderHints hints = QPainter::SmoothPixmapTransform | + QPainter::TextAntialiasing | + QPainter::Antialiasing; + p.setRenderHints(hints); + + + p.drawPixmap(0, 0, d->m_pixmap); + + + d->paintLines(p); + p.end(); +} + +void LtsHeightPlotWidget::mouseMoveEvent(QMouseEvent *e) +{ + QWidget::mouseMoveEvent(e); + if (!d->m_heightList.count()) + return; + + const QPointF pos = d->m_transInverted.map(e->localPos()); + emit mousePositionChanged(d->getDataPos(pos.x()), d->getDataHeight(pos.x()), pos.y()); + d->m_mousePos = pos; + update(); +} + +void LtsHeightPlotWidget::mouseReleaseEvent(QMouseEvent *e) +{ + QWidget::mouseReleaseEvent(e); + if (!d->m_heightList.count()) + return; + if (e->button() == Qt::LeftButton) + { + const QPointF &pos = d->m_mousePos; + if (pos.x() < 0) + return; + if (pos.x() > d->m_heightList.at(d->m_heightList.count() - 1).first) + return; + + const double curHeight = d->getDataHeight(pos.x()); + d->m_mouseLeftClickPos = QPointF(pos.x(), curHeight > pos.y() ? curHeight : pos.y()); + emit startLineOfSight(d->getDataPos(pos.x())); + } + else if (e->button() == Qt::RightButton) + { + d->m_mouseLeftClickPos = QPointF(); + emit stopLineOfSight(); + update(); + } +} + diff --git a/Lts/Common/LtsWidgets/lts_height_plot_widget.h b/Lts/Common/LtsWidgets/lts_height_plot_widget.h new file mode 100755 index 000000000..926599f43 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_height_plot_widget.h @@ -0,0 +1,69 @@ + + + +#pragma once + +#include +#include +#include +#include + +#include "lts_widgets_export.h" + +class LTSWIDGETS_EXPORT LtsHeightPlotWidget final : public QWidget +{ + Q_OBJECT +public: + explicit LtsHeightPlotWidget(QWidget *parent = nullptr); + virtual ~LtsHeightPlotWidget(); + + void clearView(); + + + + void setData(const QVector > &data); + + + void setMarks(const QVector &data); + + + + void setBackgroundBrush(const QBrush &brush); + + + void setTextFont(const QFont &f); + + + void setAxisPen(const QPen &pen); + + + void setPositionPen(const QPen &pen); + + + void setViewLinePen(const QPen &pen); + + + void setContourBrush(const QBrush &brush); + + + void setMarkerPen(const QPen &pen); + +Q_SIGNALS: + + + void mousePositionChanged(const QPointF &curPoint, double curHeight, double mouseHeight); + + + void startLineOfSight(const QPointF &startPoint); + + + void stopLineOfSight(); +protected: + void resizeEvent(QResizeEvent *e) override; + void paintEvent(QPaintEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + + class Private; + Private *const d; +}; diff --git a/Lts/Common/LtsWidgets/lts_image_editor.cpp b/Lts/Common/LtsWidgets/lts_image_editor.cpp new file mode 100755 index 000000000..c811a4187 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_image_editor.cpp @@ -0,0 +1,116 @@ + + + +#include "lts_image_editor.h" + +#include +#include +#include +#include +#include +#include + +using namespace LtsWidgets; + + +LtsImageEditor::LtsImageEditor(QWidget *parent) + : QWidget(parent) + , m_isHovered(false) + , m_menuEnabled(true) + , m_bDrawOuterRect(true) +{} + + +void LtsImageEditor::setPixmap(const QPixmap &pixmap) +{ + if (pixmap.isNull()) + m_pixmap = QPixmap(QStringLiteral(":style/noimage.png")); + else + m_pixmap = pixmap; + + update(); + emit imageChanged(); +} + + +void LtsImageEditor::enterEvent(QEvent *) +{ + + m_isHovered = true; + update(); +} + + +void LtsImageEditor::leaveEvent(QEvent *) +{ + + m_isHovered = false; + update(); +} + + +bool LtsImageEditor::event(QEvent *event) +{ + if (event->type() == QEvent::MouseButtonPress && m_menuEnabled) + { + emit signalClicked(); + event->accept(); + m_isHovered = false; + update(); + return true; + } + + return QWidget::event(event); +} + + +void LtsImageEditor::paintEvent(QPaintEvent* event) +{ + QPainter p(this); + + + p.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); + QBrush brush = QColor(QStringLiteral("white")); + + + if (!m_pixmap.isNull()) + { + if (!(m_isHovered && m_menuEnabled)) + { + const QSize s(size()); + const QPixmap pixmap(m_pixmap.scaled(s, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + const int x = qRound((s.width() - pixmap.width()) / 2.); + const int y = qRound((s.height() - pixmap.height()) / 2.); + if (m_bDrawOuterRect) + { + p.setPen(QPen(QColor("gray"), 2)); + p.drawRect(rect()); + } + p.drawPixmap(x, y, pixmap); + event->accept(); + return; + } + } + + + + p.setPen(QPen(QColor("gray"), 2)); + p.setBrush(brush); + p.drawRect(rect()); + + + + if (m_isHovered && m_menuEnabled) + { + p.setPen(QPen(QColor(QStringLiteral("gray")), (size().height() / 48 * 2))); + + double xc = size().width()/2.012; + double yc = size().height()/2.012; + + p.drawLine(QPointF(xc - (xc/2.01f), yc - (yc/3.01f)), QPointF(xc + (xc/2.02f), yc - (yc/3.02f))); + p.drawLine(QPointF(xc - (xc/2.01f), yc - 0.01f), QPointF(xc + (xc/2.02f), yc - 0)); + p.drawLine(QPointF(xc - (xc/2.01f), yc + (yc/3.01f)), QPointF(xc + (xc/2.02f), yc + (yc/3.02f))); + } + + event->accept(); +} diff --git a/Lts/Common/LtsWidgets/lts_image_editor.h b/Lts/Common/LtsWidgets/lts_image_editor.h new file mode 100755 index 000000000..1d482b684 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_image_editor.h @@ -0,0 +1,60 @@ + + + +#pragma once + +#include "lts_widgets_export.h" + +#include + + +namespace LtsWidgets +{ + +class LTSWIDGETS_EXPORT LtsImageEditor : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(QPixmap pixmap READ pixmap WRITE setPixmap) + Q_PROPERTY(bool menuEnabled READ isMenuEnabled WRITE setMenuEnabled) + +public: + explicit LtsImageEditor(QWidget *parent = nullptr); + + + + void setPixmap(const QPixmap &pixmap); + + + + QPixmap pixmap() const { return m_pixmap; } + + + + void setMenuEnabled(bool enabled = true) { m_menuEnabled = enabled; } + bool isMenuEnabled() const { return m_menuEnabled; } + + void setDrawOuterRect(bool bDraw) { m_bDrawOuterRect = bDraw; } + bool drawOuterRect() const { return m_bDrawOuterRect; } + +protected: + void paintEvent(QPaintEvent *event); + void enterEvent(QEvent *); + void leaveEvent(QEvent *); + bool event(QEvent *event); + +private: + QPixmap m_pixmap; + bool m_isHovered; + bool m_menuEnabled; + bool m_bDrawOuterRect; + +signals: + + + void signalClicked(); + + void imageChanged(); +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_info_dialog.cpp b/Lts/Common/LtsWidgets/lts_info_dialog.cpp new file mode 100755 index 000000000..b74298644 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_info_dialog.cpp @@ -0,0 +1,81 @@ + + + +#include "lts_info_dialog.h" +#include "ui_lts_info_dialog.h" + +#include +#include +#include + + +#ifndef LTS_VERSION_MAJOR + #define LTS_VERSION_MAJOR "undefined" +#endif +#ifndef LTS_VERSION_MINOR + #define LTS_VERSION_MINOR "undefined" +#endif +#ifndef LTS_VERSION_PATCH + #define LTS_VERSION_PATCH "undefined" +#endif +#ifndef LTS_VERSION_DATE + #define LTS_VERSION_DATE "undefined" +#endif +#ifndef JENKINS_BUILD_NUMBER + #define JENKINS_BUILD_NUMBER "unknown" +#endif +static const char ver_major[] = LTS_VERSION_MAJOR; +static const char ver_minor[] = LTS_VERSION_MINOR; +static const char ver_patch[] = LTS_VERSION_PATCH; +static const char ver_date[] = LTS_VERSION_DATE; +static const char ver_jenkins[] = JENKINS_BUILD_NUMBER; + +namespace LtsWidgets +{ + +LtsInfoDialog::LtsInfoDialog(QWidget *parent) + : LtsFramelessDialog(parent) + , m_ui(new Ui::LtsInfoDialog) +{ + m_ui->setupUi(this); + + QFont f = font(); + f.setBold(true); + m_ui->lblVersion->setFont(f); + m_ui->lblDate->setFont(f); + + const QString versionString = QStringLiteral("%1.%2-%3 (rev: %4)") + .arg(QString::fromLatin1(ver_major), + QString::fromLatin1(ver_minor), + QString::fromLatin1(ver_jenkins), + QString::fromLatin1(ver_patch)); + m_ui->lblVersion->setText(versionString); + m_ui->lblDate->setText(QString::fromLatin1(ver_date)); + + + QString projectLogoFilename(QStringLiteral(":project/login_logo.png")); + if (!QFile(projectLogoFilename).exists()) + projectLogoFilename = QStringLiteral(":/project/logo.png"); + + QPixmap pixmap(projectLogoFilename); + + if (pixmap.isNull()) + { + qWarning() << "LtsInfoDialog: project logo missing or is not an valid image " << projectLogoFilename; + } + else + { + int max = m_ui->lblProjectLogo->maximumHeight(); + pixmap = pixmap.scaled(999, max * 0.85f, Qt::KeepAspectRatio, Qt::SmoothTransformation); + m_ui->lblProjectLogo->setPixmap(pixmap); + } + connect(m_ui->pbClose, &QPushButton::clicked, this, &LtsInfoDialog::close); +} + + +LtsInfoDialog::~LtsInfoDialog() +{ + delete m_ui; +} + +} diff --git a/Lts/Common/LtsWidgets/lts_info_dialog.h b/Lts/Common/LtsWidgets/lts_info_dialog.h new file mode 100755 index 000000000..4268dcf2a --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_info_dialog.h @@ -0,0 +1,28 @@ + + + +#pragma once + +#include "lts_frameless_dialog.h" +#include "lts_widgets_export.h" + + +namespace Ui +{ +class LtsInfoDialog; +} + +namespace LtsWidgets +{ +class LTSWIDGETS_EXPORT LtsInfoDialog : public LtsFramelessDialog +{ + Q_OBJECT + +public: + explicit LtsInfoDialog(QWidget *parent = nullptr); + ~LtsInfoDialog(); + +private: + Ui::LtsInfoDialog *m_ui; +}; +} diff --git a/Lts/Common/LtsWidgets/lts_info_dialog.ui b/Lts/Common/LtsWidgets/lts_info_dialog.ui new file mode 100755 index 000000000..c45855d1b --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_info_dialog.ui @@ -0,0 +1,135 @@ + + + LtsInfoDialog + + + Operator Menu + + + + + + Rheinmetall Electronics GmbH + + + + + + + + 450 + 200 + + + + + 450 + 200 + + + + + + + :/rde_komplett.png + + + true + + + Qt::AlignCenter + + + + + + + + 0 + 150 + + + + + 16777215 + 100 + + + + <Project Logo> + + + false + + + Qt::AlignCenter + + + + + + + + + Version: + + + + + + + ? + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + Date: + + + + + + + ? + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + + Close + + + + + + + + + + diff --git a/Lts/Common/LtsWidgets/lts_led_widget.cpp b/Lts/Common/LtsWidgets/lts_led_widget.cpp new file mode 100755 index 000000000..8915e52a4 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_led_widget.cpp @@ -0,0 +1,148 @@ + + + +#include "lts_led_widget.h" +#include +#include +#include +namespace LtsWidgets +{ + +void LtsLedWidget::resizeEvent(QResizeEvent *event) +{ + switch (m_aspectHint) { + case AspectHint::NoHint: + { + + + + + + QWidget::resizeEvent(event); + break; + } + case AspectHint::HeightIsWidth: + setMinimumHeight(event->size().width()); + setMaximumHeight(event->size().width()); + setMinimumWidth(0); + setMaximumWidth(65000); + break; + case AspectHint::WidthIsHeight: + setMinimumHeight(0); + setMaximumHeight(65000); + setMinimumWidth(event->size().height()); + setMaximumWidth(event->size().height()); + break; + } +} + + + +static constexpr double GRADIENT_KEY_LOWER = 0.3; +static constexpr double GRADIENT_KEY_UPPER = 0.9; +static constexpr int GRADIENT_VALUE_LOWER = 125; +static constexpr int GRADIENT_VALUE_UPPER = 165; +static constexpr double GRADIENT_CENTER_OFFSET = 5.0; +static constexpr double GRADIENT_FOCALPOINT_OFFSET = 2.7; +static constexpr double PERCENTAGE_OF_BORDER = 0.1; +static constexpr int DARK_FACTOR = 200; + +LtsLedWidget::LtsLedWidget(QWidget *parent, const QColor &oncolor, const QColor &offcolor, bool useGradient, int animtime) + : QWidget(parent) + , m_status(true) + , m_oncolor(oncolor) + , m_offcolor(offcolor) + , m_currentcolor(oncolor) + , m_gradient(useGradient) + , m_animTime(animtime) +{ + setObjectName(QString::fromUtf8("LtsLedWidget")); +} + +void LtsLedWidget::paintEvent(QPaintEvent *pe) +{ + Q_UNUSED(pe); + QPainter p; + p.begin(this); + p.setOpacity(QWidget::isEnabled() ? 1.00 : 0.5); + + int minSize = height() > width() ? width() : height(); + if (m_gradient) + { + QRadialGradient grad(QPointF(width(), height()) / GRADIENT_CENTER_OFFSET, + minSize, + QPointF(width(), height()) / GRADIENT_FOCALPOINT_OFFSET); + grad.setColorAt(GRADIENT_KEY_LOWER, m_currentcolor.lighter(GRADIENT_VALUE_LOWER)); + grad.setColorAt(GRADIENT_KEY_UPPER, m_currentcolor.darker(GRADIENT_VALUE_UPPER)); + p.setBrush(grad); + } + else + p.setBrush(QBrush(m_currentcolor, Qt::SolidPattern)); + + p.setRenderHint(QPainter::Antialiasing); + p.setPen(QPen(Qt::black, PERCENTAGE_OF_BORDER * minSize, Qt::SolidLine, Qt::SquareCap)); + p.drawEllipse(QPointF(width(), height()) / 2.0, + width() / (2.0 * (1.00 + PERCENTAGE_OF_BORDER)), + height() / (2.0 * (1.00 + PERCENTAGE_OF_BORDER))); + p.end(); +} + +void LtsLedWidget::animate() +{ + QPropertyAnimation *anim = new QPropertyAnimation(this, "currentcolor"); + anim->setDuration(m_animTime); + anim->setEasingCurve(QEasingCurve::OutBack); + anim->setStartValue(m_currentcolor); + anim->setEndValue(m_status ? m_oncolor : m_offcolor == Qt::black ? m_oncolor.darker(DARK_FACTOR) : m_offcolor); + anim->start(QPropertyAnimation::DeleteWhenStopped); +} + +void LtsLedWidget::setStatus(bool status) +{ + if (m_status == status) + return; + m_status = status; + animate(); +} + +void LtsLedWidget::setUseGradient(bool useGradient) +{ + if (m_gradient == useGradient) + return; + m_gradient = useGradient; + repaint(); +} + +void LtsLedWidget::setColor(const QColor &oncolor, const QColor &offcolor) +{ + setOnColor(oncolor); + setOffColor(offcolor); +} + +void LtsLedWidget::setOnColor(const QColor &oncolor) +{ + if (m_oncolor == oncolor) + return; + m_oncolor = oncolor; + if (m_status) + animate(); +} + +void LtsLedWidget::setOffColor(const QColor &offcolor) +{ + if (m_offcolor == offcolor) + return; + m_offcolor = offcolor; + if (!m_status) + animate(); +} + +void LtsLedWidget::setCurrentColor(const QColor ¤tcolor) +{ + if (m_currentcolor == currentcolor) + return; + m_currentcolor = currentcolor; + repaint(); +} + +} diff --git a/Lts/Common/LtsWidgets/lts_led_widget.h b/Lts/Common/LtsWidgets/lts_led_widget.h new file mode 100755 index 000000000..c0392c094 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_led_widget.h @@ -0,0 +1,117 @@ + + +#pragma once + +#include +#include "lts_widgets_export.h" + +namespace LtsWidgets +{ + + + +class LTSWIDGETS_EXPORT LtsLedWidget : public QWidget +{ + Q_OBJECT + +public: + enum class AspectHint + { + WidthIsHeight, + NoHint, + HeightIsWidth + }; + Q_ENUM(AspectHint) + + Q_PROPERTY(QColor oncolor READ onColor WRITE setOnColor) + Q_PROPERTY(QColor offcolor READ offColor WRITE setOffColor) + Q_PROPERTY(bool status READ status WRITE setStatus) + Q_PROPERTY(bool usesGradient READ usesGradient WRITE setUseGradient) + Q_PROPERTY(int animTime READ animTime WRITE setAnimTime) + Q_PROPERTY(AspectHint aspectHint READ aspectHint WRITE setAspectHint) + Q_PROPERTY(QColor currentcolor READ currentColor WRITE setCurrentColor DESIGNABLE false) + + + + explicit LtsLedWidget(QWidget *parent = nullptr, const QColor &oncolor = Qt::red, const QColor &offcolor = Qt::black, bool useGradient = true, int animtime = 0); + + void paintEvent(QPaintEvent *pe); + + + + void setAnimTime(int time) { m_animTime = time; } + + + + int animTime() const { return m_animTime; } + + + + bool status() const { return m_status; } + + + + void setUseGradient(bool useGradient); + + + + bool usesGradient() const { return m_gradient; } + + + + void setOnColor(const QColor &oncolor); + + + + QColor onColor() const { return m_oncolor; } + + + + void setOffColor(const QColor &offcolor); + + + + QColor offColor() const { return m_offcolor; } + + + + void setColor(const QColor &oncolor, const QColor &offcolor = Qt::black); + + + + QColor currentColor() const { return m_currentcolor; } + + void resizeEvent(QResizeEvent *event); + + AspectHint aspectHint() const { return m_aspectHint; } + +public slots: + + + void setStatus(bool status); + + void setAspectHint(AspectHint aspectHint) + { + m_aspectHint = aspectHint; + } + +private: + + + void setCurrentColor(const QColor ¤tcolor); + + + + void animate(); + + bool m_status; + QColor m_oncolor; + QColor m_offcolor; + QColor m_currentcolor; + + bool m_gradient; + int m_animTime; + AspectHint m_aspectHint = AspectHint::NoHint; +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_message_dialog.cpp b/Lts/Common/LtsWidgets/lts_message_dialog.cpp new file mode 100755 index 000000000..144dc54e8 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_message_dialog.cpp @@ -0,0 +1,133 @@ +#include "lts_message_dialog.h" +#include "lts_message_dialog_helper.h" + +#include +#include +#include +#include +#include + + +namespace LtsWidgets +{ +namespace LtsMessageDialog +{ +Button showMessage(QWidget* parent, + const QString& message, + const QString &title, + const Behaviour behaviour, + const Buttons buttons, + const QHash &buttonTexts) +{ + static QAtomicInt s_used = 0; + + if (s_used) + qDebug() << "LtsMessageDialog: showMessage called while showMessage is in its Event Loop - do not open more than one Dialog at once !!"; + + s_used = 1; + + LtsMessageDialogHelper *dialog = new LtsMessageDialogHelper(); + Button result = dialog->showMessage(parent, message, title, behaviour, buttons, buttonTexts); + dialog->deleteLater(); + + s_used = 0; + + return result; +} + +Button askApplyDiscardCancel(QWidget *parent) +{ + const QString question = QCoreApplication::translate("LtsDialog", + "
" + "Apply - apply changes and close.
" + "Discard - discard changes and close.
" + "Cancel - do not close."); + const QString title = QCoreApplication::translate("LtsDialog", "Unapplied changes made. Apply the changes?"); + return LtsMessageDialog::showMessage(parent, + question, + title, + Behaviour::Modal, + Button::Apply | Button::Discard | Button::Cancel); +} + + +} + + + + +LtsMessageDialogNew::LtsMessageDialogNew(QWidget *parent, const QString &message, const QString &title, const Behaviour behaviour, const Buttons buttons, const QHash &buttonTexts, bool deleteOnClose) +{ + m_dialog = new LtsFramelessDialog(parent, behaviour); + + if (deleteOnClose) + m_dialog->setAttribute(Qt::WA_DeleteOnClose); + + connect(m_dialog, &LtsFramelessDialog::destroyed, + this, &LtsMessageDialogNew::deleteLater); + + QVBoxLayout *mainLayout = new QVBoxLayout(m_dialog); + + QString text; + if (!title.isEmpty()) + { + text = QStringLiteral("%1

").arg(title); + } + + text += message; + + QLabel *messageLabel = new QLabel(text, m_dialog); + messageLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); + mainLayout->addWidget(messageLabel); + + QHBoxLayout *buttonLayout = new QHBoxLayout; + buttonLayout->setMargin(0); + + + + + if (buttons.testFlag(Button::Ok)) + connect(addButton(buttonTexts.value(Button::Ok, tr("Ok")), buttonLayout, m_dialog, Button::Ok, parent), &QPushButton::clicked, this, &LtsMessageDialogNew::ok); + + if (buttons.testFlag(Button::Yes)) + connect(addButton(buttonTexts.value(Button::Yes, tr("Yes")), buttonLayout, m_dialog, Button::Yes, parent), &QPushButton::clicked, this, &LtsMessageDialogNew::yes); + + if (buttons.testFlag(Button::No)) + connect(addButton(buttonTexts.value(Button::No, tr("No")), buttonLayout, m_dialog, Button::No, parent), &QPushButton::clicked, this, &LtsMessageDialogNew::no); + + if (buttons.testFlag(Button::Apply)) + connect(addButton(buttonTexts.value(Button::Apply, tr("Apply")), buttonLayout, m_dialog, Button::Apply, parent), &QPushButton::clicked, this, &LtsMessageDialogNew::apply); + + if (buttons.testFlag(Button::Discard)) + connect(addButton(buttonTexts.value(Button::Discard, tr("Discard")), buttonLayout, m_dialog, Button::Discard, parent), &QPushButton::clicked, this, &LtsMessageDialogNew::discard); + + if (buttons.testFlag(Button::Cancel)) + connect(addButton(buttonTexts.value(Button::Cancel, tr("Cancel")), buttonLayout, m_dialog, Button::Cancel, parent), &QPushButton::clicked, this, &LtsMessageDialogNew::cancel); + + QWidget *buttonLayoutWidget = new QWidget; + buttonLayoutWidget->setLayout(buttonLayout); + + mainLayout->addWidget(buttonLayoutWidget); + + m_dialog->show(); + m_dialog->raise(); +} + + +QPushButton* LtsMessageDialogNew::addButton(const QString &text, QHBoxLayout *thatLayout, LtsFramelessDialog *dialog, const Button &button, QWidget *parent) +{ + QPushButton* btn = new QPushButton(text, parent); + connect(btn, &QPushButton::clicked, dialog, &LtsFramelessDialog::accept); + thatLayout->addWidget(btn); + + if (button == Button::Ok || + button == Button::Cancel) + { + btn->setDefault(true); + } + + return btn; +} + + +} diff --git a/Lts/Common/LtsWidgets/lts_message_dialog.h b/Lts/Common/LtsWidgets/lts_message_dialog.h new file mode 100755 index 000000000..ef64383eb --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_message_dialog.h @@ -0,0 +1,86 @@ +#pragma once + +#include "lts_widgets_export.h" +#include "lts_frameless_dialog.h" + +#include +#include + + +class QWidget; +class QHBoxLayout; + + +namespace LtsWidgets +{ + + + +enum class Button +{ + UNKNOWN = 0, + Ok = 1, + Yes = 2, + No = 4, + Cancel = 8, + + Apply = 16, + Discard = 32, +}; +Q_DECLARE_FLAGS(Buttons, Button) +inline uint qHash(LtsWidgets::Button b) +{ + return ::qHash(static_cast(b)); +} + + + +namespace LtsMessageDialog +{ + + +LTSWIDGETS_EXPORT Button showMessage(QWidget* parent, + const QString &message, + const QString &title = QString(), + const Behaviour behaviour = Behaviour::Modal, + const Buttons buttons = Button::Ok, + const QHash &buttonTexts = QHash()); +LTSWIDGETS_EXPORT Button askApplyDiscardCancel(QWidget *parent); +} + + + + + +class LTSWIDGETS_EXPORT LtsMessageDialogNew : public QObject +{ + Q_OBJECT + +public: + explicit LtsMessageDialogNew(QWidget* parent, + const QString &message, + const QString &title = QString(), + const Behaviour behaviour = Behaviour::Modal, + const Buttons buttons = Button::Ok, + const QHash &buttonTexts = QHash(), + bool deleteOnClose = true ); + +private: + LtsFramelessDialog *m_dialog; + + QPushButton *addButton(const QString &text, QHBoxLayout *thatLayout, LtsFramelessDialog *dialog, const Button &button, QWidget *parent); + + +signals: + void ok(); + void yes(); + void no(); + void apply(); + void discard(); + void cancel(); + +}; + +} + +Q_DECLARE_OPERATORS_FOR_FLAGS(LtsWidgets::Buttons) diff --git a/Lts/Common/LtsWidgets/lts_message_dialog_helper.cpp b/Lts/Common/LtsWidgets/lts_message_dialog_helper.cpp new file mode 100755 index 000000000..edc56ebf4 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_message_dialog_helper.cpp @@ -0,0 +1,155 @@ +#include "lts_message_dialog_helper.h" + +#include +#include +#include +#include +#include + + +namespace LtsWidgets +{ + +LtsMessageDialogHelper::LtsMessageDialogHelper(QObject *parent) + : QObject(parent) + , m_result(Button::Cancel) + , m_dialog(nullptr) +{} + + +Button LtsMessageDialogHelper::showMessage(QWidget *parent, + const QString &message, + const QString &title, + Behaviour behaviour, + Buttons buttons, + const QHash &buttonTexts) +{ + m_dialog = new LtsFramelessDialog(parent, behaviour); + + m_dialog->setAttribute(Qt::WA_DeleteOnClose); + + QVBoxLayout *mainLayout = new QVBoxLayout(m_dialog); + + QString text; + if (!title.isEmpty()) + { + text = QStringLiteral("%1

").arg(title); + } + + text += message; + + QLabel *messageLabel = new QLabel(text, m_dialog); + messageLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); + mainLayout->addWidget(messageLabel); + + QHBoxLayout *buttonLayout = new QHBoxLayout; + buttonLayout->setMargin(0); + + + connect(m_dialog, &LtsFramelessDialog::rejected, this, &LtsMessageDialogHelper::slotCancel); + + if (buttons.testFlag(Button::Ok)) + connect(addButton(buttonTexts.value(Button::Ok, tr("Ok")), buttonLayout, m_dialog, Button::Ok, parent), + &QPushButton::clicked, this, &LtsMessageDialogHelper::slotOkay); + + if (buttons.testFlag(Button::Yes)) + connect(addButton(buttonTexts.value(Button::Yes, tr("Yes")), buttonLayout, m_dialog, Button::Yes, parent), + &QPushButton::clicked, this, &LtsMessageDialogHelper::slotYes); + + if (buttons.testFlag(Button::No)) + connect(addButton(buttonTexts.value(Button::No, tr("No")), buttonLayout, m_dialog, Button::No, parent), + &QPushButton::clicked, this, &LtsMessageDialogHelper::slotNo); + + if (buttons.testFlag(Button::Apply)) + connect(addButton(buttonTexts.value(Button::Apply, tr("Apply")), buttonLayout, m_dialog, Button::Apply, parent), + &QPushButton::clicked, this, &LtsMessageDialogHelper::slotApply); + + if (buttons.testFlag(Button::Discard)) + connect(addButton(buttonTexts.value(Button::Discard, tr("Discard")), buttonLayout, m_dialog, Button::Discard, parent), + &QPushButton::clicked, this, &LtsMessageDialogHelper::slotDiscard); + + if (buttons.testFlag(Button::Cancel)) + connect(addButton(buttonTexts.value(Button::Cancel, tr("Cancel")), buttonLayout, m_dialog, Button::Cancel, parent), + &QPushButton::clicked, this, &LtsMessageDialogHelper::slotCancel); + + QWidget *buttonLayoutWidget = new QWidget; + buttonLayoutWidget->setLayout(buttonLayout); + + mainLayout->addWidget(buttonLayoutWidget); + + m_dialog->show(); + m_dialog->raise(); + + + QEventLoop loop; + connect(this, &LtsMessageDialogHelper::signalQuitEventLoop, &loop, &QEventLoop::quit, Qt::QueuedConnection); + loop.exec(); + + return m_result; +} + + +void LtsMessageDialogHelper::slotOkay() +{ + m_dialog->hide(); + m_result = Button::Ok; + emit signalQuitEventLoop(); +} + + +void LtsMessageDialogHelper::slotYes() +{ + m_dialog->hide(); + m_result = Button::Yes; + emit signalQuitEventLoop(); +} + + +void LtsMessageDialogHelper::slotNo() +{ + m_dialog->hide(); + m_result = Button::No; + emit signalQuitEventLoop(); +} + + +void LtsMessageDialogHelper::slotApply() +{ + m_dialog->hide(); + m_result = Button::Apply; + emit signalQuitEventLoop(); +} + + +void LtsMessageDialogHelper::slotDiscard() +{ + m_dialog->hide(); + m_result = Button::Discard; + emit signalQuitEventLoop(); +} + + +void LtsMessageDialogHelper::slotCancel() +{ + m_dialog->hide(); + m_result = Button::Cancel; + emit signalQuitEventLoop(); +} + + +QPushButton* LtsMessageDialogHelper::addButton(const QString &text, QHBoxLayout *thatLayout, LtsFramelessDialog *dialog, const Button &button, QWidget *parent) +{ + QPushButton* btn = new QPushButton(text, parent); + connect(btn, &QPushButton::clicked, dialog, &LtsFramelessDialog::accept); + thatLayout->addWidget(btn); + + if (button == Button::Ok || + button == Button::Cancel) + { + btn->setDefault(true); + } + + return btn; +} + +} diff --git a/Lts/Common/LtsWidgets/lts_message_dialog_helper.h b/Lts/Common/LtsWidgets/lts_message_dialog_helper.h new file mode 100755 index 000000000..0aaf73a4e --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_message_dialog_helper.h @@ -0,0 +1,52 @@ +#pragma once + +#include "lts_frameless_dialog.h" +#include "lts_message_dialog.h" + +#include +#include +#include + + +class QPushButton; +class QHBoxLayout; + + +namespace LtsWidgets +{ + + + +class LtsMessageDialogHelper : public QObject +{ + Q_OBJECT + +public: + explicit LtsMessageDialogHelper(QObject *parent = nullptr); + ~LtsMessageDialogHelper() = default; + + Button showMessage(QWidget *parent, + const QString &message, + const QString &title, + Behaviour behaviour, + LtsWidgets::Buttons buttons, + const QHash &buttonTexts); + +private: + QPushButton *addButton(const QString &text, QHBoxLayout *thatLayout, LtsFramelessDialog *dialog, const Button &button, QWidget *parent); + + Button m_result; + LtsFramelessDialog *m_dialog; + +private slots: + void slotOkay(); + void slotYes(); + void slotNo(); + void slotCancel(); + void slotApply(); + void slotDiscard(); + +signals: + void signalQuitEventLoop(); +}; +} diff --git a/Lts/Common/LtsWidgets/lts_msec_date_time_edit.cpp b/Lts/Common/LtsWidgets/lts_msec_date_time_edit.cpp new file mode 100755 index 000000000..8d9316a77 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_msec_date_time_edit.cpp @@ -0,0 +1,114 @@ +#include "lts_msec_date_time_edit.h" + + +namespace LtsWidgets +{ + +LtsMSecDateTimeEdit::LtsMSecDateTimeEdit(QWidget *parent) + : QDateTimeEdit(parent) +{ + setWrapping(true); + setDisplayFormat(QLatin1String("dd.MM.yy HH:mm:ss.zzz")); + connect(this, &LtsMSecDateTimeEdit::editingFinished, this, &LtsMSecDateTimeEdit::onEditingFinished); +} + +LtsMSecDateTimeEdit::LtsMSecDateTimeEdit(const QDateTime &dt, QWidget *parent) + : QDateTimeEdit(dt, parent) +{ + setWrapping(true); + setDisplayFormat(QLatin1String("dd.MM.yy HH:mm:ss.zzz")); + connect(this, &LtsMSecDateTimeEdit::editingFinished, this, &LtsMSecDateTimeEdit::onEditingFinished); +} + +LtsMSecDateTimeEdit::LtsMSecDateTimeEdit(const QDate &d, QWidget *parent) + : QDateTimeEdit(d, parent) +{ + setWrapping(true); + setDisplayFormat(QLatin1String("dd.MM.yy HH:mm:ss.zzz")); + connect(this, &LtsMSecDateTimeEdit::editingFinished, this, &LtsMSecDateTimeEdit::onEditingFinished); +} + +LtsMSecDateTimeEdit::LtsMSecDateTimeEdit(const QTime &t, QWidget *parent) + : QDateTimeEdit(t, parent) +{ + setWrapping(true); + setDisplayFormat(QLatin1String("dd.MM.yy HH:mm:ss.zzz")); + connect(this, &LtsMSecDateTimeEdit::editingFinished, this, &LtsMSecDateTimeEdit::onEditingFinished); +} + +void LtsMSecDateTimeEdit::stepBy(int steps) +{ + switch (currentSection()) { + case QDateTimeEdit::MonthSection: + QDateTimeEdit::setDateTime(dateTime().addMonths(steps)); + break; + case QDateTimeEdit::YearSection: + QDateTimeEdit::setDateTime(dateTime().addYears(steps)); + break; + case QDateTimeEdit::DaySection: + QDateTimeEdit::setDateTime(dateTime().addDays(steps)); + break; + case QDateTimeEdit::HourSection: + QDateTimeEdit::setDateTime(dateTime().addSecs(steps * 3600)); + break; + case QDateTimeEdit::MinuteSection: + QDateTimeEdit::setDateTime(dateTime().addSecs(steps * 60)); + break; + case QDateTimeEdit::SecondSection: + QDateTimeEdit::setDateTime(dateTime().addSecs(steps)); + break; + case QDateTimeEdit::MSecSection: + QDateTimeEdit::setDateTime(dateTime().addMSecs(steps * m_msecStepSize)); + break; + case QDateTimeEdit::NoSection: + case QDateTimeEdit::AmPmSection: + case QDateTimeEdit::TimeSections_Mask: + case QDateTimeEdit::DateSections_Mask: + default: + break; + } +} + +qint64 LtsMSecDateTimeEdit::msecsSinceEpoch() const +{ + return dateTime().toMSecsSinceEpoch() - (m_timezoneHoursOffset * 3600 * 1000); +} + +void LtsMSecDateTimeEdit::setTimezoneHoursOffset(int timezoneHoursOffset) +{ + qint64 b4 = msecsSinceEpoch(); + m_timezoneHoursOffset = timezoneHoursOffset; + setMsecsSinceEpoch(b4); +} + +void LtsMSecDateTimeEdit::setDateTime(const QDateTime &dateTime) +{ + + setMsecsSinceEpoch(QDateTime::fromMSecsSinceEpoch(dateTime.toMSecsSinceEpoch(), Qt::TimeSpec::OffsetFromUTC, m_timezoneHoursOffset * 3600).toMSecsSinceEpoch()); +} + +void LtsMSecDateTimeEdit::setMsecsSinceEpoch(qint64 msecs) +{ + QDateTimeEdit::setDateTime(QDateTime::fromMSecsSinceEpoch(msecs, Qt::TimeSpec::OffsetFromUTC, m_timezoneHoursOffset * 3600)); +} + +int LtsMSecDateTimeEdit::msecStepSize() const +{ + return m_msecStepSize; +} + +void LtsMSecDateTimeEdit::setMsecStepSize(int msecStepSize) +{ + m_msecStepSize = msecStepSize; +} + +void LtsMSecDateTimeEdit::onEditingFinished() +{ + qint64 val = msecsSinceEpoch(); + if (val % m_msecStepSize == 0) + return; + val = (val + (m_msecStepSize/2) ) / m_msecStepSize * m_msecStepSize; + setMsecsSinceEpoch(val); +} + +} diff --git a/Lts/Common/LtsWidgets/lts_msec_date_time_edit.h b/Lts/Common/LtsWidgets/lts_msec_date_time_edit.h new file mode 100755 index 000000000..9fc1b6e9e --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_msec_date_time_edit.h @@ -0,0 +1,41 @@ +#pragma once + +#include + +#include "lts_widgets_export.h" + + +namespace LtsWidgets { + +class LTSWIDGETS_EXPORT LtsMSecDateTimeEdit : public QDateTimeEdit +{ + Q_OBJECT + + Q_PROPERTY(int msecStepSize READ msecStepSize WRITE setMsecStepSize) + +public: + explicit LtsMSecDateTimeEdit(QWidget *parent = nullptr); + explicit LtsMSecDateTimeEdit(const QDateTime &dt, QWidget *parent = nullptr); + explicit LtsMSecDateTimeEdit(const QDate &d, QWidget *parent = nullptr); + explicit LtsMSecDateTimeEdit(const QTime &t, QWidget *parent = nullptr); + + qint64 msecsSinceEpoch() const; + int timezoneHoursOffset() const { return m_timezoneHoursOffset; } + void stepBy(int steps) override; + int msecStepSize() const; + +public slots: + void setDateTime(const QDateTime &dateTime); + void setMsecsSinceEpoch(qint64 msecs); + void setMsecStepSize(int msecStepSize); + void setTimezoneHoursOffset(int timezoneHoursOffset); + +private slots: + void onEditingFinished(); + +private: + int m_msecStepSize = 10; + int m_timezoneHoursOffset = 0; +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_multi_range_spinbox.cpp b/Lts/Common/LtsWidgets/lts_multi_range_spinbox.cpp new file mode 100755 index 000000000..80d4af2d0 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_multi_range_spinbox.cpp @@ -0,0 +1,313 @@ + + + +#include "lts_multi_range_spinbox.h" + +#include +#include +#include + +namespace LtsWidgets +{ + +LtsMultiRangeSpinBox::LtsMultiRangeSpinBox(QWidget *parent) + : QSpinBox(parent) +{ + m_curVal = minimum(); + +} + + + + +QValidator::State LtsMultiRangeSpinBox::validate(QString &input, int &pos) const +{ + + + + + + + + + + + + + + + + + return QValidator::Acceptable; +} + +void LtsMultiRangeSpinBox::fixup(QString &str) const +{ + + + + + + + + + + + + + + + + +} + +QValidator::State LtsMultiRangeSpinBox::validateAll(QString &input, int &pos) +{ + for(QPair range : qAsConst(m_rangesList)) + { + QIntValidator validator(range.first, range.second); + QValidator::State valState = validator.validate(input, pos); + if (valState == QValidator::Acceptable) + { + return valState; + } + } + return QValidator::Invalid; + + +} + + +QVector> LtsWidgets::LtsMultiRangeSpinBox::sortRanges(const QVector > &ranges) +{ + QVector> rangesEditable = ranges; + QVector> result; + std::sort(rangesEditable.begin(), rangesEditable.end()); + QVector>::iterator it = rangesEditable.begin(); + QPair current = *(it)++; + while (it != rangesEditable.end()) + { + if (current.second > it->first) + current.second = std::max(current.second, it->second); + else + { + result.push_back(current); + current = *(it); + } + ++it; + } + result.push_back(current); + return result; +} + +void LtsMultiRangeSpinBox::setRanges(const QVector> &ranges) +{ + if(ranges.empty()) + return; + + m_rangesList = sortRanges(ranges); + + QSpinBox::setMaximum(m_rangesList.last().second); + QSpinBox::setMinimum(m_rangesList.first().first); + + QStringList rangeStrings; + rangeStrings.reserve(m_rangesList.size()); + for (const QPair &range : qAsConst(m_rangesList)) + rangeStrings << tr("%1-%2%3").arg(range.first).arg(range.second).arg(suffix()); + setToolTip(tr("Ranges:\n%1").arg(rangeStrings.join(QStringLiteral(",\n")))); +} + +void LtsMultiRangeSpinBox::setRange(int min, int max) +{ + setRanges({{min,max}}); +} + +void LtsMultiRangeSpinBox::setMinimum(int min) +{ + auto ranges = m_rangesList; + ranges.first().first = min; + setRanges(ranges); +} + +void LtsMultiRangeSpinBox::setMaximum(int max) +{ + auto ranges = m_rangesList; + ranges.last().second = max; + setRanges(ranges); +} + +int LtsMultiRangeSpinBox::fixupValue(int value) const +{ + + int nearestValue = INT_MAX; + for(QPair range : m_rangesList) + { + if(std::abs(range.first - value) < std::abs(nearestValue - value)) + nearestValue = range.first; + if(std::abs(range.second - value) < std::abs(nearestValue - value)) + nearestValue = range.second; + } + value = nearestValue; + int buffer = value; + + int remainder = (value - minimum()) % singleStep(); + + if (2 * remainder > singleStep()) + { + value += singleStep(); + } + value -= remainder; + + if(buffer + singleStep() > value + singleStep()) + { + value+= singleStep(); + } + + if (value > maximum()) + { + value -= singleStep(); + } + + return value; +} + +int LtsMultiRangeSpinBox::fixupValue(int value, bool higher) const +{ + + int nearestValue = INT_MAX; + for(QPair range : m_rangesList) + { + if(std::abs(range.first - value) < std::abs(nearestValue - value)) + { + if(higher) + { + if(range.second < value) + continue; + + } else + if(range.second > value) + continue; + + nearestValue = range.first; + } + if(std::abs(range.second - value) < std::abs(nearestValue - value)) + { + if(higher) + { + if(range.second < value) + continue; + + } else + if(range.second > value) + continue; + + nearestValue = range.second; + } + } + + value = nearestValue; + int curVal = value; + + int remainder = (value - minimum()) % singleStep(); + + if (2 * remainder > singleStep()) + { + value += singleStep(); + } + value -= remainder; + + if(curVal + singleStep() > value + singleStep()) + { + value+= singleStep(); + } + if (value > maximum()) + { + value -= singleStep(); + } + + return value; +} + + +void LtsMultiRangeSpinBox::focusOutEvent(QFocusEvent *event) +{ + QSpinBox::focusOutEvent(event); + QString value = QString::number(this->value()); + int length = value.length(); + QValidator::State valState = validateAll(value, length); + + if(valState == QValidator::State::Invalid) + { + this->setValue(fixupValue(value.toInt())); + } +} + +void LtsMultiRangeSpinBox::wheelEvent(QWheelEvent *event) +{ + QSpinBox::wheelEvent(event); + + QString valueStr = QString::number(this->value()); + int length = valueStr.length(); + QValidator::State valState = validateAll(valueStr, length); + + if(valState == QValidator::State::Invalid) + { + + + + if(this->value() < m_curVal) + { + m_curVal = fixupValue(this->value(), false); + setValue(m_curVal); + } + else + { + m_curVal = fixupValue(this->value(), true); + setValue(m_curVal); + } + } +} + +void LtsMultiRangeSpinBox::mousePressEvent(QMouseEvent *event) +{ + QSpinBox::mousePressEvent(event); + QString valueStr = QString::number(this->value()); + int length = valueStr.length(); + QValidator::State valState = validateAll(valueStr, length); + + if(valState == QValidator::State::Invalid) + { + + + + + if(this->value() < m_curVal) + { + m_curVal = fixupValue(this->value(), false); + setValue(m_curVal); + } + else + { + m_curVal = fixupValue(this->value(), true); + setValue(m_curVal); + } + } + + +} + +void LtsMultiRangeSpinBox::keyPressEvent(QKeyEvent *event) +{ + QSpinBox::keyPressEvent(event); + QString value = QString::number(this->value()); + int length = value.length(); + QValidator::State valState = validateAll(value, length); + + if(valState == QValidator::State::Invalid) + { + setValue(fixupValue(value.toInt())); + } +} + + +} diff --git a/Lts/Common/LtsWidgets/lts_multi_range_spinbox.h b/Lts/Common/LtsWidgets/lts_multi_range_spinbox.h new file mode 100755 index 000000000..9559e50cc --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_multi_range_spinbox.h @@ -0,0 +1,52 @@ + + + +#pragma once + +#include "lts_widgets_export.h" +#include +#include + +namespace LtsWidgets +{ + +class LTSWIDGETS_EXPORT LtsMultiRangeSpinBox : public QSpinBox +{ + Q_OBJECT + +public: + explicit LtsMultiRangeSpinBox(QWidget *parent = nullptr); + ~LtsMultiRangeSpinBox() = default; + + +public slots: + void setRanges(const QVector> &ranges); + void setRange(int min, int max); + void setMinimum(int min); + void setMaximum(int max); + +protected: + QValidator::State validate(QString &input, int &pos) const override; + void fixup(QString &str) const override; + void focusOutEvent(QFocusEvent *event) override; + void wheelEvent(QWheelEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + +private: + static QVector> sortRanges(const QVector > &ranges); + + QValidator::State validateAll(QString &input, int &pos); + + int fixupValue(int value) const; + int fixupValue(int value, bool higher) const; +private: + QVector> m_rangesList; + + int m_curVal = minimum(); + QRegExp m_regex; +}; + + + +} diff --git a/Lts/Common/LtsWidgets/lts_notification_box.cpp b/Lts/Common/LtsWidgets/lts_notification_box.cpp new file mode 100755 index 000000000..f39dbaefd --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_notification_box.cpp @@ -0,0 +1,198 @@ +#include "lts_notification_box.h" +#include "ui_lts_notification_box.h" + +#include +#include +#include +#include +#include +#include +#ifdef NOTIBOX_DEBUG +#include +#endif + + +namespace LtsWidgets +{ +const int LtsNotificationBox::margin = 14; + +LtsNotificationBox::LtsNotificationBox(QWidget *parent) + : QWidget(parent) + , m_ui(new Ui::NotiBox) +{ + m_ui->setupUi(this); + m_ui->lblText->setText({}); + + connect(m_ui->lblText, &QLabel::linkActivated, + this, [this](const QString &link) + { + emit signalLinkClicked(link); + }); + + connect(m_ui->tbClose, &QToolButton::clicked, + this, &LtsNotificationBox::signalClose); + connect(m_ui->tbAck, &QToolButton::clicked, + this, &LtsNotificationBox::signalAck); + + setObjectName(QStringLiteral("NotiBox")); + + QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect(this); + shadow->setOffset(0, 0); + shadow->setBlurRadius(margin*2); + shadow->setColor(QColor(25, 25, 25)); + + setGraphicsEffect(shadow); +} + + +void LtsNotificationBox::message(const QString &title, const QString &text, MessageType type) +{ + static QString rgbBaseString(QStringLiteral("%1, %2, %3")); + static QString closeDefault(QStringLiteral("close.svg")); + static QString closeDark(QStringLiteral("close_dark.svg")); + static QString ackDefault(QStringLiteral("ack.svg")); + static QString ackDark(QStringLiteral("ack_dark.svg")); + static QString lightTextColor(QStringLiteral("fbfbfb")); + static QString darkTextColor(QStringLiteral("2c3642")); + + static QColor errorStart(254, 45, 102); + static QColor errorEnd(255, 92, 61); + + static QColor successStart(43, 167, 70); + static QColor successEnd(130, 180, 45); + + static QColor infoStart(74, 74, 74); + static QColor infoEnd(43, 43, 43); + + static QColor warningStart(253, 151, 4); + static QColor warningEnd(253, 202, 0); + + + QColor start; + QColor end; + QString close; + QString closeHover; + QString ack; + QString ackHover; + QString textColor; + + switch (type) + { + case MessageType::Error: + { + start = errorStart; + end = errorEnd; + close = closeDefault; + closeHover = closeDark; + ack = ackDefault; + ackHover = ackDark; + textColor = lightTextColor; + m_ui->lblImage->setPixmap(QPixmap(QStringLiteral(":/error.svg"))); + + break; + } + + case MessageType::Success: + { + start = successStart; + end = successEnd; + close = closeDefault; + closeHover = closeDark; + ack = ackDefault; + ackHover = ackDark; + textColor = lightTextColor; + m_ui->lblImage->setPixmap(QPixmap(QStringLiteral(":/success.svg"))); + + break; + } + + case MessageType::Info: + { + start = infoStart; + end = infoEnd; + close = closeDefault; + closeHover = closeDark; + ack = ackDefault; + ackHover = ackDark; + textColor = lightTextColor; + m_ui->lblImage->setPixmap(QPixmap(QStringLiteral(":/info.svg"))); + + break; + } + + case MessageType::Warning: + { + start = warningStart; + end = warningEnd; + close = closeDark; + closeHover = closeDefault; + ack = ackDark; + ackHover = ackDefault; + textColor = darkTextColor; + m_ui->lblImage->setPixmap(QPixmap(QStringLiteral(":/warning.svg"))); + + break; + } + } + + QStringList css; + + css << QStringLiteral("#NotiBox { background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop: 0 rgb(%1), stop: 1 rgb(%2)); border: 1px solid rgb(%3); border-radius: 8px; margin: %4px }"). + arg(rgbBaseString.arg(start.red()).arg(start.green()).arg(start.blue()), + rgbBaseString.arg(end.red()).arg(end.green()).arg(end.blue()), + rgbBaseString.arg(end.red()).arg(end.green()).arg(end.blue())). + arg(margin); + + css << QStringLiteral("QToolButton#tbClose { border-image: url(:/%1) 0 0 0 0 stretch stretch; border-width: 0px; background-color: transparent; }").arg(close); + css << QStringLiteral("QToolButton#tbClose:hover { border-image: url(:/%1) 0 0 0 0 stretch stretch; border-width: 0px; background-color: transparent; }").arg(closeHover); + + css << QStringLiteral("QToolButton#tbAck { border-image: url(:/%1) 0 0 0 0 stretch stretch; border-width: 0px; background-color: transparent; }").arg(ack); + css << QStringLiteral("QToolButton#tbAck:hover { border-image: url(:/%1) 0 0 0 0 stretch stretch; border-width: 0px; background-color: transparent; }").arg(ackHover); + +#ifdef NOTIBOX_DEBUG + qWarning() << "css" << css; + + qWarning() << "exists?" << QFile::exists(QStringLiteral(":/close.svg")); + qWarning() << "exists?" << QFile::exists(QStringLiteral(":/close_dark.svg")); + qWarning() << "exists?" << QFile::exists(QStringLiteral(":/error.svg")); + qWarning() << "exists?" << QFile::exists(QStringLiteral(":/info.svg")); + qWarning() << "exists?" << QFile::exists(QStringLiteral(":/warning.svg")); + qWarning() << "exists?" << QFile::exists(QStringLiteral(":/success.svg")); +#endif + + m_ui->lblImage->setStyleSheet(QString()); + setStyleSheet(css.join(QStringLiteral(" "))); + + m_ui->lblText->setText(QStringLiteral("

%2

%4

").arg(textColor, title, textColor, text)); + + show(); +} + + +void LtsNotificationBox::paintEvent(QPaintEvent *) +{ + QStyleOption opt; + opt.init(this); + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); +} +} + + + + + + + + + + + + + + + + + + + diff --git a/Lts/Common/LtsWidgets/lts_notification_box.h b/Lts/Common/LtsWidgets/lts_notification_box.h new file mode 100755 index 000000000..f57d88295 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_notification_box.h @@ -0,0 +1,50 @@ +#pragma once + +#include "lts_widgets_export.h" +#include +#include + + +class QPaintEvent; +class QResizeEvent; + + +namespace Ui +{ +class NotiBox; +} + + +namespace LtsWidgets +{ +class LTSWIDGETS_EXPORT LtsNotificationBox : public QWidget +{ + Q_OBJECT +public: + enum class MessageType + { + Success, + Error, + Warning, + Info, + }; + + explicit LtsNotificationBox(QWidget *parent = nullptr); + + void message(const QString &title, const QString &text, MessageType type = MessageType::Error); + +signals: + void signalClose(); + void signalAck(); + void signalLinkClicked(const QString &link); + +protected: + void paintEvent(QPaintEvent *) override; + +private: + Ui::NotiBox *m_ui; + QPainterPath roundRectPath; + const static int margin; +}; +} + diff --git a/Lts/Common/LtsWidgets/lts_notification_box.ui b/Lts/Common/LtsWidgets/lts_notification_box.ui new file mode 100755 index 000000000..bb58cd5a8 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_notification_box.ui @@ -0,0 +1,182 @@ + + + NotiBox + + + + 0 + 0 + 670 + 130 + + + + + 0 + 0 + + + + + 670 + 130 + + + + + 670 + 130 + + + + Notification + + + + :/rde_icon_256.png:/rde_icon_256.png + + + + 20 + + + 40 + + + 18 + + + 18 + + + 18 + + + + + + 0 + 0 + + + + + 50 + 50 + + + + + 50 + 50 + + + + + + + true + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + <html><head/><body><p><span style=" font-size:14pt; font-weight:600; color:#eeeeee;">Error Message</span></p><p><span style=" font-size:14pt; color:#eeeeee;">This is an error message. looooooooooooooooooooong text<br/>With 2 lines!</span></p></body></html> + + + false + + + false + + + false + + + + + + + + + 0 + + + + + + 0 + 0 + + + + + 20 + 20 + + + + + + + true + + + + + + + + 0 + 0 + + + + + 20 + 20 + + + + + + + true + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + diff --git a/Lts/Common/LtsWidgets/lts_operator_menu.cpp b/Lts/Common/LtsWidgets/lts_operator_menu.cpp new file mode 100755 index 000000000..16e844b6a --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_operator_menu.cpp @@ -0,0 +1,114 @@ + + + +#include "lts_operator_menu.h" +#include "ui_lts_operator_menu.h" +#include "lts_info_dialog.h" +#include + +#include +#include +#include + + +namespace LtsWidgets +{ +LtsOperatorMenu::LtsOperatorMenu(const QString &operatorName, const QString &workspaceId, bool debug, int screenCount, QWidget *parent) + : LtsFramelessDialog(parent) + , m_ui(new Ui::LtsOperatorMenu) +{ + m_ui->setupUi(this); + m_ui->lbOperatorName->setText(operatorName); + m_ui->lbHostName->setText(QHostInfo::localHostName()); + + if (screenCount < 2) + m_ui->pbSwap->hide(); + + if (debug) + m_ui->lbWorkspaceId->setText(tr("Workspace: >>>%1<<<").arg(workspaceId)); + else + m_ui->lbWorkspaceId->hide(); + + connect(m_ui->pbOperatorSettings, &QPushButton::clicked, this, &LtsOperatorMenu::signalShowOperatorSettings); + connect(m_ui->pbOperatorSettings, &QPushButton::clicked, this, &LtsOperatorMenu::close); + connect(m_ui->pbWindow, &QPushButton::clicked, this, &LtsOperatorMenu::onTerminalButtonClicked); + connect(m_ui->pbWindow, &QPushButton::clicked, this, &LtsOperatorMenu::close); + connect(m_ui->pbLogout, &QPushButton::clicked, this, &LtsOperatorMenu::signalLogout); + connect(m_ui->pbExit, &QPushButton::clicked, this, &LtsOperatorMenu::signalExit); + connect(m_ui->pbCancel, &QPushButton::clicked, this, &LtsOperatorMenu::close); + connect(m_ui->pbSwap, &QPushButton::clicked, this, &LtsOperatorMenu::signalSwap); + connect(m_ui->pbSwap, &QPushButton::clicked, this, &LtsOperatorMenu::close); + connect(m_ui->pbAbout, &QPushButton::clicked, this, &LtsOperatorMenu::slotShowInfoDialog); + connect(m_ui->pbAbout, &QPushButton::clicked, this, &LtsOperatorMenu::close); + + + if (NGL::translationLocale() == "de_DE") + m_ui->pbGer->setChecked(true); + else + m_ui->pbInt->setChecked(true); + + connect(m_ui->pbGer, &QPushButton::clicked, + this, &LtsOperatorMenu::onLanguageChange); + connect(m_ui->pbInt, &QPushButton::clicked, + this, &LtsOperatorMenu::onLanguageChange); + +#ifndef LTS_DEBUG + m_ui->debugWidget->hide(); +#endif +} + + +LtsOperatorMenu::~LtsOperatorMenu() +{ + delete m_ui; +} + + +void LtsOperatorMenu::slotShowInfoDialog() +{ + LtsInfoDialog *infoDialog = new LtsInfoDialog(); + infoDialog->setAttribute(Qt::WA_DeleteOnClose); + infoDialog->show(); +} + + +void LtsOperatorMenu::onTerminalButtonClicked() +{ + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + + QString lang = QString::fromLatin1(NGL::translationLocale()); + lang.append(QStringLiteral(".UTF-8")); + env.insert(QStringLiteral("LANG"), lang); + + QProcess process; + process.setProcessEnvironment(env); + QStringList args; + args << QStringLiteral("--geometry") << QStringLiteral("800x800+200+100"); + + if (QApplication::queryKeyboardModifiers() == Qt::ControlModifier) + process.startDetached(QStringLiteral("xterm"), args); + else + process.startDetached(QStringLiteral("konsole"), args); +} + + +void LtsOperatorMenu::setTerminalButton(bool bVisible) +{ + m_ui->pbWindow->setVisible(bVisible); +} + +void LtsOperatorMenu::setOperatorSettingsButton(bool bVisible) +{ + m_ui->pbOperatorSettings->setVisible(bVisible); +} + + +void LtsOperatorMenu::onLanguageChange() +{ + if (m_ui->pbGer->isChecked()) + NGL::setTranslationLocale(QByteArray("de_DE")); + + if (m_ui->pbInt->isChecked()) + NGL::setTranslationLocale(QByteArray("en_GB")); +} +} diff --git a/Lts/Common/LtsWidgets/lts_operator_menu.h b/Lts/Common/LtsWidgets/lts_operator_menu.h new file mode 100755 index 000000000..fabd479b1 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_operator_menu.h @@ -0,0 +1,42 @@ + + + +#pragma once + +#include "lts_frameless_dialog.h" +#include "lts_widgets_export.h" + + +namespace Ui { +class LtsOperatorMenu; +} + +namespace LtsWidgets +{ +class LTSWIDGETS_EXPORT LtsOperatorMenu : public LtsFramelessDialog +{ + Q_OBJECT + +public: + explicit LtsOperatorMenu(const QString &operatorName, const QString &workspaceId, bool debug, int screenCount, QWidget *parent); + ~LtsOperatorMenu(); + + void setTerminalButton(bool bVisible); + void setOperatorSettingsButton(bool bVisible); + +private: + Ui::LtsOperatorMenu *m_ui; + +signals: + void signalLogout(); + void signalExit(); + void signalShowOperatorSettings(); + void signalSwap(); + +private slots: + void onTerminalButtonClicked(); + void onLanguageChange(); + void slotShowInfoDialog(); +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_operator_menu.ui b/Lts/Common/LtsWidgets/lts_operator_menu.ui new file mode 100755 index 000000000..aed0c23cc --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_operator_menu.ui @@ -0,0 +1,247 @@ + + + LtsOperatorMenu + + + + 0 + 0 + 310 + 504 + + + + + 0 + 0 + + + + + 0 + 0 + + + + Operator Menu + + + + + + + 16 + + + + %OPERATORNAME% + + + Qt::AlignCenter + + + true + + + + + + + %HOSTNAME% + + + Qt::AlignCenter + + + + + + + Workspace: %WORKSPACEID% + + + Qt::AlignCenter + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + Qt::Horizontal + + + + + + + Operator Settings + + + + + + + Terminal Window + + + + + + + Qt::Horizontal + + + + + + + Swap with another window + + + + + + + Operator Logout + + + + + + + Exit Application + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + About + + + + + + + Cancel + + + + + + + + 0 + 0 + + + + + 50 + 50 + + + + + + + Qt::Horizontal + + + + + + + For Debug only + + + + + + + + + Deutsch - German + + + + + + + :/Germany-Flag.png:/Germany-Flag.png + + + true + + + true + + + true + + + + + + + English - Englisch (International) + + + + + + + :/Great-Britain-Flag.png:/Great-Britain-Flag.png + + + true + + + true + + + false + + + + + + + Qt::Horizontal + + + + 18 + 29 + + + + + + + + + + + + + + + + diff --git a/Lts/Common/LtsWidgets/lts_overlay_loading.cpp b/Lts/Common/LtsWidgets/lts_overlay_loading.cpp new file mode 100755 index 000000000..bd756988b --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_overlay_loading.cpp @@ -0,0 +1,45 @@ + + + +#include "lts_overlay_loading.h" +#include "ui_lts_overlay_loading.h" + +#include +#include +#include +#include + + +namespace LtsWidgets +{ +LtsOverlayLoading::LtsOverlayLoading(QWidget *parent) + : QWidget(parent) + , m_ui(new Ui::LtsOverlayLoading) +{ + m_ui->setupUi(this); + setCancelButtonVisible(false); + connect(m_ui->pbCancel, &QPushButton::clicked, + this, [this](){ setCancelButtonEnabled(false); }); + connect(m_ui->pbCancel, &QPushButton::clicked, + this, &LtsOverlayLoading::cancel); +} + + +void LtsOverlayLoading::setText(const QString &text) +{ + m_ui->label->setText(text); +} + + +void LtsOverlayLoading::setCancelButtonVisible(bool visible) +{ + m_ui->pbCancel->setVisible(visible); +} + + +void LtsOverlayLoading::setCancelButtonEnabled(bool enabled) +{ + m_ui->pbCancel->setEnabled(enabled); +} + +} diff --git a/Lts/Common/LtsWidgets/lts_overlay_loading.h b/Lts/Common/LtsWidgets/lts_overlay_loading.h new file mode 100755 index 000000000..e6184f487 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_overlay_loading.h @@ -0,0 +1,34 @@ + + + +#pragma once + +#include "lts_widgets_export.h" + +#include + + +namespace Ui { class LtsOverlayLoading; } + + +namespace LtsWidgets +{ + + +class LTSWIDGETS_EXPORT LtsOverlayLoading : public QWidget +{ + Q_OBJECT +public: + explicit LtsOverlayLoading(QWidget *parent = nullptr); + + void setText(const QString &text); + void setCancelButtonVisible(bool visible); + void setCancelButtonEnabled(bool enabled); + +signals: + void cancel(); + +private: + Ui::LtsOverlayLoading *m_ui; +}; +} diff --git a/Lts/Common/LtsWidgets/lts_overlay_loading.ui b/Lts/Common/LtsWidgets/lts_overlay_loading.ui new file mode 100755 index 000000000..2389e32b3 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_overlay_loading.ui @@ -0,0 +1,76 @@ + + + LtsOverlayLoading + + + + 0 + 0 + 370 + 120 + + + + + 0 + 0 + + + + + 370 + 120 + + + + + 555 + 200 + + + + + + + + + + Loading ... + + + Qt::AlignCenter + + + true + + + + + + + + + 0 + + + 0 + + + + + + + &Cancel + + + Shift+C + + + + + + + + + + diff --git a/Lts/Common/LtsWidgets/lts_picture_view.cpp b/Lts/Common/LtsWidgets/lts_picture_view.cpp new file mode 100755 index 000000000..eccdac91a --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_picture_view.cpp @@ -0,0 +1,339 @@ + + + +#include "lts_picture_view.h" +#include +#include +#include +#include + + +using namespace LtsWidgets; + +LtsPictureView::LtsPictureView(QWidget *parent) + : QGraphicsView(parent) + , m_gsPicture(new QGraphicsScene(this)) + , m_rubberband(new QRubberBand(QRubberBand::Rectangle, this)) + , m_image(nullptr) + , m_origimage(nullptr) +{ + m_mouseActive = false; + m_moveable = false; + m_aspectRatioEnabled = true; + m_s_inEdit = false; + + m_aspectRatio = 1; + + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); +} + +LtsPictureView::~LtsPictureView() +{ + delete m_rubberband; +} + +void LtsPictureView::setAspectRatio(double aspectRatio) +{ + m_aspectRatio = aspectRatio; + newRubberBand(); +} + +void LtsPictureView::ratioClicked(bool enabled) +{ + m_aspectRatioEnabled = enabled; + if (enabled) + newRubberBand(); +} + +void LtsPictureView::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) + { + if (m_rubberband->geometry().contains(event->pos())) + { + m_bandoffset = event->pos() - m_rubberband->pos(); + m_moveable = true; + m_rubberband->show(); + } + else + { + m_origin = event->pos(); + m_curRectangle = QRect(m_origin.x(), m_origin.y(), 0, 0); + m_rubberband->setGeometry(m_curRectangle); + m_rubberband->show(); + m_mouseActive = true; + } + } + else + { + hideRubberband(); + newRubberBand(); + m_mouseActive = false; + } + QGraphicsView::mousePressEvent(event); +} + +void LtsPictureView::mouseMoveEvent(QMouseEvent *event) +{ + if (m_moveable == true) + { + QPoint newCoord = event->pos() - m_bandoffset; + if (newCoord.x() < 0) + newCoord.setX(0); + if (newCoord.y() < 0) + newCoord.setY(0); + if (m_origimage->pixmap().width() < (newCoord.x() + m_rubberband->width())) + newCoord.setX(m_origimage->pixmap().width() - m_rubberband->width() + 1); + if (m_origimage->pixmap().height() < (newCoord.y() + m_rubberband->height())) + newCoord.setY(m_origimage->pixmap().height() - m_rubberband->height() + 1); + m_rubberband->move(newCoord); + QPoint move = m_rubberband->pos(); + int left = move.x(); + int top = move.y(); + int width = m_rubberband->rect().width(); + int height = m_rubberband->rect().height(); + emit mouseMoved(QRect(left, top, width, height)); + } + else + { + if (m_mouseActive) + { + QRect rect; + + int xMax = event->pos().x(); + int yMax = event->pos().y(); + + if (event->pos().x() > m_origimage->pixmap().width()) + xMax = m_origimage->pixmap().width(); + if (event->pos().x() < 0) + xMax = 0; + if (event->pos().y() > m_origimage->pixmap().height()) + yMax = m_origimage->pixmap().height(); + if (event->pos().y() < 0) + yMax = 0; + + if (m_aspectRatioEnabled) + { + const int xMin = m_origin.x(); + const int yMin = m_origin.y(); + + int xLengthAbs(std::abs(xMax - xMin)); + int yLengthAbs(std::abs(yMax - yMin)); + + QPoint length; + + if (xLengthAbs / m_aspectRatio > yLengthAbs * m_aspectRatio) + { + length.setX(xLengthAbs); + length.setY(length.x() / m_aspectRatio); + } + else + { + length.setY(yLengthAbs); + length.setX(length.y() * m_aspectRatio); + } + + if (!(yMax - yMin < 0) && length.y() + m_origin.y() > m_origimage->pixmap().height()) + { + length.setY(m_origimage->pixmap().height() - m_origin.y()); + length.setX(length.y()* m_aspectRatio); + } + if (!(xMax - xMin < 0) && length.x() + m_origin.x() > m_origimage->pixmap().width()) + { + length.setX(m_origimage->pixmap().width() - m_origin.x()); + length.setY(length.x() / m_aspectRatio); + } + + if (xMax - xMin < 0) + { + length.setX(length.x() * -1); + if (length.x() + m_origin.x() < 0) + { + length.setX(0 - m_origin.x()); + length.setY((length.x() / m_aspectRatio) * -1); + } + } + + if (yMax - yMin < 0) + { + length.setY(length.y() * -1); + if (length.y() + m_origin.y() < 0) + { + length.setY(0 - m_origin.y()); + length.setX(length.y() * m_aspectRatio); + if (!(xMax - xMin < 0)) + length.setX(length.x() * -1); + } + } + + length = m_origin + length; + rect.setTopLeft(m_origin); + rect.setBottomRight(length); + } + else + { + rect.setTopLeft(m_origin); + rect.setBottomRight(QPoint(xMax, yMax)); + } + m_curRectangle = rect.normalized(); + m_rubberband->setGeometry(m_curRectangle); + emit mouseMoved(m_curRectangle); + } + } + QGraphicsView::mouseMoveEvent(event); +} + +void LtsPictureView::mouseReleaseEvent(QMouseEvent *event) +{ + m_moveable = false; + QGraphicsView::mouseReleaseEvent(event); +} + +void LtsPictureView::hideRubberband() +{ + m_rubberband->hide(); +} + +void LtsPictureView::newRubberBand() +{ + delete m_rubberband; + m_rubberband = new QRubberBand(QRubberBand::Rectangle, this); +} + +void LtsPictureView::setPicture(const QImage &image, const QPoint &pos, const bool flag) +{ + if (m_image) + { + m_gsPicture->removeItem(m_image); + delete m_image; + } + if (flag) + { + if (m_origimage) + { + m_gsPicture->removeItem(m_origimage); + delete m_origimage; + } + QGraphicsOpacityEffect *opacity = new QGraphicsOpacityEffect; + opacity->setOpacity(0.2); + + m_origimage = new QGraphicsPixmapItem(QPixmap::fromImage(image)); + m_origimage->setGraphicsEffect(opacity); + + m_gsPicture->addItem(m_origimage); + + setMinimumSize(image.size()); + setMaximumSize(image.size()); + emit setLimits(m_origimage->pixmap().size(), true); + } + m_image = new QGraphicsPixmapItem(QPixmap::fromImage(image)); + + m_image->setPos(pos); + m_gsPicture->addItem(m_image); + setScene(m_gsPicture); +} + +void LtsPictureView::leftChanged(int left) +{ + if (m_s_inEdit || m_mouseActive) + return; + m_s_inEdit = true; + m_curRectangle.setLeft(left); + if(m_aspectRatioEnabled) + { + int newHeight = m_curRectangle.width() / m_aspectRatio; + if (m_origin.y() < m_curRectangle.bottom()) + { + m_curRectangle.setHeight(newHeight); + } + else + { + int offset = m_curRectangle.height() - newHeight; + m_curRectangle.setTop(m_curRectangle.top() + offset); + } + } + m_rubberband->setGeometry(m_curRectangle); + emit mouseMoved(m_curRectangle); + m_s_inEdit = false; +} + +void LtsPictureView::rightChanged(int right) +{ + if (m_s_inEdit || m_mouseActive) + return; + m_s_inEdit = true; + m_curRectangle.setRight(right); + if(m_aspectRatioEnabled) + { + int newHeight = m_curRectangle.width() / m_aspectRatio; + if (m_origin.y() < m_curRectangle.bottom()) + { + m_curRectangle.setHeight(newHeight); + } + else + { + int offset = m_curRectangle.height() - newHeight; + m_curRectangle.setTop(m_curRectangle.top() + offset); + } + } + m_rubberband->setGeometry(m_curRectangle); + emit mouseMoved(m_curRectangle); + m_s_inEdit = false; +} + +void LtsPictureView::topChanged(int top) +{ + if (m_s_inEdit || m_mouseActive) + return; + m_s_inEdit = true; + m_curRectangle.setTop(top); + if(m_aspectRatioEnabled) + { + int newWidth = m_curRectangle.height() * m_aspectRatio; + if (m_origin.x() <= m_curRectangle.left()) + { + m_curRectangle.setWidth(newWidth); + } + else + { + int offset = m_curRectangle.width() - newWidth; + m_curRectangle.setLeft(m_curRectangle.left() + offset); + } + } + m_rubberband->setGeometry(m_curRectangle); + emit mouseMoved(m_curRectangle); + m_s_inEdit = false; +} + +void LtsPictureView::bottomChanged(int bottom) +{ + if (m_s_inEdit || m_mouseActive) + return; + m_s_inEdit = true; + m_curRectangle.setBottom(bottom); + if(m_aspectRatioEnabled) + { + int newWidth = m_curRectangle.height() * m_aspectRatio; + if (m_origin.x() <= m_curRectangle.left()) + { + m_curRectangle.setWidth(newWidth); + } + else + { + int offset = m_curRectangle.width() - newWidth; + m_curRectangle.setLeft(m_curRectangle.left() + offset); + } + } + m_rubberband->setGeometry(m_curRectangle); + emit mouseMoved(m_curRectangle); + m_s_inEdit = false; +} + + +QGraphicsPixmapItem *LtsPictureView::getImage() +{ + if (!m_image) + m_image = new QGraphicsPixmapItem; + return m_image; +} diff --git a/Lts/Common/LtsWidgets/lts_picture_view.h b/Lts/Common/LtsWidgets/lts_picture_view.h new file mode 100755 index 000000000..78a0eab1d --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_picture_view.h @@ -0,0 +1,60 @@ + + + +#pragma once + +#include "lts_widgets_export.h" +#include + +class QRubberBand; + +namespace LtsWidgets +{ +class LTSWIDGETS_EXPORT LtsPictureView : public QGraphicsView +{ + Q_OBJECT + +public: + explicit LtsPictureView(QWidget *parent = nullptr); + ~LtsPictureView(); + void hideRubberband(); + void newRubberBand(); + void cutPicture(); + QGraphicsPixmapItem *getImage(); + +protected: + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + +private: + bool m_mouseActive; + bool m_moveable; + bool m_aspectRatioEnabled; + bool m_s_inEdit; + + double m_aspectRatio; + QRect m_curRectangle; + + QGraphicsScene *m_gsPicture; + QRubberBand *m_rubberband; + QGraphicsPixmapItem *m_image; + QGraphicsPixmapItem *m_origimage; + + QPoint m_origin; + QPoint m_bandoffset; + +public slots: + void setPicture(const QImage &image, const QPoint &pos, const bool flag); + void setAspectRatio(double aspectRatio); + void ratioClicked(bool enabled); + void leftChanged(int left); + void rightChanged(int right); + void topChanged(int top); + void bottomChanged(int bottom); + +signals: + void mouseMoved(const QRect &); + void setLimits(const QSize &, bool); +}; +} diff --git a/Lts/Common/LtsWidgets/lts_profile_button.cpp b/Lts/Common/LtsWidgets/lts_profile_button.cpp new file mode 100755 index 000000000..e29651b58 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_profile_button.cpp @@ -0,0 +1,133 @@ + + + +#include "lts_profile_button.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace LtsWidgets; + + +LtsProfileButton::LtsProfileButton(QWidget *parent) + : QWidget(parent) + , m_isHovered(false) + , m_menuEnabled(true) +{ + QSizePolicy qsp = QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + qsp.setHeightForWidth(true); + setSizePolicy(qsp); +} + + +void LtsProfileButton::setPixmap(const QPixmap &pixmap) +{ + if (pixmap.isNull()) + m_pixmap = QPixmap(QStringLiteral("icon:no_pic.svg")); + else + m_pixmap = pixmap; + + update(); +} + + +void LtsProfileButton::enterEvent(QEvent *event) +{ + + m_isHovered = true; + update(); + QWidget::enterEvent(event); +} + + +void LtsProfileButton::leaveEvent(QEvent *event) +{ + + m_isHovered = false; + update(); + QWidget::leaveEvent(event); +} + + +bool LtsProfileButton::event(QEvent *event) +{ + if (event->type() == QEvent::MouseButtonPress && m_menuEnabled) + { + emit signalClicked(); + event->accept(); + + update(); + return true; + } + + return QWidget::event(event); +} + + +void LtsProfileButton::paintEvent(QPaintEvent* event) +{ + QPainter p(this); + p.save(); + + int minSize; + QPointF center; + minSize = height() > width() ? width() : height(); + center = QPointF(width() / 2.0, height() / 2.0); + + + + p.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); + + + QPixmap pixmap(minSize, minSize); + pixmap.fill(Qt::white); + + if (!m_pixmap.isNull()) + { + if (!(m_isHovered && m_menuEnabled)) + { + pixmap = m_pixmap.scaled(minSize, minSize, Qt::KeepAspectRatioByExpanding); + } + } + + + + QPainterPath path; + path.addEllipse(center, + minSize / 2.0 - 4, + minSize / 2.0 - 4); + p.setClipPath(path); + p.drawPixmap(center - QPointF(minSize/2.0,minSize/2.0),pixmap); + + + + + p.setPen(QPen(QColor("gray"), 2)); + p.drawEllipse(center, + minSize / 2.0 - 4, + minSize / 2.0 - 4); + + + + if (m_isHovered && m_menuEnabled) + { + p.setPen(QPen(QColor(QStringLiteral("gray")), (minSize / 48 * 2))); + + double xc = size().width()/2.012; + double yc = size().height()/2.012; + double diff = minSize/2.012; + + p.drawLine(QPointF(xc - (diff/2.01f), yc - (diff/3.01f)), QPointF(xc + (diff/2.02f), yc - (diff/3.02f))); + p.drawLine(QPointF(xc - (diff/2.01f), yc - 0.01f), QPointF(xc + (diff/2.02f), yc - 0)); + p.drawLine(QPointF(xc - (diff/2.01f), yc + (diff/3.01f)), QPointF(xc + (diff/2.02f), yc + (diff/3.02f))); + } + + + event->accept(); + p.restore(); +} diff --git a/Lts/Common/LtsWidgets/lts_profile_button.h b/Lts/Common/LtsWidgets/lts_profile_button.h new file mode 100755 index 000000000..1f832c5fe --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_profile_button.h @@ -0,0 +1,55 @@ + + + +#pragma once + +#include "lts_widgets_export.h" + +#include + + +namespace LtsWidgets +{ + + + +class LTSWIDGETS_EXPORT LtsProfileButton : public QWidget +{ + Q_OBJECT + + Q_PROPERTY( QPixmap pixMap READ pixmap WRITE setPixmap ) + Q_PROPERTY( bool menuEnabled READ menuEnabled WRITE setMenuEnabled ) + +public: + explicit LtsProfileButton(QWidget *parent = nullptr); + + + + void setPixmap(const QPixmap &pixmap); + + const QPixmap &pixmap() const { return m_pixmap; } + + + + void setMenuEnabled(bool enabled = true) { m_menuEnabled = enabled; } + bool menuEnabled() const { return m_menuEnabled; } + + +protected: + void paintEvent(QPaintEvent *event); + void enterEvent(QEvent *event); + void leaveEvent(QEvent *event); + bool event(QEvent *event); + +private: + QPixmap m_pixmap; + bool m_isHovered; + bool m_menuEnabled; + +signals: + + + void signalClicked(); +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_progress_dialog_helper.cpp b/Lts/Common/LtsWidgets/lts_progress_dialog_helper.cpp new file mode 100755 index 000000000..3a64d04f1 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_progress_dialog_helper.cpp @@ -0,0 +1,147 @@ + + + +#include "lts_progress_dialog_helper.h" + +#include "lts_frameless_dialog.h" + +#include +#include +#include +#include +#include +#include + + +namespace LtsWidgets +{ + +LtsProgressDialogHelper::LtsProgressDialogHelper(QWidget *parent) + : QObject(parent) + , m_waitDlg(nullptr) + , m_parentWidget(parent) + , m_textLabel(nullptr) +{} + + +LtsProgressDialogHelper::~LtsProgressDialogHelper() +{ + delete m_waitDlg; +} + + +void LtsProgressDialogHelper::setCustomText(const QString &customText) +{ + if (m_waitDlg == nullptr || m_textLabel == nullptr) + return; + + if (customText.isEmpty()) + m_textLabel->setText(tr("Waiting for server reply...")); + else + m_textLabel->setText(customText); +} + +void LtsProgressDialogHelper::setParentWidget(QWidget *newParent) +{ + m_parentWidget = newParent; + if (m_waitDlg) + { + m_waitDlg->setParent(newParent); + QTimer::singleShot(0, m_waitDlg.data(), &LtsFramelessDialog::onUpdateGeometry); + } +} + +void LtsProgressDialogHelper::slotShowWaitForReplyWindow() +{ + if (m_waitDlg) + m_waitDlg->deleteLater(); + m_waitDlg = new LtsFramelessDialog(m_parentWidget, Behaviour::Modal); + + QHBoxLayout *l = new QHBoxLayout(m_waitDlg); + + QProgressBar *pb = new QProgressBar(m_waitDlg); + pb->setMaximum(0); + pb->setValue(-1); + + l->addWidget(pb); + + m_textLabel = new QLabel(m_waitDlg); + setCustomText(); + l->addWidget(m_textLabel); + + m_waitDlg->setLayout(l); + m_waitDlg->show(); + m_waitDlg->raise(); + + +} + + +void LtsProgressDialogHelper::slotStopWaitForReplyWindow(const ReplyResult &replyResult, const QString &replyString) +{ + if (m_waitDlg == nullptr) + { + + return; + } + + + m_waitDlg->deleteLater(); + m_waitDlg = nullptr; + + + QString resultText = tr("Unknown result (%1)!").arg((int)replyResult); + + switch (replyResult) + { + case ReplyResult::Close: + { + return; + } + + case ReplyResult::Success: + { + resultText = tr("Operation was successful"); + break; + } + + case ReplyResult::Failure: + { + resultText = tr("Operation has failed!\nReason: '%1'").arg(replyString); + break; + } + + case ReplyResult::TimeOut: + { + resultText = tr("Operation has timed out and could not be processed! Please try again later"); + break; + } + } + + m_waitDlg = new LtsFramelessDialog(m_parentWidget, Behaviour::Modal); + + QVBoxLayout *newLayout = new QVBoxLayout(m_waitDlg); + newLayout->addWidget(new QLabel(resultText, m_waitDlg)); + + QPushButton *btn = new QPushButton(tr("OK"),m_waitDlg); + connect(btn, &QPushButton::clicked, + this, &LtsProgressDialogHelper::accept); + + newLayout->addWidget(btn); + + m_waitDlg->setLayout(newLayout); + m_waitDlg->show(); + + +} + + +void LtsProgressDialogHelper::accept() +{ + m_waitDlg->accept(); + m_waitDlg->deleteLater(); + m_waitDlg = nullptr; + emit accepted(); +} + +} diff --git a/Lts/Common/LtsWidgets/lts_progress_dialog_helper.h b/Lts/Common/LtsWidgets/lts_progress_dialog_helper.h new file mode 100755 index 000000000..d402c1038 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_progress_dialog_helper.h @@ -0,0 +1,66 @@ + + + +#pragma once + +#include "lts_widgets_export.h" + +#include +#include + +class QLabel; +class QWidget; + +namespace LtsWidgets +{ +class LtsFramelessDialog; + + + +class LTSWIDGETS_EXPORT LtsProgressDialogHelper : public QObject +{ + Q_OBJECT + +public: + + + enum class ReplyResult + { + Close = 0, + Success, + Failure, + TimeOut, + }; + + explicit LtsProgressDialogHelper(QWidget *parent = nullptr); + ~LtsProgressDialogHelper(); + bool isShown() const { return !!m_waitDlg; } + void setCustomText(const QString &customText = QString()); + + void setParentWidget(QWidget *newParent); + +public slots: + + + void slotShowWaitForReplyWindow(); + + + + void slotStopWaitForReplyWindow(const LtsWidgets::LtsProgressDialogHelper::ReplyResult &replyResult = ReplyResult::Close , + const QString &replyString = QString()); + +private: + QPointer m_waitDlg; + QWidget *m_parentWidget; + QPointer m_textLabel; + +signals: + + + void accepted(); + +private slots: + void accept(); +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_range_slider.cpp b/Lts/Common/LtsWidgets/lts_range_slider.cpp new file mode 100755 index 000000000..89781980f --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_range_slider.cpp @@ -0,0 +1,823 @@ + + + + + + +#include "lts_range_slider.h" + + +#include +#include +#include +#include +#include +#include +#include + +namespace LtsWidgets +{ + +class LtsRangeSliderPrivate +{ + Q_DECLARE_PUBLIC(LtsRangeSlider) +protected: + LtsRangeSlider *const q_ptr; +public: + + + enum Handle + { + NoHandle = 0x0, + MinimumHandle = 0x1, + MaximumHandle = 0x2 + }; + Q_DECLARE_FLAGS(Handles, Handle) + + explicit LtsRangeSliderPrivate(LtsRangeSlider &object); + void init(); + + + + + + Handle handleAtPos(const QPoint &pos, QRect &handleRect) const; + + + int pixelPosToRangeValue(int pos) const; + int pixelPosFromRangeValue(int val) const; + + + void drawMinimumSlider(QStylePainter *painter) const; + void drawMaximumSlider(QStylePainter *painter) const; + + + int m_minimumValue = 0; + int m_maximumValue = 100; + + + int m_minimumPosition = 0; + int m_maximumPosition = 100; + + + QStyle::SubControl m_minimumSliderSelected = QStyle::SC_None; + QStyle::SubControl m_maximumSliderSelected = QStyle::SC_None; + + + + int m_subclassClickOffset = 0; + + + + int m_subclassPosition = 0; + + + float m_subclassWidth = 0; + + LtsRangeSliderPrivate::Handles m_selectedHandles = {}; + + + + bool m_symmetricMoves = false; + + QString m_handleToolTip; + +private: + Q_DISABLE_COPY(LtsRangeSliderPrivate) +}; + + +LtsRangeSliderPrivate::LtsRangeSliderPrivate(LtsRangeSlider &object) + : q_ptr(&object) +{} + + +void LtsRangeSliderPrivate::init() +{ + Q_Q(LtsRangeSlider); + m_minimumValue = q->minimum(); + m_maximumValue = q->maximum(); + m_minimumPosition = q->minimum(); + m_maximumPosition = q->maximum(); + q->connect(q, &LtsRangeSlider::rangeChanged, q, &LtsRangeSlider::onRangeChanged); +} + + +LtsRangeSliderPrivate::Handle LtsRangeSliderPrivate::handleAtPos(const QPoint &pos, QRect &handleRect)const +{ + Q_Q(const LtsRangeSlider); + + QStyleOptionSlider option; + q->initStyleOption(&option); + + + + + + + option.sliderPosition = m_minimumPosition; + option.sliderValue = m_minimumValue; + + QStyle::SubControl minimumControl = q->style()->hitTestComplexControl( + QStyle::CC_Slider, &option, pos, q); + QRect minimumHandleRect = q->style()->subControlRect( + QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, q); + + + option.sliderPosition = m_maximumPosition; + option.sliderValue = m_maximumValue; + + QStyle::SubControl maximumControl = q->style()->hitTestComplexControl(QStyle::CC_Slider, &option, pos, q); + QRect maximumHandleRect = q->style()->subControlRect(QStyle::CC_Slider, &option, QStyle::SC_SliderHandle, q); + + + if (minimumControl == QStyle::SC_SliderHandle && + maximumControl == QStyle::SC_SliderHandle) + { + int minDist = 0; + int maxDist = 0; + if (q->orientation() == Qt::Horizontal) + { + minDist = pos.x() - minimumHandleRect.left(); + maxDist = maximumHandleRect.right() - pos.x(); + } + else + { + minDist = minimumHandleRect.bottom() - pos.y(); + maxDist = pos.y() - maximumHandleRect.top(); + } + Q_ASSERT(minDist >= 0 && maxDist >= 0); + minimumControl = minDist < maxDist ? minimumControl : QStyle::SC_None; + } + + if (maximumControl == QStyle::SC_SliderHandle) + { + handleRect = maximumHandleRect; + return MaximumHandle; + } + else if (minimumControl == QStyle::SC_SliderHandle) + { + handleRect = minimumHandleRect; + return MinimumHandle; + } + + handleRect = minimumHandleRect.united(maximumHandleRect); + return NoHandle; +} + + + + +int LtsRangeSliderPrivate::pixelPosToRangeValue(int pos) const +{ + Q_Q(const LtsRangeSlider); + QStyleOptionSlider option; + q->initStyleOption(&option); + + QRect gr = q->style()->subControlRect(QStyle::CC_Slider, + &option, + QStyle::SC_SliderGroove, + q); + QRect sr = q->style()->subControlRect(QStyle::CC_Slider, + &option, + QStyle::SC_SliderHandle, + q); + int sliderMin, sliderMax, sliderLength; + if (option.orientation == Qt::Horizontal) + { + sliderLength = sr.width(); + sliderMin = gr.x(); + sliderMax = gr.right() - sliderLength + 1; + } + else + { + sliderLength = sr.height(); + sliderMin = gr.y(); + sliderMax = gr.bottom() - sliderLength + 1; + } + + return QStyle::sliderValueFromPosition(q->minimum(), + q->maximum(), + pos - sliderMin, + sliderMax - sliderMin, + option.upsideDown); +} + + +int LtsRangeSliderPrivate::pixelPosFromRangeValue(int val) const +{ + Q_Q(const LtsRangeSlider); + QStyleOptionSlider option; + q->initStyleOption(&option); + + QRect gr = q->style()->subControlRect(QStyle::CC_Slider, + &option, + QStyle::SC_SliderGroove, + q); + QRect sr = q->style()->subControlRect(QStyle::CC_Slider, + &option, + QStyle::SC_SliderHandle, + q); + int sliderMin, sliderMax, sliderLength; + if (option.orientation == Qt::Horizontal) + { + sliderLength = sr.width(); + sliderMin = gr.x(); + sliderMax = gr.right() - sliderLength + 1; + } + else + { + sliderLength = sr.height(); + sliderMin = gr.y(); + sliderMax = gr.bottom() - sliderLength + 1; + } + + return QStyle::sliderPositionFromValue(q->minimum(), + q->maximum(), + val, + sliderMax - sliderMin, + option.upsideDown) + sliderMin; +} + + + +void LtsRangeSliderPrivate::drawMinimumSlider(QStylePainter *painter) const +{ + Q_Q(const LtsRangeSlider); + QStyleOptionSlider option; + q->initMinimumSliderStyleOption(&option); + + option.subControls = QStyle::SC_SliderHandle; + option.sliderValue = m_minimumValue; + option.sliderPosition = m_minimumPosition; + if (q->isMinimumSliderDown()) + { + option.activeSubControls = QStyle::SC_SliderHandle; + option.state |= QStyle::State_Sunken; + } +#ifdef Q_OS_MAC + + QRect clip = q->style()->subControlRect(QStyle::CC_Slider, &option, + QStyle::SC_SliderHandle, q); + painter->setClipRect(clip); +#endif + painter->drawComplexControl(QStyle::CC_Slider, option); +} + + + +void LtsRangeSliderPrivate::drawMaximumSlider(QStylePainter *painter) const +{ + Q_Q(const LtsRangeSlider); + QStyleOptionSlider option; + q->initMaximumSliderStyleOption(&option); + + option.subControls = QStyle::SC_SliderHandle; + option.sliderValue = m_maximumValue; + option.sliderPosition = m_maximumPosition; + if (q->isMaximumSliderDown()) + { + option.activeSubControls = QStyle::SC_SliderHandle; + option.state |= QStyle::State_Sunken; + } +#ifdef Q_OS_MAC + + QRect clip = q->style()->subControlRect(QStyle::CC_Slider, &option, + QStyle::SC_SliderHandle, q); + painter->setClipRect(clip); +#endif + painter->drawComplexControl(QStyle::CC_Slider, option); +} + + +LtsRangeSlider::LtsRangeSlider(Qt::Orientation o, + QWidget *parent) + : QSlider(o, parent) + , d_ptr(new LtsRangeSliderPrivate(*this)) +{ + Q_D(LtsRangeSlider); + d->init(); +} + + +LtsRangeSlider::LtsRangeSlider(LtsRangeSliderPrivate *impl, Qt::Orientation o, + QWidget *parent) + : QSlider(o, parent) + , d_ptr(impl) +{ + Q_D(LtsRangeSlider); + d->init(); +} + + +LtsRangeSlider::~LtsRangeSlider() +{ +} + + +int LtsRangeSlider::minimumValue() const +{ + Q_D(const LtsRangeSlider); + return d->m_minimumValue; +} + + +void LtsRangeSlider::setMinimumValue(int min) +{ + Q_D(LtsRangeSlider); + setValues(min, qMax(d->m_maximumValue, min)); +} + + +int LtsRangeSlider::maximumValue() const +{ + Q_D(const LtsRangeSlider); + return d->m_maximumValue; +} + + +void LtsRangeSlider::setMaximumValue(int max) +{ + Q_D(LtsRangeSlider); + setValues(qMin(d->m_minimumValue, max), max); +} + + +void LtsRangeSlider::setValues(int min, int max) +{ + Q_D(LtsRangeSlider); + const int minValue = qBound(minimum(), qMin(min, max), maximum()); + const int maxValue = qBound(minimum(), qMax(min, max), maximum()); + bool emitMinValChanged = (minValue != d->m_minimumValue); + bool emitMaxValChanged = (maxValue != d->m_maximumValue); + + d->m_minimumValue = minValue; + d->m_maximumValue = maxValue; + + bool emitMinPosChanged = (minValue != d->m_minimumPosition); + bool emitMaxPosChanged = (maxValue != d->m_maximumPosition); + d->m_minimumPosition = minValue; + d->m_maximumPosition = maxValue; + + if (isSliderDown()) + { + if (emitMinPosChanged || emitMaxPosChanged) + emit positionsChanged(d->m_minimumPosition, d->m_maximumPosition); + if (emitMinPosChanged) + emit minimumPositionChanged(d->m_minimumPosition); + if (emitMaxPosChanged) + emit maximumPositionChanged(d->m_maximumPosition); + } + if (emitMinValChanged || emitMaxValChanged) + emit valuesChanged(d->m_minimumValue, d->m_maximumValue); + + if (emitMinValChanged) + emit minimumValueChanged(d->m_minimumValue); + + if (emitMaxValChanged) + emit maximumValueChanged(d->m_maximumValue); + + if (emitMinPosChanged || emitMaxPosChanged || + emitMinValChanged || emitMaxValChanged) + update(); +} + + +int LtsRangeSlider::minimumPosition() const +{ + Q_D(const LtsRangeSlider); + return d->m_minimumPosition; +} + + +int LtsRangeSlider::maximumPosition() const +{ + Q_D(const LtsRangeSlider); + return d->m_maximumPosition; +} + + +void LtsRangeSlider::setMinimumPosition(int min) +{ + Q_D(const LtsRangeSlider); + setPositions(min, qMax(min, d->m_maximumPosition)); +} + + +void LtsRangeSlider::setMaximumPosition(int max) +{ + Q_D(const LtsRangeSlider); + setPositions(qMin(d->m_minimumPosition, max), max); +} + + +void LtsRangeSlider::setPositions(int min, int max) +{ + Q_D(LtsRangeSlider); + const int minPosition = qBound(minimum(), qMin(min, max), maximum()); + const int maxPosition = qBound(minimum(), qMax(min, max), maximum()); + + bool emitMinPosChanged = (minPosition != d->m_minimumPosition); + bool emitMaxPosChanged = (maxPosition != d->m_maximumPosition); + + if (!emitMinPosChanged && !emitMaxPosChanged) + return; + + d->m_minimumPosition = minPosition; + d->m_maximumPosition = maxPosition; + + if (!hasTracking()) + update(); + + if (isSliderDown()) + { + if (emitMinPosChanged) + emit minimumPositionChanged(d->m_minimumPosition); + if (emitMaxPosChanged) + emit maximumPositionChanged(d->m_maximumPosition); + if (emitMinPosChanged || emitMaxPosChanged) + emit positionsChanged(d->m_minimumPosition, d->m_maximumPosition); + } + if (hasTracking()) + { + triggerAction(SliderMove); + setValues(d->m_minimumPosition, d->m_maximumPosition); + } +} + + +void LtsRangeSlider::setSymmetricMoves(bool symmetry) +{ + Q_D(LtsRangeSlider); + d->m_symmetricMoves = symmetry; +} + + +bool LtsRangeSlider::symmetricMoves() const +{ + Q_D(const LtsRangeSlider); + return d->m_symmetricMoves; +} + + +void LtsRangeSlider::onRangeChanged(int minimum, int maximum) +{ + Q_UNUSED(minimum); + Q_UNUSED(maximum); + Q_D(LtsRangeSlider); + setValues(d->m_minimumValue, d->m_maximumValue); +} + + + +void LtsRangeSlider::paintEvent(QPaintEvent *) +{ + Q_D(LtsRangeSlider); + QStyleOptionSlider option; + initStyleOption(&option); + + QStylePainter painter(this); + + + + + painter.setPen(Qt::lightGray); + if (option.orientation == Qt::Horizontal) + { + painter.drawLine(option.rect.left(), option.rect.top(), option.rect.left(), option.rect.bottom()); + painter.drawRect(option.rect.left(), option.rect.center().y() - 1, option.rect.right() - option.rect.left(), 3); + painter.drawLine(option.rect.right(), option.rect.top(), option.rect.right(), option.rect.bottom()); + } + else + { + painter.drawLine(option.rect.left(), option.rect.top(), option.rect.right(), option.rect.top()); + painter.drawRect(option.rect.center().x() - 1, option.rect.top(), 3, option.rect.top() - option.rect.bottom()); + painter.drawLine(option.rect.center().x(), option.rect.top(), option.rect.center().x(), option.rect.bottom()); + painter.drawLine(option.rect.left(), option.rect.bottom(), option.rect.right(), option.rect.bottom()); + } + + + option.subControls = QStyle::SC_SliderGroove; + + + + option.sliderValue = minimum() - maximum(); + option.sliderPosition = minimum() - maximum(); + option.subControls &= ~QStyle::SC_SliderGroove; + painter.drawComplexControl(QStyle::CC_Slider, option); + option.subControls |= QStyle::SC_SliderGroove; + + option.sliderPosition = d->m_minimumPosition; + const QRect lr = style()->subControlRect(QStyle::CC_Slider, + &option, + QStyle::SC_SliderHandle, + this); + + option.sliderPosition = d->m_maximumPosition; + const QRect ur = style()->subControlRect(QStyle::CC_Slider, + &option, + QStyle::SC_SliderHandle, + this); + + QRect sr = style()->subControlRect(QStyle::CC_Slider, + &option, + QStyle::SC_SliderGroove, + this); + QRect rangeBox; + if (option.orientation == Qt::Horizontal) + { + rangeBox = QRect(QPoint(qMin(lr.center().x(), ur.center().x()), sr.center().y() - 2), + QPoint(qMax(lr.center().x(), ur.center().x()), sr.center().y() + 1)); + } + else + { + rangeBox = QRect(QPoint(sr.center().x() - 2, qMin(lr.center().y(), ur.center().y())), + QPoint(sr.center().x() + 1, qMax(lr.center().y(), ur.center().y()))); + } + + + + + QRect groove = style()->subControlRect(QStyle::CC_Slider, + &option, + QStyle::SC_SliderGroove, + this); + groove.adjust(0, 0, -1, 0); + + + + QColor highlight = palette().color(QPalette::Normal, QPalette::Highlight); + QLinearGradient gradient; + if (option.orientation == Qt::Horizontal) + gradient = QLinearGradient(groove.center().x(), groove.top(), groove.center().x(), groove.bottom()); + else + gradient = QLinearGradient(groove.left(), groove.center().y(), groove.right(), groove.center().y()); + + + + + + gradient.setColorAt(0, highlight.darker(120)); + gradient.setColorAt(1, highlight.lighter(160)); + + painter.setPen(QPen(highlight.darker(150), 0)); + painter.setBrush(gradient); + painter.drawRect(rangeBox.intersected(groove)); + + + + + if (isMinimumSliderDown()) + { + painter.setClipRect(ur); + d->drawMaximumSlider(&painter); + painter.setClipRect(lr); + d->drawMinimumSlider(&painter); + } + else + { + painter.setClipRect(lr); + d->drawMinimumSlider(&painter); + painter.setClipRect(ur); + d->drawMaximumSlider(&painter); + } +} + + + +void LtsRangeSlider::mousePressEvent(QMouseEvent *ev) +{ + Q_D(LtsRangeSlider); + if (minimum() == maximum() || (ev->buttons() ^ ev->button())) + { + ev->ignore(); + return; + } + int mepos = orientation() == Qt::Horizontal ? ev->pos().x() : ev->pos().y(); + + QStyleOptionSlider option; + initStyleOption(&option); + + QRect handleRect; + LtsRangeSliderPrivate::Handle handle_ = d->handleAtPos(ev->pos(), handleRect); + + if (handle_ != LtsRangeSliderPrivate::NoHandle) + { + if (ev->buttons() == Qt::RightButton) + { + d->m_subclassPosition = (d->m_minimumPosition + d->m_maximumPosition) / 2.; + d->m_subclassClickOffset = mepos - d->pixelPosFromRangeValue(d->m_subclassPosition); + d->m_subclassWidth = (d->m_maximumPosition - d->m_minimumPosition) / 2.; + + setSliderDown(true); + if (!isMinimumSliderDown() || !isMaximumSliderDown()) + { + d->m_selectedHandles = QFlags(LtsRangeSliderPrivate::MinimumHandle) | + QFlags(LtsRangeSliderPrivate::MaximumHandle); + QRect sr = style()->subControlRect(QStyle::CC_Slider, &option, QStyle::SC_SliderGroove, this); + update(handleRect.united(sr)); + } + ev->accept(); + return; + } + + d->m_subclassPosition = (handle_ == LtsRangeSliderPrivate::MinimumHandle) ? d->m_minimumPosition : d->m_maximumPosition; + + + d->m_subclassClickOffset = mepos - (orientation() == Qt::Horizontal ? handleRect.left() : handleRect.top()); + + setSliderDown(true); + + if (d->m_selectedHandles != handle_) + { + d->m_selectedHandles = handle_; + update(handleRect); + } + + ev->accept(); + return; + } + + + + + QStyle::SubControl control = style()->hitTestComplexControl(QStyle::CC_Slider, &option, ev->pos(), this); + QRect sr = style()->subControlRect(QStyle::CC_Slider, &option, QStyle::SC_SliderGroove, this); + int minCenter = (orientation() == Qt::Horizontal ? handleRect.left() : handleRect.top()); + int maxCenter = (orientation() == Qt::Horizontal ? handleRect.right() : handleRect.bottom()); + if (control == QStyle::SC_SliderGroove && mepos > minCenter && mepos < maxCenter) + { + + d->m_subclassPosition = (d->m_minimumPosition + d->m_maximumPosition) / 2.; + d->m_subclassClickOffset = mepos - d->pixelPosFromRangeValue(d->m_subclassPosition); + d->m_subclassWidth = (d->m_maximumPosition - d->m_minimumPosition) / 2.; + + setSliderDown(true); + if (!isMinimumSliderDown() || !isMaximumSliderDown()) + { + d->m_selectedHandles = QFlags(LtsRangeSliderPrivate::MinimumHandle) | + QFlags(LtsRangeSliderPrivate::MaximumHandle); + update(handleRect.united(sr)); + } + ev->accept(); + return; + } + ev->ignore(); +} + + + +void LtsRangeSlider::mouseMoveEvent(QMouseEvent *ev) +{ + Q_D(LtsRangeSlider); + if (!d->m_selectedHandles) + { + ev->ignore(); + return; + } + int mepos = orientation() == Qt::Horizontal ? ev->pos().x() : ev->pos().y(); + + QStyleOptionSlider option; + initStyleOption(&option); + + + if (isMinimumSliderDown() && isMaximumSliderDown()) + { + int newPosition = d->pixelPosToRangeValue(mepos); + int newMinPosition = newPosition - static_cast(d->m_subclassWidth); + int newMaxPosition = newPosition + static_cast(d->m_subclassWidth + .5); + + if (newMinPosition < minimum()) + { + newMinPosition = minimum(); + newMaxPosition = newMinPosition + (d->m_maximumPosition - d->m_minimumPosition); + } + else if (newMaxPosition > maximum()) + { + newMaxPosition = maximum(); + newMinPosition = newMaxPosition - (d->m_maximumPosition - d->m_minimumPosition); + } + setPositions(newMinPosition, newMaxPosition); + ev->accept(); + return; + } + + const int m = style()->pixelMetric(QStyle::PM_MaximumDragDistance, &option, this); + + int newPosition = d->pixelPosToRangeValue(mepos - d->m_subclassClickOffset); + + if (m >= 0) + { + const QRect r = rect().adjusted(-m, -m, m, m); + if (!r.contains(ev->pos())) + newPosition = d->m_subclassPosition; + } + + + if (isMinimumSliderDown() && !isMaximumSliderDown()) + { + double newMinPos = qMin(newPosition, d->m_maximumPosition); + setPositions(newMinPos, d->m_maximumPosition + (d->m_symmetricMoves ? d->m_minimumPosition - newMinPos : 0)); + } + + else if (isMaximumSliderDown() && !isMinimumSliderDown()) + { + double newMaxPos = qMax(d->m_minimumPosition, newPosition); + setPositions(d->m_minimumPosition - (d->m_symmetricMoves ? newMaxPos - d->m_maximumPosition : 0), newMaxPos); + } + ev->accept(); +} + + + +void LtsRangeSlider::mouseReleaseEvent(QMouseEvent *ev) +{ + Q_D(LtsRangeSlider); + QSlider::mouseReleaseEvent(ev); + + setSliderDown(false); + d->m_selectedHandles = {}; + + update(); +} + + +bool LtsRangeSlider::isMinimumSliderDown() const +{ + Q_D(const LtsRangeSlider); + return d->m_selectedHandles & LtsRangeSliderPrivate::MinimumHandle; +} + + +bool LtsRangeSlider::isMaximumSliderDown() const +{ + Q_D(const LtsRangeSlider); + return d->m_selectedHandles & LtsRangeSliderPrivate::MaximumHandle; +} + + +void LtsRangeSlider::initMinimumSliderStyleOption(QStyleOptionSlider *option) const +{ + initStyleOption(option); +} + + +void LtsRangeSlider::initMaximumSliderStyleOption(QStyleOptionSlider *option) const +{ + initStyleOption(option); +} + + +QString LtsRangeSlider::handleToolTip()const +{ + Q_D(const LtsRangeSlider); + return d->m_handleToolTip; +} + + +void LtsRangeSlider::setHandleToolTip(const QString &toolTip) +{ + Q_D(LtsRangeSlider); + d->m_handleToolTip = toolTip; +} + + +bool LtsRangeSlider::event(QEvent *event) +{ + Q_D(LtsRangeSlider); + switch (event->type()) + { + case QEvent::ToolTip: + { + QHelpEvent *helpEvent = static_cast(event); + QStyleOptionSlider opt; + + opt.sliderPosition = d->m_minimumPosition; + opt.sliderValue = d->m_minimumValue; + initStyleOption(&opt); + QStyle::SubControl hoveredControl = style()->hitTestComplexControl(QStyle::CC_Slider, &opt, helpEvent->pos(), this); + if (!d->m_handleToolTip.isEmpty() && hoveredControl == QStyle::SC_SliderHandle) + { + QToolTip::showText(helpEvent->globalPos(), d->m_handleToolTip.arg(minimumValue())); + event->accept(); + return true; + } + + opt.sliderPosition = d->m_maximumPosition; + opt.sliderValue = d->m_maximumValue; + initStyleOption(&opt); + hoveredControl = style()->hitTestComplexControl(QStyle::CC_Slider, &opt, helpEvent->pos(), this); + if (!d->m_handleToolTip.isEmpty() && hoveredControl == QStyle::SC_SliderHandle) + { + QToolTip::showText(helpEvent->globalPos(), d->m_handleToolTip.arg(maximumValue())); + event->accept(); + return true; + } + } + default: + break; + } + return QSlider::event(event); +} + +} diff --git a/Lts/Common/LtsWidgets/lts_range_slider.h b/Lts/Common/LtsWidgets/lts_range_slider.h new file mode 100755 index 000000000..a0283ed47 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_range_slider.h @@ -0,0 +1,194 @@ + + + + + +#pragma once + +#include "lts_widgets_export.h" + + +#include + +namespace LtsWidgets +{ + +class LtsRangeSliderPrivate; + + + + + + + + + + + + + + + + +class LTSWIDGETS_EXPORT LtsRangeSlider : public QSlider +{ + Q_OBJECT + + Q_PROPERTY(int minimumValue READ minimumValue WRITE setMinimumValue) + Q_PROPERTY(int maximumValue READ maximumValue WRITE setMaximumValue) + Q_PROPERTY(int minimumPosition READ minimumPosition WRITE setMinimumPosition) + Q_PROPERTY(int maximumPosition READ maximumPosition WRITE setMaximumPosition) + Q_PROPERTY(bool symmetricMoves READ symmetricMoves WRITE setSymmetricMoves) + Q_PROPERTY(QString handleToolTip READ handleToolTip WRITE setHandleToolTip) + +public: + + + + explicit LtsRangeSlider(Qt::Orientation o, QWidget *parent = nullptr); + explicit LtsRangeSlider(QWidget *parent = nullptr) + : LtsRangeSlider(Qt::Vertical, parent) + {} + ~LtsRangeSlider(); + + + + + + + int minimumValue() const; + + + + + + + int maximumValue() const; + + + + + int minimumPosition() const; + void setMinimumPosition(int min); + + + + + int maximumPosition() const; + void setMaximumPosition(int max); + + + + + void setPositions(int min, int max); + + + + + bool symmetricMoves() const; + void setSymmetricMoves(bool symmetry); + + + + + + + QString handleToolTip() const; + void setHandleToolTip(const QString &toolTip); + + + + bool isMinimumSliderDown() const; + + + bool isMaximumSliderDown() const; + +Q_SIGNALS: + + + + void minimumValueChanged(int min); + + + + void maximumValueChanged(int max); + + + void valuesChanged(int min, int max); + + + + + + + void minimumPositionChanged(int min); + + + + + + + void maximumPositionChanged(int max); + + + + + void positionsChanged(int min, int max); + +public Q_SLOTS: + + + + + + + void setMinimumValue(int min); + + + + + + + + void setMaximumValue(int max); + + + + + + + + + void setValues(int min, int max); + +protected Q_SLOTS: + void onRangeChanged(int minimum, int maximum); + +protected: + LtsRangeSlider(LtsRangeSliderPrivate *impl, Qt::Orientation o, QWidget *parent = nullptr); + + + + void mousePressEvent(QMouseEvent *ev) override; + void mouseMoveEvent(QMouseEvent *ev) override; + void mouseReleaseEvent(QMouseEvent *ev) override; + + + + void paintEvent(QPaintEvent *ev) override; + void initMinimumSliderStyleOption(QStyleOptionSlider *option) const; + void initMaximumSliderStyleOption(QStyleOptionSlider *option) const; + + + + bool event(QEvent *event) override; + +protected: + QScopedPointer d_ptr; + +private: + Q_DECLARE_PRIVATE(LtsRangeSlider) + Q_DISABLE_COPY(LtsRangeSlider) +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_selection_popup.cpp b/Lts/Common/LtsWidgets/lts_selection_popup.cpp new file mode 100755 index 000000000..ed217150e --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_selection_popup.cpp @@ -0,0 +1,212 @@ + + + +#include "lts_selection_popup.h" +#include "ui_lts_selection_popup.h" +#include "lts_base.h" + +#include +#include +#include +#include + + +#ifdef NEVER +# include +#endif + + +namespace LtsWidgets +{ + +class RadioButton : public QRadioButton +{ + Q_OBJECT + +public: + explicit RadioButton(int row, const QString &text) + : QRadioButton(text) + , m_row(row) + {} + + int row() const { return m_row; } + +signals: + void doubleClickEvent(); + +private: + int m_row; + + void mouseDoubleClickEvent(QMouseEvent *) override + { + emit doubleClickEvent(); + } +}; + + + +LtsSelectionPopup::LtsSelectionPopup(QAbstractItemModel* model, + int currentIndex, + QWidget *parent) + : LtsFramelessDialog(parent, Behaviour::Popup) + , m_ui(new Ui::LtsSelectionPopup) + , m_currentIndex(currentIndex) + , m_firstIndex(currentIndex) + , m_filter(new QSortFilterProxyModel(this)) + , m_valid(false) +{ + m_ui->setupUi(this); + + m_filter->setSourceModel(model); + m_filter->setFilterCaseSensitivity(Qt::CaseInsensitive); + + slotFilterChanged(); + #ifdef TABLET_MODE + m_ui->leSearch->setVisible(false); + m_ui->label->setVisible(false); + #endif + connect(m_ui->pbCancel, &QPushButton::clicked, this, &LtsSelectionPopup::reject); + connect(m_ui->pbSelect, &QPushButton::clicked, this, &LtsSelectionPopup::accept); + connect(m_ui->leSearch, &QLineEdit::textEdited, + this, &LtsSelectionPopup::slotFilterChanged); + + #ifdef NEVER + + + + + + + + connect(this, static_cast(&LtsSelectionPopup::currentIndexChanged), this, [](int idx){ + qCritical() << " LtsSelectionPopup: called: currentIndexChanged(int), idx=" << idx; + }); + + + connect(this, static_cast(&LtsSelectionPopup::activated), this, [](int idx){ + qCritical() << " LtsSelectionPopup: called: activated(int), idx=" << idx; + }); + + #endif +} + + +LtsSelectionPopup::~LtsSelectionPopup() +{ + delete m_ui; +} + + +void LtsSelectionPopup::slotFilterChanged() +{ + m_filter->setFilterWildcard(m_ui->leSearch->text().count() ? m_ui->leSearch->text() : QStringLiteral("*") ); + refresh(); +} + + +void LtsSelectionPopup::refresh() +{ + m_valid = false; + qDeleteAll(m_ui->itemLayoutWidget->findChildren()); + QLayoutItem *tem = nullptr; + if (m_ui->itemLayoutWidget->layout()->count() > 0) + { + tem = m_ui->itemLayoutWidget->layout()->takeAt(0); + } + + int count = m_filter->rowCount(); + + for (int i = 0; i < count; i++) + { + int realRow = m_filter->mapToSource(m_filter->index(i, 0)).row(); + + RadioButton *rbItem = new RadioButton(realRow, m_filter->data(m_filter->index(i, 0)).toString()); + int disable = m_filter->data(m_filter->index(i, 0), Qt::UserRole-1).toInt(); + if (disable == 1) + rbItem->setEnabled(false); + m_ui->itemLayoutWidget->layout()->addWidget(rbItem); + + if (realRow == m_currentIndex) + { + rbItem->setChecked(true); + m_valid = true; + } + + connect(rbItem, &RadioButton::doubleClickEvent, + this, &LtsSelectionPopup::accept); + connect(rbItem, &RadioButton::toggled, + this, [this](bool checked) + { + if (checked) + m_ui->pbSelect->setEnabled(true); + }); + } + if (tem) + m_ui->itemLayoutWidget->layout()->addItem(tem); + + if (count == 1) + m_valid = true; + + m_ui->pbSelect->setEnabled(m_valid); +} + + +void LtsSelectionPopup::showEvent(QShowEvent *event) +{ + LtsFramelessDialog::showEvent(event); + m_ui->leSearch->setFocus(); + + resize(sizeHint().width() + m_ui->scrollArea->horizontalScrollBar()->height() , sizeHint().height()); +} + + +void LtsSelectionPopup::selectItem(int row, bool bCurrentIndexChanged, bool bActivated) +{ + + + if (bCurrentIndexChanged) + emit currentIndexChanged(row); + + if (bActivated) + emit activated(row); + + m_ui->pbSelect->setEnabled(true); + + LtsFramelessDialog::accept(); +} + + +void LtsSelectionPopup::accept() +{ + const QList items = m_ui->itemLayoutWidget->findChildren(); + + int count = items.count(); + + for (int i = 0; i < count; i++) + { + const RadioButton *rb = items.at(i); + + if (rb->isChecked()) + { + int realRow = m_filter->mapToSource(m_filter->index(i, 0)).row(); + + bool bCurrentIndexChanged = realRow != m_firstIndex; + bool bActivated = true; + selectItem(realRow, bCurrentIndexChanged, bActivated); + return; + } + } + + + if (count == 1) + { + const RadioButton *rb = items.at(0); + selectItem(rb->row(), true, true); + return; + } + +} + +} + +#include "lts_selection_popup.moc" diff --git a/Lts/Common/LtsWidgets/lts_selection_popup.h b/Lts/Common/LtsWidgets/lts_selection_popup.h new file mode 100755 index 000000000..ba2ad3b0d --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_selection_popup.h @@ -0,0 +1,83 @@ + + + +#pragma once + +#include "lts_frameless_dialog.h" +#include "lts_widgets_export.h" + + +namespace Ui +{ +class LtsSelectionPopup; +} + +class QAbstractItemModel; +class QSortFilterProxyModel; + +namespace LtsWidgets +{ + + + +class LTSWIDGETS_EXPORT LtsSelectionPopup : public LtsFramelessDialog +{ + Q_OBJECT + +public: + + + LtsSelectionPopup(QAbstractItemModel *model, + int currentIndex, + QWidget *parent = nullptr); + ~LtsSelectionPopup(); + +protected: + + + void showEvent(QShowEvent *event) override; + +private: + Ui::LtsSelectionPopup *m_ui; + int m_currentIndex; + int m_firstIndex; + QSortFilterProxyModel *m_filter; + bool m_valid; + + + + void refresh(); + + + + void selectItem(int row, bool bCurrentIndexChanged, bool bActivated); + +signals: + + + + + + + + + + + + void activated(int index); + + + + void currentIndexChanged(int index); + +public slots: + + + void accept() override; + + + + void slotFilterChanged(); + +}; +} diff --git a/Lts/Common/LtsWidgets/lts_selection_popup.ui b/Lts/Common/LtsWidgets/lts_selection_popup.ui new file mode 100755 index 000000000..60f6d52aa --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_selection_popup.ui @@ -0,0 +1,232 @@ + + + LtsSelectionPopup + + + + 0 + 0 + 193 + 230 + + + + + 0 + 0 + + + + true + + + + + + + 12 + + + + Select an Item + + + Qt::AlignCenter + + + + + + + + 0 + 1 + + + + + 16777215 + 1 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + 3 + + + + + + + + + + 8 + + + + + + + :/style/show.svg + + + + + + + Search ... + + + + + + + + + true + + + + 0 + 0 + + + + Qt::NoFocus + + + QFrame::NoFrame + + + Qt::ScrollBarAlwaysOff + + + QAbstractScrollArea::AdjustToContents + + + true + + + Qt::AlignCenter + + + + + 0 + 0 + 177 + 102 + + + + + 0 + 0 + + + + + 800 + 800 + + + + Qt::NoFocus + + + + 0 + + + QLayout::SetMinAndMaxSize + + + 0 + + + 0 + + + 100 + + + 0 + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + 0 + 1 + + + + + 16777215 + 1 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + 3 + + + + + + + + + Select + + + + + + + Cancel + + + + + + + + + pbSelect + pbCancel + leSearch + + + + + + diff --git a/Lts/Common/LtsWidgets/lts_spinbox.cpp b/Lts/Common/LtsWidgets/lts_spinbox.cpp new file mode 100755 index 000000000..d24161285 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_spinbox.cpp @@ -0,0 +1,173 @@ + + + +#include "lts_spinbox.h" + +#include + +namespace LtsWidgets +{ + +LtsSpinBox::LtsSpinBox(QWidget *parent) + : QSpinBox(parent) +{ +} + +QValidator::State LtsSpinBox::validate(QString &input, int &pos) const +{ + QIntValidator validator; + for (auto range : m_validRanges) + { + validator.setBottom(range.first); + validator.setTop(range.second); + QValidator::State state = validator.validate(input, pos); + if (state == QValidator::Invalid || + state == QValidator::Intermediate) + return state; + } + + + int value = input.toInt(); + value -= minimum(); + return (value % singleStep() == 0) ? QValidator::Acceptable : QValidator::Intermediate; +} + +void LtsSpinBox::fixup(QString &str) const +{ + QSpinBox::fixup(str); + + if (singleStep() == 1) + return; + + bool bOk; + int value = str.toInt(&bOk); + if (!bOk) + return; + + int remainder = (value - minimum()) % singleStep(); + + if (2 * remainder > singleStep()) + value += singleStep(); + + value -= remainder; + + if (value > maximum()) + value -= singleStep(); + + str = QString::number(value); +} + +bool LtsSpinBox::setExclusionRanges(const QVector > &exclusionRanges, bool preserveCurrentValues) +{ + bool retVal = true; + + auto newExclusions = exclusionRanges; + + if (preserveCurrentValues) + newExclusions += m_exclusions; + + for (auto &elem : newExclusions) + { + if (elem.first > elem.second) + { + int tmp = elem.first; + elem.first= elem.second; + elem.second = tmp; + } + } + + std::sort(newExclusions.begin(), newExclusions.end(), [](const QPair &a, const QPair &b) { return a.first < b.first; }); + for (int i=0; i= newExclusions[i+1].first) + { + retVal = false; + break; + } + } + + if (!retVal || newExclusions[0].first <= minimum() || newExclusions[newExclusions.size()-1].second >= maximum()) + { + + if (!preserveCurrentValues) + m_exclusions.clear(); + return false; + } + + m_exclusions = exclusionRanges; + + + m_validRanges.clear(); + if (m_exclusions[0].first - 1 > minimum()) + m_validRanges.append({minimum(), m_exclusions[0].first - 1}); + for (int i=0; i singleStep()) + value += singleStep(); + + value -= remainder; + + if (value > maximum()) + value -= singleStep(); + + str = QString::number(value, 'f', decimals()); +} + +} diff --git a/Lts/Common/LtsWidgets/lts_spinbox.h b/Lts/Common/LtsWidgets/lts_spinbox.h new file mode 100755 index 000000000..3dbd82fbe --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_spinbox.h @@ -0,0 +1,60 @@ + + + +#pragma once + +#include "lts_widgets_export.h" + +#include + +namespace LtsWidgets +{ + +class LTSWIDGETS_EXPORT LtsSpinBox : public QSpinBox +{ + Q_OBJECT + +public: + explicit LtsSpinBox(QWidget *parent = nullptr); + ~LtsSpinBox() = default; + +public slots: + void setValue(int val) { QString valStr = QString::number(val); fixup(valStr); QSpinBox::setValue(valStr.toInt()); } + bool setExclusionRanges(const QVector > &exclusionRanges, bool preserveCurrentValues = false); + bool addExclusionRange(int min, int max); + void clearExclusionRanges(); + +protected: + QValidator::State validate(QString &input, int &pos) const override; + void fixup(QString &str) const override; + +private: + + struct + { + bool operator()(const QPair &a, const QPair &b) const { return a.first < b.first; } + } customless; + + QVector > m_exclusions; + QVector > m_validRanges; + +}; + +class LTSWIDGETS_EXPORT LtsDoubleSpinBox : public QDoubleSpinBox +{ + Q_OBJECT + +public: + explicit LtsDoubleSpinBox(QWidget *parent = nullptr); + ~LtsDoubleSpinBox() = default; + +public slots: + void setValue(double val) { QString valStr = QString::number(val); fixup(valStr); QDoubleSpinBox::setValue(valStr.toDouble()); } + +protected: + QValidator::State validate(QString &input, int &pos) const override; + void fixup(QString &str) const override; + +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_splash.cpp b/Lts/Common/LtsWidgets/lts_splash.cpp new file mode 100755 index 000000000..341549ab0 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_splash.cpp @@ -0,0 +1,81 @@ +#include "lts_splash.h" + +#include + +#include +#include +#include + + +namespace LtsWidgets +{ + +Splash::Splash() + : QSplashScreen() + , m_done(false) +{ + static const auto splashFiles = + { + QStringLiteral(":/project/splash.png"), + QStringLiteral(":/project/splash.jpg"), + }; + for (const QString &f : splashFiles) + { + if (!QFile::exists(f)) + continue; + setPixmap(QPixmap(f)); + break; + } +} + + +bool Splash::event(QEvent *e) +{ + if (e->type() == QEvent::MouseButtonPress || + e->type() == QEvent::MouseButtonRelease || + e->type() == QEvent::MouseButtonDblClick) + { + e->ignore(); + return false; + } + + return QSplashScreen::event(e); +} + + +void Splash::setMessage(const QString &message) +{ + showMessage(message, Qt::AlignRight | Qt::AlignBottom); +} + + +void Splash::done() +{ + if (m_done) + return; + m_done = true; + + hide(); + LtsWidgets::LtsGlobalWaitDialog::getInstance()->setVisible(true); +} + + +static Splash *s_splashInstance = nullptr; +void Splash::cleanupSplashInstance() +{ + delete s_splashInstance; + s_splashInstance = nullptr; +} + + +Splash *Splash::getInstance() +{ + if (!s_splashInstance) + { + s_splashInstance = new Splash; + qAddPostRoutine(&Splash::cleanupSplashInstance); + } + return s_splashInstance; +} + +} diff --git a/Lts/Common/LtsWidgets/lts_splash.h b/Lts/Common/LtsWidgets/lts_splash.h new file mode 100755 index 000000000..34598fe7c --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_splash.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include +#include + + +namespace LtsWidgets +{ +class LTSWIDGETS_EXPORT Splash : public QSplashScreen +{ + Q_OBJECT +public: + static Splash* getInstance(); + + void done(); + void setMessage(const QString &message); + +private: + Splash(); + static void cleanupSplashInstance(); + + bool event(QEvent *e) override; + + bool m_done; +}; +} diff --git a/Lts/Common/LtsWidgets/lts_splitter.cpp b/Lts/Common/LtsWidgets/lts_splitter.cpp new file mode 100755 index 000000000..46b740cd4 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_splitter.cpp @@ -0,0 +1,57 @@ + + + +#include "lts_splitter.h" +#include "lts_splitter_handle.h" + + +namespace LtsWidgets +{ + +LtsSplitter::LtsSplitter(QWidget *parent) + : QSplitter(parent) +{ + connect(this, &LtsSplitter::sizesChanged, this, &LtsSplitter::changeSizes); +} + +LtsSplitter::~LtsSplitter() +{ +} + +QSplitterHandle *LtsSplitter::createHandle() +{ + LtsSplitterHandle *hand = new LtsSplitterHandle(orientation(), this); +#ifdef TABLET_MODE + setHandleWidth(25); +#else + setHandleWidth(10); +#endif + return hand; +} + +void LtsSplitter::collapseChild(const int index) +{ + if (children().size() > index) + { + QWidget *widget = static_cast(children().at(index)); + if (widget) + { + QList s = sizes(); + s[index] = 0; + setSizes(s); + } + } +} + +void LtsSplitter::animate(int handleId) +{ + LtsSplitterHandle *handle = dynamic_cast(this->handle(handleId)); + handle->animate(); +} + +void LtsSplitter::changeSizes(const QList &list) +{ + QSplitter::setSizes(list); +} + +} diff --git a/Lts/Common/LtsWidgets/lts_splitter.h b/Lts/Common/LtsWidgets/lts_splitter.h new file mode 100755 index 000000000..81f083aec --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_splitter.h @@ -0,0 +1,37 @@ + + + +#pragma once + +#include "lts_base.h" +#include "lts_widgets_export.h" + +#include +#include + + +namespace LtsWidgets +{ + +class LTSWIDGETS_EXPORT LtsSplitter : public QSplitter +{ + Q_OBJECT + Q_PROPERTY(QList sizes READ getSizes WRITE setSizes NOTIFY sizesChanged) + +public: + explicit LtsSplitter(QWidget *parent = nullptr); + ~LtsSplitter(); + + QSplitterHandle *createHandle(); + void collapseChild(const int index); + QList getSizes() const { return sizes(); } + void animate(int handleId); + +signals: + void sizesChanged(const QList &newSizes); + +public slots: + void changeSizes(const QList &list); +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_splitter_handle.cpp b/Lts/Common/LtsWidgets/lts_splitter_handle.cpp new file mode 100755 index 000000000..7ddda8f6d --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_splitter_handle.cpp @@ -0,0 +1,87 @@ + + + +#include "lts_splitter_handle.h" +#include +#include +#include +#include +#include +#include + + +namespace LtsWidgets +{ + +LtsSplitterHandle::LtsSplitterHandle(Qt::Orientation orientation, QSplitter *parent) + : QSplitterHandle(orientation, parent) +{ + auto bgrole = backgroundRole(); + QPalette p = palette(); + m_endColor = p.color(bgrole); + + m_colorAnimation = new QPropertyAnimation(this, "splittercolor"); + m_colorAnimation->setDuration(1000); + m_colorAnimation->setStartValue(m_currentColor); + m_colorAnimation->setEndValue(m_endColor); + m_colorAnimation->setLoopCount(2); + connect(m_colorAnimation, &QPropertyAnimation::finished, this, [this]{ + m_animate = false; + m_currentColor = m_endColor; + repaint(); + }); + +} + +LtsSplitterHandle::~LtsSplitterHandle() +{ +} + +void LtsSplitterHandle::paintEvent(QPaintEvent *event) +{ + if(!m_animate) + { + QSplitterHandle::paintEvent(event); + return; + } + QPainter painter(this); + QLinearGradient gradient; + gradient.setColorAt(0, m_currentColor); + + + if(orientation() == Qt::Horizontal) + { + gradient.setStart(rect().left(), rect().height()/2); + gradient.setFinalStop(rect().height(), rect().height()/2); + }else + { + gradient.setStart(rect().width()/2, rect().top()); + gradient.setFinalStop(rect().width()/2, rect().bottom()/2); + } + painter.fillRect(event->rect(), QBrush(gradient)); +} + +void LtsSplitterHandle::animate() +{ + if (m_colorAnimation->state() == QPropertyAnimation::Running) + return; + + m_currentColor = QColor(239,87,50); + m_animate = true; + m_colorAnimation->start(); +} + +QColor LtsSplitterHandle::splitterColor() const +{ + return m_currentColor; +} + +void LtsSplitterHandle::setCurrentColor(const QColor ¤tcolor) +{ + if (currentcolor == m_currentColor) + return; + m_currentColor = currentcolor; + repaint(); +} + +} diff --git a/Lts/Common/LtsWidgets/lts_splitter_handle.h b/Lts/Common/LtsWidgets/lts_splitter_handle.h new file mode 100755 index 000000000..d7769bbe5 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_splitter_handle.h @@ -0,0 +1,39 @@ + + + +#pragma once + +#include "lts_base.h" +#include "lts_widgets_export.h" + +#include +#include +#include + +namespace LtsWidgets +{ + +class LTSWIDGETS_EXPORT LtsSplitterHandle : public QSplitterHandle +{ + Q_OBJECT + Q_PROPERTY(QColor splittercolor READ splitterColor WRITE setCurrentColor DESIGNABLE false) + +public: + explicit LtsSplitterHandle(Qt::Orientation orientation, QSplitter *parent = nullptr); + ~LtsSplitterHandle(); + + void paintEvent(QPaintEvent *event) override; + void animate(); + + QColor splitterColor() const; + void setCurrentColor(const QColor ¤tcolor); + +private: + bool m_animate {false}; + QColor m_endColor {Qt::black}; + QColor m_currentColor{QColor(239,87,50)}; + QPropertyAnimation *m_colorAnimation; + +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_tab_bar.cpp b/Lts/Common/LtsWidgets/lts_tab_bar.cpp new file mode 100755 index 000000000..19663a74e --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_tab_bar.cpp @@ -0,0 +1,120 @@ + + + +#include "lts_tab_bar.h" + +#include "lts_dialog.h" +#include "lts_dialog_helper.h" +#include "lts_events.h" +#include "lts_tab_widget.h" + +#include +#include +#include + +namespace LtsWidgets +{ + +LtsTabBar::LtsTabBar(bool isSecondary, QWidget *parent) + : QTabBar(parent) + , m_secondary(isSecondary) +{ + setDocumentMode(true); + setTabsClosable(true); + + connect(this, &LtsTabBar::currentChanged, this, &LtsTabBar::updateCloseButtonColors); + + if (m_secondary) + { + connect(this, &LtsTabBar::customContextMenuRequested, + this, &LtsTabBar::onCustomContextMenuRequested); + setContextMenuPolicy(Qt::CustomContextMenu); + } +} + + +void LtsTabBar::_internal_closeTab() +{ + QObject *object = sender(); + int tabToClose = -1; + + for (int i = 0; i < count(); i++) + { + if (tabButton(i, QTabBar::RightSide) == object) + { + tabToClose = i; + break; + } + } + + if (tabToClose != -1) + emit tabCloseRequested(tabToClose); +} + + +void LtsTabBar::updateCloseButtonColors(int newIndex) +{ + + if (newIndex == -1) + newIndex = currentIndex(); + + for (int i = 0; i < count(); i++) + { + QWidget *w = tabButton(i, QTabBar::RightSide); + CloseButton *cb = qobject_cast(w); + + if (!cb) + continue; + + QColor text; + + bool enabled = isTabEnabled(i); + bool selected = (i == newIndex); + bool secondary = m_secondary; + + if (enabled) + { + if (selected) + text = (secondary) ? Styles::TabBarTabStyle::secondarySelected : Styles::TabBarTabStyle::primarySelected; + else + text = (secondary) ? Styles::TabBarTabStyle::secondaryNormal : Styles::TabBarTabStyle::primaryNormal; + } + else + text = (secondary) ? Styles::TabBarTabStyle::secondaryDisabled : Styles::TabBarTabStyle::primaryDisabled; + + + cb->setFontColor(text); + } +} + +void LtsTabBar::onCustomContextMenuRequested(const QPoint pos) +{ + if (!m_secondary) + return; + QMenu m; + QAction *a = new QAction(tr("Save as image")); + connect(a, &QAction::triggered, this, [this, pos]() { LtsTabBar::onSaveCurrentWidgetAsImage(pos); }); + m.addAction(a); + m.exec(mapToGlobal(pos)); +} + +void LtsTabBar::onSaveCurrentWidgetAsImage(const QPoint pos) +{ + int curTab = tabAt(pos); + if (curTab < 0) + curTab = currentIndex(); + if (curTab < 0) + return; + LtsTabWidget *tw = qobject_cast(parentWidget()); + QWidget *w = tw ? tw->widget(curTab) : nullptr; + if (!w) + return; + LtsDialogHelper *helper = qobject_cast(w); + if (helper) + w = helper->getDialog(); + SaveImageEvent *ev = new SaveImageEvent(w, w->grab()); + ev->setAccepted(false); + QCoreApplication::postEvent(qApp, ev); +} + +} diff --git a/Lts/Common/LtsWidgets/lts_tab_bar.h b/Lts/Common/LtsWidgets/lts_tab_bar.h new file mode 100755 index 000000000..f4fc20037 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_tab_bar.h @@ -0,0 +1,39 @@ + + + +#pragma once + +#include +#include "lts_widgets_export.h" + +namespace LtsWidgets +{ + + + +class LTSWIDGETS_EXPORT LtsTabBar : public QTabBar +{ + Q_OBJECT + Q_PROPERTY(bool secondary MEMBER m_secondary) + +public: + + + explicit LtsTabBar(bool isSecondary = false, QWidget *parent = nullptr); + + + + void _internal_closeTab(); + +protected: + void onCustomContextMenuRequested(const QPoint pos); + void onSaveCurrentWidgetAsImage(const QPoint pos); +private: + bool m_secondary; + +public slots: + void updateCloseButtonColors(int newIndex = -1); + +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_tab_sequence_widget.cpp b/Lts/Common/LtsWidgets/lts_tab_sequence_widget.cpp new file mode 100755 index 000000000..06705f39c --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_tab_sequence_widget.cpp @@ -0,0 +1,59 @@ + + +#include "lts_tab_sequence_widget.h" + +#include +#include +#include +#include + +namespace LtsWidgets +{ +LtsTabSequenceWidget::LtsTabSequenceWidget(QWidget *parent) + : LtsAbstractSequenceWidget(parent) + , m_twSequences(new QTabWidget(this)) + +{ + QHBoxLayout *gridLayout = new QHBoxLayout(this); + gridLayout->addWidget(m_twSequences); + gridLayout->addWidget(pbAddMember(), Qt::AlignTop); + + m_twSequences->setCornerWidget(pbAddMember()); + pbAddMember()->setFixedSize(m_twSequences->tabBar()->size()); + pbAddMember()->setIconSize(m_twSequences->tabBar()->size()); +} + +LtsTabSequenceWidget::~LtsTabSequenceWidget() +{ + delete pbAddMember(); + delete m_twSequences; +} + +void LtsTabSequenceWidget::setupMemberWidget(QWidget *child, QToolButton *pbChild, const QString &objectName, bool isButtonVisible) +{ + int index = m_twSequences->addTab(child, objectName); + if(isButtonVisible) + m_twSequences->tabBar()->setTabButton(index, QTabBar::RightSide, pbChild); +} + +QWidget* LtsTabSequenceWidget::getWidget(int index) +{ + return m_twSequences->widget(index); +} + +QWidget* LtsTabSequenceWidget::getCurrentWidget() +{ + return m_twSequences->currentWidget(); +} + +int LtsTabSequenceWidget::getCurrentTabIndex() +{ + return m_twSequences->currentIndex(); +} +void LtsTabSequenceWidget::changeTabName(const QString &name) +{ + m_twSequences->setTabText(m_twSequences->currentIndex(), name); +} + + +} diff --git a/Lts/Common/LtsWidgets/lts_tab_sequence_widget.h b/Lts/Common/LtsWidgets/lts_tab_sequence_widget.h new file mode 100755 index 000000000..706e744ff --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_tab_sequence_widget.h @@ -0,0 +1,37 @@ + + +#ifndef LTS_TAB_SEQUENCE_WIDGET_H +#define LTS_TAB_SEQUENCE_WIDGET_H + +#pragma once + +#include "lts_abstract_sequence_widget.h" +#include "lts_widgets_export.h" + +class QTabWidget; + +namespace LtsWidgets +{ + + class LTSWIDGETS_EXPORT LtsTabSequenceWidget : public LtsAbstractSequenceWidget + { + Q_OBJECT + + public: + LtsTabSequenceWidget(QWidget *parent = nullptr); + + virtual ~LtsTabSequenceWidget(); + QWidget* getWidget(int index); + QWidget* getCurrentWidget(); + int getCurrentTabIndex(); + void changeTabName(const QString &name); + + protected: + void setupMemberWidget(QWidget *child, QToolButton *pbChild, const QString &objectName, bool isButtonVisible = true) override; + + private: + QTabWidget *m_twSequences = nullptr; + }; +} + +#endif diff --git a/Lts/Common/LtsWidgets/lts_tab_widget.cpp b/Lts/Common/LtsWidgets/lts_tab_widget.cpp new file mode 100755 index 000000000..a866a1310 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_tab_widget.cpp @@ -0,0 +1,894 @@ + + + +#include "lts_tab_widget.h" +#include "lts_tab_bar.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace LtsWidgets +{ +LtsTabWidget::LtsTabWidget(QWidget *parent, bool isSecondary) + : QTabWidget(parent) + , m_tabbar(nullptr) + , m_hasSubTabs(false) + , m_isSecondary(isSecondary) + , m_mainSplitterState(QByteArray()) +{ + setDocumentMode(true); + setMinimumSize(200, 100); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding); + + LtsTabBar *tabBar = new LtsTabBar(isSecondary, this); + setTabBar(tabBar); + + QFont currentFont = tabBar->font(); + + QFont::Capitalization c = QFont::SmallCaps; + bool bold = true; + + if (m_isSecondary) + { + c = QFont::MixedCase; + bold = false; + } + + + currentFont.setCapitalization(c); + currentFont.setBold(bold); + + tabBar->setFont(currentFont); + + connect(this, &LtsTabWidget::currentChanged, + this, &LtsTabWidget::slotCurrentChanged); + + + QSplitter *splitter = static_cast(parent); + if (splitter && + splitter->objectName() == QStringLiteral("mainSplitter") && + splitter->widget(1) == this && + !isSecondary) + { + int tabBarHeight = m_tabbar->height() * 1.33; + int tabBarWidth = 20; + + setStyleSheet(styleSheet() + QStringLiteral("QTabWidget::tab-bar {" + "left: %1px; " + "} ").arg(tabBarWidth)); + QPushButton *maxBtn = new QPushButton(this); + maxBtn->setObjectName(QStringLiteral("MinMaxTabButton")); + maxBtn->setText(QStringLiteral("▲\n▼")); + maxBtn->setFixedSize(tabBarWidth, tabBarHeight * 2); + maxBtn->setStyleSheet(QStringLiteral("QPushButton { " + "border-radius: 0px; " + "border: 1px solid gray; " + "} ")); + connect(maxBtn, &QPushButton::clicked, this, &LtsTabWidget::slotMaximizeButtonClicked); + } +} + + +LtsTabWidget::~LtsTabWidget() +{ + + + clearAll(); +} + + +void LtsTabWidget::slotCurrentChanged(int newIdx) +{ + if (newIdx < 0) + return; + + QWidget *w = widget(newIdx); + + if (w == nullptr) + return; + + LtsDialogHelper *dialog1 = qobject_cast(w); + + if (dialog1 != nullptr) + { + LtsDialog *mainDialog = dialog1->getDialog(); + + if (mainDialog != nullptr) + emit mainDialog->signalFocused(); + + return; + } + + + LtsTabWidget *tab = qobject_cast(w); + + if (tab != nullptr) + { + LtsDialogHelper *dialog2 = qobject_cast(tab->widget(tab->currentIndex())); + + if (dialog2 != nullptr) + { + LtsDialog *mainDialog = dialog2->getDialog(); + + if (mainDialog != nullptr) + emit mainDialog->signalFocused(); + + return; + } + } +} + +void LtsTabWidget::slotMaximizeButtonClicked() +{ + LtsSplitter *splitter = static_cast(parent()); + if (splitter) + { + if (splitter->widget(0)->height() != splitter->widget(0)->minimumHeight()) + { + m_mainSplitterState = splitter->saveState(); + splitter->collapseChild(0); + } + else + { + splitter->restoreState(m_mainSplitterState); + } + } +} + + +void LtsTabWidget::closeRequestTabWidget(LtsTabWidget *tabWidget) +{ + for (int i = 0; i < tabWidget->count(); i++) + { + QWidget *w = tabWidget->widget(i); + + LtsDialogHelper *helper = qobject_cast(w); + + if (helper) + { + LtsDialog *dlg = qobject_cast(helper->getDialog()); + + if (dlg) + dlg->closeRequest(); + } + + LtsTabWidget *tab = qobject_cast(w); + if (tab) + closeRequestTabWidget(tab); + } +} + + +void LtsTabWidget::slotTabCloseRequested(int index) +{ + QWidget *w = widget(index); + + if (w == nullptr) + return; + + LtsDialogHelper *helper = qobject_cast(w); + + if (helper != nullptr) + { + LtsDialog *dialog = helper->getDialog(); + + if (dialog != nullptr) + { + if (dialog->isClosable()) + { + setCurrentIndex(index); + dialog->closeRequest(); + return; + } + + return; + } + } + + LtsTabWidget *tab = qobject_cast(w); + + if (tab != nullptr) + { + if (LtsTabWidget::hasUnsavedChanges(tab)) + { + LtsWidgets::Buttons buttons; + buttons.setFlag(Button::Yes, true); + buttons.setFlag(Button::Cancel, true); + QHash buttonTexts; + buttonTexts.insert(Button::Yes, tr("Discard All")); + const auto ret = LtsMessageDialog::showMessage(this, tr("There are dialogs with unsaved changes.
Please save or discard their changes before closing."), tr("Unsaved changes"), Behaviour::Modal, buttons, buttonTexts); + switch (ret) + { + case Button::Yes: + tab->deleteLater(); + return; + default: + return; + } + } + else + { + + closeRequestTabWidget(tab); + } + } + w->deleteLater(); + + +} + + +void LtsTabWidget::onCheckTabBarVisibility() +{ + + + if (!m_isSecondary) + { + + if (count() < 1) + setHidden(true); + } + else + { + if (count() < 1) + deleteLater(); + } + + + + + + + + + + + + + + + + + + + + + + + + + + + +} + + +void LtsTabWidget::setSubTabsVisible(bool visible) +{ + if (count()) + { + + return; + } + + m_hasSubTabs = visible; +} + + +void LtsTabWidget::addTab(const QString &menuGroup, const QString &identifier, QWidget *widget, + bool setClosable, bool focusNewTab) +{ + + setFocus(); + LtsTabWidget *tab = this; + QString fullName; + + + + + + if (m_hasSubTabs) + { + fullName = identifier; + tab = qobject_cast(m_tabMap.value(menuGroup, nullptr)); + + if (!tab) + { + + tab = new LtsTabWidget(nullptr, true); + tab->setMovable(true); + addWidget(menuGroup, tab, true , focusNewTab); + } + else + { + + tab->setHidden(false); + } + + if (focusNewTab) + setCurrentIndex(indexOf(tab)); + } + else + { + fullName = menuGroup + QLatin1Char('.') + identifier; + + } + + setHidden(false); + + tab->addWidget(fullName, widget, setClosable, focusNewTab); + + + + if (m_hasSubTabs) + { + LtsTabBar *tabbar = tab->tabBar(); + + for (int i = 0; i < tabbar->count(); i++) + { + + if (tabbar->tabButton(i, QTabBar::RightSide)->isHidden()) + { + m_tabbar->tabButton(currentIndex(), QTabBar::RightSide)->hide(); + break; + } + } + } +} + + +void LtsTabWidget::addWidget(const QString &fullName, QWidget *widget, bool setClosable, bool focusNewTab) +{ + QString theName = fullName; + + if (fullName.isEmpty()) + { + + const QString possibleCharacters(QStringLiteral("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")); + const int randomStringLength = 24; + + QString randomString; + for (int i = 0; i < randomStringLength; i++) + { + int index = qrand() % possibleCharacters.length(); + QChar nextChar = possibleCharacters.at(index); + randomString.append(nextChar); + } + + theName = randomString; + } + + widget->setObjectName(theName); + + LtsDialogHelper *helper = qobject_cast(widget); + if (helper) + helper->retranslate(); + + QString title = widget->windowTitle(); + + if (title.isEmpty()) + { + if (helper) + title = QStringLiteral("retranslate() fehlt!"); + else + title = widget->objectName(); + } + + if (helper) + { + connect(helper->getDialog(), &LtsDialog::signalHide, this, static_cast(&LtsTabWidget::onRemoveTab), + static_cast(Qt::QueuedConnection | Qt::UniqueConnection)); + + } + + connect(widget, &QWidget::destroyed, this, static_cast(&LtsTabWidget::onRemoveTab), Qt::DirectConnection); + connect(widget, &QWidget::windowTitleChanged, this, &LtsTabWidget::slotWindowTitleChanged); + + if (helper) + { + m_tabMap.insert(theName, helper); + + } + else + { + m_tabMap.insert(theName, widget); + + } + + int newTabIndex = QTabWidget::addTab(widget, title); + +#ifdef LTS_DEBUG + setTabToolTip(newTabIndex, tr("DEBUG: WindowName: %1").arg(theName)); +#endif + + if (focusNewTab) + setCurrentIndex(newTabIndex); + + LtsTabBar *tabbar = qobject_cast(tabBar()); + + if (tabbar) + { + + CloseButton *closeBtn = new CloseButton(); + connect(closeBtn, &QPushButton::clicked, tabbar, &LtsTabBar::_internal_closeTab); + tabbar->setTabButton(newTabIndex, QTabBar::RightSide, closeBtn); + } + + if (!setClosable) + m_tabbar->tabButton(newTabIndex, QTabBar::RightSide)->hide(); + + if (tabbar) + tabbar->updateCloseButtonColors(); + +} + + +void LtsTabWidget::slotWindowTitleChanged(const QString &title) +{ + QWidget *w = qobject_cast(sender()); + + if (!w) + { + qWarning() << "LtsTabWidget: slotWindowTitleChanged failed! (not a QWidget) " << sender(); + return; + } + + int index = indexOf(w); + + if (index < 0) + { + qWarning() << "LtsTabWidget: slotWindowTitleChanged failed! (widget not found on TabBar)"; + return; + } + + + QString realTitle = title; + QString readOnlyHint; + QStringList sl = title.split(QStringLiteral("- READ-ONLY")); + if (sl.size() == 2) + { + realTitle = sl.first().trimmed(); + readOnlyHint = sl.back().trimmed(); + + + if (readOnlyHint.startsWith(QStringLiteral("("))) + readOnlyHint.remove(0,1); + if (readOnlyHint.endsWith(QStringLiteral(")"))) + readOnlyHint.remove(readOnlyHint.size()-1,1); + + readOnlyHint = tr("Locked by: %1").arg(readOnlyHint); + } + + setTabText(index, realTitle); + + QTabBar *tabBar = this->tabBar(); + + if (!readOnlyHint.isEmpty()) + { + QLabel *readOnly = new QLabel(QStringLiteral("READ-ONLY"), this); + readOnly->setToolTip(readOnlyHint); + readOnly->setPixmap(QIcon(QStringLiteral("icon:warning.svg")).pixmap(QSize(25, 25))); + + tabBar->setTabButton(index, QTabBar::ButtonPosition::LeftSide, readOnly); + } else { + QWidget *readOnly = tabBar->tabButton(index, QTabBar::ButtonPosition::LeftSide); + if (readOnly) + { + + + + + tabBar->setTabButton(index, QTabBar::ButtonPosition::LeftSide, nullptr); + readOnly->setParent(this); + delete readOnly; + } + } +} + + +QWidget *LtsTabWidget::getWidget(const QString &menuGroup, const QString &identifier) +{ + LtsTabWidget *tab = this; + QString fullName; + + if (m_hasSubTabs) + { + fullName = identifier; + tab = qobject_cast(m_tabMap.value(menuGroup, nullptr)); + + if (tab == nullptr) + return nullptr; + } + else + { + fullName = menuGroup + QLatin1Char('.') + identifier; + } + + return tab->getWidget(fullName); +} + + +QWidget *LtsTabWidget::getWidget(const QString &fullName) +{ + return m_tabMap.value(fullName, nullptr); +} + + +bool LtsTabWidget::focusTab(const QString &menuGroup, const QString &identifier) +{ + QPointer tab = this; + QString fullName; + + if (m_hasSubTabs) + { + fullName = identifier; + tab = qobject_cast(m_tabMap.value(menuGroup, nullptr)); + + if (!tab) + { + + return false; + } + setCurrentIndex(indexOf(tab)); + } + else + fullName = menuGroup + QLatin1Char('.') + identifier; + + if (!tab) + return false; + + + LtsSplitter *splitter = static_cast(parent()); + + + + + + QTimer::singleShot(500, this, [splitter, tab] (){ + + + if (splitter && tab && (tab->count() >= 1) && splitter->sizes().count() && !(splitter->sizes().at(1))) + { + splitter->animate(1); + } + }); + return tab->focusWidget(fullName); +} + + +bool LtsTabWidget::focusWidget(const QString &fullName) +{ + QWidget *widget = m_tabMap.value(fullName, nullptr); + + if (widget == nullptr) + return false; + + setCurrentIndex(indexOf(widget)); + widget->setFocus(); + return true; +} + + +bool LtsTabWidget::removeWidget(QWidget * widget) +{ + if (nullptr == widget) + return false; + + for (auto i = m_tabMap.begin(); i != m_tabMap.end(); ++i) + { + QWidget *w = i.value(); + + if (w == nullptr) + continue; + + if (w == widget) + { + m_tabMap.erase(i); + disconnect(widget, &QWidget::destroyed, this, static_cast(&LtsTabWidget::onRemoveTab)); + return true; + } + + if (m_hasSubTabs) + { + LtsTabWidget *tab = qobject_cast(w); + if (tab != nullptr && tab->removeWidget(widget)) + { + return true; + } + } + } + + return false; +} + + +void LtsTabWidget::onRemoveTab(QObject *object) +{ + if (object == nullptr) + { + qWarning() << "onRemoveTab: obj == nullptr"; + return; + } + + QWidget *w = static_cast(object); + + bool rmd = false; + for (auto i = m_tabMap.begin(); i != m_tabMap.end(); ++i) + { + if (i.value() == w) + { + m_tabMap.erase(i); + rmd = true; + break; + } + } + + if (!rmd) + { + qWarning() << "LtsTabWidget::onRemoveTab: widget to remove was not on tab map!"; + } + + int index = indexOf(w); + if (index < 0) + { + + qWarning() << "LtsTabWidget::onRemoveTab: cannot remove widget, as it was not found on the tab widget" << (void *)w; + } + else + { + removeTab(index); + } + + onCheckTabBarVisibility(); +} + + +void LtsTabWidget::setActuatorTabStyle(bool enable) +{ + if (enable) + setTabBar(new LtsTabBar(false, this)); +} + + +LtsTabBar *LtsTabWidget::tabBar() +{ + return m_tabbar; +} + + +void LtsTabWidget::setTabBar(LtsTabBar *tabbar) +{ + if (m_tabbar) + m_tabbar->deleteLater(); + + + QFont currentFont = font(); + currentFont.setCapitalization(QFont::SmallCaps); + currentFont.setBold(true); + + tabbar->setFont(currentFont); + connect(tabbar, &LtsTabBar::tabCloseRequested, this, &LtsTabWidget::slotTabCloseRequested); + + m_tabbar = tabbar; + QTabWidget::setTabBar(m_tabbar); +} + + +void LtsTabWidget::clearAll() +{ + + + for (int i = 0; i < count(); i++) + { + QWidget *w = widget(i); + + if (w == nullptr) + continue; + + disconnect(w, &QWidget::destroyed, this, static_cast(&LtsTabWidget::onRemoveTab)); + + w->deleteLater(); + } + + clear(); +} + + +void LtsTabWidget::aboutToClose() +{ + for (int i = 0; i < count(); i++) + { + if (m_hasSubTabs) + { + LtsTabWidget *tab = qobject_cast(widget(i)); + tab->aboutToClose(); + } + else + { + LtsDialogHelper *helper = qobject_cast(widget(i)); + + if (helper == nullptr) + continue; + + LtsDialog *dialog = qobject_cast(helper->getDialog()); + + if (dialog == nullptr) + continue; + + dialog->aboutToClose(); + } + } +} + + +bool LtsTabWidget::hasUnsavedChanges(LtsTabWidget *tabWidget) +{ + for (int i = 0; i < tabWidget->count(); i++) + { + QWidget *w = tabWidget->widget(i); + + LtsDialogHelper *helper = qobject_cast(w); + + if (helper) + { + LtsDialog *dlg = qobject_cast(helper->getDialog()); + + if (dlg != nullptr) + { + + + if (dlg->hasUnsavedChanges()) + { + int dlgIndex = tabWidget->indexOf(helper); + if (dlgIndex != -1) + tabWidget->setCurrentIndex(dlgIndex); + + + return true; + } + } + } + + LtsTabWidget *tab = qobject_cast(w); + + if (tab) + { + + + bool _hasUnsavedChanges = hasUnsavedChanges(tab); + if (_hasUnsavedChanges) + { + int tabIndex = tabWidget->indexOf(tab); + if (tabIndex != -1) + tabWidget->setCurrentIndex(tabIndex); + + return true; + } + } + + + + } + + return false; +} + + + + + + +void CloseButton::paintEvent(QPaintEvent *) +{ + QPainter p(this); + + p.save(); + p.setPen(QPen(QColor(m_fontColor), 1.5)); + p.setRenderHint(QPainter::Antialiasing); + + + p.drawLine(line()); + + + p.setPen(QPen(QColor(m_fontColor), 2.5)); + + QRectF c = cross(); + + p.drawLine(c.topLeft(), c.bottomRight()); + p.drawLine(c.bottomLeft(), c.topRight()); + + p.restore(); +} + + +QLineF CloseButton::line() const +{ + qreal forceBlur = 0.001; + qreal lineStart = height() * (0.25 / 2) + forceBlur; + qreal lineSize = height() - lineStart; + + return QLineF(QPointF(lineStart, lineStart), QPointF(lineStart, lineSize)); +} + + +QRectF CloseButton::cross() const +{ + QLineF l = line(); + + qreal lineLeft = l.x1(); + qreal forceBlur = 0.001; + + qreal crossStartX = lineLeft * 3; + qreal crossStartY = lineLeft * 2; + qreal crossEndX = width(); crossEndX -= crossEndX * 0.15 + forceBlur; + qreal crossEndY = height() - crossStartY; + + return QRectF(QPointF(crossStartX, crossStartY), QPointF(crossEndX, crossEndY)); +} + + +QSize CloseButton::sizeHint() const +{ + QFontMetrics fm (QApplication::font()); + int fontHeight = fm.height(); + + int fontHeightQuarter = fontHeight * 0.25; + int finalSize = fontHeight + fontHeightQuarter; +#ifdef TABLET_MODE + finalSize = finalSize*1.5; +#endif + return QSize(finalSize, finalSize); +} + + +void CloseButton::mousePressEvent(QMouseEvent *e) +{ + e->ignore(); + m_isPressed = true; +} + + +void CloseButton::mouseReleaseEvent(QMouseEvent *e) +{ + bool isPressed = m_isPressed; + m_isPressed = false; + + if (!isPressed) + { + e->ignore(); + return; + } + + QRectF c = cross(); + QRect adjusted = QRect(QPoint(floor(c.x()), floor(c.y())), QPoint(ceil(c.right()), ceil(c.bottom() +1 ))); + + if (!adjusted.contains(e->localPos().toPoint())) + { + + e->ignore(); + return; + } + + + e->accept(); + emit clicked(); +} +} diff --git a/Lts/Common/LtsWidgets/lts_tab_widget.h b/Lts/Common/LtsWidgets/lts_tab_widget.h new file mode 100755 index 000000000..e7fbeac27 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_tab_widget.h @@ -0,0 +1,163 @@ + + + +#pragma once + +#include "lts_base.h" +#include "lts_widgets_export.h" + +#include +#include +#include + + +namespace LtsWidgets +{ +class LtsDialogHelper; +class LtsTabBar; + + + +class LTSWIDGETS_EXPORT LtsTabWidget : public QTabWidget +{ + Q_OBJECT +public: + + + explicit LtsTabWidget(QWidget *parent = nullptr, bool isSecondary = false); + ~LtsTabWidget(); + + + + void setActuatorTabStyle(bool enable); + + + + void clearAll(); + + + void aboutToClose(); + + + + LtsTabBar *tabBar(); + + + + void setTabBar(LtsTabBar *tabbar); + + + + bool focusTab(const QString &menuGroup, const QString &identifier); + + + + void setSubTabsVisible(bool visible); + + + + QWidget *getWidget(const QString &menuGroup, const QString &identifier); + + + + static bool hasUnsavedChanges(LtsTabWidget *tabWidget); + + + +signals: + + + void signalCheckTabBarVisibility(); + + +public slots: + + + void addTab(const QString &menuGroup, const QString &identifier, QWidget *widget, + bool setClosable = true, bool focusNewTab = true); + + + + bool removeWidget(QWidget * widget); + +private slots: + + + + + + + + + + + void onRemoveTab(QObject *object); + + + + void onCheckTabBarVisibility(); + + + + void slotTabCloseRequested(int index); + + void slotCurrentChanged(int newIdx); + + void slotMaximizeButtonClicked(); + +private: + + + void addWidget(const QString &fullName, QWidget *widget, bool setClosable, bool focusNewTab = true); + + + + QWidget *getWidget(const QString &fullName); + + + + bool focusWidget(const QString &fullName); + + + + void slotWindowTitleChanged(const QString &title); + + void closeRequestTabWidget(LtsTabWidget *tabWidget); + + QPoint startPos; + LtsTabBar *m_tabbar = nullptr; + QPalette m_palette; + + + QMap m_tabMap; + + + bool m_hasSubTabs; + bool m_isSecondary; + + QByteArray m_mainSplitterState; +}; + + +class CloseButton : public QAbstractButton +{ + Q_OBJECT + +public: + using QAbstractButton::QAbstractButton; + + void setFontColor(const QColor &fontColor) { m_fontColor = fontColor; } + QRectF cross() const; + QLineF line() const; + +protected: + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void paintEvent(QPaintEvent *e) override; + QSize sizeHint() const override; + +private: + QColor m_fontColor; + bool m_isPressed = false; +}; +} diff --git a/Lts/Common/LtsWidgets/lts_text_edit_dialog.cpp b/Lts/Common/LtsWidgets/lts_text_edit_dialog.cpp new file mode 100755 index 000000000..772f99f16 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_text_edit_dialog.cpp @@ -0,0 +1,137 @@ + + + +#include "lts_text_edit_dialog.h" +#include "lts_text_editor.h" + +#include + +#if 1 +#define USE_TEXT_EDITOR_2 +#else +#define USE_TEXT_EDITOR +#endif + +namespace LtsWidgets +{ + +LtsTextEditDialog::LtsTextEditDialog(QWidget *parent) + : LtsDialog(parent, ButtonCombination::OkApplyCancel) + #ifdef USE_TEXT_EDITOR + , m_textEditor(new LtsTextEditor(this)) + #else + , m_textEditor2(new LtsTextEditor2(this)) + #endif +{ + QHBoxLayout *hbox = new QHBoxLayout(this); +#ifdef USE_TEXT_EDITOR + hbox->addWidget(m_textEditor); + connect(m_textEditor->textEdit(), &QTextEdit::textChanged, this, [this]() { + emit signalTextChanged(this->getHtmlText()); + }); + connect(m_textEditor, &LtsTextEditor::backgroundColorChanged, + this, &LtsTextEditDialog::onBackgroundColorChanged); +#else + hbox->addWidget(m_textEditor2); + connect(m_textEditor2->textEdit(), &QTextEdit::textChanged, this, [this]() { + emit signalTextChanged(this->getHtmlText()); + }); + connect(m_textEditor2, &LtsTextEditor2::backgroundColorChanged, + this, &LtsTextEditDialog::onBackgroundColorChanged); +#endif + + + enableBackgroundColor(false); +} + +LtsTextEditDialog::~LtsTextEditDialog() +{} + +void LtsTextEditDialog::enableBackgroundColor(bool onOff) +{ +#ifdef USE_TEXT_EDITOR + m_textEditor->enableBackgroundColor(onOff); +#else + m_textEditor2->enableBackgroundColor(onOff); +#endif +} + + + + +void LtsTextEditDialog::retranslate() +{ + + QString name = tr("Edit Text"); + translate(name); +} + +bool LtsTextEditDialog::apply() +{ +#ifdef USE_TEXT_EDITOR + QString textHtml = m_textEditor->textEdit()->document()->toHtml(); + const QString backGroundColor = QLatin1String("background-color:'%1'; ").arg(m_textEditor->backgroundColor().name()); + +#else + QString textHtml = m_textEditor2->textEdit()->document()->toHtml(); + const QString backGroundColor = QLatin1String("background-color:'%1'; ").arg(m_textEditor2->backgroundColor().name()); +#endif + + emit signalTextApplied(textHtml); + + const int insertBackGroundColorIndex = textHtml.indexOf(QLatin1String("font-family")); + textHtml.insert(insertBackGroundColorIndex, backGroundColor); + + emit signalTextAppliedWithBackGroundColor(textHtml); + + return true; +} + +void LtsTextEditDialog::setHtml(const QString &htmlText) +{ + + +#ifdef USE_TEXT_EDITOR + m_textEditor->textEdit()->document()->setHtml(htmlText); +#else + m_textEditor2->textEdit()->document()->setHtml(htmlText); +#endif +} + +QString LtsTextEditDialog::getHtmlText() const +{ +#ifdef USE_TEXT_EDITOR + if(m_textEditor && m_textEditor->textEdit() && m_textEditor->textEdit()->document()) + return m_textEditor->textEdit()->document()->toHtml(); +#else + if(m_textEditor2 && m_textEditor2->textEdit() && m_textEditor2->textEdit()->document()) + return m_textEditor2->textEdit()->document()->toHtml(); +#endif + return {}; +} + +void LtsTextEditDialog::setInitialBackgroundColor(const QColor &bgColor) +{ +#ifdef USE_TEXT_EDITOR + if (m_textEditor) + m_textEditor->setInitialBackgroundColor(bgColor); +#else + if (m_textEditor2) + m_textEditor2->setInitialBackgroundColor(bgColor); +#endif +} + +void LtsTextEditDialog::dialogCanceled() +{ + emit signalDialogCanceled(); + BaseClass::dialogCanceled(); +} + +void LtsTextEditDialog::onBackgroundColorChanged(const QColor &backgroundColor) +{ + slotChangesMade(); + emit signalBackgroundColorChanged(backgroundColor); +} + +} + diff --git a/Lts/Common/LtsWidgets/lts_text_edit_dialog.h b/Lts/Common/LtsWidgets/lts_text_edit_dialog.h new file mode 100755 index 000000000..bcec6afc0 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_text_edit_dialog.h @@ -0,0 +1,55 @@ + + +#pragma once + +#include +#include +#include + + +namespace LtsWidgets +{ + +class LTSWIDGETS_EXPORT LtsTextEditDialog : public LtsWidgets::LtsDialog +{ + Q_OBJECT + + using BaseClass = LtsWidgets::LtsDialog; + +public: + LtsTextEditDialog(QWidget *parent = nullptr); + ~LtsTextEditDialog(); + + void enableBackgroundColor(bool onOff); + void setInitialBackgroundColor(const QColor &bgColor); + + void retranslate() override; + bool apply() override; + void setHtml(const QString &htmlText); + QString getHtmlText() const; + +protected: + void dialogCanceled() override; + +private: + LtsTextEditor *m_textEditor{nullptr}; + LtsTextEditor2 *m_textEditor2{nullptr}; + + +protected Q_SLOTS: + void onBackgroundColorChanged(const QColor &backgroundColor); + +Q_SIGNALS: + void signalTextChanged(const QString &htmlText); + void signalTextApplied(const QString &htmlText); + void signalTextAppliedWithBackGroundColor(const QString &htmlText); + void signalBackgroundColorChanged(const QColor &bgColor); + void signalDialogApplied(); + void signalDialogCanceled(); + +}; + +} + + + diff --git a/Lts/Common/LtsWidgets/lts_text_editor.cpp b/Lts/Common/LtsWidgets/lts_text_editor.cpp new file mode 100755 index 000000000..54fb681ef --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_text_editor.cpp @@ -0,0 +1,391 @@ +#include "lts_text_editor.h" + +#include "ui_lts_text_editor.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace LtsWidgets +{ + +LtsTextEditor::LtsTextEditor(QWidget *parent) + : QWidget(parent) + , m_ui(new Ui::LtsTextEditor) +{ + m_ui->setupUi(this); + + connect(m_ui->textEdit, &QTextEdit::currentCharFormatChanged, + this, &LtsTextEditor::slotCurrentCharFormatChanged); + connect(m_ui->textEdit, &QTextEdit::cursorPositionChanged, + this, &LtsTextEditor::slotAlignmentChanged); + + connect(m_ui->textEdit->document(), &QTextDocument::undoAvailable, + m_ui->pbUndo, &QToolButton::setEnabled); + connect(m_ui->textEdit->document(), &QTextDocument::redoAvailable, + m_ui->pbRedo, &QToolButton::setEnabled); + + m_ui->pbUndo->setEnabled(m_ui->textEdit->document()->isUndoAvailable()); + m_ui->pbRedo->setEnabled(m_ui->textEdit->document()->isRedoAvailable()); + + connect(QApplication::clipboard(), &QClipboard::dataChanged, + this, &LtsTextEditor::slotClipboardDataChanged); + + + connect(m_ui->pbCut, &QToolButton::clicked, + m_ui->textEdit, &QTextEdit::cut); + connect(m_ui->pbCopy, &QToolButton::clicked, + m_ui->textEdit, &QTextEdit::copy); + connect(m_ui->pbPaste, &QToolButton::clicked, + m_ui->textEdit, &QTextEdit::paste); + + connect(m_ui->pbUndo, &QToolButton::clicked, + m_ui->textEdit, &QTextEdit::undo); + connect(m_ui->pbRedo, &QToolButton::clicked, + m_ui->textEdit, &QTextEdit::redo); + + connect(m_ui->pbBold, &QToolButton::clicked, + this, &LtsTextEditor::slotTextBold); + connect(m_ui->pbItalic, &QToolButton::clicked, + this, &LtsTextEditor::slotTextItalic); + connect(m_ui->pbUnderline, &QToolButton::clicked, + this, &LtsTextEditor::slotTextUnderline); + + connect(m_ui->pbTextColor, &QToolButton::clicked, + this, &LtsTextEditor::slotTextColor); + connect(m_ui->pbBackgroundColor, &QToolButton::clicked, + this, &LtsTextEditor::slotBackgroundColor); + + connect(m_ui->pbAlignLeft, &QToolButton::toggled, + this, &LtsTextEditor::slotTextAlign); + connect(m_ui->pbAlignCenter, &QToolButton::toggled, + this, &LtsTextEditor::slotTextAlign); + connect(m_ui->pbAlignRight, &QToolButton::toggled, + this, &LtsTextEditor::slotTextAlign); + connect(m_ui->pbAlignJustify, &QToolButton::toggled, + this, &LtsTextEditor::slotTextAlign); + + + QFont textFont(QStringLiteral("Sans Serif")); + textFont.setStyleHint(QFont::SansSerif); + m_ui->textEdit->setFont(textFont); + slotFontChanged(m_ui->textEdit->font()); + slotTextColorChanged(m_ui->textEdit->textColor()); + slotAlignmentChanged(); + + slotBackgroundColorChanged(m_backgroundColor); + + + connect(m_ui->cbFonts, QOverload::of(&QComboBox::activated), + this, &LtsTextEditor::slotTextFamily); + + const QList standardSizes = QFontDatabase::standardSizes(); + for (int size : standardSizes) + m_ui->cbFontSize->addItem(QString::number(size)); + m_ui->cbFontSize->setCurrentIndex(standardSizes.indexOf(QApplication::font().pointSize())); + + connect(m_ui->cbFontSize, QOverload::of(&QComboBox::activated), + this, &LtsTextEditor::slotTextSize); + + connect(m_ui->cbFontSize->lineEdit(), &QLineEdit::editingFinished, + this, &LtsTextEditor::slotTextSizeEditingFinished); + + m_ui->textEdit->setFocus(); +} + + +LtsTextEditor::~LtsTextEditor() +{ + delete m_ui; +} + + +QTextEdit *LtsTextEditor::textEdit() +{ + return m_ui->textEdit; +} + +QColor LtsTextEditor::backgroundColor() const +{ + return m_backgroundColor; +} + +void LtsTextEditor::enableBackgroundColor(bool onOff) +{ + m_ui->pbBackgroundColor->setVisible(onOff); +} + +void LtsTextEditor::setInitialBackgroundColor(const QColor &bgColor) +{ + if (bgColor.isValid()) + m_backgroundColor = bgColor; + slotBackgroundColorChanged(m_backgroundColor); +} + + +void LtsTextEditor::slotTextBold() +{ + QTextCharFormat fmt; + fmt.setFontItalic(m_ui->pbItalic->isChecked()); + fmt.setFontUnderline(m_ui->pbUnderline->isChecked()); + fmt.setFontWeight(m_ui->pbBold->isChecked() ? QFont::Bold : QFont::Normal); + if (m_ui->textEdit->document()->isEmpty()) + { + const QString &f = m_ui->cbFonts->currentText(); + fmt.setFontFamily(f); + fmt.setFontFamilies({f}); + fmt.setForeground(m_foregroundColor); + const qreal pointSize = (m_ui->cbFontSize->currentText().toFloat() > 0 ? m_ui->cbFontSize->currentText().toFloat() : 11); + fmt.setFontPointSize(pointSize); + } + slotMergeFormatOnWordOrSelection(fmt); +} + + +void LtsTextEditor::slotTextUnderline() +{ + QTextCharFormat fmt; + fmt.setFontItalic(m_ui->pbItalic->isChecked()); + fmt.setFontUnderline(m_ui->pbUnderline->isChecked()); + fmt.setFontWeight(m_ui->pbBold->isChecked() ? QFont::Bold : QFont::Normal); + if (m_ui->textEdit->document()->isEmpty()) + { + const QString &f = m_ui->cbFonts->currentText(); + fmt.setFontFamily(f); + fmt.setFontFamilies({f}); + fmt.setForeground(m_foregroundColor); + const qreal pointSize = (m_ui->cbFontSize->currentText().toFloat() > 0 ? m_ui->cbFontSize->currentText().toFloat() : 11); + fmt.setFontPointSize(pointSize); + } + slotMergeFormatOnWordOrSelection(fmt); +} + + +void LtsTextEditor::slotTextItalic() +{ + QTextCharFormat fmt; + fmt.setFontItalic(m_ui->pbItalic->isChecked()); + fmt.setFontUnderline(m_ui->pbUnderline->isChecked()); + fmt.setFontWeight(m_ui->pbBold->isChecked() ? QFont::Bold : QFont::Normal); + if (m_ui->textEdit->document()->isEmpty()) + { + const QString &f = m_ui->cbFonts->currentText(); + fmt.setFontFamily(f); + fmt.setFontFamilies({f}); + fmt.setForeground(m_foregroundColor); + const qreal pointSize = (m_ui->cbFontSize->currentText().toFloat() > 0 ? m_ui->cbFontSize->currentText().toFloat() : 11); + fmt.setFontPointSize(pointSize); + } + slotMergeFormatOnWordOrSelection(fmt); +} + + +void LtsTextEditor::slotTextFamily(const QString &f) +{ + QTextCharFormat fmt; + if (m_ui->textEdit->document()->isEmpty()) + { + fmt.setFontItalic(m_ui->pbItalic->isChecked()); + fmt.setFontUnderline(m_ui->pbUnderline->isChecked()); + fmt.setFontWeight(m_ui->pbBold->isChecked() ? QFont::Bold : QFont::Normal); + + const qreal pointSize = (m_ui->cbFontSize->currentText().toFloat() > 0 ? m_ui->cbFontSize->currentText().toFloat() : 11); + fmt.setFontPointSize(pointSize); + fmt.setForeground(m_foregroundColor); + } + fmt.setFontFamily(f); + fmt.setFontFamilies({f}); + slotMergeFormatOnWordOrSelection(fmt); +} + + +void LtsTextEditor::slotTextSize(const QString &p) +{ + qreal pointSize = p.toFloat(); + + if (p.toFloat() > 0) + { + QSignalBlocker signalBlocker(m_ui->cbFontSize->lineEdit()); + m_ui->cbFontSize->clearFocus(); + + QTextCharFormat fmt; + if (m_ui->textEdit->document()->isEmpty()) + { + const QString &f = m_ui->cbFonts->currentText(); + fmt.setFontFamily(f); + fmt.setFontFamilies({f}); + fmt.setForeground(m_foregroundColor); + + fmt.setFontItalic(m_ui->pbItalic->isChecked()); + fmt.setFontUnderline(m_ui->pbUnderline->isChecked()); + fmt.setFontWeight(m_ui->pbBold->isChecked() ? QFont::Bold : QFont::Normal); + + } + fmt.setFontPointSize(pointSize); + slotMergeFormatOnWordOrSelection(fmt); + } +} + +void LtsTextEditor::slotTextSizeEditingFinished() +{ + qreal pointSize = m_ui->cbFontSize->currentText().toFloat(); + + if (pointSize > 0) + { + QTextCharFormat textCharFormat; + + textCharFormat.setFontPointSize(pointSize); + + QTextCursor cursor = m_ui->textEdit->textCursor(); + + if (cursor.charFormat().font().pointSizeF() == pointSize) + return; + + if (!cursor.hasSelection()) + cursor.select(QTextCursor::WordUnderCursor); + + cursor.mergeCharFormat(textCharFormat); + } +} + +void LtsTextEditor::slotTextColor() +{ + QColor col = QColorDialog::getColor(m_ui->textEdit->textColor(), this); + if (!col.isValid()) + return; + + QTextCharFormat fmt; + if (m_ui->textEdit->document()->isEmpty()) + { + fmt.setFontItalic(m_ui->pbItalic->isChecked()); + fmt.setFontUnderline(m_ui->pbUnderline->isChecked()); + fmt.setFontWeight(m_ui->pbBold->isChecked() ? QFont::Bold : QFont::Normal); + + const QString &f = m_ui->cbFonts->currentText(); + fmt.setFontFamily(f); + fmt.setFontFamilies({f}); + const qreal pointSize = (m_ui->cbFontSize->currentText().toFloat() > 0 ? m_ui->cbFontSize->currentText().toFloat() : 11); + fmt.setFontPointSize(pointSize); + } + fmt.setForeground(col); + slotMergeFormatOnWordOrSelection(fmt); + slotTextColorChanged(col); +} + +void LtsTextEditor::slotBackgroundColor() +{ + QColor col = QColorDialog::getColor(m_backgroundColor.isValid() ? m_backgroundColor : Qt::white, this); + if (!col.isValid()) + return; + + m_backgroundColor = col; + slotBackgroundColorChanged(m_backgroundColor); + emit backgroundColorChanged(m_backgroundColor); +} + + +void LtsTextEditor::slotCurrentCharFormatChanged(const QTextCharFormat &format) +{ + slotFontChanged(format.font()); + slotTextColorChanged(format.foreground().color()); +} + + +void LtsTextEditor::slotFontChanged(const QFont &f) +{ + m_ui->cbFonts->setCurrentIndex(m_ui->cbFonts->findText(f.family())); + + QSignalBlocker comboBoxsignalBlocker(m_ui->cbFontSize); + QSignalBlocker lineEditSignalBlocker(m_ui->cbFontSize->lineEdit()); + QString fontSizeString = QString::number(f.pointSize()); + int32_t fontSizeIndex = m_ui->cbFontSize->findText(fontSizeString); + + if (fontSizeIndex >= 0) + m_ui->cbFontSize->setCurrentIndex(fontSizeIndex); + else if (m_ui->cbFontSize->currentText() != fontSizeString) + m_ui->cbFontSize->setCurrentText(fontSizeString); + + m_ui->pbBold->setChecked(f.bold()); + m_ui->pbItalic->setChecked(f.italic()); + m_ui->pbUnderline->setChecked(f.underline()); +} + + +void LtsTextEditor::slotMergeFormatOnWordOrSelection(const QTextCharFormat &format) +{ + QTextCursor cursor = m_ui->textEdit->textCursor(); + if (!cursor.hasSelection()) + cursor.select(QTextCursor::WordUnderCursor); + + cursor.mergeCharFormat(format); + + + if (m_ui->textEdit->document()->isEmpty()) + m_ui->textEdit->setTextCursor(cursor); +} + + +void LtsTextEditor::slotTextColorChanged(const QColor &c) +{ + QPixmap pix(32, 32); + pix.fill(c); + m_ui->pbTextColor->setIcon(pix); + m_foregroundColor = c; +} + + +void LtsTextEditor::slotBackgroundColorChanged(const QColor &bgColor) +{ + QPixmap pix(32, 32); + pix.fill(bgColor); + m_ui->pbBackgroundColor->setIcon(pix); +} + + +void LtsTextEditor::slotTextAlign() +{ + QToolButton *b = qobject_cast(sender()); + + if (b == nullptr) + return; + + if (b == m_ui->pbAlignLeft) + m_ui->textEdit->setAlignment(Qt::AlignLeft | Qt::AlignAbsolute); + else if (b == m_ui->pbAlignCenter) + m_ui->textEdit->setAlignment(Qt::AlignHCenter); + else if (b == m_ui->pbAlignRight) + m_ui->textEdit->setAlignment(Qt::AlignRight | Qt::AlignAbsolute); + else if (b == m_ui->pbAlignJustify) + m_ui->textEdit->setAlignment(Qt::AlignJustify); +} + + +void LtsTextEditor::slotAlignmentChanged() +{ + auto a = m_ui->textEdit->alignment(); + + if (a & Qt::AlignLeft) + m_ui->pbAlignLeft->setChecked(true); + if (a & Qt::AlignHCenter) + m_ui->pbAlignCenter->setChecked(true); + if (a & Qt::AlignRight) + m_ui->pbAlignRight->setChecked(true); + if (a & Qt::AlignJustify) + m_ui->pbAlignJustify->setChecked(true); +} + + +void LtsTextEditor::slotClipboardDataChanged() +{ + if (const QMimeData *md = QApplication::clipboard()->mimeData()) + m_ui->pbPaste->setEnabled(md->hasText()); +} + +} diff --git a/Lts/Common/LtsWidgets/lts_text_editor.h b/Lts/Common/LtsWidgets/lts_text_editor.h new file mode 100755 index 000000000..385e0959c --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_text_editor.h @@ -0,0 +1,61 @@ +#pragma once + +#include + +#include + + +namespace Ui +{ +class LtsTextEditor; +} + +class QTextEdit; +class QTextCharFormat; + + +namespace LtsWidgets +{ + +class LTSWIDGETS_EXPORT LtsTextEditor : public QWidget +{ + Q_OBJECT +public: + LtsTextEditor(QWidget *parent = nullptr); + ~LtsTextEditor(); + void enableBackgroundColor(bool onOff); + void setInitialBackgroundColor(const QColor &bgColor); + QTextEdit *textEdit(); + QColor backgroundColor() const; + +Q_SIGNALS: + void backgroundColorChanged(const QColor &bgColor); + +private: + Ui::LtsTextEditor *m_ui; + QColor m_backgroundColor{Qt::white}; + QColor m_foregroundColor{Qt::black}; + +private slots: + void slotCurrentCharFormatChanged(const QTextCharFormat &format); + void slotFontChanged(const QFont &f); + void slotTextColorChanged(const QColor &c); + void slotBackgroundColorChanged(const QColor &bgColor); + void slotAlignmentChanged(); + void slotMergeFormatOnWordOrSelection(const QTextCharFormat &format); + void slotClipboardDataChanged(); + void slotTextColor(); + void slotBackgroundColor(); + void slotTextSize(const QString &p); + void slotTextSizeEditingFinished(); + void slotTextFamily(const QString &f); + void slotTextItalic(); + void slotTextUnderline(); + void slotTextBold(); + + void slotTextAlign(); +}; + +} + + diff --git a/Lts/Common/LtsWidgets/lts_text_editor.ui b/Lts/Common/LtsWidgets/lts_text_editor.ui new file mode 100755 index 000000000..dccc94a58 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_text_editor.ui @@ -0,0 +1,598 @@ + + + LtsTextEditor + + + + 0 + 0 + 1223 + 429 + + + + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + + + + + 42 + 42 + + + + + 42 + 42 + + + + Cut + + + + + + + :/style/edit-cut.svg:/style/edit-cut.svg + + + + 32 + 32 + + + + + + + + + 42 + 42 + + + + + 42 + 42 + + + + Copy + + + + + + + :/style/edit-copy.svg:/style/edit-copy.svg + + + + 32 + 32 + + + + + + + + + 42 + 42 + + + + + 42 + 42 + + + + Paste + + + + + + + :/style/edit-paste.svg:/style/edit-paste.svg + + + + 32 + 32 + + + + + + + + + 42 + 42 + + + + + 42 + 42 + + + + Undo + + + + + + + :/style/undo.svg:/style/undo.svg + + + + 32 + 32 + + + + + + + + + 42 + 42 + + + + + 42 + 42 + + + + Redo + + + + + + + :/style/redo.svg:/style/redo.svg + + + + 32 + 32 + + + + + + + + + + + Qt::Vertical + + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + + QLayout::SetMinimumSize + + + + + + 42 + 42 + + + + + 42 + 42 + + + + Align text left + + + + + + + :/style/align-left.svg:/style/align-left.svg + + + + 32 + 32 + + + + true + + + true + + + + + + + + 42 + 42 + + + + + 42 + 42 + + + + Center text + + + + + + + :/style/align-center.svg:/style/align-center.svg + + + + 32 + 32 + + + + true + + + true + + + + + + + + 42 + 42 + + + + + 42 + 42 + + + + Align text right + + + + + + + :/style/align-right.svg:/style/align-right.svg + + + + 32 + 32 + + + + true + + + true + + + + + + + + 42 + 42 + + + + + 42 + 42 + + + + Justify text + + + + + + + :/style/align-justify.svg:/style/align-justify.svg + + + + 32 + 32 + + + + true + + + true + + + + + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + + + + + 42 + 42 + + + + + 42 + 42 + + + + Bold + + + + + + + :/style/text-bold.svg:/style/text-bold.svg + + + + 32 + 32 + + + + true + + + + + + + + 42 + 42 + + + + + 42 + 42 + + + + Italic + + + + + + + :/style/text-italic.svg:/style/text-italic.svg + + + + 32 + 32 + + + + true + + + + + + + + 42 + 42 + + + + + 42 + 42 + + + + Underline + + + + + + + :/style/text-underline.svg:/style/text-underline.svg + + + + 32 + 32 + + + + true + + + + + + + + + + false + + + Sans Serif + + + + + + + true + + + QComboBox::NoInsert + + + + + + + + 42 + 42 + + + + + 42 + 42 + + + + Change text color + + + + + + + 32 + 32 + + + + + + + + + 42 + 42 + + + + + 42 + 42 + + + + Change background color + + + + + + + 32 + 32 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + diff --git a/Lts/Common/LtsWidgets/lts_text_editor2.cpp b/Lts/Common/LtsWidgets/lts_text_editor2.cpp new file mode 100755 index 000000000..0a0540c85 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_text_editor2.cpp @@ -0,0 +1,920 @@ + + + + +#include "lts_text_editor2.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(QT_PRINTSUPPORT_LIB) +#include +#if QT_CONFIG(printer) +#if QT_CONFIG(printdialog) +#include +#endif +#include +#if QT_CONFIG(printpreviewdialog) +#include +#endif +#endif +#endif + + +#include +#ifdef Q_OS_MAC +inline QString rsrcPath(){ QStringLiteral(":/images/mac");} +#else +inline QString rsrcPath(){return QStringLiteral(":/images/win");} +#endif + +namespace LtsWidgets +{ +LtsTextEditor2::LtsTextEditor2(QWidget *parent) + : QMainWindow(parent) +{ + setWindowFlags(Qt::CustomizeWindowHint ); + setWindowTitle(QCoreApplication::applicationName()); + + m_textEdit = new QTextEdit(this); + connect(m_textEdit, &QTextEdit::currentCharFormatChanged, + this, &LtsTextEditor2::currentCharFormatChanged); + connect(m_textEdit, &QTextEdit::cursorPositionChanged, + this, &LtsTextEditor2::cursorPositionChanged); + setCentralWidget(m_textEdit); + + setToolButtonStyle(Qt::ToolButtonFollowStyle); + setupFileActions(); + setupEditActions(); + setupTextActions(); + + { + QMenu *helpMenu = menuBar()->addMenu(tr("Help")); + helpMenu->addAction(tr("About"), this, &LtsTextEditor2::about); + helpMenu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt); + } + + QFont textFont(QStringLiteral("Helvetica")); + textFont.setStyleHint(QFont::SansSerif); + m_textEdit->setFont(textFont); + fontChanged(m_textEdit->font()); + colorChanged(m_textEdit->textColor()); + alignmentChanged(m_textEdit->alignment()); + + connect(m_textEdit->document(), &QTextDocument::modificationChanged, + m_actionSave, &QAction::setEnabled); + connect(m_textEdit->document(), &QTextDocument::modificationChanged, + this, &QWidget::setWindowModified); + connect(m_textEdit->document(), &QTextDocument::undoAvailable, + m_actionUndo, &QAction::setEnabled); + connect(m_textEdit->document(), &QTextDocument::redoAvailable, + m_actionRedo, &QAction::setEnabled); + + setWindowModified(m_textEdit->document()->isModified()); + m_actionSave->setEnabled(m_textEdit->document()->isModified()); + m_actionUndo->setEnabled(m_textEdit->document()->isUndoAvailable()); + m_actionRedo->setEnabled(m_textEdit->document()->isRedoAvailable()); + +#ifndef QT_NO_CLIPBOARD + m_actionCut->setEnabled(false); + connect(m_textEdit, &QTextEdit::copyAvailable, m_actionCut, &QAction::setEnabled); + m_actionCopy->setEnabled(false); + connect(m_textEdit, &QTextEdit::copyAvailable, m_actionCopy, &QAction::setEnabled); + + connect(QApplication::clipboard(), &QClipboard::dataChanged, this, &LtsTextEditor2::clipboardDataChanged); +#endif + + m_textEdit->setFocus(); + setCurrentFileName(QString()); + + this->menuBar()->hide(); + m_actionSave->setVisible(false); + m_actionOpen->setVisible(false); + m_actionQuit->setVisible(false); + m_actionSaveAs->setVisible(false); + m_actionNew->setVisible(false); + +#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printer) + actionPrint->setVisible(false); + actionPreview->setVisible(false); + actionExport->setVisible(false); +#endif + m_actionToggleCheckState->setVisible(false); + m_actionToggleCheckState->setVisible(false); + m_actionIndentLess->setVisible(false); + m_actionIndentMore->setVisible(false); + m_comboStyleAction->setVisible(false); + m_actionToggleCheckState->setVisible(false); + + +} + +LtsTextEditor2::~LtsTextEditor2() +{ + +} + +QTextEdit *LtsTextEditor2::textEdit() +{ + return m_textEdit; +} +QColor LtsTextEditor2::backgroundColor() const +{ + return m_backgroundColor; +} + +void LtsTextEditor2::setInitialBackgroundColor(const QColor &bgColor) +{ + if (bgColor.isValid()) + m_backgroundColor = bgColor; + slotBackgroundColorChanged(m_backgroundColor); +} +void LtsTextEditor2::enableBackgroundColor(bool onOff) +{ + m_actionBackgroundColor->setVisible(onOff); +} + + + +void LtsTextEditor2::slotBackgroundColorChanged(const QColor &bgColor) +{ + QPixmap pix(16, 16); + pix.fill(bgColor); + m_actionBackgroundColor->setIcon(pix); +} +void LtsTextEditor2::slotBackgroundColor() +{ + QColor col = QColorDialog::getColor(m_backgroundColor.isValid() ? m_backgroundColor : Qt::white, this); + if (!col.isValid()) + return; + + m_backgroundColor = col; + slotBackgroundColorChanged(m_backgroundColor); + emit backgroundColorChanged(m_backgroundColor); +} + + +void LtsTextEditor2::setupFileActions() +{ + QToolBar *tb = addToolBar(tr("File Actions")); + QMenu *menu = menuBar()->addMenu(tr("&File")); + + const QIcon newIcon = QIcon::fromTheme(QStringLiteral("document-new"), QIcon(rsrcPath() + QStringLiteral("/filenew.png"))); + m_actionNew = menu->addAction(newIcon, tr("&New"), this, &LtsTextEditor2::onFileNew); + tb->addAction(m_actionNew); + m_actionNew->setPriority(QAction::LowPriority); + m_actionNew->setShortcut(QKeySequence::New); + + const QIcon openIcon = QIcon::fromTheme(QStringLiteral("document-open"), QIcon(rsrcPath() + QStringLiteral("/fileopen.png"))); + m_actionOpen = menu->addAction(openIcon, tr("&Open..."), this, &LtsTextEditor2::onFileOpen); + m_actionOpen->setShortcut(QKeySequence::Open); + tb->addAction(m_actionOpen); + + menu->addSeparator(); + + const QIcon saveIcon = QIcon::fromTheme(QStringLiteral("document-save"), QIcon(rsrcPath()+ QStringLiteral("/filesave.png"))); + m_actionSave = menu->addAction(saveIcon, tr("&Save"), this, &LtsTextEditor2::onFileSave); + m_actionSave->setShortcut(QKeySequence::Save); + m_actionSave->setEnabled(false); + tb->addAction(m_actionSave); + + m_actionSaveAs = menu->addAction(tr("Save &As..."), this, &LtsTextEditor2::onFileSaveAs); + m_actionSaveAs->setPriority(QAction::LowPriority); + menu->addSeparator(); + +#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printer) + const QIcon printIcon = QIcon::fromTheme(QStringLiteral("document-print"), QIcon(rsrcPath()+ QStringLiteral("/fileprint.png"))); + actionPrint = menu->addAction(printIcon, tr("&Print..."), this, &LtsTextEditor2::filePrint); + actionPrint->setPriority(QAction::LowPriority); + actionPrint->setShortcut(QKeySequence::Print); + tb->addAction(actionPrint); + + const QIcon filePrintIcon = QIcon::fromTheme(QStringLiteral("fileprint"), QIcon(rsrcPath()+ QStringLiteral("/fileprint.png"))); + actionPreview = menu->addAction(filePrintIcon, tr("Print Preview..."), this, &LtsTextEditor2::filePrintPreview); + + const QIcon exportPdfIcon = QIcon::fromTheme(QStringLiteral("exportpdf"), QIcon(rsrcPath()+ QStringLiteral("/exportpdf.png"))); + actionExport = menu->addAction(exportPdfIcon, tr("&Export PDF..."), this, &LtsTextEditor2::filePrintPdf); + actionExport->setPriority(QAction::LowPriority); + actionExport->setShortcut(Qt::CTRL + Qt::Key_D); + tb->addAction(actionExport); + + menu->addSeparator(); +#endif + + m_actionQuit = menu->addAction(tr("&Quit"), this, &QWidget::close); + m_actionQuit->setShortcut(Qt::CTRL + Qt::Key_Q); +} + +void LtsTextEditor2::setupEditActions() +{ + QToolBar *tb = addToolBar(tr("Edit Actions")); + QMenu *menu = menuBar()->addMenu(tr("&Edit")); + + const QIcon undoIcon = QIcon::fromTheme(QStringLiteral("icon:undo.svg")); + m_actionUndo = menu->addAction(undoIcon, {}, m_textEdit, &QTextEdit::undo); + m_actionUndo->setToolTip(tr("&Undo")); + m_actionUndo->setShortcut(QKeySequence::Undo); + tb->addAction(m_actionUndo); + + const QIcon redoIcon = QIcon::fromTheme(QStringLiteral("icon:redo.svg")); + m_actionRedo = menu->addAction(redoIcon, tr("&Redo"), m_textEdit, &QTextEdit::redo); + m_actionRedo->setPriority(QAction::LowPriority); + m_actionRedo->setShortcut(QKeySequence::Redo); + tb->addAction(m_actionRedo); + menu->addSeparator(); + +#ifndef QT_NO_CLIPBOARD + const QIcon cutIcon = QIcon::fromTheme(QStringLiteral("icon:edit-cut.svg")); + m_actionCut = menu->addAction(cutIcon, tr("Cu&t"), m_textEdit, &QTextEdit::cut); + m_actionCut->setPriority(QAction::LowPriority); + m_actionCut->setShortcut(QKeySequence::Cut); + tb->addAction(m_actionCut); + + const QIcon copyIcon = QIcon::fromTheme(QStringLiteral("icon:edit-copy.svg")); + m_actionCopy = menu->addAction(copyIcon, tr("&Copy"), m_textEdit, &QTextEdit::copy); + m_actionCopy->setPriority(QAction::LowPriority); + m_actionCopy->setShortcut(QKeySequence::Copy); + tb->addAction(m_actionCopy); + + const QIcon pasteIcon = QIcon::fromTheme(QStringLiteral("icon:edit-paste.svg")); + m_actionPaste = menu->addAction(pasteIcon, tr("&Paste"), m_textEdit, &QTextEdit::paste); + m_actionPaste->setPriority(QAction::LowPriority); + m_actionPaste->setShortcut(QKeySequence::Paste); + tb->addAction(m_actionPaste); + if (const QMimeData *md = QApplication::clipboard()->mimeData()) + m_actionPaste->setEnabled(md->hasText()); +#endif +} + +void LtsTextEditor2::setupTextActions() +{ + QToolBar *tb = addToolBar(tr("Format Actions")); + QMenu *menu = menuBar()->addMenu(tr("F&ormat")); + + const QIcon boldIcon = QIcon::fromTheme(QStringLiteral(":icons/22x22/format-text-bold.png")); + m_actionTextBold = menu->addAction(boldIcon, tr("&Bold"), this, &LtsTextEditor2::onTextBold); + m_actionTextBold->setShortcut(Qt::CTRL + Qt::Key_B); + m_actionTextBold->setPriority(QAction::LowPriority); + QFont bold; + bold.setBold(true); + m_actionTextBold->setFont(bold); + tb->addAction(m_actionTextBold); + m_actionTextBold->setCheckable(true); + + const QIcon italicIcon = QIcon::fromTheme(QStringLiteral(":icons/22x22/format-text-italic.png")); + m_actionTextItalic = menu->addAction(italicIcon, tr("&Italic"), this, &LtsTextEditor2::onTextItalic); + m_actionTextItalic->setPriority(QAction::LowPriority); + m_actionTextItalic->setShortcut(Qt::CTRL + Qt::Key_I); + QFont italic; + italic.setItalic(true); + m_actionTextItalic->setFont(italic); + tb->addAction(m_actionTextItalic); + m_actionTextItalic->setCheckable(true); + + const QIcon underlineIcon = QIcon::fromTheme(QStringLiteral(":icons/22x22/format-text-underline.png")); + m_actionTextUnderline = menu->addAction(underlineIcon, tr("&Underline"), this, &LtsTextEditor2::onTextUnderline); + m_actionTextUnderline->setShortcut(Qt::CTRL + Qt::Key_U); + m_actionTextUnderline->setPriority(QAction::LowPriority); + QFont underline; + underline.setUnderline(true); + m_actionTextUnderline->setFont(underline); + tb->addAction(m_actionTextUnderline); + m_actionTextUnderline->setCheckable(true); + + menu->addSeparator(); + + const QIcon leftIcon = QIcon::fromTheme(QStringLiteral(":icons/22x22/format-justify-left.png")); + m_actionAlignLeft = new QAction(leftIcon, tr("&Left"), this); + m_actionAlignLeft->setShortcut(Qt::CTRL + Qt::Key_L); + m_actionAlignLeft->setCheckable(true); + m_actionAlignLeft->setPriority(QAction::LowPriority); + const QIcon centerIcon = QIcon::fromTheme(QStringLiteral(":icons/22x22/format-justify-center.png")); + m_actionAlignCenter = new QAction(centerIcon, tr("C&enter"), this); + m_actionAlignCenter->setShortcut(Qt::CTRL + Qt::Key_E); + m_actionAlignCenter->setCheckable(true); + m_actionAlignCenter->setPriority(QAction::LowPriority); + const QIcon rightIcon = QIcon::fromTheme(QStringLiteral(":icons/22x22/format-justify-right.png")); + m_actionAlignRight = new QAction(rightIcon, tr("&Right"), this); + m_actionAlignRight->setShortcut(Qt::CTRL + Qt::Key_R); + m_actionAlignRight->setCheckable(true); + m_actionAlignRight->setPriority(QAction::LowPriority); + const QIcon fillIcon = QIcon::fromTheme(QStringLiteral(":icons/22x22/format-justify-fill.png")); + m_actionAlignJustify = new QAction(fillIcon, tr("&Justify"), this); + m_actionAlignJustify->setShortcut(Qt::CTRL + Qt::Key_J); + m_actionAlignJustify->setCheckable(true); + m_actionAlignJustify->setPriority(QAction::LowPriority); + const QIcon indentMoreIcon = QIcon::fromTheme(QStringLiteral("format-indent-more"), QIcon(rsrcPath()+ QStringLiteral("/format-indent-more.png"))); + m_actionIndentMore = menu->addAction(indentMoreIcon, tr("&Indent"), this, &LtsTextEditor2::indent); + m_actionIndentMore->setShortcut(Qt::CTRL + Qt::Key_BracketRight); + m_actionIndentMore->setPriority(QAction::LowPriority); + const QIcon indentLessIcon = QIcon::fromTheme(QStringLiteral("format-indent-less"), QIcon(rsrcPath()+ QStringLiteral("/format-indent-less.png"))); + m_actionIndentLess = menu->addAction(indentLessIcon, tr("&Unindent"), this, &LtsTextEditor2::unindent); + m_actionIndentLess->setShortcut(Qt::CTRL + Qt::Key_BracketLeft); + m_actionIndentLess->setPriority(QAction::LowPriority); + + + QActionGroup *alignGroup = new QActionGroup(this); + connect(alignGroup, &QActionGroup::triggered, this, &LtsTextEditor2::onTextAlign); + + if (QApplication::isLeftToRight()) { + alignGroup->addAction(m_actionAlignLeft); + alignGroup->addAction(m_actionAlignCenter); + alignGroup->addAction(m_actionAlignRight); + } else { + alignGroup->addAction(m_actionAlignRight); + alignGroup->addAction(m_actionAlignCenter); + alignGroup->addAction(m_actionAlignLeft); + } + alignGroup->addAction(m_actionAlignJustify); + + tb->addActions(alignGroup->actions()); + menu->addActions(alignGroup->actions()); + tb->addAction(m_actionIndentMore); + tb->addAction(m_actionIndentLess); + menu->addAction(m_actionIndentMore); + menu->addAction(m_actionIndentLess); + + menu->addSeparator(); + + QPixmap pix(16, 16); + pix.fill(Qt::black); + m_actionTextColor = menu->addAction(pix, tr("&Color..."), this, &LtsTextEditor2::onTextColor); + m_actionTextColor->setPriority(QAction::LowPriority); + tb->addAction(m_actionTextColor); + + QPixmap pix2(16, 16); + pix2.fill(Qt::white); + m_actionBackgroundColor = new QAction(pix2, tr("&Background Color"), this); + m_actionBackgroundColor->setIcon(pix2); + m_actionBackgroundColor->setPriority(QAction::LowPriority); + connect(m_actionBackgroundColor, &QAction::triggered, this, &LtsTextEditor2::slotBackgroundColor); + tb->addAction(m_actionBackgroundColor); + menu->addSeparator(); + + const QIcon checkboxIcon = QIcon::fromTheme(QStringLiteral("status-checkbox-checked"), QIcon(rsrcPath()+ QStringLiteral("/checkbox-checked.png"))); + m_actionToggleCheckState = menu->addAction(checkboxIcon, tr("Chec&ked"), this, &LtsTextEditor2::setChecked); + m_actionToggleCheckState->setShortcut(Qt::CTRL + Qt::Key_K); + m_actionToggleCheckState->setCheckable(true); + m_actionToggleCheckState->setPriority(QAction::LowPriority); + tb->addAction(m_actionToggleCheckState); + + + + + + + m_comboStyle = new QComboBox(tb); + m_comboStyleAction = tb->addWidget(m_comboStyle); + m_comboStyle->addItem(QStringLiteral("Standard")); + m_comboStyle->addItem(QStringLiteral("Bullet List (Disc)")); + m_comboStyle->addItem(QStringLiteral("Bullet List (Circle)")); + m_comboStyle->addItem(QStringLiteral("Bullet List (Square)")); + m_comboStyle->addItem(QStringLiteral("Task List (Unchecked)")); + m_comboStyle->addItem(QStringLiteral("Task List (Checked)")); + m_comboStyle->addItem(QStringLiteral("Ordered List (Decimal)")); + m_comboStyle->addItem(QStringLiteral("Ordered List (Alpha lower)")); + m_comboStyle->addItem(QStringLiteral("Ordered List (Alpha upper)")); + m_comboStyle->addItem(QStringLiteral("Ordered List (Roman lower)")); + m_comboStyle->addItem(QStringLiteral("Ordered List (Roman upper)")); + m_comboStyle->addItem(QStringLiteral("Heading 1")); + m_comboStyle->addItem(QStringLiteral("Heading 2")); + m_comboStyle->addItem(QStringLiteral("Heading 3")); + m_comboStyle->addItem(QStringLiteral("Heading 4")); + m_comboStyle->addItem(QStringLiteral("Heading 5")); + m_comboStyle->addItem(QStringLiteral("Heading 6")); + + connect(m_comboStyle, QOverload::of(&QComboBox::activated), this, &LtsTextEditor2::onTextStyle); + + m_comboFont = new QFontComboBox(tb); + tb->addWidget(m_comboFont); + connect(m_comboFont, &QComboBox::textActivated, this, &LtsTextEditor2::onTextFamily); + + m_comboSize = new QComboBox(tb); + m_comboSize->setObjectName(QStringLiteral("comboSize")); + tb->addWidget(m_comboSize); + m_comboSize->setEditable(true); + + const QList standardSizes = QFontDatabase::standardSizes(); + for (int size : standardSizes) + m_comboSize->addItem(QString::number(size)); + m_comboSize->setCurrentIndex(standardSizes.indexOf(QApplication::font().pointSize())); + + connect(m_comboSize, &QComboBox::textActivated, this, &LtsTextEditor2::onTextSize); +} + +bool LtsTextEditor2::load(const QString &f) +{ + if (!QFile::exists(f)) + return false; + QFile file(f); + if (!file.open(QFile::ReadOnly)) + return false; + + QByteArray data = file.readAll(); + QTextCodec *codec = Qt::codecForHtml(data); + QString str = codec->toUnicode(data); + if (Qt::mightBeRichText(str)) { + QUrl baseUrl = (f.front() == QLatin1Char(':') ? QUrl(f) : QUrl::fromLocalFile(f)).adjusted(QUrl::RemoveFilename); + m_textEdit->document()->setBaseUrl(baseUrl); + m_textEdit->setHtml(str); + } else { +#if QT_CONFIG(textmarkdownreader) + QMimeDatabase db; + if (db.mimeTypeForFileNameAndData(f, data).name() == QStringLiteral("text/markdown")) + m_textEdit->setMarkdown(QString::fromUtf8(data)); + else +#endif + m_textEdit->setPlainText(QString::fromUtf8(data)); + } + + setCurrentFileName(f); + return true; +} + +bool LtsTextEditor2::maybeSave() +{ + if (!m_textEdit->document()->isModified()) + return true; + + const QMessageBox::StandardButton ret = + QMessageBox::warning(this, QCoreApplication::applicationName(), + tr("The document has been modified.\n" + "Do you want to save your changes?"), + QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); + if (ret == QMessageBox::Save) + return onFileSave(); + else if (ret == QMessageBox::Cancel) + return false; + return true; +} + +void LtsTextEditor2::setCurrentFileName(const QString &fileName) +{ + this->m_fileName = fileName; + m_textEdit->document()->setModified(false); + + QString shownName; + if (fileName.isEmpty()) + shownName = QStringLiteral("untitled.txt"); + else + shownName = QFileInfo(fileName).fileName(); + + setWindowTitle(tr("%1[*] - %2").arg(shownName, QCoreApplication::applicationName())); + setWindowModified(false); +} + +void LtsTextEditor2::onFileNew() +{ + if (maybeSave()) { + m_textEdit->clear(); + setCurrentFileName(QString()); + } +} + +void LtsTextEditor2::onFileOpen() +{ + QFileDialog fileDialog(this, tr("Open File...")); + fileDialog.setAcceptMode(QFileDialog::AcceptOpen); + fileDialog.setFileMode(QFileDialog::ExistingFile); + fileDialog.setMimeTypeFilters(QStringList() + #if QT_CONFIG(texthtmlparser) + << QStringLiteral("text/html") + #endif + #if QT_CONFIG(textmarkdownreader) + + << QStringLiteral("text/markdown") + #endif + << QStringLiteral("text/plain")); + if (fileDialog.exec() != QDialog::Accepted) + return; + const QStringList &fileNameList = fileDialog.selectedFiles(); + if (fileNameList.isEmpty()) + return; + QString fn = fileNameList.constFirst(); + if (load(fn)) + statusBar()->showMessage(tr("Opened \"%1\"").arg(QDir::toNativeSeparators(fn))); + else + statusBar()->showMessage(tr("Could not open \"%1\"").arg(QDir::toNativeSeparators(fn))); +} + +bool LtsTextEditor2::onFileSave() +{ + if (m_fileName.isEmpty()) + return onFileSaveAs(); + if (m_fileName.startsWith(QStringLiteral(":/"))) + return onFileSaveAs(); + + QTextDocumentWriter writer(m_fileName); + bool success = writer.write(m_textEdit->document()); + if (success) { + m_textEdit->document()->setModified(false); + statusBar()->showMessage(tr("Wrote \"%1\"").arg(QDir::toNativeSeparators(m_fileName))); + } else { + statusBar()->showMessage(tr("Could not write to file \"%1\"") + .arg(QDir::toNativeSeparators(m_fileName))); + } + return success; +} + +bool LtsTextEditor2::onFileSaveAs() +{ + QFileDialog fileDialog(this, tr("Save as...")); + fileDialog.setAcceptMode(QFileDialog::AcceptSave); + QStringList mimeTypes; + mimeTypes << QStringLiteral("text/plain") + #if QT_CONFIG(textodfwriter) + << QStringLiteral("application/vnd.oasis.opendocument.text") + #endif + #if QT_CONFIG(textmarkdownwriter) + << QStringLiteral("text/markdown") + #endif + << QStringLiteral("text/html"); + fileDialog.setMimeTypeFilters(mimeTypes); +#if QT_CONFIG(textodfwriter) + fileDialog.setDefaultSuffix(QStringLiteral("odt")); +#endif + if (fileDialog.exec() != QDialog::Accepted) + return false; + const QStringList &fileNameList = fileDialog.selectedFiles(); + if (fileNameList.isEmpty()) + return false; + QString fn = fileNameList.constFirst(); + setCurrentFileName(fn); + return onFileSave(); +} + +void LtsTextEditor2::onFilePrint() +{ +#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printdialog) + QPrinter printer(QPrinter::HighResolution); + QPrintDialog *dlg = new QPrintDialog(&printer, this); + if (textEdit->textCursor().hasSelection()) + dlg->addEnabledOption(QAbstractPrintDialog::PrintSelection); + dlg->setWindowTitle(tr("Print Document")); + if (dlg->exec() == QDialog::Accepted) + textEdit->print(&printer); + delete dlg; +#endif +} + +void LtsTextEditor2::onFilePrintPreview() +{ +#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printpreviewdialog) + QPrinter printer(QPrinter::HighResolution); + QPrintPreviewDialog preview(&printer, this); + connect(&preview, &QPrintPreviewDialog::paintRequested, this, &LtsTextEditor2::printPreview); + preview.exec(); +#endif +} + +void LtsTextEditor2::printPreview(QPrinter *printer) +{ +#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printer) + textEdit->print(printer); +#else + Q_UNUSED(printer) +#endif +} + + +void LtsTextEditor2::onFilePrintPdf() +{ +#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printer) + + QFileDialog fileDialog(this, tr("Export PDF")); + fileDialog.setAcceptMode(QFileDialog::AcceptSave); + fileDialog.setMimeTypeFilters(QStringList(QStringLiteral("application/pdf"))); + fileDialog.setDefaultSuffix(QStringLiteral("pdf")); + if (fileDialog.exec() != QDialog::Accepted) + return; + const QStringList &fileNameList = fileDialog.selectedFiles(); + if (fileNameList.isEmpty()) + return; + QString fileName = fileNameList.constFirst(); + QPrinter printer(QPrinter::HighResolution); + printer.setOutputFormat(QPrinter::PdfFormat); + printer.setOutputFileName(fileName); + textEdit->document()->print(&printer); + statusBar()->showMessage(tr("Exported \"%1\"") + .arg(QDir::toNativeSeparators(fileName))); + +#endif +} + +void LtsTextEditor2::onTextBold() +{ + QTextCharFormat fmt; + fmt.setFontWeight(m_actionTextBold->isChecked() ? QFont::Bold : QFont::Normal); + mergeFormatOnWordOrSelection(fmt); +} + +void LtsTextEditor2::onTextUnderline() +{ + QTextCharFormat fmt; + fmt.setFontUnderline(m_actionTextUnderline->isChecked()); + mergeFormatOnWordOrSelection(fmt); +} + +void LtsTextEditor2::onTextItalic() +{ + QTextCharFormat fmt; + fmt.setFontItalic(m_actionTextItalic->isChecked()); + mergeFormatOnWordOrSelection(fmt); +} + +void LtsTextEditor2::onTextFamily(const QString &f) +{ + QTextCharFormat fmt; + fmt.setFontFamily(f); + mergeFormatOnWordOrSelection(fmt); +} + +void LtsTextEditor2::onTextSize(const QString &p) +{ + qreal pointSize = p.toFloat(); + if (p.toFloat() > 0) { + QTextCharFormat fmt; + fmt.setFontPointSize(pointSize); + mergeFormatOnWordOrSelection(fmt); + } +} + +void LtsTextEditor2::onTextStyle(int styleIndex) +{ + QTextCursor cursor = m_textEdit->textCursor(); + QTextListFormat::Style style = QTextListFormat::ListStyleUndefined; + QTextBlockFormat::MarkerType marker = QTextBlockFormat::MarkerType::NoMarker; + + switch (styleIndex) { + case 1: + style = QTextListFormat::ListDisc; + break; + case 2: + style = QTextListFormat::ListCircle; + break; + case 3: + style = QTextListFormat::ListSquare; + break; + case 4: + if (cursor.currentList()) + style = cursor.currentList()->format().style(); + else + style = QTextListFormat::ListDisc; + marker = QTextBlockFormat::MarkerType::Unchecked; + break; + case 5: + if (cursor.currentList()) + style = cursor.currentList()->format().style(); + else + style = QTextListFormat::ListDisc; + marker = QTextBlockFormat::MarkerType::Checked; + break; + case 6: + style = QTextListFormat::ListDecimal; + break; + case 7: + style = QTextListFormat::ListLowerAlpha; + break; + case 8: + style = QTextListFormat::ListUpperAlpha; + break; + case 9: + style = QTextListFormat::ListLowerRoman; + break; + case 10: + style = QTextListFormat::ListUpperRoman; + break; + default: + break; + } + + cursor.beginEditBlock(); + + QTextBlockFormat blockFmt = cursor.blockFormat(); + + if (style == QTextListFormat::ListStyleUndefined) { + blockFmt.setObjectIndex(-1); + int headingLevel = styleIndex >= 11 ? styleIndex - 11 + 1 : 0; + blockFmt.setHeadingLevel(headingLevel); + cursor.setBlockFormat(blockFmt); + + int sizeAdjustment = headingLevel ? 4 - headingLevel : 0; + QTextCharFormat fmt; + fmt.setFontWeight(headingLevel ? QFont::Bold : QFont::Normal); + fmt.setProperty(QTextFormat::FontSizeAdjustment, sizeAdjustment); + cursor.select(QTextCursor::LineUnderCursor); + cursor.mergeCharFormat(fmt); + m_textEdit->mergeCurrentCharFormat(fmt); + } else { + blockFmt.setMarker(marker); + cursor.setBlockFormat(blockFmt); + QTextListFormat listFmt; + if (cursor.currentList()) { + listFmt = cursor.currentList()->format(); + } else { + listFmt.setIndent(blockFmt.indent() + 1); + blockFmt.setIndent(0); + cursor.setBlockFormat(blockFmt); + } + listFmt.setStyle(style); + cursor.createList(listFmt); + } + + cursor.endEditBlock(); +} + +void LtsTextEditor2::onTextColor() +{ + QColor col = QColorDialog::getColor(m_textEdit->textColor(), this); + if (!col.isValid()) + return; + QTextCharFormat fmt; + fmt.setForeground(col); + mergeFormatOnWordOrSelection(fmt); + colorChanged(col); +} + +void LtsTextEditor2::onTextAlign(QAction *a) +{ + if (a == m_actionAlignLeft) + m_textEdit->setAlignment(Qt::AlignLeft | Qt::AlignAbsolute); + else if (a == m_actionAlignCenter) + m_textEdit->setAlignment(Qt::AlignHCenter); + else if (a == m_actionAlignRight) + m_textEdit->setAlignment(Qt::AlignRight | Qt::AlignAbsolute); + else if (a == m_actionAlignJustify) + m_textEdit->setAlignment(Qt::AlignJustify); +} + +void LtsTextEditor2::setChecked(bool checked) +{ + onTextStyle(checked ? 5 : 4); +} + +void LtsTextEditor2::indent() +{ + modifyIndentation(1); +} + +void LtsTextEditor2::unindent() +{ + modifyIndentation(-1); +} + +void LtsTextEditor2::modifyIndentation(int amount) +{ + QTextCursor cursor = m_textEdit->textCursor(); + cursor.beginEditBlock(); + if (cursor.currentList()) { + QTextListFormat listFmt = cursor.currentList()->format(); + + + QTextCursor above(cursor); + above.movePosition(QTextCursor::Up); + if (above.currentList() && listFmt.indent() + amount == above.currentList()->format().indent()) { + above.currentList()->add(cursor.block()); + } else { + listFmt.setIndent(listFmt.indent() + amount); + cursor.createList(listFmt); + } + } else { + QTextBlockFormat blockFmt = cursor.blockFormat(); + blockFmt.setIndent(blockFmt.indent() + amount); + cursor.setBlockFormat(blockFmt); + } + cursor.endEditBlock(); +} + +void LtsTextEditor2::currentCharFormatChanged(const QTextCharFormat &format) +{ + fontChanged(format.font()); + colorChanged(format.foreground().color()); +} + +void LtsTextEditor2::cursorPositionChanged() +{ + alignmentChanged(m_textEdit->alignment()); + QTextList *list = m_textEdit->textCursor().currentList(); + if (list) { + switch (list->format().style()) { + case QTextListFormat::ListDisc: + m_comboStyle->setCurrentIndex(1); + break; + case QTextListFormat::ListCircle: + m_comboStyle->setCurrentIndex(2); + break; + case QTextListFormat::ListSquare: + m_comboStyle->setCurrentIndex(3); + break; + case QTextListFormat::ListDecimal: + m_comboStyle->setCurrentIndex(6); + break; + case QTextListFormat::ListLowerAlpha: + m_comboStyle->setCurrentIndex(7); + break; + case QTextListFormat::ListUpperAlpha: + m_comboStyle->setCurrentIndex(8); + break; + case QTextListFormat::ListLowerRoman: + m_comboStyle->setCurrentIndex(9); + break; + case QTextListFormat::ListUpperRoman: + m_comboStyle->setCurrentIndex(10); + break; + default: + m_comboStyle->setCurrentIndex(-1); + break; + } + switch (m_textEdit->textCursor().block().blockFormat().marker()) { + case QTextBlockFormat::MarkerType::NoMarker: + m_actionToggleCheckState->setChecked(false); + break; + case QTextBlockFormat::MarkerType::Unchecked: + m_comboStyle->setCurrentIndex(4); + m_actionToggleCheckState->setChecked(false); + break; + case QTextBlockFormat::MarkerType::Checked: + m_comboStyle->setCurrentIndex(5); + m_actionToggleCheckState->setChecked(true); + break; + } + } else { + int headingLevel = m_textEdit->textCursor().blockFormat().headingLevel(); + m_comboStyle->setCurrentIndex(headingLevel ? headingLevel + 10 : 0); + } +} + +void LtsTextEditor2::clipboardDataChanged() +{ +#ifndef QT_NO_CLIPBOARD + if (const QMimeData *md = QApplication::clipboard()->mimeData()) + m_actionPaste->setEnabled(md->hasText()); +#endif +} + +void LtsTextEditor2::about() +{ + QMessageBox::about(this, tr("About"), tr("This example demonstrates Qt's " + "rich text editing facilities in action, providing an example " + "document for you to experiment with.")); +} + +void LtsTextEditor2::mergeFormatOnWordOrSelection(const QTextCharFormat &format) +{ + QTextCursor cursor = m_textEdit->textCursor(); + if (!cursor.hasSelection()) + cursor.select(QTextCursor::WordUnderCursor); + cursor.mergeCharFormat(format); + m_textEdit->mergeCurrentCharFormat(format); +} + +void LtsTextEditor2::fontChanged(const QFont &f) +{ + m_comboFont->setCurrentIndex(m_comboFont->findText(QFontInfo(f).family())); + m_comboSize->setCurrentIndex(m_comboSize->findText(QString::number(f.pointSize()))); + m_actionTextBold->setChecked(f.bold()); + m_actionTextItalic->setChecked(f.italic()); + m_actionTextUnderline->setChecked(f.underline()); +} + +void LtsTextEditor2::colorChanged(const QColor &c) +{ + QPixmap pix(16, 16); + pix.fill(c); + m_actionTextColor->setIcon(pix); +} + +void LtsTextEditor2::alignmentChanged(Qt::Alignment a) +{ + if (a & Qt::AlignLeft) + m_actionAlignLeft->setChecked(true); + else if (a & Qt::AlignHCenter) + m_actionAlignCenter->setChecked(true); + else if (a & Qt::AlignRight) + m_actionAlignRight->setChecked(true); + else if (a & Qt::AlignJustify) + m_actionAlignJustify->setChecked(true); +} + +} diff --git a/Lts/Common/LtsWidgets/lts_text_editor2.h b/Lts/Common/LtsWidgets/lts_text_editor2.h new file mode 100755 index 000000000..f636101b7 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_text_editor2.h @@ -0,0 +1,135 @@ + + +#pragma once + + +#include + +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QAction; +class QComboBox; +class QFontComboBox; +class QTextEdit; +class QTextCharFormat; +class QMenu; +class QPrinter; +QT_END_NAMESPACE + +namespace LtsWidgets +{ + +class LTSWIDGETS_EXPORT LtsTextEditor2 : public QMainWindow +{ + Q_OBJECT + +public: + explicit LtsTextEditor2(QWidget *parent = nullptr); + ~LtsTextEditor2(); + + static QString rsrcPath(){return (QStringLiteral(":/images/win"));} + + QColor m_backgroundColor{Qt::white}; + QColor m_foregroundColor{Qt::black}; + + QTextEdit *textEdit(); + QColor backgroundColor() const; + void enableBackgroundColor(bool onOff); + void setInitialBackgroundColor(const QColor &bgColor); + bool load(const QString &f); + + +Q_SIGNALS: + void backgroundColorChanged(const QColor &bgColor); + +public slots: + +private slots: + void onFileNew(); + void onFileOpen(); + bool onFileSave(); + bool onFileSaveAs(); + void onFilePrint(); + void onFilePrintPreview(); + void onFilePrintPdf(); + + void onTextBold(); + void onTextUnderline(); + void onTextItalic(); + void onTextFamily(const QString &f); + void onTextSize(const QString &p); + void onTextStyle(int styleIndex); + void onTextColor(); + void onTextAlign(QAction *a); + void setChecked(bool checked); + void indent(); + void unindent(); + void slotBackgroundColorChanged(const QColor &bgColor); + void slotBackgroundColor(); + + void currentCharFormatChanged(const QTextCharFormat &format); + void cursorPositionChanged(); + + void clipboardDataChanged(); + void about(); + void printPreview(QPrinter *); + +private: + void setupFileActions(); + void setupEditActions(); + void setupTextActions(); + bool maybeSave(); + void setCurrentFileName(const QString &fileName); + void modifyIndentation(int amount); + + void mergeFormatOnWordOrSelection(const QTextCharFormat &format); + void fontChanged(const QFont &f); + void colorChanged(const QColor &c); + void alignmentChanged(Qt::Alignment a); + + QAction *m_actionSave; + QAction *m_actionOpen; + QAction *m_actionQuit; + QAction *m_actionSaveAs; + QAction *m_actionNew; + QAction *m_actionPrint; + QAction *m_actionPreview; + QAction *m_actionExport; + QAction *m_actionTextBold; + QAction *m_actionTextUnderline; + QAction *m_actionTextItalic; + QAction *m_actionTextColor; + QAction *m_actionBackgroundColor; + QAction *m_actionAlignLeft; + QAction *m_actionAlignCenter; + QAction *m_actionAlignRight; + QAction *m_actionAlignJustify; + QAction *m_actionIndentLess; + QAction *m_actionIndentMore; + QAction *m_actionToggleCheckState; + QAction *m_actionUndo; + QAction *m_actionRedo; +#ifndef QT_NO_CLIPBOARD + QAction *m_actionCut; + QAction *m_actionCopy; + QAction *m_actionPaste; +#endif + + QComboBox *m_comboStyle; + QAction *m_comboStyleAction; + QFontComboBox *m_comboFont; + QComboBox *m_comboSize; + + QToolBar *m_tb; + QString m_fileName; + QTextEdit *m_textEdit; +}; + + } diff --git a/Lts/Common/LtsWidgets/lts_tool_bar.cpp b/Lts/Common/LtsWidgets/lts_tool_bar.cpp new file mode 100755 index 000000000..1bd1fd012 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_tool_bar.cpp @@ -0,0 +1,31 @@ + + + +#include "lts_tool_bar.h" + +using namespace LtsWidgets; + +LtsToolBar::LtsToolBar(QWidget *parent) + : QToolBar(parent) +{ + setMaximumHeight(1); + setMinimumHeight(1); +} + +LtsToolBar::~LtsToolBar() +{ +} + +void LtsToolBar::enterEvent(QEvent *e) +{ + setMaximumHeight(50); + setMinimumHeight(50); + QToolBar::enterEvent(e); +} + +void LtsToolBar::leaveEvent(QEvent *e) +{ + setMaximumHeight(1); + setMinimumHeight(1); + QToolBar::leaveEvent(e); +} diff --git a/Lts/Common/LtsWidgets/lts_tool_bar.h b/Lts/Common/LtsWidgets/lts_tool_bar.h new file mode 100755 index 000000000..40ffe7d70 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_tool_bar.h @@ -0,0 +1,27 @@ + + + +#pragma once + +#include +#include "lts_widgets_export.h" + +namespace LtsWidgets +{ + +class LTSWIDGETS_EXPORT LtsToolBar : public QToolBar +{ + + Q_OBJECT +public: + explicit LtsToolBar(QWidget *parent = nullptr); + ~LtsToolBar(); + +protected: + void enterEvent(QEvent *e) override; + void leaveEvent(QEvent *e) override; + + +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_tool_bar_area.cpp b/Lts/Common/LtsWidgets/lts_tool_bar_area.cpp new file mode 100755 index 000000000..b349f2423 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_tool_bar_area.cpp @@ -0,0 +1,384 @@ + + + +#include "lts_tool_bar_area.h" +#include "ui_lts_tool_bar_area.h" +#include "lts_drag_icon.h" + +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace LtsWidgets +{ + + + +void ShortCut::clear() +{ + name.clear(); + identifier.clear(); + icon = nullptr; + shortcut->disconnect(icon); +} + + + + +LtsToolBarArea::LtsToolBarArea(QWidget *parent) + : QWidget(parent) + , m_dummyLabel(nullptr) + , m_ui(new Ui::LtsToolBarArea) +{ + m_ui->setupUi(this); + + setAcceptDrops(true); + clearToolBarArea(); + + + for(int i=Qt::Key_F5; i<=Qt::Key_F12; ++i) + { + ShortCut sc(this); + sc.shortcut->setKey(QKeySequence(i)); + sc.key = i; + m_functionKeys.insert(i, sc); + } +} + +LtsToolBarArea::~LtsToolBarArea() +{ + delete m_ui; +} + +void LtsToolBarArea::addMenuEntry(LtsDragIcon *entry, LtsDragIcon *after) +{ + if (!entry) + return; + + if (m_foreignButtonMap.contains(entry)) + return; + + delete m_dummyLabel; + m_dummyLabel = nullptr; + + bool isInternalMove = false; + LtsDragIcon *foreignDragIcon = entry; + + for (int i = 0; i < m_buttonMap.size(); ++i) + { + if (m_buttonMap.at(i) == entry) + { + isInternalMove = true; + + if (after == nullptr) + return; + m_buttonMap.removeAt(i); + foreignDragIcon = m_foreignButtonMap.at(i); + m_foreignButtonMap.removeAt(i); + m_ui->layToolbar->removeWidget(entry); + break; + } + } + + + int index = m_buttonMap.size(); + for(int i = 0; i < m_buttonMap.size(); ++i) + { + if (m_buttonMap.at(i) == after || + m_foreignButtonMap.at(i) == after) + { + index = i + 1; + break; + } + } + + + LtsDragIcon *newAction = entry; + + if (!isInternalMove) + { + newAction = new LtsDragIcon(this); + newAction->setIcon(entry->icon()); + newAction->setText(entry->text()); + newAction->setContext(entry->context()); + if (!entry->toolTip().isEmpty()) + newAction->setToolTip(entry->toolTip()); + newAction->setIconSize(ButtonSize::eDefault); + newAction->setButtonStyle(ButtonStyle::eIconOnly); + newAction->setIdentifier(entry->identifier()); + + connect(newAction, &LtsDragIcon::clicked, entry, &LtsDragIcon::clicked); + connect(entry, &LtsDragIcon::destroyed, newAction, + [this, newAction]() { m_buttonMap.removeAll(newAction); newAction->deleteLater(); }); + newAction->setVisible(!entry->text().isEmpty()); + } + newAction->setContextMenuPolicy(Qt::CustomContextMenu); + connect(newAction, &LtsDragIcon::customContextMenuRequested, + this, &LtsToolBarArea::showContextMenu); + + m_foreignButtonMap.insert(index, foreignDragIcon); + m_buttonMap.insert(index, newAction); + + connect(foreignDragIcon, &LtsDragIcon::enabledChanged, newAction, &LtsDragIcon::setEnabled); + + + m_ui->layToolbar->insertWidget(index + 1, newAction); + saveSettings(); +} + +void LtsToolBarArea::clearToolBarArea() +{ + qDeleteAll(m_buttonMap); + m_buttonMap.clear(); + m_foreignButtonMap.clear(); + for(ShortCut &item : m_functionKeys) + item.clear(); + + delete m_dummyLabel; + + m_dummyLabel = new QLabel(tr("Drop menu entry here"), this); + m_ui->layToolbar->addWidget(m_dummyLabel); +} + +void LtsToolBarArea::setMenuEntries(const QVector &actions) +{ + clearToolBarArea(); + + for (LtsDragIcon* ico : actions) + addMenuEntry(ico); +} + +QStringList LtsToolBarArea::menuEntries() const +{ + QStringList ret; + ret.reserve(m_foreignButtonMap.size()); + for (LtsDragIcon *dragIcon : m_foreignButtonMap) + ret << dragIcon->identifier(); + + return ret; +} + + + + +void LtsToolBarArea::dragEnterEvent(QDragEnterEvent *event) +{ + LtsDragIcon *source = + qobject_cast(event->source()); + if (source) + { + if (!m_foreignButtonMap.contains(source)) + { + event->setDropAction(Qt::MoveAction); + event->accept(); + } + } +} + +void LtsToolBarArea::dropEvent(QDropEvent *event) +{ + LtsDragIcon *source = qobject_cast(event->source()); + + LtsDragIcon *after = nullptr; + QWidget *w = childAt(event->pos()); + while (w) + { + after = qobject_cast(w); + if (after) + break; + w = w->parentWidget(); + } + + addMenuEntry(source, after); +} + +QMenu *LtsToolBarArea::functionKeyMenu(QWidget *parent) +{ + QMenu* menu = new QMenu(parent); + menu->setTitle(tr("Function Key")); + QAction *act; + for(auto val = m_functionKeys.begin(); val != m_functionKeys.end(); ++val) + { + ShortCut &shorty = val.value(); + act = menu->addAction(QStringLiteral("%1%2").arg(shorty.keyName(), !shorty.name.isEmpty() ? QStringLiteral(" - %1").arg(shorty.name) : QString())); + act->setData(shorty.shortcut->key()[0]); + connect(act, &QAction::triggered, this, &LtsToolBarArea::functionKeyMenuTriggered); + } + menu->addSeparator(); + act = menu->addAction(tr("Remove Function Key")); + act->setData(-1); + connect(act, &QAction::triggered, this, &LtsToolBarArea::functionKeyMenuTriggered); + return menu; +} + +void LtsToolBarArea::functionKeyMenuTriggered() +{ + QAction* act = qobject_cast(sender()); + if (!act) + return; + int fkey = act->data().toInt(); + + if (fkey == -1) + removeFunctionKey(m_currentIdentifierForContextMenue); + else if (fkey > 0) + setFunctionKey(fkey, m_currentIdentifierForContextMenue); +} + + +bool LtsToolBarArea::removeFunctionKey(const QString &action) +{ + if (action.isEmpty()) + return true; + + + for(auto val = m_functionKeys.begin(); val != m_functionKeys.end(); ++val) + { + ShortCut &itShortcut = val.value(); + if (itShortcut.identifier == action) + { + itShortcut.icon->removeModification(); + itShortcut.clear(); + break; + } + } + saveSettings(); + return true; +} + +bool LtsToolBarArea::setFunctionKey(int fkey, const QString &action) +{ + + if (fkey < Qt::Key_F5 || fkey > Qt::Key_F12) + return false; + + + LtsDragIcon *icon = nullptr; + for (LtsDragIcon *ic : qAsConst(m_buttonMap)) + { + if (ic->identifier() == action) + { + icon = ic; + break; + } + } + + if (!icon) + return false; + + + ShortCut &shorty = m_functionKeys[fkey]; + + + for (LtsDragIcon *ic : qAsConst(m_buttonMap)) + if (ic->identifier() == shorty.identifier) + ic->removeModification(); + + + for(auto val = m_functionKeys.begin(); val != m_functionKeys.end(); ++val) + { + ShortCut &itShortcut = val.value(); + if (itShortcut.identifier == action) + { + itShortcut.clear(); + break; + } + } + + + shorty.clear(); + shorty.identifier = icon->identifier(); + shorty.name = icon->text(); + shorty.icon = icon; + shorty.icon->setModification(shorty.keyName()); + connect(shorty.shortcut, &QShortcut::activated, icon, &LtsDragIcon::clicked); + saveSettings(); + return true; +} + +QMap LtsToolBarArea::functionKeys() const +{ + QMap retVal; + for(auto val = m_functionKeys.begin(); val != m_functionKeys.end(); ++val) + { + const ShortCut &itShortcut = val.value(); + if (!itShortcut.identifier.isEmpty()) + retVal[itShortcut.key] = itShortcut.identifier; + } + return retVal; +} + +void LtsToolBarArea::removeMenuAction(LtsDragIcon *icon) +{ + + for(auto val = m_functionKeys.begin(); val != m_functionKeys.end(); ++val) + { + ShortCut &itShortcut = val.value(); + if (itShortcut.identifier == icon->identifier()) + itShortcut.clear(); + } + for (LtsDragIcon *ic : qAsConst(m_foreignButtonMap)) + { + if(ic->identifier() == icon->identifier()) + { + m_foreignButtonMap.removeAll(ic); + break; + } + } + m_buttonMap.removeAll(icon); + m_ui->layToolbar->removeWidget(icon); + delete icon; + saveSettings(); +} + +void LtsToolBarArea::showContextMenu(const QPoint &pos) +{ + QPointer icon = qobject_cast(sender()); + if (!icon) + return; + + const QPoint &globalPos = icon->mapToGlobal(pos); + + QMenu *contextMenu = new QMenu(this); + contextMenu->setAttribute(Qt::WA_DeleteOnClose); + + + m_currentIdentifierForContextMenue = icon->identifier(); + connect(contextMenu, &QObject::destroyed, this, [this](){ + + m_currentIdentifierForContextMenue.clear(); + }); + + + QAction *delAction = contextMenu->addAction(tr("Remove")); + connect(delAction, &QAction::triggered, this, [this, icon] () { + if (icon) + removeMenuAction(icon); + }); + + + QMenu *fkeyMenu = functionKeyMenu(contextMenu); + contextMenu->addMenu(fkeyMenu); + + contextMenu->popup(globalPos); +} + + +void LtsToolBarArea::saveSettings() +{ + + + + + + + + + +} +} diff --git a/Lts/Common/LtsWidgets/lts_tool_bar_area.h b/Lts/Common/LtsWidgets/lts_tool_bar_area.h new file mode 100755 index 000000000..d45103722 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_tool_bar_area.h @@ -0,0 +1,115 @@ + + + +#pragma once + +#include "lts_widgets_export.h" + +#include +#include +#include +#include + + +class QMenu; +class QLabel; + +namespace Ui +{ +class LtsToolBarArea; +} + +namespace LtsWidgets +{ +class LtsDragIcon; + +struct ShortCut +{ + int key = 0; + QShortcut *shortcut = nullptr; + QString name; + QString identifier; + LtsDragIcon *icon = nullptr; + QString keyName() const {return shortcut ? shortcut->key().toString() : QString();} + void clear(); + + explicit ShortCut(QWidget *parent = nullptr) + : shortcut(new QShortcut(parent)) + {} + + ~ShortCut() { delete shortcut; } + ShortCut(const ShortCut &o) + : key(o.key) + , name(o.name) + , identifier(o.identifier) + , icon(o.icon) + { + if (o.shortcut) + { + delete shortcut; + shortcut = new QShortcut(o.shortcut->key(), qobject_cast(o.shortcut->parent()), nullptr, nullptr, o.shortcut->context()); + } + } + + ShortCut &operator=(const ShortCut &o) + { + if (&o == this) + return *this; + + key = o.key; + name = o.name; + identifier = o.identifier; + icon = o.icon; + QWidget *parent = qobject_cast(shortcut->parent()); + delete shortcut; + if (o.shortcut) + shortcut = new QShortcut(o.shortcut->key(), qobject_cast(o.shortcut->parent()), nullptr, nullptr, o.shortcut->context()); + else + shortcut = new QShortcut(parent); + return *this; + } +}; + +class LTSWIDGETS_EXPORT LtsToolBarArea : public QWidget +{ + + Q_OBJECT +public: + explicit LtsToolBarArea(QWidget *parent = nullptr); + ~LtsToolBarArea(); + + void addMenuEntry(LtsDragIcon *entry, LtsDragIcon *after = nullptr); + void clearToolBarArea(); + + void setMenuEntries(const QVector &actions); + QStringList menuEntries() const; + + bool setFunctionKey(int fkey, const QString &action); + QMap functionKeys() const; + + bool removeFunctionKey(const QString &action); + void removeMenuAction(LtsDragIcon *icon); + +public slots: + void showContextMenu(const QPoint &pos); + void functionKeyMenuTriggered(); + +protected: + void dragEnterEvent(QDragEnterEvent *event) override; + void dropEvent(QDropEvent *event) override; + +private: + QMenu* functionKeyMenu(QWidget *parent); + QString m_currentIdentifierForContextMenue; + + QVector m_buttonMap; + QVector m_foreignButtonMap; + QMap m_functionKeys; + QLabel *m_dummyLabel; + + Ui::LtsToolBarArea *m_ui; + + void saveSettings(); +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_tool_bar_area.ui b/Lts/Common/LtsWidgets/lts_tool_bar_area.ui new file mode 100755 index 000000000..4f0e14e84 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_tool_bar_area.ui @@ -0,0 +1,38 @@ + + + LtsToolBarArea + + + + 0 + 0 + 600 + 125 + + + + Form + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + diff --git a/Lts/Common/LtsWidgets/lts_tool_box.cpp b/Lts/Common/LtsWidgets/lts_tool_box.cpp new file mode 100755 index 000000000..2095b7070 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_tool_box.cpp @@ -0,0 +1,85 @@ + + + +#include "lts_tool_box.h" + +#include "lts_drag_icon.h" +#include "lts_grid_layout.h" + +#include +#include +#include + +using namespace LtsWidgets; + + +LtsToolBox::LtsToolBox(QWidget *parent) + : QToolBox(parent) +{ + + setMinimumSize(17, 200); + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, &LtsToolBox::customContextMenuRequested, + this, &LtsToolBox::showContextMenu); + + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + +} + +LtsToolBox::~LtsToolBox() +{ +} + +void LtsToolBox::resizeEvent(QResizeEvent *e) +{ + emit resized(e); + QToolBox::resizeEvent(e); + +} + +void LtsToolBox::addItem(QWidget *widget, const QString &text) +{ + setMinimumSize(120, 100); + QToolBox::addItem(widget, text); +} + + +void LtsToolBox::showContextMenu(const QPoint &pos) +{ + + + QPoint globalPos = this->mapToGlobal(pos); + + + + QMenu myMenu; + myMenu.addAction(tr("Icon and Text")); + QAction *to = myMenu.addAction(tr("Text Only")); + QAction *io = myMenu.addAction(tr("Icon Only")); + + + QAction *selectedItem = myMenu.exec(globalPos); + if (selectedItem) + { + ButtonStyle newStyle = ButtonStyle::eTextNextToIcon; + if (selectedItem == to) + newStyle = ButtonStyle::eTextOnly; + if (selectedItem == io) + newStyle = ButtonStyle::eIconOnly; + + for (int i = 0; i < count(); i++) + { + const QList list = widget(i)->findChildren(); + for (LtsDragIcon *icon : list) + icon->setButtonStyle(newStyle); + LtsGridLayout *lay = qobject_cast(widget(i)->layout()); + if (lay) + lay->setAutoSize((newStyle == ButtonStyle::eIconOnly)); + } + + } + else + { + + } +} diff --git a/Lts/Common/LtsWidgets/lts_tool_box.h b/Lts/Common/LtsWidgets/lts_tool_box.h new file mode 100755 index 000000000..b789bb0d8 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_tool_box.h @@ -0,0 +1,31 @@ + + + +#pragma once + +#include "lts_base.h" +#include "lts_widgets_export.h" + +#include + +namespace LtsWidgets +{ + +class LTSWIDGETS_EXPORT LtsToolBox : public QToolBox +{ + Q_OBJECT +public: + explicit LtsToolBox(QWidget *parent = nullptr); + ~LtsToolBox(); + void resizeEvent(QResizeEvent *e) override; + void addItem(QWidget *widget, const QString &text); + +signals: + void resized(QResizeEvent *e); + +public slots: + void showContextMenu(const QPoint &pos); + +}; + +} diff --git a/Lts/Common/LtsWidgets/lts_wait_dialog.cpp b/Lts/Common/LtsWidgets/lts_wait_dialog.cpp new file mode 100755 index 000000000..cb5e3efa3 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_wait_dialog.cpp @@ -0,0 +1,17 @@ +#include "lts_wait_dialog.h" +#include "lts_global_wait_dialog.h" + + +namespace LtsWidgets +{ +LtsWaitDialog::LtsWaitDialog(const QString &customText) +{ + LtsGlobalWaitDialog::getInstance()->wait(customText); +} + + +LtsWaitDialog::~LtsWaitDialog() +{ + LtsGlobalWaitDialog::getInstance()->done(); +} +} diff --git a/Lts/Common/LtsWidgets/lts_wait_dialog.h b/Lts/Common/LtsWidgets/lts_wait_dialog.h new file mode 100755 index 000000000..c8ce363e8 --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_wait_dialog.h @@ -0,0 +1,21 @@ +#pragma once + +#include "lts_widgets_export.h" + +#include + +#include + + +namespace LtsWidgets +{ +class LTSWIDGETS_EXPORT LtsWaitDialog +{ +public: + LtsWaitDialog(const QString &customText = {}); + ~LtsWaitDialog(); +}; + +typedef std::unique_ptr LtsWaitDialogContainer; + +} diff --git a/Lts/Common/LtsWidgets/lts_widgets_export.h b/Lts/Common/LtsWidgets/lts_widgets_export.h new file mode 100755 index 000000000..ec9e7223b --- /dev/null +++ b/Lts/Common/LtsWidgets/lts_widgets_export.h @@ -0,0 +1,17 @@ + + +#pragma once + +#include + +#ifndef LTSWIDGETS_EXPORT +# ifdef BUILD_STATIC_LIBRARIES +# define LTSWIDGETS_EXPORT +# else +# if defined(MAKE_LTSWIDGETS_LIB) +# define LTSWIDGETS_EXPORT Q_DECL_EXPORT +# else +# define LTSWIDGETS_EXPORT Q_DECL_IMPORT +# endif +# endif +#endif diff --git a/Lts/Common/LtsWidgets/ltswidgetsplugin.cpp b/Lts/Common/LtsWidgets/ltswidgetsplugin.cpp new file mode 100755 index 000000000..e31a1b85c --- /dev/null +++ b/Lts/Common/LtsWidgets/ltswidgetsplugin.cpp @@ -0,0 +1,1196 @@ + + + +#include +#include +#include + +#include +#include +#include +#include +#include "lts_dialog_button_box.h" +#include "lts_elided_label.h" +#include "lts_image_editor.h" +#include "lts_picture_view.h" +#include "lts_profile_button.h" +#include "lts_splitter.h" +#include "lts_tab_bar.h" +#include "lts_tab_widget.h" +#include "lts_tool_bar.h" +#include "lts_tool_bar_area.h" +#include "lts_tool_box.h" +#include "lts_frameless_dialog.h" +#include "lts_drag_icon.h" +#include "lts_combo_box.h" +#include "lts_spinbox.h" +#include "lts_multi_range_spinbox.h" +#include "lts_range_slider.h" +#include "lts_msec_date_time_edit.h" +#include "lts_bitfield_widget.h" +#include "lts_tab_sequence_widget.h" + +#ifdef TABLET_MODE +#include "lts_date_time_edit_touch.h" +#else +#include "lts_date_time_edit.h" +#endif + +#include "lts_led_widget.h" +#include "lts_base.h" +#include "LtsImageCropper/lts_image_cropper.h" + +namespace LtsWidgets +{ + +class LtsDesignerCustomWidgetInterface : public QObject, public QDesignerCustomWidgetInterface +{ + Q_OBJECT + + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using QObject::QObject; + + bool isContainer() const override final + { + return false; + } + bool isInitialized() const override final + { + return mInitialized; + } + QIcon icon() const override final + { + return QIcon(QStringLiteral(":style/icons/22x22/pushbutton.png")); + } + QString codeTemplate() const override final + { + return QString(); + } + QString group() const override final + { + return QLatin1String("Lts Widgets"); + } + void initialize(QDesignerFormEditorInterface *) override final + { + mInitialized = true; + } + +private: + bool mInitialized = false; +}; + +class LtsCoordWidgetPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QLatin1String("LtsCoordTrans/lts_coord_widget.h"); + } + QString name() const override + { + return QLatin1String("LtsCoordWidget::LtsCoordWidget"); + } + QString toolTip() const override + { + return QLatin1String("Widget for entering a coordinate"); + } + QString whatsThis() const override + { + return QLatin1String("Widget for entering a coordinate"); + } + QString domXml() const override + { + return QStringLiteral(""); + } + QWidget *createWidget(QWidget *parent) override + { + return new LtsCoordWidget::LtsCoordWidget(parent); + } +}; + + +class LtsDialogButtonBoxPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QLatin1String("LtsWidgets/lts_dialog_button_box.h"); + } + QString name() const override + { + return QLatin1String("LtsWidgets::LtsDialogButtonBox"); + } + QString toolTip() const override + { + return QLatin1String("Widget for a LTS version of the Button Box"); + } + QString whatsThis() const override + { + return QLatin1String("Widget for a LTS version of the Botton Box"); + } + QString domXml() const override + { + return QStringLiteral(""); + } + QWidget *createWidget(QWidget *parent) override + { + return new LtsWidgets::LtsDialogButtonBox(parent); + } +}; + +class LtsElidedLabelPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QStringLiteral("LtsWidgets/lts_elided_label.h"); + } + QString name() const override + { + return QStringLiteral("LtsWidgets::LtsElidedLabel"); + } + QString toolTip() const override + { + return QStringLiteral("A QLabel which automatically elided it's content if needed."); + } + QString whatsThis() const override + { + return toolTip(); + } + QString domXml() const override + { + return QStringLiteral(""); + } + QWidget *createWidget(QWidget *parent) override + { + return new LtsWidgets::LtsElidedLabel(parent); + } +}; + +class LtsImageEditorPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QLatin1String("LtsWidgets/lts_image_editor.h"); + } + QString name() const override + { + return QLatin1String("LtsWidgets::LtsImageEditor"); + } + QString toolTip() const override + { + return QLatin1String("Image Editor"); + } + QString whatsThis() const override + { + return QLatin1String("ImageEditor"); + } + QString domXml() const override + { + return QStringLiteral(""); + } + QWidget *createWidget(QWidget *parent) override + { + return new LtsWidgets::LtsImageEditor(parent); + } +}; + + +class LtsPictureViewPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QLatin1String("LtsWidgets/lts_picture_view.h"); + } + QString name() const override + { + return QLatin1String("LtsWidgets::LtsPictureView"); + } + QString toolTip() const override + { + return QLatin1String("Picture View Widget"); + } + QString whatsThis() const override + { + return QLatin1String("Picture View Widget"); + } + QString domXml() const override + { + return QStringLiteral(""); + } + QWidget *createWidget(QWidget *parent) override + { + return new LtsWidgets::LtsPictureView(parent); + } +}; + + +class LtsProfileButtonPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QLatin1String("LtsWidgets/lts_profile_button.h"); + } + QString name() const override + { + return QLatin1String("LtsWidgets::LtsProfileButton"); + } + QString toolTip() const override + { + return QLatin1String("Profile Button"); + } + QString whatsThis() const override + { + return QLatin1String("Profile Button"); + } + QString domXml() const override + { + return QStringLiteral(""); + } + QWidget *createWidget(QWidget *parent) override + { + return new LtsWidgets::LtsProfileButton(parent); + } +}; + + +class LtsSplitterPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QLatin1String("LtsWidgets/lts_splitter.h"); + } + QString name() const override + { + return QLatin1String("LtsWidgets::LtsSplitter"); + } + QString toolTip() const override + { + return QLatin1String("Splitter with sizes property"); + } + QString whatsThis() const override + { + return QLatin1String("Splitter with sizes property"); + } + QString domXml() const override + { + return QStringLiteral(""); + } + QWidget *createWidget(QWidget *parent) override + { + return new LtsWidgets::LtsSplitter(parent); + } +}; + + +class LtsTabBarPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QLatin1String("LtsWidgets/lts_tab_bar.h"); + } + QString name() const override + { + return QLatin1String("LtsWidgets::LtsTabBar"); + } + QString toolTip() const override + { + return QLatin1String("Tab Bar"); + } + QString whatsThis() const override + { + return QLatin1String("Tab Bar"); + } + QString domXml() const override + { + return QStringLiteral(""); + } + QWidget *createWidget(QWidget *parent) override + { + return new LtsWidgets::LtsTabBar(parent); + } +}; + + +class LtsTabWidgetPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QLatin1String("LtsWidgets/lts_tab_widget.h"); + } + QString name() const override + { + return QLatin1String("LtsWidgets::LtsTabWidget"); + } + QString toolTip() const override + { + return QLatin1String("Tab Widget"); + } + QString whatsThis() const override + { + return QLatin1String("Tab Widget"); + } + QString domXml() const override + { + return QStringLiteral(""); + } + QWidget *createWidget(QWidget *parent) override + { + return new LtsWidgets::LtsTabWidget(parent); + } +}; + + +class LtsToolBarPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QLatin1String("LtsWidgets/lts_tool_bar.h"); + } + QString name() const override + { + return QLatin1String("LtsWidgets::LtsToolBar"); + } + QString toolTip() const override + { + return QLatin1String("Tool Bar"); + } + QString whatsThis() const override + { + return QLatin1String("Tool Bar"); + } + QString domXml() const override + { + return QStringLiteral(""); + } + QWidget *createWidget(QWidget *parent) override + { + return new LtsWidgets::LtsToolBar(parent); + } +}; + + +class LtsToolBarAreaPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QLatin1String("LtsWidgets/lts_tool_bar_area.h"); + } + QString name() const override + { + return QLatin1String("LtsWidgets::LtsToolBarArea"); + } + QString toolTip() const override + { + return QLatin1String("Tool Bar Area"); + } + QString whatsThis() const override + { + return QLatin1String("Tool Bar Area"); + } + QString domXml() const override + { + return QStringLiteral(""); + } + QWidget *createWidget(QWidget *parent) override + { + return new LtsWidgets::LtsToolBarArea(parent); + } +}; + + +class LtsToolBoxPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QLatin1String("LtsWidgets/lts_tool_box.h"); + } + QString name() const override + { + return QLatin1String("LtsWidgets::LtsToolBox"); + } + QString toolTip() const override + { + return QLatin1String("Tool Box"); + } + QString whatsThis() const override + { + return QLatin1String("Tool Box"); + } + QString domXml() const override + { + return QStringLiteral(""); + } + QWidget *createWidget(QWidget *parent) override + { + return new LtsWidgets::LtsToolBox(parent); + } +}; + + +class LtsDataViewPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QLatin1String("LtsDataView/lts_data_view.h"); + } + QString name() const override + { + return QLatin1String("LtsDataView::LtsDataView"); + } + QString toolTip() const override + { + return QLatin1String("DataView"); + } + QString whatsThis() const override + { + return QLatin1String("DataView"); + } + QString domXml() const override + { + return QStringLiteral(""); + } + QWidget *createWidget(QWidget *parent) override + { + return new LtsDataView::LtsDataView(parent); + } +}; + + +class LtsFramelessDialogPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QLatin1String("LtsWidgets/lts_frameless_dialog.h"); + } + QString name() const override + { + return QLatin1String("LtsWidgets::LtsFramelessDialog"); + } + QString toolTip() const override + { + return QLatin1String("Enhanced QDialog"); + } + QString whatsThis() const override + { + return QLatin1String("Dialog Widget"); + } + QString domXml() const override + { + return QLatin1String("\ + \ + \ + \ + 0\ + 0\ + 500\ + 200\ + \ + \ + \ + "); + } + QWidget *createWidget(QWidget *parent) override + { + return new LtsWidgets::LtsFramelessDialog(parent); + } +}; + + +class LtsDragIconPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QLatin1String("LtsWidgets/lts_drag_icon.h"); + } + QString name() const override + { + return QLatin1String("LtsWidgets::LtsDragIcon"); + } + QString toolTip() const override + { + return QLatin1String("Class for standard buttons with icons"); + } + QString whatsThis() const override + { + return QLatin1String("Button Widget"); + } + QString domXml() const override + { + return QLatin1String("\ + \ + \ + "); + } + QWidget *createWidget(QWidget *parent) override + { + LtsWidgets::LtsDragIcon *b = new LtsWidgets::LtsDragIcon(parent); + return b; + } +}; + + +class LtsComboBoxPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QLatin1String("LtsWidgets/lts_combo_box.h"); + } + QString name() const override + { + return QLatin1String("LtsWidgets::LtsComboBox"); + } + QString toolTip() const override + { + return QLatin1String("Class for standard ComBoBoxes"); + } + QString whatsThis() const override + { + return QLatin1String("ComboBox Widget"); + } + QString domXml() const override + { + return QLatin1String("\ + \ + \ + "); + } + QWidget *createWidget(QWidget *parent) override + { + LtsWidgets::LtsComboBox *b = new LtsWidgets::LtsComboBox(parent); + return b; + } +}; + + +class LtsSpinBoxPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QLatin1String("LtsWidgets/lts_spinbox.h"); + } + QString name() const override + { + return QLatin1String("LtsWidgets::LtsSpinBox"); + } + QString toolTip() const override + { + return QLatin1String("Class for standard SpinBoxes"); + } + QString whatsThis() const override + { + return QLatin1String("SpinBox Widget"); + } + QString domXml() const override + { + return QLatin1String("\ + \ + \ + "); + } + QWidget *createWidget(QWidget *parent) override + { + LtsWidgets::LtsSpinBox *b = new LtsWidgets::LtsSpinBox(parent); + return b; + } +}; + +class LtsMultiRangeSpinBoxPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QLatin1String("LtsWidgets/lts_multi_range_spinbox.h"); + } + QString name() const override + { + return QLatin1String("LtsWidgets::LtsMultiRangeSpinBox"); + } + QString toolTip() const override + { + return QLatin1String("Class for standard SpinBoxes with multiple ranges"); + } + QString whatsThis() const override + { + return QLatin1String("SpinBox Widget"); + } + QString domXml() const override + { + return QLatin1String("\ + \ + \ + "); + } + QWidget *createWidget(QWidget *parent) override + { + LtsWidgets::LtsMultiRangeSpinBox *b = new LtsWidgets::LtsMultiRangeSpinBox(parent); + return b; + } +}; + + + +class LtsDoubleSpinBoxPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QLatin1String("LtsWidgets/lts_spinbox.h"); + } + QString name() const override + { + return QLatin1String("LtsWidgets::LtsDoubleSpinBox"); + } + QString toolTip() const override + { + return QLatin1String("Class for standard DoubleSpinBoxes"); + } + QString whatsThis() const override + { + return QLatin1String("DoubleSpinBox Widget"); + } + QString domXml() const override + { + return QLatin1String("\ + \ + \ + "); + } + QWidget *createWidget(QWidget *parent) override + { + LtsWidgets::LtsDoubleSpinBox *b = new LtsWidgets::LtsDoubleSpinBox(parent); + return b; + } +}; + + +class LtsDateTimeEditPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QLatin1String("LtsWidgets/lts_date_time_edit.h"); + } + QString name() const override + { + return QLatin1String("LtsWidgets::LtsDateTimeEdit"); + } + QString toolTip() const override + { + return QLatin1String("Class for LTS Date Time Edit"); + } + QString whatsThis() const override + { + return QLatin1String("LtsDateTimeEdit Widget"); + } + QString domXml() const override + { + return QLatin1String("\n" + " \n" + " \n" + " 0\n" + " 0\n" + " \n" + " \n" + ""); + } + QWidget *createWidget(QWidget *parent) override + { + LtsWidgets::LtsDateTimeEdit *b = new LtsWidgets::LtsDateTimeEdit(parent); + return b; + } +}; + +class LtsFileDialogWidgetPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QLatin1String("LtsFileDialog/lts_file_dialog.h"); + } + QString name() const override + { + return QLatin1String("LtsWidgets::LtsFileDialogWidget"); + } + QString toolTip() const override + { + return QLatin1String("File Dialog Widget"); + } + QString whatsThis() const override + { + return QLatin1String("FileDialogWidget"); + } + QString domXml() const override + { + return QStringLiteral(""); + } + QWidget *createWidget(QWidget *parent) override + { + return new LtsWidgets::LtsFileDialogWidget(parent); + } +}; + +class LtsFileDialogPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QLatin1String("LtsFileDialog/lts_file_dialog.h"); + } + QString name() const override + { + return QLatin1String("LtsWidgets::LtsFileDialog"); + } + QString toolTip() const override + { + return QLatin1String("File Dialog Widget"); + } + QString whatsThis() const override + { + return QLatin1String("FileDialog"); + } + QString domXml() const override + { + return QStringLiteral(""); + } + QWidget *createWidget(QWidget *parent) override + { + return new LtsWidgets::LtsFileDialog(parent); + } +}; + +class LtsReportDisplayWidgetPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QLatin1String("LtsReportDisplay/lts_report_display_widget.h"); + } + QString name() const override + { + return QLatin1String("LtsWidgets::LtsReportDisplayWidget"); + } + QString toolTip() const override + { + return QLatin1String("Report Display Widget"); + } + QString whatsThis() const override + { + return QLatin1String("ReportDisplayWidget"); + } + QString domXml() const override + { + return QStringLiteral(""); + } + QWidget *createWidget(QWidget *parent) override + { + return new LtsWidgets::LtsReportDisplayWidget(parent); + } +}; + +class LtsLedWidgetPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QLatin1String("LtsWidgets/lts_led_widget.h"); + } + QString name() const override + { + return QLatin1String("LtsWidgets::LtsLedWidget"); + } + QString toolTip() const override + { + return QLatin1String("Class for an LED Widget"); + } + QString whatsThis() const override + { + return QLatin1String("LED Widget"); + } + QString domXml() const override + { + return QLatin1String("\ + \ + \ + "); + } + QWidget *createWidget(QWidget *parent) override + { + return new LtsWidgets::LtsLedWidget(parent); + } +}; + +class LtsRangeSliderPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QLatin1String("LtsWidgets/lts_range_slider.h"); + } + QString name() const override + { + return QLatin1String("LtsWidgets::LtsRangeSlider"); + } + QString toolTip() const override + { + return QLatin1String("Class for an Range Slider"); + } + QString whatsThis() const override + { + return QLatin1String("Range Slider"); + } + QString domXml() const override + { + return QLatin1String("\ + \ + \ + "); + } + QWidget *createWidget(QWidget *parent) override + { + return new LtsWidgets::LtsRangeSlider(parent); + } +}; + +class LtsImageCropperPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString name() const override + { + return QLatin1String("LtsWidgets::LtsImageCropper"); + } + QString toolTip() const override + { + return QLatin1String("Class for an Image Cropper Widget"); + } + QString whatsThis() const override + { + return QLatin1String("Image Cropper Widget"); + } + QString includeFile() const override + { + return QLatin1String("LtsWidgets/LtsImageCropper/lts_image_cropper.h"); + } + QWidget *createWidget(QWidget *parent) override + { + return new LtsWidgets::LtsImageCropper(parent); + } + QString domXml() const override + { + return QLatin1String("\n" + " \n" + " \n" + " 0\n" + " 0\n" + " \n" + " \n" + " \n" + " \n" + " 100\n" + " 100\n" + " \n" + " \n" + " \n" + " \n" + " 0\n" + " 0\nitfield" + " \n" + " \n" + ""); + } +}; + +class LtsMSecDateTimeEditPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString name() const override + { + return QLatin1String("LtsWidgets::LtsMSecDateTimeEdit"); + } + QString toolTip() const override + { + return QLatin1String("Class for an Date/Time Widget with 40msecs step."); + } + QString whatsThis() const override + { + return QLatin1String("Date/Time Widget with 40msecs step"); + } + QString includeFile() const override + { + return QLatin1String("LtsWidgets/lts_msec_date_time_edit.h"); + } + QWidget *createWidget(QWidget *parent) override + { + return new LtsWidgets::LtsMSecDateTimeEdit(parent); + } + QString domXml() const override + { + return QLatin1String("\n" + ""); + } +}; + +class LtsBitfieldWidgetPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QLatin1String("lts_bitfield_widget.h"); + } + QString name() const override + { + return QLatin1String("LtsWidgets::LtsBitfieldWidget"); + } + QString toolTip() const override + { + return QLatin1String("Widget for bitwise displaying and editing of quint8"); + } + QString whatsThis() const override + { + return QLatin1String("Widget for bitwise displaying and editing of quint8"); + } + QString domXml() const override + { + return QStringLiteral(""); + } + QWidget *createWidget(QWidget *parent) override + { + return new LtsWidgets::LtsBitfieldWidget(parent); + } +}; + + +class LtsTabSequenceWidgetPlugin final : public LtsDesignerCustomWidgetInterface +{ + Q_OBJECT + + Q_INTERFACES(QDesignerCustomWidgetInterface) +public: + using LtsDesignerCustomWidgetInterface::LtsDesignerCustomWidgetInterface; + + QString includeFile() const override + { + return QLatin1String("LtsWidgets/lts_tab_sequence_widget.h"); + } + QString name() const override + { + return QLatin1String("LtsWidgets::LtsTabSequenceWidget"); + } + QString toolTip() const override + { + return QLatin1String("Widget for a Tab Sequence"); + } + QString whatsThis() const override + { + return QLatin1String("Widget for a Tab Sequence"); + } + QString domXml() const override + { + return QStringLiteral(""); + } + QWidget *createWidget(QWidget *parent) override + { + return new LtsWidgets::LtsTabSequenceWidget(parent); + } +}; + + +class Plugin final : public QObject, public QDesignerCustomWidgetCollectionInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.rde.sls.ltswidgetsplugin" FILE "ltswidgetsplugin.json") + Q_INTERFACES(QDesignerCustomWidgetCollectionInterface) +public: + explicit Plugin(QObject *parent = nullptr); + + QList customWidgets() const override + { + return m_plugins; + } + +private: + QList m_plugins; +}; + + +Plugin::Plugin(QObject *parent) + : QObject(parent) +{ + m_plugins.append(new LtsCoordWidgetPlugin(this)); + m_plugins.append(new LtsDialogButtonBoxPlugin(this)); + m_plugins.append(new LtsImageEditorPlugin(this)); + m_plugins.append(new LtsElidedLabelPlugin(this)); + m_plugins.append(new LtsPictureViewPlugin(this)); + m_plugins.append(new LtsProfileButtonPlugin(this)); + m_plugins.append(new LtsSplitterPlugin(this)); + m_plugins.append(new LtsTabBarPlugin(this)); + m_plugins.append(new LtsTabWidgetPlugin(this)); + m_plugins.append(new LtsToolBarPlugin(this)); + m_plugins.append(new LtsToolBarAreaPlugin(this)); + m_plugins.append(new LtsToolBoxPlugin(this)); + m_plugins.append(new LtsFramelessDialogPlugin(this)); + m_plugins.append(new LtsDragIconPlugin(this)); + m_plugins.append(new LtsComboBoxPlugin(this)); + m_plugins.append(new LtsSpinBoxPlugin(this)); + m_plugins.append(new LtsMultiRangeSpinBoxPlugin(this)); + m_plugins.append(new LtsDoubleSpinBoxPlugin(this)); + m_plugins.append(new LtsDateTimeEditPlugin(this)); + m_plugins.append(new LtsDataViewPlugin(this)); + m_plugins.append(new LtsFileDialogWidgetPlugin(this)); + m_plugins.append(new LtsFileDialogPlugin(this)); + m_plugins.append(new LtsReportDisplayWidgetPlugin(this)); + m_plugins.append(new LtsLedWidgetPlugin(this)); + m_plugins.append(new LtsRangeSliderPlugin(this)); + m_plugins.append(new LtsImageCropperPlugin(this)); + m_plugins.append(new LtsMSecDateTimeEditPlugin(this)); + m_plugins.append(new LtsBitfieldWidgetPlugin(this)); + m_plugins.append(new LtsTabSequenceWidgetPlugin(this)); +} + +} +#include "ltswidgetsplugin.moc" diff --git a/Lts/Common/LtsWidgets/ltswidgetsplugin.json b/Lts/Common/LtsWidgets/ltswidgetsplugin.json new file mode 100755 index 000000000..a2697da42 --- /dev/null +++ b/Lts/Common/LtsWidgets/ltswidgetsplugin.json @@ -0,0 +1,5 @@ +{ + "name" : "Lts Widgets", + "version" : "1.0.0", + "dependencies" : [] +} diff --git a/Lts/Common/LtsWidgets/resources/DMF.wav b/Lts/Common/LtsWidgets/resources/DMF.wav new file mode 100755 index 000000000..d6f7ccc71 Binary files /dev/null and b/Lts/Common/LtsWidgets/resources/DMF.wav differ diff --git a/Lts/Common/LtsWidgets/resources/European-Union-Flag.png b/Lts/Common/LtsWidgets/resources/European-Union-Flag.png new file mode 100755 index 000000000..06afb3da1 Binary files /dev/null and b/Lts/Common/LtsWidgets/resources/European-Union-Flag.png differ diff --git a/Lts/Common/LtsWidgets/resources/Germany-Flag.png b/Lts/Common/LtsWidgets/resources/Germany-Flag.png new file mode 100755 index 000000000..17d36412b Binary files /dev/null and b/Lts/Common/LtsWidgets/resources/Germany-Flag.png differ diff --git a/Lts/Common/LtsWidgets/resources/Great-Britain-Flag.png b/Lts/Common/LtsWidgets/resources/Great-Britain-Flag.png new file mode 100755 index 000000000..7465dce46 Binary files /dev/null and b/Lts/Common/LtsWidgets/resources/Great-Britain-Flag.png differ diff --git a/Lts/Common/LtsWidgets/resources/ScriptEditor-Changelog-1.png b/Lts/Common/LtsWidgets/resources/ScriptEditor-Changelog-1.png new file mode 100755 index 000000000..08135216c Binary files /dev/null and b/Lts/Common/LtsWidgets/resources/ScriptEditor-Changelog-1.png differ diff --git a/Lts/Common/LtsWidgets/resources/US-Flag.png b/Lts/Common/LtsWidgets/resources/US-Flag.png new file mode 100755 index 000000000..3e05503d7 Binary files /dev/null and b/Lts/Common/LtsWidgets/resources/US-Flag.png differ diff --git a/Lts/Common/LtsWidgets/resources/ack.svg b/Lts/Common/LtsWidgets/resources/ack.svg new file mode 100755 index 000000000..79b40b4ac --- /dev/null +++ b/Lts/Common/LtsWidgets/resources/ack.svg @@ -0,0 +1,56 @@ + + + + + + + + image/svg+xml + + + + + + + diff --git a/Lts/Common/LtsWidgets/resources/ack_dark.svg b/Lts/Common/LtsWidgets/resources/ack_dark.svg new file mode 100755 index 000000000..8739fc398 --- /dev/null +++ b/Lts/Common/LtsWidgets/resources/ack_dark.svg @@ -0,0 +1,56 @@ + + + + + + + + image/svg+xml + + + + + + + diff --git a/Lts/Common/LtsWidgets/resources/beep.wav b/Lts/Common/LtsWidgets/resources/beep.wav new file mode 100755 index 000000000..8343a63e7 Binary files /dev/null and b/Lts/Common/LtsWidgets/resources/beep.wav differ diff --git a/Lts/Common/LtsWidgets/resources/close.svg b/Lts/Common/LtsWidgets/resources/close.svg new file mode 100755 index 000000000..71d8a3065 --- /dev/null +++ b/Lts/Common/LtsWidgets/resources/close.svg @@ -0,0 +1,35 @@ + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Lts/Common/LtsWidgets/resources/close_dark.svg b/Lts/Common/LtsWidgets/resources/close_dark.svg new file mode 100755 index 000000000..cc00a5706 --- /dev/null +++ b/Lts/Common/LtsWidgets/resources/close_dark.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Lts/Common/LtsWidgets/resources/effectarea-enter.wav b/Lts/Common/LtsWidgets/resources/effectarea-enter.wav new file mode 100755 index 000000000..85d55439d Binary files /dev/null and b/Lts/Common/LtsWidgets/resources/effectarea-enter.wav differ diff --git a/Lts/Common/LtsWidgets/resources/effectarea-leave.wav b/Lts/Common/LtsWidgets/resources/effectarea-leave.wav new file mode 100755 index 000000000..ce378a5a8 Binary files /dev/null and b/Lts/Common/LtsWidgets/resources/effectarea-leave.wav differ diff --git a/Lts/Common/LtsWidgets/resources/entryNew.wav b/Lts/Common/LtsWidgets/resources/entryNew.wav new file mode 100755 index 000000000..805a6e887 Binary files /dev/null and b/Lts/Common/LtsWidgets/resources/entryNew.wav differ diff --git a/Lts/Common/LtsWidgets/resources/error.svg b/Lts/Common/LtsWidgets/resources/error.svg new file mode 100755 index 000000000..4aa24c09f --- /dev/null +++ b/Lts/Common/LtsWidgets/resources/error.svg @@ -0,0 +1,66 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Lts/Common/LtsWidgets/resources/info.svg b/Lts/Common/LtsWidgets/resources/info.svg new file mode 100755 index 000000000..4657b2788 --- /dev/null +++ b/Lts/Common/LtsWidgets/resources/info.svg @@ -0,0 +1,71 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/Lts/Common/LtsWidgets/resources/jquery-3.1.0.min.js b/Lts/Common/LtsWidgets/resources/jquery-3.1.0.min.js new file mode 100755 index 000000000..f6a6a99e6 --- /dev/null +++ b/Lts/Common/LtsWidgets/resources/jquery-3.1.0.min.js @@ -0,0 +1,4 @@ +/*! jQuery v3.1.0 | (c) jQuery Foundation | jquery.org/license */ +!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.1.0",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null!=a?a<0?this[a+this.length]:this[a]:f.call(this)},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"label"in b&&b.disabled===a||"form"in b&&b.disabled===a||"form"in b&&b.disabled===!1&&(b.isDisabled===a||b.isDisabled!==!a&&("label"in b||!ea(b))!==a)}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(_,aa),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=V.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(_,aa),$.test(j[0].type)&&qa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&sa(j),!a)return G.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||$.test(a)&&qa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext,B=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,C=/^.[^:#\[\.,]*$/;function D(a,b,c){if(r.isFunction(b))return r.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return r.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(C.test(b))return r.filter(b,a,c);b=r.filter(b,a)}return r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType})}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(D(this,a||[],!1))},not:function(a){return this.pushStack(D(this,a||[],!0))},is:function(a){return!!D(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var E,F=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,G=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||E,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:F.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),B.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};G.prototype=r.fn,E=r(d);var H=/^(?:parents|prev(?:Until|All))/,I={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function J(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return J(a,"nextSibling")},prev:function(a){return J(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return a.contentDocument||r.merge([],a.childNodes)}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(I[a]||r.uniqueSort(e),H.test(a)&&e.reverse()),this.pushStack(e)}});var K=/\S+/g;function L(a){var b={};return r.each(a.match(K)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?L(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function M(a){return a}function N(a){throw a}function O(a,b,c){var d;try{a&&r.isFunction(d=a.promise)?d.call(a).done(b).fail(c):a&&r.isFunction(d=a.then)?d.call(a,b,c):b.call(void 0,a)}catch(a){c.call(void 0,a)}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b=f&&(d!==N&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:M,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:M)),c[2][3].add(g(0,a,r.isFunction(d)?d:N))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(O(a,g.done(h(c)).resolve,g.reject),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)O(e[c],h(c),g.reject);return g.promise()}});var P=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&P.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var Q=r.Deferred();r.fn.ready=function(a){return Q.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,holdReady:function(a){a?r.readyWait++:r.ready(!0)},ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||Q.resolveWith(d,[r]))}}),r.ready.then=Q.then;function R(){d.removeEventListener("DOMContentLoaded",R),a.removeEventListener("load",R),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",R),a.addEventListener("load",R));var S=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)S(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0, +r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h1,null,!0)},removeData:function(a){return this.each(function(){W.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=V.get(a,b),c&&(!d||r.isArray(c)?d=V.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return V.get(a,c)||V.access(a,c,{empty:r.Callbacks("once memory").add(function(){V.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length\x20\t\r\n\f]+)/i,ja=/^$|\/(?:java|ecma)script/i,ka={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ka.optgroup=ka.option,ka.tbody=ka.tfoot=ka.colgroup=ka.caption=ka.thead,ka.th=ka.td;function la(a,b){var c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&r.nodeName(a,b)?r.merge([a],c):c}function ma(a,b){for(var c=0,d=a.length;c-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=la(l.appendChild(f),"script"),j&&ma(g),c){k=0;while(f=g[k++])ja.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var pa=d.documentElement,qa=/^key/,ra=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,sa=/^([^.]*)(?:\.(.+)|)/;function ta(){return!0}function ua(){return!1}function va(){try{return d.activeElement}catch(a){}}function wa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)wa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=ua;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(pa,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(K)||[""],j=b.length;while(j--)h=sa.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.hasData(a)&&V.get(a);if(q&&(i=q.events)){b=(b||"").match(K)||[""],j=b.length;while(j--)if(h=sa.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&V.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(V.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c-1:r.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h\x20\t\r\n\f]*)[^>]*)\/>/gi,ya=/\s*$/g;function Ca(a,b){return r.nodeName(a,"table")&&r.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a:a}function Da(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Ea(a){var b=Aa.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Fa(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(V.hasData(a)&&(f=V.access(a),g=V.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c1&&"string"==typeof q&&!o.checkClone&&za.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ha(f,b,c,d)});if(m&&(e=oa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(la(e,"script"),Da),i=h.length;l")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=la(h),f=la(a),d=0,e=f.length;d0&&ma(g,!i&&la(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(T(c)){if(b=c[V.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[V.expando]=void 0}c[W.expando]&&(c[W.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ia(this,a,!0)},remove:function(a){return Ia(this,a)},text:function(a){return S(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.appendChild(a)}})},prepend:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(la(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return S(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!ya.test(a)&&!ka[(ia.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c1)}});function Xa(a,b,c,d,e){return new Xa.prototype.init(a,b,c,d,e)}r.Tween=Xa,Xa.prototype={constructor:Xa,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=Xa.propHooks[this.prop];return a&&a.get?a.get(this):Xa.propHooks._default.get(this)},run:function(a){var b,c=Xa.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Xa.propHooks._default.set(this),this}},Xa.prototype.init.prototype=Xa.prototype,Xa.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},Xa.propHooks.scrollTop=Xa.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=Xa.prototype.init,r.fx.step={};var Ya,Za,$a=/^(?:toggle|show|hide)$/,_a=/queueHooks$/;function ab(){Za&&(a.requestAnimationFrame(ab),r.fx.tick())}function bb(){return a.setTimeout(function(){Ya=void 0}),Ya=r.now()}function cb(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=aa[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function db(a,b,c){for(var d,e=(gb.tweeners[b]||[]).concat(gb.tweeners["*"]),f=0,g=e.length;f1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?hb:void 0)),void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b),null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&r.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(K); +if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),hb={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=ib[b]||r.find.attr;ib[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=ib[g],ib[g]=e,e=null!=c(a,b,d)?g:null,ib[g]=f),e}});var jb=/^(?:input|select|textarea|button)$/i,kb=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return S(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):jb.test(a.nodeName)||kb.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});var lb=/[\t\r\n\f]/g;function mb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,mb(this)))});if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=mb(c),d=1===c.nodeType&&(" "+e+" ").replace(lb," ")){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=r.trim(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,mb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=mb(c),d=1===c.nodeType&&(" "+e+" ").replace(lb," ")){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=r.trim(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,mb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(K)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=mb(this),b&&V.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":V.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+mb(c)+" ").replace(lb," ").indexOf(b)>-1)return!0;return!1}});var nb=/\r/g,ob=/[\x20\t\r\n\f]+/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":r.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(nb,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:r.trim(r.text(a)).replace(ob," ")}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type,g=f?null:[],h=f?e+1:d.length,i=e<0?h:f?e:0;i-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(r.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var pb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!pb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,pb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(V.get(h,"events")||{})[b.type]&&V.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&T(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!T(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=V.access(d,b);e||d.addEventListener(a,c,!0),V.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=V.access(d,b)-1;e?V.access(d,b,e):(d.removeEventListener(a,c,!0),V.remove(d,b))}}});var qb=a.location,rb=r.now(),sb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var tb=/\[\]$/,ub=/\r?\n/g,vb=/^(?:submit|button|image|reset|file)$/i,wb=/^(?:input|select|textarea|keygen)/i;function xb(a,b,c,d){var e;if(r.isArray(b))r.each(b,function(b,e){c||tb.test(a)?d(a,e):xb(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)xb(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(r.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)xb(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&wb.test(this.nodeName)&&!vb.test(a)&&(this.checked||!ha.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:r.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(ub,"\r\n")}}):{name:b.name,value:c.replace(ub,"\r\n")}}).get()}});var yb=/%20/g,zb=/#.*$/,Ab=/([?&])_=[^&]*/,Bb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Cb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Db=/^(?:GET|HEAD)$/,Eb=/^\/\//,Fb={},Gb={},Hb="*/".concat("*"),Ib=d.createElement("a");Ib.href=qb.href;function Jb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(K)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Kb(a,b,c,d){var e={},f=a===Gb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Lb(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Mb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Nb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:qb.href,type:"GET",isLocal:Cb.test(qb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Hb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Lb(Lb(a,r.ajaxSettings),b):Lb(r.ajaxSettings,a)},ajaxPrefilter:Jb(Fb),ajaxTransport:Jb(Gb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Bb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||qb.href)+"").replace(Eb,qb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(K)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Ib.protocol+"//"+Ib.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Kb(Fb,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Db.test(o.type),f=o.url.replace(zb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(yb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(sb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Ab,""),n=(sb.test(f)?"&":"?")+"_="+rb++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Hb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Kb(Gb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Mb(o,y,d)),v=Nb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Ob={0:200,1223:204},Pb=r.ajaxSettings.xhr();o.cors=!!Pb&&"withCredentials"in Pb,o.ajax=Pb=!!Pb,r.ajaxTransport(function(b){var c,d;if(o.cors||Pb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Ob[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r("