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