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