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