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