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