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