001package jmri.jmrit.display.layoutEditor.LayoutEditorDialogs; 002 003import java.awt.*; 004import java.awt.event.*; 005import java.util.ArrayList; 006import java.util.List; 007 008import javax.annotation.Nonnull; 009import javax.swing.*; 010import javax.swing.border.EtchedBorder; 011import javax.swing.border.TitledBorder; 012 013import jmri.NamedBean.DisplayOptions; 014import jmri.*; 015import jmri.jmrit.display.layoutEditor.*; 016import jmri.jmrit.display.Positionable; 017import jmri.jmrit.display.SignalMastIcon; 018import jmri.swing.NamedBeanComboBox; 019import jmri.util.JmriJFrame; 020import jmri.util.swing.JmriJOptionPane; 021 022/** 023 * MVC Editor component for LayoutTraverser objects. 024 * 025 * @author Bob Jacobsen Copyright (c) 2020 026 * @author Dave Sand Copyright (c) 2024 027 */ 028public class LayoutTraverserEditor extends LayoutTrackEditor { 029 030 /** 031 * constructor method. 032 * @param layoutEditor main layout editor. 033 */ 034 public LayoutTraverserEditor(@Nonnull LayoutEditor layoutEditor) { 035 super(layoutEditor); 036 } 037 038 /*==============*\ 039 | Edit Traverser | 040 \*==============*/ 041 // variables for Edit Traverser pane 042 private LayoutTraverser layoutTraverser = null; 043 private LayoutTraverserView layoutTraverserView = null; 044 045 private JmriJFrame editLayoutTraverserFrame = null; 046 private final JTextField deckLengthTextField = new JTextField(8); 047 private final JTextField deckWidthTextField = new JTextField(8); 048 private final JComboBox<String> orientationComboBox = new JComboBox<>(); 049 private final JTextField slotOffsetTextField = new JTextField(8); 050 051 private final NamedBeanComboBox<Block> editLayoutTraverserBlockNameComboBox = new NamedBeanComboBox<>( 052 InstanceManager.getDefault(BlockManager.class), null, DisplayOptions.DISPLAYNAME); 053 private JButton editLayoutTraverserSegmentEditBlockButton; 054 private final JComboBox<String> editLayoutTraverserMainlineComboBox = new JComboBox<>(); 055 056 private JPanel editLayoutTraverserSlotPanel; 057 private JButton editLayoutTraverserAddSlotButton; 058 private JCheckBox editLayoutTraverserDccControlledCheckBox; 059 private JCheckBox editLayoutTraverserUseSignalMastsCheckBox; 060 private JPanel signalMastAssignmentsPanel; 061 private NamedBeanComboBox<SignalMast> exitMastComboBox; 062 private NamedBeanComboBox<SignalMast> bufferMastComboBox; 063 064 private final List<NamedBeanComboBox<SignalMast>> approachMastComboBoxes = new ArrayList<>(); 065 private boolean editLayoutTraverserOpen = false; 066 private boolean editLayoutTraverserNeedsRedraw = false; 067 068 private JRadioButton doNotPlaceIcons; 069 private JRadioButton placeIconsLeft; 070 private JRadioButton placeIconsRight; 071 072 private final List<Turnout> traverserTurnouts = new ArrayList<>(); 073 074 /** 075 * Edit a Traverser. 076 */ 077 @Override 078 public void editLayoutTrack(@Nonnull LayoutTrackView layoutTrackView) { 079 if ( layoutTrackView instanceof LayoutTraverserView ) { 080 this.layoutTraverserView = (LayoutTraverserView) layoutTrackView; 081 this.layoutTraverser = this.layoutTraverserView.getTraverser(); 082 } else { 083 log.error("editLayoutTrack called with wrong type {}", layoutTrackView, new Exception("traceback")); 084 } 085 sensorList.clear(); 086 087 if (editLayoutTraverserOpen) { 088 editLayoutTraverserFrame.setVisible(true); 089 return; 090 } 091 editLayoutTraverserFrame = new JmriJFrame(Bundle.getMessage("EditTraverser"), false, true); // NOI18N 092 editLayoutTraverserFrame.addHelpMenu("package.jmri.jmrit.display.EditTraverser", true); // NOI18N 093 editLayoutTraverserFrame.setLocation(50, 30); 094 095 Container contentPane = editLayoutTraverserFrame.getContentPane(); 096 JPanel headerPane = new JPanel(); 097 JPanel footerPane = new JPanel(); 098 headerPane.setLayout(new BoxLayout(headerPane, BoxLayout.Y_AXIS)); 099 footerPane.setLayout(new BoxLayout(footerPane, BoxLayout.Y_AXIS)); 100 contentPane.setLayout(new BorderLayout()); 101 contentPane.add(headerPane, BorderLayout.NORTH); 102 contentPane.add(footerPane, BorderLayout.SOUTH); 103 104 // Geometry Panel 105 JPanel geometryPanel = new JPanel(); 106 geometryPanel.setLayout(new FlowLayout()); 107 geometryPanel.add(new JLabel(Bundle.getMessage("Length"))); 108 deckLengthTextField.setEnabled(false); 109 geometryPanel.add(deckLengthTextField); 110 geometryPanel.add(new JLabel(Bundle.getMessage("Width"))); 111 geometryPanel.add(deckWidthTextField); 112 geometryPanel.add(new JLabel(Bundle.getMessage("Orientation"))); 113 orientationComboBox.removeAllItems(); 114 orientationComboBox.addItem(Bundle.getMessage("Horizontal")); 115 orientationComboBox.addItem(Bundle.getMessage("Vertical")); 116 geometryPanel.add(orientationComboBox); 117 headerPane.add(geometryPanel); 118 119 // Slot Panel 120 JPanel slotPanel = new JPanel(); 121 slotPanel.setLayout(new FlowLayout()); 122 slotPanel.add(new JLabel(Bundle.getMessage("SlotOffset"))); 123 slotPanel.add(slotOffsetTextField); 124 editLayoutTraverserAddSlotButton = new JButton(Bundle.getMessage("AddSlotPair")); 125 slotPanel.add(editLayoutTraverserAddSlotButton); 126 headerPane.add(slotPanel); 127 128 // Block Name Panel 129 JPanel blockPanel = new JPanel(); 130 blockPanel.setLayout(new FlowLayout()); 131 blockPanel.add(new JLabel(Bundle.getMessage("BlockID"))); 132 LayoutEditor.setupComboBox(editLayoutTraverserBlockNameComboBox, false, true, true); 133 blockPanel.add(editLayoutTraverserBlockNameComboBox); 134 editLayoutTraverserSegmentEditBlockButton = new JButton(Bundle.getMessage("EditBlock", "")); 135 blockPanel.add(editLayoutTraverserSegmentEditBlockButton); 136 137 boolean mainlineSaved = layoutTraverser.isMainline(); 138 editLayoutTraverserMainlineComboBox.removeAllItems(); 139 editLayoutTraverserMainlineComboBox.addItem(Bundle.getMessage("Mainline")); 140 editLayoutTraverserMainlineComboBox.addItem(Bundle.getMessage("NotMainline")); 141 layoutTraverser.setMainline(mainlineSaved); // restore the current state 142 143 blockPanel.add(editLayoutTraverserMainlineComboBox); 144 headerPane.add(blockPanel); 145 146 // Controls Panel 147 JPanel controlsPanel = new JPanel(); 148 controlsPanel.setLayout(new FlowLayout()); 149 editLayoutTraverserDccControlledCheckBox = new JCheckBox(Bundle.getMessage("TraverserDCCControlled")); 150 controlsPanel.add(editLayoutTraverserDccControlledCheckBox); 151 editLayoutTraverserUseSignalMastsCheckBox = new JCheckBox(Bundle.getMessage("TraverserUseSignalMasts")); 152 controlsPanel.add(editLayoutTraverserUseSignalMastsCheckBox); 153 headerPane.add(controlsPanel); 154 155 // set up Done and Cancel buttons 156 JPanel donePanel = new JPanel(); 157 donePanel.setLayout(new FlowLayout()); 158 addDoneCancelButtons(donePanel, editLayoutTraverserFrame.getRootPane(), 159 this::editLayoutTraverserDonePressed, this::traverserEditCancelPressed); 160 footerPane.add(donePanel); 161 162 editLayoutTraverserSlotPanel = new JPanel(); 163 editLayoutTraverserSlotPanel.setLayout(new BoxLayout(editLayoutTraverserSlotPanel, BoxLayout.Y_AXIS)); 164 JScrollPane slotScrollPane = new JScrollPane(editLayoutTraverserSlotPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); 165 contentPane.add(slotScrollPane, BorderLayout.CENTER); 166 167 // Set initial values 168 deckLengthTextField.setText(String.valueOf(layoutTraverser.getDeckLength())); 169 deckWidthTextField.setText(String.valueOf(layoutTraverser.getDeckWidth())); 170 orientationComboBox.setSelectedIndex(layoutTraverser.getOrientation()); 171 slotOffsetTextField.setText(String.valueOf(layoutTraverser.getSlotOffset())); 172 173 editLayoutTraverserBlockNameComboBox.setSelectedItem(layoutTraverser.getLayoutBlock() != null ? layoutTraverser.getLayoutBlock().getBlock() : null); 174 editLayoutTraverserDccControlledCheckBox.setSelected(layoutTraverser.isTurnoutControlled()); 175 editLayoutTraverserUseSignalMastsCheckBox.setSelected(layoutTraverser.isDispatcherManaged()); 176 if (layoutTraverser.isMainline()) { 177 editLayoutTraverserMainlineComboBox.setSelectedIndex(0); 178 } else { 179 editLayoutTraverserMainlineComboBox.setSelectedIndex(1); 180 } 181 for (ActionListener al : editLayoutTraverserMainlineComboBox.getActionListeners()) { 182 editLayoutTraverserMainlineComboBox.removeActionListener(al); 183 } 184 editLayoutTraverserMainlineComboBox.addActionListener((java.awt.event.ActionEvent e) -> { 185 if (layoutTraverser != null) { 186 layoutTraverser.setMainline(editLayoutTraverserMainlineComboBox.getSelectedIndex() == 0); 187 } 188 }); 189 190 // Add listeners 191 editLayoutTraverserAddSlotButton.addActionListener(this::addTrackPairPressed); 192 editLayoutTraverserSegmentEditBlockButton.addActionListener(this::editLayoutTraverserEditBlockPressed); 193 slotOffsetTextField.addFocusListener(new FocusAdapter() { 194 @Override 195 public void focusLost(FocusEvent e) { 196 try { 197 double offset = Double.parseDouble(slotOffsetTextField.getText()); 198 if (!jmri.util.MathUtil.equals(layoutTraverser.getSlotOffset(), offset)) { 199 layoutTraverser.setSlotOffset(offset); 200 updateSlotPanel(); 201 layoutEditor.redrawPanel(); 202 layoutEditor.setDirty(); 203 } 204 } catch (NumberFormatException ex) { 205 // ignore invalid input 206 } 207 } 208 }); 209 for (ActionListener al : orientationComboBox.getActionListeners()) { 210 orientationComboBox.removeActionListener(al); 211 } 212 orientationComboBox.addActionListener(e -> { 213 layoutTraverser.setOrientation(orientationComboBox.getSelectedIndex()); 214 updateSlotPanel(); 215 layoutEditor.redrawPanel(); 216 layoutEditor.setDirty(); 217 }); 218 editLayoutTraverserDccControlledCheckBox.addActionListener(e -> { 219 layoutTraverser.setTurnoutControlled(editLayoutTraverserDccControlledCheckBox.isSelected()); 220 updateSlotPanel(); 221 }); 222 editLayoutTraverserUseSignalMastsCheckBox.addActionListener(e -> { 223 layoutTraverser.setDispatcherManaged(editLayoutTraverserUseSignalMastsCheckBox.isSelected()); 224 updateSlotPanel(); 225 }); 226 227 updateSlotPanel(); 228 editLayoutTraverserFrame.addWindowListener(new java.awt.event.WindowAdapter() { 229 @Override 230 public void windowClosing(java.awt.event.WindowEvent e) { 231 traverserEditCancelPressed(null); 232 } 233 }); 234 editLayoutTraverserFrame.pack(); 235 editLayoutTraverserFrame.setVisible(true); 236 editLayoutTraverserOpen = true; 237 } 238 239 private void addTrackPairPressed(ActionEvent e) { 240 layoutTraverser.addSlotPair(); 241 updateSlotPanel(); 242 layoutEditor.redrawPanel(); 243 layoutEditor.setDirty(); 244 } 245 246 @InvokeOnGuiThread 247 private void editLayoutTraverserEditBlockPressed(ActionEvent a) { 248 String newName = editLayoutTraverserBlockNameComboBox.getSelectedItemDisplayName(); 249 if (newName == null) { 250 newName = ""; 251 } 252 if ((layoutTraverser.getBlockName().isEmpty()) 253 || !layoutTraverser.getBlockName().equals(newName)) { 254 layoutTraverser.setLayoutBlock(layoutEditor.provideLayoutBlock(newName)); 255 editLayoutTraverserNeedsRedraw = true; 256 } 257 LayoutBlock blockToEdit = layoutTraverser.getLayoutBlock(); 258 if (blockToEdit == null) { 259 JmriJOptionPane.showMessageDialog(editLayoutTraverserFrame, 260 Bundle.getMessage("Error1"), // NOI18N 261 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 262 return; 263 } 264 blockToEdit.editLayoutBlock(editLayoutTraverserFrame); 265 layoutEditor.setDirty(); 266 editLayoutTraverserNeedsRedraw = true; 267 } 268 269 private void updateSlotPanel() { 270 deckLengthTextField.setText(String.valueOf(layoutTraverser.getDeckLength())); 271 deckWidthTextField.setText(String.valueOf(layoutTraverser.getDeckWidth())); 272 273 editLayoutTraverserSlotPanel.removeAll(); 274 275 JPanel turnoutAssignmentsPanel = new JPanel(); 276 turnoutAssignmentsPanel.setLayout(new BoxLayout(turnoutAssignmentsPanel, BoxLayout.Y_AXIS)); 277 turnoutAssignmentsPanel.setBorder(new TitledBorder(new EtchedBorder(), Bundle.getMessage("TurnoutAssignments"))); 278 traverserTurnouts.clear(); 279 layoutTraverser.getSlotList().forEach(rt -> traverserTurnouts.add(rt.getTurnout())); 280 for (int i = 0; i < layoutTraverser.getNumberSlots() / 2; i++) { 281 turnoutAssignmentsPanel.add(new TraverserPairPanel(i)); 282 } 283 editLayoutTraverserSlotPanel.add(turnoutAssignmentsPanel); 284 285 signalMastAssignmentsPanel = new JPanel(); 286 approachMastComboBoxes.clear(); 287 288 if (layoutTraverser.isDispatcherManaged()) { 289 signalMastAssignmentsPanel.setLayout(new BoxLayout(signalMastAssignmentsPanel, BoxLayout.Y_AXIS)); 290 signalMastAssignmentsPanel.setBorder(new TitledBorder(new EtchedBorder(), Bundle.getMessage("TraverserSignalMastAssignmentsTitle"))); 291 292 for (int i = 0; i < layoutTraverser.getNumberSlots() / 2; i++) { 293 JPanel p = new JPanel(new GridBagLayout()); 294 p.setBorder(new TitledBorder(new EtchedBorder(), Bundle.getMessage("SlotPair") + " " + (i + 1))); 295 GridBagConstraints c = new GridBagConstraints(); 296 c.gridx = 0; 297 c.gridy = 0; 298 c.anchor = GridBagConstraints.LINE_START; 299 c.insets = new Insets(2, 2, 2, 2); 300 301 JLabel labelA = new JLabel(); 302 JLabel labelB = new JLabel(); 303 if (layoutTraverser.getOrientation() == LayoutTraverser.HORIZONTAL) { 304 labelA.setText(Bundle.getMessage("MakeLabel", Bundle.getMessage("ApproachMastSlotLeft"))); 305 labelB.setText(Bundle.getMessage("MakeLabel", Bundle.getMessage("ApproachMastSlotRight"))); 306 } else { 307 labelA.setText(Bundle.getMessage("MakeLabel", Bundle.getMessage("ApproachMastSlotUp"))); 308 labelB.setText(Bundle.getMessage("MakeLabel", Bundle.getMessage("ApproachMastSlotDown"))); 309 } 310 p.add(labelA, c); 311 312 c.gridy = 1; 313 p.add(labelB, c); 314 315 c.gridx = 1; 316 c.gridy = 0; 317 c.insets = new Insets(2, 5, 2, 2); 318 NamedBeanComboBox<SignalMast> comboA = new NamedBeanComboBox<>(InstanceManager.getDefault(SignalMastManager.class), layoutTraverser.getSlotList().get(i * 2).getApproachMast(), DisplayOptions.DISPLAYNAME); 319 LayoutEditor.setupComboBox(comboA, false, true, true); 320 comboA.setEditable(false); 321 comboA.setAllowNull(true); 322 p.add(comboA, c); 323 approachMastComboBoxes.add(comboA); 324 325 c.gridy = 1; 326 NamedBeanComboBox<SignalMast> comboB = new NamedBeanComboBox<>(InstanceManager.getDefault(SignalMastManager.class), layoutTraverser.getSlotList().get(i * 2 + 1).getApproachMast(), DisplayOptions.DISPLAYNAME); 327 LayoutEditor.setupComboBox(comboB, false, true, true); 328 comboB.setEditable(false); 329 comboB.setAllowNull(true); 330 p.add(comboB, c); 331 approachMastComboBoxes.add(comboB); 332 333 signalMastAssignmentsPanel.add(p); 334 } 335 336 if (!approachMastComboBoxes.isEmpty()) { 337 signalMastAssignmentsPanel.add(new JSeparator()); 338 } 339 340 JPanel placementPanel = new JPanel(); 341 placementPanel.setLayout(new BoxLayout(placementPanel, BoxLayout.Y_AXIS)); // NOI18N 342 placementPanel.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("TraverserAddMastIconsTitle"))); 343 344 doNotPlaceIcons = new JRadioButton(Bundle.getMessage("DoNotPlace")); // NOI18N 345 placeIconsLeft = new JRadioButton(Bundle.getMessage("LeftHandSide")); // NOI18N 346 placeIconsRight = new JRadioButton(Bundle.getMessage("RightHandSide")); // NOI18N 347 ButtonGroup bg = new ButtonGroup(); 348 bg.add(doNotPlaceIcons); 349 bg.add(placeIconsLeft); 350 bg.add(placeIconsRight); 351 switch (layoutTraverser.getSignalIconPlacement()) { 352 case 1: 353 placeIconsLeft.setSelected(true); 354 break; 355 case 2: 356 placeIconsRight.setSelected(true); 357 break; 358 default: doNotPlaceIcons.setSelected(true); 359 } 360 361 JPanel radioPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); 362 radioPanel.add(doNotPlaceIcons); 363 radioPanel.add(placeIconsLeft); 364 radioPanel.add(placeIconsRight); 365 placementPanel.add(radioPanel); 366 signalMastAssignmentsPanel.add(placementPanel); 367 368 signalMastAssignmentsPanel.add(new JSeparator()); 369 370 JPanel mastPanel = new JPanel(new GridBagLayout()); 371 GridBagConstraints c = new GridBagConstraints(); 372 c.gridx = 0; 373 c.gridy = 0; 374 c.anchor = GridBagConstraints.LINE_START; 375 c.insets = new Insets(2, 2, 2, 2); // Default insets 376 mastPanel.add(new JLabel(Bundle.getMessage("TraverserExitMastLabel")), c); 377 c.gridx = 1; 378 c.insets = new Insets(2, 5, 2, 2); // Add left padding 379 exitMastComboBox = new NamedBeanComboBox<>(InstanceManager.getDefault(SignalMastManager.class), layoutTraverser.getExitSignalMast(), DisplayOptions.DISPLAYNAME); 380 exitMastComboBox.setAllowNull(true); 381 mastPanel.add(exitMastComboBox, c); 382 383 c.gridx = 0; 384 c.gridy = 1; 385 c.insets = new Insets(2, 2, 2, 2); // Reset for label 386 mastPanel.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("TraverserBufferMastLabel"))), c); 387 c.gridx = 1; 388 c.insets = new Insets(2, 5, 2, 2); // Add left padding 389 bufferMastComboBox = new NamedBeanComboBox<>(InstanceManager.getDefault(SignalMastManager.class), layoutTraverser.getBufferMast(), DisplayOptions.DISPLAYNAME); 390 bufferMastComboBox.setAllowNull(true); 391 mastPanel.add(bufferMastComboBox, c); 392 393 signalMastAssignmentsPanel.add(mastPanel); 394 editLayoutTraverserSlotPanel.add(signalMastAssignmentsPanel); 395 } 396 397 editLayoutTraverserSlotPanel.revalidate(); 398 editLayoutTraverserSlotPanel.repaint(); 399 editLayoutTraverserFrame.pack(); 400 } 401 402 private void saveSlotPanelDetail() { 403 for (Component mainComp : editLayoutTraverserSlotPanel.getComponents()) { 404 if (mainComp instanceof JPanel && ((JPanel)mainComp).getBorder() instanceof TitledBorder) { 405 TitledBorder border = (TitledBorder)((JPanel)mainComp).getBorder(); 406 if (Bundle.getMessage("TurnoutAssignments").equals(border.getTitle())) { 407 JPanel turnoutAssignmentsPanel = (JPanel) mainComp; 408 for (Component tppComp : turnoutAssignmentsPanel.getComponents()) { 409 if (tppComp instanceof TraverserPairPanel) { 410 TraverserPairPanel tpp = (TraverserPairPanel) tppComp; 411 tpp.updateDetails(); 412 } 413 } 414 } 415 } 416 } 417 if (layoutTraverser.isDispatcherManaged()) { 418 layoutTraverser.setExitSignalMast(exitMastComboBox.getSelectedItemDisplayName()); 419 layoutTraverser.setBufferSignalMast(bufferMastComboBox.getSelectedItemDisplayName()); 420 int placement = 0; 421 if (placeIconsLeft.isSelected()) { 422 placement = 1; 423 } else if (placeIconsRight.isSelected()) { 424 placement = 2; 425 } 426 layoutTraverser.setSignalIconPlacement(placement); 427 428 for (int i = 0; i < approachMastComboBoxes.size(); i++) { 429 SignalMast newMast = approachMastComboBoxes.get(i).getSelectedItem(); 430 layoutTraverser.getSlotList().get(i).setApproachMast( (newMast != null) ? newMast.getSystemName() : null ); 431 } 432 } 433 } 434 435 private void editLayoutTraverserDonePressed(ActionEvent a) { 436 layoutTraverser.setOrientation(orientationComboBox.getSelectedIndex()); 437 try { 438 double width = Double.parseDouble(deckWidthTextField.getText()); 439 if (!jmri.util.MathUtil.equals(layoutTraverser.getDeckWidth(), width)) { 440 layoutTraverser.setDeckWidth(width); 441 editLayoutTraverserNeedsRedraw = true; 442 } 443 } catch (NumberFormatException ex) { 444 JmriJOptionPane.showMessageDialog(editLayoutTraverserFrame, Bundle.getMessage("EntryError") + ": " 445 + ex, Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 446 return; 447 } 448 try { 449 double offset = Double.parseDouble(slotOffsetTextField.getText()); 450 if (!jmri.util.MathUtil.equals(layoutTraverser.getSlotOffset(), offset)) { 451 layoutTraverser.setSlotOffset(offset); 452 editLayoutTraverserNeedsRedraw = true; 453 } 454 } catch (NumberFormatException ex) { 455 JmriJOptionPane.showMessageDialog(editLayoutTraverserFrame, Bundle.getMessage("EntryError") + ": " 456 + ex, Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 457 return; 458 } 459 460 String newName = editLayoutTraverserBlockNameComboBox.getSelectedItemDisplayName(); 461 if (newName == null) { 462 newName = ""; 463 } 464 if ((layoutTraverser.getBlockName().isEmpty()) || !layoutTraverser.getBlockName().equals(newName)) { 465 layoutTraverser.setLayoutBlock(layoutEditor.provideLayoutBlock(newName)); 466 editLayoutTraverserNeedsRedraw = true; 467 } 468 layoutTraverser.setMainline(editLayoutTraverserMainlineComboBox.getSelectedIndex() == 0); 469 470 if (layoutTraverser.isDispatcherManaged()) { 471 // Always remove any existing icons for this traverser's approach masts first. 472 // This ensures that moving icons from right-to-left works correctly. 473 List<SignalMastIcon> iconsToRemove = new ArrayList<>(); 474 for (Positionable p : layoutEditor.getContents()) { 475 if (p instanceof SignalMastIcon) { 476 SignalMastIcon icon = (SignalMastIcon) p; 477 if (layoutTraverser.isApproachMast(icon.getSignalMast())) { 478 iconsToRemove.add(icon); 479 } 480 } 481 } 482 for (SignalMastIcon icon : iconsToRemove) { 483 icon.remove(); 484 editLayoutTraverserNeedsRedraw = true; 485 } 486 487 // Now, if requested, place the new icons. 488 if (!doNotPlaceIcons.isSelected()) { // placeIconsLeft or placeIconsRight is selected 489 for (int i = 0; i < approachMastComboBoxes.size(); i++) { 490 LayoutTraverser.SlotTrack slot = layoutTraverser.getSlotList().get(i); 491 SignalMast mast = approachMastComboBoxes.get(i).getSelectedItem(); 492 if (mast != null) { 493 if (slot.getConnect() != null) { 494 SignalMastIcon icon = new SignalMastIcon(layoutEditor); 495 icon.setSignalMast(mast.getDisplayName()); 496 log.debug("Placing mast for traverser slot, connected to track segment: {}", slot.getConnect().getName()); // NOI18N 497 // Note: Using the turntable placement logic as it is suitable for this purpose. 498 layoutEditor.getLETools().placingBlockForTurntable(icon, placeIconsRight.isSelected(), 499 0.0, 500 slot.getConnect(), layoutTraverserView.getSlotCoordsOrdered(i)); 501 editLayoutTraverserNeedsRedraw = true; 502 } 503 } 504 } 505 } 506 } 507 508 saveSlotPanelDetail(); 509 editLayoutTraverserOpen = false; 510 editLayoutTraverserFrame.setVisible(false); 511 editLayoutTraverserFrame.dispose(); 512 editLayoutTraverserFrame = null; 513 layoutEditor.redrawPanel(); 514 layoutEditor.setDirty(); 515 } 516 517 private void traverserEditCancelPressed(ActionEvent a) { 518 editLayoutTraverserOpen = false; 519 editLayoutTraverserFrame.setVisible(false); 520 editLayoutTraverserFrame.dispose(); 521 editLayoutTraverserFrame = null; 522 if (editLayoutTraverserNeedsRedraw) { 523 layoutEditor.redrawPanel(); 524 layoutEditor.setDirty(); 525 editLayoutTraverserNeedsRedraw = false; 526 } 527 } 528 529 public class TraverserPairPanel extends JPanel { 530 531 private final LayoutTraverser.SlotTrack slotA; 532 private final LayoutTraverser.SlotTrack slotB; 533 private final int pairIndex; 534 535 private final JPanel turnoutDetailsPanel; 536 private final NamedBeanComboBox<Turnout> turnoutNameComboBoxA; 537 private final NamedBeanComboBox<Turnout> turnoutNameComboBoxB; 538 private final TitledBorder slotTitledBorder; 539 private final JComboBox<String> slotTurnoutStateComboBoxA; 540 private final JComboBox<String> slotTurnoutStateComboBoxB; 541 private final JLabel slotTurnoutLabelA; 542 private final JLabel slotTurnoutLabelB; 543 private final JCheckBox disabledCheckBoxA; 544 private final JCheckBox disabledCheckBoxB; 545 private final int[] slotTurnoutStateValues = new int[]{Turnout.CLOSED, Turnout.THROWN}; 546 547 public TraverserPairPanel(int pairIndex) { 548 this.pairIndex = pairIndex; 549 this.slotA = layoutTraverser.getSlotList().get(pairIndex * 2); 550 this.slotB = layoutTraverser.getSlotList().get(pairIndex * 2 + 1); 551 552 JPanel top = new JPanel(); 553 this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 554 this.add(top); 555 556 String turnoutStateThrown = InstanceManager.turnoutManagerInstance().getThrownText(); 557 String turnoutStateClosed = InstanceManager.turnoutManagerInstance().getClosedText(); 558 String[] turnoutStates = new String[]{turnoutStateClosed, turnoutStateThrown}; 559 560 turnoutDetailsPanel = new JPanel(new GridBagLayout()); 561 turnoutDetailsPanel.setBorder(new EtchedBorder()); 562 GridBagConstraints c = new GridBagConstraints(); 563 c.insets = new Insets(2, 5, 2, 5); 564 c.anchor = GridBagConstraints.LINE_START; 565 566 // Side A 567 turnoutNameComboBoxA = new NamedBeanComboBox<>(InstanceManager.getDefault(TurnoutManager.class)); 568 LayoutEditor.setupComboBox(turnoutNameComboBoxA, false, true, false); 569 turnoutNameComboBoxA.setSelectedItem(slotA.getTurnout()); 570 turnoutNameComboBoxA.addPopupMenuListener( 571 layoutEditor.newTurnoutComboBoxPopupMenuListener(turnoutNameComboBoxA, traverserTurnouts)); 572 slotTurnoutStateComboBoxA = new JComboBox<>(turnoutStates); 573 slotTurnoutLabelA = new JLabel(); 574 disabledCheckBoxA = new JCheckBox(Bundle.getMessage("Disabled")); 575 disabledCheckBoxA.setSelected(slotA.isDisabled()); 576 disabledCheckBoxA.addActionListener((ActionEvent e) -> { 577 if (disabledCheckBoxA.isSelected()) { 578 var msg = layoutTraverser.isSlotConnectionClear(slotA); 579 if (msg.length() > 0) { 580 msg.insert(0, Bundle.getMessage("TV_Message_Header")); 581 msg.append(Bundle.getMessage("TV_Message_Slot_Disable")); 582 JmriJOptionPane.showMessageDialog(editLayoutTraverserFrame, 583 msg.toString(), 584 Bundle.getMessage("ErrorTitle"), 585 JmriJOptionPane.ERROR_MESSAGE); 586 disabledCheckBoxA.setSelected(false); 587 } 588 } 589 slotA.setDisabled(disabledCheckBoxA.isSelected()); 590 layoutEditor.redrawPanel(); 591 layoutEditor.setDirty(); 592 }); 593 594 c.gridy = 0; 595 c.gridx = 0; 596 turnoutDetailsPanel.add(slotTurnoutLabelA, c); 597 c.gridx = 1; 598 turnoutDetailsPanel.add(turnoutNameComboBoxA, c); 599 c.gridx = 2; 600 turnoutDetailsPanel.add(new JLabel(Bundle.getMessage("TurnoutState")), c); 601 c.gridx = 3; 602 turnoutDetailsPanel.add(slotTurnoutStateComboBoxA, c); 603 c.gridx = 4; 604 turnoutDetailsPanel.add(disabledCheckBoxA, c); 605 606 if (slotA.getTurnoutState() == Turnout.CLOSED) { 607 slotTurnoutStateComboBoxA.setSelectedItem(turnoutStateClosed); 608 } else { 609 slotTurnoutStateComboBoxA.setSelectedItem(turnoutStateThrown); 610 } 611 612 // Side B 613 turnoutNameComboBoxB = new NamedBeanComboBox<>(InstanceManager.getDefault(TurnoutManager.class)); 614 LayoutEditor.setupComboBox(turnoutNameComboBoxB, false, true, false); 615 turnoutNameComboBoxB.setSelectedItem(slotB.getTurnout()); 616 turnoutNameComboBoxB.addPopupMenuListener( 617 layoutEditor.newTurnoutComboBoxPopupMenuListener(turnoutNameComboBoxB, traverserTurnouts)); 618 slotTurnoutStateComboBoxB = new JComboBox<>(turnoutStates); 619 slotTurnoutLabelB = new JLabel(); 620 disabledCheckBoxB = new JCheckBox(Bundle.getMessage("Disabled")); 621 disabledCheckBoxB.setSelected(slotB.isDisabled()); 622 disabledCheckBoxB.addActionListener((ActionEvent e) -> { 623 if (disabledCheckBoxB.isSelected()) { 624 var msg = layoutTraverser.isSlotConnectionClear(slotB); 625 if (msg.length() > 0) { 626 msg.insert(0, Bundle.getMessage("TV_Message_Header")); 627 JmriJOptionPane.showMessageDialog(editLayoutTraverserFrame, 628 msg.toString(), 629 Bundle.getMessage("ErrorTitle"), 630 JmriJOptionPane.ERROR_MESSAGE); 631 disabledCheckBoxB.setSelected(false); 632 } 633 } 634 slotB.setDisabled(disabledCheckBoxB.isSelected()); 635 layoutEditor.redrawPanel(); 636 layoutEditor.setDirty(); 637 }); 638 639 c.gridy = 1; 640 c.gridx = 0; 641 turnoutDetailsPanel.add(slotTurnoutLabelB, c); 642 c.gridx = 1; 643 turnoutDetailsPanel.add(turnoutNameComboBoxB, c); 644 c.gridx = 2; 645 turnoutDetailsPanel.add(new JLabel(Bundle.getMessage("TurnoutState")), c); 646 c.gridx = 3; 647 turnoutDetailsPanel.add(slotTurnoutStateComboBoxB, c); 648 c.gridx = 4; 649 turnoutDetailsPanel.add(disabledCheckBoxB, c); 650 651 if (slotB.getTurnoutState() == Turnout.CLOSED) { 652 slotTurnoutStateComboBoxB.setSelectedItem(turnoutStateClosed); 653 } else { 654 slotTurnoutStateComboBoxB.setSelectedItem(turnoutStateThrown); 655 } 656 this.add(turnoutDetailsPanel); 657 658 setTurnoutLabels(); 659 660 JButton deleteButton = new JButton(Bundle.getMessage("Delete")); 661 top.add(deleteButton); 662 deleteButton.addActionListener((ActionEvent e) -> { 663 delete(); 664 updateSlotPanel(); 665 }); 666 667 JButton moveUpButton = new JButton(Bundle.getMessage("MoveUp")); 668 top.add(moveUpButton); 669 moveUpButton.addActionListener((ActionEvent e) -> { 670 layoutTraverser.moveSlotPairUp(pairIndex); 671 updateSlotPanel(); 672 }); 673 moveUpButton.setVisible(layoutTraverser.isTurnoutControlled() && pairIndex > 0); 674 675 JButton moveDownButton = new JButton(Bundle.getMessage("MoveDown")); 676 top.add(moveDownButton); 677 moveDownButton.addActionListener((ActionEvent e) -> { 678 layoutTraverser.moveSlotPairDown(pairIndex); 679 updateSlotPanel(); 680 }); 681 moveDownButton.setVisible(layoutTraverser.isTurnoutControlled() && pairIndex < (layoutTraverser.getNumberSlots() / 2) - 1); 682 683 slotTitledBorder = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black)); 684 this.setBorder(slotTitledBorder); 685 686 showTurnoutDetails(); 687 688 slotTitledBorder.setTitle(Bundle.getMessage("SlotPair") + " : " + (pairIndex + 1)); 689 } 690 691 private void setTurnoutLabels() { 692 if (layoutTraverser.getOrientation() == LayoutTraverser.HORIZONTAL) { 693 slotTurnoutLabelA.setText(Bundle.getMessage("MakeLabel", Bundle.getMessage("TurnoutLeft"))); 694 slotTurnoutLabelB.setText(Bundle.getMessage("MakeLabel", Bundle.getMessage("TurnoutRight"))); 695 } else { 696 slotTurnoutLabelA.setText(Bundle.getMessage("MakeLabel", Bundle.getMessage("TurnoutUp"))); 697 slotTurnoutLabelB.setText(Bundle.getMessage("MakeLabel", Bundle.getMessage("TurnoutDown"))); 698 } 699 } 700 701 private void delete() { 702 int n = JmriJOptionPane.showConfirmDialog(null, 703 Bundle.getMessage("Question8s"), 704 Bundle.getMessage("WarningTitle"), 705 JmriJOptionPane.YES_NO_OPTION); 706 if (n == JmriJOptionPane.YES_OPTION) { 707 if (layoutTraverser.isSlotDeleteAllowed(pairIndex)) { 708 layoutTraverser.deleteTrackPair(pairIndex); 709 } 710 } 711 } 712 713 private void updateDetails() { 714 if (layoutTraverser.isTurnoutControlled()) { 715 String turnoutNameA = turnoutNameComboBoxA.getSelectedItemDisplayName(); 716 if (turnoutNameA == null) turnoutNameA = ""; 717 slotA.setTurnout(turnoutNameA, slotTurnoutStateValues[slotTurnoutStateComboBoxA.getSelectedIndex()]); 718 719 String turnoutNameB = turnoutNameComboBoxB.getSelectedItemDisplayName(); 720 if (turnoutNameB == null) turnoutNameB = ""; 721 slotB.setTurnout(turnoutNameB, slotTurnoutStateValues[slotTurnoutStateComboBoxB.getSelectedIndex()]); 722 } 723 slotA.setDisabled(disabledCheckBoxA.isSelected()); 724 slotB.setDisabled(disabledCheckBoxB.isSelected()); 725 } 726 727 private void showTurnoutDetails() { 728 boolean visible = layoutTraverser.isTurnoutControlled(); 729 turnoutDetailsPanel.setVisible(visible); 730 } 731 } 732 733 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutTraverserEditor.class); 734}