001package jmri.jmrit.display.switchboardEditor;
002
003import java.awt.*;
004import java.awt.event.*;
005import java.util.*;
006import java.util.List;
007
008import javax.annotation.Nonnull;
009import javax.swing.*;
010import javax.swing.border.TitledBorder;
011
012import jmri.*;
013import jmri.jmrit.display.CoordinateEdit;
014import jmri.jmrit.display.Editor;
015import jmri.jmrit.display.Positionable;
016import jmri.jmrit.display.PositionableJComponent;
017import jmri.jmrix.SystemConnectionMemoManager;
018import jmri.swing.ManagerComboBox;
019import jmri.util.ColorUtil;
020import jmri.util.JmriJFrame;
021import jmri.util.ThreadingUtil;
022import jmri.util.swing.JmriColorChooser;
023import jmri.util.swing.JmriJOptionPane;
024import jmri.util.swing.JmriMouseEvent;
025import jmri.util.swing.JmriMouseAdapter;
026import jmri.util.swing.JmriMouseListener;
027import jmri.util.swing.JmriMouseMotionListener;
028
029import static jmri.util.ColorUtil.contrast;
030
031/**
032 * Provides a simple editor for adding jmri.jmrit.display.switchBoard items to a
033 * JLayeredPane inside a captive JFrame. Primary use is for new users.
034 * <p>
035 * GUI is structured as a separate setup panel to set the visible range and type
036 * plus menus.
037 * <p>
038 * All created objects are placed in a GridLayout grid. No special use of the
039 * LayeredPane layers. Inspired by Oracle JLayeredPane demo.
040 * <p>
041 * The "switchesOnBoard" LinkedHashMap keeps track of all the objects added to the target
042 * frame for later manipulation. May be used in an update to store mixed
043 * switchboards with more than 1 connection and more than 1 bean type/range.<br>
044 * The 'ready' flag protects the map during regeneration.
045 * <p>
046 * No DnD as panels will be automatically populated in order of the DCC address.
047 * New beans may be created from the Switchboard by right clicking an
048 * unconnected switch.
049 * TODO allow user entry of connection specific starting name, validated in manager
050 * using hardwareAddressValidator
051 *
052 * @author Pete Cressman Copyright (c) 2009, 2010, 2011
053 * @author Egbert Broerse Copyright (c) 2017, 2018, 2021
054 */
055public class SwitchboardEditor extends Editor {
056
057    protected JMenuBar _menuBar;
058    private JMenu _editorMenu;
059    //protected JMenu _editMenu;
060    protected JMenu _fileMenu;
061    protected JMenu _optionMenu;
062    private transient boolean panelChanged = false;
063
064    // Switchboard items
065    ImageIcon iconPrev = new ImageIcon("resources/icons/misc/gui3/LafLeftArrow_m.gif");
066    private final JLabel prev = new JLabel(iconPrev);
067    ImageIcon iconNext = new ImageIcon("resources/icons/misc/gui3/LafRightArrow_m.gif");
068    private final JLabel next = new JLabel(iconNext);
069    private final int rangeBottom = 1;
070    private final int rangeTop = 100000; // for MERG etc where thousands = node number, total number on board limited to unconnectedRangeLimit anyway
071    private final static int unconnectedRangeLimit = 400;
072    private final static int rangeSizeWarning = 250;
073    private final static int initialMax = 24;
074    private final JSpinner minSpinner = new JSpinner(new SpinnerNumberModel(rangeBottom, rangeBottom, rangeTop - 1, 1));
075    private final JSpinner maxSpinner = new JSpinner(new SpinnerNumberModel(initialMax, rangeBottom + 1, rangeTop, 1));
076    private final JCheckBox hideUnconnected = new JCheckBox(Bundle.getMessage("CheckBoxHideUnconnected"));
077    private final JCheckBox autoItemRange = new JCheckBox(Bundle.getMessage("CheckBoxAutoItemRange"));
078    private JButton allOffButton;
079    private JButton allOnButton;
080    private TargetPane switchboardLayeredPane; // is a JLayeredPane
081    static final String TURNOUT = Bundle.getMessage("Turnouts");
082    static final String SENSOR = Bundle.getMessage("Sensors");
083    static final String LIGHT = Bundle.getMessage("Lights");
084    private final String[] beanTypeStrings = {TURNOUT, SENSOR, LIGHT};
085    private JComboBox<String> beanTypeList;
086    private String _type = TURNOUT;
087    private final String[] switchShapeStrings = {
088        Bundle.getMessage("Buttons"),
089        Bundle.getMessage("Sliders"),
090        Bundle.getMessage("Keys"),
091        Bundle.getMessage("Symbols")
092    };
093    private JComboBox<String> shapeList;
094    final static int BUTTON = 0;
095    final static int SLIDER = 1;
096    final static int KEY = 2;
097    final static int SYMBOL = 3;
098    //final static int ICON = 4;
099    private final ManagerComboBox<Turnout> turnoutManComboBox = new ManagerComboBox<>();
100    private final ManagerComboBox<Sensor> sensorManComboBox = new ManagerComboBox<>();
101    private final ManagerComboBox<Light> lightManComboBox = new ManagerComboBox<>();
102    protected TurnoutManager turnoutManager = InstanceManager.getDefault(TurnoutManager.class);
103    protected SensorManager sensorManager = InstanceManager.getDefault(SensorManager.class);
104    protected LightManager lightManager = InstanceManager.getDefault(LightManager.class);
105    private SystemConnectionMemo memo;
106    private int shape = BUTTON; // for: button
107    //SystemNameValidator hardwareAddressValidator;
108    JTextField addressTextField = new JTextField(10);
109    private TitledBorder border;
110    private final String interact = Bundle.getMessage("SwitchboardInteractHint");
111    private final String noInteract = Bundle.getMessage("SwitchboardNoInteractHint");
112
113    // editor items (adapted from LayoutEditor toolbar)
114    private Color defaultTextColor = Color.BLACK;
115    private Color defaultActiveColor = Color.RED; // user configurable since 4.21.3
116    protected final static Color darkActiveColor = new Color(180, 50, 50);
117    private Color defaultInactiveColor = Color.GREEN; // user configurable since 4.21.3
118    protected final static Color darkInactiveColor = new Color(40, 150, 30);
119    private boolean _hideUnconnected = false;
120    private boolean _autoItemRange = true;
121    private int rows = 4; // matches initial autoRows pref for default pane size
122    private final float cellProportion = 1.0f; // TODO analyse actual W:H per switch type/shape: worthwhile?
123    private int _tileSize = 100;
124    private int _iconSquare = 75;
125    private SwitchBoardLabelDisplays _showUserName = SwitchBoardLabelDisplays.BOTH_NAMES;
126    // tmp @GuardedBy("this")
127    private final JSpinner rowsSpinner = new JSpinner(new SpinnerNumberModel(rows, 1, 25, 1));
128    private final JButton updateButton = new JButton(Bundle.getMessage("ButtonUpdate"));
129    // number of rows displayed on switchboard, disabled when autoRows is on
130    private final JTextArea help2 = new JTextArea(Bundle.getMessage("Help2"));
131    private final JTextArea help3 = new JTextArea(Bundle.getMessage("Help3", Bundle.getMessage("CheckBoxHideUnconnected")));
132    // saved state of options when panel was loaded or created
133    private transient boolean savedEditMode = true;
134    private transient boolean savedControlLayout = true; // menu option to turn this off
135    private final int height = 455;
136    private final int width = 544;
137    private int verticalMargin = 55; // for Nimbus and CDE/Motif
138
139    private final JCheckBoxMenuItem controllingBox = new JCheckBoxMenuItem(Bundle.getMessage("CheckBoxControlling"));
140    private final JCheckBoxMenuItem hideUnconnectedBox = new JCheckBoxMenuItem(Bundle.getMessage("CheckBoxHideUnconnected"));
141    private final JCheckBoxMenuItem autoItemRangeBox = new JCheckBoxMenuItem(Bundle.getMessage("CheckBoxAutoItemRange"));
142    private final JCheckBoxMenuItem showToolTipBox = new JCheckBoxMenuItem(Bundle.getMessage("CheckBoxShowTooltips"));
143    //tmp @GuardedBy("this")
144    private final JCheckBoxMenuItem autoRowsBox = new JCheckBoxMenuItem(Bundle.getMessage("CheckBoxAutoRows"));
145    private final JMenu labelNamesMenu = new JMenu(Bundle.getMessage("SwitchNameDisplayMenu"));
146    private final JCheckBoxMenuItem systemNameBox = new JCheckBoxMenuItem(Bundle.getMessage("CheckBoxSystemName"));
147    private final JCheckBoxMenuItem bothNamesBox = new JCheckBoxMenuItem(Bundle.getMessage("CheckBoxBothNames"));
148    private final JCheckBoxMenuItem displayNameBox = new JCheckBoxMenuItem(Bundle.getMessage("CheckBoxDisplayName"));
149    private final JRadioButtonMenuItem scrollBoth = new JRadioButtonMenuItem(Bundle.getMessage("ScrollBoth"));
150    private final JRadioButtonMenuItem scrollNone = new JRadioButtonMenuItem(Bundle.getMessage("ScrollNone"));
151    private final JRadioButtonMenuItem scrollHorizontal = new JRadioButtonMenuItem(Bundle.getMessage("ScrollHorizontal"));
152    private final JRadioButtonMenuItem scrollVertical = new JRadioButtonMenuItem(Bundle.getMessage("ScrollVertical"));
153    private final JRadioButtonMenuItem sizeSmall = new JRadioButtonMenuItem(Bundle.getMessage("optionSmaller"));
154    private final JRadioButtonMenuItem sizeDefault = new JRadioButtonMenuItem(Bundle.getMessage("optionDefault"));
155    private final JRadioButtonMenuItem sizeLarge = new JRadioButtonMenuItem(Bundle.getMessage("optionLarger"));
156    final static int SIZE_MIN = 50;
157    final static int SIZE_INIT = 100;
158    final static int SIZE_MAX = 150;
159
160    /**
161     * To count number of displayed beanswitches, this array holds all beanswitches to be displayed
162     * until the GridLayout is configured, used to determine the total number of items to be placed.
163     * Accounts for "hide unconnected" setting, so it can be empty. Not synchronized for risk of locking up.
164     */
165    private final LinkedHashMap<String, BeanSwitch> switchesOnBoard = new LinkedHashMap<>();
166    private volatile boolean ready = true;
167
168    /**
169     * Ctor
170     */
171    public SwitchboardEditor() {
172    }
173
174    /**
175     * Ctor by a given name.
176     *
177     * @param name title to assign to the new SwitchBoard
178     */
179    public SwitchboardEditor(String name) {
180        super(name, false, true);
181        init(name);
182    }
183
184    /**
185     * Initialize the newly created Switchboard.
186     *
187     * @param name the title of the switchboard content frame
188     */
189    @Override
190    protected final void init(String name) {
191        //memo = SystemConnectionMemoManager.getDefault().getSystemConnectionMemoForUserName("Internal");
192        // always available (?) and supports all types, not required now, will be set by listener
193
194        Container contentPane = getContentPane(); // the actual Editor configuration pane
195        ThreadingUtil.runOnGUI( () -> setVisible(false)); // start with Editor window hidden
196        setUseGlobalFlag(true); // always true for a Switchboard
197        // handle Editor close box clicked without deleting the Switchboard panel
198        super.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
199        super.addWindowListener(new java.awt.event.WindowAdapter() {
200            @Override
201            public void windowClosing(java.awt.event.WindowEvent e) {
202                log.debug("switchboardEditor close box selected");
203                setAllEditable(false);
204                ThreadingUtil.runOnGUI( () -> setVisible(false)); // hide Editor window
205            }
206        });
207        // make menus
208        _menuBar = new JMenuBar();
209        makeOptionMenu();
210        makeFileMenu();
211
212        setJMenuBar(_menuBar);
213        addHelpMenu("package.jmri.jmrit.display.SwitchboardEditor", true);
214        // set GUI dependant margin if not Nimbus, CDE/Motif (or undefined)
215        if (UIManager.getLookAndFeel() != null) {
216            if (UIManager.getLookAndFeel().getName().equals("Metal")) {
217                verticalMargin = 47;
218            } else if (UIManager.getLookAndFeel().getName().equals("Mac OS X")) {
219                verticalMargin = 25;
220            }
221        }
222        switchboardLayeredPane = new TargetPane(); // extends JLayeredPane();
223        switchboardLayeredPane.setPreferredSize(new Dimension(width, height));
224        border = BorderFactory.createTitledBorder(
225                BorderFactory.createLineBorder(defaultTextColor),
226                "temp",
227                TitledBorder.LEADING,
228                TitledBorder.ABOVE_BOTTOM,
229                getFont(),
230                defaultTextColor);
231        switchboardLayeredPane.setBorder(border);
232        // create contrast with background, should also specify border style
233        // specify title for turnout, sensor, light, mixed? (wait for the Editor to be created)
234        switchboardLayeredPane.addMouseMotionListener(JmriMouseMotionListener.adapt(this));
235
236        // add control pane and layered pane to this JPanel
237        JPanel beanSetupPane = new JPanel();
238        beanSetupPane.setLayout(new FlowLayout(FlowLayout.TRAILING));
239        JLabel beanTypeTitle = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("BeanTypeLabel")));
240        beanSetupPane.add(beanTypeTitle);
241        beanTypeList = new JComboBox<>(beanTypeStrings);
242        beanTypeList.setSelectedIndex(0); // select bean type T in comboBox
243        beanTypeList.addActionListener((ActionEvent event) -> {
244            String typeChoice = (String) beanTypeList.getSelectedItem();
245            if (typeChoice != null) {
246                displayManagerComboBoxes(typeChoice); // so these boxes should already be instantiated by now
247            }
248            updatePressed();
249            setDirty();
250        });
251        beanSetupPane.add(beanTypeList);
252
253        // add connection selection comboBox
254        char beanTypeChar = getSwitchType().charAt(0); // translate from selectedIndex to char
255        log.debug("beanTypeChar set to [{}]", beanTypeChar);
256        JLabel beanManuTitle = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("ConnectionLabel")));
257        beanSetupPane.add(beanManuTitle);
258
259        beanSetupPane.add(turnoutManComboBox);
260        beanSetupPane.add(sensorManComboBox);
261        beanSetupPane.add(lightManComboBox);
262
263        turnoutManComboBox.setToolTipText(Bundle.getMessage("ManComboBoxTip", Bundle.getMessage("BeanNameTurnout")));
264        sensorManComboBox.setToolTipText(Bundle.getMessage("ManComboBoxTip", Bundle.getMessage("BeanNameSensor")));
265        lightManComboBox.setToolTipText(Bundle.getMessage("ManComboBoxTip", Bundle.getMessage("BeanNameLight")));
266
267        configureManagerComboBoxes(); // fill the combos
268        displayManagerComboBoxes(TURNOUT); // show TurnoutManagerBox (matches the beanType combo
269
270//        hardwareAddressValidator = new SystemNameValidator(addressTextField,
271//                turnoutManComboBox.getItemAt(0),
272//                false); // initial system (for type Turnout)
273//        addressTextField.setInputVerifier(hardwareAddressValidator);
274
275//        hardwareAddressValidator.addPropertyChangeListener("validation", (evt) -> { // NOI18N
276//            Validation validation = hardwareAddressValidator.getValidation();
277//            Validation.Type valid = validation.getType();
278//            updateButton.setEnabled(valid != Validation.Type.WARNING && valid != Validation.Type.DANGER);
279//            help2.setText(validation.getMessage());
280//        });
281//        hardwareAddressValidator.setManager(turnoutManComboBox.getItemAt(0)); // initial system (for type Turnout)
282//        hardwareAddressValidator.verify(addressTextField);
283
284        add(beanSetupPane);
285
286        // add shape combobox
287        JPanel switchShapePane = new JPanel();
288        switchShapePane.setLayout(new FlowLayout(FlowLayout.TRAILING));
289        JLabel switchShapeTitle = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("SwitchShape")));
290        switchShapePane.add(switchShapeTitle);
291        shapeList = new JComboBox<>(switchShapeStrings);
292        shapeList.setSelectedIndex(0); // select Button choice in comboBox
293        shapeList.addActionListener((ActionEvent event) -> {
294            shape = (Math.max(shapeList.getSelectedIndex(), 0)); // picks 1st item when no selection
295            updatePressed();
296            setDirty();
297        });
298        switchShapePane.add(shapeList);
299        // add column spinner
300        JLabel rowsLabel = new JLabel(Bundle.getMessage("NumberOfRows"));
301        switchShapePane.add(rowsLabel);
302        rowsSpinner.setToolTipText(Bundle.getMessage("RowsSpinnerOnTooltip"));
303        rowsSpinner.addChangeListener(e -> {
304            //tmp synchronized (this) {
305                if (!autoRowsBox.isSelected()) { // spinner is disabled when autoRows is on, but just in case
306                    rows = (Integer) rowsSpinner.getValue();
307                    updatePressed();
308                    setDirty();
309                }
310            //tmp }
311        });
312        switchShapePane.add(rowsSpinner);
313        rowsSpinner.setEnabled(false);
314        add(switchShapePane);
315
316        JPanel checkboxPane = new JPanel();
317        checkboxPane.setLayout(new FlowLayout(FlowLayout.TRAILING));
318        // autoItemRange checkbox on panel
319        autoItemRange.setSelected(autoItemRange());
320        log.debug("autoItemRangeBox set to {}", autoItemRange.isSelected());
321        autoItemRange.addActionListener((ActionEvent event) -> {
322            setAutoItemRange(autoItemRange.isSelected());
323            autoItemRangeBox.setSelected(autoItemRange()); // also (un)check the box on the menu
324            // if set to checked, store the current range from the spinners
325        });
326        checkboxPane.add(autoItemRange);
327        autoItemRange.setToolTipText(Bundle.getMessage("AutoItemRangeTooltip"));
328        // hideUnconnected checkbox on panel
329        hideUnconnected.setSelected(_hideUnconnected);
330        log.debug("hideUnconnectedBox set to {}", hideUnconnected.isSelected());
331        hideUnconnected.addActionListener((ActionEvent event) -> {
332            setHideUnconnected(hideUnconnected.isSelected());
333            hideUnconnectedBox.setSelected(_hideUnconnected); // also (un)check the box on the menu
334            help2.setVisible(!_hideUnconnected && (!switchesOnBoard.isEmpty())); // and show/hide instruction line unless no items on board
335            updatePressed();
336            setDirty();
337        });
338        checkboxPane.add(hideUnconnected);
339        add(checkboxPane);
340
341        /* Construct special JFrame to hold the actual switchboard */
342        switchboardLayeredPane.setLayout(new GridLayout(3, 8)); // initial layout params
343        // Add at least 1 switch to pane to create switchList: done later, would be deleted soon if added now
344        // see updatePressed()
345
346        // provide a JLayeredPane to place the switches on
347        super.setTargetPanel(switchboardLayeredPane, makeFrame(name));
348        super.getTargetFrame().setSize(550, 430); // width x height //+ was 550, 330
349        //super.getTargetFrame().setSize(width + 6, height + 25); // width x height
350
351        // set scrollbar initial state
352        setScroll(SCROLL_NONE);
353        scrollNone.setSelected(true);
354        // set icon size initial state
355        _iconSquare = SIZE_INIT;
356        sizeDefault.setSelected(true);
357        // register the resulting panel for later configuration
358        ConfigureManager cm = InstanceManager.getNullableDefault(ConfigureManager.class);
359        if (cm != null) {
360            cm.registerUser(this);
361        }
362
363        //add(addressTextField);
364        add(createControlPanel());
365
366        updateButton.addActionListener((ActionEvent event) -> {
367            updatePressed();
368            setDirty();
369        });
370        allOnButton = new JButton(Bundle.getMessage("AllOn"));
371        allOnButton.addActionListener((ActionEvent event) -> switchAllLights(Light.ON));
372        allOffButton = new JButton(Bundle.getMessage("AllOff"));
373        allOffButton.addActionListener((ActionEvent event) -> switchAllLights(Light.OFF));
374        JPanel allPane = new JPanel();
375        allPane.setLayout(new BoxLayout(allPane, BoxLayout.PAGE_AXIS));
376        allPane.add(allOnButton);
377        allPane.add(allOffButton);
378
379        JPanel updatePanel = new JPanel();
380        updatePanel.add(updateButton);
381        updatePanel.add(allPane);
382
383        contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.PAGE_AXIS));
384        contentPane.add(updatePanel);
385        setupEditorPane(); // re-layout all the toolbar items
386
387        lightManComboBox.addActionListener((ActionEvent event) -> {
388            Manager<Light> manager = lightManComboBox.getSelectedItem();
389            if (manager != null) {
390                memo = manager.getMemo();
391                addressTextField.setText("");     // Reset input before switching managers
392                //hardwareAddressValidator.setManager(manager);
393                log.debug("Lbox set to {}. Updating", memo.getUserName());
394                updatePressed();
395                setDirty();
396            }
397        });
398        sensorManComboBox.addActionListener((ActionEvent event) -> {
399            Manager<Sensor> manager = sensorManComboBox.getSelectedItem();
400            if (manager != null) {
401                memo = manager.getMemo();
402                addressTextField.setText("");     // Reset input before switching managers
403                //hardwareAddressValidator.setManager(manager);
404                log.debug("Sbox set to {}. Updating", memo.getUserName());
405                updatePressed();
406                setDirty();
407            }
408        });
409        turnoutManComboBox.addActionListener((ActionEvent event) -> {
410            Manager<Turnout> manager = turnoutManComboBox.getSelectedItem();
411            if (manager != null) {
412                memo = manager.getMemo();
413                addressTextField.setText("");     // Reset input before switching managers
414                //hardwareAddressValidator.setManager(manager);
415                log.debug("Tbox set to {}. Updating", memo.getUserName());
416                updatePressed();
417                setDirty();
418            }
419        });
420        turnoutManComboBox.setSelectedItem("Internal"); // order of items in combo may vary on init() wait till now for init completed
421        lightManComboBox.setSelectedItem("Internal"); // NOI18N
422        sensorManComboBox.setSelectedItem("Internal"); // NOI18N
423        log.debug("boxes are set to Internal, attaching listeners");
424
425        updatePressed(); // refresh default Switchboard, rebuilds and resizes all switches, required for tests
426
427        // component listener handles frame resizing event
428        super.getTargetFrame().addComponentListener(new ComponentAdapter() {
429            @Override
430            public void componentResized(ComponentEvent e) {
431                //log.debug("PANEL RESIZED");
432                resizeInFrame();
433            }
434        });
435    }
436
437    /**
438     * Just repaint the Switchboard target panel.
439     * Fired on componentResized(e) event.
440     */
441    private void resizeInFrame() {
442        Dimension frSize = super.getTargetFrame().getSize(); // 5 px for border, var px for footer, autoRows(int)
443        // some GUIs include (wide) menu bar inside frame
444        switchboardLayeredPane.setSize(new Dimension((int) frSize.getWidth() - 6, (int) frSize.getHeight() - verticalMargin));
445        switchboardLayeredPane.repaint();
446        //tmp synchronized (this) {
447            if (autoRowsBox.isSelected()) { // check if autoRows is active
448                int oldRows = rows;
449                rows = autoRows(cellProportion); // if it suggests a different value for rows, call updatePressed()
450                if (rows != oldRows) {
451                    rowsSpinner.setValue(rows); // updatePressed will update rows spinner in display, but spinner will not propagate when disabled
452                    updatePressed(); // redraw if rows value changed
453                }
454            }
455        //tmp }
456    }
457
458    /**
459     * Create a new set of switches after removing the current array.
460     * <p>
461     * Called by Update button click, and automatically after loading a panel
462     * from XML (with all saved options set).
463     * Switchboard JPanel WindowResize() event is handled by resizeInFrame()
464     */
465    public void updatePressed() {
466        ThreadingUtil.runOnGUI(this::updatePressedOnGui);
467    }
468
469    private void updatePressedOnGui() {
470        log.debug("updatePressed START _tileSize = {}", _tileSize);
471
472        if (_autoItemRange && !autoItemRange.isSelected()) {
473            autoItemRange.setSelected(true);
474        }
475        setVisible(_editable); // show/hide editor
476
477        // update selected address range
478        int range = (Integer) maxSpinner.getValue() - (Integer) minSpinner.getValue() + 1;
479        if (range > unconnectedRangeLimit && !_hideUnconnected) {
480            // fixed maximum number of items on a Switchboard to prevent memory overflow
481            range = unconnectedRangeLimit;
482            maxSpinner.setValue((Integer) minSpinner.getValue() + range - 1);
483        }
484        // check for extreme number of items
485        log.debug("address range = {}", range);
486        if (range > rangeSizeWarning) {
487            // ask user if range is indeed desired
488            log.debug("Warning for big range");
489            int retval = JmriJOptionPane.showOptionDialog(this,
490                    Bundle.getMessage("LargeRangeWarning", range, Bundle.getMessage("CheckBoxHideUnconnected")),
491                    Bundle.getMessage("WarningTitle"), JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.INFORMATION_MESSAGE, null,
492                    new Object[]{Bundle.getMessage("ButtonYes"), Bundle.getMessage("ButtonCancel")}, null);
493            log.debug("Retval: {}", retval);
494            if (retval != 0) { // NOT array position 0 ButtonYes
495                return;
496            }
497        }
498        ready = false; // set flag for updating
499        // if range is confirmed, go ahead with switchboard update
500        for (int i = switchesOnBoard.size() - 1; i >= 0; i--) {
501            //            if (i >= switchboardLayeredPane.getComponentCount()) { // turn off this check for now
502            //                continue;
503            //            }
504            // remove listeners before removing switches from JLayeredPane
505            ((BeanSwitch) switchboardLayeredPane.getComponent(i)).cleanup();
506            // deleting items starting from 0 will result in skipping the even numbered items
507            switchboardLayeredPane.remove(i);
508        }
509        switchesOnBoard.clear(); // reset beanswitches LinkedHashMap
510        log.debug("switchesOnBoard cleared, size is now: 0"); // always 0 at this point
511        switchboardLayeredPane.setSize(width, height);
512
513        String memoName = (memo != null ? memo.getUserName() : "UNKNOWN");
514        log.debug("creating range for manu index {}", memoName);
515
516//        Validation.Type valid = hardwareAddressValidator.getValidation().getType();
517        String startAddress = "";
518//        if (addressTextField.getText() != null && valid != Validation.Type.WARNING && valid != Validation.Type.DANGER) {
519//            startAddress = addressTextField.getText();
520//        }
521        // fill switchesOnBoard LinkedHashMap, uses memo/manager already set
522        createSwitchRange((Integer) minSpinner.getValue(),
523                (Integer) maxSpinner.getValue(),
524                beanTypeList.getSelectedIndex(),
525                shapeList.getSelectedIndex(),
526                startAddress);
527
528        if (autoRowsBox.isSelected()) {
529            rows = autoRows(cellProportion); // TODO: use specific proportion value per Type/Shape choice?
530            log.debug("autoRows() called in updatePressed(). Rows = {}", rows);
531            rowsSpinner.setValue(rows);
532        }
533        // disable the Rows spinner & Update button on the Switchboard Editor pane
534        // param: GridLayout(vertical, horizontal), at least 1x1
535        switchboardLayeredPane.setLayout(new GridLayout(Math.max(rows, 1), 1));
536
537        // add switches to LayeredPane
538        for (BeanSwitch bs : switchesOnBoard.values()) {
539            switchboardLayeredPane.add(bs);
540        }
541        ready = true; // reset flag
542        help3.setVisible(switchesOnBoard.isEmpty()); // show/hide help3 warning
543        help2.setVisible(!switchesOnBoard.isEmpty()); // hide help2 when help3 is shown vice versa (as no items are dimmed or not)
544        // update the title at the bottom of the switchboard to match (no) layout control
545        if (beanTypeList.getSelectedIndex() >= 0) {
546            border.setTitle(memoName + " " +
547                    beanTypeList.getSelectedItem() + " - " + (allControlling() ? interact : noInteract));
548        }
549        // hide AllOn/Off buttons unless type is Light and control is allowed
550        allOnButton.setVisible((beanTypeList.getSelectedIndex() == 2) && allControlling());
551        allOffButton.setVisible((beanTypeList.getSelectedIndex() == 2) && allControlling());
552        pack();
553        // must repaint again to fit inside frame
554        Dimension frSize = super.getTargetFrame().getSize(); // 2x3 px for border, var px for footer + optional UI menubar, autoRows(int)
555        switchboardLayeredPane.setSize(new Dimension((int) frSize.getWidth() - 6, (int) frSize.getHeight() - verticalMargin));
556        switchboardLayeredPane.repaint();
557
558        log.debug("updatePressed END _tileSize = {}", _tileSize);
559    }
560
561    /**
562     * From default or user entry in Editor, create a LinkedHashMap of Switches.
563     * <p>
564     * Items in range that can connect to existing beans in JMRI are active. The
565     * others are greyed out. Option to later connect (new) beans to switches.
566     *
567     * @param min         starting ordinal of Switch address range
568     * @param max         highest ordinal of Switch address range
569     * @param beanType    index of selected item in Type comboBox, either T, S
570     *                    or L
571     * @param shapeChoice index of selected visual presentation of Switch shape
572     *                    selected in Type comboBox, choose either a JButton
573     *                    showing the name or (to do) a graphic image
574     */
575    private void createSwitchRange(int min, int max, int beanType, int shapeChoice, @Nonnull String startAddress) {
576        log.debug("createSwitchRange - _hideUnconnected = {}", _hideUnconnected);
577        String name;
578        BeanSwitch _switch;
579        NamedBean nb;
580        if (memo == null) {
581            log.error("createSwitchRange - null memo, can't create range");
582            return;
583        }
584        String prefix = memo.getSystemPrefix();
585        // TODO handling of non-numeric system names such as MERG, C/MRI using validator textField
586        // if (!startAddress.equals("")) { // use as start address, spinners are only for the number of items
587        log.debug("createSwitchRange - _manuprefix={} beanType={}", prefix, beanType);
588        // use validated bean names
589        for (int i = min; i <= max; i++) {
590            switch (beanType) {
591                case 0:
592                    try {
593                        name = memo.get(TurnoutManager.class).createSystemName(i + "", prefix);
594                    } catch (JmriException ex) {
595                        log.error("Error creating range at turnout {}", i);
596                        return;
597                    }
598                    nb = InstanceManager.getDefault(TurnoutManager.class).getTurnout(name);
599                    break;
600                case 1:
601                    try { // was: InstanceManager.getDefault(SensorManager.class)
602                        name = memo.get(SensorManager.class).createSystemName(i + "", prefix);
603                    } catch (JmriException | NullPointerException ex) {
604                        log.trace("Error creating range at sensor {}. Connection {}", i, memo.getUserName(), ex);
605                        return;
606                    }
607                    nb = InstanceManager.getDefault(SensorManager.class).getSensor(name);
608                    break;
609                case 2:
610                    try {
611                        name = memo.get(LightManager.class).createSystemName(i + "", prefix);
612                    } catch (JmriException ex) {
613                        log.error("Error creating range at light {}", i);
614                        return;
615                    }
616                    nb = InstanceManager.lightManagerInstance().getLight(name);
617                    break;
618                default:
619                    log.error("addSwitchRange: cannot parse bean name. Prefix = {}; i = {}; type={}", prefix, i, beanType);
620                    return;
621            }
622            if (nb == null && _hideUnconnected) {
623                continue; // skip bean i
624            }
625            log.debug("Creating Switch for {}", name);
626            _switch = new BeanSwitch(i, nb, name, shapeChoice, this); // add button instance i
627            if (nb == null) {
628                _switch.setEnabled(false); // not connected
629            } else {
630                // set switch to display current bean state
631                _switch.displayState(nb.getState());
632            }
633            switchesOnBoard.put(name, _switch); // add to LinkedHashMap of switches for later placement on JLayeredPane
634            log.debug("Added switch {}", name);
635            // keep total number of switches below practical total of 400 (20 x 20 items)
636            if (switchesOnBoard.size() >= unconnectedRangeLimit) {
637                log.warn("switchboards are limited to {} items", unconnectedRangeLimit);
638                break;
639            }
640            // was already checked in first counting loop in init()
641        }
642    }
643
644    /**
645     * Create the setup pane for the top of the frame. From layeredpane demo.
646     */
647    private JPanel createControlPanel() {
648        JPanel controls = new JPanel();
649
650        // navigation top row and range to set
651        JPanel navBarPanel = new JPanel();
652        navBarPanel.setLayout(new BoxLayout(navBarPanel, BoxLayout.X_AXIS));
653
654        navBarPanel.add(prev);
655        prev.addMouseListener(JmriMouseListener.adapt(new JmriMouseAdapter() {
656            @Override
657            public void mouseClicked(JmriMouseEvent e) {
658                int oldMin = getMinSpinner();
659                int oldMax = getMaxSpinner();
660                int range = Math.max(oldMax - oldMin + 1, 1); // make sure range > 0
661                log.debug("prev range was {}, oldMin ={}, oldMax ={}", range, oldMin, oldMax);
662                setMinSpinner(Math.max((oldMin - range), rangeBottom)); // first set new min
663                if (_autoItemRange) {
664                    setMaxSpinner(Math.max((oldMax - range), range));   // set new max (only if auto)
665                }
666                updatePressed();
667                setDirty();
668                log.debug("new prev range = {}, newMin ={}, newMax ={}", range, getMinSpinner(), getMaxSpinner());
669            }
670        }));
671        prev.setToolTipText(Bundle.getMessage("PreviousToolTip", Bundle.getMessage("CheckBoxAutoItemRange")));
672        navBarPanel.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("From"))));
673        JComponent minEditor = minSpinner.getEditor();
674        // enlarge minSpinner editor text field width
675        JFormattedTextField minTf = ((JSpinner.DefaultEditor) minEditor).getTextField();
676        minTf.setColumns(5);
677        minSpinner.addChangeListener(e -> {
678            JSpinner spinner = (JSpinner) e.getSource();
679            int value = (int)spinner.getValue();
680            // stop if value >= maxSpinner -1 (range <= 0)
681            if (value >= (Integer) maxSpinner.getValue() - 1) {
682                maxSpinner.setValue(value + 1);
683            }
684            updatePressed();
685            setDirty();
686        });
687        navBarPanel.add(minSpinner);
688        navBarPanel.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("UpTo"))));
689        // enlarge maxSpinner editor text field width
690        JComponent maxEditor = maxSpinner.getEditor();
691        JFormattedTextField maxTf = ((JSpinner.DefaultEditor) maxEditor).getTextField();
692        maxTf.setColumns(5);
693        maxSpinner.addChangeListener(e -> {
694            JSpinner spinner = (JSpinner) e.getSource();
695            int value = (int)spinner.getValue();
696            // stop if value <= minSpinner + 1 (range <= 0)
697            if (value <= (Integer) minSpinner.getValue() + 1) {
698                minSpinner.setValue(value - 1);
699            }
700            updatePressed();
701            setDirty();
702        });
703        navBarPanel.add(maxSpinner);
704
705        navBarPanel.add(next);
706        next.addMouseListener(JmriMouseListener.adapt(new JmriMouseAdapter() {
707            @Override
708            public void mouseClicked(JmriMouseEvent e) {
709                int oldMin = getMinSpinner();
710                int oldMax = getMaxSpinner();
711                int range = Math.max(oldMax - oldMin + 1, 1); // make sure range > 0
712                log.debug("next range was {}, oldMin ={}, oldMax ={}", range, oldMin, oldMax);
713                setMaxSpinner(Math.min((oldMax + range), rangeTop));               // first set new max
714                if (_autoItemRange) {
715                    setMinSpinner(Math.min(oldMin + range, rangeTop - range + 1)); // set new min (only if auto)
716                }
717                updatePressed();
718                setDirty();
719                log.debug("new next range = {}, newMin ={}, newMax ={}", range, getMinSpinner(), getMaxSpinner());
720            }
721        }));
722        next.setToolTipText(Bundle.getMessage("NextToolTip", Bundle.getMessage("CheckBoxAutoItemRange")));
723        navBarPanel.add(Box.createHorizontalGlue());
724
725        controls.add(navBarPanel); // put items on 2nd Editor Panel
726        controls.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("SelectRangeTitle")));
727        return controls;
728    }
729
730    private int getMinSpinner() { //tmp
731        return (Integer) minSpinner.getValue();
732    }
733
734    private int getMaxSpinner() { //tmp synchronized
735        return (Integer) maxSpinner.getValue();
736    }
737
738    protected void setMinSpinner(int value) { //tmp synchronized
739        if (value >= rangeBottom && value < rangeTop) { // allows to set above MaxSpinner temporarily
740            minSpinner.setValue(value);
741        }
742    }
743
744    protected void setMaxSpinner(int value) { //tmp synchronized
745        if (value > rangeBottom && value <= rangeTop) { // allows to set above MinSpinner temporarily
746            maxSpinner.setValue(value);
747        }
748    }
749
750    private void setupEditorPane() {
751        // Initial setup
752        Container contentPane = getContentPane(); // Editor (configuration) pane
753
754        JPanel innerBorderPanel = new JPanel();
755        innerBorderPanel.setLayout(new BoxLayout(innerBorderPanel, BoxLayout.PAGE_AXIS));
756        TitledBorder TitleBorder = BorderFactory.createTitledBorder(Bundle.getMessage("SwitchboardHelpTitle"));
757        innerBorderPanel.setBorder(TitleBorder);
758        innerBorderPanel.add(new JTextArea(Bundle.getMessage("Help1")));
759        // help2 explains: dimmed icons = unconnected
760        innerBorderPanel.add(help2);
761        if (!_hideUnconnected) {
762            help2.setVisible(false); // hide this text when _hideUnconnected is set to true from menu or checkbox
763        }
764        // help3 warns: no icons to show on switchboard
765        help3.setForeground(Color.red);
766        innerBorderPanel.add(help3);
767        help3.setVisible(false); // initially hide help3 warning text
768        contentPane.add(innerBorderPanel);
769    }
770
771    //@Override
772    protected void makeOptionMenu() {
773        _optionMenu = new JMenu(Bundle.getMessage("MenuOptions"));
774        _menuBar.add(_optionMenu, 0);
775        // controllable item
776        _optionMenu.add(controllingBox);
777        controllingBox.addActionListener((ActionEvent event) -> {
778            setAllControlling(controllingBox.isSelected());
779            // update the title on the switchboard to match (no) layout control
780            if (beanTypeList.getSelectedItem() != null) {
781                border.setTitle(memo.getUserName() + " " +
782                        beanTypeList.getSelectedItem().toString() + " - " + (allControlling() ? interact : noInteract));
783            }
784            allOnButton.setVisible((beanTypeList.getSelectedIndex() == 2) && allControlling());
785            allOffButton.setVisible((beanTypeList.getSelectedIndex() == 2) && allControlling());
786            switchboardLayeredPane.repaint();
787            log.debug("border title updated");
788        });
789        controllingBox.setSelected(allControlling());
790
791        // autoItemRange item
792        _optionMenu.add(autoItemRangeBox);
793        autoItemRangeBox.addActionListener((ActionEvent event) -> {
794            setAutoItemRange(autoItemRangeBox.isSelected());
795            autoItemRange.setSelected(autoItemRange()); // also (un)check the box on the editor
796        });
797        autoItemRangeBox.setSelected(autoItemRange());
798
799        _optionMenu.addSeparator();
800
801        // auto rows item
802        _optionMenu.add(autoRowsBox);
803        //tmp synchronized (this) {
804            autoRowsBox.setSelected(true); // default on
805        //tmp }
806        autoRowsBox.addActionListener((ActionEvent event) -> {
807            //tmp synchronized (this) {
808                if (autoRowsBox.isSelected()) {
809                    log.debug("autoRows was turned ON");
810                    int oldRows = rows;
811                    rows = autoRows(cellProportion); // recalculates rows x columns and redraws pane
812                    // sets _tileSize TODO: specific proportion value per Type/Shape choice?
813                    rowsSpinner.setEnabled(false);
814                    rowsSpinner.setToolTipText(Bundle.getMessage("RowsSpinnerOffTooltip"));
815                    // hide rowsSpinner + rowsLabel?
816                    if (rows != oldRows) {
817                        // rowsSpinner will be recalculated by auto so we don't copy the old value
818                        updatePressed(); // redraw if rows value changed
819                    }
820                } else {
821                    log.debug("autoRows was turned OFF");
822                    rowsSpinner.setValue(rows); // autoRows turned off, copy current auto value to spinner
823                    rowsSpinner.setEnabled(true); // show rowsSpinner + rowsLabel?
824                    rowsSpinner.setToolTipText(Bundle.getMessage("RowsSpinnerOnTooltip"));
825                    // calculate tile size
826                    int colNum = (((getTotal() > 0) ? (getTotal()) : 1) + rows - 1) / Math.max(rows, 1);
827                    int maxW = (super.getTargetFrame().getWidth() - 10) / colNum; // int division, subtract 2x3px for border
828                    int maxH = (super.getTargetFrame().getHeight() - verticalMargin) / Math.max(rows, 1); // for footer
829                    _tileSize = Math.min(maxW, maxH); // store for tile graphics
830                    log.debug("_tileSize {} from {}, {}", _tileSize, maxW, maxH);
831                }
832            //tmp }
833        });
834        // show tooltip item
835        _optionMenu.add(showToolTipBox);
836        showToolTipBox.addActionListener((ActionEvent e) -> setAllShowToolTip(showToolTipBox.isSelected()));
837        showToolTipBox.setSelected(showToolTip());
838        // hideUnconnected item
839        _optionMenu.add(hideUnconnectedBox);
840        hideUnconnectedBox.setSelected(_hideUnconnected);
841        hideUnconnectedBox.addActionListener((ActionEvent event) -> {
842            setHideUnconnected(hideUnconnectedBox.isSelected());
843            hideUnconnected.setSelected(_hideUnconnected); // also (un)check the box on the editor
844            help2.setVisible(!_hideUnconnected && (!switchesOnBoard.isEmpty())); // and show/hide instruction line unless no items on board
845            updatePressed();
846            setDirty();
847        });
848        // switch label options
849        _optionMenu.add(labelNamesMenu);
850        // only system name
851        labelNamesMenu.add(systemNameBox);
852        systemNameBox.setSelected(false); // default off
853        systemNameBox.addActionListener((ActionEvent e) -> setLabel(SwitchBoardLabelDisplays.SYSTEM_NAME));
854        // both names (when set)
855        labelNamesMenu.add(bothNamesBox);
856        bothNamesBox.setSelected(true); // default on
857        bothNamesBox.addActionListener((ActionEvent e) -> setLabel(SwitchBoardLabelDisplays.BOTH_NAMES));
858        // only user name (when set), aka display name
859        labelNamesMenu.add(displayNameBox);
860        displayNameBox.setSelected(false); // default off
861        displayNameBox.addActionListener((ActionEvent e) -> setLabel(SwitchBoardLabelDisplays.USER_NAME));
862
863        // Show/Hide Scroll Bars
864        JMenu scrollMenu = new JMenu(Bundle.getMessage("ComboBoxScrollable"));
865        _optionMenu.add(scrollMenu);
866        ButtonGroup scrollGroup = new ButtonGroup();
867        scrollGroup.add(scrollBoth);
868        scrollMenu.add(scrollBoth);
869        scrollBoth.addActionListener((ActionEvent event) -> setScroll(SCROLL_BOTH));
870        scrollGroup.add(scrollNone);
871        scrollMenu.add(scrollNone);
872        scrollNone.addActionListener((ActionEvent event) -> setScroll(SCROLL_NONE));
873        scrollGroup.add(scrollHorizontal);
874        scrollMenu.add(scrollHorizontal);
875        scrollHorizontal.addActionListener((ActionEvent event) -> setScroll(SCROLL_HORIZONTAL));
876        scrollGroup.add(scrollVertical);
877        scrollMenu.add(scrollVertical);
878        scrollVertical.addActionListener((ActionEvent event) -> setScroll(SCROLL_VERTICAL));
879
880        // add beanswitch size menu item
881        JMenu iconSizeMenu = new JMenu(Bundle.getMessage("MenuIconSize"));
882        _optionMenu.add(iconSizeMenu);
883        ButtonGroup sizeGroup = new ButtonGroup();
884        sizeGroup.add(sizeSmall);
885        iconSizeMenu.add(sizeSmall);
886        sizeSmall.addActionListener((ActionEvent event) -> setIconScale(SIZE_MIN));
887        sizeGroup.add(sizeDefault);
888        iconSizeMenu.add(sizeDefault);
889        sizeDefault.addActionListener((ActionEvent event) -> setIconScale(SIZE_INIT));
890        sizeGroup.add(sizeLarge);
891        iconSizeMenu.add(sizeLarge);
892        sizeLarge.addActionListener((ActionEvent event) -> setIconScale(SIZE_MAX));
893
894        JMenu colorMenu = new JMenu(Bundle.getMessage("Colors"));
895        _optionMenu.add(colorMenu);
896        // add text color menu item
897        JMenuItem textColorMenuItem = new JMenuItem(Bundle.getMessage("DefaultTextColor", "..."));
898        colorMenu.add(textColorMenuItem);
899        textColorMenuItem.addActionListener((ActionEvent event) -> {
900            Color desiredColor = JmriColorChooser.showDialog(this,
901                                 Bundle.getMessage("DefaultTextColor", ""),
902                                 defaultTextColor);
903            if (desiredColor != null && !defaultTextColor.equals(desiredColor)) {
904                // if new defaultTextColor matches bgColor, ask user as labels will become unreadable
905                if (desiredColor.equals(defaultBackgroundColor)) {
906                    int retval = JmriJOptionPane.showOptionDialog(this,
907                    Bundle.getMessage("ColorIdenticalWarningF"), Bundle.getMessage("WarningTitle"),
908                JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.INFORMATION_MESSAGE, null,
909                    new Object[]{Bundle.getMessage("ButtonOK"), Bundle.getMessage("ButtonInvert"),
910                    Bundle.getMessage("ButtonCancel")}, Bundle.getMessage("ButtonCancel"));
911                    if (retval == 1) { // array position 1, invert the other color
912                        setDefaultBackgroundColor(contrast(defaultBackgroundColor));
913                    } else if (retval != 0) { // NOT ButtonOK, ie cancel or Dialog closed
914                        return;
915                    }
916                }
917                defaultTextColor = desiredColor;
918                border.setTitleColor(desiredColor);
919                setDirty(true);
920                JmriColorChooser.addRecentColor(desiredColor);
921                updatePressed();
922            }
923        });
924        // add background color menu item
925        JMenuItem backgroundColorMenuItem = new JMenuItem(Bundle.getMessage("SetBackgroundColor", "..."));
926        colorMenu.add(backgroundColorMenuItem);
927        backgroundColorMenuItem.addActionListener((ActionEvent event) -> {
928            Color desiredColor = JmriColorChooser.showDialog(this,
929                    Bundle.getMessage("SetBackgroundColor", ""),
930                    defaultBackgroundColor);
931            if (desiredColor != null && !defaultBackgroundColor.equals(desiredColor)) {
932                // if new bgColor matches the defaultTextColor, ask user as labels will become unreadable
933                if (desiredColor.equals(defaultTextColor)) {
934                    int retval = JmriJOptionPane.showOptionDialog(this,
935                        Bundle.getMessage("ColorIdenticalWarningR"), Bundle.getMessage("WarningTitle"),
936                        JmriJOptionPane.YES_NO_OPTION, JmriJOptionPane.INFORMATION_MESSAGE, null,
937                        new Object[]{Bundle.getMessage("ButtonOK"), Bundle.getMessage("ButtonInvert"), Bundle.getMessage("ButtonCancel")},
938                        Bundle.getMessage("ButtonCancel"));
939                    if (retval == 1) { // invert the other color
940                        defaultTextColor = contrast(defaultTextColor);
941                        border.setTitleColor(defaultTextColor);
942                    } else if (retval != 0) {  // cancel or close Dialog
943                        return;
944                    }
945                }
946                defaultBackgroundColor = desiredColor;
947                setBackgroundColor(desiredColor);
948                setDirty(true);
949                JmriColorChooser.addRecentColor(desiredColor);
950                updatePressed();
951            }
952        });
953        // add ActiveColor menu item
954        JMenuItem activeColorMenuItem = new JMenuItem(Bundle.getMessage("SetActiveColor", "..."));
955        colorMenu.add(activeColorMenuItem);
956        activeColorMenuItem.addActionListener((ActionEvent event) -> {
957            Color desiredColor = JmriColorChooser.showDialog(this,
958                    Bundle.getMessage("SetActiveColor", ""),
959                    defaultActiveColor);
960            if (desiredColor != null && !defaultActiveColor.equals(desiredColor)) {
961                // if new ActiveColor matches InactiveColor, ask user as state will become unreadable
962                if (desiredColor.equals(defaultInactiveColor)) {
963                    int retval = JmriJOptionPane.showOptionDialog(this,
964                        Bundle.getMessage("ColorIdenticalWarningF"), Bundle.getMessage("WarningTitle"),
965                        JmriJOptionPane.YES_NO_OPTION, JmriJOptionPane.INFORMATION_MESSAGE, null,
966                        new Object[]{Bundle.getMessage("ButtonOK"), Bundle.getMessage("ButtonInvert"), Bundle.getMessage("ButtonCancel")}, Bundle.getMessage("ButtonCancel"));
967                    if (retval == 1) { // array position 1 invert the other color
968                        setDefaultInactiveColor(contrast(defaultInactiveColor));
969                    } else if (retval != 0) { // Cancel or Dialog closed
970                        return; // cancel
971                    }
972                }
973                defaultActiveColor = desiredColor;
974                setDirty(true);
975                JmriColorChooser.addRecentColor(desiredColor);
976                updatePressed();
977            }
978        });
979        // add InctiveColor menu item
980        JMenuItem inactiveColorMenuItem = new JMenuItem(Bundle.getMessage("SetInactiveColor", "..."));
981        colorMenu.add(inactiveColorMenuItem);
982        inactiveColorMenuItem.addActionListener((ActionEvent event) -> {
983            Color desiredColor = JmriColorChooser.showDialog(this,
984                    Bundle.getMessage("SetInactiveColor", ""),
985                    defaultInactiveColor);
986            if (desiredColor != null && !defaultInactiveColor.equals(desiredColor)) {
987                // if new InactiveColor matches ActiveColor, ask user as state will become unreadable
988                if (desiredColor.equals(defaultInactiveColor)) {
989                    int retval = JmriJOptionPane.showOptionDialog(this,
990                        Bundle.getMessage("ColorIdenticalWarningF"), Bundle.getMessage("WarningTitle"),
991                        JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.INFORMATION_MESSAGE, null,
992                        new Object[]{Bundle.getMessage("ButtonOK"), Bundle.getMessage("ButtonInvert"), Bundle.getMessage("ButtonCancel")}, Bundle.getMessage("ButtonCancel"));
993                    if (retval == 1) { // array position 1 invert the other color
994                        setDefaultActiveColor(contrast(defaultActiveColor));
995                    } else if (retval != 0) { // cancel or Dialog closed
996                        return; // cancel
997                    }
998                }
999                defaultInactiveColor = desiredColor;
1000                setDirty(true);
1001                JmriColorChooser.addRecentColor(desiredColor);
1002                updatePressed();
1003            }
1004        });
1005    }
1006
1007    private void makeFileMenu() {
1008        _fileMenu = new JMenu(Bundle.getMessage("MenuFile"));
1009        _menuBar.add(_fileMenu, 0);
1010        _fileMenu.add(new jmri.jmrit.display.NewPanelAction(Bundle.getMessage("MenuItemNew")));
1011
1012        _fileMenu.add(new jmri.configurexml.StoreXmlUserAction(Bundle.getMessage("FileMenuItemStore")));
1013
1014        JMenuItem editItem = new JMenuItem(Bundle.getMessage("renamePanelMenu", "..."));
1015        PositionableJComponent z = new PositionableJComponent(this);
1016        z.setScale(getPaintScale());
1017        editItem.addActionListener(CoordinateEdit.getNameEditAction(z));
1018        _fileMenu.add(editItem);
1019
1020        _fileMenu.addSeparator();
1021
1022        JMenuItem deleteItem = new JMenuItem(Bundle.getMessage("DeletePanel"));
1023        _fileMenu.add(deleteItem);
1024        deleteItem.addActionListener((ActionEvent event) -> {
1025            if (deletePanel()) {
1026                getTargetFrame().dispose();
1027                dispose();
1028            }
1029        });
1030        _fileMenu.addSeparator();
1031        editItem = new JMenuItem(Bundle.getMessage("CloseEditor"));
1032        _fileMenu.add(editItem);
1033        editItem.addActionListener((ActionEvent event) -> {
1034            log.debug("switchboardeditor edit menu CloseEditor selected");
1035            setAllEditable(false);
1036            setVisible(false); // hide Editor pane
1037        });
1038    }
1039
1040    public void setDefaultTextColor(Color color) {
1041        defaultTextColor = color;
1042        border.setTitleColor(color);
1043    }
1044
1045    public String getDefaultTextColor() {
1046        return ColorUtil.colorToColorName(defaultTextColor);
1047    }
1048
1049    public Color getDefaultTextColorAsColor() {
1050        return defaultTextColor;
1051    }
1052
1053    public String getActiveSwitchColor() {
1054        return ColorUtil.colorToColorName(defaultActiveColor);
1055    }
1056    public Color getActiveColorAsColor() {
1057        return defaultActiveColor;
1058    }
1059    public void setDefaultActiveColor(Color color) {
1060        defaultActiveColor = color;
1061    }
1062
1063    public String getInactiveSwitchColor() {
1064        return ColorUtil.colorToColorName(defaultInactiveColor);
1065    }
1066    public Color getInactiveColorAsColor() {
1067        return defaultInactiveColor;
1068    }
1069    public void setDefaultInactiveColor(Color color) {
1070        defaultInactiveColor = color;
1071    }
1072
1073    /**
1074     * Load from xml and set bg color of _targetpanel as well as variable.
1075     *
1076     * @param color RGB Color for switchboard background and beanSwitches
1077     */
1078    public void setDefaultBackgroundColor(Color color) {
1079        setBackgroundColor(color); // via Editor to update bg color of JPanel
1080        defaultBackgroundColor = color;
1081    }
1082
1083    /**
1084     * Get current default background color.
1085     *
1086     * @return background color of this Switchboard
1087     */
1088    public Color getDefaultBackgroundColor() {
1089        return defaultBackgroundColor;
1090    }
1091
1092    public void setLabel(SwitchBoardLabelDisplays label) {
1093        _showUserName = label;
1094        switch (label) {
1095            case SYSTEM_NAME :
1096                //deselect box 2 and 3
1097                bothNamesBox.setSelected(false);
1098                displayNameBox.setSelected(false);
1099                break;
1100            case USER_NAME :
1101                //deselect box 1 and 2
1102                systemNameBox.setSelected(false);
1103                bothNamesBox.setSelected(false);
1104                break;
1105            case BOTH_NAMES :
1106            default:
1107                //deselect box 1 and 3
1108                systemNameBox.setSelected(false);
1109                displayNameBox.setSelected(false);
1110                break;
1111        }
1112        updatePressed();
1113    }
1114
1115    // *********************** end Menus ************************
1116
1117    @Override
1118    public void setAllEditable(boolean edit) {
1119        log.debug("_editable set to {} in super", edit);
1120        if (edit) {
1121            if (_editorMenu != null) {
1122                _menuBar.remove(_editorMenu);
1123            }
1124            if (_optionMenu == null) {
1125                makeOptionMenu();
1126            } else {
1127                _menuBar.add(_optionMenu, 0);
1128            }
1129            if (_fileMenu == null) {
1130                makeFileMenu();
1131            } else {
1132                _menuBar.add(_fileMenu, 0);
1133            }
1134            log.debug("added File and Options menubar");
1135            //contentPane.SetUpdateButtonEnabled(false);
1136        } else {
1137            if (_fileMenu != null) {
1138                _menuBar.remove(_fileMenu);
1139            }
1140            if (_optionMenu != null) {
1141                _menuBar.remove(_optionMenu);
1142            }
1143            if (_editorMenu == null) {
1144                _editorMenu = new JMenu(Bundle.getMessage("MenuEdit"));
1145                _editorMenu.add(new AbstractAction(Bundle.getMessage("OpenEditor")) {
1146                    @Override
1147                    public void actionPerformed(ActionEvent e) {
1148                        setAllEditable(true);
1149                        log.debug("Switchboard Editor Open Editor menu called");
1150                    }
1151                });
1152                _menuBar.add(_editorMenu, 0);
1153            }
1154            //contentPane.SetUpdateButtonEnabled(true);
1155        }
1156        super.setAllEditable(edit);
1157        super.setTitle();
1158        _menuBar.revalidate();
1159    }
1160
1161    @Override
1162    public void setUseGlobalFlag(boolean set) {
1163        controllingBox.setEnabled(set);
1164        super.setUseGlobalFlag(set);
1165    }
1166
1167    @Override
1168    public void setTitle() {
1169        String name = getName(); // get name of JFrame
1170        log.debug("JFrame name = {}", name);
1171        if (name == null || name.length() == 0) {
1172            name = Bundle.getMessage("SwitchboardDefaultName", "");
1173        }
1174        super.setTitle(name + " " + Bundle.getMessage("LabelEditor"));
1175        super.getTargetFrame().setTitle(name);
1176    }
1177
1178    /**
1179     * Control whether target panel items without a connection to the layout are
1180     * displayed.
1181     *
1182     * @param state true to hide all in range
1183     */
1184    public void setHideUnconnected(boolean state) {
1185        _hideUnconnected = state;
1186    }
1187
1188    public boolean hideUnconnected() {
1189        return _hideUnconnected;
1190    }
1191
1192    /**
1193     * Control whether range of items is automatically preserved.
1194     *
1195     * @param state true to calculate upper limit from lowest value range value set (default)
1196     */
1197    public void setAutoItemRange(boolean state) {
1198        _autoItemRange = state;
1199    }
1200
1201    public boolean autoItemRange() {
1202        return _autoItemRange;
1203    }
1204
1205    /**
1206     * Determine optimal cols/rows inside JPanel using switch range, icon proportions of beanswitch icons +
1207     * web canvas W:H proportions range from 1.5 (3:2) to 0.7 (1:1.5), assume squares for now.
1208     *
1209     * @param cellProp the W:H proportion of image, currently 1.0f for all shapes
1210     * @return number of rows on current target pane size/proportions displaying biggest tiles
1211     */
1212    private int autoRows(float cellProp) {
1213        // find cell matrix that allows largest size icons
1214        double paneEffectiveWidth = Math.ceil((super.getTargetFrame().getWidth() - 6)/ Math.max(cellProp, 0.1f)); // -2x3px for border
1215        //log.debug("paneEffectiveWidth: {}", paneEffectiveWidth); // compare to resizeInFrame()
1216        double paneHeight = super.getTargetFrame().getHeight() - verticalMargin; // for footer
1217        int columnsNum = 1;
1218        int rowsNum = 1;
1219        float tileSize = 0.1f; // start value
1220        float tileSizeOld = 0.0f;
1221        int totalDisplayed = ((getTotal() > 0) ? (getTotal()) : 1);
1222        // if all items unconnected and set to be hidden, use 1
1223        if (totalDisplayed >= unconnectedRangeLimit) {
1224            log.warn("switchboards are limited to {} items", unconnectedRangeLimit);
1225        }
1226
1227        while (tileSize > tileSizeOld) {
1228            rowsNum = (totalDisplayed + columnsNum - 1) / Math.max(columnsNum, 1); // int roundup
1229            tileSizeOld = tileSize; // store for comparison
1230            tileSize = (float) Math.min(paneEffectiveWidth / Math.max(columnsNum, 1), paneHeight / Math.max(rowsNum, 1));
1231            //log.debug("C>R Cols {} x Rows {}, tileSize {} was {}", columnsNum, rowsNum, String.format("%.2f", tileSize),
1232            //        String.format("%.2f", tileSizeOld));
1233            if (tileSize < tileSizeOld) {
1234                rowsNum = (totalDisplayed + columnsNum - 2) / Math.max((columnsNum - 1), 1);
1235                break;
1236            }
1237            columnsNum++;
1238        }
1239
1240        // start over stepping columns instead of rows
1241        int columnsNumC;
1242        int rowsNumC = 1;
1243        float tileSizeC = 0.1f;
1244        float tileSizeCOld = 0.0f;
1245        while (tileSizeC > tileSizeCOld) {
1246            columnsNumC = (totalDisplayed + rowsNumC - 1) / Math.max(rowsNumC, 1); // int roundup
1247            tileSizeCOld = tileSizeC; // store for comparison
1248            tileSizeC = (float) Math.min(paneEffectiveWidth / Math.max(columnsNumC, 1), paneHeight / Math.max(rowsNumC, 1));
1249            //log.debug("R>C Cols {} x Rows {}, tileSizeC {} was {}", columnsNumC, rowsNumC, String.format("%.2f", tileSizeC),
1250            //        String.format("%.2f", tileSizeCOld));
1251            if (tileSizeC < tileSizeCOld) {
1252                rowsNumC--;
1253                break;
1254            }
1255            rowsNumC++;
1256        }
1257
1258        if (tileSizeC > tileSize) { // we must choose the largest solution
1259            rowsNum = rowsNumC;
1260        }
1261        // Math.min(1,... to prevent >100% width calc (when hide unconnected selected)
1262        // rows = (total + columns - 1) / columns (int roundup) to account for unused tiles in grid:
1263        // for 23 switches we need at least 24 tiles (4x6, 3x8, 2x12 etc)
1264        // similar calculations repeated in panel.js for web display
1265        log.debug("CELL SIZE optimum found: CxR = {}x{} for {} x {} pixels", ((totalDisplayed + rowsNum - 1) / rowsNum), rowsNum, super.getTargetFrame().getWidth(), super.getTargetFrame().getHeight());
1266
1267        _tileSize = Math.round((float) paneHeight / Math.max(rowsNum, 1)); // recalculate tileSize from rowNum, store for tile graphics
1268        log.debug("_tileSize {} from {} / {}", _tileSize, paneHeight, rowsNum);
1269        return rowsNum;
1270    }
1271
1272    /**
1273     * Allow external reset of dirty bit.
1274     */
1275    public void resetDirty() {
1276        setDirty(false);
1277        savedEditMode = isEditable();
1278        savedControlLayout = allControlling();
1279    }
1280
1281    /**
1282     * Allow external set of dirty bit.
1283     * @param val new dirty flag value, true dirty, false clean.
1284     */
1285    public void setDirty(boolean val) {
1286        panelChanged = val;
1287    }
1288
1289    public void setDirty() {
1290        setDirty(true);
1291    }
1292
1293    /**
1294     * Check the dirty state.
1295     * @return true if panel changed, else false.
1296     */
1297    public boolean isDirty() {
1298        return panelChanged;
1299    }
1300
1301    // ********************** load/store board *******************
1302    /**
1303     * Load Range minimum.
1304     *
1305     * @param rangemin lowest address to show
1306     */
1307    public void setPanelMenuRangeMin(int rangemin) {
1308        minSpinner.setValue(rangemin);
1309    }
1310
1311    /**
1312     * Load Range maximum.
1313     *
1314     * @param rangemax highest address to show
1315     */
1316    public void setPanelMenuRangeMax(int rangemax) {
1317        maxSpinner.setValue(rangemax);
1318    }
1319
1320    /**
1321     * Store Range minimum.
1322     *
1323     * @return lowest address shown
1324     */
1325    public int getPanelMenuRangeMin() {
1326        return (int) minSpinner.getValue();
1327    }
1328
1329    /**
1330     * Store Range maximum.
1331     *
1332     * @return highest address shown
1333     */
1334    public int getPanelMenuRangeMax() {
1335        return (int) maxSpinner.getValue();
1336    }
1337
1338    // ***************** Store & Load xml ********************
1339    /**
1340     * Store bean type.
1341     *
1342     * @return bean type prefix as set for Switchboard
1343     */
1344    public String getSwitchType() {
1345        String typePref;
1346        String switchType = "";
1347        if (beanTypeList.getSelectedItem() != null) {
1348            switchType = beanTypeList.getSelectedItem().toString();
1349        }
1350        if (switchType.equals(LIGHT)) { // switch-case doesn't work here
1351            typePref = "L";
1352        } else if (switchType.equals(SENSOR)) {
1353            typePref = "S";
1354        } else { // Turnout
1355            typePref = "T";
1356        }
1357        return typePref;
1358    }
1359
1360    /**
1361     * Get bean type name.
1362     *
1363     * @return bean type name
1364     */
1365    public String getSwitchTypeName() {
1366        return _type;
1367    }
1368
1369    /**
1370     * Load bean type from xml.
1371     *
1372     * @param prefix the bean type prefix
1373     */
1374    public void setSwitchType(String prefix) {
1375        switch (prefix) {
1376            case "L":
1377                _type = LIGHT;
1378                break;
1379            case "S":
1380                _type = SENSOR;
1381                break;
1382            case "T":
1383            default:
1384                _type = TURNOUT;
1385        }
1386        try {
1387            beanTypeList.setSelectedItem(_type);
1388        } catch (IllegalArgumentException e) {
1389            log.error("invalid bean type [{}] in Switchboard", prefix);
1390        }
1391    }
1392
1393    /**
1394     * Store connection type.
1395     *
1396     * @return active bean connection prefix
1397     */
1398    public String getSwitchManu() {
1399        return memo.getSystemPrefix();
1400    }
1401
1402    /**
1403     * Load connection type.
1404     *
1405     * @param manuPrefix connection prefix
1406     */
1407    public void setSwitchManu(String manuPrefix) {
1408        try {
1409            memo = SystemConnectionMemoManager.getDefault().getSystemConnectionMemoForSystemPrefix(manuPrefix);
1410            if (memo == null) {
1411                log.error("No default SystemConnectionMemo defined for prefix {}", manuPrefix);
1412                return;
1413            }
1414            if (memo.get(TurnoutManager.class) != null) { // just for initial view
1415                turnoutManComboBox.setSelectedItem(memo.get(TurnoutManager.class));
1416                log.debug("turnoutManComboBox set to {} for {}", memo.getUserName(), manuPrefix);
1417            }
1418            if (memo.get(SensorManager.class) != null) { // we expect the user has same preference for the other types
1419                sensorManComboBox.setSelectedItem(memo.get(SensorManager.class));
1420                // TODO LocoNet does not provide a sensormanager via the memo
1421                log.debug("sensorManComboBox set to {} for {}", memo.getUserName(), manuPrefix);
1422            }
1423            if (memo.get(LightManager.class) != null) { // so we set them the same (only 1 value stored as set on store)
1424                lightManComboBox.setSelectedItem(memo.get(LightManager.class));
1425                log.debug("lightManComboBox set to {} for {}", memo.getUserName(), manuPrefix);
1426            }
1427        } catch (IllegalArgumentException e) {
1428            log.error("invalid connection [{}] in Switchboard, {}", manuPrefix, e.getMessage());
1429        } catch (NullPointerException e) {
1430            log.error("NPE setting prefix to [{}] in Switchboard", manuPrefix);
1431        }
1432    }
1433
1434    /**
1435     * Store switch shape.
1436     *
1437     * @return bean shape prefix
1438     */
1439    public String getSwitchShape() {
1440        String shapeAsString;
1441        switch (shape) {
1442            case SLIDER:
1443                shapeAsString = "icon";
1444                break;
1445            case KEY:
1446                shapeAsString = "drawing";
1447                break;
1448            case SYMBOL:
1449                shapeAsString = "symbol";
1450                break;
1451            case (BUTTON):
1452            default: // 0 = basic labelled button
1453                shapeAsString = "button";
1454        }
1455        return shapeAsString;
1456    }
1457
1458    /**
1459     * Load switch shape.
1460     *
1461     * @param switchShape name of switch shape
1462     */
1463    public void setSwitchShape(String switchShape) {
1464        switch (switchShape) {
1465            case "icon":
1466                shape = SLIDER;
1467                break;
1468            case "drawing":
1469                shape = KEY;
1470                break;
1471            case "symbol":
1472                shape = SYMBOL;
1473                break;
1474            default: // button
1475                shape = BUTTON;
1476        }
1477        try {
1478            shapeList.setSelectedIndex(shape);
1479        } catch (IllegalArgumentException e) {
1480            log.error("invalid switch shape [{}] in Switchboard", shape);
1481        }
1482    }
1483
1484    /**
1485     * Store Switchboard rowsNum JSpinner or turn on autoRows option.
1486     *
1487     * @return the number of switches to display per row or 0 if autoRowsBox (menu-setting) is selected
1488     */
1489    public int getRows() { //tmp synchronized
1490        if (autoRowsBox.isSelected()) {
1491            return 0;
1492        } else {
1493            return rows;
1494        }
1495    }
1496
1497    /**
1498     * Load Switchboard rowsNum JSpinner.
1499     *
1500     * @param rws the number of switches displayed per row (as text) or 0 te activate autoRowsBox setting
1501     */
1502    public void setRows(int rws) { //tmp synchronized
1503        autoRowsBox.setSelected(rws == 0);
1504        if (rws > 0) {
1505            rowsSpinner.setValue(rws); // rows is set via rowsSpinner
1506            rowsSpinner.setEnabled(true);
1507        } else {
1508            rowsSpinner.setEnabled(false);
1509            rowsSpinner.setToolTipText(Bundle.getMessage("RowsSpinnerOffTooltip"));
1510            rows = autoRows(cellProportion); // recalculate, TODO: specific proportion value for Type/Shape choice?
1511            rowsSpinner.setValue(rows);
1512        }
1513    }
1514
1515    /**
1516     * Store total number of switches displayed (unconnected/hidden excluded).
1517     *
1518     * @return the total number of switches displayed
1519     */
1520    public int getTotal() {
1521        return switchesOnBoard.size();
1522    }
1523
1524    // all content loaded from file.
1525    public void loadComplete() {
1526        log.debug("loadComplete");
1527    }
1528
1529    // used for xml persistent storage and web display
1530    public String showUserName() {
1531        switch (_showUserName) {
1532            case SYSTEM_NAME :
1533                return "no";
1534            case USER_NAME :
1535                return "displayname";
1536            case BOTH_NAMES :
1537            default :
1538                return "yes";
1539        }
1540        // xml type="labelType", see xml/schema/types/switchboardeditor.xsd and panel.js
1541    }
1542
1543    /**
1544     * Get the label type.
1545     * @return current setting of display type (e.g. system name, both, user name)
1546     */
1547    public SwitchBoardLabelDisplays nameDisplay() {
1548        return _showUserName;
1549    }
1550
1551    public void setShowUserName(SwitchBoardLabelDisplays label) {
1552        _showUserName = label;
1553        switch (label) {
1554            case BOTH_NAMES:
1555                systemNameBox.setSelected(false);
1556                bothNamesBox.setSelected(true);
1557                displayNameBox.setSelected(false);
1558                break;
1559            case USER_NAME:
1560                systemNameBox.setSelected(false);
1561                bothNamesBox.setSelected(false);
1562                displayNameBox.setSelected(true);
1563                break;
1564            case SYSTEM_NAME:
1565            default:
1566                systemNameBox.setSelected(true);
1567                bothNamesBox.setSelected(false);
1568                displayNameBox.setSelected(false);
1569                break;
1570        }
1571    }
1572
1573    /**
1574     * After construction, initialize all the widgets to their saved config
1575     * settings.
1576     */
1577    @Override
1578    public void initView() {
1579        controllingBox.setSelected(allControlling());
1580        showToolTipBox.setSelected(showToolTip());
1581        switch (_scrollState) {
1582            case SCROLL_NONE:
1583                scrollNone.setSelected(true);
1584                break;
1585            case SCROLL_BOTH:
1586                scrollBoth.setSelected(true);
1587                break;
1588            case SCROLL_HORIZONTAL:
1589                scrollHorizontal.setSelected(true);
1590                break;
1591            default:
1592                scrollVertical.setSelected(true);
1593        }
1594        log.debug("InitView done");
1595    }
1596
1597    protected Manager<?> getManager(char typeChar) {
1598        switch (typeChar) {
1599            case 'T': // Turnout
1600                return InstanceManager.getNullableDefault(TurnoutManager.class);
1601            case 'S': // Sensor
1602                return InstanceManager.getNullableDefault(SensorManager.class);
1603            case 'L': // Light
1604                return InstanceManager.getNullableDefault(LightManager.class);
1605            default:
1606                log.error("Unsupported bean type character \"{}\" found.", typeChar);
1607                return null;
1608        }
1609    }
1610
1611    /**
1612     * Get the currently active manager.
1613     *
1614     * @return manager in use for the currently selected bean type and connection
1615     */
1616    protected Manager<?> getManager() {
1617        if (_type.equals(TURNOUT)) {
1618            return turnoutManComboBox.getSelectedItem();
1619        } else if (_type.equals(SENSOR)) {
1620                return sensorManComboBox.getSelectedItem();
1621        } else if (_type.equals(LIGHT)) {
1622            return lightManComboBox.getSelectedItem();
1623        } else {
1624            log.error("Unsupported bean type character \"{}\" found.", _type);
1625            return null;
1626        }
1627    }
1628
1629    /**
1630     * KeyListener of Editor.
1631     *
1632     * @param e the key event heard
1633     */
1634    @Override
1635    public void keyPressed(KeyEvent e) {
1636        repaint();
1637        // TODO select another switch using keypad? accessibility
1638    }
1639
1640    @Override
1641    public void mousePressed(JmriMouseEvent event) {
1642    }
1643
1644    @Override
1645    public void mouseReleased(JmriMouseEvent event) {
1646    }
1647
1648    @Override
1649    public void mouseClicked(JmriMouseEvent event) {
1650    }
1651
1652    @Override
1653    public void mouseDragged(JmriMouseEvent event) {
1654    }
1655
1656    @Override
1657    public void mouseMoved(JmriMouseEvent event) {
1658    }
1659
1660    @Override
1661    public void mouseEntered(JmriMouseEvent event) {
1662        _targetPanel.repaint();
1663    }
1664
1665    @Override
1666    public void mouseExited(JmriMouseEvent event) {
1667        setToolTip(null);
1668        _targetPanel.repaint(); // needed for ToolTip on targetPane
1669    }
1670
1671    /**
1672     * Handle close of Editor window.
1673     * <p>
1674     * Overload/override method in JmriJFrame parent, which by default is
1675     * permanently closing the window. Here, we just want to make it invisible,
1676     * so we don't dispose it (yet).
1677     */
1678    @Override
1679    public void windowClosing(java.awt.event.WindowEvent e) {
1680        setVisible(false);
1681        setAllEditable(false);
1682        log.debug("windowClosing");
1683    }
1684
1685    /**
1686     * Handle opening of Editor window.
1687     * <p>
1688     * Overload/override method in JmriJFrame parent to reset _menuBar.
1689     */
1690    @Override
1691    public void windowOpened(java.awt.event.WindowEvent e) {
1692        _menuBar.revalidate();
1693    }
1694
1695    // ************* implementation of Abstract Editor methods **********
1696
1697    /**
1698     * The target window has been requested to close. Don't delete it at this
1699     * time. Deletion must be accomplished via the "Delete this Panel" menu item.
1700     */
1701    @Override
1702    protected void targetWindowClosingEvent(java.awt.event.WindowEvent e) {
1703        boolean save = (isDirty() || (savedEditMode != isEditable())
1704                || (savedControlLayout != allControlling()));
1705        log.trace("Temp fix to disable CI errors: save = {}", save);
1706        targetWindowClosing();
1707    }
1708
1709    /**
1710     * changeView is not supported by SwitchBoards.
1711     * {@inheritDoc}
1712     */
1713    @Override
1714    protected Editor changeView(String className) {
1715        return null;
1716    }
1717
1718    /**
1719     * Create sequence of panels, etc. for switches: JFrame contains its
1720     * ContentPane which contains a JPanel with BoxLayout (p1) which contains a
1721     * JScrollPane (js) which contains the targetPane.
1722     * Note this is a private menuBar, looking identical to the Editor's _menuBar
1723     *
1724     * @param name title for the Switchboard.
1725     * @return frame containing the switchboard editor.
1726     */
1727    public JmriJFrame makeFrame(String name) {
1728        JmriJFrame targetFrame = new JmriJFrame(name);
1729        ThreadingUtil.runOnGUI( () -> targetFrame.setVisible(true) );
1730
1731        JMenuBar menuBar = new JMenuBar();
1732        JMenu editMenu = new JMenu(Bundle.getMessage("MenuEdit"));
1733        menuBar.add(editMenu);
1734        editMenu.add(new AbstractAction(Bundle.getMessage("OpenEditor")) {
1735            @Override
1736            public void actionPerformed(ActionEvent e) {
1737                setVisible(true);
1738                setAllEditable(true);
1739                log.debug("Switchboard Open Editor menu called");
1740            }
1741        });
1742        targetFrame.setJMenuBar(menuBar);
1743
1744        targetFrame.addHelpMenu("package.jmri.jmrit.display.SwitchboardEditor", true);
1745        return targetFrame;
1746    }
1747
1748    @Override
1749    protected void paintTargetPanel(Graphics g) {
1750        // Switch shapes not directly available from switchboardEditor
1751    }
1752
1753    /**
1754     * Get a beanSwitch object from this SwitchBoard panel by a given name.
1755     *
1756     * @param sName name of switch label/connected bean
1757     * @return BeanSwitch switch object with the given name
1758     */
1759    protected BeanSwitch getSwitch(String sName) {
1760        if (ready && switchesOnBoard.containsKey(sName)) {
1761            return switchesOnBoard.get(sName);
1762        }
1763        log.warn("Switch {} not found on panel. Number of switches displayed: {}", sName, switchesOnBoard.size());
1764        return null;
1765    }
1766
1767    /**
1768     * Get a list with copies of BeanSwitch objects currently displayed to transfer to
1769     * Web Server for display.
1770     *
1771     * @return list of all BeanSwitch switch object
1772     */
1773    public List<BeanSwitch> getSwitches() {
1774        ArrayList<BeanSwitch> _switches = new ArrayList<>();
1775        log.debug("N = {}", switchesOnBoard.size());
1776        if (ready) {
1777            for (Map.Entry<String, BeanSwitch> bs : switchesOnBoard.entrySet()) {
1778                _switches.add(bs.getValue());
1779            }
1780        }
1781        return _switches;
1782    }
1783
1784    /**
1785     * Set up item(s) to be copied by paste.
1786     * <p>
1787     * Not used on switchboards but has to override Editor.
1788     */
1789    @Override
1790    protected void copyItem(Positionable p) {
1791    }
1792
1793    /**
1794     * Set an object's location when it is created.
1795     * <p>
1796     * Not used on switchboards but has to override Editor.
1797     *
1798     * @param obj object to position
1799     */
1800    @Override
1801    public void setNextLocation(Positionable obj) {
1802    }
1803
1804    protected ArrayList<Positionable> getSelectionGroup() {
1805        return null;
1806    }
1807
1808    @Override
1809    public List<NamedBeanUsageReport> getUsageReport(NamedBean bean) {
1810        List<NamedBeanUsageReport> report = new ArrayList<>();
1811        if (bean != null) {
1812            getSwitches().forEach((beanSwitch) -> {
1813                if (bean.equals(beanSwitch.getNamedBean())) {
1814                    report.add(new NamedBeanUsageReport("SwitchBoard", getName()));
1815                }
1816            });
1817        }
1818        return report;
1819    }
1820
1821    public int getTileSize() { //tmp synchronized
1822        return _tileSize; // initially 100
1823    }
1824
1825    /**
1826     * Set connected Lights (only).
1827     *
1828     * @param on state to set Light.ON or Light.OFF
1829     */
1830    public void switchAllLights(int on) {
1831        if (ready) {
1832            for (BeanSwitch bs : switchesOnBoard.values()) {
1833                bs.switchLight(on);
1834            }
1835        }
1836    }
1837
1838    /**
1839     * Configure the combo box listing managers.
1840     * Adapted from AbstractTableAction.
1841     */
1842    protected void configureManagerComboBoxes() {
1843        LightManager defaultManagerL = InstanceManager.getDefault(LightManager.class);
1844        if (defaultManagerL instanceof ProxyManager) {
1845            lightManComboBox.setManagers(defaultManagerL);
1846        } else {
1847            lightManComboBox.setManagers(lightManager);
1848        }
1849
1850        SensorManager defaultManagerS = InstanceManager.getDefault(SensorManager.class);
1851        if (defaultManagerS instanceof ProxyManager) {
1852            sensorManComboBox.setManagers(defaultManagerS);
1853            log.debug("using PROXYmanager for Sensors");
1854        } else {
1855            sensorManComboBox.setManagers(sensorManager);
1856        }
1857
1858        TurnoutManager defaultManagerT = InstanceManager.getDefault(TurnoutManager.class);
1859        if (defaultManagerT instanceof ProxyManager) {
1860            turnoutManComboBox.setManagers(defaultManagerT);
1861            log.debug("using PROXYmanager for Turnouts");
1862        } else {
1863            turnoutManComboBox.setManagers(turnoutManager);
1864        }
1865    }
1866        // TODO store current selection in prefman
1867
1868    /**
1869     * Show only one of the manuf (manager) combo boxes.
1870     *
1871     * @param type one of the three NamedBean types as String
1872     */
1873    protected void displayManagerComboBoxes(String type) {
1874        _type = type;
1875        if (type.equals(LIGHT)) {
1876            Manager<Light> manager = lightManComboBox.getSelectedItem();
1877            if (manager != null) {
1878                memo = manager.getMemo();
1879            }
1880            turnoutManComboBox.setVisible(false);
1881            sensorManComboBox.setVisible(false);
1882            lightManComboBox.setVisible(true);
1883            log.debug("BOX for LightManager set. LightManComboVisible={}", lightManComboBox.isVisible());
1884        } else if (type.equals(SENSOR)) {
1885            Manager<Sensor> manager = sensorManComboBox.getSelectedItem();
1886            if (manager != null) {
1887                memo = manager.getMemo();
1888            }
1889            turnoutManComboBox.setVisible(false);
1890            sensorManComboBox.setVisible(true);
1891            lightManComboBox.setVisible(false);
1892            log.debug("BOX for SensorManager set. SensorManComboVisible={}", sensorManComboBox.isVisible());
1893        } else { // TURNOUT
1894            Manager<Turnout> manager = turnoutManComboBox.getSelectedItem();
1895            if (manager != null) {
1896                memo = manager.getMemo();
1897            }
1898            turnoutManComboBox.setVisible(true);
1899            sensorManComboBox.setVisible(false);
1900            lightManComboBox.setVisible(false);
1901            log.debug("BOX for TurnoutManager set. TurnoutManComboVisible={}", turnoutManComboBox.isVisible());
1902        }
1903    }
1904
1905    public void setIconScale(int size) {
1906        _iconSquare = size;
1907        // also set the scale radio menu items, all 3 are in sizeGroup so will auto deselect
1908        if (size < 100) {
1909            sizeSmall.setSelected(true);
1910        } else if (size > 100) {
1911            sizeLarge.setSelected(true);
1912        } else {
1913            sizeDefault.setSelected(true);
1914        }
1915        updatePressed();
1916    }
1917
1918    public int getIconScale() {
1919        return _iconSquare;
1920    }
1921
1922    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SwitchboardEditor.class);
1923
1924}