1482 lines
55 KiB
C++
Executable File
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
|