#include "tln_timeline_dialog.h" #include "logger.h" #include "tln_abstract_participant_event_item.h" #include "tln_filter_dialog.h" #include "tln_filter_edit_dialog.h" #include "tln_item_info_dialog.h" #include "tln_item_list_dialog.h" #include "tln_item_list_model.h" #include "tln_legend.h" #include "tln_timeline_delegate.h" #include "tln_timeline_select_handler.h" #include "tln_timeline_progress.h" #include "tln_timeline_proxy_model.h" #include "EventTypes/CameraVideo/tln_camera_video_filter.h" #include "EventTypes/FireEvent/tln_fire_event_filter.h" #include "EventTypes/Hit/tln_hit_filter.h" #include "EventTypes/HitBuilding/tln_hit_building_filter.h" #include "EventTypes/HitEffectSimulation/tln_hit_effect_simulation_filter.h" #include "EventTypes/Marker/tln_marker_filter.h" #include "EventTypes/RadioCnr/tln_radio_cnr_filter.h" #include "EventTypes/RadioTvc/tln_radio_tvc_filter.h" #include "EventTypes/WeaponAssignment/tln_weapon_assignment_filter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include // Debug Window Includes #include #include #include #include #include // end of Debug Window Includes #include #include #include #include #include #include #include #include namespace Timeline { TimelineDialog::TimelineDialog( LtsSession::LtsSession *session, const QString &menuGroup, bool isMain, const std::shared_ptr &coreDtc, const std::shared_ptr &avmDtc, const std::shared_ptr &ismDtc, const std::shared_ptr &opmDtc, const std::shared_ptr &pdmDtc, const std::shared_ptr &ppmDtc, const std::shared_ptr &avmIfc, const std::shared_ptr &avmDataIfc, const std::shared_ptr &flmIfc, const std::shared_ptr &fltParticipantIfc, const std::shared_ptr &fltAesIfc, const std::shared_ptr &fltBuildingComponentIfc, const std::shared_ptr &mrkIfc, const std::shared_ptr &srmIfc ) noexcept(false) : LtsWidgets::LtsDialog(nullptr, LtsWidgets::ButtonCombination::CloseOnly, !isMain) , m_session(session) , m_menuGroup(menuGroup) , m_coreDtc(coreDtc) , m_avmDtc(avmDtc) , m_ismDtc(ismDtc) , m_opmDtc(opmDtc) , m_pdmDtc(pdmDtc) , m_ppmDtc(ppmDtc) , m_avmIfc(avmIfc) , m_avmDataIfc(avmDataIfc) , m_flmIfc(flmIfc) , m_fltParticipantIfc(fltParticipantIfc) , m_fltAesIfc(fltAesIfc) , m_fltBuildingComponentIfc(fltBuildingComponentIfc) , m_mrkIfc(mrkIfc) , m_srmIfc(srmIfc) , m_liveTimer(new QTimer(this)) , m_ui(new Ui::TimelineDialog) , m_logger(getLogger("TimelineDialog")) { INFO_LOG(m_logger) << LOGFUNCTION << " START"; setOnlineHelpFilename(QStringLiteral("Timeline-Application")); m_ui->setupUi(this); // auto *loading = new LtsWidgets::LtsOverlayLoading; // setOverlay(loading); m_buttonBox->getButton(LtsWidgets::DialogButton::Edit)->setText(tr("Filters")); m_buttonBox->connectButton(LtsWidgets::DialogButton::Edit, this, &TimelineDialog::onFilter); m_buttonBox->getButton(LtsWidgets::DialogButton::Show)->setText(tr("Legend", "Deutsch: Legende (erklärende Ergänzung zu einer Grafik)")); m_buttonBox->connectButton(LtsWidgets::DialogButton::Show, this, &TimelineDialog::onShowLegend); if (LtsUtils::ProjectSettings::getInstance()->value(QStringLiteral("tln"), QStringLiteral("enableListEvents")).toBool()) { m_buttonBox->getButton(LtsWidgets::DialogButton::Load)->setText(tr("List events")); m_buttonBox->connectButton(LtsWidgets::DialogButton::Load, this, &TimelineDialog::onShowItemsForAllFilters); } std::shared_ptr exmDtc; if (!m_session->getDtc(exmDtc)) throw "EXM-DTC unavailable"; ExerciseManagement::Exercise exc = exmDtc->exercise(session->exerciseId()); if (exc.id() == 0) // aka. not found throw "exercise not found"; m_exerciseStart = exc.startTime() > 0 ? exc.startTime() : exc.beginTime(); m_exerciseEnd = exc.stopTime() > 0 ? exc.stopTime() : exc.endTime(); m_range.minStartMSecs = m_exerciseStart; m_range.minStartSecs = m_range.minStartMSecs / 1000LL; if (m_exerciseEnd == 0) throw QStringLiteral("both end time and stop time of exercise %1 are unknown").arg(exc.id()); if (m_session->app() == LtsAppDescriptor::App::ThpSituationReplay || exc.state() == ExerciseManagement::ExerciseState_def::Paused || exc.state() == ExerciseManagement::ExerciseState_def::Stopped) { m_range.maxEndSecs = m_exerciseEnd / 1000LL; m_range.maxEndMSecs = static_cast(m_range.maxEndSecs) * 1000LL; // cut off msecs } else { m_range.maxEndSecs = QDateTime::currentSecsSinceEpoch(); m_range.maxEndMSecs = static_cast(m_range.maxEndSecs) * 1000LL; // cut off msecs } if (m_range.maxEndMSecs - m_range.minStartMSecs < ZoomFactor::SixtyMinutes) // range must be at least 1 hour { m_range.minStartMSecs -= ZoomFactor::SixtyMinutes; m_range.minStartSecs = m_range.minStartMSecs / 1000LL; } m_model = new TimelineModel(m_session, this, m_exerciseStart, m_range.maxEndMSecs, this); m_model->setEditable(false); // connect(m_model, &TimelineModel::initializationFinished, // this, [this, loading]() // { // unsetOverlay(); // loading->deleteLater(); // }, Qt::QueuedConnection); auto view = m_ui->timeline->view(); #define ENABLE_SORT_PROXY #ifdef ENABLE_SORT_PROXY //QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel; m_pModel = new TimelineProxyModel; m_pModel->setSourceModel((QAbstractItemModel *)m_model); m_ui->timeline->setModel(m_pModel); view->setSortingEnabled(true); view->sortByColumn(0, Qt::SortOrder::AscendingOrder); #else m_ui->timeline->setModel(m_model); #endif m_timeline_progress = new TimelineProgress(this); m_timeline_progress->setVisible(false); resizeTimelineProgress(); if (!static_cast(m_model->filter(EventType::CameraVideo))->init(m_avmDtc, m_ismDtc, m_avmDataIfc, m_avmIfc, exc.id())) WARNING_LOG(m_logger) << "initializing CameraVideoFilter failed"; if (!static_cast(m_model->filter(EventType::FireEvent))->init(m_pdmDtc)) WARNING_LOG(m_logger) << "initializing FireEventFilter failed"; if (!static_cast(m_model->filter(EventType::Hit))->init(m_session, m_pdmDtc, m_ppmDtc)) WARNING_LOG(m_logger) << "initializing HitFilter failed"; if (!static_cast(m_model->filter(EventType::HitBuilding))->init(m_session, m_ismDtc, m_pdmDtc, m_ppmDtc)) WARNING_LOG(m_logger) << "initializing HitBuildingFilter failed"; if (!static_cast(m_model->filter(EventType::HitEffectSimulation))->init(m_pdmDtc)) WARNING_LOG(m_logger) <<"initializing HitEffectSimulationFilter failed"; if (!static_cast(m_model->filter(EventType::Marker))->init(m_opmDtc)) WARNING_LOG(m_logger) << "initializing MarkerFilter failed"; if (!static_cast(m_model->filter(EventType::RadioCnr))->init(m_avmDtc, exc.id(), m_model)) WARNING_LOG(m_logger) << "initializing RadioCnrFilter failed"; if (!static_cast(m_model->filter(EventType::RadioTvc))->init(m_avmDtc, exc.id(), m_model)) WARNING_LOG(m_logger) << "initializing RadioTvcFilter failed"; if (!static_cast(m_model->filter(EventType::WeaponAssignment))->init(m_pdmDtc)) WARNING_LOG(m_logger) << "initializing WeaponAssignmentFilter failed"; // also initialize filters for temporary item lists ... if (!static_cast(m_model->tempFilter(EventType::CameraVideo))->init(m_avmDtc, m_ismDtc, m_avmDataIfc, m_avmIfc, exc.id())) WARNING_LOG(m_logger) << "initializing CameraVideoFilter failed"; if (!static_cast(m_model->tempFilter(EventType::FireEvent))->init(m_pdmDtc)) WARNING_LOG(m_logger) << "initializing FireEventFilter failed"; if (!static_cast(m_model->tempFilter(EventType::Hit))->init(m_session, m_pdmDtc, m_ppmDtc)) WARNING_LOG(m_logger) << "initializing HitFilter failed"; if (!static_cast(m_model->tempFilter(EventType::HitBuilding))->init(m_session, m_ismDtc, m_pdmDtc, m_ppmDtc)) WARNING_LOG(m_logger) << "initializing HitBuildingFilter failed"; if (!static_cast(m_model->tempFilter(EventType::HitEffectSimulation))->init(m_pdmDtc)) WARNING_LOG(m_logger) <<"initializing HitEffectSimulationFilter failed"; if (!static_cast(m_model->tempFilter(EventType::Marker))->init(m_opmDtc)) WARNING_LOG(m_logger) << "initializing MarkerFilter failed"; if (!static_cast(m_model->tempFilter(EventType::RadioCnr))->init(m_avmDtc, exc.id(), m_model)) WARNING_LOG(m_logger) << "initializing RadioCnrFilter failed"; if (!static_cast(m_model->tempFilter(EventType::RadioTvc))->init(m_avmDtc, exc.id(), m_model)) WARNING_LOG(m_logger) << "initializing RadioTvcFilter failed"; if (!static_cast(m_model->tempFilter(EventType::WeaponAssignment))->init(m_pdmDtc)) WARNING_LOG(m_logger) << "initializing WeaponAssignmentFilter failed"; view->setItemDelegate(new TimelineDelegate(view)); view->setSelectHandler(new TimelineSelectHandler(view)); view->setMinimumZoomFactor(LtsTimeline::ZoomFactor::FiveMinutes); // using it on timeline would init combobox twice m_ui->timeline->setMaximumZoomFactor(LtsTimeline::ZoomFactor::EightHours); view->setDragHandler(nullptr); view->setResizeHandler(nullptr); view->setExpandsOnDoubleClick(false); m_ui->timeline->header()->setTrackLabel(tr("Actuator")); m_ui->timeline->header()->setDateTimeFormat(LtsTimeline::DateTimeFormat::DTGVeryShort); m_ui->timeline->header()->setDateTimeFormatChangable(false); m_ui->timeline->header()->setHoverDateTimeFormat(LtsTimeline::DateTimeFormat::DTGShort); m_ui->timeline->header()->setLimitsVisible(true); m_ui->timeline->header()->setIndicatorScalingFactor(0.33); m_ui->timeline->header()->setLiveIndicatorScalingFactor(0.33); m_ui->timeline->header()->setLiveIndicatorColor(Qt::darkGreen); m_ui->timeline->header()->font().setBold(false); view->expandAll(); connect(view, &TimelineView::customContextMenuRequested, this, &TimelineDialog::onContextMenu); connect(view, &TimelineView::trackActivated, this, &TimelineDialog::onTrackActivated); connect(view, &TimelineView::selectedItemsChanged, this, &TimelineDialog::onItemsSelected); m_ui->dteStart->setRetrieveCurrentTimeCallback([this]() { return m_session->partitionTime(); }); m_ui->dteEnd->setRetrieveCurrentTimeCallback([this]() { return m_session->partitionTime(); }); m_ui->dteStart->setMinimumTimeMSec(m_range.minStartMSecs); m_ui->dteStart->setMaximumTimeMSec(m_range.maxEndMSecs - m_range.minDurationMSecs); m_ui->dteEnd->setMinimumTimeMSec(m_range.minStartMSecs + m_range.minDurationMSecs); m_ui->dteEnd->setMaximumTimeMSec(m_range.maxEndMSecs); m_ui->rsTimelineRange->setRange(m_range.minStartSecs, m_range.maxEndSecs); connect(m_ui->rsTimelineRange, &LtsWidgets::LtsRangeSlider::valuesChanged, this, &TimelineDialog::onRangeEdited); connect(m_ui->rsTimelineRange, &LtsWidgets::LtsRangeSlider::minimumValueChanged, this, &TimelineDialog::onRangeStartEdited); connect(m_ui->rsTimelineRange, &LtsWidgets::LtsRangeSlider::maximumValueChanged, this, &TimelineDialog::onRangeEndEdited); connect(m_ui->dteStart, &LtsWidgets::LtsDateTimeEdit::dateTimeChanged, this, &TimelineDialog::onStartEdited); connect(m_ui->dteEnd, &LtsWidgets::LtsDateTimeEdit::dateTimeChanged, this, &TimelineDialog::onEndEdited); m_ui->rsTimelineRange->setPositions(m_range.maxEndSecs - m_range.minDurationSecs, m_range.maxEndSecs); view->setRange(m_range.maxEndMSecs - m_range.minDurationMSecs, m_range.maxEndMSecs); m_ui->tbLive->setToolTip(tr("Currently not live. Click to enter live mode.")); m_liveTimer->setSingleShot(false); connect(m_ui->tbLive, &QToolButton::clicked, this, &TimelineDialog::onLive); connect(m_liveTimer.get(), &QTimer::timeout, this, &TimelineDialog::onLiveTimeout); connect(m_liveTimer.get(), &QTimer::timeout, this, &TimelineDialog::hideProgress); connect(view, &LtsTimeline::TimelineView::rangeChanged, m_model, &TimelineModel::onRangeChanged); connect(view, &LtsTimeline::TimelineView::currentTimeChanged, m_model, &TimelineModel::onCurrentTimeChanged); connect(view, &LtsTimeline::TimelineView::zoomChanged, m_model, &TimelineModel::onZoomChanged); m_ui->dteStart->setMsecsSinceEpoch(QDateTime::currentMSecsSinceEpoch() - LtsTimeline::ZoomFactor::SixtyMinutes); m_ui->dteEnd->setMsecsSinceEpoch(QDateTime::currentMSecsSinceEpoch()); m_ui->dteStart->setMsecsSinceEpoch(QDateTime::currentMSecsSinceEpoch() - LtsTimeline::ZoomFactor::SixtyMinutes); m_ui->dteEnd->setMsecsSinceEpoch(QDateTime::currentMSecsSinceEpoch()); m_ui->timeline->setZoomFactor(ZoomFactor::SixtyMinutes); m_model->onRangeChanged(m_ui->timeline->startTime(), m_ui->timeline->startTime() + m_ui->timeline->duration()); m_model->onCurrentTimeChanged(m_ui->timeline->currentTime()); m_model->onZoomChanged(m_ui->timeline->zoomFactor()); connect(m_model, &TimelineModel::errorCountExceeded, this, [this]() { new LtsWidgets::LtsMessageDialogNew(this, tr("The range you have selected has more than 750.000 results, please narrow down your selected range to try again.")); }); connect(m_model, &TimelineModel::updateStatusChanged, this, &TimelineDialog::modelUpdateStatusChanged); #ifdef LTS_DEBUG m_ui->timeline->showDebugWidget(); connect(m_ui->pbDebugFakeTick, &QPushButton::clicked, this, [this]() { //1614156795000 AND 1614171017000 m_ui->dteStart->setMsecsSinceEpoch(1614155795000); m_ui->dteEnd->setMsecsSinceEpoch(1614172017000); }); connect(m_ui->pbDebugFakeTick2, &QPushButton::clicked, this, [this]() { QInputDialog *id = new QInputDialog(this); id->setComboBoxEditable(true); id->setComboBoxItems(QStringList() << QStringLiteral("1628857765000 - 1628857765000") << QStringLiteral("1615973800000 - 1615988560000 - HitBuilding events (invalid ids in ex92)") << QStringLiteral("1616598140000 - 1616627300000 - HitBuilding events") << QStringLiteral("1616597700000 - 1616626860000 - 3x CameraVideo events") << QStringLiteral("1617182760000 - 1617191920000 - several CameraVideo events") << QStringLiteral("1614772900000 - 1614802060000 - several cross LOCAL events") << QStringLiteral("1615902200000 - 1615931360000 - several cross GLOBAL events") ); id->setOption(QInputDialog::InputDialogOption::NoButtons); id->show(); connect(id, &QInputDialog::textValueChanged, this, [this, id](const QString &val) { QStringList x(val.split(QStringLiteral(" - "))); if (x.count() < 2) return; bool k = false; qint64 s = x.at(0).toLongLong(&k); if (!k) return; qint64 e = x.at(1).toLongLong(&k); if (!k) return; m_ui->dteStart->setMsecsSinceEpoch(s); m_ui->dteEnd->setMsecsSinceEpoch(e); }); }); connect(m_ui->pbDebugAddFake, &QPushButton::clicked, this, &TimelineDialog::onDebugCreateFakeEvent); /* connect(m_ui->pbDebugToggleRowVis, &QPushButton::clicked, this, [this, proxyModel]() { proxyModel->toggleShowAllRows(); }); */ // we don't need them anymore: m_ui->pbDebugFakeTick->hide(); m_ui->pbDebugFakeTick2->hide(); m_ui->pbDebugAddFake->hide(); m_ui->timeline->hideDebugWidget(); #else m_ui->pbDebugFakeTick->hide(); m_ui->pbDebugFakeTick2->hide(); m_ui->pbDebugAddFake->hide(); m_ui->pbDebugToggleRowVis->hide(); m_ui->timeline->hideDebugWidget(); #endif hideProgress(); setLive(false); INFO_LOG(m_logger) << LOGFUNCTION << " FINISH"; } /////////////////////////////////////////////////////////////////////////////// // PUBLIC void TimelineDialog::onDebugCreateFakeEvent() { QFormLayout *fl = new QFormLayout; static int uniqueId = 999999; QSpinBox *idWidget = new QSpinBox; idWidget->setMaximum(INT_MAX); idWidget->setValue(uniqueId); fl->addRow(QStringLiteral("id"), idWidget); QPushButton *videoBtn = new QPushButton(QStringLiteral("video")); fl->addRow(QStringLiteral("video"), videoBtn); QPushButton *participantBtn = new QPushButton(QStringLiteral("participant")); fl->addRow(QStringLiteral("participant"), participantBtn); QLineEdit *typeIdWidget = new QLineEdit; fl->addRow(QStringLiteral("typeId"), typeIdWidget); QLineEdit *partWidget = new QLineEdit; fl->addRow(QStringLiteral("participantId"), partWidget); QLineEdit *partyWidget = new QLineEdit; fl->addRow(QStringLiteral("partyId"), partyWidget); QLineEdit *eqWidget = new QLineEdit; fl->addRow(QStringLiteral("eqId"), eqWidget); QDateTimeEdit *startWidget = new QDateTimeEdit; startWidget->setDisplayFormat(QStringLiteral("dd.MM.yyyy hh:mm:ss.zzz")); startWidget->setDateTime(QDateTime::currentDateTime()); fl->addRow(QStringLiteral("start"), startWidget); QPushButton *nowButton = new QPushButton(QStringLiteral("now")); connect(nowButton, &QPushButton::clicked, this, [startWidget]() { startWidget->setDateTime(QDateTime::currentDateTime()); }); fl->addRow(QStringLiteral("start"), nowButton); QTextEdit *te = new QTextEdit; fl->addRow(QStringLiteral("detail"), te); connect(videoBtn, &QPushButton::clicked, this, [=]() { typeIdWidget->setText(QStringLiteral("8")); partWidget->setText(QStringLiteral("0")); partyWidget->setText(QStringLiteral("0")); eqWidget->setText(QStringLiteral("0")); te->setText(QStringLiteral("{ \"MainStructureElementId\": 0, \"streamName\": \"test1\", \"fake\": true, \"duration\": -1 }")); }); auto initialState = [=]() { typeIdWidget->setText(QStringLiteral("1")); partWidget->setText(QStringLiteral("2")); partyWidget->setText(QStringLiteral("3")); eqWidget->setText(QStringLiteral("7725")); te->setText(QStringLiteral("{ \"fake\": true, \"duration\": 0 }")); }; initialState(); connect(participantBtn, &QPushButton::clicked, this, initialState); QPushButton *goBtn = new QPushButton(QStringLiteral("Go")); connect(goBtn, &QPushButton::clicked, this, [this, typeIdWidget, partWidget, partyWidget, eqWidget, startWidget, idWidget, te]() { auto intiMcInt = [this](const QString &value, const QString &msg) -> int { bool k = false; int n = value.toInt(&k); if (!k) { new LtsWidgets::LtsMessageDialogNew(this, msg); throw; } return n; }; int typeId; int participantId; int partyId; int eqId; try { typeId = intiMcInt(typeIdWidget->text(), QStringLiteral("typeId invalid")); participantId = intiMcInt(partWidget->text(), QStringLiteral("participantId invalid")); partyId = intiMcInt(partyWidget->text(), QStringLiteral("partyId invalid")); eqId = intiMcInt(eqWidget->text(), QStringLiteral("eqId invalid")); } catch(...){} uniqueId = idWidget->value(); idWidget->setValue(++uniqueId); qint64 start = startWidget->dateTime().toMSecsSinceEpoch(); QJsonParseError error; QJsonDocument::fromJson(te->toPlainText().toUtf8(), &error); 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; } m_model->onLiveEvent(ExerciseEventManagement::Event(uniqueId, start, ProjectDataManagement::EventType(static_cast(typeId)), participantId, partyId, eqId, te->toPlainText().toStdString(), {})); }); fl->addRow({}, goBtn); QDialog *d = new QDialog(this); d->setWindowTitle(QStringLiteral("add fake live event")); d->setLayout(fl); d->show(); d->activateWindow(); } /** * @brief * @details Set buttons/slider/DateTimeEdit to live/normal mode * @details Start/Stop timer to update the timeline * @param live */ void TimelineDialog::setLive(bool live) { if (live == m_isLive) return; m_isLive = live; m_model->setLive(live); if (m_isLive) { m_ui->dteStart->setEnabled(false); m_ui->dteEnd->setEnabled(false); QColor color = QGuiApplication::palette().color(QPalette::Link); QString style = QStringLiteral("QToolButton { background-color: rgb(%1,%2,%3); color: white }").arg( QString::number(color.red()), QString::number(color.green()), QString::number(color.blue()) ); m_ui->tbLive->setStyleSheet(style); m_ui->tbLive->setToolTip(tr("Currently live. Click to leave live mode.")); if (!m_liveTimer->isActive()) m_liveTimer->start(1000); m_ui->timeline->header()->setLiveIndicatorVisible(true); qint64 now = QDateTime::currentMSecsSinceEpoch() / 1000LL * 1000LL; // cut off msecs m_range.maxEndSecs = now / 1000LL; m_range.maxEndMSecs = m_range.maxEndSecs * 1000LL; m_ui->rsTimelineRange->setMaximum(m_range.maxEndSecs); m_ui->dteStart->setMaximumTimeMSec(m_range.maxEndMSecs - m_range.minDurationMSecs); m_ui->dteEnd->setMaximumTimeMSec(m_range.maxEndMSecs); m_ui->timeline->setStartTime(QDateTime::currentMSecsSinceEpoch() / 1000LL * 1000LL - m_ui->timeline->zoomFactor()); m_ui->timeline->setCurrentTime(m_ui->timeline->startTime()); m_ui->timeline->setDuration(ZoomFactor::SixtyMinutes); m_ui->timeline->setZoomFactor(ZoomFactor::SixtyMinutes); onLiveTimeout(); } else { m_ui->dteStart->setEnabled(true); m_ui->dteEnd->setEnabled(true); if (m_liveTimer->isActive()) m_liveTimer->stop(); QColor backgroundColor = QToolButton().palette().window().color(); QColor textColor = QGuiApplication::palette().color(QPalette::Text); QString style = QStringLiteral("QToolButton { background-color: rgb(%1,%2,%3); color: rgb(%4,%5,%6) }") .arg(backgroundColor.red()).arg(backgroundColor.green()).arg(backgroundColor.blue()) .arg(textColor.red()).arg(textColor.green()).arg(textColor.blue()); m_ui->tbLive->setStyleSheet(style); m_ui->tbLive->setToolTip(tr("Currently not live. Click to enter live mode.")); m_ui->timeline->header()->setLiveIndicatorVisible(false); } } LtsTimeline::TimelineView *TimelineDialog::view() { return m_ui->timeline->view(); } /////////////////////////////////////////////////////////////////////////////// // PRIVATE void TimelineDialog::retranslate() { translate(m_ui.get(), tr("Timeline")); } void TimelineDialog::aboutToClose() { if (m_replayApp) m_replayApp->softQuit(); } void TimelineDialog::resizeEvent(QResizeEvent *event) { LtsDialog::resizeEvent(event); int spacing = m_ui->hboxDateTime->spacing(); m_ui->wPlaceHolderStart->setFixedWidth(m_ui->rsTimelineRange->geometry().left() - 2 * spacing - m_ui->lbxStart->width()); resizeTimelineProgress(); } void TimelineDialog::paintEvent(QPaintEvent *event) { LtsDialog::paintEvent(event); int top = m_ui->wPlaceHolder->geometry().top(); int bottom = m_ui->wPlaceHolder->geometry().bottom(); int height = bottom - top; QRect rect = m_ui->rsTimelineRange->geometry(); QPainter painter(this); painter.setPen(Qt::lightGray); painter.drawLine(rect.left(), top - 10, rect.left(), bottom); // NOTE: -10 to draw the vertical line connected to the slider start painter.drawLine(rect.right(), top - 10, rect.right(), bottom); // NOTE: -10 to draw the vertical line connected to the slider end QString timeString = LtsTimeline::DateTime::toString(m_ui->dteStart->minimumTimeMSec(), LtsTimeline::DateTimeFormat::DTG); int width = painter.fontMetrics().horizontalAdvance(timeString); painter.drawText(QRect(rect.left() + 5, top, width, height), timeString); timeString = LtsTimeline::DateTime::toString(m_ui->dteEnd->maximumTimeMSec(), LtsTimeline::DateTimeFormat::DTG); width = painter.fontMetrics().horizontalAdvance(timeString); painter.drawText(QRect(rect.right() - width - 5, top, width, height), timeString); } void TimelineDialog::openItemInfoDialog(AbstractEventItem &item, bool scrollToPosition) { EventType eventType = item.type(); try { int equipmentNumber = -1; if (isParticipantEvent(eventType) || eventType == EventType::HitBuilding) { try { const AbstractParticipantEventItem &participantItem = dynamic_cast(item); equipmentNumber = participantItem.isEquipmentNumberSet() ? participantItem.equipmentNumber() : dynamic_cast(m_model->track(item.parentTrackId())).equipmentNumber(); } catch (const QString &errMsg) { ERROR_LOG(m_logger) << LOGFUNCTION << " obtaining equipment number failed: " << errMsg; } catch (...) { ERROR_LOG(m_logger) << LOGFUNCTION << " obtaining equipment number failed"; } } if (scrollToPosition) m_ui->timeline->setCurrentTime(item.startTime() - m_ui->timeline->zoomFactor() / 2); m_ui->timeline->header()->setIndicatorTime(item.startTime()); auto eventFactory = m_model->eventFactory(eventType); if (!eventFactory) return; ItemInfoDialog *dialog = new ItemInfoDialog; if (!dialog->init(eventFactory->copyItem(item), equipmentNumber, m_session, m_replayAppSession, m_avmIfc, m_mrkIfc, m_fltParticipantIfc, m_fltAesIfc, m_fltBuildingComponentIfc, m_session->app() == LtsAppDescriptor::App::Timeline, this)) throw QStringLiteral("dialog init failed event type %1").arg((int)eventType); connect(dialog, &ItemInfoDialog::startSituationReplayApp, this, &TimelineDialog::onStartReplay); connect(this, &TimelineDialog::situtionReplayAppStarted, dialog, &ItemInfoDialog::startSituationPlayback); QString uIdString; // fallback int uId = item.dbId() == -1 ? item.markerDbId() : item.dbId(); if (uId != -1) uIdString = QString::number(uId); else uIdString = QString::fromUtf8(QCryptographicHash::hash(QJsonDocument(item.properties()).toJson(), QCryptographicHash::Md5).toHex()); /*DEBUG*/ERROR_LOG(m_logger) << "uIdString = " << uIdString; addDialog(dialog, m_menuGroup, QStringLiteral("ItemInfoDialog_%1").arg(uIdString), LtsWidgets::LayoutArea::eInfoTab); } catch (const QString &str) { ERROR_LOG(m_logger) << LOGFUNCTION << " failed for type "<<(int)eventType<<": " << str; } } void TimelineDialog::openItemListDialog(const QVector &itemIds) { EventItemPtrs items; items.reserve(itemIds.size()); for (int itemId : qAsConst(itemIds)) { try { AbstractEventItem &item = static_cast(m_model->item(itemId)); if (item.type() == EventType::Block) { WARNING_LOG(m_logger) << "openItemListDialog does not handle block items"; continue; } items.push_back(m_model->eventFactory(item.type())->copyItem(item)); } catch (const QString &str) { ERROR_LOG(m_logger) << LOGFUNCTION << " " << str; } } if (!items.empty()) openItemListDialog(std::move(items)); } void TimelineDialog::openItemListDialog(EventItemPtrs &&items) { try { ItemListDialog *dialog = new ItemListDialog( m_model, m_menuGroup, m_session, m_ppmDtc, m_pdmDtc, m_ismDtc, m_flmIfc, m_opmDtc, m_avmDtc, m_mrkDtc, true, this ); dialog->init(std::move(items), false); connect(dialog, &ItemListDialog::showItemDetails, this, [this, dialog](int row) { AbstractEventItem *item = dialog->model()->items().at(row); openItemInfoDialog(*item); }); addDialog(dialog, m_menuGroup, {}, LtsWidgets::LayoutArea::eDialogTab); } catch (const QString &str) { ERROR_LOG(m_logger) << LOGFUNCTION << " " << str; } catch (...) { ERROR_LOG(m_logger) << LOGFUNCTION << " failed"; } } void TimelineDialog::setZoomForDuration(const qint64 &durationInMSecs) { if (durationInMSecs < static_cast(ZoomFactor::TenMinutes)) m_zoomFactor = ZoomFactor::FiveMinutes; else if (durationInMSecs < static_cast(ZoomFactor::FifteenMinutes)) m_zoomFactor = ZoomFactor::TenMinutes; else if (durationInMSecs < static_cast(ZoomFactor::ThirtyMinutes)) m_zoomFactor = ZoomFactor::FifteenMinutes; else if (durationInMSecs < static_cast(ZoomFactor::SixtyMinutes)) m_zoomFactor = ZoomFactor::ThirtyMinutes; else if (durationInMSecs < static_cast(ZoomFactor::TwoHours)) m_zoomFactor = ZoomFactor::SixtyMinutes; else if (durationInMSecs < static_cast(ZoomFactor::FourHours)) m_zoomFactor = ZoomFactor::TwoHours; else if (durationInMSecs < static_cast(ZoomFactor::EightHours)) m_zoomFactor = ZoomFactor::FourHours; else m_zoomFactor = ZoomFactor::EightHours; } /////////////////////////////////////////////////////////////////////////////// // PRIVATE SLOTS void TimelineDialog::onStartReplay() // blocks { std::unique_ptr _(new LtsWidgets::LtsWaitDialog(tr("Waiting for Situation Replay app"))); if (m_replayApp != nullptr) return; int replayId; try { replayId = m_srmIfc->replayPartitionCreateRequest( m_session->exerciseId(), LtsSessionData::LtsSessionData::getInstance()->getWorkstationId(), LtsSessionData::LtsSessionData::getInstance()->getOperatorId(), Core::ReplayType::SituationData); } catch(...) { LtsWidgets::LtsMessageDialog::showMessage(this, tr("Error trying to create a replay session.")); return; } bool found = false; Core::ReplayPartition rp; for (int i = 0; i < 40; i++) { rp = m_coreDtc->replayPartition(replayId); if (rp.id() == replayId) { found = true; break; } try{ LtsUtils::awaitTimeout(250); }catch(...){} } auto rmPartFn = [this, replayId]() { try { m_srmIfc->replayPartitionRemoveRequest(replayId, LtsSessionData::LtsSessionData::getInstance()->getWorkstationId(), LtsSessionData::LtsSessionData::getInstance()->getOperatorId()); // NOTE: unfortunately waits... } catch (...) { ERROR_LOG(m_logger) << "[timeout] Failed to remove failed-to-start replay. domainId: " << m_session->domainId() << " - replay id: " << replayId << " - url " << m_session->tcpConnectionUrl().toString(); } }; if (!found) { LtsWidgets::LtsMessageDialog::showMessage(this, tr("Error trying to create a replay session")); WARNING_LOG(m_logger) << "[timeout] Error while trying to create a replay session: domainId: " << m_session->domainId() << " - replay partition does not exist - replay id: " << replayId << " - url " << m_session->tcpConnectionUrl().toString(); rmPartFn(); return; } 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")); WARNING_LOG(m_logger) << "Error while trying to create a replay session: domainId: " << m_session->domainId() << " - partition create reply (failed) - replay id: " << replayId << " - url: " << m_session->tcpConnectionUrl().toString(); rmPartFn(); return; } 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")); rmPartFn(); return; } replayAppSession->presetBaseLayoutAsSecondaryWidget(); // see https://devhub.defence-elec.de/confluence/x/yLjeDg 13.1 // NOTE: no `irreplacableOnce` needed anymore as this is a secondary widget and will not replace this app if the same screen was selected // replayAppSession->setDependency(m_session); // prevents mmm to let that secondary widget be placed above this app => jetzt doch: GUEZE-2619 replayAppSession->setNeedsToBeSuccessfullyPlaced(); // so 'signalInitializationFinished' is only called after the window was placed auto fn = [this, replayAppSession]() { m_replayAppSession = replayAppSession; WARNING_LOG(m_logger) << "New Situation replay App started"; emit situtionReplayAppStarted(m_replayAppSession); }; if (replayAppSession->isInitializationDone()) fn(); else connect(replayAppSession, &LtsSession::LtsSession::signalDoneInitializingSession, this, fn); } /** * @brief * @details Show legend at the left side of the screen */ void TimelineDialog::onShowLegend() { addDialog(new TimelineLegend(this), m_menuGroup, QStringLiteral("timeline_legend"), LtsWidgets::LayoutArea::eInfoTab); } void TimelineDialog::onContextMenu(QPoint pos) { QMenu *contextMenu = new QMenu(this); contextMenu->setAttribute(Qt::WA_DeleteOnClose); { QAction *action = new QAction(tr("Expand All"), this); action->setEnabled(true); connect(action, &QAction::triggered, this, [this]() { m_ui->timeline->view()->expandAll(); }); contextMenu->addAction(action); } { QAction *action = new QAction(tr("Collapse All"), this); action->setEnabled(true); connect(action, &QAction::triggered, this, [this]() { m_ui->timeline->view()->collapseAll(); }); contextMenu->addAction(action); } contextMenu->addSeparator(); QModelIndex index = m_ui->timeline->view()->indexAt(pos); index = m_model->index(index.row(), LtsTimeline::Section::eActuator, index.parent()); bool hasChildren = false; if (index.isValid()) { try { hasChildren = m_model->track(index.data().toInt()).hasTracks(); } catch (...) {} } { QAction *action = new QAction(tr("Expand"), this); action->setEnabled(hasChildren); connect(action, &QAction::triggered, this, [this, index]() { #if QT_VERSION < QT_VERSION_CHECK(5,13,0) m_ui->timeline->view()->expand(index); #else m_ui->timeline->view()->expandRecursively(index); #endif }); contextMenu->addAction(action); } { QAction *action = new QAction(tr("Collapse"), this); action->setEnabled(hasChildren); connect(action, &QAction::triggered, this, [this, index]() { m_ui->timeline->view()->collapse(index); }); contextMenu->addAction(action); } pos.ry() += m_ui->timeline->view()->header()->height(); contextMenu->popup(m_ui->timeline->view()->mapToGlobal(pos)); } void TimelineDialog::onFilter() { FilterDialog *filterDialog = new FilterDialog(m_menuGroup, m_session, m_model->filters(), this); connect(filterDialog, &FilterDialog::editFilter, this, &TimelineDialog::onEditFilter); connect(filterDialog, &FilterDialog::showItemsForEvent, this, &TimelineDialog::onShowItemsForEvents); connect(filterDialog, &FilterDialog::showItemsForFilters, this, &TimelineDialog::onShowItemsForAllFilters); connect(filterDialog, &FilterDialog::filtersUpdated, m_model, &TimelineModel::onFilterChanged); connect(filterDialog, &FilterDialog::filtersUpdated, this, [this]() { if (!isLive()) return; onLive(); // turns live mode off, gets new events, ... onLive(); // turns live mode back on, events now filtered }); connect(this, &TimelineDialog::destroyed, filterDialog, &FilterDialog::deleteLater); addDialog(filterDialog, m_menuGroup, QStringLiteral("timeline_filter")); } void TimelineDialog::onEditFilter(EventType eventType) { AbstractEventFilter *filter = m_model->filter(eventType); if (!filter) return; FilterEditDialog *filterEditDialog = new FilterEditDialog(m_menuGroup, filter, m_model, this); connect(filterEditDialog, &FilterEditDialog::showItemsForFilter, this, [this](AbstractEventFilter *filter) { showItemListDialogByFilter(Filters() << filter); }); connect(filterEditDialog, &FilterEditDialog::applyRange, this, [this](QHBoxLayout *hbox) { LtsWidgets::LtsDateTimeEdit *dteStart = qobject_cast(hbox->itemAt(1)->widget()); LtsWidgets::LtsDateTimeEdit *dteEnd = qobject_cast(hbox->itemAt(3)->widget()); if (!dteStart || !dteEnd) { ERROR_LOG(m_logger) << "could not convert widgets to LtsDateTimeEdit for range start and end"; return; } dteStart->setMsecsSinceEpoch(m_ui->timeline->view()->startTime()); dteEnd->setMsecsSinceEpoch(m_ui->timeline->view()->endTime()); }); connect(this, &TimelineDialog::destroyed, filterEditDialog, &FilterEditDialog::deleteLater); addDialog(filterEditDialog, m_menuGroup, QStringLiteral("timeline_filter_edit_") + QString::number(eventType)); } void TimelineDialog::onShowItemsForAllFilters() { DEBUG_LOG(m_logger) << "onShowItemsForAllFilters"; EventTypes eventTypes; eventTypes.reserve(m_model->filters().size()); for (auto it = m_model->filters().constBegin(); it != m_model->filters().constEnd(); ++it) { if (it.value()->isActive()) { DEBUG_LOG(m_logger) << "onShowItemsForAllFilters: add active event type = " << it.key(); eventTypes.append(it.key()); } } onShowItemsForEvents(eventTypes); } void TimelineDialog::onShowItemsForEventWithUnsavedFilters(EventType eventType) { DEBUG_LOG(m_logger) << __FUNCTION__; std::unique_ptr filter(m_model->eventFactories().value(eventType)->filter()); // create temp filter! showItemListDialogByFilter(QVector() << filter.get()); } void TimelineDialog::showItemListDialogByFilter(const QVector &filters) { LtsWidgets::LtsGlobalWaitDialog::getInstance()->wait(tr("Fetching events ...")); try { EventItemPtrs items = m_model->getEventsByFilter(filters); if (items.size() > TimelineModel::s_maxRows) { new LtsWidgets::LtsMessageDialogNew( this, tr("There are more than %1 events for this request. Please consider configuring your filter settings.").arg(TimelineModel::s_maxRows) ); LtsWidgets::LtsGlobalWaitDialog::getInstance()->done(); return; } showItemListDialog(std::move(items)); } catch (const QString &str) { ERROR_LOG(m_logger) << str; } catch (...) { ERROR_LOG(m_logger) << LOGFUNCTION << " failed"; } LtsWidgets::LtsGlobalWaitDialog::getInstance()->done(); } void TimelineDialog::onShowItemsForEvents(EventTypes eventTypes) { DEBUG_LOG(m_logger) << "onShowItemsForFilters: " << eventTypes.size(); QVector filters; filters.reserve(eventTypes.size()); for (EventType eventType : qAsConst(eventTypes)) { DEBUG_LOG(m_logger) << "onShowItemsForFilters: EventType = " << QString::number(eventType); if (m_model->filters().contains(eventType)) { DEBUG_LOG(m_logger) << "onShowItemsForFilters: Add EventType = " << QString::number(eventType); filters.append(m_model->filter(eventType)); } } showItemListDialogByFilter(filters); } /** * @brief * @details Show ItemListDialog with list of events * @param items */ void TimelineDialog::showItemListDialog(EventItemPtrs items) { ItemListDialog *dialog = new ItemListDialog(m_model, m_menuGroup, m_session, m_ppmDtc, m_pdmDtc, m_ismDtc, m_flmIfc, m_opmDtc, m_avmDtc, m_mrkDtc, true, this); connect(dialog, &ItemListDialog::showItemDetails, this, [this, dialog](int row) { AbstractEventItem *item = dialog->model()->items().at(row); openItemInfoDialog(*item); }); dialog->init(std::move(items), false); addDialog(dialog, m_menuGroup, {}, LtsWidgets::LayoutArea::eDialogTab); } /** * @brief * @details Shows ItemInfoDialog for one specific event * @param itemId */ void TimelineDialog::onOpenItemInfoDialog(int itemId) { bool signalFromFilterEditDialog = (qobject_cast(sender()) != nullptr); if (signalFromFilterEditDialog) focusDialog(m_menuGroup, QStringLiteral("EventTimeline"), LtsWidgets::LayoutArea::eDialogTab); try { openItemInfoDialog(static_cast(m_model->item(itemId)), signalFromFilterEditDialog); } catch (const QString &str) { ERROR_LOG(m_logger) << str; } } void TimelineDialog::onTrackActivated(int trackId) { TrackType trackType = m_model->trackType(trackId); switch (trackType) { case TrackType::BuildingStream: onEditFilter(EventType::CameraVideo); return; case TrackType::BuildingHits: onEditFilter(EventType::HitBuilding); return; case TrackType::Marker: onEditFilter(EventType::Marker); return; case TrackType::CnrRoot: onEditFilter(EventType::RadioCnr); return; case TrackType::TvcRoot: onEditFilter(EventType::RadioTvc); return; default: return; } } void TimelineDialog::addItemToMap(int itemId, QMap &map) noexcept(false) { // cannot show more than TimelineModel::s_maxRows items if (map.size() > TimelineModel::s_maxRows) { new LtsWidgets::LtsMessageDialogNew(this, tr("More than %1 events have been selected. Please change your selection.").arg(TimelineModel::s_maxRows), tr("Cannot open list of events")); throw QStringLiteral("too many events"); } try { const AbstractEventItem &item = static_cast(m_model->item(itemId)); // throws QString hashHex( QString::fromUtf8( QCryptographicHash::hash( QStringLiteral("[%1][%2][%3][%4]") .arg(item.dbId()) .arg(item.startTime()) .arg(QString::fromUtf8(QJsonDocument(item.properties()).toJson(QJsonDocument::JsonFormat::Compact))) .arg(item.markerDbId()).toUtf8() , QCryptographicHash::Algorithm::Sha1).toHex())); // super expensive ... if (map.contains(hashHex)) // already in list? don't ass this one return; // continue; map.insert(hashHex, itemId); } catch (const QString &msg) { ERROR_LOG(m_logger) << "cannot find item for item id" << itemId << ": " << msg; } } void TimelineDialog::onItemsSelected(const QVector &itemIds) { if (itemIds.isEmpty()) return; // remove duplicates, as channel summing and rubberbands can select one item multiple times /* well, as AVS stuff is now received from AVS directly, we have NO db ids * for these events. so to distinguish items from another, we have to given * them unique identifiers by doing this hashing stuff ... :( */ QMap uniqueItems; //uniqueItems.reserve(itemIds.size()); qCritical() << "PPPPPP itemSize" << itemIds.size(); EventItemPtrs prts; for (int itemId : itemIds) { try { addItemToMap(itemId, uniqueItems); } catch (...) { return; } } QMap uniqueItemsClean; // an item list WITHOUT ANY BLOCK ITEMS // only one item selected? open item info dialog directly const AbstractEventItem &Uniitem = static_cast(m_model->item(uniqueItems.first())); if (uniqueItems.size() == 1 && Uniitem.type() != EventType::Block) { qCritical() << "PPPPPP NO BLOCK"; try { openItemInfoDialog(static_cast(m_model->item(uniqueItems.first()))); } catch (const QString &errMsg) { ERROR_LOG(m_logger) << LOGFUNCTION << " failed: " << errMsg; } return; }else if ( Uniitem.type() == EventType::Block) { const BlockItem &blItem = static_cast(m_model->item(uniqueItems.first())); qCritical() << "PPPPPP inner itemSize" << blItem.items().size(); prts.reserve(blItem.items().size()); for (const ItemPtr &itemPtr : qAsConst(static_cast(blItem).items())) { const AbstractEventItem &internalItem = static_cast(*itemPtr.get()); prts.push_back(m_model->eventFactory(internalItem.type())->copyItem(internalItem)); qCritical() << "PPPPPP inner itemSize" << internalItem.id(); qCritical() << "PPPPPP inner itemSize" << internalItem.properties(); try { //addItemToMap(internalItem.id(), uniqueItemsClean); } catch (...) { return; } } } // multiple items selected // first get all block items that were also selected to get the contained item ids from them //uniqueItemsClean.reserve(uniqueItems.size()); //for (int itemId : qAsConst(uniqueItems)) QMutableMapIterator it1(uniqueItems); while (it1.hasNext()) { const auto pair = it1.next(); const QString &hashHex = pair.key(); const int itemId = pair.value(); try { const AbstractEventItem &item = static_cast(m_model->item(itemId)); // single item if (item.type() != EventType::Block) { uniqueItemsClean.insert(hashHex, itemId); continue; } // a block item! for (const ItemPtr &itemPtr : qAsConst(static_cast(item).items())) { const AbstractEventItem &internalItem = static_cast(*itemPtr.get()); try { addItemToMap(internalItem.id(), uniqueItemsClean); } catch (...) { return; } } } catch (const QString &errMsg) { ERROR_LOG(m_logger) << LOGFUNCTION << " failed for item with id = " << itemId << ": " << errMsg; } } // NOW the list is clean of any block items or duplicate items - now copy all items! NOTE: block items never contain more block items. only single items. EventItemPtrs items; items.reserve(uniqueItemsClean.size()); QMutableMapIterator it2(uniqueItemsClean); while (it2.hasNext()) { const auto pair = it2.next(); //const QString &hashHex = pair.key(); const int itemId = pair.value(); try { const AbstractEventItem &item = static_cast(m_model->item(itemId)); items.push_back(m_model->eventFactory(item.type())->copyItem(item)); } catch (const QString &errMsg) { ERROR_LOG(m_logger) << LOGFUNCTION << " failed for x item with id = " << itemId << ": " << errMsg; } } // show list dialog with all the copied items ItemListDialog *dialog = new ItemListDialog( m_model, m_menuGroup, m_session, m_ppmDtc, m_pdmDtc, m_ismDtc, m_flmIfc, m_opmDtc, m_avmDtc, m_mrkDtc, true, this ); if (prts.size()) dialog->init(std::move(prts), false); else dialog->init(std::move(items), false); connect(dialog, &ItemListDialog::showItemDetails, this, [this, dialog](int row) { AbstractEventItem *item = dialog->model()->items().at(row); openItemInfoDialog(*item); // NOTE: dialog copies all data on c_tor, then never accesses ptr again }); addDialog(dialog, m_menuGroup, {}, LtsWidgets::LayoutArea::eDialogTab); } /** * @brief * @details Triggered if the Live Mode Button is clicked */ void TimelineDialog::onLive() { m_ui->tbLive->clearFocus(); setLive(!m_isLive); m_ui->dteStart->setEnabled(!m_isLive); m_ui->dteEnd->setEnabled(!m_isLive); m_ui->rsTimelineRange->setEnabled(!m_isLive); m_model->queueUpdate(); } void TimelineDialog::onLiveTimeout() { if (!m_isLive) { if (m_liveTimer->isActive()) m_liveTimer->stop(); return; } qint64 now = QDateTime::currentMSecsSinceEpoch() / 1000LL * 1000LL; // cut off msecs m_range.maxEndSecs = (now + m_ui->timeline->zoomFactor() / 100LL) / 1000LL; m_range.maxEndMSecs = m_range.maxEndSecs * 1000LL; m_ui->rsTimelineRange->setMaximum(m_range.maxEndSecs); m_ui->dteStart->setMaximumTimeMSec(m_range.maxEndMSecs - m_range.minDurationMSecs); m_ui->dteEnd->setMaximumTimeMSec(m_range.maxEndMSecs); if (now - m_ui->timeline->startTime() > m_range.maxDurationMSecs) m_ui->rsTimelineRange->setPositions(m_range.maxEndSecs - m_range.minDurationSecs, m_range.maxEndSecs); else m_ui->rsTimelineRange->setPositions(m_ui->timeline->startTime() / 1000LL, m_range.maxEndSecs); m_ui->timeline->setEndTime(m_range.maxEndMSecs); m_ui->timeline->setCurrentTime(m_range.maxEndMSecs - m_ui->timeline->zoomFactor()); m_ui->timeline->header()->setLiveIndicatorTime(now); QHash factories = m_model->eventFactories(); for (auto it = factories.begin(); it != factories.end(); ++it) it.value()->setMaximumRangeEnd(m_range.maxEndMSecs); update(); } /** * @brief * @details Triggered if one or both of the slider handle are used or if the user click between the slider handles * @param rangeStart * @param rangeEnd */ void TimelineDialog::onRangeEdited(int rangeStart, int rangeEnd) { if (!m_ui->rsTimelineRange->isMinimumSliderDown() || !m_ui->rsTimelineRange->isMaximumSliderDown()) return; m_ui->dteStart->setMsecsSinceEpoch(static_cast(rangeStart) * 1000LL); m_ui->dteEnd->setMsecsSinceEpoch(static_cast(rangeEnd) * 1000LL); m_ui->timeline->view()->setRange(m_ui->dteStart->msecsSinceEpoch(), m_ui->dteEnd->msecsSinceEpoch()); m_ui->timeline->onUpdateZoomButtons(); // TODO: ensure that it will be called implicitly } /** * @brief * @details Triggered if the lower/start/first slider handle is used * @details Dragging one Slider Handle should not move the other one * @details Not used by DateTimeEdit * @param rangeStart */ void TimelineDialog::onRangeStartEdited(int rangeStart) { if (m_ui->rsTimelineRange->isMinimumSliderDown() && m_ui->rsTimelineRange->isMaximumSliderDown()) return; int rangeEnd = m_ui->rsTimelineRange->maximumPosition(); if (rangeEnd - rangeStart > m_range.maxDurationSecs) { m_ui->rsTimelineRange->setMinimumPosition(rangeEnd - m_range.maxDurationSecs); m_ui->timeline->onUpdateZoomButtons(); return; } if (rangeEnd - rangeStart < m_range.minDurationSecs) { m_ui->rsTimelineRange->setMinimumPosition(rangeEnd - m_range.minDurationSecs); m_ui->timeline->onUpdateZoomButtons(); return; } qint64 start = static_cast(rangeStart) * 1000LL; if (m_ui->timeline->startTime() != start) { qint64 end = m_ui->timeline->endTime(); m_ui->timeline->setStartTime(start); m_ui->timeline->setEndTime(end); m_ui->timeline->onUpdateZoomButtons(); // TODO: ensure that it will be called implicitly } if (m_ui->dteStart->msecsSinceEpoch() != start) m_ui->dteStart->setMsecsSinceEpoch(start); } /** * @brief * @details Triggered if the higher/end/last slider handle is used * @details Dragging one Slider Handle should not move the other one * @details Not used by DateTimeEdit * @param rangeEnd */ void TimelineDialog::onRangeEndEdited(int rangeEnd) { if (m_ui->rsTimelineRange->isMinimumSliderDown() && m_ui->rsTimelineRange->isMaximumSliderDown()) return; int rangeStart = m_ui->rsTimelineRange->minimumPosition(); if (rangeEnd - rangeStart > m_range.maxDurationSecs) { m_ui->rsTimelineRange->setMaximumPosition(rangeStart + m_range.maxDurationSecs); m_ui->timeline->onUpdateZoomButtons(); return; } if (rangeEnd - rangeStart < m_range.minDurationSecs) { m_ui->rsTimelineRange->setMaximumPosition(rangeStart + m_range.minDurationSecs); m_ui->timeline->onUpdateZoomButtons(); return; } qint64 end = static_cast(rangeEnd) * 1000LL; if (m_ui->timeline->endTime() != end) { m_ui->timeline->setEndTime(end); m_ui->timeline->onUpdateZoomButtons(); // TODO: ensure that it will be called implicitly } if (m_ui->dteEnd->msecsSinceEpoch() != end) m_ui->dteEnd->setMsecsSinceEpoch(end); } /** * @brief * @details Triggered if the LtsDateTimeEdit for the start time was edited * @details Not used by the Slider */ void TimelineDialog::onStartEdited() { ZoomFactor lastZoom = m_ui->timeline->zoomFactor(); int start = m_ui->dteStart->msecsSinceEpoch() / 1000LL; int end = m_ui->dteEnd->msecsSinceEpoch() / 1000LL; if (start < m_range.minStartSecs) return; if (start > end || end - start < m_range.minDurationSecs) end = std::min(m_range.maxEndSecs, start + m_ui->timeline->duration() / 1000); if (end - start > m_range.maxDurationSecs) end = std::min(m_range.maxEndSecs, start + m_range.maxDurationSecs); if (m_ui->rsTimelineRange->minimumPosition() != start || m_ui->rsTimelineRange->maximumPosition() != end) m_ui->rsTimelineRange->setPositions(start, end); if (m_ui->timeline->startTime() != m_ui->dteStart->msecsSinceEpoch()) { m_ui->timeline->setStartTime(m_ui->dteStart->msecsSinceEpoch()); m_ui->timeline->onUpdateZoomButtons(); // TODO: ensure that it will be called implicitly } m_ui->timeline->setZoomFactor(lastZoom); } /** * @brief * @details Triggered if the LtsDateTimeEdit for the end time was edited * @details Not used by the slider */ void TimelineDialog::onEndEdited() { ZoomFactor lastZoom = m_ui->timeline->zoomFactor(); int start = m_ui->dteStart->msecsSinceEpoch() / 1000LL; int end = m_ui->dteEnd->msecsSinceEpoch() / 1000LL; if (end > m_range.maxEndSecs) return; if (end < start || end - start < m_range.minDurationSecs) start = std::max(m_range.minStartSecs, end - m_ui->timeline->duration() / 1000); if (end - start > m_range.maxDurationSecs) start = std::max(m_range.minStartSecs, end - m_range.maxDurationSecs); if (m_ui->rsTimelineRange->minimumPosition() != start || m_ui->rsTimelineRange->maximumPosition() != end) m_ui->rsTimelineRange->setPositions(start, end); if (m_ui->timeline->endTime() != m_ui->dteEnd->msecsSinceEpoch()) { m_ui->timeline->setEndTime(m_ui->dteEnd->msecsSinceEpoch()); m_ui->timeline->onUpdateZoomButtons(); // TODO: ensure that it will be called implicitly } m_ui->timeline->setZoomFactor(lastZoom); } /** * @brief * @details Shows ProgressBar */ void TimelineDialog::showProgress() { m_timeline_progress->setVisible(true); } void TimelineDialog::hideProgress() { m_timeline_progress->setVisible(false); } void TimelineDialog::modelUpdateStatusChanged(bool running) { if (!m_timeline_progress) return; if (running) showProgress(); else { hideProgress(); if (m_pModel) m_pModel->invalidate(); } } void TimelineDialog::resizeTimelineProgress() { m_timeline_progress->setGeometry(this->geometry()); int height = m_ui->timeline->geometry().top() + m_ui->timeline->height() * 0.66; m_timeline_progress->setHeightOfProgressIndicator(height); } } // namespace Timeline