Files
qt5-legatus-legacy/Lts/Plugins/Timeline/Gui/EventTimelineClassic/tln_dialog.cpp
T
2026-08-13 18:33:10 +02:00

1086 lines
35 KiB
C++
Executable File

/**
* @RDE_HEADER@
* ExerciseManangement - Exercise Overview Dialog
* $Id$
* $HeadURL$
* $Author$
*
* Created by Mike Wille, SLSE, 2017-02-10
*/
#include "tln_dialog.h"
#include "ui_tln_dialog.h"
#include "tln_dialog_model.h"
#include "tln_cache_dialog.h"
#include "tln_show_details.h"
#include "tln_event_list.h"
#include "tln_legend.h"
#include "detail_data.h"
#include "excon_filter.h"
#include "logger.h"
#include "EventFilter/FilterWindow.h" // TODO: rename to EventFilter and manage the window AND the whole EventFilter there
#include <LtsSession/lts_session_manager.h>
#include <LtsUtilsSettings/settings.h>
#include <LtsAppManager/lts_app_manager.h>
#include <LtsPluginManager/lts_plugin_manager.h>
#include <LtsSession/lts_session_data.h>
#include <LtsSession/lts_session.h>
#include <LtsWidgets/lts_global_wait_dialog.h>
#include <LtsTimelineClassic/EventGraphic.h>
#include <LtsTimelineClassic/EventTools.h>
#include <ngltools/ngltools_dtg.h>
#include <QDialog>
#include <QPushButton>
#include <QLineEdit>
#include <QTextEdit>
#include <QFormLayout>
#include <QSpinBox>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonParseError>
#include <QDebug>
using namespace LtsTimelineClassic;
namespace Timeline
{
TimelineViewDialog::TimelineViewDialog(const QString &menuGroup, const LtsWidgets::ButtonCombination &buttonCombo, bool isMain, QWidget *parent)
: LtsWidgets::LtsDialog(parent, buttonCombo, !isMain)
, m_ui(new Ui::TimelineViewDialog)
, m_menuGroup(menuGroup)
, m_model(new DDSTimelineModel(this))
, m_detailData(nullptr)
, m_filterWindow(nullptr)
, m_eventListDialog(nullptr)
, m_allEventsListDialog(nullptr)
, m_session(nullptr)
, m_requestHelper(new LtsUtils::RequestHelper(this))
, m_streamNameMapping(nullptr)
, m_rangeChanged(false)
, m_loadingCount(0)
, m_armed(false)
, m_exerciseStart(0)
, m_legendDialog(nullptr)
, m_logger(getLogger("Dialog"))
{
m_ui->setupUi(this);
#ifndef LTS_DEBUG
if (LtsSessionData::LtsSessionData::getInstance()->debugFlagValue(QStringLiteral("Timeline.DebugWidget")) != QVariant(true))
{
m_ui->debugWidget->hide();
m_ui->widget->hideDebugWidget();
}
#endif
m_buttonBox->connectButton(LtsWidgets::DialogButton::Edit, this, &TimelineViewDialog::slotShowFiltersDialog);
m_buttonBox->connectButton(LtsWidgets::DialogButton::Show, this, &TimelineViewDialog::slotShowLegend);
m_buttonBox->connectButton(LtsWidgets::DialogButton::Import, this, &TimelineViewDialog::slotStartReplaySituation);
connect(m_ui->pbDebugCreateFakeEvent, &QPushButton::clicked,
this, &TimelineViewDialog::slotDebugCreateFakeEvent);
LtsUtils::ProjectSettings *ps = LtsUtils::ProjectSettings::getInstance();
const bool enableListEvents = ps->value(QStringLiteral("tln"),
QStringLiteral("enableListEvents")).toBool();
if (enableListEvents)
m_buttonBox->connectButton(LtsWidgets::DialogButton::Load, this, &TimelineViewDialog::loadAllEvents);
connect(m_ui->widget, &TimelineWidget::signalZoomChanged,
this, [this]()
{
m_model->invalidateTimeline(); // rebuild all EventGraphics on zoom as every zoom changes the visibility of events as events may overlap on zooming and "unoverlap" on unzooming
});
connect(this, &TimelineViewDialog::signalWaitForReady,
this, &TimelineViewDialog::slotWaitForReady, Qt::QueuedConnection);
connect(m_ui->widget, &TimelineWidget::signalEventAction,
this, &TimelineViewDialog::showDetailsAction);
setOnlineHelpFilename(QStringLiteral("Timeline-Application"));
}
TimelineViewDialog::~TimelineViewDialog()
{
if (m_allEventsListDialog)
{
delete m_allEventsListDialog.data();
m_allEventsListDialog = nullptr;
}
if (m_eventListDialog)
{
delete m_eventListDialog.data();
m_eventListDialog = nullptr;
}
delete m_ui;
delete m_filterWindow;
delete m_legendDialog;
EventTools::s_inUse = false;
}
void TimelineViewDialog::slotDebugCreateFakeEvent()
{
QFormLayout *fl = new QFormLayout;
QLineEdit *typeIdWidget = new QLineEdit;
typeIdWidget->setText(QStringLiteral("8"));
fl->addRow(QStringLiteral("typeId"), typeIdWidget);
QLineEdit *startWidget = new QLineEdit;
startWidget->setText(QStringLiteral("1571141793040"));
fl->addRow(QStringLiteral("start"), startWidget);
QSpinBox *sb = new QSpinBox;
sb->setMaximum(INT_MAX);
sb->setValue(26);
fl->addRow(QStringLiteral("duration [s]"), sb);
QTextEdit *te = new QTextEdit;
te->setPlainText(QStringLiteral("{ \"streamName\": \"00003\", \"endTime\": 1571141819620 }"));
fl->addRow(QStringLiteral("detail"), te);
QPushButton *goBtn = new QPushButton(QStringLiteral("Go"));
connect(goBtn, &QPushButton::clicked, this, [this, typeIdWidget, startWidget, sb, te]()
{
bool k = false;
int typeId = typeIdWidget->text().toInt(&k);
if (!k)
{
new LtsWidgets::LtsMessageDialogNew(this, QStringLiteral("TypeId invalid"));
return;
}
qint64 start = startWidget->text().toLongLong(&k);
if (!k)
{
new LtsWidgets::LtsMessageDialogNew(this, QStringLiteral("start invalid"));
return;
}
qint64 end = start + (sb->value() * 1000);
QJsonParseError error;
const QJsonObject root(QJsonDocument::fromJson(te->toPlainText().toUtf8(), &error).object());
if (error.error != QJsonParseError::NoError)
{
new LtsWidgets::LtsMessageDialogNew(this, QStringLiteral("Detail data json invalid: '%1' at offset %2").arg(error.errorString()).arg(error.offset));
return;
}
static int uniqueId = 9999999;
Event *e = new Event(uniqueId++, typeId, start, end, -1, -1, 1, { QStringLiteral("none") }, LtsTimelineClassic::Event::drawCustomEventGraphicRect, {}, root, -1);
showDetails({e});
});
fl->addRow({}, goBtn);
QDialog *d = new QDialog(this);
d->setLayout(fl);
d->show();
d->activateWindow();
}
void TimelineViewDialog::aboutToClose()
{
if (m_allEventsListDialog)
{
delete m_allEventsListDialog.data();
m_allEventsListDialog = nullptr;
}
if (m_eventListDialog)
{
delete m_eventListDialog.data();
m_eventListDialog = nullptr;
}
if (m_replayApp != nullptr)
m_replayApp->deleteLater();
}
void TimelineViewDialog::retranslate()
{
translate(m_ui, tr("Timeline"));
m_detailData->populate(); // TODO: has currently no effect
m_buttonBox->getButton(LtsWidgets::DialogButton::Edit)->setText(tr("Filters"));
m_buttonBox->getButton(LtsWidgets::DialogButton::Show)->setText(tr("Legend", "Deutsch: Legende (erklärende Ergänzung zu einer Grafik)"));
m_buttonBox->getButton(LtsWidgets::DialogButton::Load)->setText(tr("List events"));
m_buttonBox->getButton(LtsWidgets::DialogButton::Import)->setText(tr("Start Situation Replay App"));
m_buttonBox->getButton(LtsWidgets::DialogButton::Import)->setToolTip(tr("Replays started from the Show Details dialog will play in the Situation Replay App."));
}
bool TimelineViewDialog::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<FileManagement::ForeignGuiInterface> &flmIfc,
const std::shared_ptr<ParticipantManagement::Interface::Dtc> &ppmDtc,
const std::shared_ptr<ParticipantManagement::ForeignParticipantInfoGuiInterface> &ppmInfoIfc,
const std::shared_ptr<AudioVideoManagement::ForeignMenuEditGuiInterface> &avmIfc,
const std::shared_ptr<MarkerManagement::ForeignGuiInterface> &mrkIfc,
const std::shared_ptr<SituationReplayManagement::ForeignDataInterface> &srmDataIfc,
const std::shared_ptr<Core::Interface::Dtc> &coreDtc,
const std::shared_ptr<ProjectDataManagement::Interface::Dtc> &pdmDtc,
const std::shared_ptr<InfrastructureManagement::Interface::Dtc> &ismDtc,
const std::shared_ptr<FilterManagement::ForeignFltParticipantGuiInterface> &fltParticipantIfc,
const std::shared_ptr<FilterManagement::ForeignAesGuiInterface> &fltAesIfc,
const std::shared_ptr<FilterManagement::ForeignFltBuildingComponentGuiInterface> &fltBuildingComponentIfc,
LtsSession::LtsSession *session,
AudioVideoManagement::StreamNameMapping *streamNameMapping)
{
if (EventTools::s_inUse)
{
new LtsWidgets::LtsMessageDialogNew(session->baseLayout(), tr("Cannot open timeline as there is already another timeline instance running in the application."), tr("Another timeline instance already in use"));
return false;
}
EventTools::s_inUse = true;
m_session = session;
m_exmDtc = exmDtc;
m_flmIfc = flmIfc;
m_ppmDtc = ppmDtc;
m_ppmInfoIfc = ppmInfoIfc;
m_avmIfc = avmIfc;
m_mrkIfc = mrkIfc;
m_srmDataIfc = srmDataIfc;
m_coreDtc = coreDtc;
m_pdmDtc = pdmDtc;
m_ismDtc = ismDtc;
m_mrkDtc = mrkDtc;
m_fltParticipantIfc = fltParticipantIfc;
m_fltAesIfc = fltAesIfc;
m_fltBuildingComponentIfc = fltBuildingComponentIfc;
m_streamNameMapping = streamNameMapping;
if (m_coreDtc == nullptr || m_srmDataIfc == nullptr) // Situation Replay App's requirements
{
m_buttonBox->setButtonEnabled(LtsWidgets::DialogButton::Import, false);
m_buttonBox->getButton(LtsWidgets::DialogButton::Import)->setToolTip(tr("Required interfaces missing for starting the Situation Replay App."));
}
m_detailData = new DetailData(session, this);
if (!m_model->init(eemForeign, mrkDtc, exmDtc, ppmDtc, pdmDtc, ismDtc, session, m_detailData, m_streamNameMapping))
{
ERROR_LOG(m_logger) << "model init failed";
return false;
}
m_ui->widget->setTimelineModel(m_model);
connect(m_model, &DDSTimelineModel::signalRebuildTimelineKnownRanges,
this, [this]()
{
m_ui->widget->knownRangesChanged(m_model->getKnownRanges());
emit m_model->dataChanged(QModelIndex(), QModelIndex()); // new events added, time to redraw
});
connect(m_model, &DDSTimelineModel::signalLoading,
this, &TimelineViewDialog::slotLoading);
connect(m_model, &DDSTimelineModel::signalLoadingDone,
this, &TimelineViewDialog::slotLoadingDone);
connect(this, &TimelineViewDialog::signalInitEventList,
this, [this]()
{
if (m_allEventsListDialog)
{
//qCritical() << "signalInitEventList";
EventList visibleEvents = setFilter(m_detailData, m_model->getRootItem()->getFullEventList(true, true), m_filter, this, true, false);
m_allEventsListDialog->reInitEventList(visibleEvents, m_ui->widget->getRangeMinimum(), m_ui->widget->getRangeMaximum());
}
});
// changes arming
QTimer *armTimer = new QTimer(this);
armTimer->setInterval(750);
connect(armTimer, &QTimer::timeout,
this, [this]()
{
if (!m_rangeChanged)
return;
m_rangeChanged = false;
qint64 rangeMin = m_ui->widget->getRangeMinimum();
qint64 rangeMax = m_ui->widget->getRangeMaximum();
if (rangeMin > rangeMax) // no events to request
return;
qint64 now = m_session->partitionTime();
if (rangeMin >= now)
return; // don't ask for events from the future
if (rangeMax >= now)
{
rangeMax = m_session->partitionTime() -1;
//qWarning() << "rangeMax >= now !";
}
//slotLoading();
m_model->getEvents(rangeMin, rangeMax);
});
#ifndef LTS_DEBUG // don't automatically arm on debug
m_armed = true;
armTimer->start();
#endif
#ifndef LTS_DEBUG
if (LtsSessionData::LtsSessionData::getInstance()->debugFlagValue(QStringLiteral("Timeline.DebugWidget")) == QVariant(true))
#endif
{
// show current cursor position as event time
QTimer *tCursor = new QTimer(this);
tCursor->setInterval(500);
connect(tCursor, &QTimer::timeout,
this, [this]()
{
qint64 cursorTime = EventTools::getEventTime(m_ui->widget->mapFromGlobal(QCursor::pos()).x());
m_ui->lblCursorTime->setText(QStringLiteral("%1 (%2)").arg(QDateTime::fromMSecsSinceEpoch(cursorTime).toString()).arg(cursorTime));
});
tCursor->start();
// show draws per second
QTimer *tDraws = new QTimer(this);
tDraws->setInterval(1000);
connect(tDraws, &QTimer::timeout,
this, [this]()
{
int paints = EventTools::s_test;
EventTools::s_test = 0;
m_ui->label_8->setText(QStringLiteral("%1 draw(s) per second").arg(paints));
});
tDraws->start();
// fake events instead of requesting real events
connect(m_ui->cbFake, &QCheckBox::toggled,
this, [this](bool toggled)
{
qWarning() << "fake toggled? " << toggled;
m_model->setCreateFakeEvents(toggled);
});
// arm manually
connect(m_ui->widget, &TimelineWidget::signalArm,
this, [this, armTimer](bool arm)
{
m_armed = arm;
if (arm)
armTimer->start();
else
armTimer->stop();
});
// show cache window
connect(m_ui->pbShowCache, &QPushButton::clicked,
this, [this]()
{
TimelineCacheDialog *cacheDialog = new TimelineCacheDialog(this);
connect(cacheDialog, &TimelineCacheDialog::signalUncache,
this, [this](int /*idx*/)
{
//#warning reimpl
//m_model->forceUncache(idx);
WARNING_LOG(m_logger) << "reimpl me!!";
});
cacheDialog->setAttribute(Qt::WA_DeleteOnClose, true);
cacheDialog->init(m_model->cachedEvents(), m_model->liveCache());
cacheDialog->show();
});
// set live mode manually
// connect(m_ui->cbLive, &QCheckBox::toggled,
// this, [this](bool toggled)
// {
// m_model->setLive(toggled);
// });
}
// arming timer on changes
connect(m_ui->widget, &TimelineWidget::signalRangeChanged, // if scrollbar was moved, set changed flag (timer requests events if flag active)
this, [this, armTimer]()
{
//#warning do not range change if range did not rly change
if (!m_model->hasActiveEventTypes())
{
DEBUG_LOG(m_logger) << "range changed, but no active event types! not arming timer..";
return;
}
if (m_armed)
armTimer->start(); // reset timer
m_rangeChanged = true;
});
connect(m_session, &LtsSession::LtsSession::partitionTimeChanged,
this, [this](qint64 )
{
#ifdef LTS_DEBUG
if (m_ui->cbUpdate->isChecked())
#endif
updateRange();
});
updateRange();
m_ui->widget->setPos(m_session->partitionTime());
connect(m_ui->sbSpan, &QScrollBar::valueChanged,
this, &TimelineViewDialog::slotSpanChanged);
connect(m_ui->pbLive, &QPushButton::clicked,
this, [this]()
{
updateRange(true /* initial */);
});
m_exerciseStart = m_exmDtc->exercise(m_session->exerciseId()).beginTime();
if (m_exerciseStart < 1)
{
LtsWidgets::LtsMessageDialog::showMessage(m_session->baseLayout(), tr("Exercise data not available or exercise not started yet."), tr("Cannot open timeline")); // block ..
ERROR_LOG(m_logger) << "exercise data not available - exercise start: " << m_exerciseStart;
return false;
}
{
// manually update timeline, then set initial timeline pos
QSignalBlocker block(m_ui->sbSpan);
(void)block;
updateRange(true /* init */);
}
m_ui->widget->setMaxPos(); // set timeline to live position on initial start
// enabled by setting active event types - user has to see that there have to be active event types to have the timeline usable
m_ui->widget->setEnabled(false);
m_ui->sbSpan->setEnabled(false);
m_ui->pbLive->setEnabled(false);
LtsWidgets::LtsGlobalWaitDialog::getInstance()->wait();
emit signalWaitForReady();
return true;
}
void TimelineViewDialog::slotSpanChanged()
{
qint64 end = currentSpanTime();
qint64 start = end - (8 * 60 * 60 * 1000);
m_model->setTimeRange(start, end);
m_ui->lbStartTime->setText(DTG::toStringMSec(start));
m_ui->lbEndTime->setText(DTG::toStringMSec(end));
bool live = (m_ui->sbSpan->value() == m_ui->sbSpan->maximum()); // auto-scroll (aka. live mode) if at end, like any text edit does
m_model->setLive(live);
m_ui->widget->slotUpdateTimeline(); // only update [emits signalRangeChanged => m_ui->sbSpan]
}
void TimelineViewDialog::slotLoading()
{
#ifdef LTS_DEBUG
m_ui->lblStartTime->setStyleSheet(QLatin1String("background-color: green;"));
m_ui->lblEndTime->setStyleSheet(QLatin1String("background-color: green;"));
#endif
m_loadingCount++;
//qWarning() << "m_loadingCount++ =" << m_loadingCount;
}
void TimelineViewDialog::slotLoadingDone()
{
if (m_loadingCount > 0)
m_loadingCount--;
#ifdef LTS_DEBUG
if (m_loadingCount == 0) // only reset if there are no requests left
{
m_ui->lblStartTime->setStyleSheet(QString());
m_ui->lblEndTime->setStyleSheet(QString());
}
#endif
//qWarning() << "m_loadingCount-- =" << m_loadingCount;
//qCritical() << "slotLoadingDone!";
emit signalInitEventList();
}
qint64 TimelineViewDialog::currentSpanTime() const
{
qint64 value = m_ui->sbSpan->value();
value *= /*60 * 60 **/ 1000;
return value;
}
void TimelineViewDialog::updateRange(bool initial)
{
qint64 start = m_exerciseStart;
qint64 partitionTime = m_session->partitionTime();
qint64 now = partitionTime;
if (now < 1)
now = start;
if (now == 0)
return;
now = (now / 1000);// + 1; // msecs to hours since epoch (add 1 hour to end so the last event is always visible)
bool liveSpan = (m_ui->sbSpan->value() == m_ui->sbSpan->maximum()); // auto-scroll (the span scrollbar) if last value was at end, like any text edit does
m_ui->sbSpan->setMinimum(start / 1000);
m_ui->sbSpan->setMaximum(now);
if (initial || liveSpan)
{
QSignalBlocker block(m_ui->sbSpan); // don't let the widget tell us that its value changed, we know it here already
(void)block;
m_ui->sbSpan->setValue(now); // live [sbSpan calls slotSpanChanged, which updates the timeline]
}
m_ui->widget->setLiveTime(partitionTime);
slotSpanChanged(); // span changes when range changes
//qWarning() << "pos" << m_ui->widget->getPos() << "max" << m_ui->widget->getMaxPos() << "diff" << m_ui->widget->getPos() - m_ui->widget->getMaxPos();
bool liveRange = (m_ui->widget->getPos() >= m_ui->widget->getMaxPos() - 1); // auto-scroll (the range scrollbar) if last value was at end, like any text edit does [NOTE: slotSpanChanged changes the scrollbar pos]
//#warning Initiale Timeline Position fehlerhaft - Timeline Position zu gering nach update !
if (initial || liveRange)
{
// must be called after slotSpanChanged
m_ui->widget->setPos(EventTools::getTimelineMax());//m_model->highestPositionedItem());
}
}
void TimelineViewDialog::slotStartReplaySituation()
{
if (m_replayApp != nullptr)
return;
m_buttonBox->setButtonEnabled(LtsWidgets::DialogButton::Import, false);
if (m_requestHelper->startSingleRequest([&]()
{
//#warning !!
return m_srmDataIfc->replayPartitionCreateRequest(m_session->exerciseId(),
LtsSessionData::LtsSessionData::getInstance()->getWorkstationId(),
LtsSessionData::LtsSessionData::getInstance()->getOperatorId(),
Core::ReplayType::SituationData,
this);
}))
{
Core::ReplayPartition rp;
for (int i = 0; i < 50; i++) // FIXME: UGLY HACK
{
//#warning TODO: FIXME: that shouldn't work! replace with QTimer! (steal from other replay starter methods)
// QTimer ...
//QThread::usleep(100 * 1000);
rp = m_coreDtc->replayPartition(m_requestHelper->getRepliedId());
if (rp.id() > 0)
break;
}
if (rp.id() == 0)
{
LtsWidgets::LtsMessageDialog::showMessage(this, tr("Error trying to create a replay session in stage 1."));
WARNING_LOG(m_logger) << "[timeout] Error while trying to create a replay session: domainId: " << m_session->domainId() <<
" - replay partition does not exist - replay id: " << m_requestHelper->getRepliedId() <<
" - url " << m_session->tcpConnectionUrl().toString();
m_buttonBox->setButtonEnabled(LtsWidgets::DialogButton::Import, true);
return;
}
else
{
LtsSession::DdsConnectionParams connParams(rp);
m_replayApp = LtsAppManager::LtsAppManager::getInstance()->createApp(LtsAppDescriptor::App::Situation, connParams);
if (m_replayApp == nullptr)
{
LtsWidgets::LtsMessageDialog::showMessage(this, tr("Error trying to create a replay session in stage 2."));
WARNING_LOG(m_logger) << "Error while trying to create a replay session: domainId: " << m_session->domainId() <<
" - partition create reply (failed) - replay id: " << m_requestHelper->getRepliedId() <<
" - url: " << m_session->tcpConnectionUrl().toString();
m_buttonBox->setButtonEnabled(LtsWidgets::DialogButton::Import, true);
return;
}
else
{
connect(m_replayApp->m_sessionManager, &LtsSession::LtsSessionManager::signalInitializationFinished,
this, [this, rp]()
{
LtsSession::LtsSession *replayAppSession = m_replayApp->m_sessionManager->getSession(IDL::PartitionType::Replay, rp.id());
if (replayAppSession == nullptr)
{
LtsWidgets::LtsMessageDialog::showMessage(this, tr("Error trying to create a replay session in stage 4."));
return;
}
m_replayAppSession = replayAppSession;
// >> DO THIS IN SHOW DETAiLS DIALOG
// if (!replayAppSession->getForeignGuiInterface<SituationReplay::ForeignGuiInterface>(m_srpIfc))
// {
// LtsWidgets::LtsMessageDialog::showMessage(this, tr("Error trying to create a replay session in stage 5"));
// return;
// }
// if (!replayAppSession->getForeignGuiInterface<SituationMap::ForeignGuiInterface>(m_sitMapIfc))
// {
// LtsWidgets::LtsMessageDialog::showMessage(this, tr("Error trying to create a replay session in stage 6"));
// return;
// }
connect(m_replayApp.data(), &LtsPluginManager::LtsPluginManager::destroyed,
this, [this]()
{
// m_srpIfc = nullptr;
// m_sitMapIfc = nullptr;
m_buttonBox->setButtonEnabled(LtsWidgets::DialogButton::Import, true);
});
});
LtsSession::LtsSession *replayAppSession = m_replayApp->m_sessionManager->getSession(IDL::PartitionType::Replay, rp.id());
auto fn = [this, replayAppSession]()
{
LtsWidgets::LtsMessageDialog::showMessage(this, tr("Error trying to create a replay session in stage 3."));
m_buttonBox->setButtonEnabled(LtsWidgets::DialogButton::Import, true);
return;
};
if (replayAppSession->isInitializationDone())
fn();
else
connect(replayAppSession, &LtsSession::LtsSession::signalDoneInitializingSession,
this, fn);
}
}
}
else
{
LtsWidgets::LtsMessageDialog::showMessage(this, tr("Error trying to create a replay session."));
m_buttonBox->setButtonEnabled(LtsWidgets::DialogButton::Import, true);
}
}
void TimelineViewDialog::slotEventTypesChanged(QSet<int> activeEventTypeIds)
{
if (m_filterWindow != nullptr && m_filterWindow->hasUnsavedChanges())
{
new LtsWidgets::LtsMessageDialogNew(this, tr("You have unsaved filters in Timeline's Filter dialog. Please save or discard them before you add or remove active events in Timeline's Legend dialog."), tr("Unsaved filters"));
return;
}
if (m_model->activeEventTypeIds().contains(markerTypeId))
activeEventTypeIds.insert(markerTypeId);
if (m_legendDialog != nullptr)
m_legendDialog->__unchange__(); // no unsaved filters, filters can be applied
setTimelineUsable(!activeEventTypeIds.isEmpty());
QSet<int> newActiveEventTypesIds(activeEventTypeIds - m_model->activeEventTypeIds());
//qWarning() << "Event Types Changed: new" << activeEventTypeIds << "old" << m_model->activeEventTypeIds() << "sub" << newActiveEventTypesIds;
m_model->setActiveEventTypeIds(activeEventTypeIds);
updateFiltersByActiveEventTypes();
if (!newActiveEventTypesIds.isEmpty()) // only request events of newly added event type, if there was indeed a newly added event type
m_model->getAllEventsOfType(newActiveEventTypesIds);
}
void TimelineViewDialog::slotShowLegend()
{
QString timelineLegendDialogIdent(QStringLiteral("Timeline.Legend"));
auto layoutArea = LtsWidgets::LayoutArea::eInfoTab;
if (m_legendDialog != nullptr)
{
focusDialog(m_menuGroup, timelineLegendDialogIdent, layoutArea);
return;
}
m_legendDialog = new TimelineLegendDialog;
connect(m_legendDialog, &TimelineLegendDialog::destroyed,
this, [this]()
{
if (static_cast<QWidget *>(sender()) == m_legendDialog)
m_legendDialog = nullptr;
});
connect(m_legendDialog, &TimelineLegendDialog::signalEventTypesChanged,
this, &TimelineViewDialog::slotEventTypesChanged);
if (!m_legendDialog->init(m_model, m_model->activeEventTypeIds()))
{
m_legendDialog->deleteLater();
m_legendDialog = nullptr;
return;
}
m_session->addWidget(m_legendDialog, m_menuGroup, timelineLegendDialogIdent, layoutArea);
}
void TimelineViewDialog::slotShowFiltersDialog()
{
if (m_detailData == nullptr)
return;
if (m_filterWindow != nullptr)
{
focusDialog(m_menuGroup, QStringLiteral("timelineFilterDialog"), LtsWidgets::LayoutArea::eDialogTab);
return;
}
m_filterWindow = new FilterWindow(m_detailData, m_flmIfc, m_session, this);
connect(m_filterWindow, &FilterWindow::signalShowEvents,
this, &TimelineViewDialog::slotShowEvents);
connect(m_filterWindow, &FilterWindow::signalApplyFilter,
this, &TimelineViewDialog::slotApplyFilter);
connect(m_filterWindow, &FilterWindow::destroyed,
this, [this]()
{
if (sender() == static_cast<QObject *>(m_filterWindow))
m_filterWindow = nullptr;
});
m_filterWindow->loadFilters(m_filter);
addDialog(m_filterWindow, m_menuGroup, QStringLiteral("timelineFilterDialog"));
}
void TimelineViewDialog::slotShowEvents(const QJsonArray &filter)
{
EventList visibleEvents = setFilter(m_detailData, m_model->getRootItem()->getFullEventList(true, true), filter, this, true /* simulation */, false /* handle errors */);
if (visibleEvents.isEmpty())
{
LtsWidgets::LtsMessageDialog::showMessage(this, tr("This filter has no results"), tr("No Events found"));
return;
}
showDetails(visibleEvents);
}
void TimelineViewDialog::loadAllEvents()
{
LtsTimelineClassic::EventList visibleEvents = setFilter(m_detailData, m_model->getRootItem()->getFullEventList(true, true), m_filter, this, true /* simulation */, false /* handle errors */);
qint64 endTime = m_ui->widget->getRangeMaximum();
qint64 startTime = m_ui->widget->getRangeMinimum();
LtsTimelineClassic::EventList newEventList;
for (auto i = visibleEvents.constBegin(); i != visibleEvents.constEnd(); ++i)
{
LtsTimelineClassic::Event *e = (*i);
if (e->startTime() >= startTime && (e->startTime() + e->duration()) <= endTime)
newEventList += e;
}
if (newEventList.isEmpty())
{
LtsWidgets::LtsMessageDialog::showMessage(this, tr("This filter has no results"), tr("No Events found"));
return;
}
if (newEventList.isEmpty())
{
LtsWidgets::LtsMessageDialog::showMessage(this, tr("No Events to show"));
return;
}
if (m_allEventsListDialog == nullptr)
{
m_allEventsListDialog = new EventListDialog(m_menuGroup, m_session, m_ppmDtc, m_pdmDtc, m_ismDtc, m_flmIfc, false /* don't show markers */);
connect(m_allEventsListDialog.data(), &EventListDialog::signalShowEventDetails,
this, &TimelineViewDialog::slotShowEventDetails);
if (!m_allEventsListDialog->init(m_detailData, newEventList, true))
{
m_allEventsListDialog->deleteLater();
m_allEventsListDialog = nullptr;
return;
}
addDialog(m_allEventsListDialog, m_menuGroup, QStringLiteral("allEventsListDialog"), LtsWidgets::LayoutArea::eDialogTab);
}
else
{
m_allEventsListDialog->clearModel();
m_allEventsListDialog->appendEventList(newEventList);
focusDialog(m_menuGroup, QStringLiteral("allEventsListDialog"), LtsWidgets::LayoutArea::eDialogTab);
}
}
void TimelineViewDialog::setTimelineUsable(bool usable)
{
m_ui->widget->setEnabled(usable);
m_ui->sbSpan->setEnabled(usable);
m_ui->pbLive->setEnabled(usable);
m_ui->lblNoEventsSelected->setVisible(!usable); // tell the user there are no event types selected to get (if none selected though)
}
void TimelineViewDialog::updateActiveEventTypesByFilters()
{
QSet<int> activeEventTypes;
for (auto i1 = m_filter.constBegin(); i1 != m_filter.constEnd(); ++i1)
{
const QJsonObject &filter = (*i1).toObject();
QString eventTypeString = filter[QStringLiteral("EventType")].toString();
bool k = false;
int eventType = eventTypeString.toInt(&k); // timeline is type safe by default in the meantime
if (!k || eventType < 0 || !filter[QStringLiteral("Active")].toBool(false))
continue;
activeEventTypes.insert(eventType);
}
m_model->setActiveEventTypeIds(activeEventTypes);
setTimelineUsable(!activeEventTypes.isEmpty());
if (m_legendDialog != nullptr)
m_legendDialog->setActiveEventTypeIds(activeEventTypes);
}
void TimelineViewDialog::updateFiltersByActiveEventTypes() // legend changes, de-/activate filters
{
const QSet<int> &activeEventTypes = m_model->activeEventTypeIds();
QVector<int> filterEventTypes;
// de-/activate filters
for (QJsonValueRef filter : m_filter) // clazy:exclude=range-loop
{
QJsonObject filterObj = filter.toObject();
QString eventTypeString = filterObj[QStringLiteral("EventType")].toString();
bool k = false;
int eventType = eventTypeString.toInt(&k); // timeline is type safe by default in the meantime
if (!k || eventType < 0)
continue;
if (!filterEventTypes.contains(eventType))
filterEventTypes.append(eventType);
bool active = filterObj[QStringLiteral("Active")].toBool(false);
if (active && !activeEventTypes.contains(eventType)) // filter is active but event type is not active - deactivate filter
active = false;
else if (!active && activeEventTypes.contains(eventType)) // filter is inactive but event type is active - (re)activate filter
active = true;
filterObj[QStringLiteral("Active")] = active;
filter = filterObj;
}
// create bare filters for active events that currently don't have any filters
for (int eventType : activeEventTypes)
{
if (!filterEventTypes.contains(eventType))
{
QJsonObject filter;
filter[QStringLiteral("EventType")] = QString::number(eventType);
filter[QStringLiteral("Active")] = true;
m_filter.append(filter);
}
}
applyFilter(m_filter, false /* do not update active event types, because that's where we come from */, true);
}
void TimelineViewDialog::applyFilter(const QJsonArray &filter, bool doUpdateActiveEventTypes, bool reloadFilterWindow)
{
m_filter = filter;
setFilter(m_detailData, m_model->getRootItem()->getFullEventList(true, true), filter, this);
m_model->applyFilter(filter);
if (doUpdateActiveEventTypes)
updateActiveEventTypesByFilters();
if (reloadFilterWindow && m_filterWindow != nullptr)
m_filterWindow->loadFilters(filter);
}
void TimelineViewDialog::slotApplyFilter(const QJsonArray &filter)
{
applyFilter(filter, true /* update legend */, false /* don't reload filter window, that's where we come from */);
}
void TimelineViewDialog::slotWaitForReady()
{
// test if exercise is inside partition time
ExerciseManagement::Exercise ex = m_exmDtc->exercise(m_session->partitionId());
if (ex.id() > 0)
{
qint64 partitionTime = 0;
partitionTime = m_session->partitionTime();
if (partitionTime < 1)
{
emit signalWaitForReady();
return;
}
if (partitionTime < ex.beginTime())
{
LtsWidgets::LtsMessageDialog::showMessage(this, tr("The current time is outside of the Exercise's period.<br>Events are only recorded in Exercise period boundaries."), tr("No Events available"));
WARNING_LOG(m_logger) << "Partition time was " << partitionTime << ", exercise begin time: " << ex.beginTime();
}
}
else
{
WARNING_LOG(m_logger) << "TimelineViewDialog::init: could not check exercise's boundaries!";
}
LtsWidgets::LtsGlobalWaitDialog::getInstance()->done();
}
void TimelineViewDialog::slotShowEventDetails(const Event *event)
{
switch (event->typeId())
{
case ProjectDataManagement::EventType::BiteMessage:
{
m_ppmInfoIfc->showBiteWindow(event->detailData());
break;
}
default:
{
addDialog(LtsWidgets::initDialog(new ShowDetailsDialog(),
m_detailData, event,
m_session,
m_replayAppSession, m_avmIfc, m_mrkIfc, m_fltParticipantIfc, m_fltAesIfc, m_fltBuildingComponentIfc,
m_session->app() == LtsAppDescriptor::App::Timeline,
this, m_streamNameMapping),
m_menuGroup, QStringLiteral("ShowEventDetails%1").arg(event->uniqueId()), LtsWidgets::LayoutArea::eInfoTab);
break;
}
}
}
void TimelineViewDialog::showDetails(const EventList &eventList)
{
if (eventList.isEmpty())
{
LtsWidgets::LtsMessageDialog::showMessage(this, tr("No Events to show"));
return;
}
if (eventList.count() > 1) // multiple events - show list of events (instead of the event detail dialog itself) in detail area
{
if (m_eventListDialog == nullptr)
{
m_eventListDialog = new EventListDialog(m_menuGroup, m_session, m_ppmDtc, m_pdmDtc, m_ismDtc, m_flmIfc, true /* show markers */);
connect(m_eventListDialog.data(), &EventListDialog::signalShowEventDetails,
this, &TimelineViewDialog::slotShowEventDetails);
if (!m_eventListDialog->init(m_detailData, eventList, false))
{
m_eventListDialog->deleteLater();
m_eventListDialog = nullptr;
return;
}
addDialog(m_eventListDialog, m_menuGroup, QStringLiteral("eventListDialog"), LtsWidgets::LayoutArea::eDialogTab);
}
else
{
m_eventListDialog->appendEventList(eventList);
focusDialog(m_menuGroup, QStringLiteral("eventListDialog"), LtsWidgets::LayoutArea::eDialogTab);
}
}
else // single event - show single event in info area
{
slotShowEventDetails(eventList.at(0));
}
}
void TimelineViewDialog::showDetailsAction(EventGraphic *eg, int action)
{
if (action != EventGraphic::ACTION_LEFT_CLICKED)
return;
showDetails(eg->eventList());
}
} // namespace Timeline