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