001package jmri.jmrit.operations.trains.gui; 002 003import java.awt.*; 004import java.beans.PropertyChangeEvent; 005import java.beans.PropertyChangeListener; 006import java.util.Hashtable; 007import java.util.List; 008 009import javax.swing.*; 010import javax.swing.table.DefaultTableCellRenderer; 011import javax.swing.table.TableCellEditor; 012 013import jmri.InstanceManager; 014import jmri.jmrit.beantable.EnablingCheckboxRenderer; 015import jmri.jmrit.operations.OperationsTableModel; 016import jmri.jmrit.operations.routes.Route; 017import jmri.jmrit.operations.setup.Control; 018import jmri.jmrit.operations.setup.Setup; 019import jmri.jmrit.operations.trains.Train; 020import jmri.jmrit.operations.trains.TrainManager; 021import jmri.util.swing.JmriJOptionPane; 022import jmri.util.swing.XTableColumnModel; 023import jmri.util.table.ButtonEditor; 024import jmri.util.table.ButtonRenderer; 025 026/** 027 * Table Model for edit of trains used by operations 028 * 029 * @author Daniel Boudreau Copyright (C) 2008, 2012, 2026 030 */ 031public class TrainsTableModel extends OperationsTableModel implements PropertyChangeListener { 032 033 TrainManager trainManager = InstanceManager.getDefault(TrainManager.class); // There is only one manager 034 volatile List<Train> sysList = trainManager.getTrainsByTimeList(); 035 TrainsTableFrame _frame = null; 036 037 // Defines the columns 038 private static final int ID_COLUMN = 0; 039 private static final int TIME_COLUMN = ID_COLUMN + 1; 040 private static final int BUILDBOX_COLUMN = TIME_COLUMN + 1; 041 private static final int BUILD_COLUMN = BUILDBOX_COLUMN + 1; 042 private static final int NAME_COLUMN = BUILD_COLUMN + 1; 043 private static final int DESCRIPTION_COLUMN = NAME_COLUMN + 1; 044 private static final int BUILT_COLUMN = DESCRIPTION_COLUMN + 1; 045 private static final int CAR_ROAD_COLUMN = BUILT_COLUMN + 1; 046 private static final int CABOOSE_ROAD_COLUMN = CAR_ROAD_COLUMN + 1; 047 private static final int LOCO_ROAD_COLUMN = CABOOSE_ROAD_COLUMN + 1; 048 private static final int LOAD_COLUMN = LOCO_ROAD_COLUMN + 1; 049 private static final int OWNER_COLUMN = LOAD_COLUMN + 1; 050 private static final int ROUTE_COLUMN = OWNER_COLUMN + 1; 051 private static final int DEPARTS_COLUMN = ROUTE_COLUMN + 1; 052 private static final int TERMINATES_COLUMN = DEPARTS_COLUMN + 1; 053 private static final int CURRENT_COLUMN = TERMINATES_COLUMN + 1; 054 private static final int CARS_COLUMN = CURRENT_COLUMN + 1; 055 private static final int STATUS_COLUMN = CARS_COLUMN + 1; 056 private static final int ACTION_COLUMN = STATUS_COLUMN + 1; 057 private static final int EDIT_COLUMN = ACTION_COLUMN + 1; 058 059 private static final int HIGHESTCOLUMN = EDIT_COLUMN + 1; 060 061 public TrainsTableModel() { 062 super(); 063 trainManager.addPropertyChangeListener(this); 064 Setup.getDefault().addPropertyChangeListener(this); 065 updateList(); 066 } 067 068 public final int SORTBYTIME = 2; 069 public final int SORTBYID = 7; 070 071 private int _sort = SORTBYTIME; 072 073 public void setSort(int sort) { 074 _sort = sort; 075 updateList(); 076 updateColumnVisible(); 077 } 078 079 private boolean _showAll = true; 080 081 public void setShowAll(boolean showAll) { 082 _showAll = showAll; 083 updateList(); 084 fireTableDataChanged(); 085 } 086 087 public boolean isShowAll() { 088 return _showAll; 089 } 090 091 private void updateList() { 092 // first, remove listeners from the individual objects 093 removePropertyChangeTrains(); 094 095 List<Train> tempList; 096 if (_sort == SORTBYID) { 097 tempList = trainManager.getTrainsByIdList(); 098 } else { 099 tempList = trainManager.getTrainsByTimeList(); 100 } 101 102 if (!isShowAll()) { 103 // filter out trains not checked 104 for (int i = tempList.size() - 1; i >= 0; i--) { 105 if (!tempList.get(i).isBuildEnabled()) { 106 tempList.remove(i); 107 } 108 } 109 } 110 sysList = tempList; 111 112 // and add listeners back in 113 addPropertyChangeTrains(); 114 } 115 116 private Train getTrainByRow(int row) { 117 return sysList.get(row); 118 } 119 120 void initTable(JTable table, TrainsTableFrame frame) { 121 _table = table; 122 _frame = frame; 123 // allow row color to be controlled 124 table.setDefaultRenderer(Object.class, new MyTableCellRenderer()); 125 table.setDefaultRenderer(Integer.class, new MyTableCellRenderer()); 126 initTable(); 127 } 128 129 // Train frame table column widths, starts with id column and ends with edit 130 private final int[] _tableColumnWidths = 131 {50, 50, 50, 72, 100, 140, 50, 50, 50, 50, 50, 50, 120, 120, 120, 120, 50, 120, 90, 132 70}; 133 134 void initTable() { 135 // Use XTableColumnModel so we can control which columns are visible 136 XTableColumnModel tcm = new XTableColumnModel(); 137 _table.setColumnModel(tcm); 138 _table.createDefaultColumnsFromModel(); 139 140 // Install the button handlers 141 ButtonRenderer buttonRenderer = new ButtonRenderer(); 142 ButtonRenderer buttonRenderer2 = new ButtonRenderer(); // for tool tips 143 TableCellEditor buttonEditor = new ButtonEditor(new javax.swing.JButton()); 144 tcm.getColumn(EDIT_COLUMN).setCellRenderer(buttonRenderer); 145 tcm.getColumn(EDIT_COLUMN).setCellEditor(buttonEditor); 146 tcm.getColumn(ACTION_COLUMN).setCellRenderer(buttonRenderer); 147 tcm.getColumn(ACTION_COLUMN).setCellEditor(buttonEditor); 148 tcm.getColumn(BUILD_COLUMN).setCellRenderer(buttonRenderer2); 149 tcm.getColumn(BUILD_COLUMN).setCellEditor(buttonEditor); 150 _table.setDefaultRenderer(Boolean.class, new EnablingCheckboxRenderer()); 151 152 // for tool tips 153 DefaultTableCellRenderer defaultRenderer = new DefaultTableCellRenderer(); 154 tcm.getColumn(TIME_COLUMN).setCellRenderer(defaultRenderer); 155 156 // set column preferred widths 157 for (int i = 0; i < tcm.getColumnCount(); i++) { 158 tcm.getColumn(i).setPreferredWidth(_tableColumnWidths[i]); 159 } 160 _frame.loadTableDetails(_table); 161 162 // turn off column 163 updateColumnVisible(); 164 } 165 166 private void updateColumnVisible() { 167 XTableColumnModel tcm = (XTableColumnModel) _table.getColumnModel(); 168 tcm.setColumnVisible(tcm.getColumnByModelIndex(ID_COLUMN), _sort == SORTBYID); 169 tcm.setColumnVisible(tcm.getColumnByModelIndex(TIME_COLUMN), _sort == SORTBYTIME); 170 tcm.setColumnVisible(tcm.getColumnByModelIndex(BUILT_COLUMN), trainManager.isBuiltRestricted()); 171 tcm.setColumnVisible(tcm.getColumnByModelIndex(CAR_ROAD_COLUMN), trainManager.isCarRoadRestricted()); 172 tcm.setColumnVisible(tcm.getColumnByModelIndex(CABOOSE_ROAD_COLUMN), trainManager.isCabooseRoadRestricted()); 173 tcm.setColumnVisible(tcm.getColumnByModelIndex(LOCO_ROAD_COLUMN), trainManager.isLocoRoadRestricted()); 174 tcm.setColumnVisible(tcm.getColumnByModelIndex(LOAD_COLUMN), trainManager.isLoadRestricted()); 175 tcm.setColumnVisible(tcm.getColumnByModelIndex(OWNER_COLUMN), trainManager.isOwnerRestricted()); 176 } 177 178 @Override 179 public int getRowCount() { 180 return sysList.size(); 181 } 182 183 @Override 184 public int getColumnCount() { 185 return HIGHESTCOLUMN; 186 } 187 188 public static final String IDCOLUMNNAME = Bundle.getMessage("Id"); 189 public static final String TIMECOLUMNNAME = Bundle.getMessage("Time"); 190 public static final String BUILDBOXCOLUMNNAME = Bundle.getMessage("Build"); 191 public static final String BUILDCOLUMNNAME = Bundle.getMessage("Function"); 192 public static final String NAMECOLUMNNAME = Bundle.getMessage("Name"); 193 public static final String DESCRIPTIONCOLUMNNAME = Bundle.getMessage("Description"); 194 public static final String ROUTECOLUMNNAME = Bundle.getMessage("Route"); 195 public static final String DEPARTSCOLUMNNAME = Bundle.getMessage("Departs"); 196 public static final String CURRENTCOLUMNNAME = Bundle.getMessage("Current"); 197 public static final String TERMINATESCOLUMNNAME = Bundle.getMessage("Terminates"); 198 public static final String STATUSCOLUMNNAME = Bundle.getMessage("Status"); 199 public static final String ACTIONCOLUMNNAME = Bundle.getMessage("Action"); 200 public static final String EDITCOLUMNNAME = Bundle.getMessage("ButtonEdit"); 201 202 @Override 203 public String getColumnName(int col) { 204 switch (col) { 205 case ID_COLUMN: 206 return IDCOLUMNNAME; 207 case TIME_COLUMN: 208 return TIMECOLUMNNAME; 209 case BUILDBOX_COLUMN: 210 return BUILDBOXCOLUMNNAME; 211 case BUILD_COLUMN: 212 return BUILDCOLUMNNAME; 213 case NAME_COLUMN: 214 return NAMECOLUMNNAME; 215 case DESCRIPTION_COLUMN: 216 return DESCRIPTIONCOLUMNNAME; 217 case BUILT_COLUMN: 218 return Bundle.getMessage("Built"); 219 case CAR_ROAD_COLUMN: 220 return Bundle.getMessage("RoadsCar"); 221 case CABOOSE_ROAD_COLUMN: 222 return Bundle.getMessage("RoadsCaboose"); 223 case LOCO_ROAD_COLUMN: 224 return Bundle.getMessage("RoadsLoco"); 225 case LOAD_COLUMN: 226 return Bundle.getMessage("Load"); 227 case OWNER_COLUMN: 228 return Bundle.getMessage("Owner"); 229 case ROUTE_COLUMN: 230 return ROUTECOLUMNNAME; 231 case DEPARTS_COLUMN: 232 return DEPARTSCOLUMNNAME; 233 case CURRENT_COLUMN: 234 return CURRENTCOLUMNNAME; 235 case TERMINATES_COLUMN: 236 return TERMINATESCOLUMNNAME; 237 case CARS_COLUMN: 238 return Bundle.getMessage("Cars"); 239 case STATUS_COLUMN: 240 return STATUSCOLUMNNAME; 241 case ACTION_COLUMN: 242 return ACTIONCOLUMNNAME; 243 case EDIT_COLUMN: 244 return EDITCOLUMNNAME; 245 default: 246 return "unknown"; // NOI18N 247 } 248 } 249 250 @Override 251 public Class<?> getColumnClass(int col) { 252 switch (col) { 253 case BUILDBOX_COLUMN: 254 return Boolean.class; 255 case ID_COLUMN: 256 case CARS_COLUMN: 257 return Integer.class; 258 case TIME_COLUMN: 259 case NAME_COLUMN: 260 case DESCRIPTION_COLUMN: 261 case BUILT_COLUMN: 262 case CAR_ROAD_COLUMN: 263 case CABOOSE_ROAD_COLUMN: 264 case LOCO_ROAD_COLUMN: 265 case LOAD_COLUMN: 266 case OWNER_COLUMN: 267 case ROUTE_COLUMN: 268 case DEPARTS_COLUMN: 269 case CURRENT_COLUMN: 270 case TERMINATES_COLUMN: 271 case STATUS_COLUMN: 272 return String.class; 273 case BUILD_COLUMN: 274 case ACTION_COLUMN: 275 case EDIT_COLUMN: 276 return JButton.class; 277 default: 278 return null; 279 } 280 } 281 282 @Override 283 public boolean isCellEditable(int row, int col) { 284 switch (col) { 285 case BUILD_COLUMN: 286 case BUILDBOX_COLUMN: 287 case ACTION_COLUMN: 288 case EDIT_COLUMN: 289 return true; 290 default: 291 return false; 292 } 293 } 294 295 @Override 296 public Object getValueAt(int row, int col) { 297 if (row >= getRowCount()) { 298 return "ERROR row " + row; // NOI18N 299 } 300 Train train = getTrainByRow(row); 301 if (train == null) { 302 return "ERROR train unknown " + row; // NOI18N 303 } 304 switch (col) { 305 case ID_COLUMN: 306 return Integer.parseInt(train.getId()); 307 case TIME_COLUMN: 308 setToolTip(Bundle.getMessage("TimeTip"), col); 309 return train.getDepartureTime(); 310 case NAME_COLUMN: 311 return train.getIconName(); 312 case DESCRIPTION_COLUMN: 313 return train.getDescription(); 314 case BUILDBOX_COLUMN: 315 return Boolean.valueOf(train.isBuildEnabled()); 316 case BUILT_COLUMN: 317 return getBuiltString(train); 318 case CAR_ROAD_COLUMN: 319 return getModifiedString(train.getCarRoadNames().length, 320 train.getCarRoadOption().equals(Train.ALL_ROADS), 321 train.getCarRoadOption().equals(Train.INCLUDE_ROADS)); 322 case CABOOSE_ROAD_COLUMN: 323 return getModifiedString(train.getCabooseRoadNames().length, 324 train.getCabooseRoadOption().equals(Train.ALL_ROADS), 325 train.getCabooseRoadOption().equals(Train.INCLUDE_ROADS)); 326 case LOCO_ROAD_COLUMN: 327 return getModifiedString(train.getLocoRoadNames().length, 328 train.getLocoRoadOption().equals(Train.ALL_ROADS), 329 train.getLocoRoadOption().equals(Train.INCLUDE_ROADS)); 330 case LOAD_COLUMN: 331 return getModifiedString(train.getLoadNames().length, train.getLoadOption().equals(Train.ALL_LOADS), 332 train.getLoadOption().equals(Train.INCLUDE_LOADS)); 333 case OWNER_COLUMN: 334 return getModifiedString(train.getOwnerNames().length, train.getOwnerOption().equals(Train.ALL_OWNERS), 335 train.getOwnerOption().equals(Train.INCLUDE_OWNERS)); 336 case ROUTE_COLUMN: 337 return train.getTrainRouteName(); 338 case DEPARTS_COLUMN: { 339 if (train.getDepartureTrack() == null) { 340 return train.getTrainDepartsName(); 341 } else { 342 return train.getTrainDepartsName() + " (" + train.getDepartureTrack().getName() + ")"; 343 } 344 } 345 case CURRENT_COLUMN: 346 return train.getCurrentLocationName(); 347 case TERMINATES_COLUMN: { 348 if (train.getTerminationTrack() == null) { 349 return train.getTrainTerminatesName(); 350 } else { 351 return train.getTrainTerminatesName() + " (" + train.getTerminationTrack().getName() + ")"; 352 } 353 } 354 case CARS_COLUMN: 355 return train.getNumberCarsInTrain(); 356 case STATUS_COLUMN: 357 return train.getStatus(); 358 case BUILD_COLUMN: { 359 if (train.isBuilt()) { 360 if (Setup.isGenerateCsvManifestEnabled() && trainManager.isOpenFileEnabled()) { 361 setToolTip(Bundle.getMessage("OpenTrainTip", train.getName()), col); 362 return Bundle.getMessage("OpenFile"); 363 } 364 if (Setup.isGenerateCsvManifestEnabled() && trainManager.isRunFileEnabled()) { 365 setToolTip(Bundle.getMessage("RunTrainTip", train.getName()), col); 366 return Bundle.getMessage("RunFile"); 367 } 368 setToolTip(Bundle.getMessage("PrintTrainTip"), col); 369 if (trainManager.isPrintPreviewEnabled()) { 370 return Bundle.getMessage("Preview"); 371 } else if (train.isPrinted()) { 372 return Bundle.getMessage("Printed"); 373 } else { 374 return Bundle.getMessage("Print"); 375 } 376 } 377 setToolTip(Bundle.getMessage("BuildTrainTip", train.getName()), col); 378 return Bundle.getMessage("Build"); 379 } 380 case ACTION_COLUMN: { 381 if (train.isBuildFailed()) { 382 return Bundle.getMessage("Report"); 383 } 384 if (train.getCurrentRouteLocation() == train.getTrainTerminatesRouteLocation() && 385 trainManager.getTrainsFrameTrainAction().equals(TrainsTableFrame.MOVE)) { 386 return Bundle.getMessage("Terminate"); 387 } 388 return trainManager.getTrainsFrameTrainAction(); 389 } 390 case EDIT_COLUMN: 391 return Bundle.getMessage("ButtonEdit"); 392 default: 393 return "unknown " + col; // NOI18N 394 } 395 } 396 397 private String getBuiltString(Train train) { 398 if (!train.getBuiltStartYear().equals(Train.NONE) && train.getBuiltEndYear().equals(Train.NONE)) { 399 return "A " + train.getBuiltStartYear(); 400 } 401 if (train.getBuiltStartYear().equals(Train.NONE) && !train.getBuiltEndYear().equals(Train.NONE)) { 402 return "B " + train.getBuiltEndYear(); 403 } 404 if (!train.getBuiltStartYear().equals(Train.NONE) && !train.getBuiltEndYear().equals(Train.NONE)) { 405 return "R " + train.getBuiltStartYear() + ":" + train.getBuiltEndYear(); 406 } 407 return ""; 408 } 409 410 private String getModifiedString(int number, boolean all, boolean accept) { 411 if (all) { 412 return ""; 413 } 414 if (accept) { 415 return "A " + Integer.toString(number); // NOI18N 416 } 417 return "E " + Integer.toString(number); // NOI18N 418 } 419 420 @Override 421 public void setValueAt(Object value, int row, int col) { 422 switch (col) { 423 case EDIT_COLUMN: 424 editTrain(row); 425 break; 426 case BUILD_COLUMN: 427 buildTrain(row); 428 break; 429 case ACTION_COLUMN: 430 actionTrain(row); 431 break; 432 case BUILDBOX_COLUMN: { 433 Train train = getTrainByRow(row); 434 train.setBuildEnabled(((Boolean) value).booleanValue()); 435 break; 436 } 437 default: 438 break; 439 } 440 } 441 442 public Color getRowColor(int row) { 443 Train train = getTrainByRow(row); 444 return train.getTableRowColor(); 445 } 446 447 TrainEditFrame tef = null; 448 449 private void editTrain(int row) { 450 if (tef != null) { 451 tef.dispose(); 452 } 453 // use invokeLater so new window appears on top 454 SwingUtilities.invokeLater(() -> { 455 Train train = getTrainByRow(row); 456 log.debug("Edit train ({})", train.getName()); 457 tef = new TrainEditFrame(train); 458 }); 459 } 460 461 Thread build; 462 463 private void buildTrain(int row) { 464 final Train train = getTrainByRow(row); 465 if (!train.isBuilt()) { 466 // only one train build at a time 467 if (build != null && build.isAlive()) { 468 return; 469 } 470 // use a thread to allow table updates during build 471 build = jmri.util.ThreadingUtil.newThread(new Runnable() { 472 @Override 473 public void run() { 474 train.build(); 475 } 476 }); 477 build.setName("Build Train (" + train.getName() + ")"); // NOI18N 478 build.start(); 479 // print build report, print manifest, run or open file 480 } else { 481 if (trainManager.isBuildReportEnabled()) { 482 train.printBuildReport(); 483 } 484 if (Setup.isGenerateCsvManifestEnabled() && trainManager.isOpenFileEnabled()) { 485 train.openFile(); 486 } else if (Setup.isGenerateCsvManifestEnabled() && trainManager.isRunFileEnabled()) { 487 train.runFile(); 488 } else { 489 if (!train.printManifestIfBuilt()) { 490 log.debug("Manifest file for train ({}) not found", train.getName()); 491 int result = JmriJOptionPane.showConfirmDialog(null, 492 Bundle.getMessage("TrainManifestFileMissing", 493 train.getName()), 494 Bundle.getMessage("TrainManifestFileError"), JmriJOptionPane.YES_NO_OPTION); 495 if (result == JmriJOptionPane.YES_OPTION) { 496 train.setModified(true); 497 if (!train.printManifestIfBuilt()) { 498 log.error("Unable to create manifest for train ({})", train.getName()); 499 } 500 } 501 } 502 } 503 } 504 } 505 506 // one of five buttons: Report, Move, Reset, Conductor or Terminate 507 private void actionTrain(int row) { 508 // no actions while a train is being built 509 if (build != null && build.isAlive()) { 510 return; 511 } 512 Train train = getTrainByRow(row); 513 // move button becomes report if failure 514 if (train.isBuildFailed()) { 515 train.printBuildReport(); 516 } else if (trainManager.getTrainsFrameTrainAction().equals(TrainsTableFrame.RESET)) { 517 log.debug("Reset train ({})", train.getName()); 518 // check to see if departure track was reused 519 if (train.checkDepartureTrack()) { 520 log.debug("Train is departing staging that already has inbound cars"); 521 JmriJOptionPane.showMessageDialog(null, 522 Bundle.getMessage("StagingTrackUsed", 523 train.getDepartureTrack().getName()), 524 Bundle.getMessage("CanNotResetTrain"), JmriJOptionPane.INFORMATION_MESSAGE); 525 } else if (!train.reset()) { 526 JmriJOptionPane.showMessageDialog(null, 527 Bundle.getMessage("TrainIsInRoute", 528 train.getTrainTerminatesName()), 529 Bundle.getMessage("CanNotResetTrain"), JmriJOptionPane.ERROR_MESSAGE); 530 } 531 } else if (!train.isBuilt()) { 532 int reply = JmriJOptionPane.showOptionDialog(null, 533 Bundle.getMessage("TrainNeedsBuild", train.getName()), 534 Bundle.getMessage("CanNotPerformAction"), JmriJOptionPane.NO_OPTION, 535 JmriJOptionPane.INFORMATION_MESSAGE, null, 536 new Object[]{Bundle.getMessage("ButtonOK"), Bundle.getMessage("Build")}, null); 537 if (reply == 1) { 538 train.build(); 539 } 540 } else if (train.isBuilt() && trainManager.getTrainsFrameTrainAction().equals(TrainsTableFrame.MOVE)) { 541 log.debug("Move train ({})", train.getName()); 542 train.move(); 543 } else if (train.isBuilt() && trainManager.getTrainsFrameTrainAction().equals(TrainsTableFrame.TERMINATE)) { 544 log.debug("Terminate train ({})", train.getName()); 545 int status = JmriJOptionPane.showConfirmDialog(null, 546 Bundle.getMessage("TerminateTrain", 547 train.getName(), train.getDescription()), 548 Bundle.getMessage("DoYouWantToTermiate", train.getName()), 549 JmriJOptionPane.YES_NO_OPTION); 550 if (status == JmriJOptionPane.YES_OPTION) { 551 train.terminate(); 552 } 553 } else if (train.isBuilt() && trainManager.getTrainsFrameTrainAction().equals(TrainsTableFrame.CONDUCTOR)) { 554 log.debug("Enable conductor for train ({})", train.getName()); 555 launchConductor(train); 556 } 557 } 558 559 private static Hashtable<String, TrainConductorFrame> _trainConductorHashTable = new Hashtable<>(); 560 561 private void launchConductor(Train train) { 562 // use invokeLater so new window appears on top 563 SwingUtilities.invokeLater(() -> { 564 TrainConductorFrame f = _trainConductorHashTable.get(train.getId()); 565 // create a copy train frame 566 if (f == null || !f.isVisible()) { 567 f = new TrainConductorFrame(train); 568 _trainConductorHashTable.put(train.getId(), f); 569 } else { 570 f.setExtendedState(Frame.NORMAL); 571 } 572 f.setVisible(true); // this also brings the frame into focus 573 }); 574 } 575 576 @Override 577 public void propertyChange(PropertyChangeEvent e) { 578 if (Control.SHOW_PROPERTY) { 579 log.debug("Property change {} old: {} new: {}", e.getPropertyName(), e.getOldValue(), e.getNewValue()); // NOI18N 580 } 581 if (e.getPropertyName().equals(Train.BUILT_YEAR_CHANGED_PROPERTY) || 582 e.getPropertyName().equals(Train.ROADS_CHANGED_PROPERTY) || 583 e.getPropertyName().equals(Train.LOADS_CHANGED_PROPERTY) || 584 e.getPropertyName().equals(Train.OWNERS_CHANGED_PROPERTY)) { 585 updateColumnVisible(); 586 } 587 if (e.getPropertyName().equals(TrainManager.LISTLENGTH_CHANGED_PROPERTY) || 588 e.getPropertyName().equals(TrainManager.PRINTPREVIEW_CHANGED_PROPERTY) || 589 e.getPropertyName().equals(TrainManager.OPEN_FILE_CHANGED_PROPERTY) || 590 e.getPropertyName().equals(TrainManager.RUN_FILE_CHANGED_PROPERTY) || 591 e.getPropertyName().equals(Setup.MANIFEST_CSV_PROPERTY_CHANGE) || 592 e.getPropertyName().equals(TrainManager.TRAIN_ACTION_CHANGED_PROPERTY) || 593 e.getPropertyName().equals(Train.DEPARTURETIME_CHANGED_PROPERTY) || 594 (e.getPropertyName().equals(Train.BUILD_CHANGED_PROPERTY) && !isShowAll())) { 595 SwingUtilities.invokeLater(() -> { 596 updateList(); 597 fireTableDataChanged(); 598 }); 599 } else if (e.getSource().getClass().equals(Train.class) && 600 !e.getPropertyName().equals(Route.ROUTE_STATUS_CHANGED_PROPERTY)) { 601 Train train = ((Train) e.getSource()); 602 SwingUtilities.invokeLater(() -> { 603 int row = sysList.indexOf(train); 604 if (row >= 0 && _table != null) { 605 fireTableRowsUpdated(row, row); 606 int viewRow = _table.convertRowIndexToView(row); 607 log.debug("Scroll table to row: {}, train: {}, property: {}", viewRow, train.getName(), 608 e.getPropertyName()); 609 _table.scrollRectToVisible(_table.getCellRect(viewRow, 0, true)); 610 } 611 }); 612 } 613 } 614 615 private void removePropertyChangeTrains() { 616 for (Train train : trainManager.getList()) { 617 train.removePropertyChangeListener(this); 618 } 619 } 620 621 private void addPropertyChangeTrains() { 622 for (Train train : trainManager.getList()) { 623 train.addPropertyChangeListener(this); 624 } 625 } 626 627 public void dispose() { 628 if (tef != null) { 629 tef.dispose(); 630 } 631 trainManager.removePropertyChangeListener(this); 632 Setup.getDefault().removePropertyChangeListener(this); 633 removePropertyChangeTrains(); 634 } 635 636 class MyTableCellRenderer extends DefaultTableCellRenderer { 637 638 @Override 639 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, 640 int row, int column) { 641 Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); 642 if (!isSelected) { 643 int modelRow = table.convertRowIndexToModel(row); 644 // log.debug("View row: {} Column: {} Model row: {}", row, column, modelRow); 645 Color background = getRowColor(modelRow); 646 component.setBackground(background); 647 component.setForeground(getForegroundColor(background)); 648 } 649 return component; 650 } 651 652 Color[] darkColors = {Color.BLACK, Color.BLUE, Color.GRAY, Color.RED, Color.MAGENTA}; 653 654 /** 655 * Dark colors need white lettering 656 */ 657 private Color getForegroundColor(Color background) { 658 if (background == null) { 659 return null; 660 } 661 for (Color color : darkColors) { 662 if (background == color) { 663 return Color.WHITE; 664 } 665 } 666 return Color.BLACK; // all others get black lettering 667 } 668 } 669 670 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TrainsTableModel.class); 671}