001package jmri.jmrit.display.layoutEditor.LayoutEditorDialogs; 002 003import java.awt.*; 004import java.awt.event.ActionEvent; 005import java.awt.event.WindowEvent; 006import java.awt.geom.Line2D; 007import java.awt.geom.Point2D; 008import java.util.List; 009import java.util.*; 010 011import javax.annotation.Nonnull; 012import javax.swing.*; 013 014import jmri.NamedBean.DisplayOptions; 015import jmri.*; 016import jmri.jmrit.display.layoutEditor.*; 017import jmri.swing.NamedBeanComboBox; 018import jmri.util.JmriJFrame; 019import jmri.util.MathUtil; 020 021/** 022 * MVC Editor component for LayoutSlip objects. 023 * 024 * @author Bob Jacobsen Copyright (c) 2020 025 * 026 */ 027public class LayoutSlipEditor extends LayoutTurnoutEditor { 028 029 /** 030 * constructor method. 031 * @param layoutEditor main layout editor. 032 */ 033 public LayoutSlipEditor(@Nonnull LayoutEditor layoutEditor) { 034 super(layoutEditor); 035 } 036 037 /*================*\ 038 | Edit Layout Slip | 039 \*================*/ 040 // variables for Edit slip Crossing pane 041 private LayoutSlipView layoutSlipView = null; 042 private LayoutSlip layoutSlip = null; 043 044 private JmriJFrame editLayoutSlipFrame = null; 045 private JButton editLayoutSlipBlockButton; 046 private NamedBeanComboBox<Turnout> editLayoutSlipTurnoutAComboBox; 047 private NamedBeanComboBox<Turnout> editLayoutSlipTurnoutBComboBox; 048 private final JCheckBox editLayoutSlipHiddenBox = new JCheckBox(Bundle.getMessage("HideSlip")); 049 private final NamedBeanComboBox<Block> editLayoutSlipBlockNameComboBox = new NamedBeanComboBox<>( 050 InstanceManager.getDefault(BlockManager.class), null, DisplayOptions.DISPLAYNAME); 051 052 private boolean editLayoutSlipOpen = false; 053 private boolean editLayoutSlipNeedsRedraw = false; 054 private boolean editLayoutSlipNeedsBlockUpdate = false; 055 056 /** 057 * Edit a Slip. 058 */ 059 @Override 060 public void editLayoutTrack(@Nonnull LayoutTrackView layoutTrackView) { 061 if ( layoutTrackView instanceof LayoutSlipView ) { 062 this.layoutSlipView = (LayoutSlipView) layoutTrackView; 063 this.layoutSlip = this.layoutSlipView.getSlip(); 064 } else { 065 log.error("editLayoutTrack called with wrong type {}", layoutTrackView, new Exception("traceback")); 066 } 067 sensorList.clear(); 068 069 if (editLayoutSlipOpen) { 070 editLayoutSlipFrame.setVisible(true); 071 } else if (editLayoutSlipFrame == null) { // Initialize if needed 072 editLayoutSlipFrame = new JmriJFrame(Bundle.getMessage("EditSlip"), false, true); // NOI18N 073 editLayoutSlipFrame.addHelpMenu("package.jmri.jmrit.display.EditLayoutSlip", true); // NOI18N 074 editLayoutSlipFrame.setLocation(50, 30); 075 076 Container contentPane = editLayoutSlipFrame.getContentPane(); 077 contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS)); 078 079 // Setup turnout A 080 JPanel panel1 = new JPanel(); 081 panel1.setLayout(new FlowLayout()); 082 JLabel turnoutNameLabel = new JLabel(Bundle.getMessage("BeanNameTurnout") + " A"); // NOI18N 083 panel1.add(turnoutNameLabel); 084 editLayoutSlipTurnoutAComboBox = new NamedBeanComboBox<>(InstanceManager.getDefault(TurnoutManager.class)); 085 editLayoutSlipTurnoutAComboBox.setToolTipText(Bundle.getMessage("EditTurnoutToolTip")); 086 LayoutEditor.setupComboBox(editLayoutSlipTurnoutAComboBox, false, true, false); 087 turnoutNameLabel.setLabelFor(editLayoutSlipTurnoutAComboBox); 088 panel1.add(editLayoutSlipTurnoutAComboBox); 089 contentPane.add(panel1); 090 091 // Setup turnout B 092 JPanel panel1a = new JPanel(); 093 panel1a.setLayout(new FlowLayout()); 094 JLabel turnoutBNameLabel = new JLabel(Bundle.getMessage("BeanNameTurnout") + " B"); // NOI18N 095 panel1a.add(turnoutBNameLabel); 096 editLayoutSlipTurnoutBComboBox = new NamedBeanComboBox<>(InstanceManager.getDefault(TurnoutManager.class)); 097 editLayoutSlipTurnoutBComboBox.setToolTipText(Bundle.getMessage("EditTurnoutToolTip")); 098 LayoutEditor.setupComboBox(editLayoutSlipTurnoutBComboBox, false, true, false); 099 turnoutBNameLabel.setLabelFor(editLayoutSlipTurnoutBComboBox); 100 panel1a.add(editLayoutSlipTurnoutBComboBox); 101 102 contentPane.add(panel1a); 103 104 JPanel panel2 = new JPanel(); 105 panel2.setLayout(new GridLayout(0, 3, 2, 2)); 106 107 panel2.add(new Label(" ")); 108 panel2.add(new Label(Bundle.getMessage("BeanNameTurnout") + " A:")); // NOI18N 109 panel2.add(new Label(Bundle.getMessage("BeanNameTurnout") + " B:")); // NOI18N 110 for (Map.Entry<Integer, LayoutSlip.TurnoutState> ts : layoutSlip.getTurnoutStates().entrySet()) { 111 SampleStates draw = new SampleStates(ts.getKey()); 112 draw.repaint(); 113 draw.setPreferredSize(new Dimension(40, 40)); 114 panel2.add(draw); 115 116 panel2.add(ts.getValue().getComboA()); 117 panel2.add(ts.getValue().getComboB()); 118 } 119 120 testPanel = new TestState(); 121 testPanel.setSize(40, 40); 122 testPanel.setPreferredSize(new Dimension(40, 40)); 123 panel2.add(testPanel); 124 JButton testButton = new JButton("Test"); // NOI18N 125 testButton.addActionListener((ActionEvent e) -> toggleStateTest()); 126 panel2.add(testButton); 127 contentPane.add(panel2); 128 129 JPanel panel33 = new JPanel(); 130 panel33.setLayout(new FlowLayout()); 131 editLayoutSlipHiddenBox.setToolTipText(Bundle.getMessage("HiddenToolTip")); // NOI18N 132 panel33.add(editLayoutSlipHiddenBox); 133 contentPane.add(panel33); 134 135 // setup block name 136 JPanel panel3 = new JPanel(); 137 panel3.setLayout(new FlowLayout()); 138 JLabel block1NameLabel = new JLabel(Bundle.getMessage("BlockID")); // NOI18N 139 panel3.add(block1NameLabel); 140 block1NameLabel.setLabelFor(editLayoutSlipBlockNameComboBox); 141 panel3.add(editLayoutSlipBlockNameComboBox); 142 LayoutEditor.setupComboBox(editLayoutSlipBlockNameComboBox, false, true, true); 143 editLayoutSlipBlockNameComboBox.setToolTipText(Bundle.getMessage("EditBlockNameHint")); // NOI18N 144 145 contentPane.add(panel3); 146 // set up Edit Block buttons 147 JPanel panel4 = new JPanel(); 148 panel4.setLayout(new FlowLayout()); 149 // Edit Block 150 panel4.add(editLayoutSlipBlockButton = new JButton(Bundle.getMessage("EditBlock", ""))); // NOI18N 151 editLayoutSlipBlockButton.addActionListener(this::editLayoutSlipEditBlockPressed 152 ); 153 editLayoutSlipBlockButton.setToolTipText(Bundle.getMessage("EditBlockHint", "")); // empty value for block 1 // NOI18N 154 155 contentPane.add(panel4); 156 157 // set up Done and Cancel buttons 158 JPanel panel5 = new JPanel(); 159 panel5.setLayout(new FlowLayout()); 160 addDoneCancelButtons(panel5, editLayoutSlipFrame.getRootPane(), 161 this::editLayoutSlipDonePressed, this::editLayoutSlipCancelPressed); 162 contentPane.add(panel5); 163 } 164 165 editLayoutSlipHiddenBox.setSelected(layoutSlipView.isHidden()); 166 167 // Set up for Edit 168 List<Turnout> currentTurnouts = new ArrayList<>(); 169 currentTurnouts.add(layoutSlip.getTurnout()); 170 currentTurnouts.add(layoutSlip.getTurnoutB()); 171 172 editLayoutSlipTurnoutAComboBox.setSelectedItem(layoutSlip.getTurnout()); 173 editLayoutSlipTurnoutAComboBox.addPopupMenuListener( 174 layoutEditor.newTurnoutComboBoxPopupMenuListener(editLayoutSlipTurnoutAComboBox, currentTurnouts)); 175 176 editLayoutSlipTurnoutBComboBox.setSelectedItem(layoutSlip.getTurnoutB()); 177 editLayoutSlipTurnoutBComboBox.addPopupMenuListener( 178 layoutEditor.newTurnoutComboBoxPopupMenuListener(editLayoutSlipTurnoutBComboBox, currentTurnouts)); 179 180 BlockManager bm = InstanceManager.getDefault(BlockManager.class); 181 editLayoutSlipBlockNameComboBox.getEditor().setItem(bm.getBlock(layoutSlip.getBlockName())); 182 editLayoutSlipBlockNameComboBox.setEnabled(!hasNxSensorPairs(layoutSlip.getLayoutBlock())); 183 184 editLayoutSlipFrame.addWindowListener(new java.awt.event.WindowAdapter() { 185 @Override 186 public void windowClosing(WindowEvent e) { 187 editLayoutSlipCancelPressed(null); 188 } 189 }); 190 editLayoutSlipFrame.pack(); 191 editLayoutSlipFrame.setVisible(true); 192 editLayoutSlipOpen = true; 193 editLayoutSlipNeedsBlockUpdate = false; 194 195 showSensorMessage(); 196 } // editLayoutSlip 197 198 /* 199 * draw the current state (STATE_AC, STATE_BD et al) 200 * with fixed geometry 201 */ 202 private void drawSlipState(Graphics2D g2, int state) { 203 Point2D cenP = layoutSlipView.getCoordsCenter(); 204 Point2D A = MathUtil.subtract(layoutSlipView.getCoordsA(), cenP); 205 Point2D B = MathUtil.subtract(layoutSlipView.getCoordsB(), cenP); 206 Point2D C = MathUtil.subtract(layoutSlipView.getCoordsC(), cenP); 207 Point2D D = MathUtil.subtract(layoutSlipView.getCoordsD(), cenP); 208 209 Point2D ctrP = new Point2D.Double(20.0, 20.0); 210 A = MathUtil.add(MathUtil.normalize(A, 18.0), ctrP); 211 B = MathUtil.add(MathUtil.normalize(B, 18.0), ctrP); 212 C = MathUtil.add(MathUtil.normalize(C, 18.0), ctrP); 213 D = MathUtil.add(MathUtil.normalize(D, 18.0), ctrP); 214 215 g2.setColor(Color.black); 216 g2.setStroke(new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND)); 217 218 g2.draw(new Line2D.Double(A, MathUtil.oneThirdPoint(A, C))); 219 g2.draw(new Line2D.Double(C, MathUtil.oneThirdPoint(C, A))); 220 221 if (state == LayoutTurnout.STATE_AC || state == LayoutTurnout.STATE_BD || state == LayoutTurnout.UNKNOWN) { 222 g2.draw(new Line2D.Double(A, MathUtil.oneThirdPoint(A, D))); 223 g2.draw(new Line2D.Double(D, MathUtil.oneThirdPoint(D, A))); 224 225 drawSlipStatePart1A(g2,state, A,B,C,D); 226 227 } else { 228 g2.draw(new Line2D.Double(B, MathUtil.oneThirdPoint(B, D))); 229 g2.draw(new Line2D.Double(D, MathUtil.oneThirdPoint(D, B))); 230 } 231 232 drawSlipStatePart2A(g2,state, A,B,C,D); 233 } 234 235 protected void drawSlipStatePart1A(Graphics2D g2, int state, Point2D A, Point2D B, Point2D C, Point2D D) { 236 } 237 238 protected void drawSlipStatePart1B(Graphics2D g2, int state, Point2D A, Point2D B, Point2D C, Point2D D) { 239 g2.draw(new Line2D.Double(B, MathUtil.oneThirdPoint(B, C))); 240 g2.draw(new Line2D.Double(C, MathUtil.oneThirdPoint(C, B))); 241 } 242 243 // all others implementation 244 protected void drawSlipStatePart2A(Graphics2D g2, int state, Point2D A, Point2D B, Point2D C, Point2D D) { 245 g2.draw(new Line2D.Double(A, MathUtil.oneThirdPoint(A, D))); 246 g2.draw(new Line2D.Double(D, MathUtil.oneThirdPoint(D, A))); 247 248 if (state == LayoutTurnout.STATE_AD) { 249 g2.setColor(Color.red); 250 g2.draw(new Line2D.Double(A, D)); 251 } else if (state == LayoutTurnout.STATE_AC) { 252 g2.draw(new Line2D.Double(B, MathUtil.oneThirdPoint(B, D))); 253 g2.draw(new Line2D.Double(D, MathUtil.oneThirdPoint(D, B))); 254 255 g2.setColor(Color.red); 256 g2.draw(new Line2D.Double(A, C)); 257 } else if (state == LayoutTurnout.STATE_BD) { 258 g2.setColor(Color.red); 259 g2.draw(new Line2D.Double(B, D)); 260 } else if (state == LayoutTurnout.STATE_BC) { 261 g2.setColor(Color.red); 262 g2.draw(new Line2D.Double(B, C)); 263 } else { 264 g2.draw(new Line2D.Double(B, MathUtil.oneThirdPoint(B, D))); 265 g2.draw(new Line2D.Double(D, MathUtil.oneThirdPoint(D, B))); 266 } 267 } 268 269 // DOUBLE_SLIP implementation 270 protected void drawSlipStatePart2B(Graphics2D g2, int state, Point2D A, Point2D B, Point2D C, Point2D D) { 271 if (state == LayoutTurnout.STATE_AC) { 272 g2.draw(new Line2D.Double(B, MathUtil.oneThirdPoint(B, D))); 273 g2.draw(new Line2D.Double(D, MathUtil.oneThirdPoint(D, B))); 274 275 g2.setColor(Color.red); 276 g2.draw(new Line2D.Double(A, C)); 277 } else if (state == LayoutTurnout.STATE_BD) { 278 g2.setColor(Color.red); 279 g2.draw(new Line2D.Double(B, D)); 280 } else if (state == LayoutTurnout.STATE_AD) { 281 g2.draw(new Line2D.Double(B, MathUtil.oneThirdPoint(B, C))); 282 283 g2.draw(new Line2D.Double(C, MathUtil.oneThirdPoint(C, B))); 284 285 g2.setColor(Color.red); 286 g2.draw(new Line2D.Double(A, D)); 287 } else if (state == LayoutTurnout.STATE_BC) { 288 g2.draw(new Line2D.Double(A, MathUtil.oneThirdPoint(A, D))); 289 290 g2.draw(new Line2D.Double(D, MathUtil.oneThirdPoint(D, A))); 291 g2.setColor(Color.red); 292 g2.draw(new Line2D.Double(B, C)); 293 } else { 294 g2.draw(new Line2D.Double(B, MathUtil.oneThirdPoint(B, D))); 295 g2.draw(new Line2D.Double(D, MathUtil.oneThirdPoint(D, B))); 296 } 297 } 298 299 300 301 class SampleStates extends JPanel { 302 303 // Methods, constructors, fields. 304 SampleStates(int state) { 305 super(); 306 this.state = state; 307 } 308 int state; 309 310 @Override 311 public void paintComponent(Graphics g) { 312 super.paintComponent(g); // paints background 313 if (g instanceof Graphics2D) { 314 drawSlipState((Graphics2D) g, state); 315 } 316 } 317 } 318 319 private int testState = LayoutTurnout.UNKNOWN; 320 321 /** 322 * Toggle slip states if clicked on, physical turnout exists, and not 323 * disabled. 324 */ 325 public void toggleStateTest() { 326 int turnAState; 327 int turnBState; 328 switch (testState) { 329 default: 330 case LayoutTurnout.STATE_AC: { 331 testState = LayoutTurnout.STATE_AD; 332 break; 333 } 334 335 case LayoutTurnout.STATE_BD: { 336 if (layoutSlip.getSlipType() == LayoutTurnout.TurnoutType.SINGLE_SLIP) { 337 testState = LayoutTurnout.STATE_AC; 338 } else { 339 testState = LayoutTurnout.STATE_BC; 340 } 341 break; 342 } 343 344 case LayoutTurnout.STATE_AD: { 345 testState = LayoutTurnout.STATE_BD; 346 break; 347 } 348 349 case LayoutTurnout.STATE_BC: { 350 testState = LayoutTurnout.STATE_AC; 351 break; 352 } 353 } 354 turnAState = layoutSlip.getTurnoutStates().get(testState).getTestTurnoutAState(); 355 turnBState = layoutSlip.getTurnoutStates().get(testState).getTestTurnoutBState(); 356 357 if (editLayoutSlipTurnoutAComboBox.getSelectedItem() != null) { 358 editLayoutSlipTurnoutAComboBox.getSelectedItem().setCommandedState(turnAState); 359 } 360 if (editLayoutSlipTurnoutBComboBox.getSelectedItem() != null) { 361 editLayoutSlipTurnoutBComboBox.getSelectedItem().setCommandedState(turnBState); 362 } 363 if (testPanel != null) { 364 testPanel.repaint(); 365 } 366 } 367 368 class TestState extends JPanel { 369 370 @Override 371 public void paintComponent(Graphics g) { 372 super.paintComponent(g); 373 if (g instanceof Graphics2D) { 374 drawSlipState((Graphics2D) g, testState); 375 } 376 } 377 } 378 379 private TestState testPanel; 380 381 private void editLayoutSlipEditBlockPressed(ActionEvent a) { 382 // check if a block name has been entered 383 String newName = editLayoutSlipBlockNameComboBox.getSelectedItemDisplayName(); 384 if (newName == null) { 385 newName = ""; 386 } 387 if (!layoutSlip.getBlockName().equals(newName)) { 388 // get new block, or null if block has been removed 389 layoutSlipView.setLayoutBlock(layoutEditor.provideLayoutBlock(newName)); 390 editLayoutSlipNeedsRedraw = true; 391 editLayoutSlipNeedsBlockUpdate = true; 392 } 393 // check if a block exists to edit 394 if (layoutSlip.getLayoutBlock() == null) { 395 JOptionPane.showMessageDialog(editLayoutSlipFrame, 396 Bundle.getMessage("Error1"), 397 Bundle.getMessage("ErrorTitle"), JOptionPane.ERROR_MESSAGE); 398 return; 399 } 400 layoutSlip.getLayoutBlock().editLayoutBlock(editLayoutSlipFrame); 401 editLayoutSlipNeedsRedraw = true; 402 layoutEditor.setDirty(); 403 } 404 405 private void editLayoutSlipDonePressed(ActionEvent a) { 406 String newName = editLayoutSlipTurnoutAComboBox.getSelectedItemDisplayName(); 407 if (newName == null) { 408 newName = ""; 409 } 410 if (!layoutSlip.getTurnoutName().equals(newName)) { 411 if (layoutEditor.validatePhysicalTurnout(newName, editLayoutSlipFrame)) { 412 layoutSlip.setTurnout(newName); 413 } else { 414 layoutSlip.setTurnout(""); 415 } 416 editLayoutSlipNeedsRedraw = true; 417 } 418 419 newName = editLayoutSlipTurnoutBComboBox.getSelectedItemDisplayName(); 420 if (newName == null) { 421 newName = ""; 422 } 423 if (!layoutSlip.getTurnoutBName().equals(newName)) { 424 if (layoutEditor.validatePhysicalTurnout(newName, editLayoutSlipFrame)) { 425 layoutSlip.setTurnoutB(newName); 426 } else { 427 layoutSlip.setTurnoutB(""); 428 } 429 editLayoutSlipNeedsRedraw = true; 430 } 431 432 newName = editLayoutSlipBlockNameComboBox.getSelectedItemDisplayName(); 433 if (newName == null) { 434 newName = ""; 435 } 436 if (!layoutSlip.getBlockName().equals(newName)) { 437 // get new block, or null if block has been removed 438 layoutSlipView.setLayoutBlock(layoutEditor.provideLayoutBlock(newName)); 439 editLayoutSlipNeedsRedraw = true; 440 layoutEditor.getLEAuxTools().setBlockConnectivityChanged(); 441 editLayoutSlipNeedsBlockUpdate = true; 442 } 443 for (LayoutSlip.TurnoutState ts : layoutSlip.getTurnoutStates().values()) { 444 ts.updateStatesFromCombo(); 445 } 446 447 // Verify that there are no turnouts or two turnouts. A single turnout is an error. 448 var turnoutNameA = layoutSlip.getTurnoutName(); 449 var turnoutNameB = layoutSlip.getTurnoutBName(); 450 if ((turnoutNameA.isEmpty() && !turnoutNameB.isEmpty()) || 451 (turnoutNameB.isEmpty() && !turnoutNameA.isEmpty())) { 452 JOptionPane.showMessageDialog(editLayoutSlipFrame, 453 Bundle.getMessage("Error20"), 454 Bundle.getMessage("ErrorTitle"), JOptionPane.ERROR_MESSAGE); 455 return; 456 } 457 458 // set hidden 459 boolean oldHidden = layoutSlipView.isHidden(); 460 layoutSlipView.setHidden(editLayoutSlipHiddenBox.isSelected()); 461 if (oldHidden != layoutSlipView.isHidden()) { 462 editLayoutSlipNeedsRedraw = true; 463 } 464 465 editLayoutSlipOpen = false; 466 editLayoutSlipFrame.setVisible(false); 467 editLayoutSlipFrame.dispose(); 468 editLayoutSlipFrame = null; 469 if (editLayoutSlipNeedsBlockUpdate) { 470 layoutSlip.updateBlockInfo(); 471 } 472 if (editLayoutSlipNeedsRedraw) { 473 layoutEditor.redrawPanel(); 474 layoutEditor.setDirty(); 475 editLayoutSlipNeedsRedraw = false; 476 } 477 } 478 479 private void editLayoutSlipCancelPressed(ActionEvent a) { 480 editLayoutSlipOpen = false; 481 editLayoutSlipFrame.setVisible(false); 482 editLayoutSlipFrame.dispose(); 483 editLayoutSlipFrame = null; 484 if (editLayoutSlipNeedsBlockUpdate) { 485 layoutSlip.updateBlockInfo(); 486 } 487 if (editLayoutSlipNeedsRedraw) { 488 layoutEditor.redrawPanel(); 489 layoutEditor.setDirty(); 490 editLayoutSlipNeedsRedraw = false; 491 } 492 } 493 494 495 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutSlipEditor.class); 496}