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}