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