001package jmri.jmrit.beantable; 002 003import java.awt.BorderLayout; 004import java.awt.Color; 005import java.awt.Container; 006import java.awt.FlowLayout; 007import java.awt.GridLayout; 008import java.awt.event.ActionEvent; 009import java.beans.PropertyChangeListener; 010import java.util.ArrayList; 011 012import javax.annotation.Nonnull; 013import javax.swing.*; 014import javax.swing.border.Border; 015import javax.swing.table.AbstractTableModel; 016import javax.swing.table.TableCellEditor; 017import javax.swing.table.TableColumn; 018import javax.swing.table.TableColumnModel; 019import javax.swing.table.TableRowSorter; 020 021import jmri.InstanceManager; 022import jmri.SignalGroup; 023import jmri.SignalGroupManager; 024import jmri.SignalHead; 025import jmri.SignalHeadManager; 026import jmri.SignalMast; 027import jmri.SignalMastManager; 028import jmri.NamedBean.DisplayOptions; 029import jmri.swing.NamedBeanComboBox; 030import jmri.swing.RowSorterUtil; 031import jmri.util.JmriJFrame; 032import jmri.util.AlphanumComparator; 033import jmri.util.StringUtil; 034import jmri.util.swing.JComboBoxUtil; 035import jmri.util.swing.JmriJOptionPane; 036import jmri.util.table.ButtonEditor; 037import jmri.util.table.ButtonRenderer; 038 039/** 040 * Swing action to create and register a Signal Group Table. 041 * <p> 042 * Based in part on RouteTableAction.java by Bob Jacobsen 043 * 044 * @author Kevin Dickerson Copyright (C) 2010 045 * @author Egbert Broerse 2017, 2018 046 */ 047public class SignalGroupTableAction extends AbstractTableAction<SignalGroup> implements PropertyChangeListener { 048 049 /** 050 * Create an action with a specific title. 051 * <p> 052 * Note that the argument is the Action title, not the title of the 053 * resulting frame. Perhaps this should be changed? 054 * 055 * @param s title of the action 056 */ 057 public SignalGroupTableAction(String s) { 058 super(s); 059 // disable ourself if there is no primary SignalGroup manager available 060 if (InstanceManager.getNullableDefault(SignalGroupManager.class) == null) { 061 super.setEnabled(false); 062 } 063 } 064 065 public SignalGroupTableAction() { 066 this(Bundle.getMessage("TitleSignalGroupTable")); 067 } 068 069 @Override 070 public void propertyChange(java.beans.PropertyChangeEvent e) { 071 if (e.getPropertyName().equals("UpdateCondition")) { 072 for (int i = _signalHeadsList.size() - 1; i >= 0; i--) { 073 SignalGroupSignalHead signalHead = _signalHeadsList.get(i); 074 SignalHead sigBean = signalHead.getBean(); 075 if (curSignalGroup.isHeadIncluded(sigBean)) { 076 signalHead.setIncluded(true); 077 signalHead.setOnState(curSignalGroup.getHeadOnState(sigBean)); 078 signalHead.setOffState(curSignalGroup.getHeadOffState(sigBean)); 079 } else { 080 signalHead.setIncluded(false); 081 } 082 } 083 } 084 } 085 086 /** 087 * Create the JTable DataModel, along with the changes for the specific case 088 * of SignalGroups. 089 */ 090 @Override 091 protected void createModel() { 092 m = new BeanTableDataModel<SignalGroup>() { 093 @SuppressWarnings("hiding") // Field has same name as a field in the super class 094 static public final int COMMENTCOL = 2; 095 @SuppressWarnings("hiding") // Field has same name as a field in the super class 096 static public final int DELETECOL = 3; 097 static public final int ENABLECOL = 4; 098 static public final int EDITCOL = 5; // default name: SETCOL 099 100 @Override 101 public int getColumnCount() { 102 return 6; 103 } 104 105 @Override 106 public String getColumnName(int col) { 107 switch (col) { 108 case EDITCOL: 109 return ""; // no heading on "Edit" column 110 case ENABLECOL: 111 return Bundle.getMessage("ColumnHeadEnabled"); 112 case COMMENTCOL: 113 return Bundle.getMessage("ColumnComment"); 114 case DELETECOL: 115 return ""; 116 default: 117 return super.getColumnName(col); 118 } 119 } 120 121 @Override 122 public Class<?> getColumnClass(int col) { 123 switch (col) { 124 case EDITCOL: 125 case DELETECOL: 126 return JButton.class; 127 case ENABLECOL: 128 return Boolean.class; 129 case COMMENTCOL: 130 return String.class; 131 default: 132 return super.getColumnClass(col); 133 } 134 } 135 136 @Override 137 public int getPreferredWidth(int col) { 138 switch (col) { 139 case EDITCOL: 140 return new JTextField(Bundle.getMessage("ButtonEdit")).getPreferredSize().width; 141 case ENABLECOL: 142 return new JTextField(6).getPreferredSize().width; 143 case COMMENTCOL: 144 return new JTextField(30).getPreferredSize().width; 145 case DELETECOL: 146 return new JTextField(Bundle.getMessage("ButtonDelete")).getPreferredSize().width; 147 default: 148 return super.getPreferredWidth(col); 149 } 150 } 151 152 @Override 153 public boolean isCellEditable(int row, int col) { 154 switch (col) { 155 case COMMENTCOL: 156 case EDITCOL: 157 case ENABLECOL: 158 case DELETECOL: 159 return true; 160 default: 161 return super.isCellEditable(row, col); 162 } 163 } 164 165 @Override 166 public Object getValueAt(int row, int col) { 167 SignalGroup b; 168 switch (col) { 169 case EDITCOL: 170 return Bundle.getMessage("ButtonEdit"); 171 case ENABLECOL: 172 return ((SignalGroup) getValueAt(row, SYSNAMECOL)).getEnabled(); 173 case COMMENTCOL: 174 b = (SignalGroup) getValueAt(row, SYSNAMECOL); 175 return (b != null) ? b.getComment() : null; 176 case DELETECOL: 177 return Bundle.getMessage("ButtonDelete"); 178 default: 179 return super.getValueAt(row, col); 180 } 181 } 182 183 @Override 184 public void setValueAt(Object value, int row, int col) { 185 switch (col) { 186 case EDITCOL: 187 SwingUtilities.invokeLater(() -> { 188 addPressed(null); // set up add/edit panel addFrame (starts as Add pane) 189 _systemName.setText(((SignalGroup) getValueAt(row, SYSNAMECOL)).toString()); 190 editPressed(null); // adjust addFrame for Edit 191 }); 192 break; 193 case ENABLECOL: 194 SignalGroup r = (SignalGroup) getValueAt(row, SYSNAMECOL); 195 r.setEnabled(!(r.getEnabled())); 196 break; 197 case COMMENTCOL: 198 getBySystemName(sysNameList.get(row)).setComment( 199 (String) value); 200 fireTableRowsUpdated(row, row); 201 break; 202 case DELETECOL: 203 deleteBean(row, col); 204 break; 205 default: 206 super.setValueAt(value, row, col); 207 break; 208 } 209 } 210 211 @Override 212 protected void configDeleteColumn(JTable table) { 213 // have the delete column hold a button 214 SignalGroupTableAction.this.setColumnToHoldButton(table, DELETECOL, 215 new JButton(Bundle.getMessage("ButtonDelete"))); 216 } 217 218 /** 219 * Delete the bean after all the checking has been done. 220 * <p> 221 * (Deactivate the Signal Group), then use the superclass to delete 222 * it. 223 */ 224 @Override 225 protected void doDelete(SignalGroup bean) { 226 //((SignalGroup)bean).deActivateSignalGroup(); 227 super.doDelete(bean); 228 } 229 230 // want to update when enabled parameter changes 231 @Override 232 protected boolean matchPropertyName(java.beans.PropertyChangeEvent e) { 233 if (e.getPropertyName().equals("Enabled")) { 234 return true; 235 } else { 236 return super.matchPropertyName(e); 237 } 238 } 239 240 @Override 241 public SignalGroupManager getManager() { 242 return InstanceManager.getDefault(SignalGroupManager.class); 243 } 244 245 @Override 246 public SignalGroup getBySystemName(@Nonnull String name) { 247 return InstanceManager.getDefault(SignalGroupManager.class).getBySystemName(name); 248 } 249 250 @Override 251 public SignalGroup getByUserName(@Nonnull String name) { 252 return InstanceManager.getDefault(SignalGroupManager.class).getByUserName(name); 253 } 254 255 @Override 256 public int getDisplayDeleteMsg() { 257 return 0x00;/*return InstanceManager.getDefault(jmri.UserPreferencesManager.class).getWarnDeleteSignalGroup();*/ } 258 259 @Override 260 public void setDisplayDeleteMsg(int boo) { 261 /*InstanceManager.getDefault(jmri.UserPreferencesManager.class).setWarnDeleteSignalGroup(boo); */ 262 263 } 264 265 @Override 266 protected String getMasterClassName() { 267 return getClassName(); 268 } 269 270 @Override 271 public void clickOn(SignalGroup t) { // mute action 272 //((SignalGroup)t).setSignalGroup(); 273 } 274 275 @Override 276 public String getValue(String s) { // not directly used but should be present to implement abstract class 277 return "Set"; 278 } 279 280 /* public JButton configureButton() { 281 return new JButton(" Set "); 282 }*/ 283 @Override 284 protected String getBeanType() { 285 return "Signal Group"; 286 } 287 }; 288 } 289 290 @Override 291 protected void setTitle() { 292 f.setTitle(Bundle.getMessage("TitleSignalGroupTable")); 293 } 294 295 @Override 296 protected String helpTarget() { 297 return "package.jmri.jmrit.beantable.SignalGroupTable"; 298 } 299 300 /** 301 * Read Appearance for a Signal Group Signal Head from the state comboBox. 302 * <p> 303 * Called from SignalGroupSubTableAction. 304 * 305 * @param box comboBox to read from 306 * @return index representing selected set to appearance for head 307 */ 308 int signalStateFromBox(JComboBox<String> box) { 309 String mode = (String) box.getSelectedItem(); 310 int result = StringUtil.getStateFromName(mode, signalStatesValues, signalStates); 311 312 if (result < 0) { 313 log.warn("unexpected mode string in signalState Aspect: {}", mode); 314 throw new IllegalArgumentException(); 315 } 316 return result; 317 } 318 319 /** 320 * Set Appearance in a Signal Group Signal Head state comboBox. Called from 321 * SignalGroupSubTableAction 322 * 323 * @param mode Value to be set 324 * @param box in which to enter mode 325 */ 326 void setSignalStateBox(int mode, JComboBox<String> box) { 327 String result = StringUtil.getNameFromState(mode, signalStatesValues, signalStates); 328 box.setSelectedItem(result); 329 } 330 331 JTextField _systemName = new JTextField(10); 332 JTextField _userName = new JTextField(22); 333 JCheckBox _autoSystemName = new JCheckBox(Bundle.getMessage("LabelAutoSysName")); 334 String systemNameAuto = this.getClass().getName() + ".AutoSystemName"; 335 jmri.UserPreferencesManager pref; 336 337 JmriJFrame addFrame = null; 338 339 SignalGroupSignalHeadModel _SignalGroupHeadModel; 340 JScrollPane _SignalGroupHeadScrollPane; 341 342 SignalMastAspectModel _AspectModel; 343 JScrollPane _SignalAppearanceScrollPane; 344 345 NamedBeanComboBox<SignalMast> mainSignalComboBox; 346 347 ButtonGroup selGroup = null; 348 JRadioButton allButton = null; 349 JRadioButton includedButton = null; 350 351 JLabel nameLabel = new JLabel(Bundle.getMessage("LabelSystemName"), JLabel.TRAILING); 352 JLabel userLabel = new JLabel(Bundle.getMessage("LabelUserName"), JLabel.TRAILING); 353 JLabel fixedSystemName = new JLabel("xxxxxxxxxxx"); 354 355 JButton deleteButton = new JButton(Bundle.getMessage("ButtonDelete") + " " + Bundle.getMessage("BeanNameSignalGroup")); 356 JButton createButton = new JButton(Bundle.getMessage("ButtonCreate")); 357 JButton updateButton = new JButton(Bundle.getMessage("ButtonApply")); 358 JButton cancelButton = new JButton(Bundle.getMessage("ButtonCancel")); 359 360 static final String createInst = Bundle.getMessage("SignalGroupAddStatusInitial1", Bundle.getMessage("ButtonCreate")); // I18N to include original button name in help string 361 static final String updateInst = Bundle.getMessage("SignalGroupAddStatusInitial3", Bundle.getMessage("ButtonApply")); 362 static final String cancelInst = Bundle.getMessage("SignalGroupAddStatusInitial4", Bundle.getMessage("ButtonCancel")); 363 364 JLabel status1 = new JLabel(createInst); 365 JLabel status2 = new JLabel(cancelInst); 366 367 JPanel p2xs = null; // Container for... 368 JPanel p2xsi = null; // SignalHead list table 369 JPanel p3xsi = null; 370 371 SignalGroup curSignalGroup = null; 372 boolean signalGroupDirty = false; // true to fire reminder to save work 373 private boolean checkEnabled = jmri.InstanceManager.getDefault(jmri.configurexml.ShutdownPreferences.class).isStoreCheckEnabled(); 374 boolean inEditMode = false; // to warn and prevent opening more than 1 editing session 375 376 /** 377 * Respond to click on Add... button below Signal Group Table. 378 * <p> 379 * Create JPanel with options for configuration. 380 * 381 * @param e Event from origin; null when called from Edit button in Signal 382 * Group Table row 383 */ 384 @Override 385 protected void addPressed(ActionEvent e) { 386 pref = InstanceManager.getDefault(jmri.UserPreferencesManager.class); 387 if (inEditMode) { 388 log.debug("Can not open another editing session for Signal Groups."); 389 // add user warning that a 2nd session not allowed (cf. Logix) 390 // Already editing a Signal Group, ask for completion of that edit first 391 String workingTitle = _systemName.getText(); 392 if (workingTitle == null || workingTitle.isEmpty()) { 393 workingTitle = Bundle.getMessage("NONE"); 394 _systemName.setText(workingTitle); 395 } 396 JmriJOptionPane.showMessageDialog(addFrame, 397 Bundle.getMessage("SigGroupEditBusyWarning", workingTitle), 398 Bundle.getMessage("ErrorTitle"), 399 JmriJOptionPane.ERROR_MESSAGE); 400 // cancelEdit(); not needed as second edit is blocked 401 return; 402 } 403 404 //inEditMode = true; 405 _mastAspectsList = null; 406 407 SignalHeadManager shm = InstanceManager.getDefault(SignalHeadManager.class); 408 _signalHeadsList = new ArrayList<>(); 409 // create list of all available Single Output Signal Heads to choose from 410 for (SignalHead sh : shm.getNamedBeanSet()) { 411 String systemName = sh.getSystemName(); 412 if (sh.getClass().getName().contains("SingleTurnoutSignalHead")) { 413 String userName = sh.getUserName(); 414 // add every single output signal head item to the list 415 _signalHeadsList.add(new SignalGroupSignalHead(systemName, userName)); 416 } else { 417 log.debug("Signal Head {} is not a Single Output Controlled Signal Head", systemName); 418 } 419 } 420 421 // Set up Add/Edit Signal Group window 422 if (addFrame == null) { // if it's not yet present, create addFrame 423 424 mainSignalComboBox = new NamedBeanComboBox<>(InstanceManager.getDefault(SignalMastManager.class), null, DisplayOptions.DISPLAYNAME); 425 JComboBoxUtil.setupComboBoxMaxRows(mainSignalComboBox); 426 mainSignalComboBox.setAllowNull(true); // causes NPE when user selects that 1st line, so do not respond to result null 427 addFrame = new JmriJFrame(Bundle.getMessage("AddSignalGroup"), false, true); 428 addFrame.addHelpMenu("package.jmri.jmrit.beantable.SignalGroupAddEdit", true); 429 addFrame.setEscapeKeyClosesWindow(true); 430 addFrame.setLocation(100, 30); 431 addFrame.getContentPane().setLayout(new BoxLayout(addFrame.getContentPane(), BoxLayout.Y_AXIS)); 432 Container contentPane = addFrame.getContentPane(); 433 434 JPanel namesGrid = new JPanel(); 435 GridLayout layout = new GridLayout(2, 2, 10, 0); // (int rows, int cols, int hgap, int vgap) 436 namesGrid.setLayout(layout); 437 // row 1: add system name label + field/label 438 namesGrid.add(nameLabel); 439 nameLabel.setLabelFor(_systemName); 440 JPanel ps = new JPanel(); 441 ps.setLayout(new BoxLayout(ps, BoxLayout.X_AXIS)); 442 ps.add(_systemName); 443 _systemName.setToolTipText(Bundle.getMessage("SignalGroupSysNameTooltip")); 444 ps.add(fixedSystemName); 445 fixedSystemName.setVisible(false); 446 ps.add(_autoSystemName); 447 _autoSystemName.addActionListener((ActionEvent e1) -> { 448 autoSystemName(); 449 }); 450 if (pref.getSimplePreferenceState(systemNameAuto)) { 451 _autoSystemName.setSelected(true); 452 } 453 namesGrid.add(ps); 454 // row 2: add user name label + field 455 namesGrid.add(userLabel); 456 userLabel.setLabelFor(_userName); 457 JPanel p = new JPanel(); 458 p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS)); 459 p.add(_userName); 460 _userName.setToolTipText(Bundle.getMessage("SignalGroupUserNameTooltip")); 461 namesGrid.add(p); 462 contentPane.add(namesGrid); 463 464 // add Signal Masts/Heads Display Choice 465 JPanel py = new JPanel(); 466 py.add(new JLabel(Bundle.getMessage("Show"))); 467 selGroup = new ButtonGroup(); 468 allButton = new JRadioButton(Bundle.getMessage("All"), true); 469 selGroup.add(allButton); 470 py.add(allButton); 471 allButton.addActionListener((ActionEvent e1) -> { 472 // Setup for display of all Signal Masts & SingleTO Heads, if needed 473 if (!showAll) { 474 showAll = true; 475 _SignalGroupHeadModel.fireTableDataChanged(); 476 _AspectModel.fireTableDataChanged(); 477 } 478 }); 479 includedButton = new JRadioButton(Bundle.getMessage("Included"), false); 480 selGroup.add(includedButton); 481 py.add(includedButton); 482 includedButton.addActionListener((ActionEvent e1) -> { 483 // Setup for display of included Turnouts only, if needed 484 if (showAll) { 485 showAll = false; 486 initializeIncludedList(); 487 _SignalGroupHeadModel.fireTableDataChanged(); 488 _AspectModel.fireTableDataChanged(); 489 } 490 }); 491 py.add(new JLabel(" " + Bundle.getMessage("_and_", Bundle.getMessage("LabelAspects"), 492 Bundle.getMessage("SignalHeads")))); 493 contentPane.add(py); 494 495 // add main signal mast table 496 JPanel p3 = new JPanel(); 497 p3.setLayout(new BoxLayout(p3, BoxLayout.Y_AXIS)); 498 JPanel p31 = new JPanel(); 499 p31.add(new JLabel(Bundle.getMessage("EnterMastAttached", Bundle.getMessage("BeanNameSignalMast")))); 500 p3.add(p31); 501 JPanel p32 = new JPanel(); 502 p32.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("BeanNameSignalMast")))); 503 p32.add(mainSignalComboBox); // comboBox to pick a main Signal Mast 504 p3.add(p32); 505 506 p3xsi = new JPanel(); 507 JPanel p3xsiSpace = new JPanel(); 508 p3xsiSpace.setLayout(new BoxLayout(p3xsiSpace, BoxLayout.Y_AXIS)); 509 p3xsiSpace.add(new JLabel(" ")); 510 p3xsi.add(p3xsiSpace); 511 512 JPanel p31si = new JPanel(); 513 p31si.setLayout(new BoxLayout(p31si, BoxLayout.Y_AXIS)); 514 p31si.add(new JLabel(Bundle.getMessage("SelectAppearanceTrigger"))); 515 516 p3xsi.add(p31si); 517 _AspectModel = new SignalMastAspectModel(); 518 JTable SignalMastAspectTable = new JTable(_AspectModel); 519 TableRowSorter<SignalMastAspectModel> smaSorter = new TableRowSorter<>(_AspectModel); 520 smaSorter.setComparator(SignalMastAspectModel.ASPECT_COLUMN, new AlphanumComparator()); 521 RowSorterUtil.setSortOrder(smaSorter, SignalMastAspectModel.ASPECT_COLUMN, SortOrder.ASCENDING); 522 SignalMastAspectTable.setRowSorter(smaSorter); 523 SignalMastAspectTable.setRowSelectionAllowed(false); 524 SignalMastAspectTable.setPreferredScrollableViewportSize(new java.awt.Dimension(200, 80)); 525 TableColumnModel SignalMastAspectColumnModel = SignalMastAspectTable.getColumnModel(); 526 TableColumn includeColumnA = SignalMastAspectColumnModel. 527 getColumn(SignalGroupTableAction.SignalMastAspectModel.INCLUDE_COLUMN); 528 includeColumnA.setResizable(false); 529 includeColumnA.setMinWidth(30); 530 includeColumnA.setMaxWidth(60); 531 @SuppressWarnings("static-access") 532 TableColumn sNameColumnA = SignalMastAspectColumnModel. 533 getColumn(_AspectModel.ASPECT_COLUMN); 534 sNameColumnA.setResizable(true); 535 sNameColumnA.setMinWidth(75); 536 sNameColumnA.setMaxWidth(140); 537 538 _SignalAppearanceScrollPane = new JScrollPane(SignalMastAspectTable); 539 p3xsi.add(_SignalAppearanceScrollPane, BorderLayout.CENTER); 540 p3.add(p3xsi); 541 p3xsi.setVisible(true); 542 543 mainSignalComboBox.addActionListener( // respond to comboBox selection 544 (ActionEvent event) -> { 545 if (mainSignalComboBox.getSelectedItem() == null) { // ie. empty first row was selected or set 546 log.debug("Empty line in mainSignal comboBox"); 547 //setValidSignalMastAspects(); // clears the Aspect table 548 } else { 549 if (curSignalGroup == null 550 || mainSignalComboBox.getSelectedItem() != curSignalGroup.getSignalMast()) { 551 log.debug("comboBox closed, choice: {}", mainSignalComboBox.getSelectedItem()); 552 setValidSignalMastAspects(); // refresh table with signal mast aspects 553 } else { 554 log.debug("Mast {} picked in mainSignal comboBox", mainSignalComboBox.getSelectedItem()); 555 } 556 } 557 }); 558 559 // complete this panel 560 Border p3Border = BorderFactory.createEtchedBorder(); 561 p3.setBorder(p3Border); 562 contentPane.add(p3); 563 564 p2xsi = new JPanel(); 565 JPanel p2xsiSpace = new JPanel(); 566 p2xsiSpace.setLayout(new BoxLayout(p2xsiSpace, BoxLayout.Y_AXIS)); 567 p2xsiSpace.add(new JLabel("XXX")); 568 p2xsi.add(p2xsiSpace); 569 570 JPanel p21si = new JPanel(); 571 p21si.setLayout(new BoxLayout(p21si, BoxLayout.Y_AXIS)); 572 p21si.add(new JLabel(Bundle.getMessage("SelectInGroup", Bundle.getMessage("SignalHeads")))); 573 p2xsi.add(p21si); 574 _SignalGroupHeadModel = new SignalGroupSignalHeadModel(); 575 JTable SignalGroupHeadTable = new JTable(_SignalGroupHeadModel); 576 TableRowSorter<SignalGroupSignalHeadModel> sgsSorter = new TableRowSorter<>(_SignalGroupHeadModel); 577 578 // use NamedBean's built-in Comparator interface for sorting the system name column 579 RowSorterUtil.setSortOrder(sgsSorter, SignalGroupSignalHeadModel.SNAME_COLUMN, SortOrder.ASCENDING); 580 SignalGroupHeadTable.setRowSorter(sgsSorter); 581 SignalGroupHeadTable.setRowSelectionAllowed(false); 582 SignalGroupHeadTable.setPreferredScrollableViewportSize(new java.awt.Dimension(480, 160)); 583 TableColumnModel SignalGroupSignalColumnModel = SignalGroupHeadTable.getColumnModel(); 584 585 TableColumn includeColumnSi = SignalGroupSignalColumnModel. 586 getColumn(SignalGroupSignalHeadModel.INCLUDE_COLUMN); 587 includeColumnSi.setResizable(false); 588 includeColumnSi.setMinWidth(30); 589 includeColumnSi.setMaxWidth(60); 590 591 TableColumn sNameColumnSi = SignalGroupSignalColumnModel. 592 getColumn(SignalGroupSignalHeadModel.SNAME_COLUMN); 593 sNameColumnSi.setResizable(true); 594 sNameColumnSi.setMinWidth(75); 595 sNameColumnSi.setMaxWidth(95); 596 597 TableColumn uNameColumnSi = SignalGroupSignalColumnModel. 598 getColumn(SignalGroupSignalHeadModel.UNAME_COLUMN); 599 uNameColumnSi.setResizable(true); 600 uNameColumnSi.setMinWidth(100); 601 uNameColumnSi.setMaxWidth(260); 602 603 TableColumn stateOnColumnSi = SignalGroupSignalColumnModel. 604 getColumn(SignalGroupSignalHeadModel.STATE_ON_COLUMN); // a 6 column table 605 stateOnColumnSi.setResizable(false); 606 stateOnColumnSi.setMinWidth(Bundle.getMessage("SignalHeadStateFlashingYellow").length()); // was 50 607 stateOnColumnSi.setMaxWidth(100); 608 609 TableColumn stateOffColumnSi = SignalGroupSignalColumnModel. 610 getColumn(SignalGroupSignalHeadModel.STATE_OFF_COLUMN); 611 stateOffColumnSi.setResizable(false); 612 stateOffColumnSi.setMinWidth(50); 613 stateOffColumnSi.setMaxWidth(100); 614 615 TableColumn editColumnSi = SignalGroupSignalColumnModel. 616 getColumn(SignalGroupSignalHeadModel.EDIT_COLUMN); 617 editColumnSi.setResizable(false); 618 editColumnSi.setMinWidth(Bundle.getMessage("ButtonEdit").length()); // was 50 619 editColumnSi.setMaxWidth(100); 620 JButton editButton = new JButton(Bundle.getMessage("ButtonEdit")); 621 setColumnToHoldButton(SignalGroupHeadTable, SignalGroupSignalHeadModel.EDIT_COLUMN, editButton); 622 623 _SignalGroupHeadScrollPane = new JScrollPane(SignalGroupHeadTable); 624 p2xsi.add(_SignalGroupHeadScrollPane, BorderLayout.CENTER); 625 p2xsi.setToolTipText(Bundle.getMessage("SignalGroupHeadTableTooltip")); // add tooltip to explain which head types are shown 626 contentPane.add(p2xsi); 627 p2xsi.setVisible(true); 628 629 // add notes panel 630 JPanel pa = new JPanel(); 631 pa.setLayout(new BoxLayout(pa, BoxLayout.Y_AXIS)); 632 // include status bar 633 JPanel p1 = new JPanel(); 634 p1.setLayout(new FlowLayout()); 635 status1.setFont(status1.getFont().deriveFont(0.9f * nameLabel.getFont().getSize())); // a bit smaller 636 status1.setForeground(Color.gray); 637 p1.add(status1); 638 JPanel p2 = new JPanel(); 639 p2.setLayout(new FlowLayout()); 640 status2.setFont(status1.getFont().deriveFont(0.9f * nameLabel.getFont().getSize())); // a bit smaller 641 status2.setForeground(Color.gray); 642 p2.add(status2); 643 pa.add(p1); 644 pa.add(p2); 645 646 Border pBorder = BorderFactory.createEtchedBorder(); 647 pa.setBorder(pBorder); 648 contentPane.add(pa); 649 650 // buttons at bottom of panel 651 JPanel pb = new JPanel(); 652 pb.setLayout(new FlowLayout(FlowLayout.TRAILING)); 653 654 pb.add(cancelButton); 655 cancelButton.addActionListener(this::cancelPressed); 656 cancelButton.setVisible(true); 657 pb.add(deleteButton); 658 deleteButton.addActionListener(this::deletePressed); 659 deleteButton.setToolTipText(Bundle.getMessage("DeleteSignalGroupInSystem")); 660 // Add Create Group button 661 pb.add(createButton); 662 createButton.addActionListener(this::createPressed); 663 createButton.setToolTipText(Bundle.getMessage("TooltipCreateGroup")); 664 // [Update] Signal Group button in Add/Edit SignalGroup pane 665 pb.add(updateButton); 666 updateButton.addActionListener((ActionEvent e1) -> { 667 updatePressed(e1, false, false); 668 }); 669 updateButton.setToolTipText(Bundle.getMessage("TooltipUpdateGroup")); 670 671 contentPane.add(pb); 672 // pack and release space 673 addFrame.pack(); 674 p2xsiSpace.setVisible(false); 675 } else { 676 mainSignalComboBox.setSelectedItem(null); 677 addFrame.setTitle(Bundle.getMessage("AddSignalGroup")); // reset title for new group 678 } 679 status1.setText(createInst); 680 _autoSystemName.setVisible(true); 681 updateButton.setVisible(false); 682 createButton.setVisible(true); 683 // set listener for window closing 684 addFrame.addWindowListener(new java.awt.event.WindowAdapter() { 685 @Override 686 public void windowClosing(java.awt.event.WindowEvent e) { 687 // remind to save, if Signal Group was created or edited 688 if (signalGroupDirty && !checkEnabled) { 689 InstanceManager.getDefault(jmri.UserPreferencesManager.class). 690 showInfoMessage(Bundle.getMessage("ReminderTitle"), 691 Bundle.getMessage("ReminderSaveString", Bundle.getMessage("SignalGroup")), 692 "beantable.SignalGroupTableAction", 693 "remindSignalGroup"); // NOI18N 694 signalGroupDirty = false; 695 } 696 // hide addFrame 697 if (addFrame != null) { 698 addFrame.setVisible(false); 699 } // hide first, could be gone by the time of the close event, so prevent NPE 700 inEditMode = false; // release editing soon, as long as NPEs occor in the following methods 701 finishUpdate(); 702 _SignalGroupHeadModel.dispose(); 703 _AspectModel.dispose(); 704 } 705 }); 706 // display the pane 707 addFrame.setVisible(true); 708 autoSystemName(); 709 } 710 711 void autoSystemName() { 712 if (_autoSystemName.isSelected()) { 713 _systemName.setEnabled(false); 714 nameLabel.setEnabled(false); 715 } else { 716 _systemName.setEnabled(true); 717 nameLabel.setEnabled(true); 718 } 719 } 720 721 void setColumnToHoldButton(JTable table, int column, JButton sample) { 722 // install a button renderer & editor 723 ButtonRenderer buttonRenderer = new ButtonRenderer(); 724 table.setDefaultRenderer(JButton.class, buttonRenderer); 725 TableCellEditor buttonEditor = new ButtonEditor(new JButton()); 726 table.setDefaultEditor(JButton.class, buttonEditor); 727 // ensure the table rows, columns have enough room for buttons 728 table.setRowHeight(sample.getPreferredSize().height); 729 table.getColumnModel().getColumn(column) 730 .setPreferredWidth((sample.getPreferredSize().width) + 4); 731 } 732 733 /** 734 * Initialize list of included signal head appearances for when "Included" 735 * is selected. 736 */ 737 void initializeIncludedList() { 738 _includedMastAspectsList = new ArrayList<>(); 739 for (int i = 0; i < _mastAspectsList.size(); i++) { 740 if (_mastAspectsList.get(i).isIncluded()) { 741 _includedMastAspectsList.add(_mastAspectsList.get(i)); 742 } 743 } 744 _includedSignalHeadsList = new ArrayList<>(); 745 for (int i = 0; i < _signalHeadsList.size(); i++) { 746 if (_signalHeadsList.get(i).isIncluded()) { 747 _includedSignalHeadsList.add(_signalHeadsList.get(i)); 748 } 749 } 750 } 751 752 /** 753 * Respond to the Create button. 754 * 755 * @param e the action event 756 */ 757 void createPressed(ActionEvent e) { 758 if (!_autoSystemName.isSelected()) { 759 if (!checkNewNamesOK()) { 760 log.debug("NewNames not OK"); 761 return; 762 } 763 } 764 updatePressed(e, true, true); // to close pane after creating 765 pref.setSimplePreferenceState(systemNameAuto, _autoSystemName.isSelected()); 766 // activate the signal group 767 } 768 769 /** 770 * Check name for a new SignalGroup object using the _systemName field on 771 * the addFrame pane. Not used when autoSystemName is checked. 772 * 773 * @return whether name entered is allowed 774 */ 775 boolean checkNewNamesOK() { 776 // Get system name and user name from Add Signal Group pane 777 String sName = _systemName.getText(); 778 String uName = _userName.getText(); // may be empty 779 if (sName.length() == 0) { // show warning in status bar 780 status1.setText(Bundle.getMessage("AddBeanStatusEnter")); 781 status1.setForeground(Color.red); 782 return false; 783 } 784 SignalGroup g; 785 // check if a SignalGroup with the same user name exists 786 if (!uName.isEmpty()) { 787 g = InstanceManager.getDefault(SignalGroupManager.class).getByUserName(uName); 788 if (g != null) { 789 // SignalGroup already exists 790 status1.setText(Bundle.getMessage("SignalGroupDuplicateUserNameWarning", uName)); 791 return false; 792 } 793 } 794 // check if a SignalGroup with this system name already exists 795 sName = InstanceManager.getDefault(SignalGroupManager.class).makeSystemName(sName); 796 g = InstanceManager.getDefault(SignalGroupManager.class).getBySystemName(sName); 797 if (g != null) { 798 // SignalGroup already exists 799 status1.setText(Bundle.getMessage("SignalGroupDuplicateSystemNameWarning", sName)); 800 return false; 801 } 802 return true; 803 } 804 805 /** 806 * Check selection in Main Mast comboBox and store object as mMast for 807 * further calculations. 808 * 809 * @return The new/updated SignalGroup object 810 */ 811 boolean checkValidSignalMast() { 812 SignalMast mMast = mainSignalComboBox.getSelectedItem(); 813 if (mMast == null) { 814 //log.warn("Signal Mast not selected. mainSignal = {}", mainSignalComboBox.getSelectedItem()); 815 JmriJOptionPane.showMessageDialog(null, 816 Bundle.getMessage("NoMastSelectedWarning"), 817 Bundle.getMessage("ErrorTitle"), 818 JmriJOptionPane.WARNING_MESSAGE); 819 return false; 820 } 821 return true; 822 } 823 824 /** 825 * Check name and return a new or existing SignalGroup object with the name 826 * as entered in the _systemName field on the addFrame pane. 827 * 828 * @return The new/updated SignalGroup object 829 */ 830 SignalGroup checkNamesOK() { 831 // Get system name and user name 832 String sName = _systemName.getText(); 833 String uName = _userName.getText(); 834 SignalGroup g; 835 if (_autoSystemName.isSelected() && !inEditMode) { 836 // create new Signal Group with auto system name 837 log.debug("SignalGroupTableAction checkNamesOK new autogroup"); 838 g = InstanceManager.getDefault(SignalGroupManager.class).newSignalGroupWithUserName(uName); 839 } else { 840 if (sName.length() == 0) { // show warning in status bar 841 status1.setText(Bundle.getMessage("AddBeanStatusEnter")); 842 status1.setForeground(Color.red); 843 return null; 844 } 845 try { 846 sName = InstanceManager.getDefault(SignalGroupManager.class).makeSystemName(sName); 847 g = InstanceManager.getDefault(SignalGroupManager.class).provideSignalGroup(sName, uName); 848 } catch (IllegalArgumentException ex) { 849 log.error("checkNamesOK; Unknown failure to create Signal Group with System Name: {}", sName); // NOI18N 850 g = null; // for later check 851 } 852 } 853 if (g == null) { 854 // should never get here 855 log.error("Unknown failure to create Signal Group with System Name: {}", sName); // NOI18N 856 } 857 return g; 858 } 859 860 /** 861 * Check all available Single Output Signal Heads against the list of signal 862 * head items registered with the group. Updates the list, which is stored 863 * in the field _includedSignalHeadsList. 864 * 865 * @param g Signal Group object 866 * @return The number of Signal Heads included in the group 867 */ 868 int setHeadInformation(SignalGroup g) { 869 for (int i = 0; i < g.getNumHeadItems(); i++) { 870 SignalHead sig = g.getHeadItemBeanByIndex(i); 871 boolean valid = false; 872 for (int x = 0; x < _includedSignalHeadsList.size(); x++) { 873 SignalGroupSignalHead sh = _includedSignalHeadsList.get(x); 874 if (sig == sh.getBean()) { 875 valid = true; 876 break; 877 } 878 } 879 if (!valid) { 880 g.deleteSignalHead(sig); 881 } 882 } 883 for (int i = 0; i < _includedSignalHeadsList.size(); i++) { 884 SignalGroupSignalHead s = _includedSignalHeadsList.get(i); 885 SignalHead sig = s.getBean(); 886 if (!g.isHeadIncluded(sig)) { 887 g.addSignalHead(sig); 888 g.setHeadOnState(sig, s.getOnStateInt()); 889 g.setHeadOffState(sig, s.getOffStateInt()); 890 } 891 } 892 return _includedSignalHeadsList.size(); 893 } 894 895 /** 896 * Store included Aspects for the selected main Signal Mast in the Signal 897 * Group 898 * 899 * @param g Signal Group object 900 */ 901 void setMastAspectInformation(SignalGroup g) { 902 g.clearSignalMastAspect(); 903 for (int x = 0; x < _includedMastAspectsList.size(); x++) { 904 g.addSignalMastAspect(_includedMastAspectsList.get(x).getAspect()); 905 } 906 } 907 908 /** 909 * Look up the list of valid Aspects for the selected main Signal Mast in 910 * the comboBox and store them in a table on the addFrame using _AspectModel 911 */ 912 void setValidSignalMastAspects() { 913 SignalMast sm = mainSignalComboBox.getSelectedItem(); 914 if (sm == null) { 915 log.debug("Null picked in mainSignal comboBox. Probably line 1 or no masts in system"); 916 return; 917 } 918 log.debug("Mast {} picked in mainSignal comboBox", mainSignalComboBox.getSelectedItem()); 919 java.util.Vector<String> aspects = sm.getValidAspects(); 920 921 _mastAspectsList = new ArrayList<>(aspects.size()); 922 for (int i = 0; i < aspects.size(); i++) { 923 _mastAspectsList.add(new SignalMastAspect(aspects.get(i))); 924 } 925 _AspectModel.fireTableDataChanged(); 926 } 927 928 /** 929 * When user clicks Cancel during editing a Signal Group, close the 930 * Add/Edit pane and reset default entries. 931 * 932 * @param e Event from origin 933 */ 934 void cancelPressed(ActionEvent e) { 935 cancelEdit(); 936 } 937 938 /** 939 * Cancels edit mode 940 */ 941 void cancelEdit() { 942 if (inEditMode) { 943 status1.setText(createInst); 944 } 945 if (addFrame != null) { 946 addFrame.setVisible(false); 947 } // hide first, may cause NPE unchecked 948 inEditMode = false; // release editing soon, as NPEs may occur in the following methods 949 finishUpdate(); 950 _SignalGroupHeadModel.dispose(); 951 _AspectModel.dispose(); 952 } 953 954 /** 955 * Respond to the Edit button in the Signal Group Table after creating the 956 * Add/Edit pane with AddPressed supplying _SystemName. Hides the editable 957 * _systemName field on the Add Group pane and displays the value as a label 958 * instead. 959 * 960 * @param e Event from origin, null if invoked by clicking the Edit button 961 * in a Signal Group Table row 962 */ 963 void editPressed(ActionEvent e) { 964 // identify the Signal Group with this name if it already exists 965 String sName = InstanceManager.getDefault(SignalGroupManager.class).makeSystemName(_systemName.getText()); 966 // sName is already filled in from the Signal Group table by addPressed() 967 SignalGroup g = InstanceManager.getDefault(SignalGroupManager.class).getBySystemName(sName); 968 if (g == null) { 969 // Signal Group does not exist, so cannot be edited 970 return; 971 } 972 g.addPropertyChangeListener(this); 973 974 // Signal Group was found, make its system name not changeable 975 curSignalGroup = g; 976 log.debug("curSignalGroup was set"); 977 978 SignalMast sm = InstanceManager.getDefault(SignalMastManager.class).getSignalMast(g.getSignalMastName()); 979 if (sm != null) { 980 java.util.Vector<String> aspects = sm.getValidAspects(); 981 _mastAspectsList = new ArrayList<>(aspects.size()); 982 983 for (int i = 0; i < aspects.size(); i++) { 984 _mastAspectsList.add(new SignalMastAspect(aspects.get(i))); 985 } 986 } else { 987 log.error("Failed to get signal mast {}", g.getSignalMastName()); // false indicates Can't find mast 988 } 989 990 nameLabel.setEnabled(true); 991 fixedSystemName.setText(sName); 992 fixedSystemName.setVisible(true); 993 _systemName.setVisible(false); 994 mainSignalComboBox.setSelectedItem(g.getSignalMast()); 995 _userName.setText(g.getUserName()); 996 997 int setRow = 0; 998 999 for (int i = _signalHeadsList.size() - 1; i >= 0; i--) { 1000 SignalGroupSignalHead sgsh = _signalHeadsList.get(i); 1001 SignalHead sigBean = sgsh.getBean(); 1002 if (g.isHeadIncluded(sigBean)) { 1003 sgsh.setIncluded(true); 1004 sgsh.setOnState(g.getHeadOnState(sigBean)); 1005 sgsh.setOffState(g.getHeadOffState(sigBean)); 1006 setRow = i; 1007 } else { 1008 sgsh.setIncluded(false); 1009 } 1010 } 1011 _SignalGroupHeadScrollPane.getVerticalScrollBar().setValue(setRow * ROW_HEIGHT); 1012 _SignalGroupHeadModel.fireTableDataChanged(); 1013 1014 for (int i = 0; i < _mastAspectsList.size(); i++) { 1015 SignalMastAspect _aspect = _mastAspectsList.get(i); 1016 String asp = _aspect.getAspect(); 1017 if (g.isSignalMastAspectIncluded(asp)) { 1018 _aspect.setIncluded(true); 1019 setRow = i; 1020 } else { 1021 _aspect.setIncluded(false); 1022 } 1023 } 1024 _SignalAppearanceScrollPane.getVerticalScrollBar().setValue(setRow * ROW_HEIGHT); 1025 1026 _AspectModel.fireTableDataChanged(); 1027 initializeIncludedList(); 1028 1029 signalGroupDirty = true; // to fire reminder to save work 1030 // set up buttons and notes fot Edit 1031 status1.setText(updateInst); 1032 updateButton.setVisible(true); 1033 createButton.setVisible(false); 1034 _autoSystemName.setVisible(false); 1035 fixedSystemName.setVisible(true); 1036 _systemName.setVisible(false); 1037 addFrame.setTitle(Bundle.getMessage("EditSignalGroup")); 1038 addFrame.setEscapeKeyClosesWindow(true); 1039 inEditMode = true; // to block opening another edit session 1040 } 1041 1042 /** 1043 * Respond to the Delete button in the Add/Edit pane. 1044 * 1045 * @param e the event heard 1046 */ 1047 void deletePressed(ActionEvent e) { 1048 InstanceManager.getDefault(SignalGroupManager.class).deleteSignalGroup(curSignalGroup); 1049 curSignalGroup = null; 1050 log.debug("DeletePressed; curSignalGroup set to null"); 1051 finishUpdate(); 1052 } 1053 1054 /** 1055 * Respond to the Update button on the Edit Signal Group pane - store new 1056 * properties in the Signal Group. 1057 * 1058 * @param e Event from origin, null if invoked by clicking the 1059 * Edit button in a Signal Group Table row 1060 * @param newSignalGroup False when called as Update, True after editing 1061 * Signal Head details 1062 * @param close True if the pane is closing, False if it stays open 1063 */ 1064 void updatePressed(ActionEvent e, boolean newSignalGroup, boolean close) { 1065 // Check if the User Name has been changed 1066 String uName = _userName.getText(); 1067 SignalGroup g = checkNamesOK(); // look up signal group under edit. If this fails, we are stuck 1068 if (g == null) { // error logging/dialog handled in checkNamesOK() 1069 log.debug("null signalGroup under edit"); 1070 return; 1071 } 1072 // We might want to check if the User Name has been changed. But there's 1073 // nothing to compare with so this is propably a newly created Signal Group. 1074 // TODO cannot be compared since curSignalGroup is null, causes NPE 1075 if (!checkValidSignalMast()) { 1076 log.debug("invalid signal mast under edit"); 1077 return; 1078 } 1079 // user name is unique, change it 1080 g.setUserName(uName); 1081 initializeIncludedList(); 1082 setHeadInformation(g); 1083 setMastAspectInformation(g); 1084 g.setSignalMast(mainSignalComboBox.getSelectedItem(), mainSignalComboBox.getSelectedItemDisplayName()); 1085 1086 signalGroupDirty = true; // to fire reminder to save work 1087 curSignalGroup = g; 1088 if (close) { 1089 finishUpdate(); 1090 } 1091 status1.setForeground(Color.gray); 1092 status1.setText((newSignalGroup ? Bundle.getMessage("SignalGroupAddStatusCreated") : Bundle.getMessage("SignalGroupAddStatusUpdated")) 1093 + ": \"" + uName + "\""); 1094 } 1095 1096 /** 1097 * Clean up the Edit Signal Group pane. 1098 */ 1099 void finishUpdate() { 1100 if (curSignalGroup != null) { 1101 curSignalGroup.removePropertyChangeListener(this); 1102 } 1103 _systemName.setVisible(true); 1104 fixedSystemName.setVisible(false); 1105 _systemName.setText(""); 1106 _userName.setText(""); 1107 _autoSystemName.setVisible(true); 1108 autoSystemName(); 1109 // clear page 1110 mainSignalComboBox.setSelectedItem(null); // empty the "main mast" comboBox 1111 if (_signalHeadsList == null) { 1112 // prevent NPE when clicking Cancel/close pane with no work done, after first display of pane (no mast selected) 1113 log.debug("FinishUpdate; _signalHeadsList empty; no heads present"); 1114 } else { 1115 for (int i = _signalHeadsList.size() - 1; i >= 0; i--) { 1116 _signalHeadsList.get(i).setIncluded(false); 1117 } 1118 } 1119 if (_mastAspectsList == null) { 1120 // prevent NPE when clicking Cancel/close pane with no work done, after first display of pane (no mast selected) 1121 log.debug("FinishUpdate; _mastAspectsList empty; no mast was selected"); 1122 } else { 1123 for (int i = _mastAspectsList.size() - 1; i >= 0; i--) { 1124 _mastAspectsList.get(i).setIncluded(false); 1125 } 1126 } 1127 inEditMode = false; 1128 showAll = true; 1129 curSignalGroup = null; 1130 log.debug("FinishUpdate; curSignalGroup set to null. Hiding addFrame next"); 1131 if (addFrame != null) { 1132 addFrame.setVisible(false); 1133 } 1134 } 1135 1136 /** 1137 * Table Model for masts and their "Set To" aspect. 1138 */ 1139 public class SignalMastAspectModel extends AbstractTableModel implements PropertyChangeListener { 1140 1141 @Override 1142 public Class<?> getColumnClass(int c) { 1143 if (c == INCLUDE_COLUMN) { 1144 return Boolean.class; 1145 } else { 1146 return String.class; 1147 } 1148 } 1149 1150 @Override 1151 public String getColumnName(int col) { 1152 if (col == INCLUDE_COLUMN) { 1153 return Bundle.getMessage("Include"); 1154 } 1155 if (col == ASPECT_COLUMN) { 1156 return Bundle.getMessage("LabelAspectType"); 1157 // list contains Signal Mast Aspects (might be called "Appearances" by some but in code keep to JMRI bean names and Help) 1158 } 1159 return ""; 1160 } 1161 1162 public void dispose() { 1163 InstanceManager.getDefault(SignalMastManager.class).removePropertyChangeListener(this); 1164 } 1165 1166 @Override 1167 public void propertyChange(java.beans.PropertyChangeEvent e) { 1168 if (e.getPropertyName().equals("length")) { 1169 // a new NamedBean is available in the manager 1170 fireTableDataChanged(); 1171 } 1172 } 1173 1174 @Override 1175 public int getColumnCount() { 1176 return 2; 1177 } 1178 1179 @Override 1180 public boolean isCellEditable(int r, int c) { 1181 return ((c == INCLUDE_COLUMN)); 1182 } 1183 1184 public static final int ASPECT_COLUMN = 0; 1185 public static final int INCLUDE_COLUMN = 1; 1186 1187 public void setSetToState(String x) { 1188 } 1189 1190 @Override 1191 public int getRowCount() { 1192 if (_mastAspectsList == null) { 1193 return 0; 1194 } 1195 else if (showAll) { 1196 return _mastAspectsList.size(); 1197 } else { 1198 return _includedMastAspectsList.size(); 1199 } 1200 } 1201 1202 @Override 1203 public Object getValueAt(int r, int c) { 1204 ArrayList<SignalMastAspect> aspectList = ( showAll ? _mastAspectsList : _includedMastAspectsList); 1205 // some error checking 1206 if (aspectList == null || r >= aspectList.size()) { 1207 // prevent NPE when clicking Add... in table to add new group (with 1 group existing using a different mast type) 1208 if (aspectList == null) { 1209 log.debug("SGTA getValueAt: row value {} aspectList is null", r); 1210 } else { 1211 log.debug("SGTA getValueAt: row value {} is greater than aspectList size {}", r, aspectList.size()); 1212 } 1213 return null; 1214 } 1215 switch (c) { 1216 case INCLUDE_COLUMN: 1217 return aspectList.get(r).isIncluded(); 1218 case ASPECT_COLUMN: 1219 return aspectList.get(r).getAspect(); 1220 default: 1221 return null; 1222 } 1223 } 1224 1225 @Override 1226 public void setValueAt(Object type, int r, int c) { 1227 log.debug("SigGroupEditSet A; row = {}", r); 1228 ArrayList<SignalMastAspect> aspectList = ( showAll ? _mastAspectsList : _includedMastAspectsList); 1229 if (_mastAspectsList == null || r >= aspectList.size()) { 1230 // prevent NPE when closing window after NPE in getValueAdd() happened 1231 log.debug("row value {} is greater than aspectList size {}", r, aspectList); 1232 return; 1233 } 1234 log.debug("SigGroupEditSet B; row = {}; aspectList.size() = {}.", r, aspectList.size()); 1235 switch (c) { 1236 case INCLUDE_COLUMN: 1237 aspectList.get(r).setIncluded(((Boolean) type)); 1238 break; 1239 case ASPECT_COLUMN: 1240 aspectList.get(r).setAspect((String) type); 1241 break; 1242 default: 1243 break; 1244 } 1245 } 1246 1247 } 1248 1249 /** 1250 * Base table model for managing generic Signal Group outputs. 1251 */ 1252 public abstract class SignalGroupOutputModel extends AbstractTableModel implements PropertyChangeListener { 1253 1254 @Override 1255 public Class<?> getColumnClass(int c) { 1256 if (c == INCLUDE_COLUMN) { 1257 return Boolean.class; 1258 } else { 1259 return String.class; 1260 } 1261 } 1262 1263 @Override 1264 public void propertyChange(java.beans.PropertyChangeEvent e) { 1265 if (e.getPropertyName().equals("length")) { 1266 // a new NamedBean is available in the manager 1267 fireTableDataChanged(); 1268 } else if (e.getPropertyName().equals("UpdateCondition")) { 1269 fireTableDataChanged(); 1270 } 1271 } 1272 1273 @Override 1274 public String getColumnName(int c) { 1275 return COLUMN_NAMES[c]; 1276 } 1277 1278 @Override 1279 public int getColumnCount() { 1280 return 4; 1281 } 1282 1283 @Override 1284 public boolean isCellEditable(int r, int c) { 1285 return ((c == INCLUDE_COLUMN) || (c == STATE_COLUMN)); 1286 } 1287 1288 public static final int SNAME_COLUMN = 0; 1289 public static final int UNAME_COLUMN = 1; 1290 public static final int INCLUDE_COLUMN = 2; 1291 public static final int STATE_COLUMN = 3; 1292 1293 } 1294 1295 /** 1296 * Table Model to manage Signal Head outputs in a Signal Group. 1297 */ 1298 class SignalGroupSignalHeadModel extends SignalGroupOutputModel { 1299 1300 SignalGroupSignalHeadModel() { 1301 addPcl(); 1302 } 1303 1304 final void addPcl(){ 1305 InstanceManager.getDefault(SignalHeadManager.class).addPropertyChangeListener(this); 1306 } 1307 1308 @Override 1309 public boolean isCellEditable(int r, int c) { 1310 return ((c == INCLUDE_COLUMN) || (c == STATE_ON_COLUMN) || (c == STATE_OFF_COLUMN) || (c == EDIT_COLUMN)); 1311 } 1312 1313 @Override 1314 public int getColumnCount() { 1315 return 6; 1316 } 1317 1318 public static final int STATE_ON_COLUMN = 3; 1319 public static final int STATE_OFF_COLUMN = 4; 1320 public static final int EDIT_COLUMN = 5; 1321 1322 @Override 1323 public Class<?> getColumnClass(int c) { 1324 switch (c) { 1325 case INCLUDE_COLUMN: 1326 return Boolean.class; 1327 case EDIT_COLUMN: 1328 return JButton.class; 1329 default: 1330 return String.class; 1331 } 1332 } 1333 1334 @Override 1335 public String getColumnName(int c) { 1336 return COLUMN_SIG_NAMES[c]; 1337 } 1338 1339 public void setSetToState(String x) { 1340 } 1341 1342 /** 1343 * The number of rows in the Signal Head table. 1344 * 1345 * @return The number of rows 1346 */ 1347 @Override 1348 public int getRowCount() { 1349 return ( showAll ? _signalHeadsList.size() : _includedSignalHeadsList.size() ); 1350 } 1351 1352 /** 1353 * Fill in info cells of the Signal Head table on the Add/Edit Group 1354 * Edit pane. 1355 * 1356 * @param r Index of the cell row 1357 * @param c Index of the cell column 1358 */ 1359 @Override 1360 public Object getValueAt(int r, int c) { 1361 ArrayList<SignalGroupSignalHead> headsList = ( showAll ? _signalHeadsList : _includedSignalHeadsList); 1362 // some error checking 1363 if (r >= headsList.size()) { 1364 log.debug("Row num {} is greater than headsList size {}", r, headsList.size()); 1365 return null; 1366 } 1367 switch (c) { 1368 case INCLUDE_COLUMN: 1369 return headsList.get(r).isIncluded(); 1370 case SNAME_COLUMN: 1371 return headsList.get(r).getSysName(); 1372 case UNAME_COLUMN: 1373 return headsList.get(r).getUserName(); 1374 case STATE_ON_COLUMN: 1375 return headsList.get(r).getOnState(); 1376 case STATE_OFF_COLUMN: 1377 return headsList.get(r).getOffState(); 1378 case EDIT_COLUMN: 1379 return (Bundle.getMessage("ButtonEdit")); 1380 default: 1381 return null; 1382 } 1383 } 1384 1385 /** 1386 * Fetch User Name (System Name if User Name is empty) for a row in the 1387 * Signal Head table. 1388 * 1389 * @param r index in the signal head table of head to be edited 1390 * @return name of signal head 1391 */ 1392 public String getDisplayName(int r) { 1393 if (((String) getValueAt(r, UNAME_COLUMN) != null) && (!((String) getValueAt(r, UNAME_COLUMN)).isEmpty())) { 1394 return (String) getValueAt(r, UNAME_COLUMN); 1395 } else { 1396 return (String) getValueAt(r, SNAME_COLUMN); 1397 } 1398 } 1399 1400 /** 1401 * Fetch existing bean object for a row in the Signal Head table. 1402 * 1403 * @param r index in the signal head table of head to be edited 1404 * @return bean object of the head 1405 */ 1406 public SignalHead getBean(int r) { 1407 return InstanceManager.getDefault(SignalHeadManager.class).getSignalHead((String) getValueAt(r, SNAME_COLUMN)); 1408 } 1409 1410 /** 1411 * Store info from the cells of the Signal Head table of the Add/Edit 1412 * Group Edit pane. 1413 * 1414 * @param type The contents from the table 1415 * @param r Index of the cell row of the entry 1416 * @param c Index of the cell column of the entry 1417 */ 1418 @Override 1419 public void setValueAt(Object type, int r, int c) { 1420 ArrayList<SignalGroupSignalHead> headsList = (showAll ? _signalHeadsList : _includedSignalHeadsList); 1421 switch (c) { 1422 case INCLUDE_COLUMN: 1423 headsList.get(r).setIncluded(((Boolean) type)); 1424 break; 1425 case STATE_ON_COLUMN: 1426 headsList.get(r).setSetOnState((String) type); 1427 break; 1428 case STATE_OFF_COLUMN: 1429 headsList.get(r).setSetOffState((String) type); 1430 break; 1431 case EDIT_COLUMN: 1432 headsList.get(r).setIncluded(true); 1433 SwingUtilities.invokeLater(() -> { 1434 signalHeadEditPressed(r); 1435 }); 1436 break; 1437 default: 1438 break; 1439 } 1440 } 1441 1442 /** 1443 * Remove listener from Signal Head in group. Called on Delete. 1444 */ 1445 public void dispose() { 1446 InstanceManager.getDefault(SignalHeadManager.class).removePropertyChangeListener(this); 1447 } 1448 } 1449 1450 JmriJFrame signalHeadEditFrame = null; 1451 1452 /** 1453 * Open an editor to set the details of a Signal Head as part of a Signal 1454 * Group when user clicks the Edit button in the Signal Head table in the 1455 * lower half of the Edit Signal Group pane. 1456 * (renamed from signalEditPressed in 4.7.1 to explain what's in here) 1457 * 1458 * @see SignalGroupSubTableAction#editHead(SignalGroup, String) 1459 * SignalGroupSubTableAction.editHead 1460 * 1461 * @param row Index of line clicked in the displayed Signal Head table 1462 */ 1463 void signalHeadEditPressed(int row) { 1464 if (curSignalGroup == null) { 1465 log.debug("From signalHeadCreatePressed"); 1466 if (!_autoSystemName.isSelected()) { // when creating a new Group with autoSystemName, allow empty sName field 1467 if (!checkNewNamesOK()) { 1468 log.debug("signalHeadEditPressed: checkNewNamesOK = false"); 1469 return; 1470 } 1471 } 1472 if (!checkValidSignalMast()) { 1473 return; 1474 } 1475 updatePressed(null, true, false); 1476 // Read new entries provided in the Add pane before opening the Edit Signal Head subpane 1477 } 1478 if (!curSignalGroup.isHeadIncluded(_SignalGroupHeadModel.getBean(row))) { 1479 curSignalGroup.addSignalHead(_SignalGroupHeadModel.getBean(row)); 1480 } 1481 _SignalGroupHeadModel.fireTableDataChanged(); 1482 log.debug("signalHeadEditPressed: opening sbaTableAction for edit"); 1483 SignalGroupSubTableAction editSignalHead = new SignalGroupSubTableAction(); 1484 // calls separate class file SignalGroupSubTableAction to edit details for Signal Head 1485 editSignalHead.editHead(curSignalGroup, _SignalGroupHeadModel.getDisplayName(row)); 1486 } 1487 1488 private boolean showAll = true; // false indicates: show only included Signal Masts & SingleTO Heads 1489 1490 private static int ROW_HEIGHT; 1491 1492 private static String[] COLUMN_NAMES = { // used in class SignalGroupOutputModel (Turnouts and Sensors) 1493 Bundle.getMessage("ColumnSystemName"), 1494 Bundle.getMessage("ColumnUserName"), 1495 Bundle.getMessage("Include"), 1496 Bundle.getMessage("ColumnLabelSetState") 1497 }; 1498 private static String[] COLUMN_SIG_NAMES = { // used in class SignalGroupSignalHeadModel 1499 Bundle.getMessage("ColumnSystemName"), 1500 Bundle.getMessage("ColumnUserName"), 1501 Bundle.getMessage("Include"), 1502 Bundle.getMessage("OnAppearance"), 1503 Bundle.getMessage("OffAppearance"), 1504 "" // No label above last (Edit) column 1505 }; 1506 1507 private static String[] signalStates = new String[]{Bundle.getMessage("SignalHeadStateDark"), Bundle.getMessage("SignalHeadStateRed"), Bundle.getMessage("SignalHeadStateYellow"), Bundle.getMessage("SignalHeadStateGreen"), Bundle.getMessage("SignalHeadStateLunar")}; 1508 private static int[] signalStatesValues = new int[]{SignalHead.DARK, SignalHead.RED, SignalHead.YELLOW, SignalHead.GREEN, SignalHead.LUNAR}; 1509 1510 private ArrayList<SignalGroupSignalHead> _signalHeadsList; // array of all single output signal heads 1511 private ArrayList<SignalGroupSignalHead> _includedSignalHeadsList; // subset of heads included in sh table 1512 1513 private ArrayList<SignalMastAspect> _mastAspectsList; // array of all valid aspects for the main signal mast 1514 private ArrayList<SignalMastAspect> _includedMastAspectsList; // subset of aspects included in asp table 1515 1516 /** 1517 * Class to store definition of a Signal Head as part of a Signal Group. 1518 * Includes properties for what to display (renamed from SignalGroupSignal 1519 * in 4.7.1 to explain what's in here) 1520 */ 1521 private static class SignalGroupSignalHead { 1522 1523 SignalHead _signalHead = null; 1524 boolean _included; 1525 1526 /** 1527 * Create an object to hold name and configuration of a Signal Head as 1528 * part of a Signal Group. 1529 * Filters only existing Single Turnout Signal 1530 * Heads from the loaded configuration. 1531 * Used while editing Signal Groups. 1532 * Contains whether it is included in a group, the On state and Off 1533 * state 1534 * 1535 * @param sysName System Name of the grouphead 1536 * @param userName Optional User Name 1537 */ 1538 SignalGroupSignalHead(String sysName, String userName) { 1539 _included = false; 1540 SignalHead anySigHead = InstanceManager.getDefault(SignalHeadManager.class).getBySystemName(sysName); 1541 if (anySigHead != null) { 1542 if (anySigHead.getClass().getName().contains("SingleTurnoutSignalHead")) { 1543 jmri.implementation.SingleTurnoutSignalHead oneSigHead = (jmri.implementation.SingleTurnoutSignalHead) InstanceManager.getDefault(SignalHeadManager.class).getBySystemName(sysName); 1544 if (oneSigHead != null) { 1545 _onState = oneSigHead.getOnAppearance(); 1546 _offState = oneSigHead.getOffAppearance(); 1547 _signalHead = oneSigHead; 1548 } else { 1549 log.error("SignalGroupSignalHead: Failed to get oneSigHead head {}", sysName); 1550 } 1551 } 1552 } else { 1553 log.error("SignalGroupSignalHead: Failed to get signal head {}", sysName); 1554 } 1555 1556 } 1557 1558 SignalHead getBean() { 1559 return _signalHead; 1560 } 1561 1562 String getSysName() { 1563 return _signalHead.getSystemName(); 1564 } 1565 1566 String getUserName() { 1567 return _signalHead.getUserName(); 1568 } 1569 1570 boolean isIncluded() { 1571 return _included; 1572 } 1573 1574 void setIncluded(boolean include) { 1575 _included = include; 1576 } 1577 1578 /** 1579 * Retrieve On setting for Signal Head in Signal Group. Should match 1580 * entries in setOnState() 1581 * 1582 * @return localized string as the name for the Signal Head Appearance 1583 * when this head is On 1584 */ 1585 String getOnState() { 1586 switch (_onState) { 1587 case SignalHead.DARK: 1588 return Bundle.getMessage("SignalHeadStateDark"); 1589 case SignalHead.RED: 1590 return Bundle.getMessage("SignalHeadStateRed"); 1591 case SignalHead.YELLOW: 1592 return Bundle.getMessage("SignalHeadStateYellow"); 1593 case SignalHead.GREEN: 1594 return Bundle.getMessage("SignalHeadStateGreen"); 1595 case SignalHead.LUNAR: 1596 return Bundle.getMessage("SignalHeadStateLunar"); 1597 case SignalHead.FLASHRED: 1598 return Bundle.getMessage("SignalHeadStateFlashingRed"); 1599 case SignalHead.FLASHYELLOW: 1600 return Bundle.getMessage("SignalHeadStateFlashingYellow"); 1601 case SignalHead.FLASHGREEN: 1602 return Bundle.getMessage("SignalHeadStateFlashingGreen"); 1603 case SignalHead.FLASHLUNAR: 1604 return Bundle.getMessage("SignalHeadStateFlashingLunar"); 1605 default: 1606 // fall through 1607 break; 1608 } 1609 return ""; 1610 } 1611 1612 /** 1613 * Retrieve Off setting for Signal Head in Signal Group. Should match 1614 * entries in setOffState() 1615 * 1616 * @return localized string as the name for the Signal Head Appearance 1617 * when this head is Off 1618 */ 1619 String getOffState() { 1620 switch (_offState) { 1621 case SignalHead.DARK: 1622 return Bundle.getMessage("SignalHeadStateDark"); 1623 case SignalHead.RED: 1624 return Bundle.getMessage("SignalHeadStateRed"); 1625 case SignalHead.YELLOW: 1626 return Bundle.getMessage("SignalHeadStateYellow"); 1627 case SignalHead.GREEN: 1628 return Bundle.getMessage("SignalHeadStateGreen"); 1629 case SignalHead.LUNAR: 1630 return Bundle.getMessage("SignalHeadStateLunar"); 1631 case SignalHead.FLASHRED: 1632 return Bundle.getMessage("SignalHeadStateFlashingRed"); 1633 case SignalHead.FLASHYELLOW: 1634 return Bundle.getMessage("SignalHeadStateFlashingYellow"); 1635 case SignalHead.FLASHGREEN: 1636 return Bundle.getMessage("SignalHeadStateFlashingGreen"); 1637 case SignalHead.FLASHLUNAR: 1638 return Bundle.getMessage("SignalHeadStateFlashingLunar"); 1639 default: 1640 // fall through 1641 break; 1642 } 1643 return ""; 1644 } 1645 1646 int getOnStateInt() { 1647 return _onState; 1648 } 1649 1650 int getOffStateInt() { 1651 return _offState; 1652 } 1653 1654 /** 1655 * Store On setting for Signal Head in Signal Group. Should match 1656 * entries in getOnState() 1657 * 1658 * @param state Localized name for the Signal Head Appearance when this head 1659 * is On 1660 */ 1661 void setSetOnState(String state) { 1662 if (state.equals(Bundle.getMessage("SignalHeadStateDark"))) { 1663 _onState = SignalHead.DARK; 1664 } else if (state.equals(Bundle.getMessage("SignalHeadStateRed"))) { 1665 _onState = SignalHead.RED; 1666 } else if (state.equals(Bundle.getMessage("SignalHeadStateYellow"))) { 1667 _onState = SignalHead.YELLOW; 1668 } else if (state.equals(Bundle.getMessage("SignalHeadStateGreen"))) { 1669 _onState = SignalHead.GREEN; 1670 } else if (state.equals(Bundle.getMessage("SignalHeadStateLunar"))) { 1671 _onState = SignalHead.LUNAR; 1672 } else if (state.equals(Bundle.getMessage("SignalHeadStateFlashingRed"))) { 1673 _onState = SignalHead.FLASHRED; 1674 } else if (state.equals(Bundle.getMessage("SignalHeadStateFlashingYellow"))) { 1675 _onState = SignalHead.FLASHYELLOW; 1676 } else if (state.equals(Bundle.getMessage("SignalHeadStateFlashingGreen"))) { 1677 _onState = SignalHead.FLASHGREEN; 1678 } else if (state.equals(Bundle.getMessage("SignalHeadStateFlashingLunar"))) { 1679 _onState = SignalHead.FLASHLUNAR; 1680 } 1681 } 1682 1683 /** 1684 * Store Off setting for Signal Head in Signal Group. Should match 1685 * entries in getOffState() 1686 * 1687 * @param state Localized name for the Signal Head Appearance when this head 1688 * is Off 1689 */ 1690 void setSetOffState(String state) { 1691 if (state.equals(Bundle.getMessage("SignalHeadStateDark"))) { 1692 _offState = SignalHead.DARK; 1693 } else if (state.equals(Bundle.getMessage("SignalHeadStateRed"))) { 1694 _offState = SignalHead.RED; 1695 } else if (state.equals(Bundle.getMessage("SignalHeadStateYellow"))) { 1696 _offState = SignalHead.YELLOW; 1697 } else if (state.equals(Bundle.getMessage("SignalHeadStateGreen"))) { 1698 _offState = SignalHead.GREEN; 1699 } else if (state.equals(Bundle.getMessage("SignalHeadStateLunar"))) { 1700 _offState = SignalHead.LUNAR; 1701 } else if (state.equals(Bundle.getMessage("SignalHeadStateFlashingRed"))) { 1702 _offState = SignalHead.FLASHRED; 1703 } else if (state.equals(Bundle.getMessage("SignalHeadStateFlashingYellow"))) { 1704 _offState = SignalHead.FLASHYELLOW; 1705 } else if (state.equals(Bundle.getMessage("SignalHeadStateFlashingGreen"))) { 1706 _offState = SignalHead.FLASHGREEN; 1707 } else if (state.equals(Bundle.getMessage("SignalHeadStateFlashingLunar"))) { 1708 _offState = SignalHead.FLASHLUNAR; 1709 } 1710 } 1711 1712 int _onState = 0x00; 1713 int _offState = 0x00; 1714 1715 public void setOnState(int state) { 1716 _onState = state; 1717 } 1718 1719 public void setOffState(int state) { 1720 _offState = state; 1721 } 1722 } 1723 1724 /** 1725 * Definition of main Signal Mast in a Signal Group. 1726 */ 1727 private static class SignalMastAspect { 1728 1729 SignalMastAspect(String aspect) { 1730 _aspect = aspect; 1731 } 1732 1733 boolean _include; 1734 String _aspect; 1735 1736 void setIncluded(boolean include) { 1737 _include = include; 1738 } 1739 1740 boolean isIncluded() { 1741 return _include; 1742 } 1743 1744 void setAspect(String asp) { 1745 _aspect = asp; 1746 } 1747 1748 String getAspect() { 1749 return _aspect; 1750 } 1751 1752 } 1753 1754 @Override 1755 protected String getClassName() { 1756 return SignalGroupTableAction.class.getName(); 1757 } 1758 1759 @Override 1760 public String getClassDescription() { 1761 return Bundle.getMessage("TitleSignalGroupTable"); 1762 } 1763 1764 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SignalGroupTableAction.class); 1765 1766}