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