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(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 TurnoutOperationEditorDialog dialog = new TurnoutOperationEditorDialog(op, t, box); 741 dialog.setVisible(true); 742 } else { 743 JmriJOptionPane.showMessageDialog(box, Bundle.getMessage("TurnoutOperationErrorDialog"), 744 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 745 } 746 } 747 } 748 749 /** 750 * Create a {@literal JComboBox<String>} containing all the options for 751 * turnout automation parameters for this turnout. 752 * 753 * @param t the turnout 754 * @return the JComboBox 755 */ 756 protected JComboBox<String> makeAutomationBox(Turnout t) { 757 String[] str = new String[]{"empty"}; 758 final JComboBox<String> cb = new JComboBox<>(str); 759 final Turnout myTurnout = t; 760 TurnoutTableAction.updateAutomationBox(t, cb); 761 cb.addActionListener(new ActionListener() { 762 @Override 763 public void actionPerformed(ActionEvent e) { 764 setTurnoutOperation(myTurnout, cb); 765 cb.removeActionListener(this); // avoid recursion 766 TurnoutTableAction.updateAutomationBox(myTurnout, cb); 767 cb.addActionListener(this); 768 } 769 }); 770 return cb; 771 } 772 773 /** 774 * Set the turnout's operation info based on the contents of the combo box. 775 * 776 * @param t turnout being configured 777 * @param cb JComboBox for ops for t in the TurnoutTable 778 */ 779 protected void setTurnoutOperation(Turnout t, JComboBox<String> cb) { 780 switch (cb.getSelectedIndex()) { 781 case 0: // Off 782 t.setInhibitOperation(true); 783 t.setTurnoutOperation(null); 784 break; 785 case 1: // Default 786 t.setInhibitOperation(false); 787 t.setTurnoutOperation(null); 788 break; 789 default: // named operation 790 t.setInhibitOperation(false); 791 t.setTurnoutOperation(InstanceManager.getDefault(TurnoutOperationManager.class). 792 getOperation(((String) java.util.Objects.requireNonNull(cb.getSelectedItem())))); 793 break; 794 } 795 } 796 797 /** 798 * Create action to edit a turnout in Edit pane. (also used in windowTest) 799 * 800 * @param t the turnout to be edited 801 */ 802 void editButton(Turnout t) { 803 jmri.jmrit.beantable.beanedit.TurnoutEditAction beanEdit = new jmri.jmrit.beantable.beanedit.TurnoutEditAction(); 804 beanEdit.setBean(t); 805 beanEdit.actionPerformed(null); 806 } 807 808 /** 809 * Create a JButton to edit a turnout's operation. 810 * 811 * @return the JButton 812 */ 813 protected JButton editButton() { 814 return new JButton(Bundle.getMessage("EditTurnoutOperation")); 815 } 816 817 private void updateClosedList() { 818 speedListClosed.remove(defaultClosedSpeedText); 819 defaultClosedSpeedText = (Bundle.getMessage("UseGlobal", "Global") + " " + turnoutManager.getDefaultClosedSpeed()); 820 speedListClosed.add(0, defaultClosedSpeedText); 821 fireTableDataChanged(); 822 } 823 824 private void updateThrownList() { 825 speedListThrown.remove(defaultThrownSpeedText); 826 defaultThrownSpeedText = (Bundle.getMessage("UseGlobal", "Global") + " " + turnoutManager.getDefaultThrownSpeed()); 827 speedListThrown.add(0, defaultThrownSpeedText); 828 fireTableDataChanged(); 829 } 830 831 public void showFeedbackChanged(boolean visible, JTable table ) { 832 XTableColumnModel columnModel = (XTableColumnModel) table.getColumnModel(); 833 TableColumn column = columnModel.getColumnByModelIndex(KNOWNCOL); 834 columnModel.setColumnVisible(column, visible); 835 column = columnModel.getColumnByModelIndex(MODECOL); 836 columnModel.setColumnVisible(column, visible); 837 column = columnModel.getColumnByModelIndex(SENSOR1COL); 838 columnModel.setColumnVisible(column, visible); 839 column = columnModel.getColumnByModelIndex(SENSOR2COL); 840 columnModel.setColumnVisible(column, visible); 841 column = columnModel.getColumnByModelIndex(OPSONOFFCOL); 842 columnModel.setColumnVisible(column, visible); 843 column = columnModel.getColumnByModelIndex(OPSEDITCOL); 844 columnModel.setColumnVisible(column, visible); 845 } 846 847 public void showLockChanged(boolean visible, JTable table) { 848 XTableColumnModel columnModel = (XTableColumnModel) table.getColumnModel(); 849 TableColumn column = ((XTableColumnModel) table.getColumnModel()).getColumnByModelIndex(LOCKDECCOL); 850 columnModel.setColumnVisible(column, visible); 851 column = columnModel.getColumnByModelIndex(LOCKOPRCOL); 852 columnModel.setColumnVisible(column, visible); 853 } 854 855 public void showTurnoutSpeedChanged(boolean visible, JTable table) { 856 XTableColumnModel columnModel = (XTableColumnModel) table.getColumnModel(); 857 TableColumn column = ((XTableColumnModel) table.getColumnModel()).getColumnByModelIndex(STRAIGHTCOL); 858 columnModel.setColumnVisible(column, visible); 859 column = columnModel.getColumnByModelIndex(DIVERGCOL); 860 columnModel.setColumnVisible(column, visible); 861 } 862 863 public void showStateForgetAndQueryChanged(boolean visible, JTable table) { 864 XTableColumnModel columnModel = (XTableColumnModel) table.getColumnModel(); 865 TableColumn column = columnModel.getColumnByModelIndex(FORGETCOL); 866 columnModel.setColumnVisible(column, visible); 867 column = columnModel.getColumnByModelIndex(QUERYCOL); 868 columnModel.setColumnVisible(column, visible); 869 } 870 871 872 /** 873 * Visualize state in table as a graphic, customized for Turnouts (4 874 * states). 875 * Renderer and Editor are identical, as the cell contents 876 * are not actually edited, only used to toggle state using 877 * {@link #clickOn(Turnout)}. 878 * 879 */ 880 class ImageIconRenderer extends AbstractCellEditor implements TableCellEditor, TableCellRenderer { 881 882 protected JLabel label; 883 protected String rootPath = "resources/icons/misc/switchboard/"; // also used in display.switchboardEditor 884 protected char beanTypeChar = 'T'; // for Turnout 885 protected String onIconPath = rootPath + beanTypeChar + "-on-s.png"; 886 protected String offIconPath = rootPath + beanTypeChar + "-off-s.png"; 887 protected BufferedImage onImage; 888 protected BufferedImage offImage; 889 protected ImageIcon onIcon; 890 protected ImageIcon offIcon; 891 protected int iconHeight = -1; 892 893 @Override 894 public java.awt.Component getTableCellRendererComponent( 895 JTable table, Object value, boolean isSelected, 896 boolean hasFocus, int row, int column) { 897 log.debug("Renderer Item = {}, State = {}", row, value); 898 if (iconHeight < 0) { // load resources only first time, either for renderer or editor 899 loadIcons(); 900 log.debug("icons loaded"); 901 } 902 return updateLabel((String) value, row, table); 903 } 904 905 @Override 906 public java.awt.Component getTableCellEditorComponent( 907 JTable table, Object value, boolean isSelected, 908 int row, int column) { 909 log.debug("Renderer Item = {}, State = {}", row, value); 910 if (iconHeight < 0) { // load resources only first time, either for renderer or editor 911 loadIcons(); 912 log.debug("icons loaded"); 913 } 914 return updateLabel((String) value, row, table); 915 } 916 917 public JLabel updateLabel(String value, int row, JTable table) { 918 if (iconHeight > 0) { // if necessary, increase row height; 919 table.setRowHeight(row, Math.max(table.getRowHeight(), iconHeight - 5)); 920 } 921 if (value.equals(closedText) && onIcon != null) { 922 label = new JLabel(onIcon); 923 label.setVerticalAlignment(JLabel.BOTTOM); 924 log.debug("onIcon set"); 925 } else if (value.equals(thrownText) && offIcon != null) { 926 label = new JLabel(offIcon); 927 label.setVerticalAlignment(JLabel.BOTTOM); 928 log.debug("offIcon set"); 929 } else if (value.equals(Bundle.getMessage("BeanStateInconsistent"))) { 930 label = new JLabel("X", JLabel.CENTER); // centered text alignment 931 label.setForeground(java.awt.Color.red); 932 log.debug("Turnout state inconsistent"); 933 iconHeight = 0; 934 } else if (value.equals(Bundle.getMessage("BeanStateUnknown"))) { 935 label = new JLabel("?", JLabel.CENTER); // centered text alignment 936 log.debug("Turnout state unknown"); 937 iconHeight = 0; 938 } else { // failed to load icon 939 label = new JLabel(value, JLabel.CENTER); // centered text alignment 940 log.warn("Error reading icons for TurnoutTable"); 941 iconHeight = 0; 942 } 943 label.setToolTipText(value); 944 label.addMouseListener(new MouseAdapter() { 945 @Override 946 public final void mousePressed(MouseEvent evt) { 947 log.debug("Clicked on icon in row {}", row); 948 stopCellEditing(); 949 } 950 }); 951 return label; 952 } 953 954 @Override 955 public Object getCellEditorValue() { 956 log.debug("getCellEditorValue, me = {})", this.toString()); 957 return this.toString(); 958 } 959 960 /** 961 * Read and buffer graphics. Only called once for this table. 962 * 963 * @see #getTableCellEditorComponent(JTable, Object, boolean, 964 * int, int) 965 */ 966 protected void loadIcons() { 967 try { 968 onImage = ImageIO.read(new File(onIconPath)); 969 offImage = ImageIO.read(new File(offIconPath)); 970 } catch (IOException ex) { 971 log.error("error reading image from {} or {}", onIconPath, offIconPath, ex); 972 } 973 log.debug("Success reading images"); 974 int imageWidth = onImage.getWidth(); 975 int imageHeight = onImage.getHeight(); 976 // scale icons 50% to fit in table rows 977 java.awt.Image smallOnImage = onImage.getScaledInstance(imageWidth / 2, imageHeight / 2, java.awt.Image.SCALE_DEFAULT); 978 java.awt.Image smallOffImage = offImage.getScaledInstance(imageWidth / 2, imageHeight / 2, java.awt.Image.SCALE_DEFAULT); 979 onIcon = new ImageIcon(smallOnImage); 980 offIcon = new ImageIcon(smallOffImage); 981 iconHeight = onIcon.getIconHeight(); 982 } 983 984 } // end of ImageIconRenderer class 985 986 protected static java.util.concurrent.atomic.AtomicBoolean editingOps = new java.util.concurrent.atomic.AtomicBoolean(false); 987 988 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TurnoutTableDataModel.class); 989 990}