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}