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