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.jmrit.display.Positionable;
018import jmri.jmrit.display.SignalMastIcon;
019import jmri.swing.NamedBeanComboBox;
020import jmri.util.JmriJFrame;
021import jmri.util.swing.JmriJOptionPane;
022
023/**
024 * MVC Editor component for PositionablePoint objects.
025 *
026 * @author Bob Jacobsen  Copyright (c) 2020
027 *
028 */
029public class LayoutTurntableEditor extends LayoutTrackEditor {
030
031    /**
032     * constructor method.
033     * @param layoutEditor main layout editor.
034     */
035    public LayoutTurntableEditor(@Nonnull LayoutEditor layoutEditor) {
036        super(layoutEditor);
037    }
038
039    /*==============*\
040    | Edit Turntable |
041    \*==============*/
042    // variables for Edit Turntable pane
043    private LayoutTurntable layoutTurntable = null;
044    private LayoutTurntableView layoutTurntableView = null;
045
046    private JmriJFrame editLayoutTurntableFrame = null;
047    private final JTextField editLayoutTurntableRadiusTextField = new JTextField(8);
048    private final JTextField editLayoutTurntableAngleTextField = new JTextField(8);
049    private final NamedBeanComboBox<Block> editLayoutTurntableBlockNameComboBox = new NamedBeanComboBox<>(
050             InstanceManager.getDefault(BlockManager.class), null, DisplayOptions.DISPLAYNAME);
051    private JButton editLayoutTurntableSegmentEditBlockButton;
052    private final JComboBox<String> editLayoutTurntableMainlineComboBox = new JComboBox<>();
053
054    private JPanel editLayoutTurntableRayPanel;
055    private JButton editLayoutTurntableAddRayTrackButton;
056    private JCheckBox editLayoutTurntableDccControlledCheckBox;
057    private JCheckBox editLayoutTurntableUseSignalMastsCheckBox;
058    private JPanel signalMastParametersPanel;
059    private NamedBeanComboBox<SignalMast> exitMastComboBox;
060    private NamedBeanComboBox<SignalMast> bufferMastComboBox;
061
062    private String editLayoutTurntableOldRadius = "";
063    private final List<NamedBeanComboBox<SignalMast>> approachMastComboBoxes = new ArrayList<>();
064    private final java.util.Set<SignalMast> mastsUsedElsewhere = new java.util.HashSet<>();
065    private boolean editLayoutTurntableOpen = false;
066    private boolean editLayoutTurntableNeedsRedraw = false;
067
068    private JRadioButton doNotPlaceIcons;
069    private JRadioButton placeIconsLeft;
070    private JRadioButton placeIconsRight;
071
072    private final List<Turnout> turntableTurnouts = new ArrayList<>();
073
074    /**
075     * Edit a Turntable.
076     */
077    @Override
078    public void editLayoutTrack(@Nonnull LayoutTrackView layoutTrackView) {
079        if ( layoutTrackView instanceof LayoutTurntableView ) {
080            this.layoutTurntableView = (LayoutTurntableView) layoutTrackView;
081            this.layoutTurntable = this.layoutTurntableView.getTurntable();
082        } else {
083            log.error("editLayoutTrack called with wrong type {}", layoutTrackView, new Exception("traceback"));
084        }
085        sensorList.clear();
086
087        if (editLayoutTurntableOpen) {
088            editLayoutTurntableFrame.setVisible(true);
089        } else // Initialize if needed
090        if (editLayoutTurntableFrame == null) {
091            editLayoutTurntableFrame = new JmriJFrame(Bundle.getMessage("EditTurntable"), false, true);  // NOI18N
092            editLayoutTurntableFrame.addHelpMenu("package.jmri.jmrit.display.EditTurntable", true);  // NOI18N
093            editLayoutTurntableFrame.setLocation(50, 30);
094
095            Container contentPane = editLayoutTurntableFrame.getContentPane();
096            JPanel headerPane = new JPanel();
097            JPanel footerPane = new JPanel();
098            headerPane.setLayout(new BoxLayout(headerPane, BoxLayout.Y_AXIS));
099            footerPane.setLayout(new BoxLayout(footerPane, BoxLayout.Y_AXIS));
100            contentPane.setLayout(new BorderLayout());
101            contentPane.add(headerPane, BorderLayout.NORTH);
102            contentPane.add(footerPane, BorderLayout.SOUTH);
103
104            // setup radius text field
105            JPanel panel1 = new JPanel();
106            panel1.setLayout(new FlowLayout());
107            JLabel radiusLabel = new JLabel(Bundle.getMessage("TurntableRadius"));  // NOI18N
108            panel1.add(radiusLabel);
109            radiusLabel.setLabelFor(editLayoutTurntableRadiusTextField);
110            panel1.add(editLayoutTurntableRadiusTextField);
111            editLayoutTurntableRadiusTextField.setToolTipText(Bundle.getMessage("TurntableRadiusHint"));  // NOI18N
112            headerPane.add(panel1);
113
114            // setup ray track angle text field
115            JPanel panel2 = new JPanel();
116            panel2.setLayout(new FlowLayout());
117            JLabel rayAngleLabel = new JLabel(Bundle.getMessage("RayAngle"));  // NOI18N
118            panel2.add(rayAngleLabel);
119            rayAngleLabel.setLabelFor(editLayoutTurntableAngleTextField);
120            panel2.add(editLayoutTurntableAngleTextField);
121            editLayoutTurntableAngleTextField.setToolTipText(Bundle.getMessage("RayAngleHint"));  // NOI18N
122            headerPane.add(panel2);
123
124            // setup block name
125            JPanel panel2a = new JPanel();
126            panel2a.setLayout(new FlowLayout());
127            JLabel blockNameLabel = new JLabel(Bundle.getMessage("BlockID"));  // NOI18N
128            panel2a.add(blockNameLabel);
129            blockNameLabel.setLabelFor(editLayoutTurntableBlockNameComboBox);
130            LayoutEditor.setupComboBox(editLayoutTurntableBlockNameComboBox, false, true, true);
131            editLayoutTurntableBlockNameComboBox.setToolTipText(Bundle.getMessage("EditBlockNameHint"));  // NOI18N
132            panel2a.add(editLayoutTurntableBlockNameComboBox);
133
134            // Edit Block
135            panel2a.add(editLayoutTurntableSegmentEditBlockButton = new JButton(Bundle.getMessage("EditBlock", "")));  // NOI18N
136            editLayoutTurntableSegmentEditBlockButton.addActionListener(this::editLayoutTurntableEditBlockPressed);
137            editLayoutTurntableSegmentEditBlockButton.setToolTipText(Bundle.getMessage("EditBlockHint", "")); // empty value for block 1  // NOI18N
138            log.debug("mainline0 {}", layoutTurntable.isMainline());
139
140            boolean mainlineSaved = layoutTurntable.isMainline();  // the listener may be active so preserve the current state
141            editLayoutTurntableMainlineComboBox.removeAllItems();
142            editLayoutTurntableMainlineComboBox.addItem(Bundle.getMessage("Mainline"));
143            editLayoutTurntableMainlineComboBox.addItem(Bundle.getMessage("NotMainline"));
144            layoutTurntable.setMainline(mainlineSaved);  // restore the current state
145
146            panel2a.add(editLayoutTurntableMainlineComboBox);
147            log.debug("mainline1 {}", layoutTurntable.isMainline());
148
149            headerPane.add(panel2a);
150
151            // setup add ray track button
152            JPanel panel3 = new JPanel();
153            panel3.setLayout(new FlowLayout());
154            panel3.add(editLayoutTurntableAddRayTrackButton = new JButton(Bundle.getMessage("AddRayTrack")));  // NOI18N
155            editLayoutTurntableAddRayTrackButton.setToolTipText(Bundle.getMessage("AddRayTrackHint"));  // NOI18N
156            editLayoutTurntableAddRayTrackButton.addActionListener((ActionEvent e) -> {
157                addRayTrackPressed(e);
158                updateRayPanel();
159            });
160
161            panel3.add(editLayoutTurntableDccControlledCheckBox = new JCheckBox(Bundle.getMessage("TurntableDCCControlled")));  // NOI18N
162            headerPane.add(panel3);
163
164            JPanel panel4 = new JPanel();
165            panel4.setLayout(new FlowLayout());
166            panel4.add(editLayoutTurntableUseSignalMastsCheckBox = new JCheckBox(Bundle.getMessage("TurntableUseSignalMasts"))); // NOI18N
167            headerPane.add(panel4);
168
169            // setup signal mast parameters panel
170            signalMastParametersPanel = new JPanel();
171            signalMastParametersPanel.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("TurntableSignalMastAssignmentsTitle"))); // NOI18N
172            exitMastComboBox = new NamedBeanComboBox<>(InstanceManager.getDefault(SignalMastManager.class), null, DisplayOptions.DISPLAYNAME);
173            LayoutEditor.setupComboBox(exitMastComboBox, false, true, true);
174            exitMastComboBox.setEditable(false);
175            exitMastComboBox.setAllowNull(true);
176            bufferMastComboBox = new NamedBeanComboBox<>(InstanceManager.getDefault(SignalMastManager.class), null, DisplayOptions.DISPLAYNAME);
177            LayoutEditor.setupComboBox(bufferMastComboBox, false, true, true);
178            bufferMastComboBox.setEditable(false);
179            bufferMastComboBox.setAllowNull(true);
180
181            // set up Done and Cancel buttons
182            JPanel panel5 = new JPanel();
183            panel5.setLayout(new FlowLayout());
184            addDoneCancelButtons(panel5, editLayoutTurntableFrame.getRootPane(),
185                    this::editLayoutTurntableDonePressed, this::turntableEditCancelPressed);
186            footerPane.add(panel5);
187
188            editLayoutTurntableRayPanel = new JPanel();
189            editLayoutTurntableRayPanel.setLayout(new BoxLayout(editLayoutTurntableRayPanel, BoxLayout.Y_AXIS));
190            JScrollPane rayScrollPane = new JScrollPane(editLayoutTurntableRayPanel, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
191            contentPane.add(rayScrollPane, BorderLayout.CENTER);
192        }
193
194        editLayoutTurntableBlockNameComboBox.setSelectedIndex(-1);
195        LayoutBlock lb = layoutTurntable.getLayoutBlock();
196        if (lb != null) {
197            Block blk = lb.getBlock();
198            if (blk != null) {
199                editLayoutTurntableBlockNameComboBox.setSelectedItem(blk);
200            }
201        }
202
203        if (layoutTurntable.isMainline()) {
204            editLayoutTurntableMainlineComboBox.setSelectedIndex(0);
205            log.debug("setting mainline");
206        } else {
207            editLayoutTurntableMainlineComboBox.setSelectedIndex(1);
208            log.debug("setting sideline");
209        }
210
211        editLayoutTurntableMainlineComboBox.addActionListener((java.awt.event.ActionEvent e) -> {
212            if (layoutTurntable != null) {
213                layoutTurntable.setMainline(editLayoutTurntableMainlineComboBox.getSelectedIndex() == 0);
214            }
215        });
216
217        editLayoutTurntableDccControlledCheckBox.setSelected(layoutTurntable.isTurnoutControlled());
218        editLayoutTurntableUseSignalMastsCheckBox.setSelected(layoutTurntable.isDispatcherManaged());
219        editLayoutTurntableDccControlledCheckBox.addActionListener((ActionEvent e) -> {
220            layoutTurntable.setTurnoutControlled(editLayoutTurntableDccControlledCheckBox.isSelected());
221
222            for (Component comp : editLayoutTurntableRayPanel.getComponents()) {
223                if (comp instanceof TurntableRayPanel) {
224                    TurntableRayPanel trp = (TurntableRayPanel) comp;
225                    trp.showTurnoutDetails();
226                }
227            }
228            editLayoutTurntableFrame.pack();
229        });
230
231        signalMastParametersPanel.setVisible(layoutTurntable.isDispatcherManaged());
232        editLayoutTurntableUseSignalMastsCheckBox.addActionListener((ActionEvent e) -> {
233            boolean isSelected = editLayoutTurntableUseSignalMastsCheckBox.isSelected();
234            layoutTurntable.setDispatcherManaged(isSelected); // Update the model
235            signalMastParametersPanel.setVisible(layoutTurntable.isDispatcherManaged());
236            updateRayPanel(); // Rebuild to show/hide mast details, which also handles visibility
237            editLayoutTurntableFrame.pack();
238        });
239
240        // Cache the list of masts used on other parts of the layout.
241        // This is the performance-critical change, preventing a full layout scan on every click.
242        mastsUsedElsewhere.clear();
243        layoutEditor.getLETools().createListUsedSignalMasts();
244        mastsUsedElsewhere.addAll(layoutEditor.getLETools().usedMasts);
245
246        // Remove masts assigned to the current turntable from the "used elsewhere" list
247        if (layoutTurntable.getBufferMast() != null) {
248            mastsUsedElsewhere.remove(layoutTurntable.getBufferMast());
249        }
250        if (layoutTurntable.getExitSignalMast() != null) {
251            mastsUsedElsewhere.remove(layoutTurntable.getExitSignalMast());
252        }
253        for (LayoutTurntable.RayTrack ray : layoutTurntable.getRayTrackList()) {
254            if (ray.getApproachMast() != null) {
255                mastsUsedElsewhere.remove(ray.getApproachMast());
256            }
257        }
258
259        exitMastComboBox.setExcludedItems(mastsUsedElsewhere);
260        exitMastComboBox.addActionListener(e -> {
261            SignalMast newMast = exitMastComboBox.getSelectedItem();
262            layoutTurntable.setExitSignalMast( (newMast != null) ? newMast.getSystemName() : null );
263        });
264
265        bufferMastComboBox.setExcludedItems(mastsUsedElsewhere);
266        bufferMastComboBox.addActionListener(e -> {
267            SignalMast newMast = bufferMastComboBox.getSelectedItem();
268            layoutTurntable.setBufferSignalMast( (newMast != null) ? newMast.getSystemName() : null );
269        });
270
271        // Set up for Edit
272        editLayoutTurntableRadiusTextField.setText(" " + layoutTurntable.getRadius());
273        editLayoutTurntableOldRadius = editLayoutTurntableRadiusTextField.getText();
274        editLayoutTurntableAngleTextField.setText("0");
275        editLayoutTurntableFrame.addWindowListener(new java.awt.event.WindowAdapter() {
276            @Override
277            public void windowClosing(java.awt.event.WindowEvent e) {
278                turntableEditCancelPressed(null);
279            }
280        });
281        updateRayPanel();
282        SignalMast exitMast = layoutTurntable.getExitSignalMast();
283        SignalMast bufferMast = layoutTurntable.getBufferMast();
284        if (exitMast != null) {
285            exitMastComboBox.setSelectedItem(exitMast);
286        }
287        if (bufferMast != null) {
288            bufferMastComboBox.setSelectedItem(bufferMast);
289        }
290
291        editLayoutTurntableFrame.pack();
292        editLayoutTurntableFrame.setVisible(true);
293        editLayoutTurntableOpen = true;
294    }   // editLayoutTurntable
295
296    @InvokeOnGuiThread
297    private void editLayoutTurntableEditBlockPressed(ActionEvent a) {
298         // check if a block name has been entered
299         String newName = editLayoutTurntableBlockNameComboBox.getSelectedItemDisplayName();
300         if (newName == null) {
301             newName = "";
302         }
303         if ((layoutTurntable.getBlockName().isEmpty())
304                 || !layoutTurntable.getBlockName().equals(newName)) {
305             // get new block, or null if block has been removed
306             layoutTurntable.setLayoutBlock(layoutEditor.provideLayoutBlock(newName));
307             editLayoutTurntableNeedsRedraw = true;
308             ///layoutEditor.getLEAuxTools().setBlockConnectivityChanged();
309             ///layoutTurntable.updateBlockInfo();
310         }
311         // check if a block exists to edit
312         LayoutBlock blockToEdit = layoutTurntable.getLayoutBlock();
313         if (blockToEdit == null) {
314             JmriJOptionPane.showMessageDialog(editLayoutTurntableFrame,
315                     Bundle.getMessage("Error1"), // NOI18N
316                     Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
317             return;
318         }
319         blockToEdit.editLayoutBlock(editLayoutTurntableFrame);
320         layoutEditor.setDirty();
321         editLayoutTurntableNeedsRedraw = true;
322     }
323
324    // Remove old rays and add them back in
325    private void updateRayPanel() {
326        // Create list of turnouts to be retained in the NamedBeanComboBox
327        turntableTurnouts.clear();
328        layoutTurntable.getRayTrackList().forEach(rt -> turntableTurnouts.add(rt.getTurnout()));
329
330        editLayoutTurntableRayPanel.removeAll();
331        editLayoutTurntableRayPanel.setLayout(new BoxLayout(editLayoutTurntableRayPanel, BoxLayout.Y_AXIS));
332        for (LayoutTurntable.RayTrack rt : layoutTurntable.getRayTrackList()) {
333            editLayoutTurntableRayPanel.add(new TurntableRayPanel(rt));
334        }
335
336        // Rebuild signal mast panel
337        signalMastParametersPanel.removeAll();
338        approachMastComboBoxes.clear();
339
340        if (layoutTurntable.isDispatcherManaged()) {
341            signalMastParametersPanel.setLayout(new BoxLayout(signalMastParametersPanel, BoxLayout.Y_AXIS));
342
343            // Add approach masts for each ray
344            for (LayoutTurntable.RayTrack rt : layoutTurntable.getRayTrackList()) {
345                JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT));
346                p.add(new JLabel(Bundle.getMessage("ApproachMastRay", rt.getConnectionIndex() + 1)));
347                NamedBeanComboBox<SignalMast> combo = new NamedBeanComboBox<>(
348                        InstanceManager.getDefault(SignalMastManager.class), rt.getApproachMast(), DisplayOptions.DISPLAYNAME);
349                LayoutEditor.setupComboBox(combo, false, true, true);
350                combo.setEditable(false);
351                combo.setAllowNull(true);
352                p.add(combo);
353                signalMastParametersPanel.add(p);
354                approachMastComboBoxes.add(combo);
355            }
356
357            // Add action listeners now that the list is complete
358            for (int i = 0; i < approachMastComboBoxes.size(); i++) {
359                final int index = i; // final variable for use in lambda
360                approachMastComboBoxes.get(i).setExcludedItems(mastsUsedElsewhere);
361                approachMastComboBoxes.get(i).addActionListener(e -> {
362                    SignalMast newMast = approachMastComboBoxes.get(index).getSelectedItem();
363                    layoutTurntable.getRayTrackList().get(index).setApproachMast( (newMast != null) ? newMast.getSystemName() : null );
364                });
365            }
366            if (!approachMastComboBoxes.isEmpty()) {
367                signalMastParametersPanel.add(new JSeparator());
368            }
369
370            // Add shared icon placement controls
371            JPanel placementPanel = new JPanel();
372            placementPanel.setLayout(new BoxLayout(placementPanel, BoxLayout.Y_AXIS)); // NOI18N
373            placementPanel.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("TurntableAddMastIconsTitle")));
374
375            doNotPlaceIcons = new JRadioButton(Bundle.getMessage("DoNotPlace")); // NOI18N
376            placeIconsLeft = new JRadioButton(Bundle.getMessage("LeftHandSide")); // NOI18N
377            placeIconsRight = new JRadioButton(Bundle.getMessage("RightHandSide")); // NOI18N
378            ButtonGroup bg = new ButtonGroup();
379            bg.add(doNotPlaceIcons);
380            bg.add(placeIconsLeft);
381            bg.add(placeIconsRight);
382            switch (layoutTurntable.getSignalIconPlacement()) {
383                case 1:
384                    placeIconsLeft.setSelected(true);
385                    break;
386                case 2:
387                    placeIconsRight.setSelected(true);
388                    break;
389                default: doNotPlaceIcons.setSelected(true);
390            }
391
392            JPanel radioPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
393            radioPanel.add(doNotPlaceIcons);
394            radioPanel.add(placeIconsLeft);
395            radioPanel.add(placeIconsRight);
396            placementPanel.add(radioPanel);
397            signalMastParametersPanel.add(placementPanel);
398
399            signalMastParametersPanel.add(new JSeparator());
400
401            JPanel mastPanel = new JPanel(new GridBagLayout());
402            GridBagConstraints c = new GridBagConstraints();
403            c.gridx = 0;
404            c.gridy = 0;
405            c.anchor = GridBagConstraints.LINE_START;
406            c.insets = new Insets(2, 2, 2, 2); // Default insets
407            mastPanel.add(new JLabel(Bundle.getMessage("TurntableExitMastLabel")), c);
408            c.gridx = 1;
409            c.insets = new Insets(2, 5, 2, 2); // Add left padding
410            mastPanel.add(exitMastComboBox, c);
411
412            c.gridx = 0;
413            c.gridy = 1;
414            c.insets = new Insets(2, 2, 2, 2); // Reset for label
415            mastPanel.add(new JLabel(Bundle.getMessage("TurntableBufferMastLabel")), c);
416            c.gridx = 1;
417            c.insets = new Insets(2, 5, 2, 2); // Add left padding
418            mastPanel.add(bufferMastComboBox, c);
419
420            signalMastParametersPanel.add(mastPanel);
421            editLayoutTurntableRayPanel.add(signalMastParametersPanel);
422        }
423        editLayoutTurntableRayPanel.revalidate();
424        editLayoutTurntableRayPanel.repaint();
425        editLayoutTurntableFrame.pack();
426    }
427
428    private void saveRayPanelDetail() {
429        for (Component comp : editLayoutTurntableRayPanel.getComponents()) {
430            if (comp instanceof TurntableRayPanel) {
431                TurntableRayPanel trp = (TurntableRayPanel) comp;
432                trp.updateDetails();
433            }
434        }
435    }
436
437    private void addRayTrackPressed(ActionEvent a) {
438        double ang = 0.0;
439        try {
440            ang = Float.parseFloat(editLayoutTurntableAngleTextField.getText());
441        } catch (Exception e) {
442            JmriJOptionPane.showMessageDialog(editLayoutTurntableFrame, Bundle.getMessage("EntryError") + ": "
443                    + e + Bundle.getMessage("TryAgain"), Bundle.getMessage("ErrorTitle"),
444                    JmriJOptionPane.ERROR_MESSAGE);
445            return;
446        }
447        layoutTurntable.addRay(ang);
448        layoutEditor.redrawPanel();
449        layoutEditor.setDirty();
450        editLayoutTurntableNeedsRedraw = false;
451    }
452
453    private void editLayoutTurntableDonePressed(ActionEvent a) {
454        // check if Block changed
455        String newName = editLayoutTurntableBlockNameComboBox.getSelectedItemDisplayName();
456        if (newName == null) {
457            newName = "";
458        }
459
460        if ((layoutTurntable.getBlockName().isEmpty()) || !layoutTurntable.getBlockName().equals(newName)) {
461            // get new block, or null if block has been removed
462            layoutTurntable.setLayoutBlock(layoutEditor.provideLayoutBlock(newName));
463            editLayoutTurntableNeedsRedraw = true;
464            ///layoutEditor.getLEAuxTools().setBlockConnectivityChanged();
465            ///layoutTurntable.updateBlockInfo();
466        }
467
468        layoutTurntable.setMainline(editLayoutTurntableMainlineComboBox.getSelectedIndex() == 0);
469
470        layoutTurntable.setDispatcherManaged(editLayoutTurntableUseSignalMastsCheckBox.isSelected());
471        if (editLayoutTurntableUseSignalMastsCheckBox.isSelected()) {
472            String exitMastName = exitMastComboBox.getSelectedItemDisplayName();
473            String bufferMastName = bufferMastComboBox.getSelectedItemDisplayName();
474            layoutTurntable.setExitSignalMast(exitMastName);
475            layoutTurntable.setBufferSignalMast(bufferMastName);
476
477            for (int i = 0; i < approachMastComboBoxes.size(); i++) {
478                LayoutTurntable.RayTrack ray = layoutTurntable.getRayTrackList().get(i);
479                NamedBeanComboBox<SignalMast> combo = approachMastComboBoxes.get(i);
480                ray.setApproachMast(combo.getSelectedItemDisplayName());
481            }
482
483            // Always remove any existing icons for this turntable's approach masts first.
484            // This ensures that moving icons from right-to-left works correctly.
485            List<SignalMastIcon> iconsToRemove = new ArrayList<>();
486            for (Positionable p : layoutEditor.getContents()) {
487                if (p instanceof SignalMastIcon) {
488                    SignalMastIcon icon = (SignalMastIcon) p;
489                    if (layoutTurntable.isApproachMast(icon.getSignalMast())) {
490                        iconsToRemove.add(icon);
491                    }
492                }
493            }
494            for (SignalMastIcon icon : iconsToRemove) {
495                icon.remove();
496                editLayoutTurntableNeedsRedraw = true;
497            }
498
499            // Now, if requested, place the new icons.
500            if (!doNotPlaceIcons.isSelected()) { // placeIconsLeft or placeIconsRight is selected
501                for (int i = 0; i < approachMastComboBoxes.size(); i++) {
502                    LayoutTurntable.RayTrack ray = layoutTurntable.getRayTrackList().get(i);
503                    SignalMast mast = approachMastComboBoxes.get(i).getSelectedItem();
504                    if (mast != null) {
505                        if (ray.getConnect() != null) {
506                            SignalMastIcon icon = new SignalMastIcon(layoutEditor);
507                            icon.setSignalMast(mast.getDisplayName());
508                            log.debug("Placing mast for turntable ray, connected to track segment: {}", ray.getConnect().getName()); // NOI18N
509                            layoutEditor.getLETools().placingBlockForTurntable(icon, placeIconsRight.isSelected(), 0.0,
510                                    ray.getConnect(), layoutTurntableView.getRayCoordsIndexed(ray.getConnectionIndex()));
511                            editLayoutTurntableNeedsRedraw = true;
512                        }
513                    }
514                }
515            }
516
517            // Save placement selection
518            int placementSelection;
519            if (placeIconsLeft.isSelected()) {
520                placementSelection = 1;
521            } else if (placeIconsRight.isSelected()) {
522                placementSelection = 2;
523            } else {
524                placementSelection = 0;
525            }
526            layoutTurntable.setSignalIconPlacement(placementSelection);
527        }
528
529        // check if new radius was entered
530        String str = editLayoutTurntableRadiusTextField.getText();
531        if (!str.equals(editLayoutTurntableOldRadius)) {
532            double rad = 0.0;
533            try {
534                rad = Float.parseFloat(str);
535            } catch (Exception e) {
536                JmriJOptionPane.showMessageDialog(editLayoutTurntableFrame, Bundle.getMessage("EntryError") + ": " // NOI18N
537                        + e + Bundle.getMessage("TryAgain"), Bundle.getMessage("ErrorTitle"), // NOI18N
538                        JmriJOptionPane.ERROR_MESSAGE);
539                return;
540            }
541            layoutTurntable.setRadius(rad);
542            editLayoutTurntableNeedsRedraw = true;
543        }
544        // clean up
545        saveRayPanelDetail();
546        editLayoutTurntableOpen = false;
547        editLayoutTurntableFrame.setVisible(false);
548        editLayoutTurntableFrame.dispose();
549        editLayoutTurntableFrame = null;
550        if (editLayoutTurntableNeedsRedraw) {
551            layoutEditor.redrawPanel();
552            layoutEditor.setDirty();
553            editLayoutTurntableNeedsRedraw = false;
554        }
555    }
556
557    private void turntableEditCancelPressed(ActionEvent a) {
558        editLayoutTurntableOpen = false;
559        editLayoutTurntableFrame.setVisible(false);
560        editLayoutTurntableFrame.dispose();
561        editLayoutTurntableFrame = null;
562        if (editLayoutTurntableNeedsRedraw) {
563            layoutEditor.redrawPanel();
564            layoutEditor.setDirty();
565            editLayoutTurntableNeedsRedraw = false;
566        }
567    }
568
569    /*===================*\
570    | Turntable Ray Panel |
571    \*===================*/
572    public class TurntableRayPanel extends JPanel {
573
574        // variables for Edit Turntable ray pane
575        private LayoutTurntable.RayTrack rayTrack = null;
576        private final JPanel rayTurnoutPanel;
577        private final NamedBeanComboBox<Turnout> turnoutNameComboBox;
578        private final TitledBorder rayTitledBorder;
579        private final JComboBox<String> rayTurnoutStateComboBox;
580        private final JLabel rayTurnoutStateLabel;
581        private final JTextField rayAngleTextField;
582        private final int[] rayTurnoutStateValues = new int[]{Turnout.CLOSED, Turnout.THROWN};
583        private final DecimalFormat twoDForm = new DecimalFormat("#.00");
584
585        /**
586         * constructor method.
587         * @param rayTrack the single ray track to edit.
588         */
589        public TurntableRayPanel(@Nonnull LayoutTurntable.RayTrack rayTrack) {
590            this.rayTrack = rayTrack;
591
592            JPanel top = new JPanel();
593
594            JLabel rayAngleLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("RayAngle")));
595            top.add(rayAngleLabel);
596            top.add(rayAngleTextField = new JTextField(5));
597            rayAngleLabel.setLabelFor(rayAngleTextField);
598
599            rayAngleTextField.addFocusListener(new FocusListener() {
600                @Override
601                public void focusGained(FocusEvent e) {
602                }
603
604                @Override
605                public void focusLost(FocusEvent e) {
606                    try {
607                        Float.parseFloat(rayAngleTextField.getText());
608                    } catch (Exception ex) {
609                        JmriJOptionPane.showMessageDialog(editLayoutTurntableFrame, Bundle.getMessage("EntryError") + ": " // NOI18N
610                                + ex + Bundle.getMessage("TryAgain"), Bundle.getMessage("ErrorTitle"), // NOI18N
611                                JmriJOptionPane.ERROR_MESSAGE);
612                    }
613                }
614            }
615            );
616            this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
617            this.add(top);
618
619            turnoutNameComboBox = new NamedBeanComboBox<>(InstanceManager.getDefault(TurnoutManager.class));
620            turnoutNameComboBox.setToolTipText(Bundle.getMessage("EditTurnoutToolTip"));
621            LayoutEditor.setupComboBox(turnoutNameComboBox, false, true, false);
622            turnoutNameComboBox.setSelectedItem(rayTrack.getTurnout());
623            turnoutNameComboBox.addPopupMenuListener(
624                    layoutEditor.newTurnoutComboBoxPopupMenuListener(turnoutNameComboBox, turntableTurnouts));
625            String turnoutStateThrown = InstanceManager.turnoutManagerInstance().getThrownText();
626            String turnoutStateClosed = InstanceManager.turnoutManagerInstance().getClosedText();
627            String[] turnoutStates = new String[]{turnoutStateClosed, turnoutStateThrown};
628
629            rayTurnoutStateComboBox = new JComboBox<>(turnoutStates);
630            rayTurnoutStateLabel = new JLabel(Bundle.getMessage("TurnoutState"));  // NOI18N
631            rayTurnoutPanel = new JPanel();
632
633            rayTurnoutPanel.setBorder(new EtchedBorder());
634            JLabel turnoutLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("BeanNameTurnout"))); // NOI18N
635            rayTurnoutPanel.add(turnoutLabel);
636            turnoutLabel.setLabelFor(turnoutNameComboBox);
637            rayTurnoutPanel.add(turnoutNameComboBox);
638            rayTurnoutPanel.add(rayTurnoutStateLabel);
639            rayTurnoutStateLabel.setLabelFor(rayTurnoutStateComboBox);
640            rayTurnoutPanel.add(rayTurnoutStateComboBox);
641            if (rayTrack.getTurnoutState() == Turnout.CLOSED) {
642                rayTurnoutStateComboBox.setSelectedItem(turnoutStateClosed);
643            } else {
644                rayTurnoutStateComboBox.setSelectedItem(turnoutStateThrown);
645            }
646            this.add(rayTurnoutPanel);
647
648            JButton deleteRayButton;
649            top.add(deleteRayButton = new JButton(Bundle.getMessage("ButtonDelete")));  // NOI18N
650            deleteRayButton.setToolTipText(Bundle.getMessage("DeleteRayTrack"));  // NOI18N
651            deleteRayButton.addActionListener((ActionEvent e) -> {
652                delete();
653                updateRayPanel();
654            });
655            rayTitledBorder = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black));
656
657            this.setBorder(rayTitledBorder);
658
659            showTurnoutDetails();
660
661            rayAngleTextField.setText(twoDForm.format(rayTrack.getAngle()));
662            int rayNumber = rayTrack.getConnectionIndex() + 1;
663            rayTitledBorder.setTitle(Bundle.getMessage("Ray") + " : " + rayNumber);  // NOI18N
664            if (rayTrack.getConnect() == null) {
665                rayTitledBorder.setTitle(Bundle.getMessage("MakeLabel",
666                        Bundle.getMessage("Unconnected")) + " "
667                        + rayNumber);  // NOI18N
668            } else if (rayTrack.getConnect().getLayoutBlock() != null) {
669                rayTitledBorder.setTitle(Bundle.getMessage("MakeLabel",
670                        Bundle.getMessage("Connected")) + " "
671                        + rayTrack.getConnect().getLayoutBlock().getDisplayName()
672                        + " (" + Bundle.getMessage("Ray") + " " + rayNumber + ")");  // NOI18N
673            }
674        }
675
676        private void delete() {
677            int n = JmriJOptionPane.showConfirmDialog(null,
678                    Bundle.getMessage("Question7"), // NOI18N
679                    Bundle.getMessage("WarningTitle"), // NOI18N
680                    JmriJOptionPane.YES_NO_OPTION);
681            if (n == JmriJOptionPane.YES_OPTION) {
682                if (layoutTurntable.isRayDeleteAllowed(rayTrack)) {
683                    layoutTurntable.deleteRay(rayTrack);
684                }
685            }
686        }
687
688        private void updateDetails() {
689            if (turnoutNameComboBox == null || rayTurnoutStateComboBox == null) {
690                return;
691            }
692            String turnoutName = turnoutNameComboBox.getSelectedItemDisplayName();
693            if (turnoutName == null) {
694                turnoutName = "";
695            }
696            rayTrack.setTurnout(turnoutName, rayTurnoutStateValues[rayTurnoutStateComboBox.getSelectedIndex()]);
697            if (!rayAngleTextField.getText().equals(twoDForm.format(rayTrack.getAngle()))) {
698                try {
699                    double ang = Float.parseFloat(rayAngleTextField.getText());
700                    rayTrack.setAngle(ang);
701                } catch (Exception e) {
702                    log.error("Angle is not in correct format so will skip {}", rayAngleTextField.getText());  // NOI18N
703                }
704            }
705        }
706
707        private void showTurnoutDetails() {
708            boolean vis = layoutTurntable.isTurnoutControlled();
709            rayTurnoutPanel.setVisible(vis);
710            turnoutNameComboBox.setVisible(vis);
711            rayTurnoutStateComboBox.setVisible(vis);
712            rayTurnoutStateLabel.setVisible(vis);
713        }
714    }
715
716
717    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutTurntableEditor.class);
718}