001package jmri.jmrit.operations.rollingstock.engines.gui; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.util.List; 006 007import javax.swing.*; 008import javax.swing.table.TableCellEditor; 009 010import org.slf4j.Logger; 011import org.slf4j.LoggerFactory; 012 013import jmri.InstanceManager; 014import jmri.jmrit.operations.OperationsTableModel; 015import jmri.jmrit.operations.rollingstock.RollingStock; 016import jmri.jmrit.operations.rollingstock.engines.*; 017import jmri.jmrit.operations.setup.Control; 018import jmri.jmrit.operations.setup.Setup; 019import jmri.util.swing.XTableColumnModel; 020import jmri.util.table.ButtonEditor; 021import jmri.util.table.ButtonRenderer; 022 023/** 024 * Table Model for edit of engines used by operations 025 * 026 * @author Daniel Boudreau Copyright (C) 2008, 2012, 2025 027 */ 028public class EnginesTableModel extends OperationsTableModel implements PropertyChangeListener { 029 030 EngineManager engineManager = InstanceManager.getDefault(EngineManager.class); // There is only one manager 031 032 // Defines the columns 033 private static final int SELECT_COLUMN = 0; 034 private static final int NUM_COLUMN = 1; 035 private static final int ROAD_COLUMN = 2; 036 private static final int MODEL_COLUMN = 3; 037 private static final int HP_COLUMN = 4; 038 private static final int WEIGHT_COLUMN = 5; 039 private static final int TYPE_COLUMN = 6; 040 private static final int LENGTH_COLUMN = 7; 041 private static final int CONSIST_COLUMN = 8; 042 private static final int LOCATION_COLUMN = 9; 043 private static final int RFID_WHERE_LAST_SEEN_COLUMN = 10; 044 private static final int RFID_WHEN_LAST_SEEN_COLUMN = 11; 045 private static final int DESTINATION_COLUMN = 12; 046 private static final int PREVIOUS_LOCATION_COLUMN = 13; 047 private static final int TRAIN_COLUMN = 14; 048 private static final int LAST_TRAIN_COLUMN = 15; 049 private static final int MOVES_COLUMN = 16; 050 private static final int BUILT_COLUMN = 17; 051 private static final int OWNER_COLUMN = 18; 052 private static final int VALUE_COLUMN = 19; 053 private static final int RFID_COLUMN = 20; 054 private static final int LAST_COLUMN = 21; 055 private static final int DCC_ADDRESS_COLUMN = 22; 056 private static final int COMMENT_COLUMN = 23; 057 private static final int SET_COLUMN = 24; 058 private static final int EDIT_COLUMN = 25; 059 060 private static final int HIGHEST_COLUMN = EDIT_COLUMN + 1; 061 062 public EnginesTableModel(boolean showAllLocos, String locationName, String trackName) { 063 super(); 064 showAll = showAllLocos; 065 this.locationName = locationName; 066 this.trackName = trackName; 067 engineManager.addPropertyChangeListener(this); 068 updateList(); 069 } 070 071 public final int SORTBY_NUMBER = 0; 072 public final int SORTBY_ROAD = 1; 073 public final int SORTBY_MODEL = 2; 074 public final int SORTBY_LOCATION = 3; 075 public final int SORTBY_DESTINATION = 4; 076 public final int SORTBY_TRAIN = 5; 077 public final int SORTBY_MOVES = 6; 078 public final int SORTBY_CONSIST = 7; 079 public final int SORTBY_BUILT = 8; 080 public final int SORTBY_OWNER = 9; 081 public final int SORTBY_VALUE = 10; 082 public final int SORTBY_RFID = 11; 083 public final int SORTBY_LAST = 12; 084 public final int SORTBY_HP = 13; 085 public final int SORTBY_DCC_ADDRESS = 14; 086 public final int SORTBY_COMMENT = 15; 087 088 private int _sort = SORTBY_NUMBER; 089 090 /** 091 * Not all columns are visible at the same time. 092 * 093 * @param sort which sort is active 094 */ 095 public void setSort(int sort) { 096 _sort = sort; 097 updateList(); 098 if (sort == SORTBY_MOVES || 099 sort == SORTBY_BUILT || 100 sort == SORTBY_OWNER || 101 sort == SORTBY_VALUE || 102 sort == SORTBY_RFID || 103 sort == SORTBY_LAST || 104 sort == SORTBY_DCC_ADDRESS || 105 sort == SORTBY_COMMENT) { 106 XTableColumnModel tcm = (XTableColumnModel) _table.getColumnModel(); 107 tcm.setColumnVisible(tcm.getColumnByModelIndex(MOVES_COLUMN), sort == SORTBY_MOVES); 108 tcm.setColumnVisible(tcm.getColumnByModelIndex(BUILT_COLUMN), sort == SORTBY_BUILT); 109 tcm.setColumnVisible(tcm.getColumnByModelIndex(OWNER_COLUMN), sort == SORTBY_OWNER); 110 tcm.setColumnVisible(tcm.getColumnByModelIndex(VALUE_COLUMN), sort == SORTBY_VALUE); 111 tcm.setColumnVisible(tcm.getColumnByModelIndex(RFID_COLUMN), sort == SORTBY_RFID); 112 tcm.setColumnVisible(tcm.getColumnByModelIndex(RFID_WHEN_LAST_SEEN_COLUMN), sort == SORTBY_RFID); 113 tcm.setColumnVisible(tcm.getColumnByModelIndex(RFID_WHERE_LAST_SEEN_COLUMN), sort == SORTBY_RFID); 114 tcm.setColumnVisible(tcm.getColumnByModelIndex(PREVIOUS_LOCATION_COLUMN), sort == SORTBY_LAST); 115 tcm.setColumnVisible(tcm.getColumnByModelIndex(LAST_COLUMN), sort == SORTBY_LAST); 116 tcm.setColumnVisible(tcm.getColumnByModelIndex(LAST_TRAIN_COLUMN), sort == SORTBY_LAST); 117 tcm.setColumnVisible(tcm.getColumnByModelIndex(TRAIN_COLUMN), sort != SORTBY_LAST); 118 tcm.setColumnVisible(tcm.getColumnByModelIndex(DCC_ADDRESS_COLUMN), sort == SORTBY_DCC_ADDRESS); 119 tcm.setColumnVisible(tcm.getColumnByModelIndex(COMMENT_COLUMN), sort == SORTBY_COMMENT); 120 } 121 fireTableDataChanged(); 122 } 123 124 public void toggleSelectVisible() { 125 XTableColumnModel tcm = (XTableColumnModel) _table.getColumnModel(); 126 tcm.setColumnVisible(tcm.getColumnByModelIndex(SELECT_COLUMN), 127 !tcm.isColumnVisible(tcm.getColumnByModelIndex(SELECT_COLUMN))); 128 } 129 130 public void resetCheckboxes() { 131 for (Engine engine : engineList) { 132 engine.setSelected(false); 133 } 134 } 135 136 public String getSortByName() { 137 return getSortByName(_sort); 138 } 139 140 public String getSortByName(int sort) { 141 switch (sort) { 142 case SORTBY_NUMBER: 143 return Bundle.getMessage("Number"); 144 case SORTBY_ROAD: 145 return Bundle.getMessage("Road"); 146 case SORTBY_MODEL: 147 return Bundle.getMessage("Model"); 148 case SORTBY_LOCATION: 149 return Bundle.getMessage("Location"); 150 case SORTBY_DESTINATION: 151 return Bundle.getMessage("Destination"); 152 case SORTBY_TRAIN: 153 return Bundle.getMessage("Train"); 154 case SORTBY_MOVES: 155 return Bundle.getMessage("Moves"); 156 case SORTBY_CONSIST: 157 return Bundle.getMessage("Consist"); 158 case SORTBY_BUILT: 159 return Bundle.getMessage("Built"); 160 case SORTBY_OWNER: 161 return Bundle.getMessage("Owner"); 162 case SORTBY_DCC_ADDRESS: 163 return Bundle.getMessage("DccAddress"); 164 case SORTBY_HP: 165 return Bundle.getMessage("HP"); 166 case SORTBY_VALUE: 167 return Setup.getValueLabel(); 168 case SORTBY_RFID: 169 return Setup.getRfidLabel(); 170 case SORTBY_LAST: 171 return Bundle.getMessage("Last"); 172 case SORTBY_COMMENT: 173 return Bundle.getMessage("Comment"); 174 default: 175 return "Error"; // NOI18N 176 } 177 } 178 179 /** 180 * Search for engine by road number 181 * 182 * @param roadNumber The string road number to search for. 183 * 184 * @return -1 if not found, table row number if found 185 */ 186 public int findEngineByRoadNumber(String roadNumber) { 187 return findRollingStockByRoadNumber(roadNumber, engineList); 188 } 189 190 public Engine getEngineAtIndex(int index) { 191 return engineList.get(index); 192 } 193 194 private void updateList() { 195 // first, remove listeners from the individual objects 196 removePropertyChangeEngines(); 197 engineList = getSelectedEngineList(); 198 // and add listeners back in 199 addPropertyChangeEngines(); 200 } 201 202 public List<Engine> getSelectedEngineList() { 203 return getEngineList(_sort); 204 } 205 206 public List<Engine> getEngineList(int sort) { 207 List<Engine> list; 208 switch (sort) { 209 case SORTBY_ROAD: 210 list = engineManager.getByRoadNameList(); 211 break; 212 case SORTBY_MODEL: 213 list = engineManager.getByModelList(); 214 break; 215 case SORTBY_LOCATION: 216 list = engineManager.getByLocationList(); 217 break; 218 case SORTBY_DESTINATION: 219 list = engineManager.getByDestinationList(); 220 break; 221 case SORTBY_TRAIN: 222 list = engineManager.getByTrainList(); 223 break; 224 case SORTBY_MOVES: 225 list = engineManager.getByMovesList(); 226 break; 227 case SORTBY_CONSIST: 228 list = engineManager.getByConsistList(); 229 break; 230 case SORTBY_OWNER: 231 list = engineManager.getByOwnerList(); 232 break; 233 case SORTBY_BUILT: 234 list = engineManager.getByBuiltList(); 235 break; 236 case SORTBY_VALUE: 237 list = engineManager.getByValueList(); 238 break; 239 case SORTBY_RFID: 240 list = engineManager.getByRfidList(); 241 break; 242 case SORTBY_LAST: 243 list = engineManager.getByLastDateList(); 244 break; 245 case SORTBY_COMMENT: 246 list = engineManager.getByCommentList(); 247 break; 248 case SORTBY_NUMBER: 249 default: 250 list = engineManager.getByNumberList(); 251 } 252 filterList(list); 253 return list; 254 } 255 256 List<Engine> engineList = null; 257 258 EnginesTableFrame _frame; 259 260 void initTable(JTable table, EnginesTableFrame frame) { 261 _table = table; 262 _frame = frame; 263 initTable(); 264 } 265 266 // Default engines frame table column widths, starts with Number column and ends with Edit 267 private final int[] _enginesTableColumnWidths = 268 {60, 60, 60, 65, 50, 65, 65, 35, 75, 190, 190, 190, 140, 190, 65, 90, 50, 50, 50, 50, 100, 130, 50, 100, 65, 269 70}; 270 271 void initTable() { 272 // Use XTableColumnModel so we can control which columns are visible 273 XTableColumnModel tcm = new XTableColumnModel(); 274 _table.setColumnModel(tcm); 275 _table.createDefaultColumnsFromModel(); 276 277 // Install the button handlers 278 ButtonRenderer buttonRenderer = new ButtonRenderer(); 279 tcm.getColumn(SET_COLUMN).setCellRenderer(buttonRenderer); 280 TableCellEditor buttonEditor = new ButtonEditor(new javax.swing.JButton()); 281 tcm.getColumn(SET_COLUMN).setCellEditor(buttonEditor); 282 tcm.getColumn(EDIT_COLUMN).setCellRenderer(buttonRenderer); 283 tcm.getColumn(EDIT_COLUMN).setCellEditor(buttonEditor); 284 285 // set column preferred widths 286 // load defaults, xml file data not found 287 for (int i = 0; i < tcm.getColumnCount(); i++) { 288 tcm.getColumn(i).setPreferredWidth(_enginesTableColumnWidths[i]); 289 } 290 _frame.loadTableDetails(_table); 291 292 // turn off columns 293 tcm.setColumnVisible(tcm.getColumnByModelIndex(BUILT_COLUMN), false); 294 tcm.setColumnVisible(tcm.getColumnByModelIndex(OWNER_COLUMN), false); 295 tcm.setColumnVisible(tcm.getColumnByModelIndex(VALUE_COLUMN), false); 296 tcm.setColumnVisible(tcm.getColumnByModelIndex(RFID_COLUMN), false); 297 tcm.setColumnVisible(tcm.getColumnByModelIndex(RFID_WHEN_LAST_SEEN_COLUMN), false); 298 tcm.setColumnVisible(tcm.getColumnByModelIndex(RFID_WHERE_LAST_SEEN_COLUMN), false); 299 tcm.setColumnVisible(tcm.getColumnByModelIndex(PREVIOUS_LOCATION_COLUMN), false); 300 tcm.setColumnVisible(tcm.getColumnByModelIndex(LAST_COLUMN), false); 301 tcm.setColumnVisible(tcm.getColumnByModelIndex(LAST_TRAIN_COLUMN), false); 302 tcm.setColumnVisible(tcm.getColumnByModelIndex(DCC_ADDRESS_COLUMN), false); 303 tcm.setColumnVisible(tcm.getColumnByModelIndex(COMMENT_COLUMN), false); 304 305 // turn on default 306 tcm.setColumnVisible(tcm.getColumnByModelIndex(MOVES_COLUMN), true); 307 } 308 309 @Override 310 public int getRowCount() { 311 return engineList.size(); 312 } 313 314 @Override 315 public int getColumnCount() { 316 return HIGHEST_COLUMN; 317 } 318 319 @Override 320 public String getColumnName(int col) { 321 switch (col) { 322 case SELECT_COLUMN: 323 return Bundle.getMessage("ButtonSelect"); 324 case NUM_COLUMN: 325 return Bundle.getMessage("Number"); 326 case ROAD_COLUMN: 327 return Bundle.getMessage("Road"); 328 case MODEL_COLUMN: 329 return Bundle.getMessage("Model"); 330 case HP_COLUMN: 331 return Bundle.getMessage("HP"); 332 case TYPE_COLUMN: 333 return Bundle.getMessage("Type"); 334 case LENGTH_COLUMN: 335 return Bundle.getMessage("Len"); 336 case WEIGHT_COLUMN: 337 return Bundle.getMessage("Weight"); 338 case CONSIST_COLUMN: 339 return Bundle.getMessage("Consist"); 340 case LOCATION_COLUMN: 341 return Bundle.getMessage("Location"); 342 case RFID_WHERE_LAST_SEEN_COLUMN: 343 return Bundle.getMessage("WhereLastSeen"); 344 case RFID_WHEN_LAST_SEEN_COLUMN: 345 return Bundle.getMessage("WhenLastSeen"); 346 case DESTINATION_COLUMN: 347 return Bundle.getMessage("Destination"); 348 case PREVIOUS_LOCATION_COLUMN: 349 return Bundle.getMessage("LastLocation"); 350 case TRAIN_COLUMN: 351 return Bundle.getMessage("Train"); 352 case LAST_TRAIN_COLUMN: 353 return Bundle.getMessage("LastTrain"); 354 case MOVES_COLUMN: 355 return Bundle.getMessage("Moves"); 356 case BUILT_COLUMN: 357 return Bundle.getMessage("Built"); 358 case OWNER_COLUMN: 359 return Bundle.getMessage("Owner"); 360 case VALUE_COLUMN: 361 return Setup.getValueLabel(); 362 case RFID_COLUMN: 363 return Setup.getRfidLabel(); 364 case LAST_COLUMN: 365 return Bundle.getMessage("LastMoved"); 366 case DCC_ADDRESS_COLUMN: 367 return Bundle.getMessage("DccAddress"); 368 case COMMENT_COLUMN: 369 return Bundle.getMessage("Comment"); 370 case SET_COLUMN: 371 return Bundle.getMessage("Set"); 372 case EDIT_COLUMN: 373 return Bundle.getMessage("ButtonEdit"); // titles above all columns 374 default: 375 return "unknown"; // NOI18N 376 } 377 } 378 379 @Override 380 public Class<?> getColumnClass(int col) { 381 switch (col) { 382 case SELECT_COLUMN: 383 return Boolean.class; 384 case SET_COLUMN: 385 case EDIT_COLUMN: 386 return JButton.class; 387 case LENGTH_COLUMN: 388 case MOVES_COLUMN: 389 return Integer.class; 390 default: 391 return String.class; 392 } 393 } 394 395 @Override 396 public boolean isCellEditable(int row, int col) { 397 switch (col) { 398 case SELECT_COLUMN: 399 case SET_COLUMN: 400 case EDIT_COLUMN: 401 case MOVES_COLUMN: 402 case VALUE_COLUMN: 403 case RFID_COLUMN: 404 return true; 405 default: 406 return false; 407 } 408 } 409 410 @Override 411 public Object getValueAt(int row, int col) { 412 if (row >= getRowCount()) { 413 return "ERROR row " + row; // NOI18N 414 } 415 Engine engine = engineList.get(row); 416 if (engine == null) { 417 return "ERROR engine unknown " + row; // NOI18N 418 } 419 switch (col) { 420 case SELECT_COLUMN: 421 return engine.isSelected(); 422 case NUM_COLUMN: 423 return engine.getNumber(); 424 case ROAD_COLUMN: 425 return engine.getRoadName(); 426 case LENGTH_COLUMN: 427 return engine.getLengthInteger(); 428 case MODEL_COLUMN: 429 return engine.getModel(); 430 case HP_COLUMN: 431 return engine.getHp(); 432 case TYPE_COLUMN: { 433 if (engine.isBunit()) { 434 return engine.getTypeName() + " " + Bundle.getMessage("(B)"); 435 } 436 return engine.getTypeName(); 437 } 438 case WEIGHT_COLUMN: 439 return engine.getWeightTons(); 440 case CONSIST_COLUMN: { 441 if (engine.isLead()) { 442 return engine.getConsistName() + "*"; 443 } 444 return engine.getConsistName(); 445 } 446 case LOCATION_COLUMN: { 447 String s = engine.getStatus(); 448 if (!engine.getLocationName().equals(Engine.NONE)) { 449 s = engine.getStatus() + engine.getLocationName() + " (" + engine.getTrackName() + ")"; 450 } 451 return s; 452 } 453 case RFID_WHERE_LAST_SEEN_COLUMN: { 454 return engine.getWhereLastSeenName() + 455 (engine.getTrackLastSeenName().equals(Engine.NONE) ? "" : " (" + engine.getTrackLastSeenName() + ")"); 456 } 457 case RFID_WHEN_LAST_SEEN_COLUMN: { 458 return engine.getWhenLastSeenDate(); 459 } 460 case DESTINATION_COLUMN: { 461 String s = ""; 462 if (!engine.getDestinationName().equals(Engine.NONE)) { 463 s = engine.getDestinationName() + " (" + engine.getDestinationTrackName() + ")"; 464 } 465 return s; 466 } 467 case PREVIOUS_LOCATION_COLUMN: { 468 String s = ""; 469 if (!engine.getLastLocationName().equals(Engine.NONE)) { 470 s = engine.getLastLocationName() + " (" + engine.getLastTrackName() + ")"; 471 } 472 return s; 473 } 474 case TRAIN_COLUMN: { 475 // if train was manually set by user add an asterisk 476 if (engine.getTrain() != null && engine.getRouteLocation() == null) { 477 return engine.getTrainName() + "*"; 478 } 479 return engine.getTrainName(); 480 } 481 case LAST_TRAIN_COLUMN: 482 return engine.getLastTrainName(); 483 case MOVES_COLUMN: 484 return engine.getMoves(); 485 case BUILT_COLUMN: 486 return engine.getBuilt(); 487 case OWNER_COLUMN: 488 return engine.getOwnerName(); 489 case VALUE_COLUMN: 490 return engine.getValue(); 491 case RFID_COLUMN: 492 return engine.getRfid(); 493 case LAST_COLUMN: 494 return engine.getSortDate(); 495 case DCC_ADDRESS_COLUMN: 496 return engine.getDccAddress(); 497 case COMMENT_COLUMN: 498 return engine.getComment(); 499 case SET_COLUMN: 500 return Bundle.getMessage("Set"); 501 case EDIT_COLUMN: 502 return Bundle.getMessage("ButtonEdit"); 503 default: 504 return "unknown " + col; // NOI18N 505 } 506 } 507 508 EngineEditFrame engineEditFrame = null; 509 EngineSetFrame engineSetFrame = null; 510 511 @Override 512 public void setValueAt(Object value, int row, int col) { 513 Engine engine = engineList.get(row); 514 switch (col) { 515 case SELECT_COLUMN: 516 engine.setSelected(((Boolean) value).booleanValue()); 517 break; 518 case MOVES_COLUMN: 519 try { 520 engine.setMoves(Integer.parseInt(value.toString())); 521 } catch (NumberFormatException e) { 522 log.error("move count must be a number"); 523 } 524 break; 525 case BUILT_COLUMN: 526 engine.setBuilt(value.toString()); 527 break; 528 case OWNER_COLUMN: 529 engine.setOwnerName(value.toString()); 530 break; 531 case VALUE_COLUMN: 532 engine.setValue(value.toString()); 533 break; 534 case RFID_COLUMN: 535 engine.setRfid(value.toString()); 536 break; 537 case SET_COLUMN: 538 log.debug("Set engine location"); 539 if (engineSetFrame != null) { 540 engineSetFrame.dispose(); 541 } 542 // use invokeLater so new window appears on top 543 SwingUtilities.invokeLater(() -> { 544 engineSetFrame = new EngineSetFrame(); 545 engineSetFrame.initComponents(); 546 engineSetFrame.load(engine); 547 }); 548 break; 549 case EDIT_COLUMN: 550 log.debug("Edit engine"); 551 if (engineEditFrame != null) { 552 engineEditFrame.dispose(); 553 } 554 // use invokeLater so new window appears on top 555 SwingUtilities.invokeLater(() -> { 556 engineEditFrame = new EngineEditFrame(); 557 engineEditFrame.initComponents(); 558 engineEditFrame.load(engine); 559 }); 560 break; 561 default: 562 break; 563 } 564 } 565 566 public void dispose() { 567 log.debug("dispose EngineTableModel"); 568 engineManager.removePropertyChangeListener(this); 569 removePropertyChangeEngines(); 570 if (engineSetFrame != null) { 571 engineSetFrame.dispose(); 572 } 573 if (engineEditFrame != null) { 574 engineEditFrame.dispose(); 575 } 576 } 577 578 private void addPropertyChangeEngines() { 579 for (RollingStock rs : engineManager.getList()) { 580 rs.addPropertyChangeListener(this); 581 } 582 } 583 584 private void removePropertyChangeEngines() { 585 for (RollingStock rs : engineManager.getList()) { 586 rs.removePropertyChangeListener(this); 587 } 588 } 589 590 @Override 591 public void propertyChange(PropertyChangeEvent e) { 592 if (Control.SHOW_PROPERTY) { 593 log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e 594 .getNewValue()); 595 } 596 if (e.getPropertyName().equals(EngineManager.LISTLENGTH_CHANGED_PROPERTY) || 597 e.getPropertyName().equals(ConsistManager.LISTLENGTH_CHANGED_PROPERTY)) { 598 updateList(); 599 fireTableDataChanged(); 600 } 601 // Engine length, type, and HP are based on model, so multiple changes 602 else if (e.getPropertyName().equals(Engine.LENGTH_CHANGED_PROPERTY) || 603 e.getPropertyName().equals(Engine.TYPE_CHANGED_PROPERTY) || 604 e.getPropertyName().equals(Engine.HP_CHANGED_PROPERTY)) { 605 fireTableDataChanged(); 606 } 607 // must be a engine change 608 else if (e.getSource().getClass().equals(Engine.class)) { 609 Engine engine = (Engine) e.getSource(); 610 int row = engineList.indexOf(engine); 611 if (Control.SHOW_PROPERTY) { 612 log.debug("Update engine table row: {}", row); 613 } 614 if (row >= 0) { 615 fireTableRowsUpdated(row, row); 616 // next is needed when only showing engines at a location or track 617 } else if (e.getPropertyName().equals(Engine.TRACK_CHANGED_PROPERTY)) { 618 updateList(); 619 fireTableDataChanged(); 620 } 621 } 622 } 623 624 private final static Logger log = LoggerFactory.getLogger(EnginesTableModel.class); 625}