1723 lines
58 KiB
C++
Executable File
1723 lines
58 KiB
C++
Executable File
/**
|
|
* @RDE_HEADER@
|
|
* Timeline Model for DDS
|
|
* $Id$
|
|
* $HeadURL$
|
|
* $Author$
|
|
* Created by Mike Wille, SLSE, 2017-03-31
|
|
*/
|
|
|
|
#include "excon_filter.h"
|
|
#include "tln_dialog_model.h"
|
|
#include "detail_data.h"
|
|
#include "logger.h"
|
|
|
|
#include <LtsTimelineClassic/EventList.h>
|
|
#include <LtsTimelineClassic/Item.h>
|
|
#include <LtsTimelineClassic/EventTools.h>
|
|
#include <ExerciseEventManagement/eem_translation.h>
|
|
#include <LtsUtilsGuiDds/request_helper.h>
|
|
#include <LtsSession/lts_session.h>
|
|
#include <LtsSession/lts_session_data.h>
|
|
#include <Services/ProjectDataManagement/pdm_enums.hpp>
|
|
#include <Services/ParticipantManagement/ppm_translation.h>
|
|
#include <AudioVideoManagement/stream_name_mapping.h>
|
|
#include <Plugins/Timeline/tln_data_plugin_interface.h>
|
|
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QJsonArray>
|
|
#include <QJsonValue>
|
|
#include <QDateTime>
|
|
#include <QMutableVectorIterator>
|
|
#include <QtWebSockets/QWebSocket> // TODO: rm ws
|
|
#include <QTimer>
|
|
#include <QUrl>
|
|
#include <QUrlQuery>
|
|
|
|
|
|
using namespace LtsTimelineClassic;
|
|
|
|
|
|
namespace Timeline
|
|
{
|
|
|
|
DDSTimelineModel::DDSTimelineModel(QObject *parent)
|
|
: LtsTimelineClassic::AbstractTimelineModel(parent)
|
|
, m_session(nullptr)
|
|
, m_startTime(-1)
|
|
, m_endTime(-1)
|
|
, m_detailData(nullptr)
|
|
, m_streamNameMapping(nullptr)
|
|
, m_isLive(false)
|
|
, m_cacheSize(15000) // 15.000 - no problemo :)
|
|
, m_maxCacheRanges(20)
|
|
, m_eventCount(0)
|
|
, m_lastCachedEventsId(0)
|
|
, m_createFakeEvents(false)
|
|
//, m_dtorEmergencyMode(false)
|
|
, m_logger(getLogger("DialogModel"))
|
|
{
|
|
m_liveCache.visible = false;
|
|
m_liveCache.start = 0;
|
|
m_liveCache.end = 0;
|
|
m_liveCache.incomplete = false;
|
|
}
|
|
|
|
|
|
DDSTimelineModel::~DDSTimelineModel()
|
|
{
|
|
// no need to uncache anything.. AbstractTimelineModel deletes all Events itself
|
|
// for (auto i1 = m_cachedEvents.begin(); i1 != m_cachedEvents.end(); ++i1)
|
|
// {
|
|
// CacheRange &cr = *i1;
|
|
// uncache(cr);
|
|
// }
|
|
|
|
|
|
// // risk dead memory for the safety that the application does not crash..
|
|
// // FIXME: figure out why some events were already deleted but still cached !!
|
|
// if (!m_dtorEmergencyMode)
|
|
// return;
|
|
|
|
|
|
// for (auto i1 = m_cachedEvents.begin(); i1 != m_cachedEvents.end(); ++i1)
|
|
// {
|
|
// CacheRange &cr = *i1;
|
|
// qDeleteAll(cr.events); <= Item deletes all its events from its event list in its dtor
|
|
// }
|
|
}
|
|
|
|
|
|
bool DDSTimelineModel::init(const std::shared_ptr<TimeLineManagement::ForeignDataInterface> &eemForeign,
|
|
const std::shared_ptr<MarkerManagement::Interface::Dtc> &mrkDtc,
|
|
const std::shared_ptr<ExerciseManagement::Interface::Dtc> &exmDtc,
|
|
const std::shared_ptr<ParticipantManagement::Interface::Dtc> &ppmDtc,
|
|
const std::shared_ptr<ProjectDataManagement::Interface::Dtc> &pdmDtc,
|
|
const std::shared_ptr<InfrastructureManagement::Interface::Dtc> &ismDtc,
|
|
LtsSession::LtsSession *session,
|
|
DetailData *detailData,
|
|
AudioVideoManagement::StreamNameMapping *streamNameMapping)
|
|
{
|
|
//Q_ASSERT(streamNameMapping);
|
|
m_eemForeign = eemForeign;
|
|
m_dtcExm = exmDtc;
|
|
m_ppmDtc = ppmDtc;
|
|
m_pdmDtc = pdmDtc;
|
|
m_ismDtc = ismDtc;
|
|
m_mrkDtc = mrkDtc;
|
|
m_session = session;
|
|
m_detailData = detailData;
|
|
m_streamNameMapping = streamNameMapping;
|
|
|
|
connect(m_eemForeign.get(), &TimeLineManagement::ForeignDataInterface::liveEvent,
|
|
this, &DDSTimelineModel::slotAddLiveEvent);
|
|
|
|
#ifndef LTS_DEBUG
|
|
if (LtsSessionData::LtsSessionData::getInstance()->debugFlagValue(QStringLiteral("Timeline.DebugWidget")) != QVariant(true))
|
|
#endif
|
|
{
|
|
// add fake live events every x seconds if fake & live are enabled
|
|
QTimer *liveTimer = new QTimer(this);
|
|
liveTimer->setInterval(4000);
|
|
connect(liveTimer, &QTimer::timeout,
|
|
this, [this]()
|
|
{
|
|
if (m_createFakeEvents && m_isLive)
|
|
slotAddLiveEventsFake();
|
|
});
|
|
|
|
liveTimer->start();
|
|
}
|
|
|
|
connect(m_mrkDtc.get(), &MarkerManagement::Interface::Dtc::markerChanged,
|
|
this, &DDSTimelineModel::slotMarkerChanged);
|
|
|
|
// load all markers at once
|
|
auto cb = [this](const MarkerManagement::Marker &marker)
|
|
{
|
|
addMarkerManagementMarker(marker);
|
|
};
|
|
m_mrkDtc->markers(cb);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static int testLiveEventId = 0;
|
|
void DDSTimelineModel::slotAddLiveEventsFake()
|
|
{
|
|
ExerciseEventManagement::Event event;
|
|
|
|
event.id(testLiveEventId++);
|
|
event.eventTypeId(ProjectDataManagement::EventType::Hit);
|
|
event.sampleTime(currentTime());
|
|
event.properties(QStringLiteral("{}").toStdString());
|
|
|
|
slotAddLiveEvent(event);
|
|
}
|
|
|
|
|
|
void DDSTimelineModel::saveAndClearLiveCache() // if not live anymore or live cache is full
|
|
{
|
|
addCacheRange(m_liveCache, m_liveCache.events, false /* never */, false /* already added */);
|
|
m_liveCache.events.clear();
|
|
m_liveCache.start = 0;
|
|
m_liveCache.end = 0;
|
|
}
|
|
|
|
|
|
void DDSTimelineModel::setActiveEventTypeIds(const QSet<int> &activeEventTypeIds)
|
|
{
|
|
m_activeEventTypeIds = activeEventTypeIds;
|
|
}
|
|
|
|
|
|
void DDSTimelineModel::slotAddLiveEvent(const ExerciseEventManagement::Event &event)
|
|
{
|
|
Event *ev = getEvent(event.id());
|
|
|
|
if (ev != nullptr)
|
|
{
|
|
QJsonObject root(getDetailData(event));
|
|
ev->setEndT(root[QStringLiteral("endTime")].toDouble()); // might be a audio/video event
|
|
ev->setDetailData(root);
|
|
DEBUG_LOG(m_logger) << "live event not added - already exists - modified instead - end";
|
|
//invalidateItemForEvent(event.id()); // tell the item that its event list changed (because the end time changed, which triggers the item to re-create the EGLs and therefore EGL length changes visually) - this is needed for events with endTime == -1 ! because th
|
|
return;
|
|
}
|
|
|
|
if (!isLive())
|
|
{
|
|
DEBUG_LOG(m_logger) << "not adding live event because we're not live";
|
|
return;
|
|
}
|
|
|
|
|
|
// don't add if event type not in list of active event types
|
|
int eventType = static_cast<int>(event.eventTypeId().underlying());
|
|
if (!m_activeEventTypeIds.contains(eventType))
|
|
{
|
|
DEBUG_LOG(m_logger) << "live event not added - event type ("<<eventType<<") inactive";
|
|
//qWarning() << "m_activeEventTypeIds" << m_activeEventTypeIds;
|
|
return;
|
|
}
|
|
|
|
|
|
if (m_liveCache.events.count() > m_cacheSize) // cache got too big, save it as standard cache, clear the live cache and add the new event in the new cache
|
|
{
|
|
DEBUG_LOG(m_logger) << "more than " << m_cacheSize << " live events - archiving and clearing";
|
|
saveAndClearLiveCache();
|
|
}
|
|
|
|
qint64 start = m_liveCache.start;
|
|
qint64 end = m_liveCache.end;
|
|
qint64 eventTime = event.sampleTime();
|
|
|
|
if (start > eventTime || start == 0)
|
|
m_liveCache.start = eventTime;
|
|
|
|
if (end < eventTime)
|
|
m_liveCache.end = eventTime;
|
|
|
|
LtsTimelineClassic::Event *ltsEvent = createEvent(event);
|
|
m_liveCache.events.append(ltsEvent);
|
|
|
|
rebuildTimelineKnownRanges(); // schedule rebuild because live cache (start & end) has changed
|
|
|
|
#ifdef LTS_DEBUG // expensive
|
|
DEBUG_LOG(m_logger) << "added live event at " << eventTime << " - cache: " << start << " " << end << ", " << m_liveCache.events.count() << " events";
|
|
#endif
|
|
|
|
addEventAndFilter(ltsEvent);
|
|
}
|
|
|
|
|
|
void DDSTimelineModel::setTimeRange(qint64 min, qint64 max)
|
|
{
|
|
m_lowestPositionedItem = min;
|
|
m_highestPositionedItem = max;
|
|
}
|
|
|
|
|
|
qint64 DDSTimelineModel::currentTime() const
|
|
{
|
|
return m_session->partitionTime();
|
|
}
|
|
|
|
|
|
void DDSTimelineModel::applyFilter(const QJsonArray &filter)
|
|
{
|
|
m_filter = filter;
|
|
invalidateTimeline(); // force recreation of all EGLs
|
|
beginResetModel();
|
|
endResetModel();
|
|
}
|
|
|
|
|
|
void DDSTimelineModel::addCacheRangeToTimeline(const CacheRange &cr)
|
|
{
|
|
for (auto i = cr.events.begin(); i != cr.events.end(); ++i)
|
|
{
|
|
LtsTimelineClassic::Event *event = *i;
|
|
addEventAndFilter(event);
|
|
}
|
|
}
|
|
|
|
|
|
void DDSTimelineModel::addCacheRange(const LtsTimelineClassic::Range &range, const QVector<LtsTimelineClassic::Event *> &cachedEvents, qint64 incompleteEnd, bool addToTimeline) // NOTE: added as visible cache
|
|
{
|
|
CacheRange cr;
|
|
|
|
cr.visible = true;
|
|
cr.start = range.start;
|
|
cr.end = range.end;
|
|
cr.events = cachedEvents;
|
|
cr.incomplete = (incompleteEnd > 0);
|
|
|
|
m_eventCount += cachedEvents.count();
|
|
m_cachedEvents.append(QPair<qint64, CacheRange>(m_lastCachedEventsId++, cr));
|
|
|
|
std::sort(m_cachedEvents.begin(), m_cachedEvents.end(), [](const QPair<qint64, CacheRange> &cr1, const QPair<qint64, CacheRange> &cr2) { return cr1.second.start < cr2.second.start; }); // sort the list, by the lowest start time
|
|
|
|
if (addToTimeline)
|
|
{
|
|
addCacheRangeToTimeline(cr);
|
|
rebuildTimelineKnownRanges();
|
|
}
|
|
|
|
DEBUG_LOG(m_logger) << "added cache for " << range.start << " to " << range.end << " - event count now: " << m_eventCount;
|
|
}
|
|
|
|
|
|
bool DDSTimelineModel::isCacheFull() const
|
|
{
|
|
return (m_cachedEvents.count() > m_maxCacheRanges) /*|| makes no sense: (m_eventCount > m_cacheSize)*/;
|
|
}
|
|
|
|
|
|
void DDSTimelineModel::_uncache(const CacheRange &cr)
|
|
{
|
|
// remove all events from cr from timeline
|
|
for (const LtsTimelineClassic::Event *event : qAsConst(cr.events))
|
|
removeEvent(event->uniqueId());
|
|
|
|
rebuildTimelineKnownRanges();
|
|
}
|
|
|
|
|
|
void DDSTimelineModel::uncache(CacheRange &cr)
|
|
{
|
|
QString hash(QStringLiteral("<%1:%2:%3>").arg(cr.start).arg(cr.end).arg(cr.events.count()));
|
|
DEBUG_LOG(m_logger) << "uncaching " << QString::number(qHash(hash), 16) << " " << cr.start << " "<< cr.end;
|
|
|
|
cr.visible = false;
|
|
_uncache(cr);
|
|
}
|
|
|
|
|
|
//void DDSTimelineModel::forceUncache(/*int idx*/)
|
|
//{
|
|
//// if (m_cachedEvents.count() > idx)
|
|
//// {
|
|
//// const CacheRange &cr = m_cachedEvents.firstat(idx);
|
|
//// _uncache(cr);
|
|
//// m_cachedEvents.remove(idx);
|
|
//// }
|
|
|
|
// if (m_cachedEvents.begin() == m_cachedEvents.end())
|
|
// {
|
|
// WARNING_LOG(m_logger) << "could not force uncache as the cache is empty";
|
|
// return;
|
|
// }
|
|
|
|
// m_cachedEvents.erase(m_cachedEvents.begin());
|
|
// rebuildTimelineKnownRanges();
|
|
//}
|
|
|
|
|
|
void DDSTimelineModel::forceUncache() // also deletes cache
|
|
{
|
|
if (m_cachedEvents.isEmpty())
|
|
return;
|
|
|
|
// look for invisible & incomplete caches first
|
|
for (auto i = m_cachedEvents.begin(); i != m_cachedEvents.end(); ++i)
|
|
{
|
|
CacheRange &cr = i->second;
|
|
if (!cr.visible && cr.incomplete)
|
|
{
|
|
uncache(cr);
|
|
m_eventCount -= cr.events.count();
|
|
m_cachedEvents.erase(i);
|
|
DEBUG_LOG(m_logger) << "removed an invisible & incomplete cache - event count now: " << m_eventCount;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// nothing found - look for invisible but complete caches
|
|
for (auto i = m_cachedEvents.begin(); i != m_cachedEvents.end(); ++i)
|
|
{
|
|
CacheRange &cr = i->second;
|
|
if (!cr.visible)
|
|
{
|
|
uncache(cr);
|
|
m_eventCount -= cr.events.count();
|
|
m_cachedEvents.erase(i);
|
|
DEBUG_LOG(m_logger) << "removed an invisible cache - event count now: " << m_eventCount;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// nothing found - all caches are visible, force-remove cache that is the farthest away
|
|
auto i = m_cachedEvents.begin();
|
|
|
|
auto highestStart = i;
|
|
auto lowestEnd = i;
|
|
|
|
for (; i != m_cachedEvents.end(); ++i)
|
|
{
|
|
CacheRange &cr = i->second;
|
|
|
|
if (highestStart->second.start < cr.start)
|
|
highestStart = i;
|
|
if (lowestEnd->second.end > cr.end)
|
|
lowestEnd = i;
|
|
}
|
|
|
|
qint64 sbpos = EventTools::getScrollbarPosition();
|
|
|
|
qint64 highestDiff = highestStart->second.start - sbpos;
|
|
qint64 lowestDiff = sbpos - lowestEnd->second.end;
|
|
|
|
if (highestDiff > lowestDiff)
|
|
{
|
|
uncache(highestStart->second);
|
|
m_eventCount -= highestStart->second.events.count();
|
|
m_cachedEvents.erase(highestStart);
|
|
}
|
|
else
|
|
{
|
|
uncache(lowestEnd->second);
|
|
m_eventCount -= lowestEnd->second.events.count();
|
|
m_cachedEvents.erase(lowestEnd);
|
|
}
|
|
|
|
WARNING_LOG(m_logger) << "force-removing a visible cache ... - event count now: " << m_eventCount;
|
|
}
|
|
|
|
|
|
QVector<LtsTimelineClassic::Range> DDSTimelineModel::getKnownRanges() const // called by the dialog after rebuildTimelineKnownRanges has emitted the signal.... ugh..
|
|
{
|
|
QVector<LtsTimelineClassic::Range> knownRanges;
|
|
knownRanges.reserve(m_cachedEvents.count() + 1);
|
|
|
|
for (const auto &i : qAsConst(m_cachedEvents))
|
|
{
|
|
const CacheRange &cr = i.second;
|
|
|
|
if (!cr.incomplete) // only add as known range if cache is complete (and not missing events) - this tells the user that events are missing / "something is wrong"
|
|
knownRanges.append(Range(cr.start, cr.end));
|
|
}
|
|
|
|
knownRanges.append(Range(m_liveCache.start, m_liveCache.end)); // add live cache
|
|
return knownRanges;
|
|
}
|
|
|
|
|
|
void DDSTimelineModel::rebuildTimelineKnownRanges() // call when adding or removing caches
|
|
{
|
|
QTimer::singleShot(0, this, &DDSTimelineModel::signalRebuildTimelineKnownRanges); // sadly, the model cannot do this itself.. + queue because caches have to be cleaned up first before ranges can be updated with them
|
|
}
|
|
|
|
|
|
void DDSTimelineModel::processEventList(qint64 &highest, bool &incomplete, QVector<LtsTimelineClassic::Event *> &cachedEvents, QVector<LtsTimelineClassic::Event *> &eventList, const int initialCount)
|
|
{
|
|
int count = initialCount;
|
|
|
|
QMutableVectorIterator<LtsTimelineClassic::Event *> i(eventList);
|
|
while (i.hasNext())
|
|
{
|
|
count++; // ++ first, otherwise the following check will not trigger on (limit + 1)
|
|
if (count > m_cacheSize)
|
|
{
|
|
WARNING_LOG(m_logger) << "max cache exceeded - not adding more than " << m_cacheSize << " events";
|
|
incomplete = true;
|
|
emit signalTooMuchEvents();
|
|
break; // delete remaining events from receivedEventsList
|
|
}
|
|
|
|
LtsTimelineClassic::Event *event = i.next();
|
|
i.remove(); // take event!
|
|
|
|
cachedEvents.append(event); // add to caches events list
|
|
|
|
if (event->startTime() + event->duration(true /* ignore live events */) > highest)
|
|
highest = event->startTime() + event->duration(true /* ignore live events */); // the journey to find the lowest low
|
|
}
|
|
|
|
// delete remaining events from receivedEventsList, as if there are events left in the list, the cache was full, and non-added events have be be removed
|
|
qDeleteAll(eventList);
|
|
}
|
|
|
|
|
|
void DDSTimelineModel::receivedEvents(const LtsTimelineClassic::Range &range, QVector<LtsTimelineClassic::Event *> &receivedEventsList /* copy */) // NOTE: does not check if event is already added - must be done while requesting events (performance)
|
|
{
|
|
// space for cached ranges available? if not, make space!
|
|
if (isCacheFull())
|
|
{
|
|
DEBUG_LOG(m_logger) << "receivedEvents: cache full - force uncache";
|
|
forceUncache();
|
|
}
|
|
|
|
#ifdef LTS_DEBUG // count is rather expensive
|
|
DEBUG_LOG(m_logger) << "received events count: " << receivedEventsList.count();
|
|
#endif
|
|
|
|
qint64 highest = 0;
|
|
bool incomplete = false;
|
|
QVector<LtsTimelineClassic::Event *> cachedEvents;
|
|
|
|
processEventList(highest, incomplete, cachedEvents, receivedEventsList);
|
|
addCacheRange(range, cachedEvents, incomplete ? highest : 0);
|
|
|
|
DEBUG_LOG(m_logger) << "receivedEvents: loading done";
|
|
emit signalLoadingDone();
|
|
}
|
|
|
|
|
|
void DDSTimelineModel::setLive(bool isLive)
|
|
{
|
|
if (m_isLive == isLive) // ignore if already live
|
|
return;
|
|
|
|
m_isLive = isLive;
|
|
m_liveCache.visible = isLive;
|
|
|
|
if (isLive)
|
|
{
|
|
m_liveCache.start = m_session->partitionTime(); // immediately set the start time of the live cache, so that armed getEvents do not get events from inside the live time
|
|
m_liveCache.end = m_liveCache.start;
|
|
}
|
|
else // not live
|
|
{
|
|
saveAndClearLiveCache();
|
|
}
|
|
}
|
|
|
|
|
|
// #warning TODO: duplicate! (2)
|
|
void DDSTimelineModel::slotRequestMessageReceived(const QString &message)
|
|
{
|
|
EventRequest *er = qobject_cast<EventRequest *>(sender());
|
|
if (er == nullptr)
|
|
{
|
|
DEBUG_LOG(m_logger) << "not an EventRequest: " << sender();
|
|
return;
|
|
}
|
|
|
|
//qCritical() << "msg" << message;
|
|
|
|
const QJsonDocument msgDoc(QJsonDocument::fromJson(message.toUtf8()));
|
|
const QJsonArray eventArray(msgDoc.array());
|
|
|
|
for (auto i = eventArray.constBegin(); i != eventArray.constEnd(); ++i)
|
|
{
|
|
const QJsonObject &eventObj = (*i).toObject();
|
|
|
|
int id = eventObj[QStringLiteral("id")].toInt(-1);
|
|
int eventTypeId = eventObj[QStringLiteral("eventTypeId")].toInt(-1);
|
|
qint64 startTime = eventObj[QStringLiteral("sampleTime")].toDouble(-1);
|
|
const QString &properties = eventObj[QStringLiteral("properties")].toString(QStringLiteral("{}"));
|
|
int partyId = eventObj[QStringLiteral("partyId")].toInt(-1);
|
|
int participantId = eventObj[QStringLiteral("participantId")].toInt(-1);
|
|
int equipmentNumber = eventObj[QStringLiteral("equipmentNumber")].toInt(-1);
|
|
|
|
QJsonObject positionObj(eventObj[QStringLiteral("position")].toObject());
|
|
const LtsUtils::QPoint3D pos(positionObj[QStringLiteral("x")].toDouble(),
|
|
positionObj[QStringLiteral("y")].toDouble(),
|
|
positionObj[QStringLiteral("z")].toDouble());
|
|
|
|
|
|
QJsonParseError err;
|
|
const QJsonDocument doc(QJsonDocument::fromJson(properties.toUtf8(), &err));
|
|
const QJsonObject root(doc.object());
|
|
|
|
if (err.error != QJsonParseError::NoError)
|
|
{
|
|
ERROR_LOG(m_logger) << "Event #" << id << " with event type id " << eventTypeId << " and start time " << startTime << " json detail data cannot be parsed: " << err.errorString() << " at offset " << err.offset;
|
|
continue;
|
|
}
|
|
|
|
QString partyStr(ParticipantManagement::trPartyName(m_ppmDtc->party(partyId).name()));
|
|
|
|
if (partyStr.isEmpty())
|
|
partyStr = tr("Unknown Party");
|
|
|
|
QStringList path;
|
|
path.append(partyStr);
|
|
|
|
switch (eventTypeId)
|
|
{
|
|
case ProjectDataManagement::EventType::HitBuilding: // 5 - Hit Building
|
|
path = getPathForBuildingEvents(tr("Buildings"), root);
|
|
break;
|
|
case ProjectDataManagement::EventType::CameraVideo: // 8 - Camera Video
|
|
path = getPathForAvsEvents(tr("Camera Video"), root);
|
|
break;
|
|
case ProjectDataManagement::EventType::RadioCnr: // 9 - CNR Radio
|
|
path = getPathForAvsEvents(tr("CNR Radio"), root);
|
|
break;
|
|
case ProjectDataManagement::EventType::RadioTvc: // 10 - TVC Radio
|
|
path = getPathForAvsEvents(tr("TVC Radio"), root);
|
|
break;
|
|
|
|
case ProjectDataManagement::EventType_def::MineArea:
|
|
path = getPathForAesEvents(tr("Mine Area"), root);
|
|
break;
|
|
|
|
case ProjectDataManagement::EventType_def::Borderline:
|
|
path = getPathForAesEvents(tr("Borderline"), root);
|
|
break;
|
|
|
|
case ProjectDataManagement::EventType_def::BCContamination:
|
|
path = getPathForAesEvents(tr("B/C Contamination"), root);
|
|
break;
|
|
|
|
case ProjectDataManagement::EventType_def::NContamination:
|
|
path = getPathForAesEvents(tr("N Contamination"), root);
|
|
break;
|
|
|
|
case ProjectDataManagement::EventType_def::DangerArea:
|
|
path = getPathForAesEvents(tr("Danger Area"), root);
|
|
break;
|
|
|
|
case ProjectDataManagement::EventType_def::FunctionalArea:
|
|
path = getPathForAesEvents(tr("Functional Area"), root);
|
|
break;
|
|
|
|
case ProjectDataManagement::EventType_def::MineCorridor:
|
|
path = getPathForAesEvents(tr("Mine Corridor"), root);
|
|
break;
|
|
|
|
case ProjectDataManagement::EventType_def::TechnicalArea:
|
|
path = getPathForAesEvents(tr("Technical Area"), root);
|
|
break;
|
|
|
|
case ProjectDataManagement::EventType_def::GasPipe:
|
|
path = getPathForAesEvents(tr("Gas Pipe"), root);
|
|
break;
|
|
|
|
default:
|
|
path << m_ppmDtc->hierarchyFromParticipant(participantId, m_ppmDtc->participants()).split(QLatin1Char(','));
|
|
path << QString::number(equipmentNumber);
|
|
break;
|
|
}
|
|
|
|
LtsTimelineClassic::Event::EventPaintFunc paintFunc; // set by setColorPaintFunc
|
|
QColor color; // s.a.a.
|
|
setColorPaintFun(paintFunc, color, ProjectDataManagement::EventType_def::type(eventTypeId));
|
|
|
|
// add end time to event's detail data json
|
|
qint64 endTime = root[QStringLiteral("endTime")].toDouble(); // might be a audio/video event
|
|
|
|
LtsTimelineClassic::Event *event = new LtsTimelineClassic::Event(id, eventTypeId, startTime, endTime, participantId, partyId, equipmentNumber,
|
|
path, paintFunc, color, root, -1, pos, m_session);
|
|
|
|
#ifdef LTS_DEBUG // expensive
|
|
DEBUG_LOG(m_logger) << "Event #" << event->uniqueId() << " with event type id " << event->typeId() << " and start time " << event->startTime() << " created";
|
|
#endif
|
|
|
|
er->append(event);
|
|
}
|
|
}
|
|
|
|
|
|
void DDSTimelineModel::requestEvents(const LtsTimelineClassic::Range &range)
|
|
{
|
|
DEBUG_LOG(m_logger) << "requesting events from " << QDateTime::fromMSecsSinceEpoch(range.start).toString()
|
|
<< " to " << QDateTime::fromMSecsSinceEpoch(range.end).toString();
|
|
|
|
if (m_createFakeEvents)
|
|
DEBUG_LOG(m_logger) << "requesting fake events ..";
|
|
else
|
|
DEBUG_LOG(m_logger) << "requesting real events ..";
|
|
|
|
EventRequest *request = new EventRequest(m_createFakeEvents ? QUrl(QStringLiteral("ws://127.0.0.1:10090")) : m_eemForeign->eemUrl(),
|
|
range.start, range.end, m_activeEventTypeIds, m_cacheSize);
|
|
|
|
connect(request, &EventRequest::signalDone,
|
|
this, [this, range, request]()
|
|
{
|
|
QVector<LtsTimelineClassic::Event *> eventList = request->eventList();
|
|
DEBUG_LOG(m_logger) << ".. received real events! " << range.start << " - " << range.end;
|
|
|
|
receivedEvents(range, eventList);
|
|
request->deleteLater();
|
|
});
|
|
|
|
connect(request, &EventRequest::signalRequestFailed, // calls deleteLater on itself, don't queue here
|
|
this, [this, request](const QString &errorString)
|
|
{
|
|
ERROR_LOG(m_logger) << ".. event request from " << request->start() << " to " << request->end() << " failed! '" << errorString << "'";
|
|
emit signalLoadingDone(); // calls deleteLater on itself
|
|
});
|
|
|
|
connect(request, &EventRequest::signalMessageReceived,
|
|
this, &DDSTimelineModel::slotRequestMessageReceived);
|
|
|
|
emit signalLoading();
|
|
}
|
|
|
|
|
|
bool DDSTimelineModel::isCached(int eventId) const
|
|
{
|
|
for (const auto &it : m_cachedEvents)
|
|
{
|
|
const CacheRange &cr = it.second;
|
|
for (const LtsTimelineClassic::Event *ev :cr.events)
|
|
{
|
|
if (ev->uniqueId() == eventId)
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for (const LtsTimelineClassic::Event *ev : m_liveCache.events)
|
|
{
|
|
if (ev->uniqueId() == eventId)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void DDSTimelineModel::showCached(CacheRange &cacheRange)
|
|
{
|
|
cacheRange.visible = true;
|
|
addCacheRangeToTimeline(cacheRange);
|
|
}
|
|
|
|
|
|
bool DDSTimelineModel::splitLowest(qint64 &rangeMin /* out */, qint64 rangeMax /* never changes */, qint64 &requestMin /* out */, qint64 &requestMax /* out */, const CacheRange &lowestCache /* must be the *lowest* cache *inside* the range */) const
|
|
{
|
|
qint64 oldRangeMin = rangeMin;
|
|
bool spaceBeforeCache = (rangeMin < lowestCache.start);
|
|
|
|
if (spaceBeforeCache) // space before the cache? create request!
|
|
{
|
|
// there is space before the cache, that space must be requested!
|
|
// set range to request events from
|
|
requestMin = rangeMin; // space before a cache is always at rangeMin because otherwise there would not be space before it :^)
|
|
requestMax = lowestCache.start - 1; // end the request right before the cache (because we dont want to get events that are already inside the cache, right)
|
|
// NO! skip the cache!! it is already there.. OLD => rangeMin = lowestCache.start; // set range start (the "iterator" - for the next iteration) right on the cache - cache starts AFTER range start
|
|
DEBUG_LOG(m_logger) << "splitLowest: there is an uncached range before the lowest cache, adding this uncached range to the request " << requestMin << " - " << requestMax << " and set rangeMin now to " << rangeMin << " while rangeMax keeps being " << rangeMax;
|
|
}
|
|
else
|
|
{
|
|
// no space before, nothing to do, do not request, increase rangeMin (itreator, for the next iteration)
|
|
DEBUG_LOG(m_logger) << "splitLowest: no uncached range at the start of the requested range, no need to request events here - skipping";
|
|
}
|
|
|
|
// increase rangeMin to skip this range as it is now processed, either by skipping it (see code above) or because it is going to be processed by the caller function (requestMin & requestMax were set here)
|
|
rangeMin = lowestCache.end + 1; // let the next range check start right after the end of the cache
|
|
|
|
if (rangeMin > rangeMax) // is the cache bigger than the range end?
|
|
{
|
|
DEBUG_LOG(m_logger) << "splitLowest: rangeMin > rangeMax - lowestCache.end " << lowestCache.end;
|
|
rangeMin = rangeMax; // then we are at the end, done (leftover will be 0)
|
|
}
|
|
|
|
DEBUG_LOG(m_logger) << "splitLowest: space? " << spaceBeforeCache << " | requestMin" << requestMin << " - requestMax " << requestMax << " | oldRangeMin " << oldRangeMin << " - rangeMin " << rangeMin;
|
|
|
|
return spaceBeforeCache; // true: indicates that a request should be generated by requestMin & requestMax as there was space before the cache; false: skip request and try for next range / indicate that no request should be generated by this range
|
|
}
|
|
|
|
|
|
bool DDSTimelineModel::isInsideCachedRange(const CacheRange &cr, qint64 start, qint64 end)
|
|
{
|
|
// if either one side of the cache is inside the range, then there is something cached
|
|
return (cr.end >= start && cr.start <= end);
|
|
}
|
|
|
|
|
|
void DDSTimelineModel::setCreateFakeEvents(bool createFakeEvents)
|
|
{
|
|
m_createFakeEvents = createFakeEvents;
|
|
}
|
|
|
|
|
|
void DDSTimelineModel::getAllEventsOfType(const QSet<int> &eventTypeIds)
|
|
{
|
|
//for (CacheRange &cr : m_cachedEvents)
|
|
for (auto i = m_cachedEvents.begin(); i != m_cachedEvents.end(); ++i)
|
|
{
|
|
CacheRange &cr = i->second;
|
|
qint64 crId = i->first;
|
|
EventRequest *request = new EventRequest(m_createFakeEvents ? QUrl(QStringLiteral("ws://127.0.0.1:10090")) : m_eemForeign->eemUrl(), cr.start, cr.end, eventTypeIds, m_cacheSize);
|
|
|
|
connect(request, &EventRequest::signalDone,
|
|
this, [this, crId, request]()
|
|
{
|
|
auto icr = std::find_if(m_cachedEvents.begin(), m_cachedEvents.end(), [crId](const QPair<qint64, CacheRange> &pair)
|
|
{
|
|
return (pair.first == crId);
|
|
});
|
|
|
|
if (icr == m_cachedEvents.end())
|
|
{
|
|
WARNING_LOG(m_logger) << "cr after request for crId " << crId << " not available anymore";
|
|
return;
|
|
}
|
|
|
|
CacheRange &cr = icr->second;
|
|
|
|
QVector<LtsTimelineClassic::Event *> eventList = request->eventList(); // copy
|
|
DEBUG_LOG(m_logger) << "getAllEventsOfType: .. received real events! " << cr.start << " - " << cr.end;
|
|
|
|
|
|
// space for cached ranges available? if not, make space!
|
|
if (isCacheFull())
|
|
{
|
|
DEBUG_LOG(m_logger) << "getAllEventsOfType: receivedEvents: cache full - not adding new events";
|
|
cr.incomplete = true;
|
|
return;
|
|
}
|
|
|
|
|
|
#ifdef LTS_DEBUG // count is rather expensive
|
|
DEBUG_LOG(m_logger) << "getAllEventsOfType: received events count: " << eventList.count();
|
|
#endif
|
|
|
|
qint64 highest = 0;
|
|
bool incomplete = false;
|
|
QVector<LtsTimelineClassic::Event *> cachedEvents;
|
|
|
|
processEventList(highest, incomplete, cachedEvents, eventList, cr.events.count());
|
|
|
|
cr.incomplete = incomplete;
|
|
|
|
|
|
// delete new events that already exist in CacheRange (if an event type was re-added after it was removed in legend, events will be fetched again and conflict with existing ones)
|
|
// TODO: don't even request events that are already in cache - saves bandwidth & cpu (but takes time to dev 'n' test)
|
|
QMutableVectorIterator<LtsTimelineClassic::Event *> i(cachedEvents);
|
|
while (i.hasNext())
|
|
{
|
|
LtsTimelineClassic::Event *eNew = i.next();
|
|
|
|
for (LtsTimelineClassic::Event *eOld : qAsConst(cr.events))
|
|
{
|
|
if (eNew->uniqueId() == eOld->uniqueId())
|
|
{
|
|
delete eNew;
|
|
i.remove();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
cr.events += cachedEvents;
|
|
//std::sort(cr.events.begin(), cr.events.end(), [](const CacheRange &cr1, const CacheRange &cr2) { return cr1.start < cr2.start; }); // sort the list, by the lowest start time
|
|
|
|
addEventsAndFilter(cachedEvents);
|
|
rebuildTimelineKnownRanges();
|
|
//#warning TODO: tell timeline only when *all* requests are done to save big cpu time
|
|
|
|
DEBUG_LOG(m_logger) << "getAllEventsOfType: receivedEvents: loading done";
|
|
request->deleteLater();
|
|
});
|
|
|
|
connect(request, &EventRequest::signalRequestFailed, // calls deleteLater on itself, don't queue here
|
|
this, [this, request](const QString &errorString)
|
|
{
|
|
ERROR_LOG(m_logger) << "getAllEventsOfType: event request from " << request->start() << " to " << request->end() << " failed! '" << errorString << "'";
|
|
emit signalLoadingDone();
|
|
});
|
|
|
|
connect(request, &EventRequest::signalMessageReceived,
|
|
this, &DDSTimelineModel::slotRequestMessageReceived);
|
|
}
|
|
}
|
|
|
|
|
|
void DDSTimelineModel::getEvents(qint64 rangeMin, qint64 rangeMax)
|
|
{
|
|
DEBUG_LOG(m_logger) << "getEvents: requesting events from " << QDateTime::fromMSecsSinceEpoch(rangeMin).toString() << rangeMin << " to " << QDateTime::fromMSecsSinceEpoch(rangeMax).toString() << rangeMax;
|
|
|
|
if (!hasActiveEventTypes())
|
|
{
|
|
DEBUG_LOG(m_logger) << "no active event types selected";
|
|
return;
|
|
}
|
|
|
|
if (m_liveCache.start != 0 && rangeMax > m_liveCache.start) // if there is a live cache, limit request to the start of the live cache
|
|
{
|
|
DEBUG_LOG(m_logger) << "live cache active & in range - limited range max from " << rangeMax << " to " << m_liveCache.start - 1;
|
|
rangeMax = m_liveCache.start - 1;
|
|
}
|
|
|
|
|
|
QVector<Range> requestRanges;
|
|
for (auto &it : m_cachedEvents) // starts with the lowest cr
|
|
{
|
|
CacheRange &cr = it.second;
|
|
DEBUG_LOG(m_logger) << "> start " << cr.start << " end " << cr.end;
|
|
|
|
bool inside = isInsideCachedRange(cr, rangeMin, rangeMax);
|
|
if (!inside) // there are events to reuse because the cache is inside the range
|
|
continue;
|
|
|
|
DEBUG_LOG(m_logger) << "found range that was cached, using it instead of requesting new events";
|
|
|
|
if (!cr.visible) // if cache is NOT visible but is inside the current range, make cache visible
|
|
{
|
|
DEBUG_LOG(m_logger) << "found range that was cached AND not visible, showing it again";
|
|
showCached(cr);
|
|
}
|
|
|
|
qint64 requestMin = 0;
|
|
qint64 requestMax = 0;
|
|
|
|
if (!splitLowest(rangeMin, rangeMax, requestMin, requestMax, cr)) // NOTE: alters rangeMin
|
|
continue;
|
|
requestRanges.append(Range(requestMin, requestMax));
|
|
|
|
// No...
|
|
// else if (cr.visible && !inside) // is cache is visible but is not outside of the range, uncache / make invisible
|
|
// {
|
|
// qWarning() << "uncaching that was visible but now out-of-range";
|
|
// uncache(cr);
|
|
// }
|
|
}
|
|
|
|
|
|
// check live cache for events..
|
|
if (rangeMin < rangeMax && m_liveCache.start != 0) // in the end rangeMin must equal rangeMax as we iterated over freee space and over caches and should have come to the end - if not at the end then there is some space left, request that also
|
|
{
|
|
if (isInsideCachedRange(m_liveCache, rangeMin, rangeMax))
|
|
{
|
|
qint64 requestMin = 0;
|
|
qint64 requestMax = 0;
|
|
|
|
if (splitLowest(rangeMin, rangeMax, requestMin, requestMax, m_liveCache))
|
|
requestRanges.append(Range(requestMin, requestMax));
|
|
}
|
|
}
|
|
|
|
if (rangeMin < rangeMax) // in the end rangeMin must equal rangeMax as we iterated over freee space and over caches and should have come to the end - if not at the end then there is some space left, request that also
|
|
{
|
|
DEBUG_LOG(m_logger) << "adding request for leftovers from " << rangeMin << " - " << rangeMax;
|
|
requestRanges.append(Range(rangeMin, rangeMax));
|
|
}
|
|
|
|
int requestRangesCount = requestRanges.count();
|
|
DEBUG_LOG(m_logger) << "requestRanges: " << requestRangesCount;
|
|
|
|
for (const auto &range : qAsConst(requestRanges))
|
|
requestEvents(range);
|
|
|
|
if (requestRangesCount < 1)
|
|
DEBUG_LOG(m_logger) << "nothing requested (requestRangesCount empty)";
|
|
}
|
|
|
|
|
|
void DDSTimelineModel::setColorPaintFun(LtsTimelineClassic::Event::EventPaintFunc &paintFunc, QColor &color,
|
|
ProjectDataManagement::EventType eventType) const
|
|
{
|
|
paintFunc = nullptr;
|
|
color = QColor();
|
|
switch (eventType.underlying())
|
|
{
|
|
case ProjectDataManagement::EventType::BiteMessage:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicRect;
|
|
color = QColor("red");
|
|
return;
|
|
case ProjectDataManagement::EventType::CameraVideo:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicRect;
|
|
color = QColor("yellow");
|
|
return;
|
|
case ProjectDataManagement::EventType::CrossGlobalBorderline:
|
|
case ProjectDataManagement::EventType::CrossLocalBorderline:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicRect;
|
|
color = QColor("orange");
|
|
return;
|
|
case ProjectDataManagement::EventType::EnterDangerArea:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicRoundedRect;
|
|
color = QColor("blue");
|
|
return;
|
|
case ProjectDataManagement::EventType::LeaveDangerArea:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicRoundedRect;
|
|
color = QColor("red");
|
|
return;
|
|
case ProjectDataManagement::EventType::EnterFunctionalArea:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicRoundedRect;
|
|
color = QColor("green");
|
|
return;
|
|
case ProjectDataManagement::EventType::LeaveFunctionalArea:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicRoundedRect;
|
|
color = QColor("yellow");
|
|
return;
|
|
case ProjectDataManagement::EventType::EnterMineArea:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicRoundedRect;
|
|
color = QColor("orange");
|
|
return;
|
|
case ProjectDataManagement::EventType::LeaveMineArea:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicRoundedRect;
|
|
color = QColor("brown");
|
|
return;
|
|
case ProjectDataManagement::EventType::FireEvent:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicEllipse;
|
|
color = QColor("blue");
|
|
return;
|
|
case ProjectDataManagement::EventType::Hit:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicEllipse;
|
|
color = QColor("green");
|
|
return;
|
|
case ProjectDataManagement::EventType::Sanitation:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicEllipse;
|
|
color = QColor("magenta");
|
|
return;
|
|
case ProjectDataManagement::EventType::HitBuilding:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicEllipse;
|
|
color = QColor("cyan");
|
|
return;
|
|
case ProjectDataManagement::EventType::HitEffectSimulation:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicEllipse;
|
|
color = QColor("orange");
|
|
return;
|
|
case ProjectDataManagement::EventType::Manipulation:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicTriangle;
|
|
color = QColor("blue");
|
|
return;
|
|
case ProjectDataManagement::EventType::RadioCnr:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicTriangle;
|
|
color = QColor("red");
|
|
return;
|
|
case ProjectDataManagement::EventType::RadioTvc:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicTriangle;
|
|
color = QColor("green");
|
|
return;
|
|
case ProjectDataManagement::EventType::WeaponAssignment:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicTriangle;
|
|
color = QColor("orange");
|
|
return;
|
|
case ProjectDataManagement::EventType::MineArea:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicInvertTriangle;
|
|
color = QColor("green");
|
|
return;
|
|
case ProjectDataManagement::EventType::NContamination:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicInvertTriangle;
|
|
color = QColor("pink");
|
|
return;
|
|
case ProjectDataManagement::EventType::EnterNuclear:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicRoundedRect;
|
|
color = QColor("pink");
|
|
return;
|
|
case ProjectDataManagement::EventType::LeaveNuclear:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicRoundedRect;
|
|
color = QColor("pink").darker();
|
|
return;
|
|
case ProjectDataManagement::EventType::BCContamination:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicInvertTriangle;
|
|
color = QColor("blue");
|
|
return;
|
|
case ProjectDataManagement::EventType::DangerArea:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicInvertTriangle;
|
|
color = QColor("yellow");
|
|
return;
|
|
case ProjectDataManagement::EventType::FunctionalArea:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicInvertTriangle;
|
|
color = QColor("red");
|
|
return;
|
|
case ProjectDataManagement::EventType::MineCorridor:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicInvertTriangle;
|
|
color = QColor("brown");
|
|
return;
|
|
case ProjectDataManagement::EventType::Borderline:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicInvertTriangle;
|
|
color = QColor("orange");
|
|
return;
|
|
case ProjectDataManagement::EventType::TechnicalArea:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicInvertTriangle;
|
|
color = QColor("black");
|
|
return;
|
|
case ProjectDataManagement::EventType::GasPipe:
|
|
paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicRect;
|
|
color = QColor("cyan");
|
|
return;
|
|
case ProjectDataManagement::EventType::Unknown:
|
|
// don't know why HitFoo is separate - all covered by HitEffectSimulation...
|
|
case ProjectDataManagement::EventType::HitN:
|
|
case ProjectDataManagement::EventType::HitB:
|
|
case ProjectDataManagement::EventType::HitC:
|
|
case ProjectDataManagement::EventType::HitGasPipe:
|
|
case ProjectDataManagement::EventType::HitMine:
|
|
case ProjectDataManagement::EventType::FriendlyFire:
|
|
case ProjectDataManagement::EventType::EquipmentErrorReport:
|
|
case ProjectDataManagement::EventType::EmergencyCall:
|
|
return;
|
|
}
|
|
Q_ASSERT(false);
|
|
}
|
|
|
|
|
|
QJsonObject DDSTimelineModel::createMarkerDetailData(const MarkerManagement::Marker &data) const
|
|
{
|
|
QJsonObject root;
|
|
root[QStringLiteral("MarkerName")] = QString::fromStdString(data.name());
|
|
root[QStringLiteral("MarkerNote")] = QString::fromStdString(data.note());
|
|
root[QStringLiteral("Operator")] = data.operatorId();
|
|
root[QStringLiteral("MarkerOffset")] = data.timeStampOffset();
|
|
root[QStringLiteral("MapSettings")] = QString::fromStdString(data.mapSettings());
|
|
root[QStringLiteral("Filter")] = QString::fromStdString(data.filter());
|
|
|
|
bool isSit = false;
|
|
bool isGfx = false;
|
|
bool isSfx = false;
|
|
|
|
for (const MarkerManagement::MarkerDescription &md : data.descriptions())
|
|
{
|
|
switch (md.markerType().underlying())
|
|
{
|
|
case MarkerManagement::MarkerType::Situation:
|
|
{
|
|
isSit = true;
|
|
break;
|
|
}
|
|
case MarkerManagement::MarkerType::Video:
|
|
{
|
|
isGfx = true;
|
|
break;
|
|
}
|
|
case MarkerManagement::MarkerType::Audio:
|
|
{
|
|
isSfx = true;
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
WARNING_LOG(m_logger) << "Marker " << data.id() << "'s description has an invalid marker type: " << (int)md.markerType().underlying();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
root[QStringLiteral("IsSituationType")] = isSit;
|
|
root[QStringLiteral("IsVideoType")] = isGfx;
|
|
root[QStringLiteral("IsAudioType")] = isSfx;
|
|
|
|
return root;
|
|
}
|
|
|
|
|
|
LtsTimelineClassic::Event *DDSTimelineModel::getMarker(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 DDSTimelineModel::modifyMarker(const MarkerManagement::Marker &data)
|
|
{
|
|
LtsTimelineClassic::Event *event = getMarker(data.id());
|
|
|
|
if (event == nullptr)
|
|
return;
|
|
|
|
// NOTE: there are things that can't be updated !
|
|
|
|
//#warning TODO: can start, etc. change?
|
|
event->setDetailData(createMarkerDetailData(data));
|
|
|
|
// NOTE: marker have
|
|
event->setPosition(LtsUtils::QPoint3D(data.position().lon(),
|
|
data.position().lat(),
|
|
data.position().z()));
|
|
|
|
//#warning TODO: needed? event position does not change..
|
|
//emit dataChanged(QModelIndex(), QModelIndex());
|
|
}
|
|
|
|
|
|
void DDSTimelineModel::addMarkerManagementMarker(const MarkerManagement::Marker &data)
|
|
{
|
|
// as 'init' already adds markers and in some cases dds receives markers later, again, don't let dds add markers that already exist:
|
|
if (m_knownMarkerIds.contains(data.id()))
|
|
return; // don't add already added marker
|
|
m_knownMarkerIds += data.id();
|
|
|
|
//#warning TODO: also check markers, etc., etc.
|
|
QStringList path;
|
|
path.append(tr("Markers")); // FIXME: tr'd keys are dangerous ..
|
|
|
|
LtsTimelineClassic::Event::EventPaintFunc paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicTriangle;
|
|
QColor color = QColor(Qt::red);
|
|
|
|
const LtsUtils::QPoint3D pos(data.position().lon(),
|
|
data.position().lat(),
|
|
data.position().z());
|
|
|
|
addEventAndFilter(new LtsTimelineClassic::Event(data.id(), //uniqueId
|
|
markerTypeId, // HACK: there is no marker enum type in eem
|
|
//data.markerTypeId(),
|
|
data.timeStamp(), // start time
|
|
0, // end time. TODO: get duration from properties ?
|
|
-1, // player Id
|
|
-1, // team Id
|
|
-1, // equipment Id
|
|
path, // tree path
|
|
paintFunc, // paint function
|
|
color, // color
|
|
createMarkerDetailData(data), // json detail data
|
|
-1, // sub event id
|
|
pos,
|
|
m_session));
|
|
|
|
#ifdef LTS_DEBUG // expensive
|
|
DEBUG_LOG(m_logger) << "marker added! uniqueId " << data.id() << ", startTime " << data.timeStamp();
|
|
#endif
|
|
}
|
|
|
|
|
|
//const QVector<LtsTimelineClassic::Event *> DDSTimelineModel::createEvents(const QVector<ExerciseEventManagement::Event> &eventList) const
|
|
//{
|
|
// QVector<LtsTimelineClassic::Event *> ltsEvents;
|
|
|
|
// for (auto i = eventList.constBegin(); i != eventList.constEnd(); ++i)
|
|
// {
|
|
// const ExerciseEventManagement::Event &eemEvent = *i;
|
|
// LtsTimelineClassic::Event *ltsEvent = createEvent(eemEvent);
|
|
|
|
// if (ltsEvent != nullptr)
|
|
// ltsEvents.append(ltsEvent);
|
|
// }
|
|
|
|
// return ltsEvents;
|
|
//}
|
|
|
|
|
|
const QJsonObject DDSTimelineModel::getDetailData(const ExerciseEventManagement::Event &event) const
|
|
{
|
|
return getDetailData(QByteArray::fromRawData(event.properties().c_str(), event.properties().size()), event.id(), event.eventTypeId(), event.sampleTime());
|
|
}
|
|
|
|
|
|
const QJsonObject DDSTimelineModel::getDetailData(const QByteArray &data, int id, const ProjectDataManagement::EventType& type, qint64 startTime) const
|
|
{
|
|
QJsonParseError err;
|
|
QJsonDocument doc(QJsonDocument::fromJson(data, &err));
|
|
if (err.error != QJsonParseError::NoError)
|
|
{
|
|
ERROR_LOG(m_logger) << "getDetailData: Event #" << id << " with event type id " << type << " and start time " << startTime << " json detail data cannot be parsed: " << err.errorString() << " at offset " << err.offset;
|
|
return QJsonObject();
|
|
}
|
|
|
|
return doc.object();
|
|
}
|
|
|
|
|
|
//#warning TODO: duplicate! (1)
|
|
LtsTimelineClassic::Event *DDSTimelineModel::createEvent(const ExerciseEventManagement::Event &data) const
|
|
{
|
|
// if (data.sampleTime() < m_lowestPositionedItem || data.sampleTime() > m_highestPositionedItem)
|
|
// {
|
|
// ERROR_LOG(m_logger) << "Event #" << data.id() << " with event type id " << data.eventTypeId() << " and start time " << data.sampleTime() << " is out of range (low: " << m_lowestPositionedItem << ", high: " << m_highestPositionedItem << ")";
|
|
// return nullptr;
|
|
// }
|
|
|
|
QJsonObject root(getDetailData(data));
|
|
|
|
QString partyStr(ParticipantManagement::trPartyName(m_ppmDtc->party(data.partyId()).name()));
|
|
|
|
if (partyStr.isEmpty())
|
|
partyStr = tr("Unknown Party");
|
|
|
|
QStringList path;
|
|
path.append(partyStr);
|
|
|
|
switch (data.eventTypeId().underlying())
|
|
{
|
|
case ProjectDataManagement::EventType::HitBuilding: // 5 - Hit Building
|
|
path = getPathForBuildingEvents(tr("Buildings"), root);
|
|
break;
|
|
case ProjectDataManagement::EventType::CameraVideo: // 8 - Camera Video
|
|
path = getPathForAvsEvents(tr("Camera Video"), root);
|
|
break;
|
|
case ProjectDataManagement::EventType::RadioCnr: // 9 - CNR Radio
|
|
path = getPathForAvsEvents(tr("CNR Radio"), root);
|
|
break;
|
|
case ProjectDataManagement::EventType::RadioTvc: // 10 - TVC Radio
|
|
path = getPathForAvsEvents(tr("TVC Radio"), root);
|
|
break;
|
|
case ProjectDataManagement::EventType_def::MineArea:
|
|
path = getPathForAesEvents(tr("Mine Area"), root);
|
|
break;
|
|
case ProjectDataManagement::EventType_def::Borderline:
|
|
path = getPathForAesEvents(tr("Borderline"), root);
|
|
break;
|
|
case ProjectDataManagement::EventType_def::BCContamination:
|
|
path = getPathForAesEvents(tr("B/C Contamination"), root);
|
|
break;
|
|
case ProjectDataManagement::EventType_def::NContamination:
|
|
path = getPathForAesEvents(tr("N Contamination"), root);
|
|
break;
|
|
case ProjectDataManagement::EventType_def::DangerArea:
|
|
path = getPathForAesEvents(tr("Danger Area"), root);
|
|
break;
|
|
case ProjectDataManagement::EventType_def::FunctionalArea:
|
|
path = getPathForAesEvents(tr("Functional Area"), root);
|
|
break;
|
|
case ProjectDataManagement::EventType_def::MineCorridor:
|
|
path = getPathForAesEvents(tr("Mine Corridor"), root);
|
|
break;
|
|
case ProjectDataManagement::EventType_def::TechnicalArea:
|
|
path = getPathForAesEvents(tr("Technical Area"), root);
|
|
break;
|
|
case ProjectDataManagement::EventType_def::GasPipe:
|
|
path = getPathForAesEvents(tr("Gas Pipe"), root);
|
|
break;
|
|
|
|
default:
|
|
path << m_ppmDtc->hierarchyFromParticipant(data.participantId()).split(QLatin1Char(','));
|
|
path << QString::number(data.equipmentNumber());
|
|
break;
|
|
}
|
|
|
|
LtsTimelineClassic::Event::EventPaintFunc paintFunc; // set by setColorPaintFunc
|
|
QColor color; // s.a.a.
|
|
setColorPaintFun(paintFunc, color,data.eventTypeId());
|
|
|
|
// add end time to event's detail data json
|
|
qint64 endTime = root[QStringLiteral("endTime")].toDouble(); // might be a audio/video event
|
|
|
|
const LtsUtils::QPoint3D pos(data.position().lon(),
|
|
data.position().lat(),
|
|
data.position().z());
|
|
LtsTimelineClassic::Event *event = new LtsTimelineClassic::Event(data.id(), data.eventTypeId().underlying(), data.sampleTime(), endTime,
|
|
data.participantId(), data.partyId(), data.equipmentNumber(), path, paintFunc, color,
|
|
root, -1,
|
|
pos,
|
|
m_session);
|
|
|
|
// if (!checkDetailData(event))
|
|
// {
|
|
// ERROR_LOG(m_logger) << "Event #" << event->uniqueId() << " with event type id " << event->typeId() << " and start time " << event->startTime() << " has invalid detail data and cannot be added!";
|
|
// delete event;
|
|
// return nullptr;
|
|
// }
|
|
|
|
#ifdef LTS_DEBUG // expensive
|
|
DEBUG_LOG(m_logger) << "Event #" << event->uniqueId() << " with event type id " << event->typeId() << " and start time " << event->startTime() << " created";
|
|
#endif
|
|
|
|
return event;
|
|
}
|
|
|
|
|
|
//bool DDSTimelineModel::checkDetailData(const LtsTimelineClassic::Event *event) const
|
|
//{
|
|
// // after creating the event, check if DetailData can get the detail data of the event, otherwise filtering does not work and the developers will never find out!
|
|
// const DetailData::GetDetailDataResultList resultList = m_detailData->getDetailData(event);
|
|
|
|
// bool ok = true;
|
|
// for (const DetailData::GetDetailDataForAttributeResult &result : resultList)
|
|
// {
|
|
// if (result.requestResult != DetailData::RequestResult::Okay)
|
|
// {
|
|
// if (result.requestResult == DetailData::RequestResult::InvalidAttributeType)
|
|
// {
|
|
// DEBUG_LOG(m_logger) << "new event "
|
|
// << event->uniqueId() << " with type id " << event->typeId() << " has unknown attribute type "
|
|
// << result.attributeType << " / " << result.attributeTypeId;
|
|
// }
|
|
// else if (result.requestResult == DetailData::RequestResult::NoValueTranslationForAttribute)
|
|
// {
|
|
// DEBUG_LOG(m_logger) << "new event "
|
|
// << event->uniqueId() << " with type id " << event->typeId() << " has no translation for attribute type "
|
|
// << result.attributeType << " / " << result.attributeTypeId;
|
|
// }
|
|
// else
|
|
// {
|
|
// ERROR_LOG(m_logger) << "new event "
|
|
// << event->uniqueId() << " with type id " << event->typeId() << " has invalid detail data for attribute type "
|
|
// << result.attributeType << " / " << result.attributeTypeId << " with result "
|
|
// << (int)result.requestResult;// << " - event not added!";
|
|
// ok = false;
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// return ok;
|
|
//}
|
|
|
|
|
|
/*LtsTimelineClassic::Event *DDSTimelineModel::addExerciseEventManagementEvent(const ExerciseEventManagement::Event &data)
|
|
{
|
|
|
|
}*/
|
|
|
|
|
|
QStringList DDSTimelineModel::getPathForAvsEvents(const QString &type, const QJsonObject &obj) const
|
|
{
|
|
QStringList result;
|
|
|
|
int buildingId = obj[QStringLiteral("MainStructureElementId")].toInt();
|
|
DEBUG_LOG(m_logger) << "Building Id = " << buildingId;
|
|
if (buildingId > 0)
|
|
{
|
|
const QString buildingName = m_ismDtc->getBuildingDisplayName(buildingId);
|
|
result << tr("Buildings") << buildingName;
|
|
}
|
|
|
|
QString streamName = obj[QStringLiteral("streamName")].toString();
|
|
QString displayName = streamName;
|
|
|
|
if (m_streamNameMapping != nullptr)
|
|
displayName = m_streamNameMapping->displayName(streamName);
|
|
|
|
result << type << displayName;
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
QStringList DDSTimelineModel::getPathForBuildingEvents(const QString &type, const QJsonObject &obj) const
|
|
{
|
|
QStringList result;
|
|
result << type;
|
|
|
|
QString buildingName;
|
|
|
|
int buildingId = obj[QStringLiteral("MainStructureElementId")].toInt();
|
|
DEBUG_LOG(m_logger) << "Building Id = " << buildingId;
|
|
if (buildingId > 0)
|
|
{
|
|
buildingName = m_ismDtc->getBuildingDisplayName(buildingId);
|
|
result << buildingName;
|
|
}
|
|
|
|
result << tr("Hits");
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
QStringList DDSTimelineModel::getPathForAesEvents(const QString &type, const QJsonObject &obj) const
|
|
{
|
|
QStringList result;
|
|
result << type;
|
|
|
|
QString aesName = obj[QStringLiteral("AesName")].toString();
|
|
DEBUG_LOG(m_logger) << "AesName = " << aesName;
|
|
result << aesName;
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
void DDSTimelineModel::removeEvent(int eventId)
|
|
{
|
|
const LtsTimelineClassic::ItemList il = getRootItem()->getFullItemList(true);
|
|
|
|
for (LtsTimelineClassic::Item *item : il)
|
|
{
|
|
if (item->removeEvent(eventId))
|
|
{
|
|
//m_alreadyAddedEventIds.removeAll(eventId);
|
|
return;
|
|
}
|
|
}
|
|
|
|
WARNING_LOG(m_logger) << "removeEvent: event "<<eventId<<" not found !!";// - d_tor in emergency mode";
|
|
//m_dtorEmergencyMode = true;
|
|
}
|
|
|
|
|
|
const CacheRange *DDSTimelineModel::getCache(int eventId) const
|
|
{
|
|
for (const auto &it : m_cachedEvents)
|
|
{
|
|
const CacheRange &cr = it.second;
|
|
for (const LtsTimelineClassic::Event *event : cr.events)
|
|
{
|
|
if (event->uniqueId() == eventId)
|
|
return &it.second;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
void DDSTimelineModel::addEventsAndFilter(const QVector<LtsTimelineClassic::Event *> &eventList)
|
|
{
|
|
for (LtsTimelineClassic::Event *e : eventList)
|
|
addEventAndFilter(e);
|
|
}
|
|
|
|
|
|
void DDSTimelineModel::addEventAndFilter(LtsTimelineClassic::Event *event)
|
|
{
|
|
if (!m_filter.isEmpty()) // don't try to filter if there are when filters
|
|
setFilter(m_detailData, LtsTimelineClassic::EventList() << event, m_filter, nullptr, false, false /* do not show error windows here */);
|
|
|
|
addEvent(event); // => AbstractTimelineModel
|
|
|
|
//#warning TODO: only affected indexes
|
|
emit dataChanged(QModelIndex(), QModelIndex());
|
|
}
|
|
|
|
|
|
void DDSTimelineModel::deleteMarker(int uniqueId)
|
|
{
|
|
m_knownMarkerIds.removeAll(uniqueId);
|
|
|
|
LtsTimelineClassic::Event *event = getMarker(uniqueId);
|
|
|
|
if (event == nullptr)
|
|
return;
|
|
|
|
event->setDeleted();
|
|
|
|
//#warning TODO: only affected indexes
|
|
emit dataChanged(QModelIndex(), QModelIndex()); // intensive..
|
|
}
|
|
|
|
|
|
//void DDSTimelineModel::addExerciseEventManagementEvent(const ExerciseEventManagement::Event &data)
|
|
//{
|
|
// if (isCached(data.id()))
|
|
// {
|
|
// qWarning() << "not adding already cached event" << data.id();
|
|
// return;
|
|
// }
|
|
|
|
// LtsTimelineClassic::Event *event = createEvent(data);
|
|
|
|
// if (event == nullptr)
|
|
// {
|
|
// ERROR_LOG(m_logger) << "cannot add eem event";
|
|
// return;
|
|
// }
|
|
|
|
// addEventAndFilter(event);
|
|
//}
|
|
|
|
|
|
//void DDSTimelineModel::slotEventChanged(const ExerciseEventManagement::Event &data, DataModification modType)
|
|
//{
|
|
// TRACE_LOG(m_logger) << "slotEventChanged: " << data;
|
|
// switch (modType)
|
|
// {
|
|
// case DataModification::eModified:
|
|
// {
|
|
// LtsTimelineClassic::Event *event = getEvent(data.id() /* ABIGIOUS ??! */);
|
|
|
|
// if (event == nullptr)
|
|
// {
|
|
// WARNING_LOG(m_logger) << "event #" << data.id() << " has changed, but could not be found in the timeline to update!";
|
|
// break;
|
|
// }
|
|
|
|
// // NOTE: there are things that can't be updated !
|
|
|
|
// event->setPlayerId(data.participantId());
|
|
// event->setTeamId(data.partyId());
|
|
// event->setEquipmentId(data.equipmentNumber());
|
|
// const QJsonDocument doc(QJsonDocument::fromJson(QByteArray(data.properties().c_str(), data.properties().size())));
|
|
// event->setDetailData(doc.object());
|
|
|
|
// const Core::GeoPosition &geoPos = data.position();
|
|
// event->setPosition(geoPos.z(), geoPos.lat(), geoPos.lon());
|
|
|
|
// emit dataChanged(QModelIndex(), QModelIndex());
|
|
// break;
|
|
// }
|
|
// case DataModification::eAdded:
|
|
// {
|
|
// addExerciseEventManagementEvent(data);
|
|
// break;
|
|
// }
|
|
// case DataModification::eRemoved: // IGNORE ! Just for DDS bus cleanup
|
|
// {
|
|
// //deleteEvent(data.id());
|
|
// break;
|
|
// }
|
|
// }
|
|
//}
|
|
|
|
|
|
void DDSTimelineModel::slotMarkerChanged(const MarkerManagement::Marker &data, DataModification modType)
|
|
{
|
|
switch (modType)
|
|
{
|
|
case DataModification::eAdded:
|
|
{
|
|
addMarkerManagementMarker(data);
|
|
break;
|
|
}
|
|
case DataModification::eRemoved:
|
|
{
|
|
deleteMarker(data.id());
|
|
break;
|
|
}
|
|
case DataModification::eModified:
|
|
{
|
|
modifyMarker(data);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// for testing
|
|
static int testId = 0;
|
|
QVector<LtsTimelineClassic::Event *> DDSTimelineModel::createTestEvents(qint64 time, int count, qint64 distance, qint64 duration)
|
|
{
|
|
QVector<LtsTimelineClassic::Event *> eventList;
|
|
eventList.reserve(count);
|
|
LtsTimelineClassic::Event::EventPaintFunc paintFunc = LtsTimelineClassic::Event::drawCustomEventGraphicRect;
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
testId++;
|
|
|
|
QJsonObject detail;
|
|
detail[QStringLiteral("VulnerabilityState")] = testId;
|
|
|
|
qint64 pos = time + (i * (distance / count));
|
|
|
|
LtsTimelineClassic::Event *event =
|
|
new LtsTimelineClassic::Event(
|
|
testId,
|
|
1, // Hit
|
|
pos,
|
|
pos + duration,
|
|
0,
|
|
2, // party RED
|
|
0,
|
|
QStringList() << QStringLiteral("Test"),
|
|
paintFunc,
|
|
QColor(Qt::red),
|
|
detail,
|
|
-1,
|
|
LtsUtils::QPoint3D(),
|
|
m_session);
|
|
|
|
eventList.append(event);
|
|
}
|
|
|
|
return eventList;
|
|
}
|
|
|
|
|
|
|
|
// EventRequest
|
|
|
|
EventRequest::EventRequest(const QUrl &url, qint64 start, qint64 end, const QSet<int> &eventTypes, int limit)
|
|
: m_start(start)
|
|
, m_end(end)
|
|
, m_timeoutTimer(new QTimer(this))
|
|
{
|
|
QUrl realUrl(url);
|
|
realUrl.setPath(QStringLiteral("/request"));
|
|
|
|
QUrlQuery query;
|
|
query.addQueryItem(QStringLiteral("from"), QString::number(m_start));
|
|
query.addQueryItem(QStringLiteral("to"), QString::number(m_end));
|
|
query.addQueryItem(QStringLiteral("requestId"), QStringLiteral("1")); // unused, but cannot be 0
|
|
query.addQueryItem(QStringLiteral("limit"), QString::number(limit + 1 /* + 1 to trigger the maxCacheSize check */)); // prevent a big mess
|
|
|
|
if (!eventTypes.isEmpty())
|
|
{
|
|
QStringList sl;
|
|
sl.reserve(eventTypes.size());
|
|
for (int id : eventTypes)
|
|
sl += QString::number(id);
|
|
query.addQueryItem(QStringLiteral("eventTypeId"), sl.join(QLatin1Char(',')));
|
|
}
|
|
|
|
realUrl.setQuery(query);
|
|
|
|
//qWarning() << "requesting data from EEM: " << realUrl.toString();
|
|
|
|
QWebSocket *ws = new QWebSocket(QString(), QWebSocketProtocol::VersionLatest, this);
|
|
|
|
connect(ws, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error),
|
|
this, &EventRequest::slotRequestFailed);
|
|
connect(ws, &QWebSocket::textMessageReceived,
|
|
this, &EventRequest::slotMessageReceived);
|
|
|
|
ws->open(realUrl);
|
|
|
|
|
|
m_timeoutTimer->setInterval(5000);
|
|
m_timeoutTimer->setSingleShot(true);
|
|
|
|
connect(m_timeoutTimer, &QTimer::timeout,
|
|
this, [this]()
|
|
{
|
|
requestFailed(QStringLiteral("timeout"));
|
|
});
|
|
|
|
m_timeoutTimer->start();
|
|
}
|
|
|
|
|
|
void EventRequest::slotMessageReceived(const QString &message)
|
|
{
|
|
if (message.isEmpty() || message == QStringLiteral("{}"))
|
|
{
|
|
m_timeoutTimer->stop();
|
|
emit signalDone();
|
|
return;
|
|
}
|
|
|
|
m_timeoutTimer->start(); // restart timer, give the server a chance to send more, restart because the message is not complete at this point
|
|
emit signalMessageReceived(message);
|
|
}
|
|
|
|
|
|
void EventRequest::requestFailed(const QString &errorString)
|
|
{
|
|
m_timeoutTimer->stop();
|
|
emit signalRequestFailed(errorString);
|
|
deleteLater();
|
|
}
|
|
|
|
|
|
void EventRequest::slotRequestFailed()
|
|
{
|
|
QWebSocket *ws = qobject_cast<QWebSocket *>(sender());
|
|
|
|
if (ws == nullptr)
|
|
{
|
|
requestFailed(QStringLiteral("unknown error"));
|
|
return;
|
|
}
|
|
|
|
requestFailed(ws->errorString());
|
|
}
|
|
|
|
} // namespace Timeline
|
|
|
|
|
|
//#include "tln_dialog_model.moc"
|