diff --git a/src/application/MainWindow.cpp b/src/application/MainWindow.cpp index 33d3630..acdc026 100644 --- a/src/application/MainWindow.cpp +++ b/src/application/MainWindow.cpp @@ -81,6 +81,7 @@ ProjectWorkspaceState makeEvacuationScenarioDemoWorkspace() { .frame = std::move(fixture.frame), .risk = std::move(fixture.risk), .artifacts = std::move(fixture.artifacts), + .navigationView = SavedResultNavigationView::Bottleneck, }; workspace.batchResult = SavedScenarioBatchResultState{ .results = {*workspace.result}, @@ -454,6 +455,7 @@ void MainWindow::openProject(const ProjectMetadata& metadata) { workspace.result->frame, workspace.result->risk, workspace.result->artifacts, + workspace.result->navigationView, workspace.authoring.has_value() ? std::make_optional(initialStateFromSaved(*workspace.authoring, *importResult.layout)) : std::nullopt); @@ -500,16 +502,55 @@ void MainWindow::saveCurrentProject() { } } - if (auto* authoringWidget = visibleChild(centralWidget())) { + auto* authoringWidget = visibleChild(centralWidget()); + auto* batchResultWidget = visibleChild(centralWidget()); + auto* resultWidget = visibleChild(centralWidget()); + auto* runWidget = visibleChild(centralWidget()); + + const auto rootWidget = centralWidget(); + const auto widgetDepth = [rootWidget](QWidget* widget) { + int depth = 0; + for (auto* current = widget; current != nullptr; current = current->parentWidget()) { + if (current == rootWidget) { + return depth; + } + ++depth; + } + return -1; + }; + + QWidget* activeWorkflowWidget = nullptr; + int activeWorkflowDepth = -1; + const auto chooseActiveWorkflowWidget = [&](QWidget* widget) { + const int depth = widgetDepth(widget); + if (depth > activeWorkflowDepth) { + activeWorkflowDepth = depth; + activeWorkflowWidget = widget; + } + }; + chooseActiveWorkflowWidget(authoringWidget); + chooseActiveWorkflowWidget(batchResultWidget); + chooseActiveWorkflowWidget(resultWidget); + chooseActiveWorkflowWidget(runWidget); + + if (activeWorkflowWidget == authoringWidget) { workspace.activeView = ProjectWorkspaceView::ScenarioAuthoring; workspace.authoring = authoringWidget->currentSavedState(); - } else if (auto* batchResultWidget = visibleChild(centralWidget())) { + } else if (activeWorkflowWidget == batchResultWidget) { workspace.activeView = ProjectWorkspaceView::ScenarioResult; if (auto authoring = batchResultWidget->returnAuthoringState(); authoring.has_value()) { workspace.authoring = savedStateFromInitial(*authoring); } + auto results = batchResultWidget->results(); + if (!results.empty()) { + const auto index = std::clamp( + batchResultWidget->currentResultIndex(), + 0, + static_cast(results.size()) - 1); + results[static_cast(index)].navigationView = batchResultWidget->currentSavedNavigationView(); + } workspace.batchResult = SavedScenarioBatchResultState{ - .results = batchResultWidget->results(), + .results = std::move(results), .currentResultIndex = batchResultWidget->currentResultIndex(), }; if (!workspace.batchResult->results.empty()) { @@ -519,7 +560,7 @@ void MainWindow::saveCurrentProject() { static_cast(workspace.batchResult->results.size()) - 1); workspace.result = workspace.batchResult->results[static_cast(index)]; } - } else if (auto* resultWidget = visibleChild(centralWidget())) { + } else if (activeWorkflowWidget == resultWidget) { workspace.activeView = ProjectWorkspaceView::ScenarioResult; if (resultWidget->returnAuthoringState().has_value()) { workspace.authoring = savedStateFromInitial(*resultWidget->returnAuthoringState()); @@ -529,8 +570,9 @@ void MainWindow::saveCurrentProject() { .frame = resultWidget->frame(), .risk = resultWidget->risk(), .artifacts = resultWidget->artifacts(), + .navigationView = resultWidget->currentSavedNavigationView(), }; - } else if (auto* runWidget = visibleChild(centralWidget())) { + } else if (activeWorkflowWidget == runWidget) { if (runWidget->returnAuthoringState().has_value()) { workspace.authoring = savedStateFromInitial(*runWidget->returnAuthoringState()); } @@ -732,6 +774,7 @@ void MainWindow::showScenarioResult( const safecrowd::domain::SimulationFrame& frame, const safecrowd::domain::ScenarioRiskSnapshot& risk, const safecrowd::domain::ScenarioResultArtifacts& artifacts, + SavedResultNavigationView savedNavigationView, std::optional returnAuthoringState) { setCentralWidget(new ScenarioResultWidget( currentProject_.name, @@ -755,6 +798,7 @@ void MainWindow::showScenarioResult( showLayoutReview(currentProject_); } }, + savedNavigationView, std::move(returnAuthoringState), this)); } diff --git a/src/application/MainWindow.h b/src/application/MainWindow.h index e488996..0d6cbbc 100644 --- a/src/application/MainWindow.h +++ b/src/application/MainWindow.h @@ -58,6 +58,7 @@ class MainWindow : public QMainWindow { const safecrowd::domain::SimulationFrame& frame, const safecrowd::domain::ScenarioRiskSnapshot& risk, const safecrowd::domain::ScenarioResultArtifacts& artifacts, + SavedResultNavigationView savedNavigationView = SavedResultNavigationView::Bottleneck, std::optional returnAuthoringState = std::nullopt); safecrowd::domain::SafeCrowdDomain& domain_; diff --git a/src/application/ProjectPersistence.cpp b/src/application/ProjectPersistence.cpp index 84a306b..76c734f 100644 --- a/src/application/ProjectPersistence.cpp +++ b/src/application/ProjectPersistence.cpp @@ -1076,6 +1076,157 @@ safecrowd::domain::ScenarioRiskSnapshot riskSnapshotFromJson(const QJsonObject& return risk; } +QJsonObject densityCellMetricToJson(const safecrowd::domain::DensityCellMetric& cell) { + QJsonObject object; + object["center"] = pointArray(cell.center); + object["cellMin"] = pointArray(cell.cellMin); + object["cellMax"] = pointArray(cell.cellMax); + object["floorId"] = QString::fromStdString(cell.floorId); + object["agentCount"] = static_cast(cell.agentCount); + object["densityPeoplePerSquareMeter"] = cell.densityPeoplePerSquareMeter; + return object; +} + +safecrowd::domain::DensityCellMetric densityCellMetricFromJson(const QJsonObject& object) { + return { + .center = pointFromJson(object.value("center")), + .cellMin = pointFromJson(object.value("cellMin")), + .cellMax = pointFromJson(object.value("cellMax")), + .floorId = object.value("floorId").toString().toStdString(), + .agentCount = static_cast(object.value("agentCount").toInteger()), + .densityPeoplePerSquareMeter = object.value("densityPeoplePerSquareMeter").toDouble(), + }; +} + +QJsonObject densityFieldSnapshotToJson(const safecrowd::domain::DensityFieldSnapshot& snapshot) { + QJsonObject object; + object["timeSeconds"] = snapshot.timeSeconds; + object["cellSizeMeters"] = snapshot.cellSizeMeters; + QJsonArray cells; + for (const auto& cell : snapshot.cells) { + cells.append(densityCellMetricToJson(cell)); + } + object["cells"] = cells; + return object; +} + +safecrowd::domain::DensityFieldSnapshot densityFieldSnapshotFromJson(const QJsonObject& object) { + safecrowd::domain::DensityFieldSnapshot snapshot; + snapshot.timeSeconds = object.value("timeSeconds").toDouble(); + snapshot.cellSizeMeters = object.value("cellSizeMeters").toDouble(); + for (const auto& value : object.value("cells").toArray()) { + snapshot.cells.push_back(densityCellMetricFromJson(value.toObject())); + } + return snapshot; +} + +QJsonObject densitySummaryToJson(const safecrowd::domain::DensitySummary& summary) { + QJsonObject object; + object["cellSizeMeters"] = summary.cellSizeMeters; + object["highDensityThresholdPeoplePerSquareMeter"] = summary.highDensityThresholdPeoplePerSquareMeter; + object["peakDensityPeoplePerSquareMeter"] = summary.peakDensityPeoplePerSquareMeter; + object["peakAgentCount"] = static_cast(summary.peakAgentCount); + object["peakAtSeconds"] = optionalDoubleToJson(summary.peakAtSeconds); + if (summary.peakCell.has_value()) { + object["peakCell"] = densityCellMetricToJson(*summary.peakCell); + } + object["highDensityDurationSeconds"] = summary.highDensityDurationSeconds; + QJsonArray peakCells; + for (const auto& cell : summary.peakCells) { + peakCells.append(densityCellMetricToJson(cell)); + } + object["peakCells"] = peakCells; + object["peakField"] = densityFieldSnapshotToJson(summary.peakField); + return object; +} + +safecrowd::domain::DensitySummary densitySummaryFromJson(const QJsonObject& object) { + safecrowd::domain::DensitySummary summary; + summary.cellSizeMeters = object.value("cellSizeMeters").toDouble(); + summary.highDensityThresholdPeoplePerSquareMeter = + object.value("highDensityThresholdPeoplePerSquareMeter").toDouble(4.0); + summary.peakDensityPeoplePerSquareMeter = object.value("peakDensityPeoplePerSquareMeter").toDouble(); + summary.peakAgentCount = static_cast(object.value("peakAgentCount").toInteger()); + summary.peakAtSeconds = optionalDoubleFromJson(object.value("peakAtSeconds")); + if (object.value("peakCell").isObject()) { + summary.peakCell = densityCellMetricFromJson(object.value("peakCell").toObject()); + } + summary.highDensityDurationSeconds = object.value("highDensityDurationSeconds").toDouble(); + for (const auto& value : object.value("peakCells").toArray()) { + summary.peakCells.push_back(densityCellMetricFromJson(value.toObject())); + } + if (object.value("peakField").isObject()) { + summary.peakField = densityFieldSnapshotFromJson(object.value("peakField").toObject()); + } + return summary; +} + +QJsonObject exitUsageMetricToJson(const safecrowd::domain::ExitUsageMetric& exit) { + QJsonObject object; + object["exitZoneId"] = QString::fromStdString(exit.exitZoneId); + object["exitLabel"] = QString::fromStdString(exit.exitLabel); + object["floorId"] = QString::fromStdString(exit.floorId); + object["evacuatedCount"] = static_cast(exit.evacuatedCount); + object["usageRatio"] = exit.usageRatio; + object["lastExitTimeSeconds"] = optionalDoubleToJson(exit.lastExitTimeSeconds); + return object; +} + +safecrowd::domain::ExitUsageMetric exitUsageMetricFromJson(const QJsonObject& object) { + safecrowd::domain::ExitUsageMetric exit; + exit.exitZoneId = object.value("exitZoneId").toString().toStdString(); + exit.exitLabel = object.value("exitLabel").toString().toStdString(); + exit.floorId = object.value("floorId").toString().toStdString(); + exit.evacuatedCount = static_cast(object.value("evacuatedCount").toInteger()); + exit.usageRatio = object.value("usageRatio").toDouble(); + exit.lastExitTimeSeconds = optionalDoubleFromJson(object.value("lastExitTimeSeconds")); + return exit; +} + +QJsonObject zoneCompletionMetricToJson(const safecrowd::domain::ZoneCompletionMetric& zone) { + QJsonObject object; + object["zoneId"] = QString::fromStdString(zone.zoneId); + object["zoneLabel"] = QString::fromStdString(zone.zoneLabel); + object["floorId"] = QString::fromStdString(zone.floorId); + object["initialCount"] = static_cast(zone.initialCount); + object["evacuatedCount"] = static_cast(zone.evacuatedCount); + object["lastCompletionTimeSeconds"] = optionalDoubleToJson(zone.lastCompletionTimeSeconds); + return object; +} + +safecrowd::domain::ZoneCompletionMetric zoneCompletionMetricFromJson(const QJsonObject& object) { + safecrowd::domain::ZoneCompletionMetric zone; + zone.zoneId = object.value("zoneId").toString().toStdString(); + zone.zoneLabel = object.value("zoneLabel").toString().toStdString(); + zone.floorId = object.value("floorId").toString().toStdString(); + zone.initialCount = static_cast(object.value("initialCount").toInteger()); + zone.evacuatedCount = static_cast(object.value("evacuatedCount").toInteger()); + zone.lastCompletionTimeSeconds = optionalDoubleFromJson(object.value("lastCompletionTimeSeconds")); + return zone; +} + +QJsonObject placementCompletionMetricToJson(const safecrowd::domain::PlacementCompletionMetric& placement) { + QJsonObject object; + object["placementId"] = QString::fromStdString(placement.placementId); + object["zoneId"] = QString::fromStdString(placement.zoneId); + object["floorId"] = QString::fromStdString(placement.floorId); + object["initialCount"] = static_cast(placement.initialCount); + object["evacuatedCount"] = static_cast(placement.evacuatedCount); + object["lastCompletionTimeSeconds"] = optionalDoubleToJson(placement.lastCompletionTimeSeconds); + return object; +} + +safecrowd::domain::PlacementCompletionMetric placementCompletionMetricFromJson(const QJsonObject& object) { + safecrowd::domain::PlacementCompletionMetric placement; + placement.placementId = object.value("placementId").toString().toStdString(); + placement.zoneId = object.value("zoneId").toString().toStdString(); + placement.floorId = object.value("floorId").toString().toStdString(); + placement.initialCount = static_cast(object.value("initialCount").toInteger()); + placement.evacuatedCount = static_cast(object.value("evacuatedCount").toInteger()); + placement.lastCompletionTimeSeconds = optionalDoubleFromJson(object.value("lastCompletionTimeSeconds")); + return placement; +} + QJsonObject resultArtifactsToJson(const safecrowd::domain::ScenarioResultArtifacts& artifacts) { QJsonObject object; QJsonArray progress; @@ -1100,6 +1251,8 @@ QJsonObject resultArtifactsToJson(const safecrowd::domain::ScenarioResultArtifac timing["t90Seconds"] = optionalDoubleToJson(artifacts.timingSummary.t90Seconds); timing["t95Seconds"] = optionalDoubleToJson(artifacts.timingSummary.t95Seconds); timing["finalEvacuationTimeSeconds"] = optionalDoubleToJson(artifacts.timingSummary.finalEvacuationTimeSeconds); + timing["targetTimeSeconds"] = artifacts.timingSummary.targetTimeSeconds; + timing["marginSeconds"] = optionalDoubleToJson(artifacts.timingSummary.marginSeconds); if (artifacts.timingSummary.t90Frame.has_value()) { timing["t90Frame"] = simulationFrameToJson(*artifacts.timingSummary.t90Frame); } @@ -1107,6 +1260,27 @@ QJsonObject resultArtifactsToJson(const safecrowd::domain::ScenarioResultArtifac timing["t95Frame"] = simulationFrameToJson(*artifacts.timingSummary.t95Frame); } object["timingSummary"] = timing; + + object["densitySummary"] = densitySummaryToJson(artifacts.densitySummary); + + QJsonArray exitUsage; + for (const auto& exit : artifacts.exitUsage) { + exitUsage.append(exitUsageMetricToJson(exit)); + } + object["exitUsage"] = exitUsage; + + QJsonArray zoneCompletion; + for (const auto& zone : artifacts.zoneCompletion) { + zoneCompletion.append(zoneCompletionMetricToJson(zone)); + } + object["zoneCompletion"] = zoneCompletion; + + QJsonArray placementCompletion; + for (const auto& placement : artifacts.placementCompletion) { + placementCompletion.append(placementCompletionMetricToJson(placement)); + } + object["placementCompletion"] = placementCompletion; + return object; } @@ -1130,12 +1304,28 @@ safecrowd::domain::ScenarioResultArtifacts resultArtifactsFromJson(const QJsonOb artifacts.timingSummary.t90Seconds = optionalDoubleFromJson(timing.value("t90Seconds")); artifacts.timingSummary.t95Seconds = optionalDoubleFromJson(timing.value("t95Seconds")); artifacts.timingSummary.finalEvacuationTimeSeconds = optionalDoubleFromJson(timing.value("finalEvacuationTimeSeconds")); + artifacts.timingSummary.targetTimeSeconds = timing.value("targetTimeSeconds").toDouble(); + artifacts.timingSummary.marginSeconds = optionalDoubleFromJson(timing.value("marginSeconds")); if (timing.value("t90Frame").isObject()) { artifacts.timingSummary.t90Frame = simulationFrameFromJson(timing.value("t90Frame").toObject()); } if (timing.value("t95Frame").isObject()) { artifacts.timingSummary.t95Frame = simulationFrameFromJson(timing.value("t95Frame").toObject()); } + + if (object.value("densitySummary").isObject()) { + artifacts.densitySummary = densitySummaryFromJson(object.value("densitySummary").toObject()); + } + for (const auto& value : object.value("exitUsage").toArray()) { + artifacts.exitUsage.push_back(exitUsageMetricFromJson(value.toObject())); + } + for (const auto& value : object.value("zoneCompletion").toArray()) { + artifacts.zoneCompletion.push_back(zoneCompletionMetricFromJson(value.toObject())); + } + for (const auto& value : object.value("placementCompletion").toArray()) { + artifacts.placementCompletion.push_back(placementCompletionMetricFromJson(value.toObject())); + } + return artifacts; } @@ -1185,6 +1375,7 @@ QJsonObject resultStateToJson(const SavedScenarioResultState& result) { object["frame"] = simulationFrameToJson(result.frame); object["risk"] = riskSnapshotToJson(result.risk); object["artifacts"] = resultArtifactsToJson(result.artifacts); + object["navigationView"] = static_cast(result.navigationView); return object; } @@ -1194,6 +1385,7 @@ SavedScenarioResultState resultStateFromJson(const QJsonObject& object) { .frame = simulationFrameFromJson(object.value("frame").toObject()), .risk = riskSnapshotFromJson(object.value("risk").toObject()), .artifacts = resultArtifactsFromJson(object.value("artifacts").toObject()), + .navigationView = static_cast(object.value("navigationView").toInt()), }; } diff --git a/src/application/ProjectWorkspaceState.h b/src/application/ProjectWorkspaceState.h index 35af79d..04026c0 100644 --- a/src/application/ProjectWorkspaceState.h +++ b/src/application/ProjectWorkspaceState.h @@ -30,6 +30,13 @@ enum class SavedRightPanelMode { Run, }; +enum class SavedResultNavigationView { + Bottleneck, + Hotspot, + Zone, + Groups, +}; + struct SavedScenarioState { safecrowd::domain::ScenarioDraft draft{}; std::string baseScenarioId{}; @@ -48,6 +55,7 @@ struct SavedScenarioResultState { safecrowd::domain::SimulationFrame frame{}; safecrowd::domain::ScenarioRiskSnapshot risk{}; safecrowd::domain::ScenarioResultArtifacts artifacts{}; + SavedResultNavigationView navigationView{SavedResultNavigationView::Bottleneck}; }; struct SavedScenarioBatchResultState { diff --git a/src/application/ScenarioAuthoringWidget.cpp b/src/application/ScenarioAuthoringWidget.cpp index e421d42..c94b93d 100644 --- a/src/application/ScenarioAuthoringWidget.cpp +++ b/src/application/ScenarioAuthoringWidget.cpp @@ -741,7 +741,6 @@ ScenarioAuthoringWidget::ScenarioAuthoringWidget( scenario.stagedForRun = false; } } - rightPanelMode_ = RightPanelMode::Scenario; initializeUi(false); } @@ -772,7 +771,18 @@ SavedScenarioAuthoringState ScenarioAuthoringWidget::currentSavedState() const { SavedScenarioAuthoringState state; state.currentScenarioIndex = currentScenarioIndex_; state.navigationView = savedNavigationView(navigationView_); - state.rightPanelMode = SavedRightPanelMode::Scenario; + switch (rightPanelMode_) { + case RightPanelMode::None: + state.rightPanelMode = SavedRightPanelMode::None; + break; + case RightPanelMode::Run: + state.rightPanelMode = SavedRightPanelMode::Run; + break; + case RightPanelMode::Scenario: + default: + state.rightPanelMode = SavedRightPanelMode::Scenario; + break; + } state.scenarios.reserve(scenarios_.size()); for (const auto& scenario : scenarios_) { auto draft = scenario.draft; @@ -1251,9 +1261,18 @@ void ScenarioAuthoringWidget::refreshRightPanel() { return; } - rightPanelMode_ = RightPanelMode::Scenario; + if (rightPanelMode_ == RightPanelMode::None) { + shell_->setReviewPanelVisible(false); + return; + } + shell_->setReviewPanelVisible(true); - shell_->setReviewPanel(createScenarioPanel()); + if (rightPanelMode_ == RightPanelMode::Run) { + shell_->setReviewPanel(createRunPanel()); + } else { + rightPanelMode_ = RightPanelMode::Scenario; + shell_->setReviewPanel(createScenarioPanel()); + } refreshScenarioSwitcher(); refreshInspector(); } @@ -1436,6 +1455,40 @@ void ScenarioAuthoringWidget::showScenarioNameDialog(int sourceIndex) { createScenarioWithName(name, sourceIndex); } +QWidget* ScenarioAuthoringWidget::createRunPanel() { + auto* panel = new QWidget(shell_); + auto* layout = new QVBoxLayout(panel); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(12); + layout->addWidget(createLabel("Run", panel, ui::FontRole::Title)); + + stagedScenariosLabel_ = createLabel("", panel); + stagedScenariosLabel_->setStyleSheet(ui::mutedTextStyleSheet()); + layout->addWidget(stagedScenariosLabel_); + + executeRunButton_ = new QPushButton("Run Staged Scenarios", panel); + executeRunButton_->setFont(ui::font(ui::FontRole::Body)); + executeRunButton_->setStyleSheet(ui::primaryButtonStyleSheet()); + executeRunButton_->setEnabled(false); + layout->addWidget(executeRunButton_); + + auto* editButton = new QPushButton("Edit Scenario", panel); + editButton->setFont(ui::font(ui::FontRole::Body)); + editButton->setStyleSheet(ui::secondaryButtonStyleSheet()); + layout->addWidget(editButton); + layout->addStretch(1); + + connect(executeRunButton_, &QPushButton::clicked, this, [this]() { + runStagedScenarios(); + }); + connect(editButton, &QPushButton::clicked, this, [this]() { + rightPanelMode_ = RightPanelMode::Scenario; + refreshRightPanel(); + }); + + return panel; +} + QWidget* ScenarioAuthoringWidget::createScenarioPanel() { auto* inspector = new QWidget(shell_); auto* inspectorLayout = new QVBoxLayout(inspector); diff --git a/src/application/ScenarioAuthoringWidget.h b/src/application/ScenarioAuthoringWidget.h index 8f241ea..a4291cf 100644 --- a/src/application/ScenarioAuthoringWidget.h +++ b/src/application/ScenarioAuthoringWidget.h @@ -90,6 +90,7 @@ class ScenarioAuthoringWidget : public QWidget { void updateCurrentScenarioPlacements(const std::vector& placements); void showEmptyCanvas(); void showScenarioNameDialog(int sourceIndex); + QWidget* createRunPanel(); QWidget* createScenarioPanel(); ScenarioState* currentScenario(); const ScenarioState* currentScenario() const; diff --git a/src/application/ScenarioBatchResultWidget.cpp b/src/application/ScenarioBatchResultWidget.cpp index 49050d7..8732059 100644 --- a/src/application/ScenarioBatchResultWidget.cpp +++ b/src/application/ScenarioBatchResultWidget.cpp @@ -546,6 +546,34 @@ ScenarioAuthoringWidget::ScenarioState scenarioStateFromDraft( return state; } +ScenarioResultNavigationView resultNavigationViewFromSaved(SavedResultNavigationView view) { + switch (view) { + case SavedResultNavigationView::Hotspot: + return ScenarioResultNavigationView::Hotspot; + case SavedResultNavigationView::Zone: + return ScenarioResultNavigationView::Zone; + case SavedResultNavigationView::Groups: + return ScenarioResultNavigationView::Groups; + case SavedResultNavigationView::Bottleneck: + default: + return ScenarioResultNavigationView::Bottleneck; + } +} + +SavedResultNavigationView savedResultNavigationView(ScenarioResultNavigationView view) { + switch (view) { + case ScenarioResultNavigationView::Hotspot: + return SavedResultNavigationView::Hotspot; + case ScenarioResultNavigationView::Zone: + return SavedResultNavigationView::Zone; + case ScenarioResultNavigationView::Groups: + return SavedResultNavigationView::Groups; + case ScenarioResultNavigationView::Bottleneck: + default: + return SavedResultNavigationView::Bottleneck; + } +} + } // namespace ScenarioBatchResultWidget::ScenarioBatchResultWidget( @@ -570,6 +598,9 @@ ScenarioBatchResultWidget::ScenarioBatchResultWidget( if (currentResultIndex_ < 0 || currentResultIndex_ >= static_cast(results_.size())) { currentResultIndex_ = 0; } + if (!results_.empty()) { + resultNavigationView_ = resultNavigationViewFromSaved(results_[static_cast(currentResultIndex_)].navigationView); + } const auto baselineIndex = baselineResultIndex(); if (results_.size() <= 2) { for (int index = 0; index < static_cast(results_.size()); ++index) { @@ -622,6 +653,10 @@ int ScenarioBatchResultWidget::currentResultIndex() const noexcept { return currentResultIndex_; } +SavedResultNavigationView ScenarioBatchResultWidget::currentSavedNavigationView() const noexcept { + return savedResultNavigationView(resultNavigationView_); +} + std::optional ScenarioBatchResultWidget::returnAuthoringState() const { return returnAuthoringState_; } diff --git a/src/application/ScenarioBatchResultWidget.h b/src/application/ScenarioBatchResultWidget.h index 9b70585..5304557 100644 --- a/src/application/ScenarioBatchResultWidget.h +++ b/src/application/ScenarioBatchResultWidget.h @@ -40,6 +40,7 @@ class ScenarioBatchResultWidget : public QWidget { const std::vector& results() const noexcept; int currentResultIndex() const noexcept; + SavedResultNavigationView currentSavedNavigationView() const noexcept; std::optional returnAuthoringState() const; private: diff --git a/src/application/ScenarioResultWidget.cpp b/src/application/ScenarioResultWidget.cpp index 18bc287..fcac1b6 100644 --- a/src/application/ScenarioResultWidget.cpp +++ b/src/application/ScenarioResultWidget.cpp @@ -971,6 +971,34 @@ QWidget* createResultPanel( return panel; } +ScenarioResultNavigationView resultNavigationViewFromSaved(SavedResultNavigationView view) { + switch (view) { + case SavedResultNavigationView::Hotspot: + return ScenarioResultNavigationView::Hotspot; + case SavedResultNavigationView::Zone: + return ScenarioResultNavigationView::Zone; + case SavedResultNavigationView::Groups: + return ScenarioResultNavigationView::Groups; + case SavedResultNavigationView::Bottleneck: + default: + return ScenarioResultNavigationView::Bottleneck; + } +} + +SavedResultNavigationView savedResultNavigationView(ScenarioResultNavigationView view) { + switch (view) { + case ScenarioResultNavigationView::Hotspot: + return SavedResultNavigationView::Hotspot; + case ScenarioResultNavigationView::Zone: + return SavedResultNavigationView::Zone; + case ScenarioResultNavigationView::Groups: + return SavedResultNavigationView::Groups; + case ScenarioResultNavigationView::Bottleneck: + default: + return SavedResultNavigationView::Bottleneck; + } +} + } // namespace ScenarioResultWidget::ScenarioResultWidget( @@ -983,6 +1011,7 @@ ScenarioResultWidget::ScenarioResultWidget( std::function saveProjectHandler, std::function openProjectHandler, std::function backToLayoutReviewHandler, + SavedResultNavigationView savedNavigationView, std::optional returnAuthoringState, QWidget* parent) : QWidget(parent), @@ -996,6 +1025,8 @@ ScenarioResultWidget::ScenarioResultWidget( saveProjectHandler_(std::move(saveProjectHandler)), openProjectHandler_(std::move(openProjectHandler)), backToLayoutReviewHandler_(std::move(backToLayoutReviewHandler)) { + resultNavigationView_ = resultNavigationViewFromSaved(savedNavigationView); + auto* rootLayout = new QVBoxLayout(this); rootLayout->setContentsMargins(0, 0, 0, 0); rootLayout->setSpacing(0); @@ -1099,6 +1130,10 @@ const safecrowd::domain::ScenarioResultArtifacts& ScenarioResultWidget::artifact return artifacts_; } +SavedResultNavigationView ScenarioResultWidget::currentSavedNavigationView() const noexcept { + return savedResultNavigationView(resultNavigationView_); +} + const std::optional& ScenarioResultWidget::returnAuthoringState() const noexcept { return returnAuthoringState_; } diff --git a/src/application/ScenarioResultWidget.h b/src/application/ScenarioResultWidget.h index d713871..a430d64 100644 --- a/src/application/ScenarioResultWidget.h +++ b/src/application/ScenarioResultWidget.h @@ -31,6 +31,7 @@ class ScenarioResultWidget : public QWidget { std::function saveProjectHandler, std::function openProjectHandler, std::function backToLayoutReviewHandler, + SavedResultNavigationView savedNavigationView = SavedResultNavigationView::Bottleneck, std::optional returnAuthoringState = std::nullopt, QWidget* parent = nullptr); @@ -38,6 +39,7 @@ class ScenarioResultWidget : public QWidget { const safecrowd::domain::SimulationFrame& frame() const noexcept; const safecrowd::domain::ScenarioRiskSnapshot& risk() const noexcept; const safecrowd::domain::ScenarioResultArtifacts& artifacts() const noexcept; + SavedResultNavigationView currentSavedNavigationView() const noexcept; const std::optional& returnAuthoringState() const noexcept; private: diff --git a/src/application/ScenarioRunWidget.cpp b/src/application/ScenarioRunWidget.cpp index 0d1cf38..abf61c3 100644 --- a/src/application/ScenarioRunWidget.cpp +++ b/src/application/ScenarioRunWidget.cpp @@ -796,6 +796,7 @@ void ScenarioRunWidget::showResults() { } }, backToLayoutReviewHandler_, + SavedResultNavigationView::Bottleneck, returnAuthoringState_, this); } else {