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