001package jmri.jmrit.beantable.turnout; 002 003import java.awt.event.ActionEvent; 004import java.awt.event.ActionListener; 005import java.awt.event.MouseAdapter; 006import java.awt.event.MouseEvent; 007import java.awt.image.BufferedImage; 008import java.io.File; 009import java.io.IOException; 010 011import javax.annotation.CheckForNull; 012import javax.annotation.Nonnull; 013import javax.imageio.ImageIO; 014import javax.swing.*; 015import javax.swing.table.TableCellEditor; 016import javax.swing.table.TableCellRenderer; 017import javax.swing.table.TableColumn; 018import javax.swing.table.TableModel; 019 020import jmri.*; 021import jmri.implementation.SignalSpeedMap; 022import jmri.jmrit.beantable.*; 023import jmri.util.swing.*; 024 025/** 026 * Data model for a Turnout Table. 027 * Code originally within TurnoutTableAction. 028 * 029 * @author Bob Jacobsen Copyright (C) 2003, 2004, 2007 030 * @author Egbert Broerse Copyright (C) 2017 031 * @author Steve Young Copyright (C) 2021 032 */ 033public class TurnoutTableDataModel extends BeanTableDataModel<Turnout>{ 034 035 static public final int INVERTCOL = BeanTableDataModel.NUMCOLUMN; 036 static public final int LOCKCOL = INVERTCOL + 1; 037 static public final int EDITCOL = LOCKCOL + 1; 038 static public final int KNOWNCOL = EDITCOL + 1; 039 static public final int MODECOL = KNOWNCOL + 1; 040 static public final int SENSOR1COL = MODECOL + 1; 041 static public final int SENSOR2COL = SENSOR1COL + 1; 042 static public final int OPSONOFFCOL = SENSOR2COL + 1; 043 static public final int OPSEDITCOL = OPSONOFFCOL + 1; 044 static public final int LOCKOPRCOL = OPSEDITCOL + 1; 045 static public final int LOCKDECCOL = LOCKOPRCOL + 1; 046 static public final int STRAIGHTCOL = LOCKDECCOL + 1; 047 static public final int DIVERGCOL = STRAIGHTCOL + 1; 048 static public final int FORGETCOL = DIVERGCOL + 1; 049 static public final int QUERYCOL = FORGETCOL + 1; 050 051 private boolean _graphicState; 052 private TurnoutManager turnoutManager; 053 054 055 String closedText; 056 String thrownText; 057 public String defaultThrownSpeedText; 058 public String defaultClosedSpeedText; 059 // I18N TODO but note storing in xml independent from Locale 060 String useBlockSpeed; 061 String bothText = "Both"; 062 String cabOnlyText = "Cab only"; 063 String pushbutText = "Pushbutton only"; 064 String noneText = "None"; 065 066 public final java.util.Vector<String> speedListClosed = new java.util.Vector<>(); 067 public final java.util.Vector<String> speedListThrown = new java.util.Vector<>(); 068 069 070 public TurnoutTableDataModel(){ 071 super(); 072 initTable(); 073 } 074 075 public TurnoutTableDataModel(Manager<Turnout> mgr){ 076 super(); 077 setManager(mgr); 078 initTable(); 079 } 080 081 private void initTable() { 082 083 // load graphic state column display preference 084 _graphicState = InstanceManager.getDefault(jmri.util.gui.GuiLafPreferencesManager.class).isGraphicTableState(); 085 086 closedText = turnoutManager.getClosedText(); 087 thrownText = turnoutManager.getThrownText(); 088 089 //This following must contain the word Global for a correct match in the abstract turnout 090 defaultThrownSpeedText = (Bundle.getMessage("UseGlobal", "Global") + " " + turnoutManager.getDefaultThrownSpeed()); 091 defaultClosedSpeedText = (Bundle.getMessage("UseGlobal", "Global") + " " + turnoutManager.getDefaultClosedSpeed()); 092 093 //This following must contain the word Block for a correct match in the abstract turnout 094 useBlockSpeed = Bundle.getMessage("UseGlobal", "Block Speed"); 095 096 speedListClosed.add(defaultClosedSpeedText); 097 speedListThrown.add(defaultThrownSpeedText); 098 speedListClosed.add(useBlockSpeed); 099 speedListThrown.add(useBlockSpeed); 100 java.util.Vector<String> _speedMap = jmri.InstanceManager.getDefault(SignalSpeedMap.class).getValidSpeedNames(); 101 for (String s : _speedMap) { 102 if (!speedListClosed.contains(s)) { 103 speedListClosed.add(s); 104 } 105 if (!speedListThrown.contains(s)) { 106 speedListThrown.add(s); 107 } 108 } 109 110 } 111 112 /** 113 * {@inheritDoc} 114 */ 115 @Override 116 public int getColumnCount() { 117 return QUERYCOL + getPropertyColumnCount() + 1; 118 } 119 120 /** 121 * {@inheritDoc} 122 */ 123 @Override 124 public String getColumnName(int col) { 125 switch (col) { 126 case INVERTCOL: 127 return Bundle.getMessage("Inverted"); 128 case LOCKCOL: 129 return Bundle.getMessage("Locked"); 130 case KNOWNCOL: 131 return Bundle.getMessage("Feedback"); 132 case MODECOL: 133 return Bundle.getMessage("ModeLabel"); 134 case SENSOR1COL: 135 return Bundle.getMessage("BlockSensor") + " 1"; 136 case SENSOR2COL: 137 return Bundle.getMessage("BlockSensor") + " 2"; 138 case OPSONOFFCOL: 139 return Bundle.getMessage("TurnoutAutomationMenu"); 140 case OPSEDITCOL: 141 return ""; 142 case LOCKOPRCOL: 143 return Bundle.getMessage("LockMode"); 144 case LOCKDECCOL: 145 return Bundle.getMessage("Decoder"); 146 case DIVERGCOL: 147 return Bundle.getMessage("ThrownSpeed"); 148 case STRAIGHTCOL: 149 return Bundle.getMessage("ClosedSpeed"); 150 case FORGETCOL: 151 return Bundle.getMessage("StateForgetHeader"); 152 case QUERYCOL: 153 return Bundle.getMessage("StateQueryHeader"); 154 case EDITCOL: 155 return ""; 156 default: 157 return super.getColumnName(col); 158 } 159 } 160 161 /** 162 * {@inheritDoc} 163 */ 164 @Override 165 protected String getHeaderTooltip(int col) { 166 switch (col) { 167 case SENSOR1COL: 168 return Bundle.getMessage("Sensor1Tip", turnoutManager.getThrownText()); 169 case SENSOR2COL: 170 return Bundle.getMessage("Sensor2Tip", turnoutManager.getClosedText()); 171 case OPSONOFFCOL: 172 return Bundle.getMessage("TurnoutAutomationTip"); 173 case KNOWNCOL: 174 return Bundle.getMessage("FeedbackTip"); 175 case MODECOL: 176 return Bundle.getMessage("FeedbackModeTip"); 177 default: 178 return super.getHeaderTooltip(col); 179 } 180 } 181 182 /** 183 * {@inheritDoc} 184 */ 185 @Override 186 public Class<?> getColumnClass(int col) { 187 switch (col) { 188 case INVERTCOL: 189 case LOCKCOL: 190 return Boolean.class; 191 case KNOWNCOL: 192 return String.class; 193 case MODECOL: 194 case SENSOR1COL: 195 case SENSOR2COL: 196 case OPSONOFFCOL: 197 case LOCKOPRCOL: 198 case LOCKDECCOL: 199 case DIVERGCOL: 200 case STRAIGHTCOL: 201 return JComboBox.class; 202 case OPSEDITCOL: 203 case EDITCOL: 204 case FORGETCOL: 205 case QUERYCOL: 206 return JButton.class; 207 case VALUECOL: // may use an image to show turnout state 208 return ( _graphicState ? JLabel.class : JButton.class ); 209 default: 210 return super.getColumnClass(col); 211 } 212 } 213 214 /** 215 * {@inheritDoc} 216 */ 217 @Override 218 public int getPreferredWidth(int col) { 219 switch (col) { 220 case INVERTCOL: 221 case LOCKCOL: 222 return new JTextField(6).getPreferredSize().width; 223 case LOCKOPRCOL: 224 case LOCKDECCOL: 225 case KNOWNCOL: 226 case MODECOL: 227 return new JTextField(10).getPreferredSize().width; 228 case SENSOR1COL: 229 case SENSOR2COL: 230 return new JTextField(5).getPreferredSize().width; 231 case OPSEDITCOL: 232 return new JButton(Bundle.getMessage("EditTurnoutOperation")).getPreferredSize().width; 233 case EDITCOL: 234 return new JButton(Bundle.getMessage("ButtonEdit")).getPreferredSize().width+4; 235 case OPSONOFFCOL: 236 return new JTextField(Bundle.getMessage("TurnoutAutomationMenu")).getPreferredSize().width; 237 case DIVERGCOL: 238 case STRAIGHTCOL: 239 return new JTextField(14).getPreferredSize().width; 240 case FORGETCOL: 241 return new JButton(Bundle.getMessage("StateForgetButton")).getPreferredSize().width; 242 case QUERYCOL: 243 return new JButton(Bundle.getMessage("StateQueryButton")).getPreferredSize().width; 244 default: 245 return super.getPreferredWidth(col); 246 } 247 } 248 249 /** 250 * {@inheritDoc} 251 */ 252 @Override 253 public boolean isCellEditable(int row, int col) { 254 Turnout t = turnoutManager.getBySystemName(sysNameList.get(row)); 255 if (t == null){ 256 return false; 257 } 258 switch (col) { 259 case INVERTCOL: 260 return t.canInvert(); 261 case LOCKCOL: 262 // checkbox disabled unless current configuration allows locking 263 return t.canLock(Turnout.CABLOCKOUT | Turnout.PUSHBUTTONLOCKOUT); 264 case OPSEDITCOL: 265 return t.getTurnoutOperation() != null; 266 case KNOWNCOL: 267 return false; 268 case MODECOL: 269 case SENSOR1COL: 270 case SENSOR2COL: 271 case OPSONOFFCOL: 272 case LOCKOPRCOL: // editable always so user can configure it, even if current configuration prevents locking now 273 case LOCKDECCOL: // editable always so user can configure it, even if current configuration prevents locking now 274 case DIVERGCOL: 275 case STRAIGHTCOL: 276 case EDITCOL: 277 case FORGETCOL: 278 case QUERYCOL: 279 return true; 280 default: 281 return super.isCellEditable(row, col); 282 } 283 } 284 285 /** 286 * {@inheritDoc} 287 */ 288 @Override 289 public Object getValueAt(int row, int col) { 290 // some error checking 291 if (row >= sysNameList.size()) { 292 log.warn("row is greater than name list"); 293 return "error"; 294 } 295 String name = sysNameList.get(row); 296 TurnoutManager manager = turnoutManager; 297 Turnout t = manager.getBySystemName(name); 298 if (t == null) { 299 log.debug("error null turnout!"); 300 return "error"; 301 } 302 if (col == INVERTCOL) { 303 return t.getInverted(); 304 } else if (col == LOCKCOL) { 305 return t.getLocked(Turnout.CABLOCKOUT | Turnout.PUSHBUTTONLOCKOUT); 306 } else if (col == KNOWNCOL) { 307 return t.describeState(t.getKnownState()); 308 } else if (col == MODECOL) { 309 JComboBox<String> c = new JComboBox<>(t.getValidFeedbackNames()); 310 c.setSelectedItem(t.getFeedbackModeName()); 311 return c; 312 } else if (col == SENSOR1COL) { 313 return t.getFirstSensor(); 314 } else if (col == SENSOR2COL) { 315 return t.getSecondSensor(); 316 } else if (col == OPSONOFFCOL) { 317 return makeAutomationBox(t); 318 } else if (col == OPSEDITCOL) { 319 return Bundle.getMessage("EditTurnoutOperation"); 320 } else if (col == EDITCOL) { 321 return Bundle.getMessage("ButtonEdit"); 322 } else if (col == LOCKDECCOL) { 323 JComboBox<String> c; 324 if ((t.getPossibleLockModes() & Turnout.PUSHBUTTONLOCKOUT) != 0) { 325 c = new JComboBox<>(t.getValidDecoderNames()); 326 } else { 327 c = new JComboBox<>(new String[]{t.getDecoderName()}); 328 } 329 330 c.setSelectedItem(t.getDecoderName()); 331 return c; 332 } else if (col == LOCKOPRCOL) { 333 334 java.util.Vector<String> lockOperations = new java.util.Vector<>(); // Vector is a JComboBox ctor; List is not 335 int modes = t.getPossibleLockModes(); 336 if ((modes & Turnout.CABLOCKOUT) != 0 && (modes & Turnout.PUSHBUTTONLOCKOUT) != 0) { 337 lockOperations.add(bothText); 338 } 339 if ((modes & Turnout.CABLOCKOUT) != 0) { 340 lockOperations.add(cabOnlyText); 341 } 342 if ((modes & Turnout.PUSHBUTTONLOCKOUT) != 0) { 343 lockOperations.add(pushbutText); 344 } 345 lockOperations.add(noneText); 346 JComboBox<String> c = new JComboBox<>(lockOperations); 347 348 if (t.canLock(Turnout.CABLOCKOUT) && t.canLock(Turnout.PUSHBUTTONLOCKOUT)) { 349 c.setSelectedItem(bothText); 350 } else if (t.canLock(Turnout.PUSHBUTTONLOCKOUT)) { 351 c.setSelectedItem(pushbutText); 352 } else if (t.canLock(Turnout.CABLOCKOUT)) { 353 c.setSelectedItem(cabOnlyText); 354 } else { 355 c.setSelectedItem(noneText); 356 } 357 return c; 358 } else if (col == STRAIGHTCOL) { 359 360 String speed = t.getStraightSpeed(); 361 if (!speedListClosed.contains(speed)) { 362 speedListClosed.add(speed); 363 } 364 JComboBox<String> c = new JComboBox<>(speedListClosed); 365 c.setEditable(true); 366 c.setSelectedItem(speed); 367 JComboBoxUtil.setupComboBoxMaxRows(c); 368 return c; 369 } else if (col == DIVERGCOL) { 370 371 String speed = t.getDivergingSpeed(); 372 if (!speedListThrown.contains(speed)) { 373 speedListThrown.add(speed); 374 } 375 JComboBox<String> c = new JComboBox<>(speedListThrown); 376 c.setEditable(true); 377 c.setSelectedItem(speed); 378 JComboBoxUtil.setupComboBoxMaxRows(c); 379 return c; 380 // } else if (col == VALUECOL && _graphicState) { // not neeeded as the 381 // graphic ImageIconRenderer uses the same super.getValueAt(row, col) as 382 // classic bean state text button 383 } else if (col == FORGETCOL) { 384 return Bundle.getMessage("StateForgetButton"); 385 } else if (col == QUERYCOL) { 386 return Bundle.getMessage("StateQueryButton"); 387 } 388 return super.getValueAt(row, col); 389 } 390 391 /** 392 * {@inheritDoc} 393 */ 394 @Override 395 public void setValueAt(Object value, int row, int col) { 396 String name = sysNameList.get(row); 397 Turnout t = turnoutManager.getBySystemName(name); 398 if (t == null) { 399 NullPointerException ex = new NullPointerException("Unexpected null turnout in turnout table"); 400 log.error("No Turnout with system name \"{}\" exists ", name , ex); // log with stack trace 401 throw ex; 402 } 403 if (col == INVERTCOL) { 404 if (t.canInvert()) { 405 t.setInverted((Boolean) value); 406 } 407 } else if (col == LOCKCOL) { 408 t.setLocked(Turnout.CABLOCKOUT | Turnout.PUSHBUTTONLOCKOUT, (Boolean) value); 409 } else if (col == MODECOL) { 410 @SuppressWarnings("unchecked") 411 String modeName = (String) ((JComboBox<String>) value).getSelectedItem(); 412 assert modeName != null; 413 t.setFeedbackMode(modeName); 414 } else if (col == SENSOR1COL) { 415 try { 416 Sensor sensor = (Sensor) value; 417 t.provideFirstFeedbackSensor(sensor != null ? sensor.getDisplayName() : null); 418 } catch (jmri.JmriException e) { 419 JmriJOptionPane.showMessageDialog(null, e.toString()); 420 } 421 } else if (col == SENSOR2COL) { 422 try { 423 Sensor sensor = (Sensor) value; 424 t.provideSecondFeedbackSensor(sensor != null ? sensor.getDisplayName() : null); 425 } catch (jmri.JmriException e) { 426 JmriJOptionPane.showMessageDialog(null, e.toString()); 427 } 428 } else if (col == OPSONOFFCOL) { 429 // do nothing as this is handled by the combo box listener 430 // column still handled here to prevent call to super.setValueAt 431 } else if (col == OPSEDITCOL) { 432 t.setInhibitOperation(false); 433 @SuppressWarnings("unchecked") // cast to JComboBox<String> required in OPSEDITCOL 434 JComboBox<String> cb = (JComboBox<String>) getValueAt(row, OPSONOFFCOL); 435 log.debug("opsSelected = {}", getValueAt(row, OPSONOFFCOL).toString()); 436 editTurnoutOperation(t, cb); 437 fireTableRowsUpdated(row, row); 438 } else if (col == EDITCOL) { 439 javax.swing.SwingUtilities.invokeLater(() -> { 440 editButton(t); 441 }); 442 } else if (col == LOCKOPRCOL) { 443 @SuppressWarnings("unchecked") 444 String lockOpName = (String) ((JComboBox<String>) value) 445 .getSelectedItem(); 446 assert lockOpName != null; 447 if (lockOpName.equals(bothText)) { 448 t.enableLockOperation(Turnout.CABLOCKOUT | Turnout.PUSHBUTTONLOCKOUT, true); 449 } 450 if (lockOpName.equals(cabOnlyText)) { 451 t.enableLockOperation(Turnout.CABLOCKOUT, true); 452 t.enableLockOperation(Turnout.PUSHBUTTONLOCKOUT, false); 453 } 454 if (lockOpName.equals(pushbutText)) { 455 t.enableLockOperation(Turnout.CABLOCKOUT, false); 456 t.enableLockOperation(Turnout.PUSHBUTTONLOCKOUT, true); 457 } 458 fireTableRowsUpdated(row, row); 459 } else if (col == LOCKDECCOL) { 460 @SuppressWarnings("unchecked") 461 String decoderName = (String) ((JComboBox<String>) value).getSelectedItem(); 462 t.setDecoderName(decoderName); 463 fireTableRowsUpdated(row, row); 464 } else if (col == STRAIGHTCOL) { 465 @SuppressWarnings("unchecked") 466 String speed = (String) ((JComboBox<String>) value).getSelectedItem(); 467 try { 468 t.setStraightSpeed(speed); 469 } catch (jmri.JmriException ex) { 470 JmriJOptionPane.showMessageDialog(null, ex.getMessage() + "\n" + speed); 471 return; 472 } 473 if ((!speedListClosed.contains(speed))) { 474 assert speed != null; 475 if (!speed.contains("Global")) { 476 speedListClosed.add(speed); 477 } 478 } 479 } else if (col == DIVERGCOL) { 480 481 @SuppressWarnings("unchecked") 482 String speed = (String) ((JComboBox<String>) value).getSelectedItem(); 483 try { 484 t.setDivergingSpeed(speed); 485 } catch (jmri.JmriException ex) { 486 JmriJOptionPane.showMessageDialog(null, ex.getMessage() + "\n" + speed); 487 return; 488 } 489 if ((!speedListThrown.contains(speed))) { 490 assert speed != null; 491 if (!speed.contains("Global")) { 492 speedListThrown.add(speed); 493 } 494 } 495 } else if (col == FORGETCOL) { 496 t.setCommandedState(Turnout.UNKNOWN); 497 } else if (col == QUERYCOL) { 498 t.setCommandedState(Turnout.UNKNOWN); 499 t.requestUpdateFromLayout(); 500 } else if (col == VALUECOL && _graphicState) { // respond to clicking on ImageIconRenderer CellEditor 501 clickOn(t); 502 fireTableRowsUpdated(row, row); 503 } else { 504 super.setValueAt(value, row, col); 505 if (row < getRowCount()) { 506 fireTableRowsUpdated(row, row); 507 } 508 } 509 } 510 511 /** 512 * {@inheritDoc} 513 */ 514 @Override 515 public String getValue(@Nonnull String name) { 516 Turnout turn = turnoutManager.getBySystemName(name); 517 if (turn != null) { 518 return turn.describeState(turn.getCommandedState()); 519 } 520 return "Turnout not found"; 521 } 522 523 /** 524 * {@inheritDoc} 525 */ 526 @Override 527 public Manager<Turnout> getManager() { 528 if (turnoutManager == null) { 529 turnoutManager = InstanceManager.getDefault(TurnoutManager.class); 530 } 531 return turnoutManager; 532 } 533 534 /** 535 * {@inheritDoc} 536 */ 537 @Override 538 protected final void setManager(@Nonnull Manager<Turnout> manager) { 539 if (!(manager instanceof TurnoutManager)) { 540 return; 541 } 542 getManager().removePropertyChangeListener(this); 543 if (sysNameList != null) { 544 for (int i = 0; i < sysNameList.size(); i++) { 545 // if object has been deleted, it's not here; ignore it 546 NamedBean b = getBySystemName(sysNameList.get(i)); 547 if (b != null) { 548 b.removePropertyChangeListener(this); 549 } 550 } 551 } 552 turnoutManager = (TurnoutManager) manager; 553 getManager().addPropertyChangeListener(this); 554 updateNameList(); 555 } 556 557 @Override 558 public Turnout getBySystemName(@Nonnull String name) { 559 return turnoutManager.getBySystemName(name); 560 } 561 562 @Override 563 public Turnout getByUserName(@Nonnull String name) { 564 return InstanceManager.getDefault(TurnoutManager.class).getByUserName(name); 565 } 566 567 @Override 568 protected String getMasterClassName() { 569 // Force message grouping 570 return jmri.jmrit.beantable.TurnoutTableAction.class.getName(); 571 } 572 573 protected String getClassName() { 574 return jmri.jmrit.beantable.TurnoutTableAction.class.getName(); 575 } 576 577 @Override 578 public void clickOn(Turnout t) { 579 t.setCommandedState( t.getCommandedState()== Turnout.CLOSED ? Turnout.THROWN : Turnout.CLOSED); 580 } 581 582 @Override 583 public void configureTable(JTable tbl) { 584 585 setColumnToHoldButton(tbl, EDITCOL, editButton()); 586 setColumnToHoldButton(tbl, OPSEDITCOL, editButton()); 587 588 //Hide the following columns by default 589 XTableColumnModel columnModel = (XTableColumnModel) tbl.getColumnModel(); 590 TableColumn column = columnModel.getColumnByModelIndex(STRAIGHTCOL); 591 columnModel.setColumnVisible(column, false); 592 column = columnModel.getColumnByModelIndex(DIVERGCOL); 593 columnModel.setColumnVisible(column, false); 594 column = columnModel.getColumnByModelIndex(KNOWNCOL); 595 columnModel.setColumnVisible(column, false); 596 column = columnModel.getColumnByModelIndex(MODECOL); 597 columnModel.setColumnVisible(column, false); 598 column = columnModel.getColumnByModelIndex(SENSOR1COL); 599 columnModel.setColumnVisible(column, false); 600 column = columnModel.getColumnByModelIndex(SENSOR2COL); 601 columnModel.setColumnVisible(column, false); 602 column = columnModel.getColumnByModelIndex(OPSONOFFCOL); 603 columnModel.setColumnVisible(column, false); 604 column = columnModel.getColumnByModelIndex(OPSEDITCOL); 605 columnModel.setColumnVisible(column, false); 606 column = columnModel.getColumnByModelIndex(LOCKOPRCOL); 607 columnModel.setColumnVisible(column, false); 608 column = columnModel.getColumnByModelIndex(LOCKDECCOL); 609 columnModel.setColumnVisible(column, false); 610 column = columnModel.getColumnByModelIndex(FORGETCOL); 611 columnModel.setColumnVisible(column, false); 612 column = columnModel.getColumnByModelIndex(QUERYCOL); 613 columnModel.setColumnVisible(column, false); 614 615 616 // and then set user prefs 617 super.configureTable(tbl); 618 619 columnModel.getColumnByModelIndex(FORGETCOL).setHeaderValue(null); 620 columnModel.getColumnByModelIndex(QUERYCOL).setHeaderValue(null); 621 622 } 623 624 // update table if turnout lock or feedback changes 625 @Override 626 protected boolean matchPropertyName(java.beans.PropertyChangeEvent e) { 627 switch (e.getPropertyName()) { 628 case "locked": 629 case "inverted": 630 case "feedbackchange": // feedback type setting change, NOT Turnout feedback status 631 case "TurnoutDivergingSpeedChange": 632 case "TurnoutStraightSpeedChange": 633 case "turnoutFeedbackFirstSensorChange": 634 case "turnoutFeedbackSecondSensorChange": 635 case "decoderNameChange": 636 case "TurnoutOperationState": 637 case "KnownState": 638 return true; 639 default: 640 return super.matchPropertyName(e); 641 } 642 } 643 644 @Override 645 public void propertyChange(java.beans.PropertyChangeEvent e) { 646 switch (e.getPropertyName()) { 647 case "DefaultTurnoutClosedSpeedChange": 648 updateClosedList(); 649 break; 650 case "DefaultTurnoutThrownSpeedChange": 651 updateThrownList(); 652 break; 653 default: 654 super.propertyChange(e); 655 break; 656 } 657 } 658 659 /** 660 * Customize the turnout table Value (State) column to show an 661 * appropriate graphic for the turnout state if _graphicState = 662 * true, or (default) just show the localized state text when the 663 * TableDataModel is being called from ListedTableAction. 664 * 665 * @param table a JTable of Turnouts 666 */ 667 @Override 668 protected void configValueColumn(JTable table) { 669 // have the value column hold a JPanel (icon) 670 //setColumnToHoldButton(table, VALUECOL, new JLabel("12345678")); // for larger, wide round icon, but cannot be converted to JButton 671 // add extras, override BeanTableDataModel 672 log.debug("Turnout configValueColumn (I am {})", super.toString()); 673 if (_graphicState) { // load icons, only once 674 table.setDefaultEditor(JLabel.class, new ImageIconRenderer()); // editor 675 table.setDefaultRenderer(JLabel.class, new ImageIconRenderer()); // item class copied from SwitchboardEditor panel 676 } else { 677 super.configValueColumn(table); // classic text style state indication 678 } 679 } 680 681 @Override 682 public JTable makeJTable(@Nonnull String name, @Nonnull TableModel model, @CheckForNull RowSorter<? extends TableModel> sorter) { 683 if (!(model instanceof TurnoutTableDataModel)){ 684 throw new IllegalArgumentException("Model is not a TurnoutTableDataModel"); 685 } 686 return configureJTable(name, new TurnoutTableJTable((TurnoutTableDataModel)model), sorter); 687 } 688 689 @Override 690 protected void setColumnIdentities(JTable table) { 691 super.setColumnIdentities(table); 692 java.util.Enumeration<TableColumn> columns; 693 if (table.getColumnModel() instanceof XTableColumnModel) { 694 columns = ((XTableColumnModel) table.getColumnModel()).getColumns(false); 695 } else { 696 columns = table.getColumnModel().getColumns(); 697 } 698 while (columns.hasMoreElements()) { 699 TableColumn column = columns.nextElement(); 700 switch (column.getModelIndex()) { 701 case FORGETCOL: 702 column.setIdentifier("ForgetState"); 703 break; 704 case QUERYCOL: 705 column.setIdentifier("QueryState"); 706 break; 707 case SENSOR1COL: 708 column.setIdentifier("Sensor1"); 709 break; 710 case SENSOR2COL: 711 column.setIdentifier("Sensor2"); 712 break; 713 default: 714 // use existing value 715 } 716 } 717 } 718 719 /** 720 * Pop up a TurnoutOperationConfig for the turnout. 721 * 722 * @param t turnout 723 * @param box JComboBox that triggered the edit 724 */ 725 protected void editTurnoutOperation( @Nonnull Turnout t, JComboBox<String> box) { 726 if (!editingOps.getAndSet(true)) { // don't open a second edit ops pane 727 TurnoutOperation op = t.getTurnoutOperation(); 728 if (op == null) { 729 TurnoutOperation proto = InstanceManager.getDefault(TurnoutOperationManager.class).getMatchingOperationAlways(t); 730 if (proto != null) { 731 op = proto.makeNonce(t); 732 t.setTurnoutOperation(op); 733 } 734 } 735 if (op != null) { 736 if (!op.isNonce()) { 737 op = op.makeNonce(t); 738 } 739 // make and show edit dialog 740 log.debug("TurnoutOpsEditDialog starting"); 741 java.awt.Window w = JmriJOptionPane.findWindowForObject(box); 742 TurnoutOperationEditorDialog dialog = new TurnoutOperationEditorDialog(op, t, w); 743 dialog.setVisible(true); 744 } else { 745 JmriJOptionPane.showMessageDialog(box, Bundle.getMessage("TurnoutOperationErrorDialog"), 746 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 747 } 748 } 749 } 750 751 /** 752 * Create a {@literal JComboBox<String>} containing all the options for 753 * turnout automation parameters for this turnout. 754 * 755 * @param t the turnout 756 * @return the JComboBox 757 */ 758 protected JComboBox<String> makeAutomationBox(Turnout t) { 759 String[] str = new String[]{"empty"}; 760 final JComboBox<String> cb = new JComboBox<>(str); 761 final Turnout myTurnout = t; 762 TurnoutTableAction.updateAutomationBox(t, cb); 763 cb.addActionListener(new ActionListener() { 764 @Override 765 public void actionPerformed(ActionEvent e) { 766 setTurnoutOperation(myTurnout, cb); 767 cb.removeActionListener(this); // avoid recursion 768 TurnoutTableAction.updateAutomationBox(myTurnout, cb); 769 cb.addActionListener(this); 770 } 771 }); 772 return cb; 773 } 774 775 /** 776 * Set the turnout's operation info based on the contents of the combo box. 777 * 778 * @param t turnout being configured 779 * @param cb JComboBox for ops for t in the TurnoutTable 780 */ 781 protected void setTurnoutOperation( @Nonnull Turnout t, JComboBox<String> cb) { 782 switch (cb.getSelectedIndex()) { 783 case 0: // Off 784 t.setInhibitOperation(true); 785 t.setTurnoutOperation(null); 786 break; 787 case 1: // Default 788 t.setInhibitOperation(false); 789 t.setTurnoutOperation(null); 790 break; 791 default: // named operation 792 t.setInhibitOperation(false); 793 t.setTurnoutOperation(InstanceManager.getDefault(TurnoutOperationManager.class). 794 getOperation(((String) java.util.Objects.requireNonNull(cb.getSelectedItem())))); 795 break; 796 } 797 } 798 799 /** 800 * Create action to edit a turnout in Edit pane. (also used in windowTest) 801 * 802 * @param t the turnout to be edited 803 */ 804 void editButton(Turnout t) { 805 jmri.jmrit.beantable.beanedit.TurnoutEditAction beanEdit = new jmri.jmrit.beantable.beanedit.TurnoutEditAction(); 806 beanEdit.setBean(t); 807 beanEdit.actionPerformed(null); 808 } 809 810 /** 811 * Create a JButton to edit a turnout's operation. 812 * 813 * @return the JButton 814 */ 815 protected JButton editButton() { 816 return new JButton(Bundle.getMessage("EditTurnoutOperation")); 817 } 818 819 private void updateClosedList() { 820 speedListClosed.remove(defaultClosedSpeedText); 821 defaultClosedSpeedText = (Bundle.getMessage("UseGlobal", "Global") + " " + turnoutManager.getDefaultClosedSpeed()); 822 speedListClosed.add(0, defaultClosedSpeedText); 823 fireTableDataChanged(); 824 } 825 826 private void updateThrownList() { 827 speedListThrown.remove(defaultThrownSpeedText); 828 defaultThrownSpeedText = (Bundle.getMessage("UseGlobal", "Global") + " " + turnoutManager.getDefaultThrownSpeed()); 829 speedListThrown.add(0, defaultThrownSpeedText); 830 fireTableDataChanged(); 831 } 832 833 public void showFeedbackChanged(boolean visible, JTable table ) { 834 XTableColumnModel columnModel = (XTableColumnModel) table.getColumnModel(); 835 TableColumn column = columnModel.getColumnByModelIndex(KNOWNCOL); 836 columnModel.setColumnVisible(column, visible); 837 column = columnModel.getColumnByModelIndex(MODECOL); 838 columnModel.setColumnVisible(column, visible); 839 column = columnModel.getColumnByModelIndex(SENSOR1COL); 840 columnModel.setColumnVisible(column, visible); 841 column = columnModel.getColumnByModelIndex(SENSOR2COL); 842 columnModel.setColumnVisible(column, visible); 843 column = columnModel.getColumnByModelIndex(OPSONOFFCOL); 844 columnModel.setColumnVisible(column, visible); 845 column = columnModel.getColumnByModelIndex(OPSEDITCOL); 846 columnModel.setColumnVisible(column, visible); 847 } 848 849 public void showLockChanged(boolean visible, JTable table) { 850 XTableColumnModel columnModel = (XTableColumnModel) table.getColumnModel(); 851 TableColumn column = ((XTableColumnModel) table.getColumnModel()).getColumnByModelIndex(LOCKDECCOL); 852 columnModel.setColumnVisible(column, visible); 853 column = columnModel.getColumnByModelIndex(LOCKOPRCOL); 854 columnModel.setColumnVisible(column, visible); 855 } 856 857 public void showTurnoutSpeedChanged(boolean visible, JTable table) { 858 XTableColumnModel columnModel = (XTableColumnModel) table.getColumnModel(); 859 TableColumn column = ((XTableColumnModel) table.getColumnModel()).getColumnByModelIndex(STRAIGHTCOL); 860 columnModel.setColumnVisible(column, visible); 861 column = columnModel.getColumnByModelIndex(DIVERGCOL); 862 columnModel.setColumnVisible(column, visible); 863 } 864 865 public void showStateForgetAndQueryChanged(boolean visible, JTable table) { 866 XTableColumnModel columnModel = (XTableColumnModel) table.getColumnModel(); 867 TableColumn column = columnModel.getColumnByModelIndex(FORGETCOL); 868 columnModel.setColumnVisible(column, visible); 869 column = columnModel.getColumnByModelIndex(QUERYCOL); 870 columnModel.setColumnVisible(column, visible); 871 } 872 873 874 /** 875 * Visualize state in table as a graphic, customized for Turnouts (4 876 * states). 877 * Renderer and Editor are identical, as the cell contents 878 * are not actually edited, only used to toggle state using 879 * {@link #clickOn(Turnout)}. 880 * 881 */ 882 class ImageIconRenderer extends AbstractCellEditor implements TableCellEditor, TableCellRenderer { 883 884 protected JLabel label; 885 protected String rootPath = "resources/icons/misc/switchboard/"; // also used in display.switchboardEditor 886 protected char beanTypeChar = 'T'; // for Turnout 887 protected String onIconPath = rootPath + beanTypeChar + "-on-s.png"; 888 protected String offIconPath = rootPath + beanTypeChar + "-off-s.png"; 889 protected BufferedImage onImage; 890 protected BufferedImage offImage; 891 protected ImageIcon onIcon; 892 protected ImageIcon offIcon; 893 protected int iconHeight = -1; 894 895 @Override 896 public java.awt.Component getTableCellRendererComponent( 897 JTable table, Object value, boolean isSelected, 898 boolean hasFocus, int row, int column) { 899 log.debug("Renderer Item = {}, State = {}", row, value); 900 if (iconHeight < 0) { // load resources only first time, either for renderer or editor 901 loadIcons(); 902 log.debug("icons loaded"); 903 } 904 return updateLabel((String) value, row, table); 905 } 906 907 @Override 908 public java.awt.Component getTableCellEditorComponent( 909 JTable table, Object value, boolean isSelected, 910 int row, int column) { 911 log.debug("Renderer Item = {}, State = {}", row, value); 912 if (iconHeight < 0) { // load resources only first time, either for renderer or editor 913 loadIcons(); 914 log.debug("icons loaded"); 915 } 916 return updateLabel((String) value, row, table); 917 } 918 919 public JLabel updateLabel(String value, int row, JTable table) { 920 if (iconHeight > 0) { // if necessary, increase row height; 921 table.setRowHeight(row, Math.max(table.getRowHeight(), iconHeight - 5)); 922 } 923 if (value.equals(closedText) && onIcon != null) { 924 label = new JLabel(onIcon); 925 label.setVerticalAlignment(JLabel.BOTTOM); 926 log.debug("onIcon set"); 927 } else if (value.equals(thrownText) && offIcon != null) { 928 label = new JLabel(offIcon); 929 label.setVerticalAlignment(JLabel.BOTTOM); 930 log.debug("offIcon set"); 931 } else if (value.equals(Bundle.getMessage("BeanStateInconsistent"))) { 932 label = new JLabel("X", JLabel.CENTER); // centered text alignment 933 label.setForeground(java.awt.Color.red); 934 log.debug("Turnout state inconsistent"); 935 iconHeight = 0; 936 } else if (value.equals(Bundle.getMessage("BeanStateUnknown"))) { 937 label = new JLabel("?", JLabel.CENTER); // centered text alignment 938 log.debug("Turnout state unknown"); 939 iconHeight = 0; 940 } else { // failed to load icon 941 label = new JLabel(value, JLabel.CENTER); // centered text alignment 942 log.warn("Error reading icons for TurnoutTable"); 943 iconHeight = 0; 944 } 945 label.setToolTipText(value); 946 label.addMouseListener(new MouseAdapter() { 947 @Override 948 public final void mousePressed(MouseEvent evt) { 949 log.debug("Clicked on icon in row {}", row); 950 stopCellEditing(); 951 } 952 }); 953 return label; 954 } 955 956 @Override 957 public Object getCellEditorValue() { 958 log.debug("getCellEditorValue, me = {})", this.toString()); 959 return this.toString(); 960 } 961 962 /** 963 * Read and buffer graphics. Only called once for this table. 964 * 965 * @see #getTableCellEditorComponent(JTable, Object, boolean, 966 * int, int) 967 */ 968 protected void loadIcons() { 969 try { 970 onImage = ImageIO.read(new File(onIconPath)); 971 offImage = ImageIO.read(new File(offIconPath)); 972 } catch (IOException ex) { 973 log.error("error reading image from {} or {}", onIconPath, offIconPath, ex); 974 } 975 log.debug("Success reading images"); 976 int imageWidth = onImage.getWidth(); 977 int imageHeight = onImage.getHeight(); 978 // scale icons 50% to fit in table rows 979 java.awt.Image smallOnImage = onImage.getScaledInstance(imageWidth / 2, imageHeight / 2, java.awt.Image.SCALE_DEFAULT); 980 java.awt.Image smallOffImage = offImage.getScaledInstance(imageWidth / 2, imageHeight / 2, java.awt.Image.SCALE_DEFAULT); 981 onIcon = new ImageIcon(smallOnImage); 982 offIcon = new ImageIcon(smallOffImage); 983 iconHeight = onIcon.getIconHeight(); 984 } 985 986 } // end of ImageIconRenderer class 987 988 protected static java.util.concurrent.atomic.AtomicBoolean editingOps = new java.util.concurrent.atomic.AtomicBoolean(false); 989 990 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TurnoutTableDataModel.class); 991 992}