/** * @RDE_HEADER@ * GUI Plugin for SituationReplay - Replay Control * $Id$ * $HeadURL$ * $Author$ * Created by Patric Karwar, SLSE, 2016-07-19 */ #include "replay_control_gui_plugin.h" #include "replay_control_widget.h" #include "replay_control_dialog.h" #include "TopicEditor/topic_editor.h" #include #include #include #include #include #include namespace SituationReplay { std::shared_ptr GuiPlugin::createGuiInterface(LtsSession::LtsSession *session) { return std::make_shared(session); } InterfaceImpl::InterfaceImpl(LtsSession::LtsSession *session) : GuiPluginBase::Interface(session, getLogger("Plugins.Gui.Srp.ReplayControl")) , m_dtc(nullptr) , m_ftc(nullptr) , m_mrkDtc(nullptr) , m_playbackStartTime(-1) , m_playbackEndTime(-1) , m_playbackSpeed(-1) , m_replayWidget(nullptr) , m_sitMapIfc(nullptr) {} bool InterfaceImpl::fetchInterfaces() { if (!m_session->getDtc(m_dtc)) { DEBUG_LOG(m_logger) << "No Srp Dtc in ReplayControl GUI Plugin with domainId " << m_session->domainId() << " partition " << m_session->partitionId() << " type " << m_session->partitionType(); return false; } if (!m_session->getFtc(m_ftc)) { DEBUG_LOG(m_logger) << "No Srp Ftc in ReplayControl GUI Plugin with domainId " << m_session->domainId() << " partition " << m_session->partitionId() << " type " << m_session->partitionType(); return false; } // Optional: if (!m_session->getDtc(m_mrkDtc)) { DEBUG_LOG(m_logger) << "(Optional) No Mrk Data Ifc in ReplayControl GUI Plugin with domainId " << m_session->domainId() << " partition " << m_session->partitionId() << " type " << m_session->partitionType(); } LtsSession::Fetcher *fetcher = m_session->fetcher(QStringLiteral("Situation Replay Control")); fetcher->getForeignGuiInterface(m_mrkIfc, QStringLiteral("marker selection in replay control"), true); // if (!m_session->getForeignGuiInterface(m_mrkIfc)) // { // WARNING_LOG(m_logger) << "(Optional) No Mrk Gui Ifc in ReplayControl GUI Plugin with domainId " << m_session->domainId() // << " partition " << m_session->partitionId() // << " type " << m_session->partitionType(); // } if (!m_session->getForeignGuiInterface(m_sitMapGuiIfc)) WARNING_LOG(m_logger) << "No Map Gui Ifc: " << m_session->toString(); return fetcher->status(); } void InterfaceImpl::clearInterfaces() { if (m_replayWidget != nullptr) m_replayWidget->deleteLater(); m_mrkDtc = nullptr; m_ftc = nullptr; m_dtc = nullptr; m_sitMapGuiIfc = nullptr; m_mrkIfc = nullptr; } bool InterfaceImpl::startReplay(const qint64 start, const qint64 end, const double speed, const QJsonObject &attributes) { const QJsonValue mapConfig = attributes[QStringLiteral("settings")]; if (mapConfig.isNull()) { DEBUG_LOG(m_logger) << "Note: Situation Map Script Filter settings is empty"; } createReplayDialogIfNeeded(); /*DEBUG*/ERROR_LOG(m_logger) << "start = " << start << ", end = " << end << ", speed = " << speed << ", attributes: " << QString::fromUtf8(QJsonDocument(attributes).toJson()); /*DEBUG*/ERROR_LOG(m_logger) << "state = " << m_replayWidget->getReplayState() << ", replay id = " << m_replayWidget->getId(); // always stop & play if start was called, otherwise new start/end/speed values are dismissed // if (m_replayDialog->getReplayState() == SrpState_def::Paused) // m_replayDialog->resumeReplay(); // else return m_replayWidget->startReplay(start, end, speed); } void InterfaceImpl::pauseReplay() { createReplayDialogIfNeeded(false); m_replayWidget->pauseReplay(); } void InterfaceImpl::stopReplay() { createReplayDialogIfNeeded(false); m_replayWidget->stopReplay(); } QVector InterfaceImpl::getActions() { QVector ret; if (m_session->partitionType() != IDL::PartitionType::Replay) return ret; LtsWidgets::PluginData plData; plData.menuGroup = menuGroup(); LtsSessionData::LtsSessionData *sd = LtsSessionData::LtsSessionData::getInstance(); if (sd->debugFlagValue(QStringLiteral("DoNotCreateInstant.ReplayControl")) == QVariant(true) /* HACK */ && (!sd->hasDebugFlag(QStringLiteral("DoNotCreateInstant.ReplayControl.Ignore")) || sd->debugFlagValue(QStringLiteral("DoNotCreateInstant.ReplayControl.Ignore")) == QVariant(false))) sd->setDebugFlagValue(QStringLiteral("DoNotCreateInstant.ReplayControl"), false); // HACK: ! - set 'back' to false else plData.createInstant = LtsWidgets::CreateInstantType::CreateInstantAndAddAction; plData.layoutArea = LtsWidgets::LayoutArea::eDialogTab; plData.menuText = tr("Playback Control"); plData.identifier = QLatin1String("PlaybackControlDialog"); plData.menuIcon = QIcon(QLatin1String("icon:load.svg")); plData.exercisePart = LtsWidgets::ExercisePart::eAfterAction; plData.createWidgetFunc = [this](QWidget *parent) { return onShowReplayControlDialog(parent); }; ret.append(plData); return ret; } QString InterfaceImpl::menuGroup() const { return tr("Playbacks"); } // private void InterfaceImpl::createReplayDialogIfNeeded(bool useExistingReplay) { if (m_replayWidget != nullptr) return; m_replayWidget = qobject_cast(createControlWidget(useExistingReplay)); } QWidget *InterfaceImpl::createControlWidget(bool useExistingReplay) { auto situationReplayControlWidget = new SituationReplayControlWidget; bool r = situationReplayControlWidget->init(m_session, m_dtc, m_ftc, m_mrkDtc, m_sitMapGuiIfc, m_session->partitionId(), useExistingReplay); if (!r) { situationReplayControlWidget->deleteLater(); situationReplayControlWidget = nullptr; } // else // { // m_replayWidgetList.append(situationReplayControlWidget); // connect(situationReplayControlWidget, &QObject::destroyed, // this, [this, situationReplayControlWidget]() // { // m_replayWidgetList.removeAll(situationReplayControlWidget); // }); // } return situationReplayControlWidget; } LtsWidgets::LtsDialog *InterfaceImpl::onShowReplayControlDialog(QWidget *) { // NOTE: One Instance of this dialog for a session as this IS a replay session. Exactly one session. createReplayDialogIfNeeded(); return new SituationReplayControlDialog(m_replayWidget); } TopicEditorBase::TopicEditor *InterfaceImpl::createTopicEditor(const TopicEditorBase::TopicData &data) { MarkerManagement::MarkerManagementDialogModel *markerModel = nullptr; if (m_mrkIfc) markerModel = m_mrkIfc->createMarkerModel(); else WARNING_LOG(m_logger) << "No Mrk Ifc for SitRep Topic Editor"; if (m_essentialsViewSrpIfc == nullptr) WARNING_LOG(m_logger) << "essentials view's srp ifc not available - no preview available"; if (m_applyFilter == nullptr) WARNING_LOG(m_logger) << "m_applyFilter == nullptr"; SituationReplayEditor *editor = new SituationReplayEditor(markerModel, m_applyFilter, m_session, nullptr, &m_essentialsViewSrpIfc, &m_essentialsViewSitMapIfc); QObject::connect(this, &InterfaceImpl::signalEssentialsViewChanged, editor, &SituationReplayEditor::onEssentialsViewChanged); editor->setTopicData(data); return editor; } void InterfaceImpl::setSitMapIfc(SituationMap::ForeignGuiInterface *sitMapIfc) { m_sitMapIfc = sitMapIfc; } void InterfaceImpl::setEssentialsViewInterfaces(ForeignGuiInterface *srpIfc, SituationMap::ForeignGuiInterface *sitMapIfc) { m_essentialsViewSrpIfc = srpIfc; m_essentialsViewSitMapIfc = sitMapIfc; emit signalEssentialsViewChanged(m_essentialsViewSrpIfc, m_essentialsViewSitMapIfc); } void InterfaceImpl::setApplyFilter(const QPointer &applyFilter) { DEBUG_LOG(m_logger) << "setApplyFilter"; m_applyFilter = applyFilter; } bool InterfaceImpl::playbackInit(const QJsonObject& attributes) { QJsonDocument doc(attributes); DEBUG_LOG(m_logger) << "attributes=" << doc.toJson(QJsonDocument::Compact); // load map settings (if any available) const QJsonValue mapConfig = attributes[QStringLiteral("settings")]; if (!mapConfig.isNull()) // maybe a pure replay topic, not a situation topic which contains map settings, etc. { //m_oldMapData = m_sitMapIfc->operatorMapSettings(); m_sitMapIfc->setOperatorMapSettings(mapConfig); } else { WARNING_LOG(m_logger) << "Note: map settings is empty"; } // load map filters (if any available) const QJsonObject mapFilters = attributes[QStringLiteral("filters")].toObject(); if (m_applyFilter != nullptr && !mapFilters.isEmpty()) { //m_oldFilterData = m_applyFilter->getFilterParamsJson(); m_applyFilter->applyFilter(mapFilters); } else { if (m_applyFilter == nullptr) WARNING_LOG(m_logger) << "ApplyFilter missing for ReplayControl TopicReplayInterface"; else if (mapFilters.isEmpty()) WARNING_LOG(m_logger) << "Note: filter settings is empty"; } DEBUG_LOG(m_logger) << "Replay Control playbackInit"; m_playbackStartTime = attributes.value(QStringLiteral("start")).toDouble(); m_playbackEndTime = attributes.value(QStringLiteral("end")).toDouble(); m_playbackSpeed = attributes.value(QStringLiteral("speed")).toDouble(); bool result = (m_playbackStartTime != 0 && m_playbackEndTime != 0 && m_playbackSpeed != 0); if (!result) WARNING_LOG(m_logger) << "Invalid data for topic replay: start " << m_playbackStartTime << " end " << m_playbackEndTime << " speed " << m_playbackSpeed; return result; } bool InterfaceImpl::playbackStart(int secondsOffset) { DEBUG_LOG(m_logger) << "Playback start: m_playbackStartTime = " << m_playbackStartTime << ", offset = " << secondsOffset << ", speed = " << m_playbackSpeed; return startReplay(m_playbackStartTime + ((secondsOffset * m_playbackSpeed) * 1000), 0 /* infinite */, m_playbackSpeed); } bool InterfaceImpl::playbackPause() { pauseReplay(); return true; } //void InterfaceImpl::resetMap() <= Do not reset map as map filters are not going to be reversed once set, because the transition from one to another filter would otherwise be very ugly - kind of a hacky workaround //{ // if (m_sitMapIfc != nullptr) // { // if (!m_oldMapData.isNull() && !m_oldMapData.isUndefined()) // m_sitMapIfc->setOperatorMapSettings(m_oldMapData); // reset map settings // } // else // { // WARNING_LOG(m_logger) << "SitMapIfc missing for SitMapScriptFilter TopicReplayInterface"; // } // if (m_applyFilter != nullptr) // { // if (!m_oldFilterData.isNull() && !m_oldFilterData.isUndefined()) // m_applyFilter->applyFilter(m_oldFilterData.toObject()); // reset map filters // } // else // { // WARNING_LOG(m_logger) << "ApplyFilter missing for ReplayControl TopicReplayInterface"; // } //} // TopicReplayInterface InterfaceImpl::TopicReplayInterface::TopicReplayInterface(TopicEditorBase::TopicType type, InterfaceImpl *replayControl) // 1:1 : m_replayControl(replayControl) , m_type(type) {} InterfaceImpl::TopicReplayInterface::~TopicReplayInterface() { m_replayControl->stopReplay(); //m_replayControl->resetMap(); } // Script Playback Controls: bool InterfaceImpl::TopicReplayInterface::playbackInit(const QJsonObject &attributes) { return m_replayControl->playbackInit(attributes); } bool InterfaceImpl::TopicReplayInterface::playbackStart(int secondsOffset) { return m_replayControl->playbackStart(secondsOffset); } bool InterfaceImpl::TopicReplayInterface::playbackPause() { return m_replayControl->playbackPause(); } bool InterfaceImpl::TopicReplayInterface::exportTopic(const QJsonObject &attributes, const QString &folderPath, TopicEditorBase::ExportNotifier *pExportNotifier) { qint64 playbackStartTime_ms = attributes.value(QStringLiteral("start")).toDouble(); qint64 playbackEndTime_ms = attributes.value(QStringLiteral("end")).toDouble(); // filter? if (playbackStartTime_ms == 0 && playbackEndTime_ms == 0) { pExportNotifier->setExportFailed(); return false; } const QString attributesFileName = QStringLiteral("sitreplay.json"); QFile f(folderPath + attributesFileName); f.open(QIODevice::WriteOnly); f.write(QJsonDocument(attributes).toJson(QJsonDocument::Indented)); f.close(); const QString strFileName = QStringLiteral("sitreplay.sqlite"); bool success = TakeHomePackageGenerator::exportSituationMapData( m_replayControl->getSession()->exerciseId(), folderPath + strFileName, playbackStartTime_ms, playbackEndTime_ms ); if (success) pExportNotifier->setExportSuccessful(strFileName); else pExportNotifier->setExportFailed(); return success; } QVector InterfaceImpl::TopicReplayInterface::additionalTrackEntries(const QJsonObject &attributes, bool &keepOriginalTopic) const { keepOriginalTopic = true; if (m_type != TopicEditorBase::TopicType::Situation) // combined topic, including map filter settings that - filter settings need to be processed by its parent gui's additionalTrackEntries method return {}; // no additional tes if this is a simple replay topic keepOriginalTopic = false; // as the situation topic is not a real topic by itself, don't keep it! // the 'situation' topic is a 3 in one topic - map, replay, filter - so the other 2 have to be returned here for thp to recognize QVector result; // first, add the simple map topic: TopicEditorBase::TopicData data; data.topicType = TopicEditorBase::TopicType::SituationMap; result.append(data); // get processed filter topics std::shared_ptr filterIfc; if (!m_replayControl->m_session->getForeignGuiInterface(filterIfc)) { ERROR_LOG(m_replayControl->m_logger) << "sitmap filter ifc missing - map filters for thp will be missing!"; return result; } QScopedPointer topicIfc(filterIfc->createInterface(TopicEditorBase::TopicType::SituationMapFilter)); if (topicIfc == nullptr) { ERROR_LOG(m_replayControl->m_logger) << "sitmap filter topic replay interface cannot be created - map filters will not be available in thp!"; return result; } result += topicIfc->additionalTrackEntries(attributes); return result; } } // namespace SituationReplay