001package jmri.jmrit.display.layoutEditor;
002
003import static java.awt.event.KeyEvent.KEY_PRESSED;
004
005import static jmri.jmrit.display.layoutEditor.LayoutEditor.setupComboBox;
006
007import java.awt.BorderLayout;
008import java.awt.Color;
009import java.awt.Component;
010import java.awt.Dimension;
011import java.awt.FlowLayout;
012import java.awt.event.*;
013import java.awt.geom.Point2D;
014import java.util.LinkedHashMap;
015import java.util.Map;
016
017import javax.annotation.Nonnull;
018import javax.swing.*;
019
020import jmri.*;
021import jmri.jmrit.logixng.GlobalVariable;
022import jmri.jmrit.logixng.GlobalVariableManager;
023import jmri.swing.NamedBeanComboBox;
024import jmri.util.MathUtil;
025import jmri.util.swing.JmriJOptionPane;
026
027import org.apache.commons.lang3.StringUtils;
028
029/**
030 * This is the base class for the horizontal, vertical and floating toolbar
031 * panels
032 *
033 * @author George Warner Copyright: (c) 2017-2019
034 */
035public class LayoutEditorToolBarPanel extends JPanel implements Disposable {
036
037    protected final LayoutEditor layoutEditor; // initialized in constuctor
038
039    // top row of radio buttons
040    protected JLabel turnoutLabel = new JLabel();
041    protected JRadioButton turnoutRHButton = new JRadioButton(Bundle.getMessage("RightHandAbbreviation"));
042    protected JRadioButton turnoutLHButton = new JRadioButton(Bundle.getMessage("LeftHandAbbreviation"));
043    protected JRadioButton turnoutWYEButton = new JRadioButton(Bundle.getMessage("WYEAbbreviation"));
044    protected JRadioButton doubleXoverButton = new JRadioButton(Bundle.getMessage("DoubleCrossoverAbbreviation"));
045    protected JRadioButton rhXoverButton = new JRadioButton(Bundle.getMessage("RightCrossover")); //key is also used by Control Panel
046    // Editor, placed in DisplayBundle
047    protected JRadioButton lhXoverButton = new JRadioButton(Bundle.getMessage("LeftCrossover")); //idem
048    protected JRadioButton layoutSingleSlipButton = new JRadioButton(Bundle.getMessage("LayoutSingleSlip"));
049    protected JRadioButton layoutDoubleSlipButton = new JRadioButton(Bundle.getMessage("LayoutDoubleSlip"));
050
051    // Default flow layout definitions for JPanels
052    protected FlowLayout leftRowLayout = new FlowLayout(FlowLayout.LEFT, 5, 0);       //5 pixel gap between items, no vertical gap
053    protected FlowLayout centerRowLayout = new FlowLayout(FlowLayout.CENTER, 5, 0);   //5 pixel gap between items, no vertical gap
054    protected FlowLayout rightRowLayout = new FlowLayout(FlowLayout.RIGHT, 5, 0);     //5 pixel gap between items, no vertical gap
055
056    // top row of check boxes
057    protected NamedBeanComboBox<Turnout> turnoutNameComboBox = new NamedBeanComboBox<>(
058            InstanceManager.turnoutManagerInstance(), null, NamedBean.DisplayOptions.DISPLAYNAME);
059
060    protected JLabel turnoutNameLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("Name")));
061    protected JPanel turnoutNamePanel = new JPanel(leftRowLayout);
062    protected JPanel extraTurnoutPanel = new JPanel(leftRowLayout);
063    protected NamedBeanComboBox<Turnout> extraTurnoutNameComboBox = new NamedBeanComboBox<>(
064            InstanceManager.turnoutManagerInstance(), null, NamedBean.DisplayOptions.DISPLAYNAME);
065    protected JComboBox<String> rotationComboBox = null;
066    protected JPanel rotationPanel = new JPanel(leftRowLayout);
067
068    // 2nd row of radio buttons
069    protected JLabel trackLabel = new JLabel();
070    protected JRadioButton levelXingButton = new JRadioButton(Bundle.getMessage("LevelCrossing"));
071    protected JRadioButton trackButton = new JRadioButton(Bundle.getMessage("TrackSegment"));
072    protected JRadioButton turntableButton = new JRadioButton(Bundle.getMessage("Turntable"));
073    protected JRadioButton traverserButton = new JRadioButton(Bundle.getMessage("Traverser"));
074
075    // 2nd row of check boxes
076    protected JPanel trackSegmentPropertiesPanel = new JPanel(leftRowLayout);
077    protected JCheckBox mainlineTrack = new JCheckBox(Bundle.getMessage("MainlineBox"));
078    protected JCheckBox dashedLine = new JCheckBox(Bundle.getMessage("Dashed"));
079
080    protected JLabel blockLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("BlockID")));
081    protected NamedBeanComboBox<Block> blockIDComboBox = new NamedBeanComboBox<>(
082            InstanceManager.getDefault(BlockManager.class), null, NamedBean.DisplayOptions.DISPLAYNAME);
083    protected JCheckBox highlightBlockCheckBox = new JCheckBox(Bundle.getMessage("HighlightSelectedBlockTitle"));
084
085    protected JLabel blockSensorLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("BlockSensorName")));
086    protected NamedBeanComboBox<Sensor> blockSensorComboBox = new NamedBeanComboBox<>(
087            InstanceManager.getDefault(SensorManager.class), null, NamedBean.DisplayOptions.DISPLAYNAME);
088
089    // 3rd row of radio buttons (and any associated text fields)
090    protected JRadioButton endBumperButton = new JRadioButton(Bundle.getMessage("EndBumper"));
091    protected JRadioButton anchorButton = new JRadioButton(Bundle.getMessage("Anchor"));
092    protected JRadioButton edgeButton = new JRadioButton(Bundle.getMessage("EdgeConnector"));
093
094    protected JLabel labelsLabel = new JLabel();
095    protected JRadioButton textLabelButton = new JRadioButton(Bundle.getMessage("TextLabel"));
096    protected JTextField textLabelTextField = new JTextField(12);
097
098    protected JRadioButton memoryButton = new JRadioButton(Bundle.getMessage("BeanNameMemory"));
099    protected NamedBeanComboBox<Memory> textMemoryComboBox = new NamedBeanComboBox<>(
100            InstanceManager.getDefault(MemoryManager.class), null, NamedBean.DisplayOptions.DISPLAYNAME);
101
102    protected JRadioButton globalVariableButton = new JRadioButton(Bundle.getMessage("BeanNameGlobalVariable"));
103    protected NamedBeanComboBox<GlobalVariable> textGlobalVariableComboBox = new NamedBeanComboBox<>(
104            InstanceManager.getDefault(GlobalVariableManager.class), null, NamedBean.DisplayOptions.DISPLAYNAME);
105
106    protected JRadioButton blockContentsButton = new JRadioButton(Bundle.getMessage("BlockContentsLabel"));
107    protected NamedBeanComboBox<Block> blockContentsComboBox = new NamedBeanComboBox<>(
108            InstanceManager.getDefault(BlockManager.class), null, NamedBean.DisplayOptions.DISPLAYNAME);
109
110    // 4th row of radio buttons (and any associated text fields)
111    protected JRadioButton multiSensorButton = new JRadioButton(Bundle.getMessage("MultiSensor") + "...");
112
113    protected JRadioButton signalMastButton = new JRadioButton(Bundle.getMessage("SignalMastIcon"));
114    protected NamedBeanComboBox<SignalMast> signalMastComboBox = new NamedBeanComboBox<>(
115            InstanceManager.getDefault(SignalMastManager.class), null, NamedBean.DisplayOptions.DISPLAYNAME);
116
117    protected JRadioButton sensorButton = new JRadioButton(Bundle.getMessage("SensorIcon"));
118    protected NamedBeanComboBox<Sensor> sensorComboBox = new NamedBeanComboBox<>(
119            InstanceManager.getDefault(SensorManager.class), null, NamedBean.DisplayOptions.DISPLAYNAME);
120
121    protected JRadioButton turnoutButton = new JRadioButton(Bundle.getMessage("TurnoutIcon"));
122    protected NamedBeanComboBox<Turnout> turnoutComboBox = new NamedBeanComboBox<>(
123            InstanceManager.getDefault(TurnoutManager.class), null, NamedBean.DisplayOptions.DISPLAYNAME);
124
125    protected JRadioButton signalButton = new JRadioButton(Bundle.getMessage("SignalIcon"));
126    protected NamedBeanComboBox<SignalHead> signalHeadComboBox = new NamedBeanComboBox<>(
127            InstanceManager.getDefault(SignalHeadManager.class), null, NamedBean.DisplayOptions.DISPLAYNAME);
128
129    protected JRadioButton iconLabelButton = new JRadioButton(Bundle.getMessage("IconLabel"));
130    protected JRadioButton logixngButton = new JRadioButton(Bundle.getMessage("LogixNGIcon"));
131    protected JRadioButton audioButton = new JRadioButton(Bundle.getMessage("AudioIcon"));
132    protected NamedBeanComboBox<Audio> textAudioComboBox = new NamedBeanComboBox<>(
133            InstanceManager.getDefault(AudioSourceManager.class), null, NamedBean.DisplayOptions.DISPLAYNAME);
134    protected JRadioButton shapeButton = new JRadioButton(Bundle.getMessage("LayoutShape"));
135
136    protected JButton changeIconsButton = new JButton(Bundle.getMessage("ChangeIcons") + "...");
137
138    protected MultiIconEditor sensorIconEditor = null;
139    protected JFrame sensorFrame = null;
140
141    protected MultiIconEditor turnoutIconEditor = null;
142    protected JFrame turnoutFrame = null;
143
144    protected MultiIconEditor signalIconEditor = null;
145    protected JFrame signalFrame = null;
146
147    protected MultiIconEditor iconEditor = null;
148    protected JFrame iconFrame = null;
149
150    protected MultiIconEditor logixngEditor = null;
151    protected JFrame logixngFrame = null;
152
153    protected MultiIconEditor audioEditor = null;
154    protected JFrame audioFrame = null;
155
156    protected MultiSensorIconFrame multiSensorFrame = null;
157
158    protected JPanel zoomPanel = new JPanel();
159    protected JLabel zoomLabel = new JLabel("x1");
160
161    protected JPanel locationPanel = new JPanel();
162    protected JPopupMenu locationPopupMenu = new JPopupMenu();
163
164    protected JLabel xLabel = new JLabel("00");
165    protected JLabel yLabel = new JLabel("00");
166
167    protected JPanel blockPropertiesPanel = null;
168
169    // non-GUI variables
170    protected boolean toolBarIsWide = true;
171    protected ButtonGroup itemGroup = null;
172
173    /**
174     * Constructor for LayoutEditorToolBarPanel.
175     * <p>
176     * Note an unusual design feature: Since this calls the
177     * {@link #setupComponents()} and {@link #layoutComponents()} non-final
178     * methods in the constructor, any subclass reimplementing those must
179     * provide versions that will work before the subclasses own initializers
180     * and constructor is run.
181     *
182     * @param layoutEditor the layout editor that this is for
183     */
184    public LayoutEditorToolBarPanel(@Nonnull LayoutEditor layoutEditor) {
185        this.layoutEditor = layoutEditor;
186
187        setupComponents();
188        layoutComponents();
189    }
190
191    protected void setupComponents() {
192        // setup group for radio buttons selecting items to add and line style
193        itemGroup = new ButtonGroup();
194        itemGroup.add(turnoutRHButton);
195        itemGroup.add(turnoutLHButton);
196        itemGroup.add(turnoutWYEButton);
197        itemGroup.add(doubleXoverButton);
198        itemGroup.add(rhXoverButton);
199        itemGroup.add(lhXoverButton);
200        itemGroup.add(levelXingButton);
201        itemGroup.add(layoutSingleSlipButton);
202        itemGroup.add(layoutDoubleSlipButton);
203        itemGroup.add(endBumperButton);
204        itemGroup.add(anchorButton);
205        itemGroup.add(edgeButton);
206        itemGroup.add(trackButton);
207        itemGroup.add(turntableButton);
208        itemGroup.add(traverserButton);
209        itemGroup.add(multiSensorButton);
210        itemGroup.add(sensorButton);
211        itemGroup.add(turnoutButton);
212        itemGroup.add(signalButton);
213        itemGroup.add(signalMastButton);
214        itemGroup.add(textLabelButton);
215        itemGroup.add(memoryButton);
216        itemGroup.add(globalVariableButton);
217        itemGroup.add(blockContentsButton);
218        itemGroup.add(iconLabelButton);
219        itemGroup.add(logixngButton);
220        itemGroup.add(audioButton);
221        itemGroup.add(shapeButton);
222
223        // This is used to enable/disable property controls depending on which (radio) button is selected
224        ActionListener selectionListAction = (ActionEvent event) -> {
225            //turnout properties
226            boolean e = (turnoutRHButton.isSelected()
227                    || turnoutLHButton.isSelected()
228                    || turnoutWYEButton.isSelected()
229                    || doubleXoverButton.isSelected()
230                    || rhXoverButton.isSelected()
231                    || lhXoverButton.isSelected()
232                    || layoutSingleSlipButton.isSelected()
233                    || layoutDoubleSlipButton.isSelected());
234            log.debug("turnoutPropertiesPanel is {}", e ? "enabled" : "disabled");
235            turnoutNamePanel.setEnabled(e);
236
237            for (Component i : turnoutNamePanel.getComponents()) {
238                i.setEnabled(e);
239            }
240            rotationPanel.setEnabled(e);
241
242            for (Component i : rotationPanel.getComponents()) {
243                i.setEnabled(e);
244            }
245
246            //second turnout property
247            e = (layoutSingleSlipButton.isSelected() || layoutDoubleSlipButton.isSelected());
248            log.debug("extraTurnoutPanel is {}", e ? "enabled" : "disabled");
249
250            for (Component i : extraTurnoutPanel.getComponents()) {
251                i.setEnabled(e);
252            }
253
254            //track Segment properties
255            boolean isTrack = trackButton.isSelected();
256            boolean isTT = turntableButton.isSelected() || traverserButton.isSelected();
257            log.debug("trackSegmentPropertiesPanel is {}", (isTrack || isTT) ? "enabled" : "disabled");
258            mainlineTrack.setEnabled(isTrack || isTT);
259            dashedLine.setEnabled(isTrack);
260
261            // block properties
262            e = (turnoutRHButton.isSelected()
263                    || turnoutLHButton.isSelected()
264                    || turnoutWYEButton.isSelected()
265                    || doubleXoverButton.isSelected()
266                    || rhXoverButton.isSelected()
267                    || lhXoverButton.isSelected()
268                    || layoutSingleSlipButton.isSelected()
269                    || layoutDoubleSlipButton.isSelected()
270                    || levelXingButton.isSelected()
271                    || trackButton.isSelected()
272                    || turntableButton.isSelected()
273                    || traverserButton.isSelected());
274            log.debug("blockPanel is {}", e ? "enabled" : "disabled");
275
276            if (blockPropertiesPanel != null) {
277                for (Component i : blockPropertiesPanel.getComponents()) {
278                    i.setEnabled(e);
279                }
280
281                if (e) {
282                    blockPropertiesPanel.setBackground(Color.lightGray);
283                } else {
284                    blockPropertiesPanel.setBackground(new Color(238, 238, 238));
285                }
286            } else {
287                blockLabel.setEnabled(e);
288                blockIDComboBox.setEnabled(e);
289                blockSensorLabel.setEnabled(e);
290                blockSensorComboBox.setEnabled(e);
291            }
292
293            // enable/disable text label, memory, global variable & block contents text fields
294            textLabelTextField.setEnabled(textLabelButton.isSelected());
295            textMemoryComboBox.setEnabled(memoryButton.isSelected());
296            textGlobalVariableComboBox.setEnabled(globalVariableButton.isSelected());
297            blockContentsComboBox.setEnabled(blockContentsButton.isSelected());
298            textAudioComboBox.setEnabled(audioButton.isSelected());
299
300            // enable/disable signal mast, sensor, turnout & signal head text fields
301            signalMastComboBox.setEnabled(signalMastButton.isSelected());
302            sensorComboBox.setEnabled(sensorButton.isSelected());
303            turnoutComboBox.setEnabled(turnoutButton.isSelected());
304            signalHeadComboBox.setEnabled(signalButton.isSelected());
305
306            // changeIconsButton
307            e = (sensorButton.isSelected()
308                    || turnoutButton.isSelected()
309                    || signalButton.isSelected()
310                    || iconLabelButton.isSelected()
311                    || logixngButton.isSelected()
312                    || audioButton.isSelected());
313            log.debug("changeIconsButton is {}", e ? "enabled" : "disabled");
314            changeIconsButton.setEnabled(e);
315        };
316
317        turnoutRHButton.addActionListener(selectionListAction);
318        turnoutLHButton.addActionListener(selectionListAction);
319        turnoutWYEButton.addActionListener(selectionListAction);
320        doubleXoverButton.addActionListener(selectionListAction);
321        rhXoverButton.addActionListener(selectionListAction);
322        lhXoverButton.addActionListener(selectionListAction);
323        levelXingButton.addActionListener(selectionListAction);
324        layoutSingleSlipButton.addActionListener(selectionListAction);
325        layoutDoubleSlipButton.addActionListener(selectionListAction);
326        endBumperButton.addActionListener(selectionListAction);
327        anchorButton.addActionListener(selectionListAction);
328        edgeButton.addActionListener(selectionListAction);
329        trackButton.addActionListener(selectionListAction);
330        turntableButton.addActionListener(selectionListAction);
331        traverserButton.addActionListener(selectionListAction);
332        multiSensorButton.addActionListener(selectionListAction);
333        sensorButton.addActionListener(selectionListAction);
334        turnoutButton.addActionListener(selectionListAction);
335        signalButton.addActionListener(selectionListAction);
336        signalMastButton.addActionListener(selectionListAction);
337        textLabelButton.addActionListener(selectionListAction);
338        memoryButton.addActionListener(selectionListAction);
339        globalVariableButton.addActionListener(selectionListAction);
340        blockContentsButton.addActionListener(selectionListAction);
341        iconLabelButton.addActionListener(selectionListAction);
342        logixngButton.addActionListener(selectionListAction);
343        audioButton.addActionListener(selectionListAction);
344        shapeButton.addActionListener(selectionListAction);
345
346        // first row of edit tool bar items
347        // turnout items
348        turnoutRHButton.setSelected(true);
349        turnoutRHButton.setToolTipText(Bundle.getMessage("RHToolTip"));
350        turnoutLHButton.setToolTipText(Bundle.getMessage("LHToolTip"));
351        turnoutWYEButton.setToolTipText(Bundle.getMessage("WYEToolTip"));
352        doubleXoverButton.setToolTipText(Bundle.getMessage("DoubleCrossoverToolTip"));
353        rhXoverButton.setToolTipText(Bundle.getMessage("RHCrossoverToolTip"));
354        lhXoverButton.setToolTipText(Bundle.getMessage("LHCrossoverToolTip"));
355        layoutSingleSlipButton.setToolTipText(Bundle.getMessage("SingleSlipToolTip"));
356        layoutDoubleSlipButton.setToolTipText(Bundle.getMessage("DoubleSlipToolTip"));
357
358        turnoutNamePanel.add(turnoutNameLabel);
359
360        setupComboBox(turnoutNameComboBox, false, true, false);
361        turnoutNameComboBox.setToolTipText(Bundle.getMessage("TurnoutNameToolTip"));
362        turnoutNamePanel.add(turnoutNameComboBox);
363
364        // disable turnouts that are already in use
365        turnoutNameComboBox.addPopupMenuListener(layoutEditor.newTurnoutComboBoxPopupMenuListener(turnoutNameComboBox));
366        // turnoutNameComboBox.setEnabledColor(Color.green.darker().darker());
367        // turnoutNameComboBox.setDisabledColor(Color.red);
368
369        setupComboBox(extraTurnoutNameComboBox, false, true, false);
370        extraTurnoutNameComboBox.setToolTipText(Bundle.getMessage("SecondTurnoutNameToolTip"));
371
372        extraTurnoutNameComboBox.addPopupMenuListener(layoutEditor.newTurnoutComboBoxPopupMenuListener(extraTurnoutNameComboBox));
373        // extraTurnoutNameComboBox.setEnabledColor(Color.green.darker().darker());
374        // extraTurnoutNameComboBox.setDisabledColor(Color.red);
375
376        // this is enabled/disabled via selectionListAction above
377        JLabel extraTurnoutLabel = new JLabel(Bundle.getMessage("SecondName"));
378        extraTurnoutLabel.setEnabled(false);
379        extraTurnoutPanel.add(extraTurnoutLabel);
380        extraTurnoutPanel.add(extraTurnoutNameComboBox);
381        extraTurnoutPanel.setEnabled(false);
382
383        String[] angleStrings = {"-180", "-135", "-90", "-45", "0", "+45", "+90", "+135", "+180"};
384        rotationComboBox = new JComboBox<>(angleStrings);
385        rotationComboBox.setEditable(true);
386        rotationComboBox.setSelectedIndex(4);
387        rotationComboBox.setMaximumRowCount(9);
388        rotationComboBox.setToolTipText(Bundle.getMessage("RotationToolTip"));
389
390        JLabel rotationLabel = new JLabel(Bundle.getMessage("Rotation"));
391        rotationPanel.add(rotationLabel);
392        rotationPanel.add(rotationComboBox);
393
394        zoomPanel.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("ZoomLabel"))));
395        zoomPanel.add(zoomLabel);
396
397        Dimension coordSize = xLabel.getPreferredSize();
398        coordSize.width *= 2;
399        xLabel.setPreferredSize(coordSize);
400        yLabel.setPreferredSize(coordSize);
401
402        locationPanel.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("Location"))));
403        locationPanel.add(new JLabel("{x:"));
404        locationPanel.add(xLabel);
405        locationPanel.add(new JLabel(", y:"));
406        locationPanel.add(yLabel);
407        locationPanel.add(new JLabel("}    "));
408
409        locationPanel.addMouseListener(new MouseAdapter() {
410            @Override
411            public void mousePressed(MouseEvent me) {
412                if (me.isPopupTrigger()) {
413                    locationPopupMenu.show(locationPanel, me.getX(), me.getY());
414                }
415            }
416
417            @Override
418            public void mouseReleased(MouseEvent me) {
419                if (me.isPopupTrigger()) {
420                    locationPopupMenu.show(locationPanel, me.getX(), me.getY());
421                }
422            }
423
424            @Override
425            public void mouseClicked(MouseEvent me) {
426                if (me.isPopupTrigger()) {
427                    locationPopupMenu.show(locationPanel, me.getX(), me.getY());
428                }
429            }
430        });
431
432        // second row of edit tool bar items
433        levelXingButton.setToolTipText(Bundle.getMessage("LevelCrossingToolTip"));
434        trackButton.setToolTipText(Bundle.getMessage("TrackSegmentToolTip"));
435        turntableButton.setToolTipText(Bundle.getMessage("AddTurntable"));
436        traverserButton.setToolTipText(Bundle.getMessage("AddTraverser"));
437
438        // this is enabled/disabled via selectionListAction above
439        trackSegmentPropertiesPanel.add(mainlineTrack);
440
441        mainlineTrack.setSelected(false);
442        mainlineTrack.setEnabled(false);
443        mainlineTrack.setToolTipText(Bundle.getMessage("MainlineCheckBoxTip"));
444
445        trackSegmentPropertiesPanel.add(dashedLine);
446        dashedLine.setSelected(false);
447        dashedLine.setEnabled(false);
448        dashedLine.setToolTipText(Bundle.getMessage("DashedCheckBoxTip"));
449
450        // the blockPanel is enabled/disabled via selectionListAction above
451        setupComboBox(blockIDComboBox, false, true, true);
452        blockIDComboBox.setToolTipText(Bundle.getMessage("BlockIDToolTip"));
453
454        highlightBlockCheckBox.setToolTipText(Bundle.getMessage("HighlightSelectedBlockToolTip"));
455        highlightBlockCheckBox.addActionListener((ActionEvent event) -> layoutEditor.setHighlightSelectedBlock(highlightBlockCheckBox.isSelected()));
456        highlightBlockCheckBox.setSelected(layoutEditor.getHighlightSelectedBlock());
457
458        // change the block name
459        blockIDComboBox.addActionListener((ActionEvent event) -> {
460            //use the "Extra" color to highlight the selected block
461            if (layoutEditor.getHighlightSelectedBlock()) {
462                layoutEditor.highlightBlockInComboBox(blockIDComboBox);
463            }
464            String newName = blockIDComboBox.getSelectedItemDisplayName();
465            if (newName == null) {
466                newName = "";
467            }
468            LayoutBlock lb = InstanceManager.getDefault(LayoutBlockManager.class).getByUserName(newName);
469            if (lb != null) {
470                //if there is an occupancy sensor assigned already
471                String sensorName = lb.getOccupancySensorName();
472
473                if (!sensorName.isEmpty()) {
474                    //update the block sensor ComboBox
475                    blockSensorComboBox.setSelectedItem(lb.getOccupancySensor());
476                } else {
477                    blockSensorComboBox.setSelectedItem(null);
478                }
479            } else {
480                blockSensorComboBox.setSelectedItem(null);
481            }
482        });
483
484        setupComboBox(blockSensorComboBox, false, true, false);
485        blockSensorComboBox.setToolTipText(Bundle.getMessage("OccupancySensorToolTip"));
486
487        // third row of edit tool bar items
488        endBumperButton.setToolTipText(Bundle.getMessage("EndBumperToolTip"));
489        anchorButton.setToolTipText(Bundle.getMessage("AnchorToolTip"));
490        edgeButton.setToolTipText(Bundle.getMessage("EdgeConnectorToolTip"));
491        textLabelButton.setToolTipText(Bundle.getMessage("TextLabelToolTip"));
492
493        textLabelTextField.setToolTipText(Bundle.getMessage("TextToolTip"));
494        textLabelTextField.setEnabled(false);
495
496        memoryButton.setToolTipText(Bundle.getMessage("MemoryButtonToolTip", Bundle.getMessage("Memory")));
497
498        setupComboBox(textMemoryComboBox, true, false, false);
499        textMemoryComboBox.setToolTipText(Bundle.getMessage("MemoryToolTip"));
500
501        globalVariableButton.setToolTipText(Bundle.getMessage("GlobalVariableButtonToolTip", Bundle.getMessage("GlobalVariable")));
502
503        setupComboBox(textGlobalVariableComboBox, true, false, false);
504        textGlobalVariableComboBox.setToolTipText(Bundle.getMessage("GlobalVariableToolTip"));
505
506        setupComboBox(textAudioComboBox, true, false, false);
507        textAudioComboBox.setToolTipText(Bundle.getMessage("AudioToolTip"));
508
509        blockContentsButton.setToolTipText(Bundle.getMessage("BlockContentsButtonToolTip"));
510
511        setupComboBox(blockContentsComboBox, true, false, false);
512        blockContentsComboBox.setToolTipText(Bundle.getMessage("BlockContentsButtonToolTip"));
513        blockContentsComboBox.addActionListener((ActionEvent event) -> {
514            // use the "Extra" color to highlight the selected block
515            if (layoutEditor.getHighlightSelectedBlock()) {
516                layoutEditor.highlightBlockInComboBox(blockContentsComboBox);
517            }
518        });
519
520        // fourth row of edit tool bar items
521        // multi sensor...
522        multiSensorButton.setToolTipText(Bundle.getMessage("MultiSensorToolTip"));
523
524        // Signal Mast & text
525        signalMastButton.setToolTipText(Bundle.getMessage("SignalMastButtonToolTip"));
526        setupComboBox(signalMastComboBox, true, false, false);
527
528        // sensor icon & text
529        sensorButton.setToolTipText(Bundle.getMessage("SensorButtonToolTip"));
530
531        setupComboBox(sensorComboBox, true, false, false);
532        sensorComboBox.setToolTipText(Bundle.getMessage("SensorIconToolTip"));
533
534        sensorIconEditor = new MultiIconEditor(4);
535        sensorIconEditor.setIcon(0, Bundle.getMessage("MakeLabel", Bundle.getMessage("SensorStateActive")),
536                "resources/icons/smallschematics/tracksegments/circuit-occupied.gif");
537        sensorIconEditor.setIcon(1, Bundle.getMessage("MakeLabel", Bundle.getMessage("SensorStateInactive")),
538                "resources/icons/smallschematics/tracksegments/circuit-empty.gif");
539        sensorIconEditor.setIcon(2, Bundle.getMessage("MakeLabel", Bundle.getMessage("BeanStateInconsistent")),
540                "resources/icons/smallschematics/tracksegments/circuit-error.gif");
541        sensorIconEditor.setIcon(3, Bundle.getMessage("MakeLabel", Bundle.getMessage("BeanStateUnknown")),
542                "resources/icons/smallschematics/tracksegments/circuit-error.gif");
543        sensorIconEditor.complete();
544
545        // turnout icon & text
546        turnoutButton.setToolTipText(Bundle.getMessage("TurnoutButtonToolTip"));
547
548        setupComboBox(turnoutComboBox, true, false, false);
549        turnoutComboBox.setToolTipText(Bundle.getMessage("TurnoutIconToolTip"));
550
551        turnoutIconEditor = new MultiIconEditor(4);
552        turnoutIconEditor.setIcon(0, Bundle.getMessage("MakeLabel", Bundle.getMessage("TurnoutStateThrown")),
553                "resources/icons/smallschematics/tracksegments/circuit-occupied.gif");
554        turnoutIconEditor.setIcon(1, Bundle.getMessage("MakeLabel", Bundle.getMessage("TurnoutStateClosed")),
555                "resources/icons/smallschematics/tracksegments/circuit-empty.gif");
556        turnoutIconEditor.setIcon(2, Bundle.getMessage("MakeLabel", Bundle.getMessage("BeanStateInconsistent")),
557                "resources/icons/smallschematics/tracksegments/circuit-error.gif");
558        turnoutIconEditor.setIcon(3, Bundle.getMessage("MakeLabel", Bundle.getMessage("BeanStateUnknown")),
559                "resources/icons/smallschematics/tracksegments/circuit-error.gif");
560        turnoutIconEditor.complete();
561
562        // Signal icon & text
563        signalButton.setToolTipText(Bundle.getMessage("SignalButtonToolTip"));
564
565        setupComboBox(signalHeadComboBox, true, false, false);
566        signalHeadComboBox.setToolTipText(Bundle.getMessage("SignalIconToolTip"));
567
568        signalIconEditor = new MultiIconEditor(10);
569        signalIconEditor.setIcon(0, "Red:", "resources/icons/smallschematics/searchlights/left-red-short.gif");
570        signalIconEditor.setIcon(1, "Flash red:", "resources/icons/smallschematics/searchlights/left-flashred-short.gif");
571        signalIconEditor.setIcon(2, "Yellow:", "resources/icons/smallschematics/searchlights/left-yellow-short.gif");
572        signalIconEditor.setIcon(3,
573                "Flash yellow:",
574                "resources/icons/smallschematics/searchlights/left-flashyellow-short.gif");
575        signalIconEditor.setIcon(4, "Green:", "resources/icons/smallschematics/searchlights/left-green-short.gif");
576        signalIconEditor.setIcon(5, "Flash green:",
577                "resources/icons/smallschematics/searchlights/left-flashgreen-short.gif");
578        signalIconEditor.setIcon(6, "Dark:", "resources/icons/smallschematics/searchlights/left-dark-short.gif");
579        signalIconEditor.setIcon(7, "Held:", "resources/icons/smallschematics/searchlights/left-held-short.gif");
580        signalIconEditor.setIcon(8,
581                "Lunar",
582                "resources/icons/smallschematics/searchlights/left-lunar-short-marker.gif");
583        signalIconEditor.setIcon(9,
584                "Flash Lunar",
585                "resources/icons/smallschematics/searchlights/left-flashlunar-short-marker.gif");
586        signalIconEditor.complete();
587
588        sensorFrame = new JFrame(Bundle.getMessage("EditSensorIcons"));
589        sensorFrame.getContentPane().add(new JLabel(Bundle.getMessage("IconChangeInfo")), BorderLayout.NORTH);
590        sensorFrame.getContentPane().add(sensorIconEditor);
591        sensorFrame.pack();
592
593        turnoutFrame = new JFrame(Bundle.getMessage("EditTurnoutIcons"));
594        turnoutFrame.getContentPane().add(new JLabel(Bundle.getMessage("IconChangeInfo")), BorderLayout.NORTH);
595        turnoutFrame.getContentPane().add(turnoutIconEditor);
596        turnoutFrame.pack();
597
598        signalFrame = new JFrame(Bundle.getMessage("EditSignalIcons"));
599        signalFrame.getContentPane().add(new JLabel(Bundle.getMessage("IconChangeInfo")), BorderLayout.NORTH);
600        // no spaces around Label as that breaks html formatting
601        signalFrame.getContentPane().add(signalIconEditor);
602        signalFrame.pack();
603        signalFrame.setVisible(false);
604
605        // icon label
606        iconLabelButton.setToolTipText(Bundle.getMessage("IconLabelToolTip"));
607        logixngButton.setToolTipText(Bundle.getMessage("LogixNGIconToolTip"));
608        audioButton.setToolTipText(Bundle.getMessage("AudioIconToolTip"));
609        shapeButton.setToolTipText(Bundle.getMessage("LayoutShapeToolTip"));
610
611        // change icons...
612        // this is enabled/disabled via selectionListAction above
613        changeIconsButton.addActionListener((ActionEvent event) -> {
614            if (sensorButton.isSelected()) {
615                sensorFrame.setVisible(true);
616            } if (turnoutButton.isSelected()) {
617                turnoutFrame.setVisible(true);
618            } else if (signalButton.isSelected()) {
619                signalFrame.setVisible(true);
620            } else if (iconLabelButton.isSelected()) {
621                iconFrame.setVisible(true);
622            } else if (logixngButton.isSelected()) {
623                logixngFrame.setVisible(true);
624            } else if (audioButton.isSelected()) {
625                audioFrame.setVisible(true);
626            } else {
627                //explain to the user why nothing happens
628                JmriJOptionPane.showMessageDialog(changeIconsButton, Bundle.getMessage("ChangeIconNotApplied"),
629                        Bundle.getMessage("ChangeIcons"), JmriJOptionPane.INFORMATION_MESSAGE);
630            }
631        });
632
633        changeIconsButton.setToolTipText(Bundle.getMessage("ChangeIconToolTip"));
634        changeIconsButton.setEnabled(false);
635
636        // Default icon icon
637        iconEditor = new MultiIconEditor(1);
638        iconEditor.setIcon(0, "", "resources/icons/smallschematics/tracksegments/block.gif");
639        iconEditor.complete();
640        iconFrame = new JFrame(Bundle.getMessage("EditIcon"));
641        iconFrame.getContentPane().add(iconEditor);
642        iconFrame.pack();
643
644        // LogixNG Icon
645        logixngEditor = new MultiIconEditor(1);
646        logixngEditor.setIcon(0, "", "resources/icons/logixng/logixng_icon.gif");
647        logixngEditor.complete();
648        logixngFrame = new JFrame(Bundle.getMessage("EditIcon"));
649        logixngFrame.getContentPane().add(logixngEditor);
650        logixngFrame.pack();
651
652        // Audio Icon
653        audioEditor = new MultiIconEditor(1);
654        audioEditor.setIcon(0, "", "resources/icons/audio_icon.gif");
655        audioEditor.complete();
656        audioFrame = new JFrame(Bundle.getMessage("EditIcon"));
657        audioFrame.getContentPane().add(audioEditor);
658        audioFrame.pack();
659    }
660
661    /*=========================*\
662    |* toolbar location format *|
663    \*=========================*/
664    public enum LocationFormat {
665        ePIXELS,
666        eMETRIC_CM,
667        eENGLISH_FEET_INCHES;
668
669        LocationFormat() {
670        }
671    }
672
673    private LocationFormat locationFormat = LocationFormat.ePIXELS;
674
675    public LocationFormat getLocationFormat() {
676        return locationFormat;
677    }
678
679    public void setLocationFormat(LocationFormat locationFormat) {
680        if (this.locationFormat != locationFormat) {
681            switch (locationFormat) {
682                default:
683                case ePIXELS: {
684                    Dimension coordSize = new JLabel("10000").getPreferredSize();
685                    xLabel.setPreferredSize(coordSize);
686                    yLabel.setPreferredSize(coordSize);
687                    break;
688                }
689                case eMETRIC_CM: {
690                    Dimension coordSize = new JLabel(getMetricCMText(10005)).getPreferredSize();
691                    xLabel.setPreferredSize(coordSize);
692                    yLabel.setPreferredSize(coordSize);
693
694                    layoutEditor.gContext.setGridSize(10);
695                    layoutEditor.gContext.setGridSize2nd(10);
696                    break;
697                }
698                case eENGLISH_FEET_INCHES: {
699                    Dimension coordSize = new JLabel(getEnglishFeetInchesText(100008)).getPreferredSize();
700                    xLabel.setPreferredSize(coordSize);
701                    yLabel.setPreferredSize(coordSize);
702
703                    layoutEditor.gContext.setGridSize(16);
704                    layoutEditor.gContext.setGridSize2nd(12);
705                    break;
706                }
707            }
708            this.locationFormat = locationFormat;
709            InstanceManager.getOptionalDefault(UserPreferencesManager.class).ifPresent((prefsMgr) -> {
710                String windowFrameRef = layoutEditor.getWindowFrameRef();
711                prefsMgr.setProperty(windowFrameRef, "LocationFormat", locationFormat.name());
712            });
713            setLocationText(lastLocation);
714        }
715    }
716
717    private Point2D lastLocation = MathUtil.zeroPoint2D();
718
719    public void setLocationText(Point2D p) {
720        int x = (int) p.getX();
721        int y = (int) p.getY();
722
723        // default behaviour is pixels
724        String xText = Integer.toString(x);
725        String yText = Integer.toString(y);
726
727        if (locationFormat.equals(LocationFormat.eENGLISH_FEET_INCHES)) {
728            xText = getEnglishFeetInchesText(x);
729            yText = getEnglishFeetInchesText(y);
730        } else if (locationFormat.equals(LocationFormat.eMETRIC_CM)) {
731            xText = getMetricCMText(x);
732            yText = getMetricCMText(y);
733        }
734        xLabel.setText(xText);
735        yLabel.setText(yText);
736        lastLocation = p;
737    }
738
739    private String getEnglishFeetInchesText(int v) {
740        String result = "";
741
742        int denom = 16; // 16 pixels per inch
743        int ipf = 12;   // 12 inches per foot
744
745        int feet = v / (ipf * denom);
746        int inches = (v / denom) % ipf;
747
748        int numer = v % denom;
749        int gcd = MathUtil.gcd(numer, denom);
750
751        numer /= gcd;
752        denom /= gcd;
753
754        if (feet > 0) {
755            result = String.format("%d'", feet);
756        }
757
758        boolean inchesFlag = false;
759        if ((v == 0) || (inches > 0)) {
760            result += String.format(" %d", inches);
761            inchesFlag = true;
762        }
763
764        if (numer > 0) {
765            result += String.format(" %d/%d", numer, denom);
766            inchesFlag = true;
767        }
768        if (inchesFlag) {
769            result += "\"";
770        }
771
772        return result;
773    }
774
775    private String getMetricCMText(int v) {
776        return String.format("%d.%d cm", v / 10, v % 10);
777    }
778
779    /**
780     * layout the components in this panel
781     */
782    protected void layoutComponents() {
783        log.error("layoutComponents called in LayoutEditorToolBarPanel base class");
784    }
785
786    final Map<JRadioButton, String> quickKeyMap = new LinkedHashMap<JRadioButton, String>() {
787        {   // NOTE: These are in the order that the space bar will select thru
788            put(turnoutRHButton, Bundle.getMessage("TurnoutRH_QuickKeys"));
789            put(turnoutLHButton, Bundle.getMessage("TurnoutLH_QuickKeys"));
790            put(turnoutWYEButton, Bundle.getMessage("TurnoutWYE_QuickKeys"));
791            put(doubleXoverButton, Bundle.getMessage("DoubleXover_QuickKeys"));
792            put(rhXoverButton, Bundle.getMessage("RHXover_QuickKeys"));
793            put(lhXoverButton, Bundle.getMessage("LHXover_QuickKeys"));
794            put(layoutSingleSlipButton, Bundle.getMessage("LayoutSingleSlip_QuickKeys"));
795            put(layoutDoubleSlipButton, Bundle.getMessage("LayoutDoubleSlip_QuickKeys"));
796            put(levelXingButton, Bundle.getMessage("LevelXing_QuickKeys"));
797            put(trackButton, Bundle.getMessage("TrackSegment_QuickKeys"));
798            put(endBumperButton, Bundle.getMessage("EndBumper_QuickKeys"));
799            put(anchorButton, Bundle.getMessage("Anchor_QuickKeys"));
800            put(edgeButton, Bundle.getMessage("Edge_QuickKeys"));
801            put(textLabelButton, Bundle.getMessage("TextLabel_QuickKeys"));
802            put(memoryButton, Bundle.getMessage("Memory_QuickKeys"));
803            put(globalVariableButton, Bundle.getMessage("GlobalVariable_QuickKeys"));
804            put(blockContentsButton, Bundle.getMessage("BlockContents_QuickKeys"));
805            put(multiSensorButton, Bundle.getMessage("MultiSensor_QuickKeys"));
806            put(sensorButton, Bundle.getMessage("Sensor_QuickKeys"));
807            put(turnoutButton, Bundle.getMessage("Turnout_QuickKeys"));
808            put(signalMastButton, Bundle.getMessage("SignalMast_QuickKeys"));
809            put(signalButton, Bundle.getMessage("Signal_QuickKeys"));
810            put(iconLabelButton, Bundle.getMessage("IconLabel_QuickKeys"));
811            put(logixngButton, Bundle.getMessage("LogixNGIcon_QuickKeys"));
812            put(audioButton, Bundle.getMessage("AudioIcon_QuickKeys"));
813            put(shapeButton, Bundle.getMessage("Shape_QuickKeys"));
814        }
815    };
816
817    public void keyPressed(@Nonnull KeyEvent event) {
818        if (layoutEditor.isEditable()) {
819            if (!event.isMetaDown() && !event.isAltDown() && !event.isControlDown()) {
820                if (event.getID() == KEY_PRESSED) {
821                    char keyChar = event.getKeyChar();
822                    String keyString = String.valueOf(keyChar);
823                    log.trace("KeyEvent.getKeyChar() == {}", KeyEvent.getKeyText(keyChar));
824
825                    // find last radio button
826                    JRadioButton lastRadioButton = null;
827                    for (Map.Entry<JRadioButton, String> entry : quickKeyMap.entrySet()) {
828                        JRadioButton thisRadioButton = entry.getKey();
829                        if (thisRadioButton.isSelected()) {
830                            lastRadioButton = thisRadioButton;
831                            log.trace("lastRadioButton is {}", lastRadioButton.getText());
832                            break;
833                        }
834                    }
835
836                    JRadioButton firstRadioButton = null;   // the first one that matches
837                    JRadioButton nextRadioButton = null;    // the next one to select
838                    boolean foundLast = false;
839                    for (Map.Entry<JRadioButton, String> entry : quickKeyMap.entrySet()) {
840                        String quickKeys = entry.getValue();
841                        if (keyString.equals(" ") || StringUtils.containsAny(keyString, quickKeys)) {    // found keyString
842                            JRadioButton thisRadioButton = entry.getKey();
843                            log.trace("Matched keyString to {}", thisRadioButton.getText());
844                            if (foundLast) {
845                                nextRadioButton = thisRadioButton;
846                                break;
847                            } else if (lastRadioButton == thisRadioButton) {
848                                foundLast = true;
849                            } else if (firstRadioButton == null) {
850                                firstRadioButton = thisRadioButton;
851                            }
852                        }
853                    }
854                    // if we didn't find the next one...
855                    if (nextRadioButton == null) {
856                        // ...then use the first one
857                        nextRadioButton = firstRadioButton;
858                    }
859                    // if we found one...
860                    if (nextRadioButton != null) {
861                        // ...then select it
862                        nextRadioButton.setSelected(true);
863                    }
864                }   // if KEY_PRESSED event
865            }   // if no modifier keys pressed
866        }   // if is in edit mode
867    }
868
869    @Override
870    public void dispose() {
871        if (sensorFrame != null) {
872            sensorFrame.dispose();
873            sensorFrame = null;
874        }
875        if (turnoutFrame != null) {
876            turnoutFrame.dispose();
877            turnoutFrame = null;
878        }
879        if (signalFrame != null) {
880            signalFrame.dispose();
881            signalFrame = null;
882        }
883        if (iconFrame != null) {
884            iconFrame.dispose();
885            iconFrame = null;
886        }
887        if (logixngFrame != null) {
888            logixngFrame.dispose();
889            logixngFrame = null;
890        }
891        if (audioFrame != null) {
892            audioFrame.dispose();
893            audioFrame = null;
894        }
895    }
896
897    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LayoutEditorToolBarPanel.class);
898
899}