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