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