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        // set hidden
448        boolean oldHidden = layoutSlipView.isHidden();
449        layoutSlipView.setHidden(editLayoutSlipHiddenBox.isSelected());
450        if (oldHidden != layoutSlipView.isHidden()) {
451            editLayoutSlipNeedsRedraw = true;
452        }
453
454        editLayoutSlipOpen = false;
455        editLayoutSlipFrame.setVisible(false);
456        editLayoutSlipFrame.dispose();
457        editLayoutSlipFrame = null;
458        if (editLayoutSlipNeedsBlockUpdate) {
459            layoutSlip.updateBlockInfo();
460        }
461        if (editLayoutSlipNeedsRedraw) {
462            layoutEditor.redrawPanel();
463            layoutEditor.setDirty();
464            editLayoutSlipNeedsRedraw = false;
465        }
466    }
467
468    private void editLayoutSlipCancelPressed(ActionEvent a) {
469        editLayoutSlipOpen = false;
470        editLayoutSlipFrame.setVisible(false);
471        editLayoutSlipFrame.dispose();
472        editLayoutSlipFrame = null;
473        if (editLayoutSlipNeedsBlockUpdate) {
474            layoutSlip.updateBlockInfo();
475        }
476        if (editLayoutSlipNeedsRedraw) {
477            layoutEditor.redrawPanel();
478            layoutEditor.setDirty();
479            editLayoutSlipNeedsRedraw = false;
480        }
481    }
482
483
484    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutSlipEditor.class);
485}