@@ -12,15 +12,20 @@ package cz.vutbr.fit.interlockSim.gui
1212
1313import cz.vutbr.fit.interlockSim.context.EditingContext
1414import cz.vutbr.fit.interlockSim.context.JvmEditingContextFactory
15+ import cz.vutbr.fit.interlockSim.context.SimulationContext
16+ import cz.vutbr.fit.interlockSim.context.SimulationContextFactory
1517import cz.vutbr.fit.interlockSim.xml.XMLContextFactory
1618import org.koin.mp.KoinPlatform.getKoin
1719import java.awt.event.ActionEvent
20+ import java.awt.event.KeyEvent
1821import java.io.File
1922import javax.swing.AbstractAction
2023import javax.swing.JFileChooser
2124import javax.swing.JMenu
2225import javax.swing.JMenuBar
26+ import javax.swing.JMenuItem
2327import javax.swing.JOptionPane
28+ import javax.swing.KeyStroke
2429
2530/* *
2631 * Application menu bar with File and Help menus
@@ -265,6 +270,90 @@ class MenuBar : JMenuBar() {
265270 }
266271 }
267272
273+ /* *
274+ * Shows a file chooser, loads the selected XML as a [SimulationContext], sets it on the
275+ * [Frame] and immediately starts the simulation.
276+ *
277+ * **Resource management:** The intermediate [EditingContext] created by
278+ * [JvmEditingContextFactory.createContext] is wrapped in `use {}` to ensure its Koin
279+ * scope is closed after the [SimulationContext] transformation, preventing a resource
280+ * leak of the temporary editing context.
281+ *
282+ * **Report types:** All report types are enabled on the [SimulationContext] before
283+ * passing it to [Frame.setContext] so that [AnimationController] and
284+ * [cz.vutbr.fit.interlockSim.gui.animation.EventTimelinePanel] receive property-change
285+ * events and the animation is not visually frozen.
286+ *
287+ * **Modification tracker:** The tracker is cleared before switching to simulation mode
288+ * so that the "unsaved changes" path in [Frame.handleWindowClosing] does not attempt to
289+ * save a [SimulationContext] through the editor's save logic.
290+ */
291+ private inner class StartSimulationAction : AbstractAction (" Start..." ) {
292+ override fun actionPerformed (e : ActionEvent ) {
293+ val fileChooser = JFileChooser (System .getProperty(" user.dir" ))
294+ fileChooser.dialogTitle = " Start Simulation"
295+
296+ val returnValue = fileChooser.showOpenDialog(this @MenuBar)
297+ if (returnValue != JFileChooser .APPROVE_OPTION ) return
298+
299+ val selectedFile: File = fileChooser.selectedFile
300+
301+ try {
302+ val editingContextFactory = getKoin().get<JvmEditingContextFactory >()
303+ val simulationContextFactory = getKoin().get<SimulationContextFactory >()
304+
305+ // Wrap intermediate EditingContext in use{} to close its Koin scope after
306+ // transformation, avoiding a resource leak.
307+ val simContext =
308+ editingContextFactory.createContext(selectedFile).use { editCtx ->
309+ simulationContextFactory.createContext(editCtx as EditingContext )
310+ }
311+
312+ // Enable all report types so AnimationController and EventTimelinePanel receive
313+ // property-change events (without this the animation stays visually frozen).
314+ simContext.addReportTypes(* SimulationContext .ReportType .values())
315+
316+ val frame = getKoin().get<Frame >()
317+
318+ // Clear dirty flag before switching to simulation mode. If the user had unsaved
319+ // edits, continuing in simulation mode implicitly discards them; the window-close
320+ // handler must not try to save a non-existent EditingContext afterwards.
321+ frame.modificationTracker.markClean()
322+ frame.modificationTracker.setCurrentFile(null )
323+
324+ frame.setContext(simContext)
325+ frame.startSimulation()
326+ } catch (exception: Exception ) {
327+ JOptionPane .showMessageDialog(
328+ this @MenuBar,
329+ " Failed to start simulation: ${exception.message} \n\n " +
330+ " Ensure the file is a valid railway network XML." ,
331+ " Cannot Start Simulation" ,
332+ JOptionPane .ERROR_MESSAGE
333+ )
334+ }
335+ }
336+ }
337+
338+ /* * Terminates the currently running simulation via [Frame.stopSimulation]. */
339+ private inner class StopSimulationAction : AbstractAction (" Stop" ) {
340+ override fun actionPerformed (e : ActionEvent ) {
341+ val frame = getKoin().get<Frame >()
342+ frame.stopSimulation()
343+ }
344+ }
345+
346+ /* * Sets the simulation speed multiplier via [SimulationController.setSpeed]. */
347+ private inner class SetSpeedAction (
348+ private val label : String ,
349+ private val multiplier : Double ,
350+ ) : AbstractAction(label) {
351+ override fun actionPerformed (e : ActionEvent ) {
352+ val frame = getKoin().get<Frame >()
353+ frame.simulationController.setSpeed(multiplier)
354+ }
355+ }
356+
268357 private inner class InfoAction (
269358 private val infoName : String ,
270359 private val text : String
@@ -276,6 +365,7 @@ class MenuBar : JMenuBar() {
276365
277366 init {
278367 add(fileMenu())
368+ add(simulationMenu())
279369 add(helpMenu())
280370 }
281371
@@ -289,6 +379,37 @@ class MenuBar : JMenuBar() {
289379 return menu
290380 }
291381
382+ /* *
383+ * Builds the "Simulation" menu with Start/Stop actions and a Speed submenu.
384+ *
385+ * Speed presets (0.1x, 0.5x, 1x, 2x, 10x) have keyboard accelerators (keys 1–5)
386+ * so that the user can change the speed without reaching for the mouse during a run.
387+ */
388+ private fun simulationMenu (): JMenu {
389+ val menu = JMenu (" Simulation" )
390+ menu.add(StartSimulationAction ())
391+ menu.add(StopSimulationAction ())
392+ menu.addSeparator()
393+
394+ val speedMenu = JMenu (" Speed" )
395+ val speedPresets =
396+ listOf (
397+ Triple (" 0.1x" , 0.1 , KeyEvent .VK_1 ),
398+ Triple (" 0.5x" , 0.5 , KeyEvent .VK_2 ),
399+ Triple (" 1x" , 1.0 , KeyEvent .VK_3 ),
400+ Triple (" 2x" , 2.0 , KeyEvent .VK_4 ),
401+ Triple (" 10x" , 10.0 , KeyEvent .VK_5 ),
402+ )
403+ for ((label, multiplier, keyCode) in speedPresets) {
404+ val item = JMenuItem (SetSpeedAction (label, multiplier))
405+ item.accelerator = KeyStroke .getKeyStroke(keyCode, 0 )
406+ speedMenu.add(item)
407+ }
408+ menu.add(speedMenu)
409+
410+ return menu
411+ }
412+
292413 private fun helpMenu (): JMenu {
293414 val menu = JMenu (" Help" )
294415 menu.add(
@@ -300,7 +421,16 @@ class MenuBar : JMenuBar() {
300421 " <br><b>Editing:</b><br>" +
301422 " - Left mouse: Insert nodes and join them<br>" +
302423 " - Middle mouse: Delete nodes<br>" +
303- " - Right mouse: Popup menu</html>"
424+ " - Right mouse: Popup menu<br>" +
425+ " <br><b>Simulation:</b><br>" +
426+ " - Simulation > Start...: Load XML and start simulation<br>" +
427+ " - Simulation > Stop: Terminate running simulation<br>" +
428+ " <br><b>Simulation Speed (keyboard shortcuts):</b><br>" +
429+ " - Key 1: 0.1x speed<br>" +
430+ " - Key 2: 0.5x speed<br>" +
431+ " - Key 3: 1x speed (real-time)<br>" +
432+ " - Key 4: 2x speed<br>" +
433+ " - Key 5: 10x speed</html>"
304434 )
305435 )
306436 menu.add(
0 commit comments