001package jmri.jmrit.timetable.swing; 002 003import java.awt.*; 004import java.awt.event.*; 005import java.io.File; 006import java.io.IOException; 007import java.text.NumberFormat; 008import java.text.ParseException; 009import java.time.LocalTime; 010import java.time.format.DateTimeFormatter; 011import java.util.ArrayList; 012import java.util.HashMap; 013import java.util.List; 014 015import javax.swing.*; 016import javax.swing.colorchooser.AbstractColorChooserPanel; 017import javax.swing.event.ChangeEvent; 018import javax.swing.event.ChangeListener; 019import javax.swing.event.TreeSelectionEvent; 020import javax.swing.event.TreeSelectionListener; 021import javax.swing.filechooser.FileNameExtensionFilter; 022import javax.swing.tree.*; 023 024import jmri.InstanceManager; 025import jmri.Scale; 026import jmri.ScaleManager; 027import jmri.jmrit.operations.trains.tools.ExportTimetable; 028import jmri.jmrit.timetable.*; 029import jmri.jmrit.timetable.configurexml.TimeTableXml; 030import jmri.util.JmriJFrame; 031import jmri.util.swing.SplitButtonColorChooserPanel; 032import jmri.util.swing.JmriJOptionPane; 033 034/** 035 * Create and maintain timetables. 036 * <p> 037 * A timetable describes the layout and trains along with the times that each train should be at specified locations. 038 * 039 * Logical Schema 040 * Layout 041 * Train Types 042 * Segments 043 * Stations 044 * Schedules 045 * Trains 046 * Stops 047 * 048 * @author Dave Sand Copyright (c) 2018 049 */ 050public class TimeTableFrame extends jmri.util.JmriJFrame { 051 052 public static final String EMPTY_GRID = "EmptyGrid"; 053 054 public TimeTableFrame() { 055 } 056 057 public TimeTableFrame(String tt) { 058 super(true, true); 059 setTitle(Bundle.getMessage("TitleTimeTable")); // NOI18N 060 InstanceManager.setDefault(TimeTableFrame.class, this); 061 _dataMgr = TimeTableDataManager.getDataManager(); 062 buildComponents(); 063 createFrame(); 064 createMenu(); 065 setEditMode(false); 066 setShowReminder(false); 067 } 068 069 TimeTableDataManager _dataMgr; 070 boolean _isDirty = false; 071 boolean _showTrainTimes = false; 072 boolean _twoPage = false; 073 074 // ------------ Tree variables ------------ 075 JTree _timetableTree; 076 DefaultTreeModel _timetableModel; 077 DefaultMutableTreeNode _timetableRoot; 078 TreeSelectionListener _timetableListener; 079 TreePath _curTreePath = null; 080 081 // ------------ Tree components ------------ 082 TimeTableTreeNode _layoutNode = null; 083 TimeTableTreeNode _typeHead = null; 084 TimeTableTreeNode _typeNode = null; 085 TimeTableTreeNode _segmentHead = null; 086 TimeTableTreeNode _segmentNode = null; 087 TimeTableTreeNode _stationNode = null; 088 TimeTableTreeNode _scheduleHead = null; 089 TimeTableTreeNode _scheduleNode = null; 090 TimeTableTreeNode _trainNode = null; 091 TimeTableTreeNode _stopNode = null; 092 TimeTableTreeNode _leafNode = null; 093 094 // ------------ Current tree node variables ------------ 095 TimeTableTreeNode _curNode = null; 096 int _curNodeId = 0; 097 String _curNodeType = null; 098 String _curNodeText = null; 099 int _curNodeRow = -1; 100 101 // ------------ Edit detail components ------------ 102 JPanel _detailGrid = new JPanel(); 103 JPanel _detailFooter = new JPanel(); 104 JPanel _gridPanel; // Child of _detailGrid, contains the current grid labels and fields 105 boolean _editActive = false; 106 JButton _cancelAction; 107 JButton _updateAction; 108 109 // Layout 110 JTextField _editLayoutName; 111 JComboBox<Scale> _editScale; 112 JTextField _editFastClock; 113 JTextField _editThrottles; 114 JCheckBox _editMetric; 115 JLabel _showScaleMK; 116 117 // TrainType 118 JTextField _editTrainTypeName; 119 JColorChooser _editTrainTypeColor; 120 121 // Segment 122 JTextField _editSegmentName; 123 124 // Station 125 JTextField _editStationName; 126 JTextField _editDistance; 127 JCheckBox _editDoubleTrack; 128 JSpinner _editSidings; 129 JSpinner _editStaging; 130 131 // Schedule 132 JTextField _editScheduleName; 133 JTextField _editEffDate; 134 JSpinner _editStartHour; 135 JSpinner _editDuration; 136 137 // Train 138 JTextField _editTrainName; 139 JTextField _editTrainDesc; 140 JComboBox<TrainType> _editTrainType; 141 JTextField _editDefaultSpeed; 142 JTextField _editTrainStartTime; 143 JSpinner _editThrottle; 144 JTextArea _editTrainNotes; 145 JLabel _showRouteDuration; 146 147 // Stop 148 JLabel _showStopSeq; 149 JComboBox<TimeTableDataManager.SegmentStation> _editStopStation; 150 JTextField _editStopDuration; 151 JTextField _editNextSpeed; 152 JSpinner _editStagingTrack; 153 JTextArea _editStopNotes; 154 JLabel _showArriveTime; 155 JLabel _showDepartTime; 156 157 // ------------ Button bar components ------------ 158 JPanel _leftButtonBar; 159 JPanel _addButtonPanel; 160 JPanel _duplicateButtonPanel; 161 JPanel _copyButtonPanel; 162 JPanel _deleteButtonPanel; 163 JPanel _moveButtonPanel; 164 JPanel _graphButtonPanel; 165 JButton _addButton = new JButton(); 166 JButton _duplicateButton = new JButton(); 167 JButton _copyButton = new JButton(); 168 JButton _deleteButton = new JButton(); 169 JButton _displayButton = new JButton(); 170 JButton _printButton = new JButton(); 171 JButton _saveButton = new JButton(); 172 173 // ------------ Create Panel and components ------------ 174 175 /** 176 * Create the main Timetable Window 177 * The left side contains the timetable tree. 178 * The right side contains the current edit grid. 179 */ 180 private void createFrame() { 181 Container contentPane = getContentPane(); 182 contentPane.setLayout(new BorderLayout()); 183 184 // ------------ Body - tree (left side) ------------ 185 JTree treeContent = buildTree(); 186 JScrollPane treeScroll = new JScrollPane(treeContent); 187 188 // ------------ Body - detail (right side) ------------ 189 JPanel detailPane = new JPanel(); 190 detailPane.setBorder(BorderFactory.createMatteBorder(0, 2, 0, 0, Color.DARK_GRAY)); 191 detailPane.setLayout(new BoxLayout(detailPane, BoxLayout.Y_AXIS)); 192 193 // ------------ Edit Detail Panel ------------ 194 makeDetailGrid(EMPTY_GRID); // NOI18N 195 196 JPanel panel = new JPanel(); 197 panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); 198 199 _cancelAction = new JButton(Bundle.getMessage("ButtonCancel")); // NOI18N 200 _cancelAction.setToolTipText(Bundle.getMessage("HintCancelButton")); // NOI18N 201 panel.add(_cancelAction); 202 _cancelAction.addActionListener((ActionEvent e) -> cancelPressed()); 203 panel.add(Box.createHorizontalStrut(10)); 204 205 _updateAction = new JButton(Bundle.getMessage("ButtonUpdate")); // NOI18N 206 _updateAction.setToolTipText(Bundle.getMessage("HintUpdateButton")); // NOI18N 207 panel.add(_updateAction); 208 _updateAction.addActionListener((ActionEvent e) -> updatePressed()); 209 _detailFooter.add(panel); 210 211 JPanel detailEdit = new JPanel(new BorderLayout()); 212 detailEdit.add(_detailGrid, BorderLayout.NORTH); 213 detailEdit.add(_detailFooter, BorderLayout.SOUTH); 214 detailPane.add(detailEdit); 215 216 JSplitPane bodyPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, treeScroll, detailPane); 217 bodyPane.setDividerSize(10); 218 bodyPane.setResizeWeight(.35); 219 bodyPane.setOneTouchExpandable(true); 220 contentPane.add(bodyPane); 221 222 // ------------ Footer ------------ 223 JPanel footer = new JPanel(new BorderLayout()); 224 _leftButtonBar = new JPanel(); 225 226 // ------------ Add Button ------------ 227 _addButton = new JButton(Bundle.getMessage("AddLayoutButtonText")); // NOI18N 228 _addButton.setToolTipText(Bundle.getMessage("HintAddButton")); // NOI18N 229 _addButton.addActionListener(new ActionListener() { 230 @Override 231 public void actionPerformed(ActionEvent e) { 232 addPressed(); 233 } 234 }); 235 _addButtonPanel = new JPanel(); 236 _addButtonPanel.add(_addButton); 237 _leftButtonBar.add(_addButtonPanel); 238 239 // ------------ Duplicate Button ------------ 240 _duplicateButton = new JButton(Bundle.getMessage("DuplicateLayoutButtonText")); // NOI18N 241 _duplicateButton.setToolTipText(Bundle.getMessage("HintDuplicateButton")); // NOI18N 242 _duplicateButton.addActionListener(new ActionListener() { 243 @Override 244 public void actionPerformed(ActionEvent e) { 245 duplicatePressed(); 246 } 247 }); 248 _duplicateButtonPanel = new JPanel(); 249 _duplicateButtonPanel.add(_duplicateButton); 250 _leftButtonBar.add(_duplicateButtonPanel); 251 252 // ------------ Copy Button ------------ 253 _copyButton = new JButton(Bundle.getMessage("CopyStopsButton")); // NOI18N 254 _copyButton.setToolTipText(Bundle.getMessage("HintCopyButton")); // NOI18N 255 _copyButton.addActionListener(new ActionListener() { 256 @Override 257 public void actionPerformed(ActionEvent e) { 258 copyPressed(); 259 } 260 }); 261 _copyButtonPanel = new JPanel(); 262 _copyButtonPanel.add(_copyButton); 263 _leftButtonBar.add(_copyButtonPanel); 264 265 // ------------ Delete Button ------------ 266 _deleteButton = new JButton(Bundle.getMessage("DeleteLayoutButtonText")); // NOI18N 267 _deleteButton.setToolTipText(Bundle.getMessage("HintDeleteButton")); // NOI18N 268 _deleteButton.addActionListener(new ActionListener() { 269 @Override 270 public void actionPerformed(ActionEvent e) { 271 deletePressed(); 272 } 273 }); 274 _deleteButtonPanel = new JPanel(); 275 _deleteButtonPanel.add(_deleteButton); 276 _deleteButtonPanel.setVisible(false); 277 _leftButtonBar.add(_deleteButtonPanel); 278 279 // ------------ Move Buttons ------------ 280 JLabel moveLabel = new JLabel(Bundle.getMessage("LabelMove")); // NOI18N 281 282 JButton upButton = new JButton(Bundle.getMessage("ButtonUp")); // NOI18N 283 upButton.setToolTipText(Bundle.getMessage("HintUpButton")); // NOI18N 284 JButton downButton = new JButton(Bundle.getMessage("ButtonDown")); // NOI18N 285 downButton.setToolTipText(Bundle.getMessage("HintDownButton")); // NOI18N 286 287 upButton.addActionListener(new ActionListener() { 288 @Override 289 public void actionPerformed(ActionEvent e) { 290 downButton.setEnabled(false); 291 upButton.setEnabled(false); 292 upPressed(); 293 } 294 }); 295 296 downButton.addActionListener(new ActionListener() { 297 @Override 298 public void actionPerformed(ActionEvent e) { 299 upButton.setEnabled(false); 300 downButton.setEnabled(false); 301 downPressed(); 302 } 303 }); 304 305 _moveButtonPanel = new JPanel(); 306 _moveButtonPanel.add(moveLabel); 307 _moveButtonPanel.add(upButton); 308 _moveButtonPanel.add(new JLabel("|")); 309 _moveButtonPanel.add(downButton); 310 _moveButtonPanel.setVisible(false); 311 _leftButtonBar.add(_moveButtonPanel); 312 313 // ------------ Graph Buttons ------------ 314 JLabel graphLabel = new JLabel(Bundle.getMessage("LabelGraph")); // NOI18N 315 316 _displayButton = new JButton(Bundle.getMessage("ButtonDisplay")); // NOI18N 317 _displayButton.setToolTipText(Bundle.getMessage("HintDisplayButton")); // NOI18N 318 _displayButton.addActionListener(new ActionListener() { 319 @Override 320 public void actionPerformed(ActionEvent e) { 321 graphPressed("Display"); // NOI18N 322 } 323 }); 324 325 _printButton = new JButton(Bundle.getMessage("ButtonPrint")); // NOI18N 326 _printButton.setToolTipText(Bundle.getMessage("HintPrintButton")); // NOI18N 327 _printButton.addActionListener(new ActionListener() { 328 @Override 329 public void actionPerformed(ActionEvent e) { 330 graphPressed("Print"); // NOI18N 331 } 332 }); 333 334 _graphButtonPanel = new JPanel(); 335 _graphButtonPanel.add(graphLabel); 336 _graphButtonPanel.add(_displayButton); 337 _graphButtonPanel.add(new JLabel("|")); 338 _graphButtonPanel.add(_printButton); 339 _leftButtonBar.add(_graphButtonPanel); 340 341 footer.add(_leftButtonBar, BorderLayout.WEST); 342 JPanel rightButtonBar = new JPanel(); 343 344 // ------------ Save Button ------------ 345 _saveButton = new JButton(Bundle.getMessage("ButtonSave")); // NOI18N 346 _saveButton.setToolTipText(Bundle.getMessage("HintSaveButton")); // NOI18N 347 _saveButton.addActionListener(new ActionListener() { 348 @Override 349 public void actionPerformed(ActionEvent e) { 350 savePressed(); 351 } 352 }); 353 JPanel saveButtonPanel = new JPanel(); 354 saveButtonPanel.add(_saveButton); 355 rightButtonBar.add(saveButtonPanel); 356 357 // ------------ Done Button ------------ 358 JButton doneButton = new JButton(Bundle.getMessage("ButtonDone")); // NOI18N 359 doneButton.setToolTipText(Bundle.getMessage("HintDoneButton")); // NOI18N 360 doneButton.addActionListener(new ActionListener() { 361 @Override 362 public void actionPerformed(ActionEvent e) { 363 donePressed(); 364 } 365 }); 366 JPanel doneButtonPanel = new JPanel(); 367 doneButtonPanel.add(doneButton); 368 rightButtonBar.add(doneButtonPanel); 369 370 footer.add(rightButtonBar, BorderLayout.EAST); 371 contentPane.add(footer, BorderLayout.SOUTH); 372 373 addWindowListener(new java.awt.event.WindowAdapter() { 374 @Override 375 public void windowClosing(java.awt.event.WindowEvent e) { 376 donePressed(); 377 } 378 }); 379 setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); 380 381 pack(); 382 _addButtonPanel.setVisible(false); 383 _duplicateButtonPanel.setVisible(false); 384 _copyButtonPanel.setVisible(false); 385 _deleteButtonPanel.setVisible(false); 386 _graphButtonPanel.setVisible(false); 387 } 388 389 /** 390 * Create a Options/Tools menu. 391 * - Option: Show train times on the graph. 392 * - Option: Enable two page graph printing. 393 * - Tool: Import a SchedGen data file. 394 * - Tool: Import a CSV data file. 395 * - Tool: Export a CSV data file. 396 * Include the standard Windows and Help menu bar items. 397 */ 398 void createMenu() { 399 _showTrainTimes = InstanceManager.getDefault(jmri.UserPreferencesManager.class). 400 getSimplePreferenceState("jmri.jmrit.timetable:TrainTimes"); // NOI18N 401 402 JCheckBoxMenuItem trainTime = new JCheckBoxMenuItem(Bundle.getMessage("MenuTrainTimes")); // NOI18N 403 trainTime.setSelected(_showTrainTimes); 404 trainTime.addActionListener((ActionEvent event) -> { 405 _showTrainTimes = trainTime.isSelected(); 406 InstanceManager.getDefault(jmri.UserPreferencesManager.class). 407 setSimplePreferenceState("jmri.jmrit.timetable:TrainTimes", _showTrainTimes); // NOI18N 408 }); 409 410 _twoPage = InstanceManager.getDefault(jmri.UserPreferencesManager.class). 411 getSimplePreferenceState("jmri.jmrit.timetable:TwoPage"); // NOI18N 412 413 JCheckBoxMenuItem twoPage = new JCheckBoxMenuItem(Bundle.getMessage("MenuTwoPage")); // NOI18N 414 twoPage.setSelected(_twoPage); 415 twoPage.addActionListener((ActionEvent event) -> { 416 _twoPage = twoPage.isSelected(); 417 InstanceManager.getDefault(jmri.UserPreferencesManager.class). 418 setSimplePreferenceState("jmri.jmrit.timetable:TwoPage", _twoPage); // NOI18N 419 }); 420 421 JMenuItem impsgn = new JMenuItem(Bundle.getMessage("MenuImportSgn")); // NOI18N 422 impsgn.addActionListener((ActionEvent event) -> importPressed()); 423 424 JMenuItem impcsv = new JMenuItem(Bundle.getMessage("MenuImportCsv")); // NOI18N 425 impcsv.addActionListener((ActionEvent event) -> importCsvPressed()); 426 427 JMenuItem impopr = new JMenuItem(Bundle.getMessage("MenuImportOperations")); // NOI18N 428 impopr.addActionListener((ActionEvent event) -> importFromOperationsPressed()); 429 430 JMenuItem expcsv = new JMenuItem(Bundle.getMessage("MenuExportCsv")); // NOI18N 431 expcsv.addActionListener((ActionEvent event) -> exportCsvPressed()); 432 433 JMenu ttMenu = new JMenu(Bundle.getMessage("MenuTimetable")); // NOI18N 434 ttMenu.add(trainTime); 435 ttMenu.addSeparator(); 436 ttMenu.add(twoPage); 437 ttMenu.addSeparator(); 438 ttMenu.add(impsgn); 439 ttMenu.add(impcsv); 440 ttMenu.add(impopr); 441 ttMenu.add(expcsv); 442 443 JMenuBar menuBar = new JMenuBar(); 444 menuBar.add(ttMenu); 445 setJMenuBar(menuBar); 446 447 //setup Help menu 448 addHelpMenu("html.tools.TimeTable", true); // NOI18N 449 } 450 451 /** 452 * Initialize components. 453 * Add Focus and Change listeners to activate edit mode. 454 * Create the color selector for train types. 455 */ 456 void buildComponents() { 457 // Layout 458 _editLayoutName = new JTextField(20); 459 _editScale = new JComboBox<>(); 460 _editScale.addItemListener(layoutScaleItemEvent); 461 _editFastClock = new JTextField(5); 462 _editThrottles = new JTextField(5); 463 _editMetric = new JCheckBox(); 464 _showScaleMK = new JLabel(); 465 466 _editLayoutName.addFocusListener(detailFocusEvent); 467 _editScale.addFocusListener(detailFocusEvent); 468 _editFastClock.addFocusListener(detailFocusEvent); 469 _editThrottles.addFocusListener(detailFocusEvent); 470 _editMetric.addChangeListener(detailChangeEvent); 471 472 // TrainType 473 _editTrainTypeName = new JTextField(20); 474 _editTrainTypeColor = new JColorChooser(Color.BLACK); 475 _editTrainTypeColor.setPreviewPanel(new JPanel()); // remove the preview panel 476 AbstractColorChooserPanel[] editTypeColorPanels = {new SplitButtonColorChooserPanel()}; 477 _editTrainTypeColor.setChooserPanels(editTypeColorPanels); 478 479 _editTrainTypeName.addFocusListener(detailFocusEvent); 480 _editTrainTypeColor.getSelectionModel().addChangeListener(detailChangeEvent); 481 482 // Segment 483 _editSegmentName = new JTextField(20); 484 485 _editSegmentName.addFocusListener(detailFocusEvent); 486 487 // Station 488 _editStationName = new JTextField(20); 489 _editDistance = new JTextField(5); 490 _editDoubleTrack = new JCheckBox(); 491 _editSidings = new JSpinner(new SpinnerNumberModel(0, 0, null, 1)); 492 _editStaging = new JSpinner(new SpinnerNumberModel(0, 0, null, 1)); 493 494 _editStationName.addFocusListener(detailFocusEvent); 495 _editDistance.addFocusListener(detailFocusEvent); 496 _editDoubleTrack.addChangeListener(detailChangeEvent); 497 _editSidings.addChangeListener(detailChangeEvent); 498 _editStaging.addChangeListener(detailChangeEvent); 499 500 // Schedule 501 _editScheduleName = new JTextField(20); 502 _editEffDate = new JTextField(10); 503 _editStartHour = new JSpinner(new SpinnerNumberModel(0, 0, 23, 1)); 504 _editDuration = new JSpinner(new SpinnerNumberModel(24, 1, 24, 1)); 505 506 _editScheduleName.addFocusListener(detailFocusEvent); 507 _editEffDate.addFocusListener(detailFocusEvent); 508 _editStartHour.addChangeListener(detailChangeEvent); 509 _editDuration.addChangeListener(detailChangeEvent); 510 511 // Train 512 _editTrainName = new JTextField(10); 513 _editTrainDesc = new JTextField(20); 514 _editTrainType = new JComboBox<>(); 515 _editDefaultSpeed = new JTextField(5); 516 _editTrainStartTime = new JTextField(5); 517 _editThrottle = new JSpinner(new SpinnerNumberModel(0, 0, null, 1)); 518 _editTrainNotes = new JTextArea(4, 30); 519 _showRouteDuration = new JLabel(); 520 521 _editTrainName.addFocusListener(detailFocusEvent); 522 _editTrainDesc.addFocusListener(detailFocusEvent); 523 _editTrainType.addFocusListener(detailFocusEvent); 524 _editDefaultSpeed.addFocusListener(detailFocusEvent); 525 _editTrainStartTime.addFocusListener(detailFocusEvent); 526 _editThrottle.addChangeListener(detailChangeEvent); 527 _editTrainNotes.addFocusListener(detailFocusEvent); 528 529 // Stop 530 _showStopSeq = new JLabel(); 531 _editStopStation = new JComboBox<>(); 532 _editStopDuration = new JTextField(5); 533 _editNextSpeed = new JTextField(5); 534 _editStagingTrack = new JSpinner(new SpinnerNumberModel(0, 0, null, 1)); 535 _editStopNotes = new JTextArea(4, 30); 536 _showArriveTime = new JLabel(); 537 _showDepartTime = new JLabel(); 538 539 _editStopStation.addFocusListener(detailFocusEvent); 540 _editStopStation.addItemListener(stopStationItemEvent); 541 _editStopDuration.addFocusListener(detailFocusEvent); 542 _editNextSpeed.addFocusListener(detailFocusEvent); 543 _editStagingTrack.addChangeListener(detailChangeEvent); 544 _editStopNotes.addFocusListener(detailFocusEvent); 545 } 546 547 /** 548 * Enable edit mode. Used for JTextFields and JComboBoxs. 549 */ 550 transient FocusListener detailFocusEvent = new FocusListener() { 551 @Override 552 public void focusGained(FocusEvent e) { 553 if (!_editActive) { 554 setEditMode(true); 555 } 556 } 557 558 @Override 559 public void focusLost(FocusEvent e) { 560 } 561 }; 562 563 /** 564 * Enable edit mode. Used for JCheckBoxs, JSpinners and JColorChoosers. 565 */ 566 transient ChangeListener detailChangeEvent = new ChangeListener() { 567 @Override 568 public void stateChanged(ChangeEvent e) { 569 if (!_editActive) { 570 setEditMode(true); 571 } 572 } 573 }; 574 575 /** 576 * Change the max spinner value based on the station data. 577 * The number of staging tracks varies depending on the selected station. 578 */ 579 transient ItemListener stopStationItemEvent = new ItemListener() { 580 @Override 581 public void itemStateChanged(ItemEvent e) { 582 if (e.getStateChange() == ItemEvent.SELECTED) { 583 TimeTableDataManager.SegmentStation segmentStation = (TimeTableDataManager.SegmentStation) e.getItem(); 584 int stagingTracks = _dataMgr.getStation(segmentStation.getStationId()).getStaging(); 585 Stop stop = _dataMgr.getStop(_curNodeId); 586 if (stop.getStagingTrack() <= stagingTracks) { 587 _editStagingTrack.setModel(new SpinnerNumberModel(stop.getStagingTrack(), 0, stagingTracks, 1)); 588 } 589 } 590 } 591 }; 592 593 /** 594 * If the custom scale item is selected provide a dialog to set the scale ratio 595 */ 596 transient ItemListener layoutScaleItemEvent = new ItemListener() { 597 @Override 598 public void itemStateChanged(ItemEvent e) { 599 if (e.getStateChange() == ItemEvent.SELECTED) { 600 if (_editScale.hasFocus()) { 601 Scale scale = (Scale) _editScale.getSelectedItem(); 602 if (scale.getScaleName().equals("CUSTOM")) { // NOI18N 603 String ans = JmriJOptionPane.showInputDialog( _editScale, 604 Bundle.getMessage("ScaleRatioChange"), // NOI18N 605 String.valueOf(scale.getScaleRatio()) 606 ); 607 if (ans != null) { 608 try { 609 double newRatio = Double.parseDouble(ans); 610 scale.setScaleRatio(newRatio); 611 } catch (java.lang.IllegalArgumentException 612 | java.beans.PropertyVetoException ex) { 613 log.warn("Unable to change custom ratio: {}", ex.getMessage()); // NOI18N 614 JmriJOptionPane.showMessageDialog( _editScale, 615 Bundle.getMessage("NumberFormatError", ans, "Custom ratio"), // NOI18N 616 Bundle.getMessage("WarningTitle"), // NOI18N 617 JmriJOptionPane.WARNING_MESSAGE); 618 Layout layout = _dataMgr.getLayout(_curNodeId); 619 _editScale.setSelectedItem(layout.getScale()); 620 } 621 } 622 } 623 } 624 } 625 } 626 }; 627 628 // ------------ Create GridBag panels ------------ 629 630 /** 631 * Build new GridBag content. The grid panel is hidden, emptied, re-built and 632 * made visible. 633 * 634 * @param gridType The type of grid to create 635 */ 636 void makeDetailGrid(String gridType) { 637 _detailGrid.setVisible(false); 638 _detailGrid.removeAll(); 639 _detailFooter.setVisible(true); 640 641 _gridPanel = new JPanel(new GridBagLayout()); 642 GridBagConstraints c = new GridBagConstraints(); 643 c.gridwidth = 1; 644 c.gridheight = 1; 645 c.ipadx = 5; 646 647 switch (gridType) { 648 case EMPTY_GRID: // NOI18N 649 makeEmptyGrid(c); 650 _detailFooter.setVisible(false); 651 break; 652 653 case "Layout": // NOI18N 654 makeLayoutGrid(c); 655 break; 656 657 case "TrainType": // NOI18N 658 makeTrainTypeGrid(c); 659 break; 660 661 case "Segment": // NOI18N 662 makeSegmentGrid(c); 663 break; 664 665 case "Station": // NOI18N 666 makeStationGrid(c); 667 break; 668 669 case "Schedule": // NOI18N 670 makeScheduleGrid(c); 671 break; 672 673 case "Train": // NOI18N 674 makeTrainGrid(c); 675 break; 676 677 case "Stop": // NOI18N 678 makeStopGrid(c); 679 break; 680 681 default: 682 log.warn("Invalid grid type: '{}'", gridType); // NOI18N 683 makeEmptyGrid(c); 684 } 685 686 _detailGrid.add(_gridPanel); 687 _detailGrid.setVisible(true); 688 } 689 690 /** 691 * This grid is used when there are no edit grids required. 692 * 693 * @param c The constraints object used for the grid construction 694 */ 695 void makeEmptyGrid(GridBagConstraints c) { 696 // Variable type box 697 c.gridy = 0; 698 c.gridx = 0; 699 c.anchor = java.awt.GridBagConstraints.CENTER; 700 JLabel rowLabel = new JLabel(Bundle.getMessage("LabelBlank")); // NOI18N 701 _gridPanel.add(rowLabel, c); 702 } 703 704 /** 705 * This grid is used to edit Layout data. 706 * 707 * @param c The constraints object used for the grid construction 708 */ 709 void makeLayoutGrid(GridBagConstraints c) { 710 makeGridLabel(0, "LabelLayoutName", "HintLayoutName", c); // NOI18N 711 _gridPanel.add(_editLayoutName, c); 712 713 makeGridLabel(1, "LabelScale", "HintScale", c); // NOI18N 714 _gridPanel.add(_editScale, c); 715 716 makeGridLabel(2, "LabelFastClock", "HintFastClock", c); // NOI18N 717 _gridPanel.add(_editFastClock, c); 718 719 makeGridLabel(3, "LabelThrottles", "HintThrottles", c); // NOI18N 720 _gridPanel.add(_editThrottles, c); 721 722 makeGridLabel(4, "LabelMetric", "HintMetric", c); // NOI18N 723 _gridPanel.add(_editMetric, c); 724 725 makeGridLabel(5, "LabelScaleMK", "HintScaleMK", c); // NOI18N 726 _gridPanel.add(_showScaleMK, c); 727 } 728 729 /** 730 * This grid is used to edit the Train Type data. 731 * 732 * @param c The constraints object used for the grid construction 733 */ 734 void makeTrainTypeGrid(GridBagConstraints c) { 735 makeGridLabel(0, "LabelTrainTypeName", "HintTrainTypeName", c); // NOI18N 736 _gridPanel.add(_editTrainTypeName, c); 737 738 makeGridLabel(1, "LabelTrainTypeColor", "HintTrainTypeColor", c); // NOI18N 739 _gridPanel.add(_editTrainTypeColor, c); 740 } 741 742 /** 743 * This grid is used to edit the Segment data. 744 * 745 * @param c The constraints object used for the grid construction 746 */ 747 void makeSegmentGrid(GridBagConstraints c) { 748 makeGridLabel(0, "LabelSegmentName", "HintSegmentName", c); // NOI18N 749 _gridPanel.add(_editSegmentName, c); 750 } 751 752 /** 753 * This grid is used to edit the Station data. 754 * 755 * @param c The constraints object used for the grid construction 756 */ 757 void makeStationGrid(GridBagConstraints c) { 758 makeGridLabel(0, "LabelStationName", "HintStationName", c); // NOI18N 759 _gridPanel.add(_editStationName, c); 760 761 makeGridLabel(1, "LabelDistance", "HintDistance", c); // NOI18N 762 _gridPanel.add(_editDistance, c); 763 764 makeGridLabel(2, "LabelDoubleTrack", "HintDoubleTrack", c); // NOI18N 765 _gridPanel.add(_editDoubleTrack, c); 766 767 makeGridLabel(3, "LabelSidings", "HintSidings", c); // NOI18N 768 _gridPanel.add(_editSidings, c); 769 770 makeGridLabel(4, "LabelStaging", "HintStaging", c); // NOI18N 771 _gridPanel.add(_editStaging, c); 772 } 773 774 /** 775 * This grid is used to edit the Schedule data. 776 * 777 * @param c The constraints object used for the grid construction 778 */ 779 void makeScheduleGrid(GridBagConstraints c) { 780 makeGridLabel(0, "LabelScheduleName", "HintScheduleName", c); // NOI18N 781 _gridPanel.add(_editScheduleName, c); 782 783 makeGridLabel(1, "LabelEffDate", "HintEffDate", c); // NOI18N 784 _gridPanel.add(_editEffDate, c); 785 786 makeGridLabel(2, "LabelStartHour", "HintStartHour", c); // NOI18N 787 _gridPanel.add(_editStartHour, c); 788 789 makeGridLabel(3, "LabelDuration", "HintDuration", c); // NOI18N 790 _gridPanel.add(_editDuration, c); 791 } 792 793 /** 794 * This grid is used to edit the Train data. 795 * 796 * @param c The constraints object used for the grid construction 797 */ 798 void makeTrainGrid(GridBagConstraints c) { 799 makeGridLabel(0, "LabelTrainName", "HintTrainName", c); // NOI18N 800 _gridPanel.add(_editTrainName, c); 801 802 makeGridLabel(1, "LabelTrainDesc", "HintTrainDesc", c); // NOI18N 803 _gridPanel.add(_editTrainDesc, c); 804 805 makeGridLabel(2, "LabelTrainType", "HintTrainType", c); // NOI18N 806 _gridPanel.add(_editTrainType, c); 807 808 makeGridLabel(3, "LabelDefaultSpeed", "HintDefaultSpeed", c); // NOI18N 809 _gridPanel.add(_editDefaultSpeed, c); 810 811 makeGridLabel(4, "LabelTrainStartTime", "HintTrainStartTime", c); // NOI18N 812 _gridPanel.add(_editTrainStartTime, c); 813 814 makeGridLabel(5, "LabelThrottle", "HintThrottle", c); // NOI18N 815 _gridPanel.add(_editThrottle, c); 816 817 makeGridLabel(6, "LabelRouteDuration", "HintRouteDuration", c); // NOI18N 818 _gridPanel.add(_showRouteDuration, c); 819 820 makeGridLabel(7, "LabelTrainNotes", "HintTrainNotes", c); // NOI18N 821 _gridPanel.add(_editTrainNotes, c); 822 } 823 824 /** 825 * This grid is used to edit the Stop data. 826 * 827 * @param c The constraints object used for the grid construction 828 */ 829 void makeStopGrid(GridBagConstraints c) { 830 makeGridLabel(0, "LabelStopSeq", "HintStopSeq", c); // NOI18N 831 _gridPanel.add(_showStopSeq, c); 832 833 makeGridLabel(1, "LabelStopStation", "HintStopStation", c); // NOI18N 834 _gridPanel.add(_editStopStation, c); 835 836 makeGridLabel(2, "LabelStopDuration", "HintStopDuration", c); // NOI18N 837 _gridPanel.add(_editStopDuration, c); 838 839 makeGridLabel(3, "LabelNextSpeed", "HintNextSpeed", c); // NOI18N 840 _gridPanel.add(_editNextSpeed, c); 841 842 makeGridLabel(4, "LabelStagingTrack", "HintStagingTrack", c); // NOI18N 843 _gridPanel.add(_editStagingTrack, c); 844 845 makeGridLabel(5, "LabelArriveTime", "HintArriveTime", c); // NOI18N 846 _gridPanel.add(_showArriveTime, c); 847 848 makeGridLabel(6, "LabelDepartTime", "HintDepartTime", c); // NOI18N 849 _gridPanel.add(_showDepartTime, c); 850 851 makeGridLabel(7, "LabelStopNotes", "HintStopNotes", c); // NOI18N 852 _gridPanel.add(_editStopNotes, c); 853 } 854 855 /** 856 * Create the label portion of a grid row. 857 * @param row The grid row number. 858 * @param label The bundle key for the label text. 859 * @param hint The bundle key for the label tool tip. 860 * @param c The grid bag contraints object. 861 */ 862 void makeGridLabel(int row, String label, String hint, GridBagConstraints c) { 863 c.gridy = row; 864 c.gridx = 0; 865 c.anchor = java.awt.GridBagConstraints.EAST; 866 JLabel rowLabel = new JLabel(Bundle.getMessage(label)); 867 rowLabel.setToolTipText(Bundle.getMessage(hint)); 868 _gridPanel.add(rowLabel, c); 869 c.gridx = 1; 870 c.anchor = java.awt.GridBagConstraints.WEST; 871 } 872 873 // ------------ Process button bar and tree events ------------ 874 875 /** 876 * Add new items. 877 */ 878 void addPressed() { 879 switch (_curNodeType) { 880 case "Layout": // NOI18N 881 addLayout(); 882 break; 883 884 case "TrainTypes": // NOI18N 885 addTrainType(); 886 break; 887 888 case "Segments": // NOI18N 889 addSegment(); 890 break; 891 892 case "Segment": // NOI18N 893 addStation(); 894 break; 895 896 case "Schedules": // NOI18N 897 addSchedule(); 898 break; 899 900 case "Schedule": // NOI18N 901 addTrain(); 902 break; 903 904 case "Train": // NOI18N 905 addStop(); 906 break; 907 908 default: 909 log.error("Add called for unsupported node type: '{}'", _curNodeType); // NOI18N 910 } 911 } 912 913 /** 914 * Create a new Layout object with default values. 915 * Add the layout node and the TrainTypes, Segments and Schedules collection nodes. 916 */ 917 void addLayout() { 918 Layout newLayout = new Layout(); 919 setShowReminder(true); 920 921 // Build tree components 922 _curNode = new TimeTableTreeNode(newLayout.getLayoutName(), "Layout", newLayout.getLayoutId(), 0); // NOI18N 923 _timetableRoot.add(_curNode); 924 _leafNode = new TimeTableTreeNode(buildNodeText("TrainTypes", null, 0), "TrainTypes", 0, 0); // NOI18N 925 _curNode.add(_leafNode); 926 _leafNode = new TimeTableTreeNode(buildNodeText("Segments", null, 0), "Segments", 0, 0); // NOI18N 927 _curNode.add(_leafNode); 928 _leafNode = new TimeTableTreeNode(buildNodeText("Schedules", null, 0), "Schedules", 0, 0); // NOI18N 929 _curNode.add(_leafNode); 930 _timetableModel.nodeStructureChanged(_timetableRoot); 931 932 // Switch to new node 933 _timetableTree.setSelectionPath(new TreePath(_curNode.getPath())); 934 } 935 936 /** 937 * Create a new Train Type object. 938 * The default color is black. 939 */ 940 void addTrainType() { 941 TimeTableTreeNode layoutNode = (TimeTableTreeNode) _curNode.getParent(); 942 int layoutId = layoutNode.getId(); 943 TrainType newType = new TrainType(layoutId); 944 setShowReminder(true); 945 946 // Build tree components 947 _leafNode = new TimeTableTreeNode(newType.getTypeName(), "TrainType", newType.getTypeId(), 0); // NOI18N 948 _curNode.add(_leafNode); 949 _timetableModel.nodeStructureChanged(_curNode); 950 951 // Switch to new node 952 _timetableTree.setSelectionPath(new TreePath(_leafNode.getPath())); 953 } 954 955 /** 956 * Create a new Segment object with default values. 957 */ 958 void addSegment() { 959 TimeTableTreeNode layoutNode = (TimeTableTreeNode) _curNode.getParent(); 960 int layoutId = layoutNode.getId(); 961 Segment newSegment = new Segment(layoutId); 962 setShowReminder(true); 963 964 // Build tree components 965 _leafNode = new TimeTableTreeNode(newSegment.getSegmentName(), "Segment", newSegment.getSegmentId(), 0); // NOI18N 966 _curNode.add(_leafNode); 967 _timetableModel.nodeStructureChanged(_curNode); 968 969 // Switch to new node 970 _timetableTree.setSelectionPath(new TreePath(_leafNode.getPath())); 971 } 972 973 /** 974 * Create a new Station object with default values. 975 */ 976 void addStation() { 977 Station newStation = new Station(_curNodeId); 978 setShowReminder(true); 979 980 // Build tree components 981 _leafNode = new TimeTableTreeNode(newStation.getStationName(), "Station", newStation.getStationId(), 0); // NOI18N 982 _curNode.add(_leafNode); 983 _timetableModel.nodeStructureChanged(_curNode); 984 985 // Switch to new node 986 _timetableTree.setSelectionPath(new TreePath(_leafNode.getPath())); 987 } 988 989 /** 990 * Create a new Schedule object with default values. 991 */ 992 void addSchedule() { 993 TimeTableTreeNode layoutNode = (TimeTableTreeNode) _curNode.getParent(); 994 int layoutId = layoutNode.getId(); 995 Schedule newSchedule = new Schedule(layoutId); 996 setShowReminder(true); 997 998 // Build tree components 999 _leafNode = new TimeTableTreeNode(newSchedule.getScheduleName(), "Schedule", newSchedule.getScheduleId(), 0); // NOI18N 1000 _curNode.add(_leafNode); 1001 _timetableModel.nodeStructureChanged(_curNode); 1002 1003 // Switch to new node 1004 _timetableTree.setSelectionPath(new TreePath(_leafNode.getPath())); 1005 } 1006 1007 void addTrain() { 1008 Train newTrain = new Train(_curNodeId); 1009 newTrain.setStartTime(_dataMgr.getSchedule(_curNodeId).getStartHour() * 60); 1010 setShowReminder(true); 1011 1012 // Build tree components 1013 _leafNode = new TimeTableTreeNode(newTrain.getTrainName(), "Train", newTrain.getTrainId(), 0); // NOI18N 1014 _curNode.add(_leafNode); 1015 _timetableModel.nodeStructureChanged(_curNode); 1016 1017 // Switch to new node 1018 _timetableTree.setSelectionPath(new TreePath(_leafNode.getPath())); 1019 } 1020 1021 void addStop() { 1022 int newSeq = _dataMgr.getStops(_curNodeId, 0, false).size(); 1023 Stop newStop = new Stop(_curNodeId, newSeq + 1); 1024 setShowReminder(true); 1025 1026 // Build tree components 1027 _leafNode = new TimeTableTreeNode(String.valueOf(newSeq + 1), "Stop", newStop.getStopId(), newSeq + 1); // NOI18N 1028 _curNode.add(_leafNode); 1029 _timetableModel.nodeStructureChanged(_curNode); 1030 1031 // Switch to new node 1032 _timetableTree.setSelectionPath(new TreePath(_leafNode.getPath())); 1033 } 1034 1035 /** 1036 * Duplicate selected item. 1037 */ 1038 void duplicatePressed() { 1039 _dataMgr.setLockCalculate(true); 1040 switch (_curNodeType) { 1041 case "Layout": // NOI18N 1042 duplicateLayout(_curNodeId); 1043 break; 1044 1045 case "TrainType": // NOI18N 1046 duplicateTrainType(0, _curNodeId, (TimeTableTreeNode) _curNode.getParent()); 1047 break; 1048 1049 case "Segment": // NOI18N 1050 duplicateSegment(0, _curNodeId, (TimeTableTreeNode) _curNode.getParent()); 1051 break; 1052 1053 case "Station": // NOI18N 1054 duplicateStation(0, _curNodeId, (TimeTableTreeNode) _curNode.getParent()); 1055 break; 1056 1057 case "Schedule": // NOI18N 1058 duplicateSchedule(0, _curNodeId, (TimeTableTreeNode) _curNode.getParent()); 1059 break; 1060 1061 case "Train": // NOI18N 1062 duplicateTrain(0, _curNodeId, 0, (TimeTableTreeNode) _curNode.getParent()); 1063 break; 1064 1065 case "Stop": // NOI18N 1066 duplicateStop(0, _curNodeId, 0, 0, (TimeTableTreeNode) _curNode.getParent()); 1067 break; 1068 1069 default: 1070 log.error("Duplicate called for unsupported node type: '{}'", _curNodeType); // NOI18N 1071 } 1072 _dataMgr.setLockCalculate(false); 1073 } 1074 1075 // Trains have references to train types and stops have references to stations. 1076 // When a layout is copied, the references have to be changed to the copied element. 1077 private HashMap<Integer, Integer> typeMap = new HashMap<>(); // THe key is the source train type, the value is the destination train type. 1078 private HashMap<Integer, Integer> stationMap = new HashMap<>(); // THe key is the source layout stations, the value is the destination stations. 1079 1080 private boolean dupLayout = false; 1081 1082 /** 1083 * Create a copy of a layout. 1084 * @param layoutId The id of the layout to be duplicated. 1085 */ 1086 void duplicateLayout(int layoutId) { 1087 dupLayout = true; 1088 Layout layout = _dataMgr.getLayout(layoutId); 1089 Layout newLayout = layout.getCopy(); 1090 setShowReminder(true); 1091 1092 // Build tree components 1093 _curNode = new TimeTableTreeNode(newLayout.getLayoutName(), "Layout", newLayout.getLayoutId(), 0); // NOI18N 1094 _timetableRoot.add(_curNode); 1095 1096 _leafNode = new TimeTableTreeNode(buildNodeText("TrainTypes", null, 0), "TrainTypes", 0, 0); // NOI18N 1097 _curNode.add(_leafNode); 1098 var typesNode = _leafNode; 1099 1100 _leafNode = new TimeTableTreeNode(buildNodeText("Segments", null, 0), "Segments", 0, 0); // NOI18N 1101 _curNode.add(_leafNode); 1102 var segmentsNode = _leafNode; 1103 1104 _leafNode = new TimeTableTreeNode(buildNodeText("Schedules", null, 0), "Schedules", 0, 0); // NOI18N 1105 _curNode.add(_leafNode); 1106 var schedlulesNode = _leafNode; 1107 1108 _timetableModel.nodeStructureChanged(_timetableRoot); 1109 1110 1111 // Copy train types 1112 typeMap.clear(); 1113 for (var type : _dataMgr.getTrainTypes(layoutId, true)) { 1114 duplicateTrainType(newLayout.getLayoutId(), type.getTypeId(), typesNode); 1115 } 1116 1117 // Copy segments 1118 stationMap.clear(); 1119 for (var segment : _dataMgr.getSegments(layoutId, true)) { 1120 duplicateSegment(newLayout.getLayoutId(), segment.getSegmentId(), segmentsNode); 1121 } 1122 1123 // schedules 1124 for (var schedule : _dataMgr.getSchedules(layoutId, true)) { 1125 duplicateSchedule(newLayout.getLayoutId(), schedule.getScheduleId(), schedlulesNode); 1126 } 1127 1128 // Switch to new node 1129 _timetableTree.setSelectionPath(new TreePath(_curNode.getPath())); 1130 1131 dupLayout = false; 1132 } 1133 1134 /** 1135 * Create a copy of a train type. 1136 * @param layoutId The id for the parent layout. Zero if within the same layout. 1137 * @param typeId The id of the train type to be duplicated. 1138 * @param typesNode The types node which will be parent for the new train type. 1139 */ 1140 void duplicateTrainType(int layoutId, int typeId, TimeTableTreeNode typesNode) { 1141 TrainType type = _dataMgr.getTrainType(typeId); 1142 TrainType newType = type.getCopy(layoutId); 1143 setShowReminder(true); 1144 1145 // If part of duplicating a layout, create a type map entry. 1146 if (dupLayout) { 1147 typeMap.put(type.getTypeId(), newType.getTypeId()); 1148 } 1149 1150 // Build tree components 1151 _leafNode = new TimeTableTreeNode(newType.getTypeName(), "TrainType", newType.getTypeId(), 0); // NOI18N 1152 typesNode.add(_leafNode); 1153 _timetableModel.nodeStructureChanged(typesNode); 1154 1155 // Switch to new node 1156 _timetableTree.setSelectionPath(new TreePath(_leafNode.getPath())); 1157 } 1158 1159 /** 1160 * Create a copy of a segment. 1161 * @param layoutId The id for the parent layout. Zero if within the same layout. 1162 * @param segmentId The id of the segment to be duplicated. 1163 * @param segmentsNode The segments node which will be parent for the new segment. 1164 */ 1165 void duplicateSegment(int layoutId, int segmentId, TimeTableTreeNode segmentsNode) { 1166 Segment segment = _dataMgr.getSegment(segmentId); 1167 Segment newSegment = segment.getCopy(layoutId); 1168 setShowReminder(true); 1169 1170 // Build tree components 1171 _leafNode = new TimeTableTreeNode(newSegment.getSegmentName(), "Segment", newSegment.getSegmentId(), 0); // NOI18N 1172 segmentsNode.add(_leafNode); 1173 _timetableModel.nodeStructureChanged(segmentsNode); 1174 1175 // Duplicate the stations using the stations from the orignal segment 1176 var segmentNode = _leafNode; 1177 for (var station : _dataMgr.getStations(segmentId, true)) { 1178 duplicateStation(newSegment.getSegmentId(), station.getStationId(), segmentNode); 1179 } 1180 1181 // Switch to new node 1182 _timetableTree.setSelectionPath(new TreePath(_leafNode.getPath())); 1183 } 1184 1185 /** 1186 * Create a copy of a station. 1187 * @param segmentId The id for the parent segment. Zero if within the same segment. 1188 * @param stationId The id of the station to be duplicated. 1189 * @param segmentNode The segment node which will be parent for the new station. 1190 */ 1191 void duplicateStation(int segmentId, int stationId, TimeTableTreeNode segmentNode) { 1192 Station station = _dataMgr.getStation(stationId); 1193 Station newStation = station.getCopy(segmentId); 1194 setShowReminder(true); 1195 1196 // If part of duplicating a layout, create a station map entry. 1197 if (dupLayout) { 1198 stationMap.put(station.getStationId(), newStation.getStationId()); 1199 } 1200 1201 // Build tree components 1202 _leafNode = new TimeTableTreeNode(newStation.getStationName(), "Station", newStation.getStationId(), 0); // NOI18N 1203 segmentNode.add(_leafNode); 1204 _timetableModel.nodeStructureChanged(segmentNode); 1205 1206 // Switch to new node 1207 _timetableTree.setSelectionPath(new TreePath(_leafNode.getPath())); 1208 } 1209 1210 /** 1211 * Create a copy of a schedule. 1212 * @param layoutId The id for the parent layout. Zero if within the same layout. 1213 * @param scheduleId The id of the schedule to be duplicated. 1214 * @param schedulesNode The schedules node which will be parent for the new schedule. 1215 */ 1216 void duplicateSchedule(int layoutId, int scheduleId, TimeTableTreeNode schedulesNode) { 1217 Schedule schedule = _dataMgr.getSchedule(scheduleId); 1218 Schedule newSchedule = schedule.getCopy(layoutId); 1219 setShowReminder(true); 1220 1221 // Build tree components 1222 _leafNode = new TimeTableTreeNode(buildNodeText("Schedule", newSchedule, 0), "Schedule", newSchedule.getScheduleId(), 0); // NOI18N 1223 schedulesNode.add(_leafNode); 1224 _timetableModel.nodeStructureChanged(schedulesNode); 1225 1226 // Duplicate the trains using the trains from the orignal schedule 1227 TimeTableTreeNode scheduleNode = _leafNode; 1228 for (Train train : _dataMgr.getTrains(scheduleId, 0, true)) { 1229 duplicateTrain(newSchedule.getScheduleId(), train.getTrainId(), 0, scheduleNode); 1230 } 1231 1232 // Switch to new node 1233 _timetableTree.setSelectionPath(new TreePath(_leafNode.getPath())); 1234 } 1235 1236 /** 1237 * Create a copy of a train. 1238 * @param schedId The id for the parent schedule. Zero if within the same schedule. 1239 * @param trainId The id of the train to be duplicated. 1240 * @param typeId The id of the train type. If zero use the source train type. 1241 * @param schedNode The schedule node which will be parent for the new train. 1242 */ 1243 void duplicateTrain(int schedId, int trainId, int typeId, TimeTableTreeNode schedNode ) { 1244 Train train = _dataMgr.getTrain(trainId); 1245 if (typeMap != null && typeMap.containsKey(train.getTypeId())) typeId = typeMap.get(train.getTypeId()); 1246 Train newTrain = train.getCopy(schedId, typeId); 1247 setShowReminder(true); 1248 1249 // If part of duplicating a layout, update the type reference. 1250 if (dupLayout && typeMap.containsKey(train.getTypeId())) { 1251 newTrain.setTypeId(typeMap.get(train.getTypeId())); 1252 } 1253 1254 // Build tree components 1255 _leafNode = new TimeTableTreeNode(newTrain.toString(), "Train", newTrain.getTrainId(), 0); // NOI18N 1256 schedNode.add(_leafNode); 1257 _timetableModel.nodeStructureChanged(schedNode); 1258 1259 // Duplicate the stops using the stops from the orignal train 1260 TimeTableTreeNode trainNode = _leafNode; 1261 for (Stop stop : _dataMgr.getStops(trainId, 0, true)) { 1262 duplicateStop(newTrain.getTrainId(), stop.getStopId(), 0, stop.getSeq(), trainNode); 1263 } 1264 1265 // Switch to new node 1266 _timetableTree.setSelectionPath(new TreePath(_leafNode.getPath())); 1267 } 1268 1269 /** 1270 * Create a copy of a stop. 1271 * @param trainId The id for the parent train. Zero if within the same train. 1272 * @param stopId The id of the stop to be duplicated. 1273 * @param stationId The id of the station. If zero use the source station. 1274 * @param seq The sequence for the new stop. If zero calculate the next sequence number. 1275 * @param trainNode The train node which will be parent for the new stop. 1276 */ 1277 void duplicateStop(int trainId, int stopId, int stationId, int seq, TimeTableTreeNode trainNode) { 1278 Stop stop = _dataMgr.getStop(stopId); 1279 if (seq == 0) seq = _dataMgr.getStops(stop.getTrainId(), 0, false).size() + 1; 1280 Stop newStop = stop.getCopy(trainId, stationId, seq); 1281 setShowReminder(true); 1282 1283 // If part of duplicating a layout, update the station reference. 1284 if (dupLayout && stationMap.containsKey(stop.getStationId())) { 1285 newStop.setStationId(stationMap.get(stop.getStationId())); 1286 } 1287 1288 // Build tree components 1289 _leafNode = new TimeTableTreeNode(buildNodeText("Stop", newStop, 0), "Stop", newStop.getStopId(), seq); // NOI18N 1290 trainNode.add(_leafNode); 1291 _timetableModel.nodeStructureChanged(trainNode); 1292 1293 // Switch to new node 1294 _timetableTree.setSelectionPath(new TreePath(_leafNode.getPath())); 1295 } 1296 1297 /** 1298 * Copy the stops from an existing train. 1299 */ 1300 void copyPressed() { 1301 var selectedTrain = copyTrainSelection(); 1302 if (selectedTrain != null) { 1303 for (var stop : _dataMgr.getStops(selectedTrain.getTrainId(), 0, true)) { 1304 // Create stop 1305 var newSeq = _dataMgr.getStops(_curNodeId, 0, false).size(); 1306 var newStop = new Stop(_curNodeId, newSeq + 1); 1307 1308 // Clone stop 1309 newStop.setStationId(stop.getStationId()); 1310 newStop.setDuration(stop.getDuration()); 1311 newStop.setNextSpeed(stop.getNextSpeed()); 1312 newStop.setStagingTrack(stop.getStagingTrack()); 1313 newStop.setStopNotes(stop.getStopNotes()); 1314 1315 // Build tree content 1316 _leafNode = new TimeTableTreeNode(buildNodeText("Stop", newStop, 0), // NOI18N 1317 "Stop", newStop.getStopId(), newSeq + 1); // NOI18N 1318 _curNode.add(_leafNode); 1319 _timetableModel.nodeStructureChanged(_curNode); 1320 } 1321 } 1322 } 1323 1324 /** 1325 * Select the train whose stops will be added to the new train. 1326 * @return the selected train or null if there is no selection made. 1327 */ 1328 Train copyTrainSelection() { 1329 var newTrain = _dataMgr.getTrain(_curNodeId); 1330 var trainList = _dataMgr.getTrains(newTrain.getScheduleId(), 0, true); 1331 trainList.remove(newTrain); 1332 1333 var trainArray = new Train[trainList.size()]; 1334 trainList.toArray(trainArray); 1335 1336 try { 1337 var icon = new ImageIcon(jmri.util.FileUtil.getProgramPath() + jmri.Application.getLogo()); 1338 var choice = JmriJOptionPane.showInputDialog( 1339 null, 1340 Bundle.getMessage("LabelCopyStops"), // NOI18N 1341 Bundle.getMessage("TitleCopyStops"), // NOI18N 1342 JmriJOptionPane.QUESTION_MESSAGE, 1343 icon, 1344 trainArray, 1345 null); 1346 return (Train) choice; 1347 } catch (HeadlessException ex) { 1348 return null; 1349 } 1350 } 1351 1352 /** 1353 * Set up the edit environment for the selected node Called from 1354 * {@link #treeRowSelected}. This takes the place of an actual button. 1355 */ 1356 void editPressed() { 1357 switch (_curNodeType) { 1358 case "Layout": // NOI18N 1359 editLayout(); 1360 makeDetailGrid("Layout"); // NOI18N 1361 break; 1362 1363 case "TrainType": // NOI18N 1364 editTrainType(); 1365 makeDetailGrid("TrainType"); // NOI18N 1366 break; 1367 1368 case "Segment": // NOI18N 1369 editSegment(); 1370 makeDetailGrid("Segment"); // NOI18N 1371 break; 1372 1373 case "Station": // NOI18N 1374 editStation(); 1375 makeDetailGrid("Station"); // NOI18N 1376 break; 1377 1378 case "Schedule": // NOI18N 1379 editSchedule(); 1380 makeDetailGrid("Schedule"); // NOI18N 1381 break; 1382 1383 case "Train": // NOI18N 1384 editTrain(); 1385 makeDetailGrid("Train"); // NOI18N 1386 break; 1387 1388 case "Stop": // NOI18N 1389 editStop(); 1390 makeDetailGrid("Stop"); // NOI18N 1391 break; 1392 1393 default: 1394 log.error("Edit called for unsupported node type: '{}'", _curNodeType); // NOI18N 1395 } 1396 setEditMode(false); 1397 } 1398 1399 /* 1400 * Set Layout edit variables and labels 1401 */ 1402 void editLayout() { 1403 Layout layout = _dataMgr.getLayout(_curNodeId); 1404 _editLayoutName.setText(layout.getLayoutName()); 1405 _editFastClock.setText(Integer.toString(layout.getFastClock())); 1406 _editThrottles.setText(Integer.toString(layout.getThrottles())); 1407 _editMetric.setSelected(layout.getMetric()); 1408 String unitMeasure = (layout.getMetric()) 1409 ? Bundle.getMessage("LabelRealMeters") // NOI18N 1410 : Bundle.getMessage("LabelRealFeet"); // NOI18N 1411 _showScaleMK.setText(String.format("%.2f %s", layout.getScaleMK(), unitMeasure)); // NOI18N 1412 1413 _editScale.removeAllItems(); 1414 for (Scale scale : ScaleManager.getScales()) { 1415 _editScale.addItem(scale); 1416 } 1417 jmri.util.swing.JComboBoxUtil.setupComboBoxMaxRows(_editScale); 1418 _editScale.setSelectedItem(layout.getScale()); 1419 } 1420 1421 /* 1422 * Set TrainType edit variables and labels 1423 */ 1424 void editTrainType() { 1425 TrainType type = _dataMgr.getTrainType(_curNodeId); 1426 _editTrainTypeName.setText(type.getTypeName()); 1427 _editTrainTypeColor.setColor(Color.decode(type.getTypeColor())); 1428 } 1429 1430 /* 1431 * Set Segment edit variables and labels 1432 */ 1433 void editSegment() { 1434 Segment segment = _dataMgr.getSegment(_curNodeId); 1435 _editSegmentName.setText(segment.getSegmentName()); 1436 } 1437 1438 /* 1439 * Set Station edit variables and labels 1440 */ 1441 void editStation() { 1442 Station station = _dataMgr.getStation(_curNodeId); 1443 _editStationName.setText(station.getStationName()); 1444 _editDistance.setText(NumberFormat.getNumberInstance().format(station.getDistance())); 1445 _editDoubleTrack.setSelected(station.getDoubleTrack()); 1446 _editSidings.setValue(station.getSidings()); 1447 _editStaging.setValue(station.getStaging()); 1448 } 1449 1450 /* 1451 * Set Schedule edit variables and labels 1452 */ 1453 void editSchedule() { 1454 Schedule schedule = _dataMgr.getSchedule(_curNodeId); 1455 _editScheduleName.setText(schedule.getScheduleName()); 1456 _editEffDate.setText(schedule.getEffDate()); 1457 _editStartHour.setValue(schedule.getStartHour()); 1458 _editDuration.setValue(schedule.getDuration()); 1459 } 1460 1461 /* 1462 * Set Train edit variables and labels 1463 */ 1464 void editTrain() { 1465 Train train = _dataMgr.getTrain(_curNodeId); 1466 int layoutId = _dataMgr.getSchedule(train.getScheduleId()).getLayoutId(); 1467 1468 _editTrainName.setText(train.getTrainName()); 1469 _editTrainDesc.setText(train.getTrainDesc()); 1470 _editDefaultSpeed.setText(Integer.toString(train.getDefaultSpeed())); 1471 _editTrainStartTime.setText(String.format("%02d:%02d", // NOI18N 1472 train.getStartTime() / 60, 1473 train.getStartTime() % 60)); 1474 _editThrottle.setModel(new SpinnerNumberModel(train.getThrottle(), 0, _dataMgr.getLayout(layoutId).getThrottles(), 1)); 1475 _editTrainNotes.setText(train.getTrainNotes()); 1476 _showRouteDuration.setText(String.format("%02d:%02d", // NOI18N 1477 train.getRouteDuration() / 60, 1478 train.getRouteDuration() % 60)); 1479 1480 _editTrainType.removeAllItems(); 1481 for (TrainType type : _dataMgr.getTrainTypes(layoutId, true)) { 1482 _editTrainType.addItem(type); 1483 } 1484 jmri.util.swing.JComboBoxUtil.setupComboBoxMaxRows(_editTrainType); 1485 if (train.getTypeId() > 0) { 1486 _editTrainType.setSelectedItem(_dataMgr.getTrainType(train.getTypeId())); 1487 } 1488 } 1489 1490 /* 1491 * Set Stop edit variables and labels 1492 * The station combo box uses a data manager internal class to present 1493 * both the segment name and the station name. This is needed since a station 1494 * can be in multiple segments. 1495 */ 1496 void editStop() { 1497 Stop stop = _dataMgr.getStop(_curNodeId); 1498 Layout layout = _dataMgr.getLayoutForStop(_curNodeId); 1499 1500 _showStopSeq.setText(Integer.toString(stop.getSeq())); 1501 _editStopDuration.setText(Integer.toString(stop.getDuration())); 1502 _editNextSpeed.setText(Integer.toString(stop.getNextSpeed())); 1503 _editStopNotes.setText(stop.getStopNotes()); 1504 _showArriveTime.setText(String.format("%02d:%02d", // NOI18N 1505 stop.getArriveTime() / 60, 1506 stop.getArriveTime() % 60)); 1507 _showDepartTime.setText(String.format("%02d:%02d", // NOI18N 1508 stop.getDepartTime() / 60, 1509 stop.getDepartTime() % 60)); 1510 1511 _editStopStation.removeAllItems(); 1512 for (TimeTableDataManager.SegmentStation segmentStation : _dataMgr.getSegmentStations(layout.getLayoutId())) { 1513 _editStopStation.addItem(segmentStation); 1514 if (stop.getStationId() == segmentStation.getStationId()) { 1515 // This also triggers stopStationItemEvent which will set _editStagingTrack 1516 _editStopStation.setSelectedItem(segmentStation); 1517 } 1518 } 1519 jmri.util.swing.JComboBoxUtil.setupComboBoxMaxRows(_editStopStation); 1520 setMoveButtons(); 1521 } 1522 1523 /** 1524 * Apply the updates to the current node. 1525 */ 1526 void updatePressed() { 1527 switch (_curNodeType) { 1528 case "Layout": // NOI18N 1529 updateLayout(); 1530 break; 1531 1532 case "TrainType": // NOI18N 1533 updateTrainType(); 1534 break; 1535 1536 case "Segment": // NOI18N 1537 updateSegment(); 1538 break; 1539 1540 case "Station": // NOI18N 1541 updateStation(); 1542 break; 1543 1544 case "Schedule": // NOI18N 1545 updateSchedule(); 1546 break; 1547 1548 case "Train": // NOI18N 1549 updateTrain(); 1550 break; 1551 1552 case "Stop": // NOI18N 1553 updateStop(); 1554 break; 1555 1556 default: 1557 log.warn("Invalid update button press"); // NOI18N 1558 } 1559 setEditMode(false); 1560 _timetableTree.setSelectionPath(_curTreePath); 1561 _timetableTree.grabFocus(); 1562 editPressed(); 1563 } 1564 1565 /** 1566 * Update the layout information. 1567 * If the fast clock or metric values change, a recalc will be required. 1568 * The throttles value cannot be less than the highest throttle assigned to a train. 1569 */ 1570 void updateLayout() { 1571 Layout layout = _dataMgr.getLayout(_curNodeId); 1572 1573 // Pre-validate and convert inputs 1574 String newName = _editLayoutName.getText().trim(); 1575 Scale newScale = (Scale) _editScale.getSelectedItem(); 1576 int newFastClock = parseNumber(_editFastClock, "fast clock"); // NOI18N 1577 if (newFastClock < 1) { 1578 newFastClock = layout.getFastClock(); 1579 } 1580 int newThrottles = parseNumber(_editThrottles, "throttles"); // NOI18N 1581 if (newThrottles < 0) { 1582 newThrottles = layout.getThrottles(); 1583 } 1584 boolean newMetric =_editMetric.isSelected(); 1585 1586 boolean update = false; 1587 List<String> exceptionList = new ArrayList<>(); 1588 1589 // Perform updates 1590 if (!layout.getLayoutName().equals(newName)) { 1591 layout.setLayoutName(newName); 1592 _curNode.setText(newName); 1593 _timetableModel.nodeChanged(_curNode); 1594 update = true; 1595 } 1596 1597 if (!layout.getScale().equals(newScale)) { 1598 try { 1599 layout.setScale(newScale); 1600 update = true; 1601 } catch (IllegalArgumentException ex) { 1602 exceptionList.add(ex.getMessage()); 1603 } 1604 } 1605 1606 if (layout.getFastClock() != newFastClock) { 1607 try { 1608 layout.setFastClock(newFastClock); 1609 update = true; 1610 } catch (IllegalArgumentException ex) { 1611 exceptionList.add(ex.getMessage()); 1612 } 1613 } 1614 1615 if (layout.getMetric() != newMetric) { 1616 try { 1617 layout.setMetric(newMetric); 1618 update = true; 1619 } catch (IllegalArgumentException ex) { 1620 exceptionList.add(ex.getMessage()); 1621 } 1622 } 1623 1624 if (layout.getThrottles() != newThrottles) { 1625 try { 1626 layout.setThrottles(newThrottles); 1627 update = true; 1628 } catch (IllegalArgumentException ex) { 1629 exceptionList.add(ex.getMessage()); 1630 } 1631 } 1632 1633 if (update) { 1634 setShowReminder(true); 1635 } 1636 1637 // Display exceptions if necessary 1638 if (!exceptionList.isEmpty()) { 1639 StringBuilder msg = new StringBuilder(Bundle.getMessage("LayoutUpdateErrors")); // NOI18N 1640 for (String keyWord : exceptionList) { 1641 if (keyWord.startsWith(TimeTableDataManager.TIME_OUT_OF_RANGE)) { 1642 String[] comps = keyWord.split("~"); 1643 msg.append(Bundle.getMessage(comps[0], comps[1], comps[2])); 1644 } else if (keyWord.startsWith(TimeTableDataManager.SCALE_NF)) { 1645 String[] scaleMsg = keyWord.split("~"); 1646 msg.append(Bundle.getMessage(scaleMsg[0], scaleMsg[1])); 1647 } else { 1648 msg.append(String.format("%n%s", Bundle.getMessage(keyWord))); 1649 if (keyWord.equals(TimeTableDataManager.THROTTLES_IN_USE)) { 1650 // Add the affected trains 1651 for (Schedule schedule : _dataMgr.getSchedules(_curNodeId, true)) { 1652 for (Train train : _dataMgr.getTrains(schedule.getScheduleId(), 0, true)) { 1653 if (train.getThrottle() > newThrottles) { 1654 msg.append(String.format("%n %s [ %d ]", train.getTrainName(), train.getThrottle())); 1655 } 1656 } 1657 } 1658 } 1659 } 1660 } 1661 JmriJOptionPane.showMessageDialog(this, 1662 msg.toString(), 1663 Bundle.getMessage("WarningTitle"), // NOI18N 1664 JmriJOptionPane.WARNING_MESSAGE); 1665 } 1666 } 1667 1668 /** 1669 * Update the train type information. 1670 */ 1671 void updateTrainType() { 1672 TrainType type = _dataMgr.getTrainType(_curNodeId); 1673 1674 String newName = _editTrainTypeName.getText().trim(); 1675 Color newColor = _editTrainTypeColor.getColor(); 1676 String newColorHex = jmri.util.ColorUtil.colorToHexString(newColor); 1677 1678 boolean update = false; 1679 1680 if (!type.getTypeName().equals(newName)) { 1681 type.setTypeName(newName); 1682 _curNode.setText(newName); 1683 update = true; 1684 } 1685 if (!type.getTypeColor().equals(newColorHex)) { 1686 type.setTypeColor(newColorHex); 1687 update = true; 1688 } 1689 _timetableModel.nodeChanged(_curNode); 1690 1691 if (update) { 1692 setShowReminder(true); 1693 } 1694 } 1695 1696 /** 1697 * Update the segment information. 1698 */ 1699 void updateSegment() { 1700 String newName = _editSegmentName.getText().trim(); 1701 1702 Segment segment = _dataMgr.getSegment(_curNodeId); 1703 if (!segment.getSegmentName().equals(newName)) { 1704 segment.setSegmentName(newName); 1705 _curNode.setText(newName); 1706 setShowReminder(true); 1707 } 1708 _timetableModel.nodeChanged(_curNode); 1709 } 1710 1711 /** 1712 * Update the station information. 1713 * The staging track value cannot be less than any train references. 1714 */ 1715 void updateStation() { 1716 Station station = _dataMgr.getStation(_curNodeId); 1717 1718 // Pre-validate and convert inputs 1719 String newName = _editStationName.getText().trim(); 1720 double newDistance; 1721 try { 1722 newDistance = NumberFormat.getNumberInstance().parse(_editDistance.getText()).floatValue(); 1723 } catch (NumberFormatException | ParseException ex) { 1724 log.warn("'{}' is not a valid number for {}", _editDistance.getText(), "station distance"); // NOI18N 1725 JmriJOptionPane.showMessageDialog(this, 1726 Bundle.getMessage("NumberFormatError", _editDistance.getText(), "station distance"), // NOI18N 1727 Bundle.getMessage("WarningTitle"), // NOI18N 1728 JmriJOptionPane.WARNING_MESSAGE); 1729 newDistance = station.getDistance(); 1730 } 1731 boolean newDoubleTrack =_editDoubleTrack.isSelected(); 1732 int newSidings = (int) _editSidings.getValue(); 1733 int newStaging = (int) _editStaging.getValue(); 1734 1735 boolean update = false; 1736 List<String> exceptionList = new ArrayList<>(); 1737 1738 // Perform updates 1739 if (!station.getStationName().equals(newName)) { 1740 station.setStationName(newName); 1741 _curNode.setText(newName); 1742 _timetableModel.nodeChanged(_curNode); 1743 update = true; 1744 } 1745 1746 if (newDistance < 0.0) { 1747 newDistance = station.getDistance(); 1748 } 1749 if (Math.abs(station.getDistance() - newDistance) > .01 ) { 1750 try { 1751 station.setDistance(newDistance); 1752 update = true; 1753 } catch (IllegalArgumentException ex) { 1754 exceptionList.add(ex.getMessage()); 1755 } 1756 } 1757 1758 if (station.getDoubleTrack() != newDoubleTrack) { 1759 station.setDoubleTrack(newDoubleTrack); 1760 update = true; 1761 } 1762 1763 if (station.getSidings() != newSidings) { 1764 station.setSidings(newSidings); 1765 update = true; 1766 } 1767 1768 if (station.getStaging() != newStaging) { 1769 try { 1770 station.setStaging(newStaging); 1771 update = true; 1772 } catch (IllegalArgumentException ex) { 1773 exceptionList.add(ex.getMessage()); 1774 } 1775 } 1776 1777 if (update) { 1778 setShowReminder(true); 1779 } 1780 1781 // Display exceptions if necessary 1782 if (!exceptionList.isEmpty()) { 1783 StringBuilder msg = new StringBuilder(Bundle.getMessage("StationUpdateErrors")); // NOI18N 1784 for (String keyWord : exceptionList) { 1785 if (keyWord.startsWith(TimeTableDataManager.TIME_OUT_OF_RANGE)) { 1786 String[] comps = keyWord.split("~"); 1787 msg.append(Bundle.getMessage(comps[0], comps[1], comps[2])); 1788 } else { 1789 msg.append(String.format("%n%s", Bundle.getMessage(keyWord))); 1790 if (keyWord.equals(TimeTableDataManager.STAGING_IN_USE)) { 1791 // Add the affected stops 1792 for (Stop stop : _dataMgr.getStops(0, _curNodeId, false)) { 1793 if (stop.getStagingTrack() > newStaging) { 1794 Train train = _dataMgr.getTrain(stop.getTrainId()); 1795 msg.append(String.format("%n %s, %d", train.getTrainName(), stop.getSeq())); 1796 } 1797 } 1798 } 1799 } 1800 } 1801 JmriJOptionPane.showMessageDialog(this, 1802 msg.toString(), 1803 Bundle.getMessage("WarningTitle"), // NOI18N 1804 JmriJOptionPane.WARNING_MESSAGE); 1805 } 1806 } 1807 1808 /** 1809 * Update the schedule information. 1810 * Changes to the schedule times cannot make a train start time or 1811 * a stop's arrival or departure times invalid. 1812 */ 1813 void updateSchedule() { 1814 Schedule schedule = _dataMgr.getSchedule(_curNodeId); 1815 1816 // Pre-validate and convert inputs 1817 String newName = _editScheduleName.getText().trim(); 1818 String newEffDate = _editEffDate.getText().trim(); 1819 int newStartHour = (int) _editStartHour.getValue(); 1820 if (newStartHour < 0 || newStartHour > 23) { 1821 newStartHour = schedule.getStartHour(); 1822 } 1823 int newDuration = (int) _editDuration.getValue(); 1824 if (newDuration < 1 || newDuration > 24) { 1825 newDuration = schedule.getDuration(); 1826 } 1827 1828 boolean update = false; 1829 List<String> exceptionList = new ArrayList<>(); 1830 1831 // Perform updates 1832 if (!schedule.getScheduleName().equals(newName)) { 1833 schedule.setScheduleName(newName); 1834 update = true; 1835 } 1836 1837 if (!schedule.getEffDate().equals(newEffDate)) { 1838 schedule.setEffDate(newEffDate); 1839 update = true; 1840 } 1841 1842 if (update) { 1843 _curNode.setText(buildNodeText("Schedule", schedule, 0)); // NOI18N 1844 _timetableModel.nodeChanged(_curNode); 1845 } 1846 1847 if (schedule.getStartHour() != newStartHour) { 1848 try { 1849 schedule.setStartHour(newStartHour); 1850 update = true; 1851 } catch (IllegalArgumentException ex) { 1852 exceptionList.add(ex.getMessage()); 1853 } 1854 } 1855 1856 if (schedule.getDuration() != newDuration) { 1857 try { 1858 schedule.setDuration(newDuration); 1859 update = true; 1860 } catch (IllegalArgumentException ex) { 1861 exceptionList.add(ex.getMessage()); 1862 } 1863 } 1864 1865 if (update) { 1866 setShowReminder(true); 1867 } 1868 1869 // Display exceptions if necessary 1870 if (!exceptionList.isEmpty()) { 1871 StringBuilder msg = new StringBuilder(Bundle.getMessage("ScheduleUpdateErrors")); // NOI18N 1872 for (String keyWord : exceptionList) { 1873 if (keyWord.startsWith(TimeTableDataManager.TIME_OUT_OF_RANGE)) { 1874 String[] comps = keyWord.split("~"); 1875 msg.append(Bundle.getMessage(comps[0], comps[1], comps[2])); 1876 } else { 1877 msg.append(String.format("%n%s", Bundle.getMessage(keyWord))); 1878 } 1879 } 1880 JmriJOptionPane.showMessageDialog(this, 1881 msg.toString(), 1882 Bundle.getMessage("WarningTitle"), // NOI18N 1883 JmriJOptionPane.WARNING_MESSAGE); 1884 } 1885 } 1886 1887 /** 1888 * Update the train information. 1889 * The train start time has to have a h:mm format and cannot fall outside 1890 * of the schedules times. 1891 */ 1892 void updateTrain() { 1893 Train train = _dataMgr.getTrain(_curNodeId); 1894 List<String> exceptionList = new ArrayList<>(); 1895 1896 // Pre-validate and convert inputs 1897 String newName = _editTrainName.getText().trim(); 1898 String newDesc = _editTrainDesc.getText().trim(); 1899 int newType = ((TrainType) _editTrainType.getSelectedItem()).getTypeId(); 1900 int newSpeed = parseNumber(_editDefaultSpeed, "default train speed"); // NOI18N 1901 if (newSpeed < 0) { 1902 newSpeed = train.getDefaultSpeed(); 1903 } 1904 1905 LocalTime newTime; 1906 int newStart; 1907 try { 1908 newTime = LocalTime.parse(_editTrainStartTime.getText().trim(), DateTimeFormatter.ofPattern("H:mm")); // NOI18N 1909 newStart = newTime.getHour() * 60 + newTime.getMinute(); 1910 } catch (java.time.format.DateTimeParseException ex) { 1911 exceptionList.add(TimeTableDataManager.START_TIME_FORMAT + "~" + ex.getParsedString()); 1912 newStart = train.getStartTime(); 1913 } 1914 1915 int newThrottle = (int) _editThrottle.getValue(); 1916 String newNotes = _editTrainNotes.getText(); 1917 1918 boolean update = false; 1919 1920 // Perform updates 1921 if (!train.getTrainName().equals(newName)) { 1922 train.setTrainName(newName); 1923 update = true; 1924 } 1925 1926 if (!train.getTrainDesc().equals(newDesc)) { 1927 train.setTrainDesc(newDesc); 1928 update = true; 1929 } 1930 1931 if (update) { 1932 _curNode.setText(buildNodeText("Train", train, 0)); // NOI18N 1933 _timetableModel.nodeChanged(_curNode); 1934 } 1935 1936 if (train.getTypeId() != newType) { 1937 train.setTypeId(newType); 1938 update = true; 1939 } 1940 1941 if (train.getDefaultSpeed() != newSpeed) { 1942 try { 1943 train.setDefaultSpeed(newSpeed); 1944 update = true; 1945 } catch (IllegalArgumentException ex) { 1946 exceptionList.add(ex.getMessage()); 1947 } 1948 } 1949 1950 if (train.getStartTime() != newStart) { 1951 try { 1952 train.setStartTime(newStart); 1953 update = true; 1954 } catch (IllegalArgumentException ex) { 1955 exceptionList.add(ex.getMessage()); 1956 } 1957 } 1958 1959 if (train.getThrottle() != newThrottle) { 1960 try { 1961 train.setThrottle(newThrottle); 1962 update = true; 1963 } catch (IllegalArgumentException ex) { 1964 exceptionList.add(ex.getMessage()); 1965 } 1966 } 1967 1968 if (!train.getTrainNotes().equals(newNotes)) { 1969 train.setTrainNotes(newNotes); 1970 update = true; 1971 } 1972 1973 if (update) { 1974 setShowReminder(true); 1975 } 1976 1977 // Display exceptions if necessary 1978 if (!exceptionList.isEmpty()) { 1979 StringBuilder msg = new StringBuilder(Bundle.getMessage("TrainUpdateErrors")); // NOI18N 1980 for (String keyWord : exceptionList) { 1981 log.info("kw = {}", keyWord); 1982 if (keyWord.startsWith(TimeTableDataManager.TIME_OUT_OF_RANGE)) { 1983 String[] comps = keyWord.split("~"); 1984 msg.append(Bundle.getMessage(comps[0], comps[1], comps[2])); 1985 } else if (keyWord.startsWith(TimeTableDataManager.START_TIME_FORMAT)) { 1986 String[] timeMsg = keyWord.split("~"); 1987 msg.append(Bundle.getMessage(timeMsg[0], timeMsg[1])); 1988 } else if (keyWord.startsWith(TimeTableDataManager.START_TIME_RANGE)) { 1989 String[] schedMsg = keyWord.split("~"); 1990 msg.append(Bundle.getMessage(schedMsg[0], schedMsg[1], schedMsg[2])); 1991 } else { 1992 msg.append(String.format("%n%s", Bundle.getMessage(keyWord))); 1993 } 1994 } 1995 JmriJOptionPane.showMessageDialog(this, 1996 msg.toString(), 1997 Bundle.getMessage("WarningTitle"), // NOI18N 1998 JmriJOptionPane.WARNING_MESSAGE); 1999 } 2000 } 2001 2002 /** 2003 * Update the stop information. 2004 */ 2005 void updateStop() { 2006 Stop stop = _dataMgr.getStop(_curNodeId); 2007 2008 // Pre-validate and convert inputs 2009 TimeTableDataManager.SegmentStation stopSegmentStation = 2010 (TimeTableDataManager.SegmentStation) _editStopStation.getSelectedItem(); 2011 int newStation = stopSegmentStation.getStationId(); 2012 int newDuration = parseNumber(_editStopDuration, "stop duration"); // NOI18N 2013 if (newDuration < 0) { 2014 newDuration = stop.getDuration(); 2015 } 2016 int newSpeed = parseNumber(_editNextSpeed, "next speed"); // NOI18N 2017 if (newSpeed < 0) { 2018 newSpeed = stop.getNextSpeed(); 2019 } 2020 int newStagingTrack = (int) _editStagingTrack.getValue(); 2021 String newNotes = _editStopNotes.getText(); 2022 2023 boolean update = false; 2024 List<String> exceptionList = new ArrayList<>(); 2025 2026 // Perform updates 2027 if (stop.getStationId() != newStation) { 2028 stop.setStationId(newStation); 2029 _curNode.setText(buildNodeText("Stop", stop, 0)); // NOI18N 2030 _timetableModel.nodeChanged(_curNode); 2031 update = true; 2032 } 2033 2034 if (stop.getDuration() != newDuration) { 2035 try { 2036 stop.setDuration(newDuration); 2037 update = true; 2038 } catch (IllegalArgumentException ex) { 2039 exceptionList.add(ex.getMessage()); 2040 } 2041 } 2042 2043 if (stop.getNextSpeed() != newSpeed) { 2044 try { 2045 stop.setNextSpeed(newSpeed); 2046 update = true; 2047 } catch (IllegalArgumentException ex) { 2048 exceptionList.add(ex.getMessage()); 2049 } 2050 } 2051 2052 if (stop.getStagingTrack() != newStagingTrack) { 2053 try { 2054 stop.setStagingTrack(newStagingTrack); 2055 update = true; 2056 } catch (IllegalArgumentException ex) { 2057 exceptionList.add(ex.getMessage()); 2058 } 2059 } 2060 2061 if (!stop.getStopNotes().equals(newNotes)) { 2062 stop.setStopNotes(newNotes); 2063 update = true; 2064 } 2065 2066 if (update) { 2067 setShowReminder(true); 2068 } 2069 2070 // Display exceptions if necessary 2071 if (!exceptionList.isEmpty()) { 2072 StringBuilder msg = new StringBuilder(Bundle.getMessage("StopUpdateErrors")); // NOI18N 2073 for (String keyWord : exceptionList) { 2074 if (keyWord.startsWith(TimeTableDataManager.TIME_OUT_OF_RANGE)) { 2075 String[] comps = keyWord.split("~"); 2076 msg.append(Bundle.getMessage(comps[0], comps[1], comps[2])); 2077 } else { 2078 msg.append(String.format("%n%s", Bundle.getMessage(keyWord))); 2079 } 2080 } 2081 JmriJOptionPane.showMessageDialog(this, 2082 msg.toString(), 2083 Bundle.getMessage("WarningTitle"), // NOI18N 2084 JmriJOptionPane.WARNING_MESSAGE); 2085 } 2086 } 2087 2088 /** 2089 * Convert text input to an integer. 2090 * @param textField JTextField containing the probable integer. 2091 * @param fieldName The name of the field for the dialog. 2092 * @return the valid number or -1 for an invalid input. 2093 */ 2094 int parseNumber(JTextField textField, String fieldName) { 2095 String text = textField.getText().trim(); 2096 try { 2097 return Integer.parseInt(text); 2098 } catch (NumberFormatException ex) { 2099 log.warn("'{}' is not a valid number for {}", text, fieldName); // NOI18N 2100 JmriJOptionPane.showMessageDialog(textField, 2101 Bundle.getMessage("NumberFormatError", text, fieldName), // NOI18N 2102 Bundle.getMessage("WarningTitle"), // NOI18N 2103 JmriJOptionPane.WARNING_MESSAGE); 2104 return -1; 2105 } 2106 } 2107 2108 /** 2109 * Process the node delete request. 2110 */ 2111 void deletePressed() { 2112 switch (_curNodeType) { 2113 case "Layout": // NOI18N 2114 deleteLayout(); 2115 break; 2116 2117 case "TrainType": // NOI18N 2118 deleteTrainType(); 2119 break; 2120 2121 case "Segment": // NOI18N 2122 deleteSegment(); 2123 break; 2124 2125 case "Station": // NOI18N 2126 deleteStation(); 2127 break; 2128 2129 case "Schedule": // NOI18N 2130 deleteSchedule(); 2131 break; 2132 2133 case "Train": // NOI18N 2134 deleteTrain(); 2135 break; 2136 2137 case "Stop": 2138 deleteStop(); // NOI18N 2139 break; 2140 2141 default: 2142 log.error("Delete called for unsupported node type: '{}'", _curNodeType); // NOI18N 2143 } 2144 } 2145 2146 /** 2147 * After confirmation, perform a cascade delete of the layout and its components. 2148 */ 2149 void deleteLayout() { 2150 Object[] options = {Bundle.getMessage("ButtonNo"), Bundle.getMessage("ButtonYes")}; // NOI18N 2151 int selectedOption = JmriJOptionPane.showOptionDialog(this, 2152 Bundle.getMessage("LayoutCascade"), // NOI18N 2153 Bundle.getMessage("QuestionTitle"), // NOI18N 2154 JmriJOptionPane.DEFAULT_OPTION, 2155 JmriJOptionPane.QUESTION_MESSAGE, 2156 null, options, options[0]); 2157 if (selectedOption != 1) { // return if option is not array position 1, YES 2158 return; 2159 } 2160 2161 _dataMgr.setLockCalculate(true); 2162 2163 // Delete the components 2164 for (Schedule schedule : _dataMgr.getSchedules(_curNodeId, false)) { 2165 for (Train train : _dataMgr.getTrains(schedule.getScheduleId(), 0, false)) { 2166 for (Stop stop : _dataMgr.getStops(train.getTrainId(), 0, false)) { 2167 _dataMgr.deleteStop(stop.getStopId()); 2168 } 2169 _dataMgr.deleteTrain(train.getTrainId()); 2170 } 2171 _dataMgr.deleteSchedule(schedule.getScheduleId()); 2172 } 2173 2174 for (Segment segment : _dataMgr.getSegments(_curNodeId, false)) { 2175 for (Station station : _dataMgr.getStations(segment.getSegmentId(), false)) { 2176 _dataMgr.deleteStation(station.getStationId()); 2177 } 2178 _dataMgr.deleteSegment(segment.getSegmentId()); 2179 } 2180 2181 for (TrainType type : _dataMgr.getTrainTypes(_curNodeId, false)) { 2182 _dataMgr.deleteTrainType(type.getTypeId()); 2183 } 2184 2185 // delete the Layout 2186 _dataMgr.deleteLayout(_curNodeId); 2187 setShowReminder(true); 2188 2189 // Update the tree 2190// TreePath parentPath = _curTreePath.getParentPath(); 2191 TreeNode parentNode = _curNode.getParent(); 2192 _curNode.removeFromParent(); 2193 _curNode = null; 2194 _timetableModel.nodeStructureChanged(parentNode); 2195// _timetableTree.setSelectionPath(parentPath); 2196 _dataMgr.setLockCalculate(false); 2197 } 2198 2199 /** 2200 * Delete a train type after checking for usage. 2201 */ 2202 void deleteTrainType() { 2203 // Check train references 2204 ArrayList<String> typeReference = new ArrayList<>(); 2205 for (Train train : _dataMgr.getTrains(0, _curNodeId, true)) { 2206 typeReference.add(train.getTrainName()); 2207 } 2208 if (!typeReference.isEmpty()) { 2209 StringBuilder msg = new StringBuilder(Bundle.getMessage("DeleteWarning", _curNodeType)); // NOI18N 2210 for (String trainName : typeReference) { 2211 msg.append("\n " + trainName); // NOI18N 2212 } 2213 JmriJOptionPane.showMessageDialog(this, 2214 msg.toString(), 2215 Bundle.getMessage("WarningTitle"), // NOI18N 2216 JmriJOptionPane.WARNING_MESSAGE); 2217 return; 2218 } 2219 _dataMgr.deleteTrainType(_curNodeId); 2220 setShowReminder(true); 2221 2222 // Update the tree 2223 TreePath parentPath = _curTreePath.getParentPath(); 2224 TimeTableTreeNode parentNode = (TimeTableTreeNode) _curNode.getParent(); 2225 parentNode.remove(_curNode); 2226 _timetableModel.nodeStructureChanged(parentNode); 2227 _curNode = null; 2228 _timetableTree.setSelectionPath(parentPath); 2229 } 2230 2231 /** 2232 * Delete a Segment. 2233 * If the segment contains inactive stations, provide the option to perform 2234 * a cascade delete. 2235 */ 2236 void deleteSegment() { 2237 List<Station> stationList = new ArrayList<>(_dataMgr.getStations(_curNodeId, true)); 2238 if (!stationList.isEmpty()) { 2239 // The segment still has stations. See if any are still used by Stops 2240 List<Station> activeList = new ArrayList<>(); 2241 for (Station checkActive : stationList) { 2242 List<Stop> stopList = new ArrayList<>(_dataMgr.getStops(0, checkActive.getStationId(), true)); 2243 if (!stopList.isEmpty()) { 2244 activeList.add(checkActive); 2245 } 2246 } 2247 if (!activeList.isEmpty()) { 2248 // Cannot delete the Segment 2249 StringBuilder msg = new StringBuilder(Bundle.getMessage("DeleteWarning", _curNodeType)); // NOI18N 2250 for (Station activeStation : activeList) { 2251 msg.append("\n " + activeStation.getStationName()); // NOI18N 2252 } 2253 JmriJOptionPane.showMessageDialog(this, 2254 msg.toString(), 2255 Bundle.getMessage("WarningTitle"), // NOI18N 2256 JmriJOptionPane.WARNING_MESSAGE); 2257 return; 2258 } 2259 // Present the option to delete the stations and the segment 2260 Object[] options = {Bundle.getMessage("ButtonNo"), Bundle.getMessage("ButtonYes")}; // NOI18N 2261 int selectedOption = JmriJOptionPane.showOptionDialog(this, 2262 Bundle.getMessage("SegmentCascade"), // NOI18N 2263 Bundle.getMessage("QuestionTitle"), // NOI18N 2264 JmriJOptionPane.DEFAULT_OPTION, 2265 JmriJOptionPane.QUESTION_MESSAGE, 2266 null, options, options[0]); 2267 if (selectedOption != 1) { // return if option is not array position 1, YES 2268 return; 2269 } 2270 for (Station delStation : stationList) { 2271 _dataMgr.deleteStation(delStation.getStationId()); 2272 } 2273 } 2274 // delete the segment 2275 _dataMgr.deleteSegment(_curNodeId); 2276 setShowReminder(true); 2277 2278 // Update the tree 2279 TreePath parentPath = _curTreePath.getParentPath(); 2280 TimeTableTreeNode parentNode = (TimeTableTreeNode) _curNode.getParent(); 2281 _curNode.removeFromParent(); 2282 _curNode = null; 2283 _timetableModel.nodeStructureChanged(parentNode); 2284 _timetableTree.setSelectionPath(parentPath); 2285 } 2286 2287 /** 2288 * Delete a Station after checking for usage. 2289 */ 2290 void deleteStation() { 2291 // Check stop references 2292 List<String> stopReference = new ArrayList<>(); 2293 for (Stop stop : _dataMgr.getStops(0, _curNodeId, true)) { 2294 Train train = _dataMgr.getTrain(stop.getTrainId()); 2295 String trainSeq = String.format("%s : %d", train.getTrainName(), stop.getSeq()); // NOI18N 2296 stopReference.add(trainSeq); 2297 } 2298 if (!stopReference.isEmpty()) { 2299 StringBuilder msg = new StringBuilder(Bundle.getMessage("DeleteWarning", _curNodeType)); // NOI18N 2300 for (String stopTrainSeq : stopReference) { 2301 msg.append("\n " + stopTrainSeq); // NOI18N 2302 } 2303 JmriJOptionPane.showMessageDialog(this, 2304 msg.toString(), 2305 Bundle.getMessage("WarningTitle"), // NOI18N 2306 JmriJOptionPane.WARNING_MESSAGE); 2307 return; 2308 } 2309 _dataMgr.deleteStation(_curNodeId); 2310 setShowReminder(true); 2311 2312 // Update the tree 2313 TreePath parentPath = _curTreePath.getParentPath(); 2314 TimeTableTreeNode parentNode = (TimeTableTreeNode) _curNode.getParent(); 2315 parentNode.remove(_curNode); 2316 _timetableModel.nodeStructureChanged(parentNode); 2317 _curNode = null; 2318 _timetableTree.setSelectionPath(parentPath); 2319 } 2320 2321 /** 2322 * Delete a Schedule. 2323 * If the schedule contains trains, provide the option to perform 2324 * a cascade delete of trains and their stops. 2325 */ 2326 void deleteSchedule() { 2327 List<Train> trainList = new ArrayList<>(_dataMgr.getTrains(_curNodeId, 0, true)); 2328 if (!trainList.isEmpty()) { 2329 // The schedule still has trains. 2330 // Present the option to delete the stops, trains and the schedule 2331 Object[] options = {Bundle.getMessage("ButtonNo"), Bundle.getMessage("ButtonYes")}; // NOI18N 2332 int selectedOption = JmriJOptionPane.showOptionDialog(this, 2333 Bundle.getMessage("ScheduleCascade"), // NOI18N 2334 Bundle.getMessage("QuestionTitle"), // NOI18N 2335 JmriJOptionPane.DEFAULT_OPTION, 2336 JmriJOptionPane.QUESTION_MESSAGE, 2337 null, options, options[0]); 2338 if (selectedOption != 1) { // return if option is not array position 1, YES 2339 return; 2340 } 2341 for (Train train : trainList) { 2342 for (Stop stop : _dataMgr.getStops(train.getTrainId(), 0, false)) { 2343 _dataMgr.deleteStop(stop.getStopId()); 2344 } 2345 _dataMgr.deleteTrain(train.getTrainId()); 2346 } 2347 } 2348 // delete the schedule 2349 _dataMgr.deleteSchedule(_curNodeId); 2350 setShowReminder(true); 2351 2352 // Update the tree 2353 TreePath parentPath = _curTreePath.getParentPath(); 2354 TimeTableTreeNode parentNode = (TimeTableTreeNode) _curNode.getParent(); 2355 _curNode.removeFromParent(); 2356 _curNode = null; 2357 _timetableModel.nodeStructureChanged(parentNode); 2358 _timetableTree.setSelectionPath(parentPath); 2359 } 2360 2361 /** 2362 * Delete a Train. 2363 * If the train contains stops, provide the option to perform 2364 * a cascade delete of the stops. 2365 */ 2366 void deleteTrain() { 2367 List<Stop> stopList = new ArrayList<>(_dataMgr.getStops(_curNodeId, 0, true)); 2368 if (!stopList.isEmpty()) { 2369 // The trains still has stops. 2370 // Present the option to delete the stops and the train 2371 Object[] options = {Bundle.getMessage("ButtonNo"), Bundle.getMessage("ButtonYes")}; // NOI18N 2372 int selectedOption = JmriJOptionPane.showOptionDialog(this, 2373 Bundle.getMessage("TrainCascade"), // NOI18N 2374 Bundle.getMessage("QuestionTitle"), // NOI18N 2375 JmriJOptionPane.DEFAULT_OPTION, 2376 JmriJOptionPane.QUESTION_MESSAGE, 2377 null, options, options[0]); 2378 if (selectedOption != 1) { // return if option is not array position 1, YES 2379 return; 2380 } 2381 for (Stop stop : stopList) { 2382 _dataMgr.deleteStop(stop.getStopId()); 2383 } 2384 } 2385 // delete the train 2386 _dataMgr.deleteTrain(_curNodeId); 2387 setShowReminder(true); 2388 2389 // Update the tree 2390 TreePath parentPath = _curTreePath.getParentPath(); 2391 TimeTableTreeNode parentNode = (TimeTableTreeNode) _curNode.getParent(); 2392 _curNode.removeFromParent(); 2393 _curNode = null; 2394 _timetableModel.nodeStructureChanged(parentNode); 2395 _timetableTree.setSelectionPath(parentPath); 2396 } 2397 2398 /** 2399 * Delete a Stop. 2400 */ 2401 void deleteStop() { 2402 // delete the stop 2403 _dataMgr.deleteStop(_curNodeId); 2404 setShowReminder(true); 2405 2406 // Update the tree 2407 TreePath parentPath = _curTreePath.getParentPath(); 2408 TimeTableTreeNode parentNode = (TimeTableTreeNode) _curNode.getParent(); 2409 _curNode.removeFromParent(); 2410 _curNode = null; 2411 _timetableModel.nodeStructureChanged(parentNode); 2412 _timetableTree.setSelectionPath(parentPath); 2413 } 2414 2415 /** 2416 * Cancel the current node edit. 2417 */ 2418 void cancelPressed() { 2419 setEditMode(false); 2420 _timetableTree.setSelectionPath(_curTreePath); 2421 _timetableTree.grabFocus(); 2422 } 2423 2424 /** 2425 * Move a Stop row up 1 row. 2426 */ 2427 void upPressed() { 2428 setShowReminder(true); 2429 2430 DefaultMutableTreeNode prevNode = _curNode.getPreviousSibling(); 2431 if (!(prevNode instanceof TimeTableTreeNode)) { 2432 log.warn("At first node, cannot move up"); // NOI18N 2433 return; 2434 } 2435 int prevStopId = ((TimeTableTreeNode) prevNode).getId(); 2436 Stop prevStop = _dataMgr.getStop(prevStopId); 2437 prevStop.setSeq(prevStop.getSeq() + 1); 2438 Stop currStop = _dataMgr.getStop(_curNodeId); 2439 currStop.setSeq(currStop.getSeq() - 1); 2440 moveTreeNode("Up"); // NOI18N 2441 } 2442 2443 /** 2444 * Move a Stop row down 1 row. 2445 */ 2446 void downPressed() { 2447 setShowReminder(true); 2448 2449 DefaultMutableTreeNode nextNode = _curNode.getNextSibling(); 2450 if (!(nextNode instanceof TimeTableTreeNode)) { 2451 log.warn("At last node, cannot move down"); // NOI18N 2452 return; 2453 } 2454 int nextStopId = ((TimeTableTreeNode) nextNode).getId(); 2455 Stop nextStop = _dataMgr.getStop(nextStopId); 2456 nextStop.setSeq(nextStop.getSeq() - 1); 2457 Stop currStop = _dataMgr.getStop(_curNodeId); 2458 currStop.setSeq(currStop.getSeq() + 1); 2459 moveTreeNode("Down"); // NOI18N 2460 } 2461 2462 /** 2463 * Move a tree node in response to a up or down request. 2464 * 2465 * @param direction The direction of movement, Up or Down 2466 */ 2467 void moveTreeNode(String direction) { 2468 // Update the node 2469 if (direction.equals("Up")) { // NOI18N 2470 _curNodeRow -= 1; 2471 } else { 2472 _curNodeRow += 1; 2473 } 2474 _curNode.setRow(_curNodeRow); 2475 _timetableModel.nodeChanged(_curNode); 2476 2477 // Update the sibling 2478 DefaultMutableTreeNode siblingNode; 2479 TimeTableTreeNode tempNode; 2480 if (direction.equals("Up")) { // NOI18N 2481 siblingNode = _curNode.getPreviousSibling(); 2482 if (siblingNode instanceof TimeTableTreeNode) { 2483 tempNode = (TimeTableTreeNode) siblingNode; 2484 tempNode.setRow(tempNode.getRow() + 1); 2485 } 2486 } else { 2487 siblingNode = _curNode.getNextSibling(); 2488 if (siblingNode instanceof TimeTableTreeNode) { 2489 tempNode = (TimeTableTreeNode) siblingNode; 2490 tempNode.setRow(tempNode.getRow() - 1); 2491 } 2492 } 2493 _timetableModel.nodeChanged(siblingNode); 2494 2495 // Update the tree 2496 TimeTableTreeNode parentNode = (TimeTableTreeNode) _curNode.getParent(); 2497 parentNode.insert(_curNode, _curNodeRow - 1); 2498 _timetableModel.nodeStructureChanged(parentNode); 2499 _timetableTree.setSelectionPath(new TreePath(_curNode.getPath())); 2500 setMoveButtons(); 2501 2502 // Update times 2503 _dataMgr.calculateTrain(_dataMgr.getStop(_curNodeId).getTrainId(), true); 2504 } 2505 2506 /** 2507 * Enable/Disable the Up and Down buttons based on the postion in the list. 2508 */ 2509 void setMoveButtons() { 2510 if (_curNode == null) { 2511 return; 2512 } 2513 2514 Component[] compList = _moveButtonPanel.getComponents(); 2515 JButton up = (JButton) compList[1]; 2516 JButton down = (JButton) compList[3]; 2517 2518 up.setEnabled(true); 2519 down.setEnabled(true); 2520 2521 int rows = _curNode.getSiblingCount(); 2522 if (_curNodeRow < 2) { 2523 up.setEnabled(false); 2524 } 2525 if (_curNodeRow > rows - 1) { 2526 down.setEnabled(false); 2527 } 2528 2529 // Disable move buttons during Variable or Action add or edit processing, or nothing selected 2530 if (_editActive) { 2531 up.setEnabled(false); 2532 down.setEnabled(false); 2533 } 2534 2535 _moveButtonPanel.setVisible(true); 2536 } 2537 2538 void graphPressed(String graphType) { 2539 2540 // select a schedule if necessary 2541 Segment segment = _dataMgr.getSegment(_curNodeId); 2542 Layout layout = _dataMgr.getLayout(segment.getLayoutId()); 2543 int scheduleId; 2544 List<Schedule> schedules = _dataMgr.getSchedules(layout.getLayoutId(), true); 2545 2546 if (schedules.size() == 0) { 2547 log.warn("no schedule"); // NOI18N 2548 return; 2549 } else { 2550 scheduleId = schedules.get(0).getScheduleId(); 2551 if (schedules.size() > 1) { 2552 // do selection dialog 2553 Schedule[] schedArr = new Schedule[schedules.size()]; 2554 schedArr = schedules.toArray(schedArr); 2555 Schedule schedSelected = (Schedule) JmriJOptionPane.showInputDialog( 2556 null, 2557 Bundle.getMessage("GraphScheduleMessage"), // NOI18N 2558 Bundle.getMessage("QuestionTitle"), // NOI18N 2559 JmriJOptionPane.QUESTION_MESSAGE, 2560 null, 2561 schedArr, 2562 schedArr[0] 2563 ); 2564 if (schedSelected == null) { 2565 log.warn("Schedule not selected, graph request cancelled"); // NOI18N 2566 return; 2567 } 2568 scheduleId = schedSelected.getScheduleId(); 2569 } 2570 } 2571 2572 if (graphType.equals("Display")) { 2573 TimeTableDisplayGraph graph = new TimeTableDisplayGraph(_curNodeId, scheduleId, _showTrainTimes); 2574 2575 JmriJFrame f = new JmriJFrame(Bundle.getMessage("TitleTimeTableGraph"), true, true); // NOI18N 2576 f.setMinimumSize(new Dimension(600, 300)); 2577 f.getContentPane().add(graph); 2578 f.pack(); 2579 f.addHelpMenu("html.tools.TimeTable", true); // NOI18N 2580 f.setVisible(true); 2581 } 2582 2583 if (graphType.equals("Print")) { 2584 TimeTablePrintGraph print = new TimeTablePrintGraph(_curNodeId, scheduleId, _showTrainTimes, _twoPage); 2585 print.printGraph(); 2586 } 2587 } 2588 2589 JFileChooser fileChooser; 2590 void importPressed() { 2591 fileChooser = jmri.jmrit.XmlFile.userFileChooser("SchedGen File", "sgn"); // NOI18N 2592 int retVal = fileChooser.showOpenDialog(null); 2593 if (retVal == JFileChooser.APPROVE_OPTION) { 2594 File file = fileChooser.getSelectedFile(); 2595 try { 2596 new TimeTableImport().importSgn(_dataMgr, file); 2597 } catch (IOException ex) { 2598 log.error("Import exception", ex); // NOI18N 2599 JmriJOptionPane.showMessageDialog(this, 2600 Bundle.getMessage("ImportFailed", "SGN"), // NOI18N 2601 Bundle.getMessage("ErrorTitle"), // NOI18N 2602 JmriJOptionPane.ERROR_MESSAGE); 2603 return; 2604 } 2605 savePressed(); 2606 JmriJOptionPane.showMessageDialog(this, 2607 Bundle.getMessage("ImportCompleted", "SGN"), // NOI18N 2608 Bundle.getMessage("MessageTitle"), // NOI18N 2609 JmriJOptionPane.INFORMATION_MESSAGE); 2610 } 2611 } 2612 2613 List<String> feedbackList; 2614 void importCsvPressed() { 2615 fileChooser = new jmri.util.swing.JmriJFileChooser(jmri.util.FileUtil.getUserFilesPath()); 2616 fileChooser.setFileFilter(new FileNameExtensionFilter("Import File", "csv")); 2617 int retVal = fileChooser.showOpenDialog(null); 2618 if (retVal == JFileChooser.APPROVE_OPTION) { 2619 File file = fileChooser.getSelectedFile(); 2620 completeImport(file); 2621 } 2622 } 2623 2624 void completeImport(File file) { 2625 try { 2626 feedbackList = new TimeTableCsvImport().importCsv(file); 2627 } catch (IOException ex) { 2628 log.error("Import exception", ex); // NOI18N 2629 JmriJOptionPane.showMessageDialog(this, 2630 Bundle.getMessage("ImportCsvFailed", "CVS"), // NOI18N 2631 Bundle.getMessage("ErrorTitle"), // NOI18N 2632 JmriJOptionPane.ERROR_MESSAGE); 2633 return; 2634 } 2635 if (feedbackList.size() > 0) { 2636 StringBuilder msg = new StringBuilder(Bundle.getMessage("ImportCsvErrors")); // NOI18N 2637 for (String feedback : feedbackList) { 2638 msg.append(feedback + "\n"); 2639 } 2640 JmriJOptionPane.showMessageDialog(this, 2641 msg.toString(), 2642 Bundle.getMessage("ErrorTitle"), // NOI18N 2643 JmriJOptionPane.ERROR_MESSAGE); 2644 return; 2645 } 2646 savePressed(); 2647 JmriJOptionPane.showMessageDialog(this, 2648 Bundle.getMessage("ImportCompleted", "CSV"), // NOI18N 2649 Bundle.getMessage("MessageTitle"), // NOI18N 2650 JmriJOptionPane.INFORMATION_MESSAGE); 2651 } 2652 2653 void importFromOperationsPressed() { 2654 ExportTimetable ex = new ExportTimetable(); 2655 new ExportTimetable().writeOperationsTimetableFile(); 2656 completeImport(ex.getExportFile()); 2657 } 2658 2659 void exportCsvPressed() { 2660 // Select layout 2661 List<Layout> layouts = _dataMgr.getLayouts(true); 2662 if (layouts.size() == 0) { 2663 JmriJOptionPane.showMessageDialog(this, 2664 Bundle.getMessage("ExportLayoutError"), // NOI18N 2665 Bundle.getMessage("ErrorTitle"), // NOI18N 2666 JmriJOptionPane.ERROR_MESSAGE); 2667 return; 2668 } 2669 int layoutId = layouts.get(0).getLayoutId(); 2670 if (layouts.size() > 1) { 2671 Layout layout = (Layout) JmriJOptionPane.showInputDialog( 2672 this, 2673 Bundle.getMessage("ExportSelectLayout"), // NOI18N 2674 Bundle.getMessage("QuestionTitle"), // NOI18N 2675 JmriJOptionPane.PLAIN_MESSAGE, 2676 null, 2677 layouts.toArray(), 2678 null); 2679 if (layout == null) return; 2680 layoutId = layout.getLayoutId(); 2681 } 2682 2683 // Select segment 2684 List<Segment> segments = _dataMgr.getSegments(layoutId, true); 2685 if (segments.size() == 0) { 2686 JmriJOptionPane.showMessageDialog(this, 2687 Bundle.getMessage("ExportSegmentError"), // NOI18N 2688 Bundle.getMessage("ErrorTitle"), // NOI18N 2689 JmriJOptionPane.ERROR_MESSAGE); 2690 return; 2691 } 2692 int segmentId = segments.get(0).getSegmentId(); 2693 if (segments.size() > 1) { 2694 Segment segment = (Segment) JmriJOptionPane.showInputDialog( 2695 this, 2696 Bundle.getMessage("ExportSelectSegment"), // NOI18N 2697 Bundle.getMessage("QuestionTitle"), // NOI18N 2698 JmriJOptionPane.PLAIN_MESSAGE, 2699 null, 2700 segments.toArray(), 2701 null); 2702 if (segment == null) return; 2703 segmentId = segment.getSegmentId(); 2704 } 2705 2706 // Select schedule 2707 List<Schedule> schedules = _dataMgr.getSchedules(layoutId, true); 2708 if (schedules.size() == 0) { 2709 JmriJOptionPane.showMessageDialog(this, 2710 Bundle.getMessage("ExportScheduleError"), // NOI18N 2711 Bundle.getMessage("ErrorTitle"), // NOI18N 2712 JmriJOptionPane.ERROR_MESSAGE); 2713 return; 2714 } 2715 int scheduleId = schedules.get(0).getScheduleId(); 2716 if (schedules.size() > 1) { 2717 Schedule schedule = (Schedule) JmriJOptionPane.showInputDialog( 2718 this, 2719 Bundle.getMessage("ExportSelectSchedule"), // NOI18N 2720 Bundle.getMessage("QuestionTitle"), // NOI18N 2721 JmriJOptionPane.PLAIN_MESSAGE, 2722 null, 2723 schedules.toArray(), 2724 null); 2725 if (schedule == null) return; 2726 scheduleId = schedule.getScheduleId(); 2727 } 2728 2729 fileChooser = new jmri.util.swing.JmriJFileChooser(jmri.util.FileUtil.getUserFilesPath()); 2730 fileChooser.setFileFilter(new FileNameExtensionFilter("Export as CSV File", "csv")); // NOI18N 2731 int retVal = fileChooser.showSaveDialog(null); 2732 if (retVal == JFileChooser.APPROVE_OPTION) { 2733 File file = fileChooser.getSelectedFile(); 2734 String fileName = file.getAbsolutePath(); 2735 String fileNameLC = fileName.toLowerCase(); 2736 if (!fileNameLC.endsWith(".csv")) { // NOI18N 2737 fileName = fileName + ".csv"; // NOI18N 2738 file = new File(fileName); 2739 } 2740 if (file.exists()) { 2741 if (JmriJOptionPane.showConfirmDialog(this, 2742 Bundle.getMessage("FileOverwriteWarning", file.getName()), // NOI18N 2743 Bundle.getMessage("QuestionTitle"), // NOI18N 2744 JmriJOptionPane.OK_CANCEL_OPTION, 2745 JmriJOptionPane.QUESTION_MESSAGE) != JmriJOptionPane.OK_OPTION) { 2746 return; 2747 } 2748 } 2749 2750 2751 boolean hasErrors; 2752 try { 2753 hasErrors = new TimeTableCsvExport().exportCsv(file, layoutId, segmentId, scheduleId); 2754 } catch (IOException ex) { 2755 log.error("Export exception", ex); // NOI18N 2756 JmriJOptionPane.showMessageDialog(this, 2757 Bundle.getMessage("ExportFailed"), // NOI18N 2758 Bundle.getMessage("ErrorTitle"), // NOI18N 2759 JmriJOptionPane.ERROR_MESSAGE); 2760 return; 2761 } 2762 2763 if (hasErrors) { 2764 JmriJOptionPane.showMessageDialog(this, 2765 Bundle.getMessage("ExportFailed"), // NOI18N 2766 Bundle.getMessage("ErrorTitle"), // NOI18N 2767 JmriJOptionPane.ERROR_MESSAGE); 2768 } else { 2769 JmriJOptionPane.showMessageDialog(this, 2770 Bundle.getMessage("ExportCompleted", file), // NOI18N 2771 Bundle.getMessage("MessageTitle"), // NOI18N 2772 JmriJOptionPane.INFORMATION_MESSAGE); 2773 } 2774 } 2775 } 2776 2777 /** 2778 * Save the current set of timetable data. 2779 */ 2780 void savePressed() { 2781 TimeTableXml.doStore(); 2782 setShowReminder(false); 2783 } 2784 2785 /** 2786 * Check for pending updates and close if none or approved. 2787 */ 2788 void donePressed() { 2789 if (_isDirty) { 2790 Object[] options = {Bundle.getMessage("ButtonNo"), Bundle.getMessage("ButtonYes")}; // NOI18N 2791 int selectedOption = JmriJOptionPane.showOptionDialog(this, 2792 Bundle.getMessage("DirtyDataWarning"), // NOI18N 2793 Bundle.getMessage("WarningTitle"), // NOI18N 2794 JmriJOptionPane.DEFAULT_OPTION, 2795 JmriJOptionPane.WARNING_MESSAGE, 2796 null, options, options[0]); 2797 if (selectedOption == 0) { 2798 return; 2799 } 2800 } 2801 InstanceManager.reset(TimeTableFrame.class); 2802 dispose(); 2803 } 2804 2805 // ------------ Tree Content and Navigation ------------ 2806 2807 /** 2808 * Create the TimeTable tree structure. 2809 * 2810 * @return _timetableTree The tree ddefinition with its content 2811 */ 2812 JTree buildTree() { 2813 _timetableRoot = new DefaultMutableTreeNode("Root Node"); // NOI18N 2814 _timetableModel = new DefaultTreeModel(_timetableRoot); 2815 _timetableTree = new JTree(_timetableModel); 2816 2817 createTimeTableContent(); 2818 2819 // build the tree GUI 2820 _timetableTree.expandPath(new TreePath(_timetableRoot)); 2821 _timetableTree.setRootVisible(false); 2822 _timetableTree.setShowsRootHandles(true); 2823 _timetableTree.setScrollsOnExpand(true); 2824 _timetableTree.setExpandsSelectedPaths(true); 2825 _timetableTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); 2826 2827 // tree listeners 2828 _timetableTree.addTreeSelectionListener(_timetableListener = new TreeSelectionListener() { 2829 @Override 2830 public void valueChanged(TreeSelectionEvent e) { 2831 if (_editActive) { 2832 if (e.getNewLeadSelectionPath() != _curTreePath) { 2833 _timetableTree.setSelectionPath(e.getOldLeadSelectionPath()); 2834 showNodeEditMessage(); 2835 } 2836 return; 2837 } 2838 2839 _curTreePath = _timetableTree.getSelectionPath(); 2840 if (_curTreePath != null) { 2841 Object chkLast = _curTreePath.getLastPathComponent(); 2842 if (chkLast instanceof TimeTableTreeNode) { 2843 treeRowSelected((TimeTableTreeNode) chkLast); 2844 } 2845 } 2846 } 2847 }); 2848 2849 return _timetableTree; 2850 } 2851 2852 /** 2853 * Create the tree content. 2854 * Level 1 -- Layouts 2855 * Level 2 -- Train Type, Segment and Schedule Containers 2856 * Level 3 -- Train Types, Segments, Schedules 2857 * Level 4 -- Stations, Trains 2858 * Level 5 -- Stops 2859 */ 2860 void createTimeTableContent() { 2861 for (Layout l : _dataMgr.getLayouts(true)) { 2862 _layoutNode = new TimeTableTreeNode(l.getLayoutName(), "Layout", l.getLayoutId(), 0); // NOI18N 2863 _timetableRoot.add(_layoutNode); 2864 2865 _typeHead = new TimeTableTreeNode(buildNodeText("TrainTypes", null, 0), "TrainTypes", 0, 0); // NOI18N 2866 _layoutNode.add(_typeHead); 2867 for (TrainType y : _dataMgr.getTrainTypes(l.getLayoutId(), true)) { 2868 _typeNode = new TimeTableTreeNode(y.getTypeName(), "TrainType", y.getTypeId(), 0); // NOI18N 2869 _typeHead.add(_typeNode); 2870 } 2871 2872 _segmentHead = new TimeTableTreeNode(buildNodeText("Segments", null, 0), "Segments", 0, 0); // NOI18N 2873 _layoutNode.add(_segmentHead); 2874 for (Segment sg : _dataMgr.getSegments(l.getLayoutId(), true)) { 2875 _segmentNode = new TimeTableTreeNode(sg.getSegmentName(), "Segment", sg.getSegmentId(), 0); // NOI18N 2876 _segmentHead.add(_segmentNode); 2877 for (Station st : _dataMgr.getStations(sg.getSegmentId(), true)) { 2878 _leafNode = new TimeTableTreeNode(st.getStationName(), "Station", st.getStationId(), 0); // NOI18N 2879 _segmentNode.add(_leafNode); 2880 } 2881 } 2882 2883 _scheduleHead = new TimeTableTreeNode(buildNodeText("Schedules", null, 0), "Schedules", 0, 0); // NOI18N 2884 _layoutNode.add(_scheduleHead); 2885 for (Schedule c : _dataMgr.getSchedules(l.getLayoutId(), true)) { 2886 _scheduleNode = new TimeTableTreeNode(buildNodeText("Schedule", c, 0), "Schedule", c.getScheduleId(), 0); // NOI18N 2887 _scheduleHead.add(_scheduleNode); 2888 for (Train tr : _dataMgr.getTrains(c.getScheduleId(), 0, true)) { 2889 _trainNode = new TimeTableTreeNode(buildNodeText("Train", tr, 0), "Train", tr.getTrainId(), 0); // NOI18N 2890 _scheduleNode.add(_trainNode); 2891 for (Stop sp : _dataMgr.getStops(tr.getTrainId(), 0, true)) { 2892 _leafNode = new TimeTableTreeNode(buildNodeText("Stop", sp, 0), "Stop", sp.getStopId(), sp.getSeq()); // NOI18N 2893 _trainNode.add(_leafNode); 2894 } 2895 } 2896 } 2897 } 2898 } 2899 2900 /** 2901 * Create the localized node text display strings based on node type. 2902 * 2903 * @param nodeType The type of the node 2904 * @param component The object or child object 2905 * @param idx Optional index value 2906 * @return nodeText containing the text to display on the node 2907 */ 2908 String buildNodeText(String nodeType, Object component, int idx) { 2909 switch (nodeType) { 2910 case "TrainTypes": 2911 return Bundle.getMessage("LabelTrainTypes"); // NOI18N 2912 case "Segments": 2913 return Bundle.getMessage("LabelSegments"); // NOI18N 2914 case "Schedules": 2915 return Bundle.getMessage("LabelSchedules"); // NOI18N 2916 case "Schedule": 2917 Schedule schedule = (Schedule) component; 2918 return Bundle.getMessage("LabelSchedule", schedule.getScheduleName(), schedule.getEffDate()); // NOI18N 2919 case "Train": 2920 Train train = (Train) component; 2921 return Bundle.getMessage("LabelTrain", train.getTrainName(), train.getTrainDesc()); // NOI18N 2922 case "Stop": 2923 Stop stop = (Stop) component; 2924 int stationId = stop.getStationId(); 2925 return Bundle.getMessage("LabelStop", stop.getSeq(), _dataMgr.getStation(stationId).getStationName()); // NOI18N 2926 default: 2927 return "None"; // NOI18N 2928 } 2929 } 2930 2931 /** 2932 * Change the button row based on the currently selected node type. Invoke 2933 * edit where appropriate. 2934 * 2935 * @param selectedNode The node object 2936 */ 2937 void treeRowSelected(TimeTableTreeNode selectedNode) { 2938 // Set the current node variables 2939 _curNode = selectedNode; 2940 _curNodeId = selectedNode.getId(); 2941 _curNodeType = selectedNode.getType(); 2942 _curNodeText = selectedNode.getText(); 2943 _curNodeRow = selectedNode.getRow(); 2944 2945 // Reset button bar 2946 _addButtonPanel.setVisible(false); 2947 _duplicateButtonPanel.setVisible(false); 2948 _copyButtonPanel.setVisible(false); 2949 _deleteButtonPanel.setVisible(false); 2950 _moveButtonPanel.setVisible(false); 2951 _graphButtonPanel.setVisible(false); 2952 2953 switch (_curNodeType) { 2954 case "Layout": // NOI18N 2955 _addButton.setText(Bundle.getMessage("AddLayoutButtonText")); // NOI18N 2956 _addButtonPanel.setVisible(true); 2957 _duplicateButton.setText(Bundle.getMessage("DuplicateLayoutButtonText")); // NOI18N 2958 _duplicateButtonPanel.setVisible(true); 2959 _deleteButton.setText(Bundle.getMessage("DeleteLayoutButtonText")); // NOI18N 2960 _deleteButtonPanel.setVisible(true); 2961 editPressed(); 2962 break; 2963 2964 case "TrainTypes": // NOI18N 2965 _addButton.setText(Bundle.getMessage("AddTrainTypeButtonText")); // NOI18N 2966 _addButtonPanel.setVisible(true); 2967 makeDetailGrid(EMPTY_GRID); // NOI18N 2968 break; 2969 2970 case "TrainType": // NOI18N 2971 _duplicateButton.setText(Bundle.getMessage("DuplicateTrainTypeButtonText")); // NOI18N 2972 _duplicateButtonPanel.setVisible(true); 2973 _deleteButton.setText(Bundle.getMessage("DeleteTrainTypeButtonText")); // NOI18N 2974 _deleteButtonPanel.setVisible(true); 2975 editPressed(); 2976 break; 2977 2978 case "Segments": // NOI18N 2979 _addButton.setText(Bundle.getMessage("AddSegmentButtonText")); // NOI18N 2980 _addButtonPanel.setVisible(true); 2981 makeDetailGrid(EMPTY_GRID); // NOI18N 2982 break; 2983 2984 case "Segment": // NOI18N 2985 _addButton.setText(Bundle.getMessage("AddStationButtonText")); // NOI18N 2986 _addButtonPanel.setVisible(true); 2987 _duplicateButton.setText(Bundle.getMessage("DuplicateSegmentButtonText")); // NOI18N 2988 _duplicateButtonPanel.setVisible(true); 2989 _deleteButton.setText(Bundle.getMessage("DeleteSegmentButtonText")); // NOI18N 2990 _deleteButtonPanel.setVisible(true); 2991 _graphButtonPanel.setVisible(true); 2992 editPressed(); 2993 break; 2994 2995 case "Station": // NOI18N 2996 _duplicateButton.setText(Bundle.getMessage("DuplicateStationButtonText")); // NOI18N 2997 _duplicateButtonPanel.setVisible(true); 2998 _deleteButton.setText(Bundle.getMessage("DeleteStationButtonText")); // NOI18N 2999 _deleteButtonPanel.setVisible(true); 3000 editPressed(); 3001 break; 3002 3003 case "Schedules": // NOI18N 3004 _addButton.setText(Bundle.getMessage("AddScheduleButtonText")); // NOI18N 3005 _addButtonPanel.setVisible(true); 3006 makeDetailGrid(EMPTY_GRID); // NOI18N 3007 break; 3008 3009 case "Schedule": // NOI18N 3010 _addButton.setText(Bundle.getMessage("AddTrainButtonText")); // NOI18N 3011 _addButtonPanel.setVisible(true); 3012 _duplicateButton.setText(Bundle.getMessage("DuplicateScheduleButtonText")); // NOI18N 3013 _duplicateButtonPanel.setVisible(true); 3014 _deleteButton.setText(Bundle.getMessage("DeleteScheduleButtonText")); // NOI18N 3015 _deleteButtonPanel.setVisible(true); 3016 editPressed(); 3017 break; 3018 3019 case "Train": // NOI18N 3020 _addButton.setText(Bundle.getMessage("AddStopButtonText")); // NOI18N 3021 _addButtonPanel.setVisible(true); 3022 3023 var stops = _dataMgr.getStops(_curNodeId, 0, false); 3024 if (stops.size() == 0) { 3025 _copyButtonPanel.setVisible(true); 3026 } 3027 3028 _duplicateButton.setText(Bundle.getMessage("DuplicateTrainButtonText")); // NOI18N 3029 _duplicateButtonPanel.setVisible(true); 3030 _deleteButton.setText(Bundle.getMessage("DeleteTrainButtonText")); // NOI18N 3031 _deleteButtonPanel.setVisible(true); 3032 editPressed(); 3033 break; 3034 3035 case "Stop": // NOI18N 3036 _duplicateButton.setText(Bundle.getMessage("DuplicateStopButtonText")); // NOI18N 3037 _duplicateButtonPanel.setVisible(true); 3038 _deleteButton.setText(Bundle.getMessage("DeleteStopButtonText")); // NOI18N 3039 _deleteButtonPanel.setVisible(true); 3040 editPressed(); 3041 break; 3042 3043 default: 3044 log.warn("Should not be here"); // NOI18N 3045 } 3046 } 3047 3048 /** 3049 * Display reminder to save. 3050 */ 3051 void showNodeEditMessage() { 3052 if (InstanceManager.getNullableDefault(jmri.UserPreferencesManager.class) != null) { 3053 InstanceManager.getDefault(jmri.UserPreferencesManager.class). 3054 showInfoMessage( this, Bundle.getMessage("NodeEditTitle"), // NOI18N 3055 Bundle.getMessage("NodeEditText"), // NOI18N 3056 getClassName(), 3057 "SkipTimeTableEditMessage"); // NOI18N 3058 } 3059 } 3060 3061 /** 3062 * Set/clear dirty flag and save button 3063 * @param dirty True if changes have been made that are not saved. 3064 */ 3065 public void setShowReminder(boolean dirty) { 3066 _isDirty = dirty; 3067 _saveButton.setEnabled(dirty); 3068 } 3069 3070 /** 3071 * Enable/disable buttons based on edit state. 3072 * The edit state controls the ability to select tree nodes. 3073 * 3074 * @param active True to make edit active, false to make edit inactive 3075 */ 3076 void setEditMode(boolean active) { 3077 _editActive = active; 3078 _cancelAction.setEnabled(active); 3079 _updateAction.setEnabled(active); 3080 _addButton.setEnabled(!active); 3081 _deleteButton.setEnabled(!active); 3082 if (_curNodeType != null && _curNodeType.equals("Stop")) { // NOI18N 3083 setMoveButtons(); 3084 } 3085 } 3086 3087 /** 3088 * Timetable Tree Node Definition. 3089 */ 3090 static class TimeTableTreeNode extends DefaultMutableTreeNode { 3091 3092 private String ttText; 3093 private String ttType; 3094 private int ttId; 3095 private int ttRow; 3096 3097 public TimeTableTreeNode(String nameText, String type, int sysId, int row) { 3098 this.ttText = nameText; 3099 this.ttType = type; 3100 this.ttId = sysId; 3101 this.ttRow = row; 3102 } 3103 3104 public String getType() { 3105 return ttType; 3106 } 3107 3108 public int getId() { 3109 return ttId; 3110 } 3111 3112 public void setId(int newId) { 3113 ttId = newId; 3114 } 3115 3116 public int getRow() { 3117 return ttRow; 3118 } 3119 3120 public void setRow(int newRow) { 3121 ttRow = newRow; 3122 } 3123 3124 public String getText() { 3125 return ttText; 3126 } 3127 3128 public void setText(String newText) { 3129 ttText = newText; 3130 } 3131 3132 @Override 3133 public String toString() { 3134 return ttText; 3135 } 3136 } 3137 3138 protected String getClassName() { 3139 return TimeTableFrame.class.getName(); 3140 } 3141 3142 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TimeTableFrame.class); 3143}