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