/** * @RDE_HEADER@ * Handle situation display in thp * $Id$ * $HeadURL$ * $Author$ */ #include "thp_situation_building_base.h" #include "ui_thp_situation_building_base.h" #include "thp_situation_filter_aes.h" #include "thp_situation_filter_symbol_appearance.h" #include "thp_situation_filter_collapse_expand.h" #include "thp_situation_filter_types.h" #include "thp_plugin_provider.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace THP { using namespace SituationReplay; SituationBuildingBase::SituationBuildingBase(const char *loggerName, QWidget *parent) : QFrame(parent) , m_playTimer(new QTimer(this)) , m_ui(new Ui::SituationBuildingBase) , m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger(loggerName)) { m_ui->setupUi(this); m_ui->zoomSlider->hide(); m_ui->sldPosition->setMinimum(0); m_ui->sldPosition->setMaximum(s_sliderMax); m_playTimer->setSingleShot(true); connect(m_playTimer, &QTimer::timeout, this, &SituationBuildingBase::onPlay); connect(m_ui->btnSlower, &QToolButton::clicked, this, &SituationBuildingBase::onSlower); connect(m_ui->btnFaster, &QToolButton::clicked, this, &SituationBuildingBase::onFaster); connect(m_ui->btn100pct, &QToolButton::clicked, this, &SituationBuildingBase::on100pct); connect(m_ui->btnPlay, &QToolButton::clicked, this, &SituationBuildingBase::onPlay); connect(m_ui->btnStop, &QToolButton::clicked, this, &SituationBuildingBase::onStop); connect(m_ui->sldPosition, &QSlider::sliderPressed, this, &SituationBuildingBase::onSliderPressed); connect(m_ui->sldPosition, &QSlider::sliderMoved, this, &SituationBuildingBase::onSliderPositionChanged); connect(m_ui->sldPosition, &QSlider::sliderReleased, this, &SituationBuildingBase::onSliderReleased); connect(m_ui->cbPlaybackParts, QOverload::of(&QComboBox::currentIndexChanged), this, &SituationBuildingBase::onCbPlaybackPartsChanged); } SituationBuildingBase::~SituationBuildingBase() { delete m_ui; } bool SituationBuildingBase::init(SituationReplay::MainHandlerSqlite *replayHandler) { if (m_replayHandler) m_replayHandler->disconnect(this); m_replayHandler = replayHandler; connect(m_replayHandler, &SituationReplay::MainHandlerSqlite::initialReplaySpeed, this, &SituationBuildingBase::setSpeed); connect(m_replayHandler, &SituationReplay::MainHandlerSqlite::applyCurentFilter, this, &SituationBuildingBase::applyCurentFilter); connect(m_replayHandler, &SituationReplay::MainHandlerSqlite::applyNewOverlay, this, &SituationBuildingBase::applyNewOverlay); connect(m_replayHandler, &SituationReplay::MainHandlerSqlite::removeOverlay, this, &SituationBuildingBase::removeOverlay); connect(m_replayHandler, &SituationReplay::MainHandlerSqlite::fileContentAvailable, this, &SituationBuildingBase::onFileContentAvailable); updatePositionLabel(LtsUtils::QPoint3D(), false); updateSpeedLabel(); return true; } bool SituationBuildingBase::showSituation(const QString &sitReplayDescription, const QString &basePath) { // read ReplayInformation from sitmap.json (key 'replays') QFile f(sitReplayDescription); // ...Exercise_123//n/m/sitmap.json if (!f.open(QIODevice::ReadOnly)) { WARNING_LOG(m_logger) << "Can not open file '" << sitReplayDescription << "': " << f.errorString(); return false; } QFileInfo fi(f); m_scriptPath = fi.absolutePath(); QJsonParseError jError; const QByteArray ba = f.readAll(); QJsonDocument doc(QJsonDocument::fromJson(ba, &jError)); if (jError.error) { WARNING_LOG(m_logger) << "Can not parse json document: " << jError.errorString(); return false; } const QJsonObject rootObj = doc.object(); const QJsonArray arrReplays(rootObj[QStringLiteral("replays")].toArray()); QVector replayInformation; replayInformation.reserve(arrReplays.size()); for (const auto &val : arrReplays) { const QJsonObject obj = val.toObject(); const QJsonObject objPb = obj[QStringLiteral("playbackTime")].toObject(); const QJsonObject objSqlite = obj[QStringLiteral("replayInformation")].toObject(); const QJsonObject objTip = objSqlite[QStringLiteral("timeInPresentation")].toObject(); ReplayInformation info; info.id = obj[QStringLiteral("id")].toInt(); info.timeSpan = TimeSpan::fromJson(objPb); info.relativeTimeSpan = TimeSpan::fromJson(objTip); const int sqliteFolderId = objSqlite[QStringLiteral("id")].toInt(); info.databaseFilePath = QStringLiteral("%1/%2/sitreplay.sqlite").arg(basePath).arg(sqliteFolderId); info.replayConfigPath = QStringLiteral("%1/%2/sitreplay.json").arg(basePath).arg(sqliteFolderId); if (info.relativeTimeSpan.begin < 0 || info.relativeTimeSpan.end <= info.relativeTimeSpan.begin || sqliteFolderId == 0) { WARNING_LOG(m_logger) << "Can not parse json document: " << QJsonDocument(obj).toJson(); return false; } replayInformation.push_back(std::move(info)); } const QJsonObject attributes(rootObj[QStringLiteral("attributes")].toObject()); const QJsonArray arrFilters1(attributes[QStringLiteral("Filters")].toArray()); QVector filterInformation; QVector overlayInformation; for (const auto &arrVal : arrFilters1) { // const QJsonArray arrFilters2(arrVal.toArray()); // for (const auto &val : arrFilters2) // { FilterInformation filter; const QJsonObject obj = arrVal.toObject(); LtsScriptTimeline::TrackEntry te; te.fromJson(obj); if (te.m_topicId <= 0) { WARNING_LOG(m_logger) << "Can not load filter information from '" << QJsonDocument(obj).toJson() << "'"; continue; } if (te.m_type != LtsScriptTimeline::TrackEntryType::SituationMapFilter && te.m_type != LtsScriptTimeline::TrackEntryType::TacticalPlan) { DEBUG_LOG(m_logger) << "Unknown Filter type " << te.m_type << " - ignoring: " << QJsonDocument(obj).toJson() << "'"; continue; } filter.id = te.m_topicId; filter.relativeTimeSpan.begin = te.m_startTime * 1000LL; filter.relativeTimeSpan.end = te.m_length * 1000LL + filter.relativeTimeSpan.begin; const QJsonObject details = obj[QStringLiteral("detailData")].toObject(); if (te.m_type == LtsScriptTimeline::TrackEntryType::SituationMapFilter) { const QJsonObject filters = te.m_detailData[QStringLiteral("filters")].toObject(); filter.aesFilters = filters[QStringLiteral("AesFilterJson")].toObject(); filter.symbolSettings = filters[QStringLiteral("SymbolAppearanceJson")].toObject(); filter.buildingComponentSettings = filters[QStringLiteral("BuildingComponentSettingsJson")].toObject(); filter.mapSettings = details[QStringLiteral("settings")].toObject(); if (filter.relativeTimeSpan.begin < 0 || filter.aesFilters.isEmpty() || filter.symbolSettings.isEmpty()) { WARNING_LOG(m_logger) << "Can not parse json document - can not read filter data " << QJsonDocument(obj).toJson(); return false; } if (filter.buildingComponentSettings.isEmpty()) { WARNING_LOG(m_logger) << "Can not parse json document - can not read filter data 'BuildingComponentSettingsJson', json= " << QJsonDocument(obj).toJson(); // return false; // support older thps with this thpViewer } filterInformation.push_back(std::move(filter)); } else if (te.m_type == LtsScriptTimeline::TrackEntryType::TacticalPlan) { if (filter.relativeTimeSpan.begin < 0) { WARNING_LOG(m_logger) << "Can not parse json document - can not read filter data " << QJsonDocument(obj).toJson(); return false; } // old version (before 2020/02) filter.overlayId = details[QStringLiteral("FileId")].toInt(-1); if (filter.overlayId >= 0) { DEBUG_LOG(m_logger) << "Add overlay with id " << filter.overlayId; overlayInformation.push_back(std::move(filter)); continue; } const QJsonArray arr = details[QStringLiteral("PlanItems")].toArray(); if (arr.isEmpty()) { ERROR_LOG(m_logger) << "No PlanItems given - " << QJsonDocument(details).toJson(); } for (const auto &ele : arr) { const auto pi = PlanOverlaysPlugin::PlanOverlayModel::PlanItem::fromJson(ele.toObject()); if (!pi.isValid() || pi.m_fileId <= 0) { WARNING_LOG(m_logger) << "Can not parse json document - can not read filter data " << QJsonDocument(ele.toObject()).toJson(); return false; } filter.overlayId = pi.m_fileId; DEBUG_LOG(m_logger) << "Add overlay with id " << filter.overlayId; overlayInformation.push_back(filter); } } //} } m_replayInformation = replayInformation; m_filterInformation = filterInformation; m_overlayInformation = overlayInformation; m_attributes = rootObj[QStringLiteral("attributes")].toObject(); return showSituationInternal(); } bool SituationBuildingBase::showSituationInternal() { m_ui->cbPlaybackParts->setVisible(m_replayInformation.size() > 1); if (m_replayInformation.isEmpty()) { WARNING_LOG(m_logger) << "No replay information given..."; return false; } const QString sqliteDb = m_replayInformation.first().databaseFilePath; if (sqliteDb.isEmpty()) { WARNING_LOG(m_logger) << "No database given..."; return false; } QFileInfo fi(sqliteDb); if (!fi.exists() || !fi.isFile() || !fi.isReadable()) { Q_ASSERT(false); WARNING_LOG(m_logger) << "Can not access sqlite database '" << fi.absoluteFilePath() << "'"; return false; } m_ui->cbPlaybackParts->clear(); m_ui->cbPlaybackParts->blockSignals(true); for (int i = 0; i < m_replayInformation.size(); ++i) { const ReplayInformation &info = m_replayInformation.at(i); m_ui->cbPlaybackParts->addItem(QStringLiteral("%1: %2 - %3").arg(i+1).arg(DTG::toStringMSec(info.timeSpan.begin, DTG::Long), DTG::toStringMSec(info.timeSpan.end, DTG::Long)), i); } m_ui->cbPlaybackParts->setCurrentIndex(0); m_ui->cbPlaybackParts->blockSignals(false); onCbPlaybackPartsChanged(); // creates replayHandler for correct db etc. return true; } void SituationBuildingBase::setSpeed(double newSpeed) { m_speedVal = qBound(0.0, newSpeed, 16.0); if (m_replayHandler->handlerSrp()) m_replayHandler->handlerSrp()->onPaceChangeRequest(m_speedVal); updateSpeedLabel(); } void SituationBuildingBase::onSlower() { // cur speed = 9 -> new speed = 7 // cur speed = 4 -> new speed = 3 int diff = lrint(sqrt(m_speedVal) - 1); if (diff < 1) diff = 1; setSpeed(m_speedVal - diff); } void SituationBuildingBase::onFaster() { int diff = lrint(sqrt(m_speedVal) - 1); if (diff < 1) diff = 1; setSpeed(m_speedVal + diff); } void SituationBuildingBase::on100pct() { setSpeed(1.0); } void SituationBuildingBase::onPlay() { DEBUG_LOG(m_logger) << "OnPlay " << int(m_state); switch (m_state) { case State::PS_STOP: m_state = State::PS_PLAY; if (m_replayHandler->handlerSrp()) m_replayHandler->handlerSrp()->onStartReplayRequest(m_speedVal); break; case State::PS_PAUSE: m_state = State::PS_PLAY; if (m_replayHandler->handlerSrp()) m_replayHandler->handlerSrp()->onResumeReplayRequest(); break; case State::PS_PLAY: m_state = State::PS_PAUSE; if (m_replayHandler->handlerSrp()) m_replayHandler->handlerSrp()->onPauseReplayRequest(); break; case State::PS_UNDEFINED: // dürfte nicht vorkommen break; } updateButtons(); } void SituationBuildingBase::onStop() { auto srp = m_replayHandler->handlerSrp(); if (srp) { srp->onStopReplayRequest(); onUpdateTimeSlider(srp->beginTime()); } m_state = State::PS_STOP; updateButtons(); m_ui->sldPosition->setValue(0); } void SituationBuildingBase::onSliderPressed() { if (m_replayHandler->handlerSrp()) m_replayHandler->handlerSrp()->onPauseReplayRequest(); m_state = State::PS_PAUSE; updateButtons(); } void SituationBuildingBase::onSliderPositionChanged(int newPosition) { if (!m_replayHandler->handlerSrp()) return; auto srp = m_replayHandler->handlerSrp(); const double len = (srp->endTime() - srp->beginTime()); const double ofs = newPosition * len / s_sliderMax; m_sliderMoveTime = qRound64(srp->beginTime() + ofs); m_currentTimeDTGString = DTG::toStringMSec(m_sliderMoveTime, DTG::Long); m_ui->lblPosition->setText(m_currentTimeDTGString); } void SituationBuildingBase::onSliderReleased() { if (m_replayHandler->handlerSrp()) m_replayHandler->handlerSrp()->onStartReplayRequest(m_sliderMoveTime, m_replayHandler->handlerSrp()->endTime(), m_speedVal); m_state = State::PS_PLAY; updateButtons(); } void SituationBuildingBase::onUpdateTimeSlider(qint64 curTime) { if (!m_replayHandler->handlerSrp() || m_state == State::PS_STOP) return; const auto srp = m_replayHandler->handlerSrp(); const double len = (srp->endTime() - srp->beginTime()); const double ofs = (curTime - srp->beginTime()); double pos = ofs / len * s_sliderMax; if (pos < 0) pos = 0; if (pos >= s_sliderMax) { onStop(); return; } DEBUG_LOG(m_logger) << "onUpdateTimeSlider: " << curTime << ", pos: " << pos; m_ui->sldPosition->setValue(qRound(pos)); m_currentTimeDTGString = DTG::toStringMSec(curTime, DTG::Long); m_ui->lblPosition->setText(m_currentTimeDTGString); emitPartitionTimeChanged(curTime); } void SituationBuildingBase::onCbPlaybackPartsChanged() { onStop(); int idx = m_ui->cbPlaybackParts->currentData().toInt(); if (idx < 0 || idx >= m_replayInformation.size()) return; const ReplayInformation &info = m_replayInformation.at(idx); if (!m_replayHandler->initDb(info, m_filterInformation, m_overlayInformation)) { WARNING_LOG(m_logger) << "Can not initialize replay handler (" << info.databaseFilePath << ")"; return; } // currently after initDb since there handlerSrp is (re)created connect(m_replayHandler->handlerSrp().get(), &SituationReplay::HandlerSituationReplaySqlite::replayTimeChanged, this, &SituationBuildingBase::onUpdateTimeSlider, Qt::UniqueConnection); } void SituationBuildingBase::updateSpeedLabel() { QString speedStr = QStringLiteral("%1x").arg(m_speedVal, 4, 'f', 2); m_ui->lblSpeed->setText(speedStr); } void SituationBuildingBase::updatePositionLabel(const LtsUtils::QPoint3D &geoPos, bool isValid) { if (isValid) { const LtsCoordTrans::UtmCoordinate coord(LtsCoordTrans::UtmCoordinate::fromGeographic(geoPos.x(), geoPos.y())); m_ui->lblCursorPos->setText(coord.mgrs()); } else { m_ui->lblCursorPos->setText(QString()); } } QString SituationBuildingBase::currentDTGTime() const { return m_currentTimeDTGString; } void SituationBuildingBase::updateButtons() { switch (m_state) { case State::PS_UNDEFINED: m_ui->btnStop->setEnabled(false); m_ui->btnPlay->setEnabled(false); m_ui->btnPlay->setIcon(QIcon(QStringLiteral(":icons/16x16/media-playback-start.png"))); m_ui->sldPosition->setEnabled(false); break; case State::PS_STOP: m_ui->btnStop->setEnabled(false); m_ui->btnPlay->setEnabled(true); m_ui->btnPlay->setIcon(QIcon(QStringLiteral(":icons/16x16/media-playback-start.png"))); m_ui->sldPosition->setEnabled(true); break; case State::PS_PLAY: m_ui->btnStop->setEnabled(true); m_ui->btnPlay->setEnabled(true); m_ui->btnPlay->setIcon(QIcon(QStringLiteral(":icons/16x16/media-playback-pause.png"))); m_ui->sldPosition->setEnabled(true); break; case State::PS_PAUSE: m_ui->btnStop->setEnabled(true); m_ui->btnPlay->setEnabled(true); m_ui->btnPlay->setIcon(QIcon(QStringLiteral(":icons/16x16/media-playback-start.png"))); m_ui->sldPosition->setEnabled(true); break; } } bool SituationBuildingBase::stop() { onStop(); return true; } void SituationBuildingBase::onInitDone() { m_state = State::PS_STOP; m_playTimer->start(); } } // namespace THP