1086 lines
35 KiB
C++
Executable File
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
|