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