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