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

1482 lines
55 KiB
C++
Executable File

#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 <ExerciseManagement/exm_data_plugin_interface.h>
#include <ExerciseEventManagement/eem.hpp>
#include <LtsAppManager/lts_app_manager.h>
#include <LtsSession/lts_session.h>
#include <LtsSession/lts_session_data.h>
#include <LtsTimeline/timeline_header_view.h>
#include <LtsTimeline/timeline_utils.h>
#include <LtsTimeline/timeline_view.h>
#include <LtsUtilsCore/await.h>
#include <LtsUtilsSettings/settings.h>
#include <LtsWidgets/lts_global_wait_dialog.h>
#include <LtsWidgets/lts_wait_dialog.h>
#include <LtsWidgets/lts_overlay_loading.h>
// Debug Window Includes
#include <QFormLayout>
#include <QSpinBox>
#include <QLineEdit>
#include <QTextEdit>
#include <QPushButton>
// end of Debug Window Includes
#include <QJsonDocument>
#include <QJsonObject>
#include <QCryptographicHash>
#include <QInputDialog>
#include <QLabel>
#include <QMenu>
#include <QThread>
#include <QTimer>
namespace Timeline
{
TimelineDialog::TimelineDialog(
LtsSession::LtsSession *session, const QString &menuGroup, bool isMain,
const std::shared_ptr<Core::Interface::Dtc> &coreDtc, const std::shared_ptr<AudioVideoManagement::Interface::Dtc> &avmDtc,
const std::shared_ptr<InfrastructureManagement::Interface::Dtc> &ismDtc, const std::shared_ptr<OperatorManagement::Interface::Dtc> &opmDtc,
const std::shared_ptr<ProjectDataManagement::Interface::Dtc> &pdmDtc,
const std::shared_ptr<ParticipantManagement::Interface::Dtc> &ppmDtc,
const std::shared_ptr<AudioVideoManagement::ForeignMenuEditGuiInterface> &avmIfc,
const std::shared_ptr<AudioVideoManagement::ForeignDataInterface> &avmDataIfc,
const std::shared_ptr<FileManagement::ForeignGuiInterface> &flmIfc, const std::shared_ptr<FilterManagement::ForeignFltParticipantGuiInterface> &fltParticipantIfc,
const std::shared_ptr<FilterManagement::ForeignAesGuiInterface> &fltAesIfc,
const std::shared_ptr<FilterManagement::ForeignFltBuildingComponentGuiInterface> &fltBuildingComponentIfc,
const std::shared_ptr<MarkerManagement::ForeignGuiInterface> &mrkIfc,
const std::shared_ptr<SituationReplayManagement::ForeignDataInterface> &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<ExerciseManagement::Interface::Dtc> exmDtc;
if (!m_session->getDtc<ExerciseManagement::DataInterface>(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<qint64>(m_range.maxEndSecs) * 1000LL; // cut off msecs
}
else
{
m_range.maxEndSecs = QDateTime::currentSecsSinceEpoch();
m_range.maxEndMSecs = static_cast<qint64>(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<CameraVideoFilter *>(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<FireEventFilter *>(m_model->filter(EventType::FireEvent))->init(m_pdmDtc))
WARNING_LOG(m_logger) << "initializing FireEventFilter failed";
if (!static_cast<HitFilter *>(m_model->filter(EventType::Hit))->init(m_session, m_pdmDtc, m_ppmDtc))
WARNING_LOG(m_logger) << "initializing HitFilter failed";
if (!static_cast<HitBuildingFilter *>(m_model->filter(EventType::HitBuilding))->init(m_session, m_ismDtc, m_pdmDtc, m_ppmDtc))
WARNING_LOG(m_logger) << "initializing HitBuildingFilter failed";
if (!static_cast<HitEffectSimulationFilter *>(m_model->filter(EventType::HitEffectSimulation))->init(m_pdmDtc))
WARNING_LOG(m_logger) <<"initializing HitEffectSimulationFilter failed";
if (!static_cast<MarkerFilter *>(m_model->filter(EventType::Marker))->init(m_opmDtc))
WARNING_LOG(m_logger) << "initializing MarkerFilter failed";
if (!static_cast<RadioCnrFilter *>(m_model->filter(EventType::RadioCnr))->init(m_avmDtc, exc.id(), m_model))
WARNING_LOG(m_logger) << "initializing RadioCnrFilter failed";
if (!static_cast<RadioTvcFilter *>(m_model->filter(EventType::RadioTvc))->init(m_avmDtc, exc.id(), m_model))
WARNING_LOG(m_logger) << "initializing RadioTvcFilter failed";
if (!static_cast<WeaponAssignmentFilter *>(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<CameraVideoFilter *>(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<FireEventFilter *>(m_model->tempFilter(EventType::FireEvent))->init(m_pdmDtc))
WARNING_LOG(m_logger) << "initializing FireEventFilter failed";
if (!static_cast<HitFilter *>(m_model->tempFilter(EventType::Hit))->init(m_session, m_pdmDtc, m_ppmDtc))
WARNING_LOG(m_logger) << "initializing HitFilter failed";
if (!static_cast<HitBuildingFilter *>(m_model->tempFilter(EventType::HitBuilding))->init(m_session, m_ismDtc, m_pdmDtc, m_ppmDtc))
WARNING_LOG(m_logger) << "initializing HitBuildingFilter failed";
if (!static_cast<HitEffectSimulationFilter *>(m_model->tempFilter(EventType::HitEffectSimulation))->init(m_pdmDtc))
WARNING_LOG(m_logger) <<"initializing HitEffectSimulationFilter failed";
if (!static_cast<MarkerFilter *>(m_model->tempFilter(EventType::Marker))->init(m_opmDtc))
WARNING_LOG(m_logger) << "initializing MarkerFilter failed";
if (!static_cast<RadioCnrFilter *>(m_model->tempFilter(EventType::RadioCnr))->init(m_avmDtc, exc.id(), m_model))
WARNING_LOG(m_logger) << "initializing RadioCnrFilter failed";
if (!static_cast<RadioTvcFilter *>(m_model->tempFilter(EventType::RadioTvc))->init(m_avmDtc, exc.id(), m_model))
WARNING_LOG(m_logger) << "initializing RadioTvcFilter failed";
if (!static_cast<WeaponAssignmentFilter *>(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<ProjectDataManagement::EventType::inner_enum>(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<const AbstractParticipantEventItem &>(item);
equipmentNumber = participantItem.isEquipmentNumberSet() ? participantItem.equipmentNumber()
: dynamic_cast<const ParticipantTrack &>(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<int> &itemIds)
{
EventItemPtrs items;
items.reserve(itemIds.size());
for (int itemId : qAsConst(itemIds))
{
try
{
AbstractEventItem &item = static_cast<AbstractEventItem &>(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<qint64>(ZoomFactor::TenMinutes))
m_zoomFactor = ZoomFactor::FiveMinutes;
else if (durationInMSecs < static_cast<qint64>(ZoomFactor::FifteenMinutes))
m_zoomFactor = ZoomFactor::TenMinutes;
else if (durationInMSecs < static_cast<qint64>(ZoomFactor::ThirtyMinutes))
m_zoomFactor = ZoomFactor::FifteenMinutes;
else if (durationInMSecs < static_cast<qint64>(ZoomFactor::SixtyMinutes))
m_zoomFactor = ZoomFactor::ThirtyMinutes;
else if (durationInMSecs < static_cast<qint64>(ZoomFactor::TwoHours))
m_zoomFactor = ZoomFactor::SixtyMinutes;
else if (durationInMSecs < static_cast<qint64>(ZoomFactor::FourHours))
m_zoomFactor = ZoomFactor::TwoHours;
else if (durationInMSecs < static_cast<qint64>(ZoomFactor::EightHours))
m_zoomFactor = ZoomFactor::FourHours;
else
m_zoomFactor = ZoomFactor::EightHours;
}
///////////////////////////////////////////////////////////////////////////////
// PRIVATE SLOTS
void TimelineDialog::onStartReplay() // blocks
{
std::unique_ptr<LtsWidgets::LtsWaitDialog> _(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<LtsWidgets::LtsDateTimeEdit *>(hbox->itemAt(1)->widget());
LtsWidgets::LtsDateTimeEdit *dteEnd = qobject_cast<LtsWidgets::LtsDateTimeEdit *>(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<AbstractEventFilter> filter(m_model->eventFactories().value(eventType)->filter()); // create temp filter!
showItemListDialogByFilter(QVector<AbstractEventFilter *>() << filter.get());
}
void TimelineDialog::showItemListDialogByFilter(const QVector<AbstractEventFilter *> &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<AbstractEventFilter *> 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<FilterEditDialog *>(sender()) != nullptr);
if (signalFromFilterEditDialog)
focusDialog(m_menuGroup, QStringLiteral("EventTimeline"), LtsWidgets::LayoutArea::eDialogTab);
try
{
openItemInfoDialog(static_cast<EventItem &>(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<QString, int> &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<const AbstractEventItem &>(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<int> &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<QString /* hash */, int /* item id */> uniqueItems;
//uniqueItems.reserve(itemIds.size());
qCritical() << "PPPPPP itemSize" << itemIds.size();
EventItemPtrs prts;
for (int itemId : itemIds)
{
try
{
addItemToMap(itemId, uniqueItems);
}
catch (...) { return; }
}
QMap<QString /* hash */, int /* item id */> uniqueItemsClean; // an item list WITHOUT ANY BLOCK ITEMS
// only one item selected? open item info dialog directly
const AbstractEventItem &Uniitem = static_cast<const AbstractEventItem &>(m_model->item(uniqueItems.first()));
if (uniqueItems.size() == 1 && Uniitem.type() != EventType::Block)
{
qCritical() << "PPPPPP NO BLOCK";
try
{
openItemInfoDialog(static_cast<AbstractEventItem &>(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<const BlockItem &>(m_model->item(uniqueItems.first()));
qCritical() << "PPPPPP inner itemSize" << blItem.items().size();
prts.reserve(blItem.items().size());
for (const ItemPtr &itemPtr : qAsConst(static_cast<const BlockItem &>(blItem).items()))
{
const AbstractEventItem &internalItem = static_cast<const AbstractEventItem &>(*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<QString, int> 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<const AbstractEventItem &>(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<const BlockItem &>(item).items()))
{
const AbstractEventItem &internalItem = static_cast<const AbstractEventItem &>(*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<QString, int> 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<const AbstractEventItem &>(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<EventType, AbstractEventFactory *> 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<qint64>(rangeStart) * 1000LL);
m_ui->dteEnd->setMsecsSinceEpoch(static_cast<qint64>(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<qint64>(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<qint64>(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