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 return getClassName(); 570 } 571 572 protected String getClassName() { 573 return jmri.jmrit.beantable.TurnoutTableAction.class.getName(); 574 } 575 576 @Override 577 public void clickOn(Turnout t) { 578 t.setCommandedState( t.getCommandedState()== Turnout.CLOSED ? Turnout.THROWN : Turnout.CLOSED); 579 } 580 581 @Override 582 public void configureTable(JTable tbl) { 583 584 setColumnToHoldButton(tbl, EDITCOL, editButton()); 585 setColumnToHoldButton(tbl, OPSEDITCOL, editButton()); 586 587 //Hide the following columns by default 588 XTableColumnModel columnModel = (XTableColumnModel) tbl.getColumnModel(); 589 TableColumn column = columnModel.getColumnByModelIndex(STRAIGHTCOL); 590 columnModel.setColumnVisible(column, false); 591 column = columnModel.getColumnByModelIndex(DIVERGCOL); 592 columnModel.setColumnVisible(column, false); 593 column = columnModel.getColumnByModelIndex(KNOWNCOL); 594 columnModel.setColumnVisible(column, false); 595 column = columnModel.getColumnByModelIndex(MODECOL); 596 columnModel.setColumnVisible(column, false); 597 column = columnModel.getColumnByModelIndex(SENSOR1COL); 598 columnModel.setColumnVisible(column, false); 599 column = columnModel.getColumnByModelIndex(SENSOR2COL); 600 columnModel.setColumnVisible(column, false); 601 column = columnModel.getColumnByModelIndex(OPSONOFFCOL); 602 columnModel.setColumnVisible(column, false); 603 column = columnModel.getColumnByModelIndex(OPSEDITCOL); 604 columnModel.setColumnVisible(column, false); 605 column = columnModel.getColumnByModelIndex(LOCKOPRCOL); 606 columnModel.setColumnVisible(column, false); 607 column = columnModel.getColumnByModelIndex(LOCKDECCOL); 608 columnModel.setColumnVisible(column, false); 609 column = columnModel.getColumnByModelIndex(FORGETCOL); 610 columnModel.setColumnVisible(column, false); 611 column = columnModel.getColumnByModelIndex(QUERYCOL); 612 columnModel.setColumnVisible(column, false); 613 614 615 // and then set user prefs 616 super.configureTable(tbl); 617 618 columnModel.getColumnByModelIndex(FORGETCOL).setHeaderValue(null); 619 columnModel.getColumnByModelIndex(QUERYCOL).setHeaderValue(null); 620 621 } 622 623 // update table if turnout lock or feedback changes 624 @Override 625 protected boolean matchPropertyName(java.beans.PropertyChangeEvent e) { 626 switch (e.getPropertyName()) { 627 case "locked": 628 case "inverted": 629 case "feedbackchange": // feedback type setting change, NOT Turnout feedback status 630 case "TurnoutDivergingSpeedChange": 631 case "TurnoutStraightSpeedChange": 632 case "turnoutFeedbackFirstSensorChange": 633 case "turnoutFeedbackSecondSensorChange": 634 case "decoderNameChange": 635 case "TurnoutOperationState": 636 case "KnownState": 637 return true; 638 default: 639 return super.matchPropertyName(e); 640 } 641 } 642 643 @Override 644 public void propertyChange(java.beans.PropertyChangeEvent e) { 645 switch (e.getPropertyName()) { 646 case "DefaultTurnoutClosedSpeedChange": 647 updateClosedList(); 648 break; 649 case "DefaultTurnoutThrownSpeedChange": 650 updateThrownList(); 651 break; 652 default: 653 super.propertyChange(e); 654 break; 655 } 656 } 657 658 /** 659 * Customize the turnout table Value (State) column to show an 660 * appropriate graphic for the turnout state if _graphicState = 661 * true, or (default) just show the localized state text when the 662 * TableDataModel is being called from ListedTableAction. 663 * 664 * @param table a JTable of Turnouts 665 */ 666 @Override 667 protected void configValueColumn(JTable table) { 668 // have the value column hold a JPanel (icon) 669 //setColumnToHoldButton(table, VALUECOL, new JLabel("12345678")); // for larger, wide round icon, but cannot be converted to JButton 670 // add extras, override BeanTableDataModel 671 log.debug("Turnout configValueColumn (I am {})", super.toString()); 672 if (_graphicState) { // load icons, only once 673 table.setDefaultEditor(JLabel.class, new ImageIconRenderer()); // editor 674 table.setDefaultRenderer(JLabel.class, new ImageIconRenderer()); // item class copied from SwitchboardEditor panel 675 } else { 676 super.configValueColumn(table); // classic text style state indication 677 } 678 } 679 680 @Override 681 public JTable makeJTable(@Nonnull String name, @Nonnull TableModel model, @CheckForNull RowSorter<? extends TableModel> sorter) { 682 if (!(model instanceof TurnoutTableDataModel)){ 683 throw new IllegalArgumentException("Model is not a TurnoutTableDataModel"); 684 } 685 return configureJTable(name, new TurnoutTableJTable((TurnoutTableDataModel)model), sorter); 686 } 687 688 @Override 689 protected void setColumnIdentities(JTable table) { 690 super.setColumnIdentities(table); 691 java.util.Enumeration<TableColumn> columns; 692 if (table.getColumnModel() instanceof XTableColumnModel) { 693 columns = ((XTableColumnModel) table.getColumnModel()).getColumns(false); 694 } else { 695 columns = table.getColumnModel().getColumns(); 696 } 697 while (columns.hasMoreElements()) { 698 TableColumn column = columns.nextElement(); 699 switch (column.getModelIndex()) { 700 case FORGETCOL: 701 column.setIdentifier("ForgetState"); 702 break; 703 case QUERYCOL: 704 column.setIdentifier("QueryState"); 705 break; 706 case SENSOR1COL: 707 column.setIdentifier("Sensor1"); 708 break; 709 case SENSOR2COL: 710 column.setIdentifier("Sensor2"); 711 break; 712 default: 713 // use existing value 714 } 715 } 716 } 717 718 /** 719 * Pop up a TurnoutOperationConfig for the turnout. 720 * 721 * @param t turnout 722 * @param box JComboBox that triggered the edit 723 */ 724 protected void editTurnoutOperation( @Nonnull Turnout t, JComboBox<String> box) { 725 if (!editingOps.getAndSet(true)) { // don't open a second edit ops pane 726 TurnoutOperation op = t.getTurnoutOperation(); 727 if (op == null) { 728 TurnoutOperation proto = InstanceManager.getDefault(TurnoutOperationManager.class).getMatchingOperationAlways(t); 729 if (proto != null) { 730 op = proto.makeNonce(t); 731 t.setTurnoutOperation(op); 732 } 733 } 734 if (op != null) { 735 if (!op.isNonce()) { 736 op = op.makeNonce(t); 737 } 738 // make and show edit dialog 739 log.debug("TurnoutOpsEditDialog starting"); 740 java.awt.Window w = JmriJOptionPane.findWindowForObject(box); 741 TurnoutOperationEditorDialog dialog = new TurnoutOperationEditorDialog(op, t, w); 742 dialog.setVisible(true); 743 } else { 744 JmriJOptionPane.showMessageDialog(box, Bundle.getMessage("TurnoutOperationErrorDialog"), 745 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 746 } 747 } 748 } 749 750 /** 751 * Create a {@literal JComboBox<String>} containing all the options for 752 * turnout automation parameters for this turnout. 753 * 754 * @param t the turnout 755 * @return the JComboBox 756 */ 757 protected JComboBox<String> makeAutomationBox(Turnout t) { 758 String[] str = new String[]{"empty"}; 759 final JComboBox<String> cb = new JComboBox<>(str); 760 final Turnout myTurnout = t; 761 TurnoutTableAction.updateAutomationBox(t, cb); 762 cb.addActionListener(new ActionListener() { 763 @Override 764 public void actionPerformed(ActionEvent e) { 765 setTurnoutOperation(myTurnout, cb); 766 cb.removeActionListener(this); // avoid recursion 767 TurnoutTableAction.updateAutomationBox(myTurnout, cb); 768 cb.addActionListener(this); 769 } 770 }); 771 return cb; 772 } 773 774 /** 775 * Set the turnout's operation info based on the contents of the combo box. 776 * 777 * @param t turnout being configured 778 * @param cb JComboBox for ops for t in the TurnoutTable 779 */ 780 protected void setTurnoutOperation( @Nonnull Turnout t, JComboBox<String> cb) { 781 switch (cb.getSelectedIndex()) { 782 case 0: // Off 783 t.setInhibitOperation(true); 784 t.setTurnoutOperation(null); 785 break; 786 case 1: // Default 787 t.setInhibitOperation(false); 788 t.setTurnoutOperation(null); 789 break; 790 default: // named operation 791 t.setInhibitOperation(false); 792 t.setTurnoutOperation(InstanceManager.getDefault(TurnoutOperationManager.class). 793 getOperation(((String) java.util.Objects.requireNonNull(cb.getSelectedItem())))); 794 break; 795 } 796 } 797 798 /** 799 * Create action to edit a turnout in Edit pane. (also used in windowTest) 800 * 801 * @param t the turnout to be edited 802 */ 803 void editButton(Turnout t) { 804 jmri.jmrit.beantable.beanedit.TurnoutEditAction beanEdit = new jmri.jmrit.beantable.beanedit.TurnoutEditAction(); 805 beanEdit.setBean(t); 806 beanEdit.actionPerformed(null); 807 } 808 809 /** 810 * Create a JButton to edit a turnout's operation. 811 * 812 * @return the JButton 813 */ 814 protected JButton editButton() { 815 return new JButton(Bundle.getMessage("EditTurnoutOperation")); 816 } 817 818 private void updateClosedList() { 819 speedListClosed.remove(defaultClosedSpeedText); 820 defaultClosedSpeedText = (Bundle.getMessage("UseGlobal", "Global") + " " + turnoutManager.getDefaultClosedSpeed()); 821 speedListClosed.add(0, defaultClosedSpeedText); 822 fireTableDataChanged(); 823 } 824 825 private void updateThrownList() { 826 speedListThrown.remove(defaultThrownSpeedText); 827 defaultThrownSpeedText = (Bundle.getMessage("UseGlobal", "Global") + " " + turnoutManager.getDefaultThrownSpeed()); 828 speedListThrown.add(0, defaultThrownSpeedText); 829 fireTableDataChanged(); 830 } 831 832 public void showFeedbackChanged(boolean visible, JTable table ) { 833 XTableColumnModel columnModel = (XTableColumnModel) table.getColumnModel(); 834 TableColumn column = columnModel.getColumnByModelIndex(KNOWNCOL); 835 columnModel.setColumnVisible(column, visible); 836 column = columnModel.getColumnByModelIndex(MODECOL); 837 columnModel.setColumnVisible(column, visible); 838 column = columnModel.getColumnByModelIndex(SENSOR1COL); 839 columnModel.setColumnVisible(column, visible); 840 column = columnModel.getColumnByModelIndex(SENSOR2COL); 841 columnModel.setColumnVisible(column, visible); 842 column = columnModel.getColumnByModelIndex(OPSONOFFCOL); 843 columnModel.setColumnVisible(column, visible); 844 column = columnModel.getColumnByModelIndex(OPSEDITCOL); 845 columnModel.setColumnVisible(column, visible); 846 } 847 848 public void showLockChanged(boolean visible, JTable table) { 849 XTableColumnModel columnModel = (XTableColumnModel) table.getColumnModel(); 850 TableColumn column = ((XTableColumnModel) table.getColumnModel()).getColumnByModelIndex(LOCKDECCOL); 851 columnModel.setColumnVisible(column, visible); 852 column = columnModel.getColumnByModelIndex(LOCKOPRCOL); 853 columnModel.setColumnVisible(column, visible); 854 } 855 856 public void showTurnoutSpeedChanged(boolean visible, JTable table) { 857 XTableColumnModel columnModel = (XTableColumnModel) table.getColumnModel(); 858 TableColumn column = ((XTableColumnModel) table.getColumnModel()).getColumnByModelIndex(STRAIGHTCOL); 859 columnModel.setColumnVisible(column, visible); 860 column = columnModel.getColumnByModelIndex(DIVERGCOL); 861 columnModel.setColumnVisible(column, visible); 862 } 863 864 public void showStateForgetAndQueryChanged(boolean visible, JTable table) { 865 XTableColumnModel columnModel = (XTableColumnModel) table.getColumnModel(); 866 TableColumn column = columnModel.getColumnByModelIndex(FORGETCOL); 867 columnModel.setColumnVisible(column, visible); 868 column = columnModel.getColumnByModelIndex(QUERYCOL); 869 columnModel.setColumnVisible(column, visible); 870 } 871 872 873 /** 874 * Visualize state in table as a graphic, customized for Turnouts (4 875 * states). 876 * Renderer and Editor are identical, as the cell contents 877 * are not actually edited, only used to toggle state using 878 * {@link #clickOn(Turnout)}. 879 * 880 */ 881 class ImageIconRenderer extends AbstractCellEditor implements TableCellEditor, TableCellRenderer { 882 883 protected JLabel label; 884 protected String rootPath = "resources/icons/misc/switchboard/"; // also used in display.switchboardEditor 885 protected char beanTypeChar = 'T'; // for Turnout 886 protected String onIconPath = rootPath + beanTypeChar + "-on-s.png"; 887 protected String offIconPath = rootPath + beanTypeChar + "-off-s.png"; 888 protected BufferedImage onImage; 889 protected BufferedImage offImage; 890 protected ImageIcon onIcon; 891 protected ImageIcon offIcon; 892 protected int iconHeight = -1; 893 894 @Override 895 public java.awt.Component getTableCellRendererComponent( 896 JTable table, Object value, boolean isSelected, 897 boolean hasFocus, int row, int column) { 898 log.debug("Renderer Item = {}, State = {}", row, value); 899 if (iconHeight < 0) { // load resources only first time, either for renderer or editor 900 loadIcons(); 901 log.debug("icons loaded"); 902 } 903 return updateLabel((String) value, row, table); 904 } 905 906 @Override 907 public java.awt.Component getTableCellEditorComponent( 908 JTable table, Object value, boolean isSelected, 909 int row, int column) { 910 log.debug("Renderer Item = {}, State = {}", row, value); 911 if (iconHeight < 0) { // load resources only first time, either for renderer or editor 912 loadIcons(); 913 log.debug("icons loaded"); 914 } 915 return updateLabel((String) value, row, table); 916 } 917 918 public JLabel updateLabel(String value, int row, JTable table) { 919 if (iconHeight > 0) { // if necessary, increase row height; 920 table.setRowHeight(row, Math.max(table.getRowHeight(), iconHeight - 5)); 921 } 922 if (value.equals(closedText) && onIcon != null) { 923 label = new JLabel(onIcon); 924 label.setVerticalAlignment(JLabel.BOTTOM); 925 log.debug("onIcon set"); 926 } else if (value.equals(thrownText) && offIcon != null) { 927 label = new JLabel(offIcon); 928 label.setVerticalAlignment(JLabel.BOTTOM); 929 log.debug("offIcon set"); 930 } else if (value.equals(Bundle.getMessage("BeanStateInconsistent"))) { 931 label = new JLabel("X", JLabel.CENTER); // centered text alignment 932 label.setForeground(java.awt.Color.red); 933 log.debug("Turnout state inconsistent"); 934 iconHeight = 0; 935 } else if (value.equals(Bundle.getMessage("BeanStateUnknown"))) { 936 label = new JLabel("?", JLabel.CENTER); // centered text alignment 937 log.debug("Turnout state unknown"); 938 iconHeight = 0; 939 } else { // failed to load icon 940 label = new JLabel(value, JLabel.CENTER); // centered text alignment 941 log.warn("Error reading icons for TurnoutTable"); 942 iconHeight = 0; 943 } 944 label.setToolTipText(value); 945 label.addMouseListener(new MouseAdapter() { 946 @Override 947 public final void mousePressed(MouseEvent evt) { 948 log.debug("Clicked on icon in row {}", row); 949 stopCellEditing(); 950 } 951 }); 952 return label; 953 } 954 955 @Override 956 public Object getCellEditorValue() { 957 log.debug("getCellEditorValue, me = {})", this.toString()); 958 return this.toString(); 959 } 960 961 /** 962 * Read and buffer graphics. Only called once for this table. 963 * 964 * @see #getTableCellEditorComponent(JTable, Object, boolean, 965 * int, int) 966 */ 967 protected void loadIcons() { 968 try { 969 onImage = ImageIO.read(new File(onIconPath)); 970 offImage = ImageIO.read(new File(offIconPath)); 971 } catch (IOException ex) { 972 log.error("error reading image from {} or {}", onIconPath, offIconPath, ex); 973 } 974 log.debug("Success reading images"); 975 int imageWidth = onImage.getWidth(); 976 int imageHeight = onImage.getHeight(); 977 // scale icons 50% to fit in table rows 978 java.awt.Image smallOnImage = onImage.getScaledInstance(imageWidth / 2, imageHeight / 2, java.awt.Image.SCALE_DEFAULT); 979 java.awt.Image smallOffImage = offImage.getScaledInstance(imageWidth / 2, imageHeight / 2, java.awt.Image.SCALE_DEFAULT); 980 onIcon = new ImageIcon(smallOnImage); 981 offIcon = new ImageIcon(smallOffImage); 982 iconHeight = onIcon.getIconHeight(); 983 } 984 985 } // end of ImageIconRenderer class 986 987 protected static java.util.concurrent.atomic.AtomicBoolean editingOps = new java.util.concurrent.atomic.AtomicBoolean(false); 988 989 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TurnoutTableDataModel.class); 990 991}