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