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 Engine engine = engineList.get(row); 398 if (engine.isClone()) { 399 return false; 400 } 401 switch (col) { 402 case SELECT_COLUMN: 403 case SET_COLUMN: 404 case EDIT_COLUMN: 405 case MOVES_COLUMN: 406 case VALUE_COLUMN: 407 case RFID_COLUMN: 408 return true; 409 default: 410 return false; 411 } 412 } 413 414 @Override 415 public Object getValueAt(int row, int col) { 416 if (row >= getRowCount()) { 417 return "ERROR row " + row; // NOI18N 418 } 419 Engine engine = engineList.get(row); 420 if (engine == null) { 421 return "ERROR engine unknown " + row; // NOI18N 422 } 423 switch (col) { 424 case SELECT_COLUMN: 425 return engine.isSelected(); 426 case NUM_COLUMN: 427 return engine.getNumber(); 428 case ROAD_COLUMN: 429 return engine.getRoadName(); 430 case LENGTH_COLUMN: 431 return engine.getLengthInteger(); 432 case MODEL_COLUMN: 433 return engine.getModel(); 434 case HP_COLUMN: 435 return engine.getHp(); 436 case TYPE_COLUMN: { 437 if (engine.isBunit()) { 438 return engine.getTypeName() + " " + Bundle.getMessage("(B)"); 439 } 440 return engine.getTypeName(); 441 } 442 case WEIGHT_COLUMN: 443 return engine.getWeightTons(); 444 case CONSIST_COLUMN: { 445 if (engine.isLead()) { 446 return engine.getConsistName() + "*"; 447 } 448 return engine.getConsistName(); 449 } 450 case LOCATION_COLUMN: { 451 String s = engine.getStatus(); 452 if (!engine.getLocationName().equals(Engine.NONE)) { 453 s = engine.getStatus() + engine.getLocationName() + " (" + engine.getTrackName() + ")"; 454 } 455 return s; 456 } 457 case RFID_WHERE_LAST_SEEN_COLUMN: { 458 return engine.getWhereLastSeenName() + 459 (engine.getTrackLastSeenName().equals(Engine.NONE) ? "" : " (" + engine.getTrackLastSeenName() + ")"); 460 } 461 case RFID_WHEN_LAST_SEEN_COLUMN: { 462 return engine.getWhenLastSeenDate(); 463 } 464 case DESTINATION_COLUMN: { 465 String s = ""; 466 if (!engine.getDestinationName().equals(Engine.NONE)) { 467 s = engine.getDestinationName() + " (" + engine.getDestinationTrackName() + ")"; 468 } 469 return s; 470 } 471 case PREVIOUS_LOCATION_COLUMN: { 472 String s = ""; 473 if (!engine.getLastLocationName().equals(Engine.NONE)) { 474 s = engine.getLastLocationName() + " (" + engine.getLastTrackName() + ")"; 475 } 476 return s; 477 } 478 case TRAIN_COLUMN: { 479 // if train was manually set by user add an asterisk 480 if (engine.getTrain() != null && engine.getRouteLocation() == null) { 481 return engine.getTrainName() + "*"; 482 } 483 return engine.getTrainName(); 484 } 485 case LAST_TRAIN_COLUMN: 486 return engine.getLastTrainName(); 487 case MOVES_COLUMN: 488 return engine.getMoves(); 489 case BUILT_COLUMN: 490 return engine.getBuilt(); 491 case OWNER_COLUMN: 492 return engine.getOwnerName(); 493 case VALUE_COLUMN: 494 return engine.getValue(); 495 case RFID_COLUMN: 496 return engine.getRfid(); 497 case LAST_COLUMN: 498 return engine.getSortDate(); 499 case DCC_ADDRESS_COLUMN: 500 return engine.getDccAddress(); 501 case COMMENT_COLUMN: 502 return engine.getComment(); 503 case SET_COLUMN: 504 return Bundle.getMessage("Set"); 505 case EDIT_COLUMN: 506 return Bundle.getMessage("ButtonEdit"); 507 default: 508 return "unknown " + col; // NOI18N 509 } 510 } 511 512 EngineEditFrame engineEditFrame = null; 513 EngineSetFrame engineSetFrame = null; 514 515 @Override 516 public void setValueAt(Object value, int row, int col) { 517 Engine engine = engineList.get(row); 518 switch (col) { 519 case SELECT_COLUMN: 520 engine.setSelected(((Boolean) value).booleanValue()); 521 break; 522 case MOVES_COLUMN: 523 try { 524 engine.setMoves(Integer.parseInt(value.toString())); 525 } catch (NumberFormatException e) { 526 log.error("move count must be a number"); 527 } 528 break; 529 case BUILT_COLUMN: 530 engine.setBuilt(value.toString()); 531 break; 532 case OWNER_COLUMN: 533 engine.setOwnerName(value.toString()); 534 break; 535 case VALUE_COLUMN: 536 engine.setValue(value.toString()); 537 break; 538 case RFID_COLUMN: 539 engine.setRfid(value.toString()); 540 break; 541 case SET_COLUMN: 542 log.debug("Set engine location"); 543 if (engineSetFrame != null) { 544 engineSetFrame.dispose(); 545 } 546 // use invokeLater so new window appears on top 547 SwingUtilities.invokeLater(() -> { 548 engineSetFrame = new EngineSetFrame(); 549 engineSetFrame.initComponents(); 550 engineSetFrame.load(engine); 551 }); 552 break; 553 case EDIT_COLUMN: 554 log.debug("Edit engine"); 555 if (engineEditFrame != null) { 556 engineEditFrame.dispose(); 557 } 558 // use invokeLater so new window appears on top 559 SwingUtilities.invokeLater(() -> { 560 engineEditFrame = new EngineEditFrame(); 561 engineEditFrame.initComponents(); 562 engineEditFrame.load(engine); 563 }); 564 break; 565 default: 566 break; 567 } 568 } 569 570 public void dispose() { 571 log.debug("dispose EngineTableModel"); 572 engineManager.removePropertyChangeListener(this); 573 removePropertyChangeEngines(); 574 if (engineSetFrame != null) { 575 engineSetFrame.dispose(); 576 } 577 if (engineEditFrame != null) { 578 engineEditFrame.dispose(); 579 } 580 } 581 582 private void addPropertyChangeEngines() { 583 for (RollingStock rs : engineManager.getList()) { 584 rs.addPropertyChangeListener(this); 585 } 586 } 587 588 private void removePropertyChangeEngines() { 589 for (RollingStock rs : engineManager.getList()) { 590 rs.removePropertyChangeListener(this); 591 } 592 } 593 594 @Override 595 public void propertyChange(PropertyChangeEvent e) { 596 if (Control.SHOW_PROPERTY) { 597 log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e 598 .getNewValue()); 599 } 600 if (e.getPropertyName().equals(EngineManager.LISTLENGTH_CHANGED_PROPERTY) || 601 e.getPropertyName().equals(ConsistManager.LISTLENGTH_CHANGED_PROPERTY)) { 602 updateList(); 603 fireTableDataChanged(); 604 } 605 // Engine length, type, and HP are based on model, so multiple changes 606 else if (e.getPropertyName().equals(Engine.LENGTH_CHANGED_PROPERTY) || 607 e.getPropertyName().equals(Engine.TYPE_CHANGED_PROPERTY) || 608 e.getPropertyName().equals(Engine.HP_CHANGED_PROPERTY)) { 609 fireTableDataChanged(); 610 } 611 // must be a engine change 612 else if (e.getSource().getClass().equals(Engine.class)) { 613 Engine engine = (Engine) e.getSource(); 614 int row = engineList.indexOf(engine); 615 if (Control.SHOW_PROPERTY) { 616 log.debug("Update engine table row: {}", row); 617 } 618 if (row >= 0) { 619 fireTableRowsUpdated(row, row); 620 // next is needed when only showing engines at a location or track 621 } else if (e.getPropertyName().equals(Engine.TRACK_CHANGED_PROPERTY)) { 622 updateList(); 623 fireTableDataChanged(); 624 } 625 } 626 } 627 628 private final static Logger log = LoggerFactory.getLogger(EnginesTableModel.class); 629}