/** * @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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 &eemForeign, const std::shared_ptr &mrkDtc, const std::shared_ptr &exmDtc, const std::shared_ptr &flmIfc, const std::shared_ptr &ppmDtc, const std::shared_ptr &ppmInfoIfc, const std::shared_ptr &avmIfc, const std::shared_ptr &mrkIfc, const std::shared_ptr &srmDataIfc, const std::shared_ptr &coreDtc, const std::shared_ptr &pdmDtc, const std::shared_ptr &ismDtc, const std::shared_ptr &fltParticipantIfc, const std::shared_ptr &fltAesIfc, const std::shared_ptr &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(m_srpIfc)) // { // LtsWidgets::LtsMessageDialog::showMessage(this, tr("Error trying to create a replay session in stage 5")); // return; // } // if (!replayAppSession->getForeignGuiInterface(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 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 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(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(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 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 &activeEventTypes = m_model->activeEventTypeIds(); QVector 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.
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