001package jmri.jmrit.display.layoutEditor.LayoutEditorDialogs;
002
003import java.awt.*;
004import java.awt.event.*;
005import java.text.DecimalFormat;
006import java.util.ArrayList;
007import java.util.List;
008
009import javax.annotation.Nonnull;
010import javax.swing.*;
011import javax.swing.border.EtchedBorder;
012import javax.swing.border.TitledBorder;
013
014import jmri.NamedBean.DisplayOptions;
015import jmri.*;
016import jmri.jmrit.display.layoutEditor.*;
017import jmri.swing.NamedBeanComboBox;
018import jmri.util.JmriJFrame;
019import jmri.util.swing.JmriJOptionPane;
020
021/**
022 * MVC Editor component for PositionablePoint objects.
023 *
024 * @author Bob Jacobsen  Copyright (c) 2020
025 *
026 */
027public class LayoutTurntableEditor extends LayoutTrackEditor {
028
029    /**
030     * constructor method.
031     * @param layoutEditor main layout editor.
032     */
033    public LayoutTurntableEditor(@Nonnull LayoutEditor layoutEditor) {
034        super(layoutEditor);
035    }
036
037    /*==============*\
038    | Edit Turntable |
039    \*==============*/
040    // variables for Edit Turntable pane
041    private LayoutTurntable layoutTurntable = null;
042    private LayoutTurntableView layoutTurntableView = null;
043
044    private JmriJFrame editLayoutTurntableFrame = null;
045    private final JTextField editLayoutTurntableRadiusTextField = new JTextField(8);
046    private final JTextField editLayoutTurntableAngleTextField = new JTextField(8);
047    private final NamedBeanComboBox<Block> editLayoutTurntableBlockNameComboBox = new NamedBeanComboBox<>(
048             InstanceManager.getDefault(BlockManager.class), null, DisplayOptions.DISPLAYNAME);
049    private JButton editLayoutTurntableSegmentEditBlockButton;
050
051    private JPanel editLayoutTurntableRayPanel;
052    private JButton editLayoutTurntableAddRayTrackButton;
053    private JCheckBox editLayoutTurntableDccControlledCheckBox;
054
055    private String editLayoutTurntableOldRadius = "";
056    private boolean editLayoutTurntableOpen = false;
057    private boolean editLayoutTurntableNeedsRedraw = false;
058
059    private final List<Turnout> turntableTurnouts = new ArrayList<>();
060
061    /**
062     * Edit a Turntable.
063     */
064    @Override
065    public void editLayoutTrack(@Nonnull LayoutTrackView layoutTrackView) {
066        if ( layoutTrackView instanceof LayoutTurntableView ) {
067            this.layoutTurntableView = (LayoutTurntableView) layoutTrackView;
068            this.layoutTurntable = this.layoutTurntableView.getTurntable();
069        } else {
070            log.error("editLayoutTrack called with wrong type {}", layoutTrackView, new Exception("traceback"));
071        }
072        sensorList.clear();
073
074        if (editLayoutTurntableOpen) {
075            editLayoutTurntableFrame.setVisible(true);
076        } else // Initialize if needed
077        if (editLayoutTurntableFrame == null) {
078            editLayoutTurntableFrame = new JmriJFrame(Bundle.getMessage("EditTurntable"), false, true);  // NOI18N
079            editLayoutTurntableFrame.addHelpMenu("package.jmri.jmrit.display.EditTurntable", true);  // NOI18N
080            editLayoutTurntableFrame.setLocation(50, 30);
081
082            Container contentPane = editLayoutTurntableFrame.getContentPane();
083            JPanel headerPane = new JPanel();
084            JPanel footerPane = new JPanel();
085            headerPane.setLayout(new BoxLayout(headerPane, BoxLayout.Y_AXIS));
086            footerPane.setLayout(new BoxLayout(footerPane, BoxLayout.Y_AXIS));
087            contentPane.setLayout(new BorderLayout());
088            contentPane.add(headerPane, BorderLayout.NORTH);
089            contentPane.add(footerPane, BorderLayout.SOUTH);
090
091            // setup radius text field
092            JPanel panel1 = new JPanel();
093            panel1.setLayout(new FlowLayout());
094            JLabel radiusLabel = new JLabel(Bundle.getMessage("TurntableRadius"));  // NOI18N
095            panel1.add(radiusLabel);
096            radiusLabel.setLabelFor(editLayoutTurntableRadiusTextField);
097            panel1.add(editLayoutTurntableRadiusTextField);
098            editLayoutTurntableRadiusTextField.setToolTipText(Bundle.getMessage("TurntableRadiusHint"));  // NOI18N
099            headerPane.add(panel1);
100
101            // setup ray track angle text field
102            JPanel panel2 = new JPanel();
103            panel2.setLayout(new FlowLayout());
104            JLabel rayAngleLabel = new JLabel(Bundle.getMessage("RayAngle"));  // NOI18N
105            panel2.add(rayAngleLabel);
106            rayAngleLabel.setLabelFor(editLayoutTurntableAngleTextField);
107            panel2.add(editLayoutTurntableAngleTextField);
108            editLayoutTurntableAngleTextField.setToolTipText(Bundle.getMessage("RayAngleHint"));  // NOI18N
109            headerPane.add(panel2);
110
111            // setup block name
112             JPanel panel2a = new JPanel();
113             panel2a.setLayout(new FlowLayout());
114             JLabel blockNameLabel = new JLabel(Bundle.getMessage("BlockID"));  // NOI18N
115             panel2a.add(blockNameLabel);
116             blockNameLabel.setLabelFor(editLayoutTurntableBlockNameComboBox);
117             LayoutEditor.setupComboBox(editLayoutTurntableBlockNameComboBox, false, true, true);
118             editLayoutTurntableBlockNameComboBox.setToolTipText(Bundle.getMessage("EditBlockNameHint"));  // NOI18N
119             panel2a.add(editLayoutTurntableBlockNameComboBox);
120
121             // Edit Block
122             panel2a.add(editLayoutTurntableSegmentEditBlockButton = new JButton(Bundle.getMessage("EditBlock", "")));  // NOI18N
123             editLayoutTurntableSegmentEditBlockButton.addActionListener(this::editLayoutTurntableEditBlockPressed);
124             editLayoutTurntableSegmentEditBlockButton.setToolTipText(Bundle.getMessage("EditBlockHint", "")); // empty value for block 1  // NOI18N
125             headerPane.add(panel2a);
126
127            // setup add ray track button
128            JPanel panel3 = new JPanel();
129            panel3.setLayout(new FlowLayout());
130            panel3.add(editLayoutTurntableAddRayTrackButton = new JButton(Bundle.getMessage("AddRayTrack")));  // NOI18N
131            editLayoutTurntableAddRayTrackButton.setToolTipText(Bundle.getMessage("AddRayTrackHint"));  // NOI18N
132            editLayoutTurntableAddRayTrackButton.addActionListener((ActionEvent e) -> {
133                addRayTrackPressed(e);
134                updateRayPanel();
135            });
136
137            panel3.add(editLayoutTurntableDccControlledCheckBox = new JCheckBox(Bundle.getMessage("TurntableDCCControlled")));  // NOI18N
138            headerPane.add(panel3);
139
140            // set up Done and Cancel buttons
141            JPanel panel5 = new JPanel();
142            panel5.setLayout(new FlowLayout());
143            addDoneCancelButtons(panel5, editLayoutTurntableFrame.getRootPane(),
144                    this::editLayoutTurntableDonePressed, this::turntableEditCancelPressed);
145            footerPane.add(panel5);
146
147            editLayoutTurntableRayPanel = new JPanel();
148            editLayoutTurntableRayPanel.setLayout(new BoxLayout(editLayoutTurntableRayPanel, BoxLayout.Y_AXIS));
149            JScrollPane rayScrollPane = new JScrollPane(editLayoutTurntableRayPanel, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
150            contentPane.add(rayScrollPane, BorderLayout.CENTER);
151        }
152
153        editLayoutTurntableBlockNameComboBox.setSelectedIndex(-1);
154        LayoutBlock lb = layoutTurntable.getLayoutBlock();
155        if (lb != null) {
156            Block blk = lb.getBlock();
157            if (blk != null) {
158                editLayoutTurntableBlockNameComboBox.setSelectedItem(blk);
159            }
160        }
161
162        editLayoutTurntableDccControlledCheckBox.setSelected(layoutTurntable.isTurnoutControlled());
163        editLayoutTurntableDccControlledCheckBox.addActionListener((ActionEvent e) -> {
164            layoutTurntable.setTurnoutControlled(editLayoutTurntableDccControlledCheckBox.isSelected());
165
166            for (Component comp : editLayoutTurntableRayPanel.getComponents()) {
167                if (comp instanceof TurntableRayPanel) {
168                    TurntableRayPanel trp = (TurntableRayPanel) comp;
169                    trp.showTurnoutDetails();
170                }
171            }
172            editLayoutTurntableFrame.pack();
173        });
174        updateRayPanel();
175        // Set up for Edit
176        editLayoutTurntableRadiusTextField.setText(" " + layoutTurntable.getRadius());
177        editLayoutTurntableOldRadius = editLayoutTurntableRadiusTextField.getText();
178        editLayoutTurntableAngleTextField.setText("0");
179        editLayoutTurntableFrame.addWindowListener(new java.awt.event.WindowAdapter() {
180            @Override
181            public void windowClosing(java.awt.event.WindowEvent e) {
182                turntableEditCancelPressed(null);
183            }
184        });
185        editLayoutTurntableFrame.pack();
186        editLayoutTurntableFrame.setVisible(true);
187        editLayoutTurntableOpen = true;
188    }   // editLayoutTurntable
189
190    @InvokeOnGuiThread
191    private void editLayoutTurntableEditBlockPressed(ActionEvent a) {
192         // check if a block name has been entered
193         String newName = editLayoutTurntableBlockNameComboBox.getSelectedItemDisplayName();
194         if (newName == null) {
195             newName = "";
196         }
197         if ((layoutTurntable.getBlockName().isEmpty())
198                 || !layoutTurntable.getBlockName().equals(newName)) {
199             // get new block, or null if block has been removed
200             layoutTurntable.setLayoutBlock(layoutEditor.provideLayoutBlock(newName));
201             editLayoutTurntableNeedsRedraw = true;
202             ///layoutEditor.getLEAuxTools().setBlockConnectivityChanged();
203             ///layoutTurntable.updateBlockInfo();
204         }
205         // check if a block exists to edit
206         LayoutBlock blockToEdit = layoutTurntable.getLayoutBlock();
207         if (blockToEdit == null) {
208             JmriJOptionPane.showMessageDialog(editLayoutTurntableFrame,
209                     Bundle.getMessage("Error1"), // NOI18N
210                     Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
211             return;
212         }
213         blockToEdit.editLayoutBlock(editLayoutTurntableFrame);
214         layoutEditor.setDirty();
215         editLayoutTurntableNeedsRedraw = true;
216     }
217
218    // Remove old rays and add them back in
219    private void updateRayPanel() {
220        for (Component comp : editLayoutTurntableRayPanel.getComponents()) {
221            editLayoutTurntableRayPanel.remove(comp);
222        }
223
224        // Create list of turnouts to be retained in the NamedBeanComboBox
225        turntableTurnouts.clear();
226        for (LayoutTurntable.RayTrack rt : layoutTurntable.getRayTrackList()) {
227            turntableTurnouts.add(rt.getTurnout());
228        }
229
230        editLayoutTurntableRayPanel.setLayout(new BoxLayout(editLayoutTurntableRayPanel, BoxLayout.Y_AXIS));
231        for (LayoutTurntable.RayTrack rt : layoutTurntable.getRayTrackList()) {
232            editLayoutTurntableRayPanel.add(new TurntableRayPanel(rt));
233        }
234        editLayoutTurntableRayPanel.revalidate();
235        editLayoutTurntableRayPanel.repaint();
236        editLayoutTurntableFrame.pack();
237    }
238
239    private void saveRayPanelDetail() {
240        for (Component comp : editLayoutTurntableRayPanel.getComponents()) {
241            if (comp instanceof TurntableRayPanel) {
242                TurntableRayPanel trp = (TurntableRayPanel) comp;
243                trp.updateDetails();
244            }
245        }
246    }
247
248    private void addRayTrackPressed(ActionEvent a) {
249        double ang = 0.0;
250        try {
251            ang = Float.parseFloat(editLayoutTurntableAngleTextField.getText());
252        } catch (Exception e) {
253            JmriJOptionPane.showMessageDialog(editLayoutTurntableFrame, Bundle.getMessage("EntryError") + ": "
254                    + e + Bundle.getMessage("TryAgain"), Bundle.getMessage("ErrorTitle"),
255                    JmriJOptionPane.ERROR_MESSAGE);
256            return;
257        }
258        layoutTurntable.addRay(ang);
259        layoutEditor.redrawPanel();
260        layoutEditor.setDirty();
261        editLayoutTurntableNeedsRedraw = false;
262    }
263
264    private void editLayoutTurntableDonePressed(ActionEvent a) {
265        // check if Block changed
266        String newName = editLayoutTurntableBlockNameComboBox.getSelectedItemDisplayName();
267        if (newName == null) {
268            newName = "";
269        }
270
271        if ((layoutTurntable.getBlockName().isEmpty()) || !layoutTurntable.getBlockName().equals(newName)) {
272            // get new block, or null if block has been removed
273            layoutTurntable.setLayoutBlock(layoutEditor.provideLayoutBlock(newName));
274            editLayoutTurntableNeedsRedraw = true;
275            ///layoutEditor.getLEAuxTools().setBlockConnectivityChanged();
276            ///layoutTurntable.updateBlockInfo();
277        }
278
279        // check if new radius was entered
280        String str = editLayoutTurntableRadiusTextField.getText();
281        if (!str.equals(editLayoutTurntableOldRadius)) {
282            double rad = 0.0;
283            try {
284                rad = Float.parseFloat(str);
285            } catch (Exception e) {
286                JmriJOptionPane.showMessageDialog(editLayoutTurntableFrame, Bundle.getMessage("EntryError") + ": " // NOI18N
287                        + e + Bundle.getMessage("TryAgain"), Bundle.getMessage("ErrorTitle"), // NOI18N
288                        JmriJOptionPane.ERROR_MESSAGE);
289                return;
290            }
291            layoutTurntable.setRadius(rad);
292            editLayoutTurntableNeedsRedraw = true;
293        }
294        // clean up
295        editLayoutTurntableOpen = false;
296        editLayoutTurntableFrame.setVisible(false);
297        editLayoutTurntableFrame.dispose();
298        editLayoutTurntableFrame = null;
299        saveRayPanelDetail();
300        if (editLayoutTurntableNeedsRedraw) {
301            layoutEditor.redrawPanel();
302            layoutEditor.setDirty();
303            editLayoutTurntableNeedsRedraw = false;
304        }
305    }
306
307    private void turntableEditCancelPressed(ActionEvent a) {
308        editLayoutTurntableOpen = false;
309        editLayoutTurntableFrame.setVisible(false);
310        editLayoutTurntableFrame.dispose();
311        editLayoutTurntableFrame = null;
312        if (editLayoutTurntableNeedsRedraw) {
313            layoutEditor.redrawPanel();
314            layoutEditor.setDirty();
315            editLayoutTurntableNeedsRedraw = false;
316        }
317    }
318
319    /*===================*\
320    | Turntable Ray Panel |
321    \*===================*/
322    public class TurntableRayPanel extends JPanel {
323
324        // variables for Edit Turntable ray pane
325        private LayoutTurntable.RayTrack rayTrack = null;
326        private final JPanel rayTurnoutPanel;
327        private final NamedBeanComboBox<Turnout> turnoutNameComboBox;
328        private final TitledBorder rayTitledBorder;
329        private final JComboBox<String> rayTurnoutStateComboBox;
330        private final JLabel rayTurnoutStateLabel;
331        private final JTextField rayAngleTextField;
332        private final int[] rayTurnoutStateValues = new int[]{Turnout.CLOSED, Turnout.THROWN};
333        private final DecimalFormat twoDForm = new DecimalFormat("#.00");
334
335        /**
336         * constructor method.
337         * @param rayTrack the single ray track to edit.
338         */
339        public TurntableRayPanel(@Nonnull LayoutTurntable.RayTrack rayTrack) {
340            this.rayTrack = rayTrack;
341
342            JPanel top = new JPanel();
343
344            JLabel rayAngleLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("RayAngle")));
345            top.add(rayAngleLabel);
346            top.add(rayAngleTextField = new JTextField(5));
347            rayAngleLabel.setLabelFor(rayAngleTextField);
348
349            rayAngleTextField.addFocusListener(new FocusListener() {
350                @Override
351                public void focusGained(FocusEvent e) {
352                }
353
354                @Override
355                public void focusLost(FocusEvent e) {
356                    try {
357                        Float.parseFloat(rayAngleTextField.getText());
358                    } catch (Exception ex) {
359                        JmriJOptionPane.showMessageDialog(editLayoutTurntableFrame, Bundle.getMessage("EntryError") + ": " // NOI18N
360                                + ex + Bundle.getMessage("TryAgain"), Bundle.getMessage("ErrorTitle"), // NOI18N
361                                JmriJOptionPane.ERROR_MESSAGE);
362                    }
363                }
364            }
365            );
366            this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
367            this.add(top);
368
369            turnoutNameComboBox = new NamedBeanComboBox<>(InstanceManager.getDefault(TurnoutManager.class));
370            turnoutNameComboBox.setToolTipText(Bundle.getMessage("EditTurnoutToolTip"));
371            LayoutEditor.setupComboBox(turnoutNameComboBox, false, true, false);
372            turnoutNameComboBox.setSelectedItem(rayTrack.getTurnout());
373            turnoutNameComboBox.addPopupMenuListener(
374                    layoutEditor.newTurnoutComboBoxPopupMenuListener(turnoutNameComboBox, turntableTurnouts));
375            String turnoutStateThrown = InstanceManager.turnoutManagerInstance().getThrownText();
376            String turnoutStateClosed = InstanceManager.turnoutManagerInstance().getClosedText();
377            String[] turnoutStates = new String[]{turnoutStateClosed, turnoutStateThrown};
378
379            rayTurnoutStateComboBox = new JComboBox<>(turnoutStates);
380            rayTurnoutStateLabel = new JLabel(Bundle.getMessage("TurnoutState"));  // NOI18N
381            rayTurnoutPanel = new JPanel();
382
383            rayTurnoutPanel.setBorder(new EtchedBorder());
384            rayTurnoutPanel.add(turnoutNameComboBox);
385            rayTurnoutPanel.add(rayTurnoutStateLabel);
386            rayTurnoutStateLabel.setLabelFor(rayTurnoutStateComboBox);
387            rayTurnoutPanel.add(rayTurnoutStateComboBox);
388            if (rayTrack.getTurnoutState() == Turnout.CLOSED) {
389                rayTurnoutStateComboBox.setSelectedItem(turnoutStateClosed);
390            } else {
391                rayTurnoutStateComboBox.setSelectedItem(turnoutStateThrown);
392            }
393            this.add(rayTurnoutPanel);
394
395            JButton deleteRayButton;
396            top.add(deleteRayButton = new JButton(Bundle.getMessage("ButtonDelete")));  // NOI18N
397            deleteRayButton.setToolTipText(Bundle.getMessage("DeleteRayTrack"));  // NOI18N
398            deleteRayButton.addActionListener((ActionEvent e) -> {
399                delete();
400                updateRayPanel();
401            });
402            rayTitledBorder = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black));
403
404            this.setBorder(rayTitledBorder);
405
406            showTurnoutDetails();
407
408            rayAngleTextField.setText(twoDForm.format(rayTrack.getAngle()));
409            rayTitledBorder.setTitle(Bundle.getMessage("Ray") + " : " + rayTrack.getConnectionIndex());  // NOI18N
410            if (rayTrack.getConnect() == null) {
411                rayTitledBorder.setTitle(Bundle.getMessage("MakeLabel",
412                        Bundle.getMessage("Unconnected")) + " "
413                        + rayTrack.getConnectionIndex());  // NOI18N
414            } else if (rayTrack.getConnect().getLayoutBlock() != null) {
415                rayTitledBorder.setTitle(Bundle.getMessage("MakeLabel",
416                        Bundle.getMessage("Connected")) + " "
417                        + rayTrack.getConnect().getLayoutBlock().getDisplayName());  // NOI18N
418            }
419        }
420
421        private void delete() {
422            int n = JmriJOptionPane.showConfirmDialog(null,
423                    Bundle.getMessage("Question7"), // NOI18N
424                    Bundle.getMessage("WarningTitle"), // NOI18N
425                    JmriJOptionPane.YES_NO_OPTION);
426            if (n == JmriJOptionPane.YES_OPTION) {
427                layoutTurntable.deleteRay(rayTrack);
428            }
429        }
430
431        private void updateDetails() {
432            if (turnoutNameComboBox == null || rayTurnoutStateComboBox == null) {
433                return;
434            }
435            String turnoutName = turnoutNameComboBox.getSelectedItemDisplayName();
436            if (turnoutName == null) {
437                turnoutName = "";
438            }
439            rayTrack.setTurnout(turnoutName, rayTurnoutStateValues[rayTurnoutStateComboBox.getSelectedIndex()]);
440            if (!rayAngleTextField.getText().equals(twoDForm.format(rayTrack.getAngle()))) {
441                try {
442                    double ang = Float.parseFloat(rayAngleTextField.getText());
443                    rayTrack.setAngle(ang);
444                } catch (Exception e) {
445                    log.error("Angle is not in correct format so will skip {}", rayAngleTextField.getText());  // NOI18N
446                }
447            }
448        }
449
450        private void showTurnoutDetails() {
451            boolean vis = layoutTurntable.isTurnoutControlled();
452            rayTurnoutPanel.setVisible(vis);
453            turnoutNameComboBox.setVisible(vis);
454            rayTurnoutStateComboBox.setVisible(vis);
455            rayTurnoutStateLabel.setVisible(vis);
456        }
457    }
458
459
460    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutTurntableEditor.class);
461}