/** * @RDE_HEADER@ * HMI Plugin for ExerciseManangement - Exercise Overview Dialog * $Id$ * $HeadURL$ * $Author$ * Created by Christian Ehrlicher, SLSE, 2016-02-29 */ #include "snapshot_dialog.h" #include "ui_snapshot_dialog.h" #include "snapshot_dialog_model.h" #include "mrk_create_snapshot.h" #include #include #include #include #include #include #include #include // PlanOverlay #include #include #include #include #include #include #include #include #include #include namespace MarkerManagement { const int snapShotOverlayId = -4711; //----------------------------------------------------------------------------------------------------- // SnapshotDialog //----------------------------------------------------------------------------------------------------- SnapshotDialog::SnapshotDialog(const QString &menuGroup, LtsSession::LtsSession *session, QWidget *parent) : LtsWidgets::LtsDialog(parent, LtsWidgets::ButtonCombination::CloseOnly) , m_menuGroup(menuGroup) , m_model(nullptr) , m_ui(new Ui::SnapshotDialog) , m_session(session) , m_requestHelper(new LtsUtils::RequestHelper(this)) , m_planEditorHasUpdatedThePlan(false) , m_planEditorApp(nullptr) , m_fileId(-1) , m_logger(getLogger("SnapshotDialog")) { TRACE_LOG(m_logger) << LOGFUNCTION << " START"; m_ui->setupUi(this); m_buttonBox->connectButton(LtsWidgets::DialogButton::Show, this, &SnapshotDialog::onShow); m_buttonBox->connectButton(LtsWidgets::DialogButton::Edit, this, &SnapshotDialog::onEditMetaData); m_buttonBox->connectButton(LtsWidgets::DialogButton::Remove, this, &SnapshotDialog::onRemoveSnapshots); m_buttonBox->connectButton(LtsWidgets::DialogButton::Save, this, &SnapshotDialog::onSaveSettings); m_buttonBox->connectButton(LtsWidgets::DialogButton::Load, this, &SnapshotDialog::onEditPlan); m_buttonBox->connectButton(LtsWidgets::DialogButton::Print, this, &SnapshotDialog::onPrint); //m_buttonBox->getButton(LtsWidgets::DialogButton::Save)->setEnabled(false); m_buttonBox->getButton(LtsWidgets::DialogButton::Save)->setText(tr("Save Settings")); m_buttonBox->getButton(LtsWidgets::DialogButton::Load)->setText(tr("Edit Plan")); connect(m_ui->widget, &LtsDataView::LtsDataView::signalDoubleClicked, this, &SnapshotDialog::onEditMetaData); // connect(m_ui->widget, &LtsDataView::LtsDataView::signalSelectionChanged, // this, &SnapshotDialog::onSelectionChanged); setOnlineHelpFilename(QStringLiteral("Situation-Snapshots")); m_ui->widget->setSelectionMode(QAbstractItemView::SelectionMode::ExtendedSelection); session->getForeignGuiInterface(m_flmGui); TRACE_LOG(m_logger) << LOGFUNCTION << " END"; } //----------------------------------------------------------------------------------------------------- SnapshotDialog::~SnapshotDialog() { TRACE_LOG(m_logger) << LOGFUNCTION << " START"; // plan editor app delete-flagged in aboutToClose delete m_ui; TRACE_LOG(m_logger) << LOGFUNCTION << " END"; } //----------------------------------------------------------------------------------------------------- void SnapshotDialog::retranslate() { translate(m_ui, tr("Snapshots")); } /* //----------------------------------------------------------------------------------------------------- void SnapshotDialog::onSelectionChanged() { const QModelIndex idx = m_ui->widget->currentIndex(); const MarkerManagement::Snapshot & snapshot = m_model->getRowData(idx.row()); if (snapshot.id() != m_currentSnapshot.id()) { m_buttonBox->getButton(LtsWidgets::DialogButton::Save)->setEnabled(false); } } */ //----------------------------------------------------------------------------------------------------- void SnapshotDialog::onShow() { const QModelIndex &idx = m_ui->widget->currentIndex(); const MarkerManagement::Snapshot &snapshot = m_model->getRowData(idx.row()); if (snapshot.id() <= 0) return; m_currentSnapshot = snapshot; // fileContendId is the fileId of previewImage showSnapshot(); } //----------------------------------------------------------------------------------------------------- void SnapshotDialog::onRefreshCurrent(const Snapshot &snapshot) { INFO_LOG(m_logger) << LOGFUNCTION << " current SnapshotId: " << snapshot.id(); // show Snapshot only if current snapshot changed from outside if (snapshot.id() != m_currentSnapshot.id()) return; m_currentSnapshot = snapshot; showSnapshotInternal(m_currentSnapshot, false); // applyFilter } //----------------------------------------------------------------------------------------------------- void SnapshotDialog::showSnapshot() { INFO_LOG(m_logger) << LOGFUNCTION << " current SnapshotId: " << m_currentSnapshot.id(); showSnapshotInternal(m_currentSnapshot, true); // applyFilter enaDisa(); } //----------------------------------------------------------------------------------------------------- bool SnapshotDialog::applyFilter(const Snapshot &snapshot) { const QByteArray filterContent(QByteArray::fromRawData(snapshot.filter().c_str(), snapshot.filter().size())); if (filterContent.isEmpty()) { ERROR_LOG(m_logger) << "filterContent is empty, snapshot.id()="<< snapshot.id(); return false; } // 1) shortcut for our session/widget: MarkerManagementApplyFilter appFilter(m_session, m_fltParticipantIfc, m_fltAesIfc, m_fltBuildingComponentIfc); if (!appFilter.applyFilter(filterContent)) { ERROR_LOG(m_logger) << LOGFUNCTION << " Error while applying filters"; return false; } // 2) shortcut for session/widget of PlanEditor : // but only if snapshot.id and PlanEditor.m_currentSnapshot.id match: if (m_planEditorApp && (m_currentSnapshot.id() == snapshot.id())) { const int partitionId = m_session->partitionId(); auto pe_session = m_planEditorApp->getSession(IDL::PartitionType::Replay, partitionId); if (pe_session) { std::shared_ptr pe_fltParticipantIfc; std::shared_ptr pe_fltAesIfc; std::shared_ptr pe_fltBuildingComponentIfc; pe_session->getForeignGuiInterface(pe_fltParticipantIfc); // not in global pe_session->getForeignGuiInterface(pe_fltAesIfc); // not in global pe_session->getForeignGuiInterface(pe_fltBuildingComponentIfc); // not in global if (pe_fltParticipantIfc && pe_fltAesIfc) { MarkerManagementApplyFilter appFilter2(pe_session, pe_fltParticipantIfc, pe_fltAesIfc, pe_fltBuildingComponentIfc); if (!appFilter2.applyFilter(filterContent)) { ERROR_LOG(m_logger) << LOGFUNCTION << "Error while applying filters"; return false; } } else ERROR_LOG(m_logger) << LOGFUNCTION << "getForeignGuiInterface failed"; } else ERROR_LOG(m_logger) << LOGFUNCTION << "Internal error - can not get previously created session!"; } // : shortcut for session/widget of PlanEditor return true; } void SnapshotDialog::tableColumnOrganiserSetting() { //LtsColumnOrganiser m_ui->widget->setColumnsOrganiserEnabled(identifier(), true); } //----------------------------------------------------------------------------------------------------- bool SnapshotDialog::showSnapshotInternal(const Snapshot &snapshot, bool bApplyFilter) { auto spSitMapGuiIfc = m_sitMapGuiIfc.lock(); SituationMap::SitMapUpdatesEnabledHelper sitMapHelper(spSitMapGuiIfc); const int partitionId = m_session->partitionId(); INFO_LOG(m_logger) << LOGFUNCTION << " current Snapshot: " << snapshot << ", replayPartition: " << partitionId << ", applyFilter=" << bApplyFilter; SituationReplay::ChangeReplayStateRequest req; req.id(0); req.replayPartitionId(partitionId); req.state(SituationReplay::SrpState::Started); req.beginTime(snapshot.timeStamp()); req.endTime(snapshot.timeStamp()); // snapshot req.pace(0); if (!m_requestHelper->startSingleRequest([req, this]() { return m_srpFtc->changeReplayStateRequest(req, this); })) { ERROR_LOG(m_logger) << "Replay could not be started for partition id = " << partitionId; LtsWidgets::LtsMessageDialog::showMessage(this, tr("Replay could not be started for partition with id %1").arg(partitionId)); return false; } bool success = true; // Filter: if (bApplyFilter) if (!applyFilter(snapshot)) { ERROR_LOG(m_logger) << "applyFilter failed, partition id = " << partitionId; success = false; } if (spSitMapGuiIfc) { std::shared_ptr ovrDtc; if (m_session->getForeignGuiInterface(ovrDtc)) { INFO_LOG(m_logger) << LOGFUNCTION << " unloadAllPlans via PlanOverlaysPlugin to update dialog"; ovrDtc->unloadAllPlans(); } // SitMapForeignInterface => set Settings { const QByteArray ba(QByteArray::fromRawData(snapshot.mapSettings().c_str(), snapshot.mapSettings().size())); spSitMapGuiIfc->setOperatorMapSettingsJson(ba); const QByteArray planOverlay(snapshot.planOverlay().c_str(), snapshot.planOverlay().size()); if (!planOverlay.isEmpty()) { // this is editable Plan/Overlay of snapshot: { const bool bIsMapReady = spSitMapGuiIfc->isMapReady(); INFO_LOG(m_logger) << LOGFUNCTION << " bIsMapReady = " << bIsMapReady; if (bIsMapReady) { if (!spSitMapGuiIfc->planOverlaySetContent(snapShotOverlayId, tr("Snapshot-PlanOverlay"), planOverlay, true)) { ERROR_LOG(m_logger) << LOGFUNCTION << " Error add planOverlay to snapshot, SnapshotId: " << snapshot.id() << " planOverlay.size()=" << planOverlay.size() << " planOverlay=" << planOverlay; success = false; } } else { // GUEZE-811 3D Lage Filter werde nicht korrekt abgebaut... wenn 2D-Snap // mit SnapshotPlan auf 3D-Topic folgt... // Szenario: 2D-Snapshuss wird wiedergegeben (App Snapshot|Script) // und SitMap muss dafür von 3D auf 2D wechseln. // Dabei werden die Overlays via MapOperatorSettings zunächst vorbereitet. // Dabei wurde aber in MapImpl::restoreOperatorMapSettings der SNAPSHOT-Overlay // als herkömmlicher Overlay vorbereitet. // Beim nachfolgendem OverlayHandlerImpl::setOverlay usw. ergaben sich Fehler // wg. LayerType::SnapshotOverlay vs. LayerType::Overlay. // Mit dem FixPart1 wird in restoreOperatorMapSettings der snapShotOverlayId == -4711 // als isSnapshot==true eintragen. // Mit FixPart2 bei SnapshotDialog::showSnapshotInternal ein // setMapReadyCallback genutzt, weil die SitMap noch nicht mapReady ist. // Mit FixPart3 bei InterfaceImpl::snapshotAdjustSitMap ebenfalls ein // setMapReadyCallback genutzt, weil die SitMap noch nicht mapReady ist. // dirty hack: try again // QTimer::singleShot(1000, this, [this, snapshot, bApplyFilter]() { // showSnapshotInternal(snapshot, bApplyFilter); // }); // QTimer::singleShot(1000, this, [this, spSitMapGuiIfc, snapShotOverlayId, planOverlay, snapshot]() { // if (!spSitMapGuiIfc->planOverlaySetContent(snapShotOverlayId, tr("Snapshot-PlanOverlay"), planOverlay, true)) // { // ERROR_LOG(m_logger) << LOGFUNCTION << " Error add planOverlay to snapshot, SnapshotId: " << snapshot.id() // << " planOverlay.size()=" << planOverlay.size() // << " planOverlay=" << planOverlay; // } // }); const int snapshotId = snapshot.id(); auto onMapIsReadyFn = [this, spSitMapGuiIfc, planOverlay, snapshotId]() { INFO_LOG(m_logger) << LOGFUNCTION << " try again: do planOverlaySetContent..."; if (!spSitMapGuiIfc->planOverlaySetContent(snapShotOverlayId, tr("Snapshot-PlanOverlay"), planOverlay, true)) { ERROR_LOG(m_logger) << LOGFUNCTION << " Error add planOverlay to snapshot, SnapshotId: " << snapshotId << " planOverlay.size()=" << planOverlay.size() << " planOverlay=" << planOverlay; } }; if (!spSitMapGuiIfc->setMapReadyCallback(this, onMapIsReadyFn)) ERROR_LOG(m_logger) << LOGFUNCTION << ", setMapReadyCallback failed, SnapshotId: " << snapshotId; } } // : this is editable Plan/Overlay of snapshot } } // SitMapForeignInterface => restore MovementTracks { const QByteArray ba(QByteArray::fromRawData(snapshot.movementTracks().c_str(), snapshot.movementTracks().size())); if (!spSitMapGuiIfc->setMovementTrackManagerSettingsJSon(ba)) { ERROR_LOG(m_logger) << LOGFUNCTION << " Error setMovementTrackManagerSettingsJSon, SnapshotId: " << snapshot.id(); success = false; } } } QString name = tr("Snapshot '%1'").arg(QString::fromStdString(m_currentSnapshot.name())); m_session->baseLayout()->setCustomText(name); spSitMapGuiIfc->showExerciseTimeString(true); return success; } //----------------------------------------------------------------------------------------------------- void SnapshotDialog::onEditPlan() { // the user-selected snapshot should be displayed in the viewer while the according plan is loaded by the planEditor: // (but don´t reload if already loaded to not discard changes to center and scale) const QModelIndex idx = m_ui->widget->currentIndex(); const MarkerManagement::Snapshot &snapshot = m_model->getRowData(idx.row()); // show snapshot if necessary // another than the current is requested to be modified with planEditor if (snapshot.id() != m_currentSnapshot.id()) onShow(); if (m_currentSnapshot.id() <= 0) return; /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * ToDo: showing message like the following * * const QString & msg = tr("Launching PlanEditor to edit snapshot plan. Saving the plan to snap shot will be done automatically at finish of planeditor."); * * LtsWidgets::LtsMessageDialog::showMessage(this, msg, tr("Launching PlanEditor"), LtsWidgets::Behaviour::Popup ); * * with the difference that the message should be shown only for a short time interval and will be removed from screen without any user interaction. * Eventually it would be nice to add the name of snapshot to the message. * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ // Keep in mind operator rights of plan editor here. Has the user the required rights to start the plan editor? if (!LtsSessionData::LtsSessionData::getInstance()->hasOperatorRight(OperatorRights::PlanEditor::TacticalPlansEditSnapShot)) { QString msg = tr("Operator right to launching Plan Editor to edit snapshot plans is missing"); LtsWidgets::LtsMessageDialog::showMessage(this, msg, tr("Launching Plan Editor is forbidden"), LtsWidgets::Behaviour::Popup ); return; } m_planEditorHasUpdatedThePlan = false; LtsSession::DdsConnectionParams connParams(m_session->connParams()); m_planEditorApp = LtsAppManager::LtsAppManager::getInstance()->createApp(LtsAppDescriptor::App::PlanEditor, connParams); if (!m_planEditorApp) { // In case of failure createApp will show an error message ERROR_LOG(m_logger) << "Creating Plan Editor-Snapshot failed."; return; } // (re-) enable btn 'start PlanEditor' if planEditor is finished // QueuedConnection since this signal is fired during the destruction process and // onPlaneditorFinished creates a new local eventloop... connect(m_planEditorApp.data(), &QObject::destroyed, this, &SnapshotDialog::onPlaneditorFinished, Qt::QueuedConnection); LtsSession::LtsSession *session = m_planEditorApp->getSession(IDL::PartitionType::Replay, connParams.partitionId); session->presetBaseLayoutAsSecondaryWidget(); // see https://devhub.defence-elec.de/confluence/x/yLjeDg 13.1 // session->setDependency(m_session); // prevents mmm to let that secondary widget be placed above this app => jetzt doch: GUEZE-2619 if (session->isInitializationDone()) slotReplaySessionForPlanEditorStarted(); else connect(session, &LtsSession::LtsSession::signalDoneInitializingSession, this, &SnapshotDialog::slotReplaySessionForPlanEditorStarted); // wait until the new replay session is initialized enaDisa(); } void SnapshotDialog::onPlaneditorFinished() { DEBUG_LOG(m_logger) << LOGFUNCTION << " onPlaneditorFinished" << " m_planEditorHasUpdatedThePlan=" << m_planEditorHasUpdatedThePlan; // User has has saved the Plan for snapshot before PE is finished. // Due to onRefreshCurrent the data is updated. (and ReplayStartRequested) // So an additional showSnapshot will cause an additonal ReplayStartRequested // The ReplayProcess doesn´t like to requests with such a small amount of time (~300ms) // so skip showSnapshot here: // showSnapshot(); if (m_planEditorHasUpdatedThePlan) { const QString &title = QString::fromStdString(m_currentSnapshot.name()); saveSnapshotPreviewInLibrary(title); } enaDisa(); } //----------------------------------------------------------------------------------------------------- void SnapshotDialog::slotReplaySessionForPlanEditorStarted() { const int partitionId = m_session->partitionId(); auto session = m_planEditorApp->getSession(IDL::PartitionType::Replay, partitionId); if (!session) { ERROR_LOG(m_logger) << LOGFUNCTION << " Internal error - can not get previously created session!"; return; } disconnect(session, &LtsSession::LtsSession::signalDoneInitializingSession, this, nullptr); if (!m_planEditorApp) return; if (!m_srpFtc) return; std::shared_ptr sitMapGuiIfc; std::shared_ptr planEditorIfc; session->getForeignGuiInterface(sitMapGuiIfc); session->getForeignGuiInterface(planEditorIfc); if (!sitMapGuiIfc || !planEditorIfc) { WARNING_LOG(m_logger) << "Can not create plan editor app - found not all required interfaces"; m_planEditorApp->deleteLater(); m_planEditorApp = nullptr; return; } // get Settings from SitMap => SitMapForeignInterface DEBUG_LOG(m_logger) << LOGFUNCTION << " current SnapshotId: " << m_currentSnapshot.id() << ", PlanEditor - partitionId = " << partitionId; SituationReplay::ChangeReplayStateRequest req; req.id(0); req.replayPartitionId(partitionId); req.state(SituationReplay::SrpState::Started); req.beginTime(m_currentSnapshot.timeStamp()); req.endTime(m_currentSnapshot.timeStamp()); req.pace(0); if (!m_requestHelper->startSingleRequest([req, this]() { return m_srpFtc->changeReplayStateRequest(req, this); })) { ERROR_LOG(m_logger) << LOGFUNCTION << " Replay could not be started for partition id = " << partitionId; LtsWidgets::LtsMessageDialog::showMessage(this, tr("Replay could not be started for partition with id %1").arg(partitionId)); m_planEditorApp->deleteLater(); return; } #define PlanEditorForSnapshot3D #ifdef PlanEditorForSnapshot3D { const QByteArray ba(QByteArray::fromRawData(m_currentSnapshot.mapSettings().c_str(), m_currentSnapshot.mapSettings().size())); QJsonParseError parserError; const QJsonDocument jDoc(QJsonDocument::fromJson(ba, &parserError)); if (parserError.error != QJsonParseError::NoError) { ERROR_LOG(m_logger) << "Error parsing json document: " << parserError.errorString(); return; } SituationMap::OperatorMapSettings oms; oms.fromJsonObject(jDoc.object()); if (oms.mode == SituationMap::Mode::Mode_3D) { DEBUG_LOG(m_logger) << " PlanEditor in 3D not supported yet, use 2D"; oms.mode = SituationMap::Mode::Mode_2D; sitMapGuiIfc->setOperatorMapSettingsJson(oms.toJson()); // SitMapForeignInterface => set Settings } else { sitMapGuiIfc->setOperatorMapSettingsJson(ba); // SitMapForeignInterface => set Settings } } #else const QByteArray ba(QByteArray::fromRawData(m_currentSnapshot.mapSettings().c_str(), m_currentSnapshot.mapSettings().size())); sitMapGuiIfc->setOperatorMapSettingsJson(ba); // SitMapForeignInterface => set Settings #endif // PlanEditorForSnapshot3D applyFilter(m_currentSnapshot); planEditorIfc->setPlanToEdit( QByteArray::fromStdString(m_currentSnapshot.planOverlay()) ); // read plan from snapshot planEditorIfc->addSaveSnapshotAction(QIcon(), tr("Save Plan to Snapshot"), [this](const QByteArray & planXMLContent) -> bool { return saveSnapshotPlan(planXMLContent); }); // const QByteArray filterContent(QByteArray::fromRawData(snapshot.filter().c_str(), snapshot.filter().size())); // if (filterContent.isEmpty()) // ERROR_LOG(m_logger) << "filterContent is empty, snapshot.id()="<< snapshot.id(); // std::shared_ptr pe_fltIfc; // session->getForeignGuiInterface(pe_fltIfc); // not in global // if (pe_fltIfc) // { // MarkerManagementApplyFilter appFilter(session, pe_fltIfc); // if (!appFilter.applyFilter(filterContent)) // { // ERROR_LOG(m_logger) << LOGFUNCTION << " Error while applying filters"; // } // } else // ERROR_LOG(m_logger) << LOGFUNCTION << " getForeignGuiInterface failed"; m_buttonBox->getButton(LtsWidgets::DialogButton::Load)->setEnabled(false); // start PlanEditor only once } //----------------------------------------------------------------------------------------------------- void SnapshotDialog::prepareIndex(const QModelIndex &idx) { const MarkerManagement::Snapshot &snapshot = m_model->getRowData(idx.row()); DEBUG_LOG(m_logger) << "Prepare index for snapshot with id = " << snapshot.id(); const int fileId = snapshot.fileContentId(); if (fileId > 0) // found the snapshot in db { DEBUG_LOG(m_logger) << "Loading file content with id = " << fileId; QByteArray content; if (m_flmGui->loadContent(fileId, content) && content.size() > 0) { QString timeStr = DTG::toStringMSec(snapshot.timeStamp()); QString name = QString::fromStdString(snapshot.name()); m_descriptions.append( QString::fromStdString(snapshot.description())); m_imageTitles.append( name + QStringLiteral(" (")+ timeStr + QStringLiteral(")")); m_images.append(QImage::fromData(content)); DEBUG_LOG(m_logger) << " title=" << m_imageTitles.last() << " description=" << m_descriptions.last() << " content.size=" << content.size(); } else { ERROR_LOG(m_logger) << "Cannot retrieve file content for fileId = " << fileId; } } else WARNING_LOG(m_logger) << "Not found: file id = 0 for snapshot id = " << snapshot.id(); } //----------------------------------------------------------------------------------------------------- void SnapshotDialog::onPrint() { const QModelIndexList selectedRows = m_ui->widget->selectedRows(); m_descriptions.clear(); m_imageTitles.clear(); m_images.clear(); DEBUG_LOG(m_logger) << "selectedRows.size()=" << selectedRows.size(); for (const QModelIndex &idx : selectedRows) prepareIndex(idx); if (m_images.size() > 0) LtsPrinting::printImages(m_session, this, tr("Snapshots"), m_images, m_imageTitles, m_descriptions); else ERROR_LOG(m_logger) << "No snapshots selected or no valid images to print"; } //----------------------------------------------------------------------------------------------------- bool SnapshotDialog::saveSnapshotPlan(const QByteArray &planXMLContent) { DEBUG_LOG(m_logger) << LOGFUNCTION << " save snapshot"; // store planXMLContent in snapshot m_currentSnapshot.planOverlay(planXMLContent.constData()); bool ok = m_requestHelper->startSingleRequest([this]() { return m_mrkFtc->snapshotModifyRequest(m_currentSnapshot,this); }); if (!ok) { ERROR_LOG(m_logger) << "Error while updating snapshot plan overlay"; LtsWidgets::LtsMessageDialog::showMessage(this, tr("Error while updating snapshot plan overlay"), tr("Saving Plan to Snapshot") ); } else { m_planEditorHasUpdatedThePlan = true; } return ok; } //----------------------------------------------------------------------------------------------------- bool SnapshotDialog::init(const std::shared_ptr &mrkDtc, const std::shared_ptr &mrkFtc, const std::shared_ptr &flmDtc, const std::shared_ptr &flmFtc, const std::shared_ptr &opmDtc, const std::shared_ptr &fltParticipipantIfc, const std::shared_ptr &fltAesIfc, const std::shared_ptr &fltBuildingComponentIfc ) { m_mrkDtc = mrkDtc; m_mrkFtc = mrkFtc; m_flmDtc = flmDtc; m_flmFtc = flmFtc; m_opmDtc = opmDtc; m_fltParticipantIfc = fltParticipipantIfc; m_fltAesIfc = fltAesIfc; m_fltBuildingComponentIfc = fltBuildingComponentIfc; m_model = new SnapshotDialogModel(this); m_model->initModel(m_mrkDtc, m_opmDtc, m_flmDtc, m_flmGui); auto proxy = new SnapshotDialogFilterProxyModel(this); proxy->setSourceModel(m_model); m_ui->widget->setModel(proxy); m_ui->widget->hideColumn(SnapshotDialogModel::Columns::ColImage); m_ui->widget->setInitialSearchContent(SnapshotDialogModel::ColOperator, LtsSessionData::LtsSessionData::getInstance()->getOperatorId()); connect(m_ui->widget, &LtsDataView::LtsDataView::signalSelectionChanged, this, &SnapshotDialog::enaDisa); connect(m_model, &SnapshotDialogModel::modelChanged, this, &SnapshotDialog::onRefreshCurrent); m_session->getFtc(m_srpFtc); if (!m_srpFtc) return false; std::shared_ptr spSitMapGuiIfc; m_session->getForeignGuiInterface(spSitMapGuiIfc); if (!spSitMapGuiIfc) return false; m_sitMapGuiIfc = spSitMapGuiIfc; // pay attention to mapReady: // A small delay is required. Otherwise SituationMapInit does not exists: QTimer::singleShot(1000, this, [this, spSitMapGuiIfc]() { if (!spSitMapGuiIfc->setMapReadyCallback(this, [this]() { enaDisa(); })) ERROR_LOG(m_logger) << LOGFUNCTION << " setMapReadyCallback failed" << ", m_session->toString " << m_session->toString() << ", this=" << this; }); enaDisa(); return true; } //----------------------------------------------------------------------------------------------------- void SnapshotDialog::aboutToClose() { DEBUG_LOG(m_logger) << "aboutToClose - start"; if (m_planEditorApp) { m_planEditorApp->disconnect(this); // disconnect me from m_planEditorApp //We get the session and "close the baseLayout" which will result in a perfect cleanup of the PlanEditor const int partitionId = m_session->partitionId(); auto pe_session = m_planEditorApp->getSession(IDL::PartitionType::Replay, partitionId); if (pe_session) { pe_session->baseLayout()->close(); } m_planEditorApp->deleteLater(); } m_planEditorApp = nullptr; } //----------------------------------------------------------------------------------------------------- void SnapshotDialog::enaDisa() { m_buttonBox->setToolTip(LtsWidgets::DialogButton::Load, QString()); // do not change selected row, if PlanEditor is running. m_ui->widget->setEnabled(m_planEditorApp.isNull()); QModelIndexList selectedRows = m_ui->widget->selectedRows(); const int cntSelectedRows = selectedRows.size(); // BTN Edit m_buttonBox->setButtonEnabled(LtsWidgets::DialogButton::Edit, (cntSelectedRows == 1)); // BTN Remove m_buttonBox->setButtonEnabled(LtsWidgets::DialogButton::Remove, (cntSelectedRows > 0)); // BTN Print m_buttonBox->setButtonEnabled(LtsWidgets::DialogButton::Print, (cntSelectedRows > 0)); // BTN Save (Save Settings) { const QModelIndex &idx = m_ui->widget->currentIndex(); const MarkerManagement::Snapshot &userSelSnapshot = m_model->getRowData(idx.row()); m_buttonBox->setButtonEnabled(LtsWidgets::DialogButton::Save, (m_currentSnapshot.id() > 0) && (userSelSnapshot.id() == m_currentSnapshot.id())); } // show btn: bool mapReady = false; bool snapshotIn2D = false; auto spSitMapGuiIfc = m_sitMapGuiIfc.lock(); if (spSitMapGuiIfc != nullptr) mapReady = spSitMapGuiIfc->isMapReady(); const QModelIndex idx = m_ui->widget->currentIndex(); const MarkerManagement::Snapshot &snapshot = m_model->getRowData(idx.row()); QJsonParseError error; QJsonDocument doc(QJsonDocument::fromJson(QByteArray::fromRawData(snapshot.mapSettings().c_str(), snapshot.mapSettings().size()), &error)); if (error.error == QJsonParseError::NoError) { QJsonObject root(doc.object()); const int mode = root[QStringLiteral("mode")].toInt(-1); snapshotIn2D = (mode == int(SituationMap::MapMode::MapMode_2D)); } QStringList errors; if (cntSelectedRows != 1) errors += tr("Select one snapshot to edit"); if (!spSitMapGuiIfc) errors += tr("Map interface missing"); if (m_planEditorApp) errors += tr("Plan editor already open"); if (error.error != QJsonParseError::NoError) errors += tr("Snapshot is missing critical map informations"); if (!snapshotIn2D) errors += tr("Unable to edit a snapshot that was taken in 3D mode - edit the snapshot and save it while in 2D mode"); if (!mapReady) errors += tr("Situation isn't ready yet. Please try again later."); m_buttonBox->setToolTip(LtsWidgets::DialogButton::Load, errors.join(QStringLiteral("
"))); // BTN Show bool bSelectedSnapshotActuallyInEditor = true; if (m_planEditorApp) bSelectedSnapshotActuallyInEditor = snapshot.id() == m_currentSnapshot.id(); m_buttonBox->setButtonEnabled(LtsWidgets::DialogButton::Show, (cntSelectedRows == 1) && mapReady && bSelectedSnapshotActuallyInEditor); // BTN Load (Edit Plan) const bool bHasPlanEditor = LtsSessionData::LtsSessionData::getInstance()->hasOperatorRight(OperatorRights::PlanEditor::TacticalPlansEditSnapShot) && LtsAppManager::LtsAppManager::getInstance()->hasApp(LtsAppDescriptor::App::PlanEditor); m_buttonBox->setButtonVisible(LtsWidgets::DialogButton::Load, bHasPlanEditor); m_buttonBox->setButtonEnabled(LtsWidgets::DialogButton::Load, (cntSelectedRows == 1) && mapReady && m_planEditorApp.isNull() /*&& snapshotIn2D*/); // BTN Remove const QVector &selectedSnapIds = selectedSnapshotIds(); if (selectedSnapIds.isEmpty()) m_buttonBox->setButtonEnabled(LtsWidgets::DialogButton::Remove, false); else m_buttonBox->setButtonEnabled(LtsWidgets::DialogButton::Remove, !selectedSnapIds.contains(m_currentSnapshot.id())); } //----------------------------------------------------------------------------------------------------- void SnapshotDialog::onEditMetaData() { const QModelIndex idx = m_ui->widget->currentIndex(); const MarkerManagement::Snapshot snapshot = m_model->getRowData(idx.row()); if (snapshot.id() <= 0) return; INFO_LOG(m_logger) << "current SnapshotId: " << snapshot.id(); if (snapshot.operatorId() == LtsSessionData::LtsSessionData::getInstance()->getOperatorId()) { // If snapshot is owned by current operator addDialog(LtsWidgets::initDialog(new SnapshotCreateDialog(this), m_mrkFtc, m_flmDtc, m_flmFtc, m_sitMapGuiIfc, m_fltParticipantIfc, m_fltAesIfc, m_fltBuildingComponentIfc, m_session, snapshot), m_menuGroup, QStringLiteral("EditSnapshot%1").arg(snapshot.id())); } else { // If snapshot belongs to an other operator // Show message 'Want to copy?' then do nothing or copy switch (LtsWidgets::LtsMessageDialog::showMessage(this, tr("You are not allowed to edit the snapshots of other operators.\nDo you want to make a copy of it?"), QString(), LtsWidgets::Behaviour::Modal, LtsWidgets::Button::Yes | LtsWidgets::Button::No)) { case LtsWidgets::Button::No: // Do nothing break; case LtsWidgets::Button::Yes: // Make a new snap with old data { Snapshot req(snapshot); req.id(0); req.name((tr("Copy - %1").arg(QString::fromStdString(snapshot.name()))).toStdString()); req.operatorId(LtsSessionData::LtsSessionData::getInstance()->getOperatorId()); req.fileContentId(0); //no preview image when copying a snapshot, later by edit metadata when we change the name, a new image will be saved m_requestHelper->startSingleRequest([req, this]() { return m_mrkFtc->snapshotModifyRequest(req, this); }); } break; default: // Can't happen but satisfies the compiler break; } } } //----------------------------------------------------------------------------------------------------- QVector SnapshotDialog::selectedSnapshotIds() const { const QModelIndexList selectedRows = m_ui->widget->selectedRows(); QVector selectedSnapshotIds; selectedSnapshotIds.reserve(selectedRows.size()); for (const QModelIndex &idx : selectedRows) { if (!idx.isValid()) continue; const MarkerManagement::Snapshot snapshot = m_model->getRowData(idx.row()); if (snapshot.id() <= 0) continue; selectedSnapshotIds.push_back(snapshot.id()); } // for return selectedSnapshotIds; } //----------------------------------------------------------------------------------------------------- void SnapshotDialog::onRemoveSnapshots() { const QModelIndexList selectedRows = m_ui->widget->selectedRows(); for (const QModelIndex &idx : selectedRows) { if (!idx.isValid()) continue; const MarkerManagement::Snapshot &snapshot = m_model->getRowData(idx.row()); if (snapshot.id() <= 0) continue; Core::RemoveElementRequest req(snapshot.id()); m_requestHelper->addRequest([req, this]() { return m_mrkFtc->snapshotRemoveRequest(req, this); }); if (snapshot.fileContentId() > 0) { Core::RemoveElementRequest req1(snapshot.fileContentId()); m_requestHelper->addRequest([req1, this]() { return m_flmFtc->fileContentRemoveRequest(req1, this); }); } } m_requestHelper->startRequests(); } //----------------------------------------------------------------------------------------------------- void SnapshotDialog::saveSnapshotFilter() { // Save MapSetting Changes auto spSitMapGuiIfc = m_sitMapGuiIfc.lock(); if (!spSitMapGuiIfc) { ERROR_LOG(m_logger) << LOGFUNCTION << ", no m_sitMapGuiIfc, return"; return; } // Save Filter, Filter = {operatorMapSettings, AesFilter, SymbolAppearance, (possibly modified) MovementTracks} // 1) operatorMapSettings/OMS const QByteArray mapSettings = spSitMapGuiIfc->operatorMapSettingsJson(); Snapshot req(m_currentSnapshot); req.mapSettings(mapSettings.data()); req.operatorId(LtsSessionData::LtsSessionData::getInstance()->getOperatorId()); DEBUG_LOG(m_logger) << "File content id (of preview image) = " << m_currentSnapshot.fileContentId(); req.fileContentId(m_fileId); // 2,3) AesFilter, SymbolAppearance, BuildingComponents MarkerManagementApplyFilter appFilter(m_session, m_fltParticipantIfc, m_fltAesIfc, m_fltBuildingComponentIfc); const QByteArray &filterParams = appFilter.getFilterParams(); req.filter(filterParams.constData()); // 4) MovementTracks const QByteArray &movementTracks = spSitMapGuiIfc->movementTrackManagerSettingsJson(); req.movementTracks(movementTracks.constData()); // 'planOverlay' is editable Plan/Overlay of snapshot: // it can be empty. A size is 0 will be ignored, means old value will remain at svc :-) if (!m_requestHelper->startSingleRequest([req, this]() { return m_mrkFtc->snapshotModifyRequest(req, this);})) ERROR_LOG(m_logger) << "Saving snapshot filter failed"; else { __unchange__(); // Do not update m_currentSnapshot. It´s done via onRefreshCurrent. Avoid race condition // m_currentSnapshot = req; } } //----------------------------------------------------------------------------------------------------- void SnapshotDialog::onSaveSettings() { // Update in Library const MarkerManagement::Snapshot &snapshot = m_currentSnapshot; const QString title = QString::fromStdString(snapshot.name()); m_fileId = snapshot.fileContentId(); saveSnapshotPreviewInLibrary(title); } // TODO: dup code, same in mrk_create_snapshot //------------------------------------------------------------------------------------------------------------------------ void SnapshotDialog::saveSnapshotPreviewInLibrary(const QString &snapshotTitle) { QString dtg = DTG::toStringMSec(QDateTime::currentMSecsSinceEpoch()); dtg.replace(QLatin1Char(' '), QLatin1Char('_')); const QString title = QStringLiteral("Snapshot_%1_%2_%3") .arg(dtg) .arg(LtsSessionData::LtsSessionData::getInstance()->getOperatorId()) .arg(snapshotTitle.left(20)); DEBUG_LOG(m_logger) << "saveSnapshotPreviewInLibrary: " << title; auto spSitMapGuiIfc = m_sitMapGuiIfc.lock(); if (!spSitMapGuiIfc->isMapReady()) { ERROR_LOG(m_logger) << LOGFUNCTION << " sitMapGuiInterface not ready"; return; } LtsWidgets::LtsBaseLayoutWidget *blw = m_session->baseLayout(); bool menuVisible = blw->isMenuVisible(); bool topBarVisible = blw->isTopBarVisible(); // make room for a full sized snapshot by removing the top bar, the detail bar and the menu - reshow after the image was successfully created blw->setMenuVisible(false); blw->setTopBarVisible(false); blw->hideVisibleLayouts(); std::function onImage = [this, title, menuVisible, topBarVisible](const QImage & image) { int initialFolderId = 1; int snapshotFolderId = 1; // folder_id = 1 -> Global documents as initial folder for admin session if (m_session->exerciseId() > 0) { initialFolderId = m_flmDtc->getExerciseFolderId(m_session->exerciseId()); snapshotFolderId = m_flmDtc->findFolder(initialFolderId, QStringLiteral("Snapshots")); } DEBUG_LOG(m_logger) << "Initial folder Id = " << initialFolderId << " snapshotFolderId =" << snapshotFolderId; const int folderId = snapshotFolderId > 0 ? snapshotFolderId : initialFolderId; FileManagement::FileContentModifyRequest request; request.id(m_currentSnapshot.fileContentId()); request.name(title.toStdString()); request.isTemplate(false); request.folderId(folderId); request.fileCategory(FileManagement::FileCategory::Exercise); request.fileStatus(FileManagement::FileStatus::Valid); request.operatorId(LtsSessionData::LtsSessionData::getInstance()->getOperatorId()); bool bIsPng; request.content() = LtsUtils::fromImage(image, &bIsPng); request.fileType(bIsPng ? FileManagement::FileType::Png : FileManagement::FileType::Jpg); request.isUsedBy(true); request.usedBy(1); request.lastModified(QDateTime::currentMSecsSinceEpoch()); if (m_session->exerciseId() > 0) request.exerciseId(m_session->exerciseId()); if (m_requestHelper->startSingleRequest([this, request]() { return m_flmFtc->fileContentModifyRequest(request, this); })) { m_fileId = m_requestHelper->getRepliedId(); DEBUG_LOG(m_logger) << LOGFUNCTION << " Saved file in library with id = " << m_fileId; saveSnapshotFilter(); if (m_currentSnapshot.fileContentId() != m_fileId) { DEBUG_LOG(m_logger) << LOGFUNCTION << " One more request needed!"; // should never happen at snapshot_dialog.cpp/modification m_currentSnapshot.fileContentId(m_fileId); } } else { ERROR_LOG(m_logger) << LOGFUNCTION << " Saved file in library with id = " << m_fileId << " failed"; } // done! LtsWidgets::LtsBaseLayoutWidget *blw = m_session->baseLayout(); blw->setMenuVisible(menuVisible); blw->setTopBarVisible(topBarVisible); blw->resetLayoutVisibility(); }; if (!spSitMapGuiIfc->toImage(this, onImage)) // => async { // reset directly after toImage failed blw->setMenuVisible(menuVisible); blw->setTopBarVisible(topBarVisible); blw->resetLayoutVisibility(); return; } } } // namespace MarkerManagement