001package jmri.jmrit.operations.trains.schedules; 002 003import java.awt.*; 004import java.beans.PropertyChangeEvent; 005import java.beans.PropertyChangeListener; 006import java.util.Enumeration; 007import java.util.List; 008 009import javax.swing.*; 010 011import jmri.InstanceManager; 012import jmri.jmrit.operations.OperationsFrame; 013import jmri.jmrit.operations.OperationsXml; 014import jmri.jmrit.operations.locations.Location; 015import jmri.jmrit.operations.locations.LocationManager; 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.jmrit.operations.trains.excel.TrainCustomManifest; 021import jmri.swing.JTablePersistenceManager; 022import jmri.util.swing.JmriJOptionPane; 023 024/** 025 * Frame for adding and editing train schedules for operations. 026 * 027 * @author Bob Jacobsen Copyright (C) 2001 028 * @author Daniel Boudreau Copyright (C) 2010, 2012, 2016 029 */ 030public class TrainsScheduleTableFrame extends OperationsFrame implements PropertyChangeListener { 031 032 TrainManager trainManager = InstanceManager.getDefault(TrainManager.class); 033 TrainScheduleManager trainScheduleManager = InstanceManager.getDefault(TrainScheduleManager.class); 034 LocationManager locationManager = InstanceManager.getDefault(LocationManager.class); 035 036 TrainsScheduleTableModel trainsScheduleModel = new TrainsScheduleTableModel(); 037 javax.swing.JTable trainsScheduleTable = new javax.swing.JTable(trainsScheduleModel); 038 JScrollPane trainsPane; 039 040 // labels 041 JLabel textSort = new JLabel(Bundle.getMessage("SortBy")); 042 043 // radio buttons 044 JRadioButton sortByName = new JRadioButton(Bundle.getMessage("Name")); 045 JRadioButton sortByTime = new JRadioButton(Bundle.getMessage("Time")); 046 047 JRadioButton noneButton = new JRadioButton(Bundle.getMessage("None")); 048 JRadioButton anyButton = new JRadioButton(Bundle.getMessage("Any")); 049 050 // radio button groups 051 ButtonGroup schGroup = new ButtonGroup(); 052 053 // major buttons 054 JButton selectButton = new JButton(Bundle.getMessage("SelectAll")); 055 JButton clearButton = new JButton(Bundle.getMessage("ClearAll")); 056 057 JButton applyButton = new JButton(Bundle.getMessage("ButtonApply")); 058 JButton buildButton = new JButton(Bundle.getMessage("Build")); 059 JButton printButton = new JButton(Bundle.getMessage("Print")); 060 JButton runFileButton = new JButton(Bundle.getMessage("RunFile")); 061 JButton switchListsButton = new JButton(); 062 JButton terminateButton = new JButton(Bundle.getMessage("Terminate")); 063 064 JButton activateButton = new JButton(Bundle.getMessage("Activate")); 065 JButton saveButton = new JButton(Bundle.getMessage("ButtonSave")); 066 067 // check boxes 068 // panel 069 JPanel schedule = new JPanel(); 070 071 // text area 072 JTextArea commentTextArea = new JTextArea(2, 70); 073 JScrollPane commentScroller = new JScrollPane(commentTextArea, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, 074 JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); 075 076 public TrainsScheduleTableFrame() { 077 078 // general GUI configuration 079 getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)); 080 081 // Set up the jtable in a Scroll Pane.. 082 trainsPane = new JScrollPane(trainsScheduleTable); 083 trainsPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); 084 trainsPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); 085 trainsScheduleModel.initTable(trainsScheduleTable, this); 086 087 // row comment 088 JPanel pC = new JPanel(); 089 pC.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Comment"))); 090 pC.setLayout(new GridBagLayout()); 091 addItem(pC, commentScroller, 1, 0); 092 093 // adjust text area width based on window size 094 adjustTextAreaColumnWidth(commentScroller, commentTextArea); 095 096 // Set up the control panel 097 // row 1 098 JPanel cp1 = new JPanel(); 099 cp1.setLayout(new BoxLayout(cp1, BoxLayout.X_AXIS)); 100 101 // row 1 102 JPanel sortBy = new JPanel(); 103 sortBy.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("SortBy"))); 104 sortBy.add(sortByTime); 105 sortBy.add(sortByName); 106 107 // row 2 108 schedule.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Active"))); 109 updateControlPanel(); 110 111 cp1.add(sortBy); 112 cp1.add(schedule); 113 114 JPanel pButtons = new JPanel(); 115 pButtons.setLayout(new BoxLayout(pButtons, BoxLayout.X_AXIS)); 116 117 JPanel cp3 = new JPanel(); 118 cp3.setBorder(BorderFactory.createTitledBorder("")); 119 cp3.add(clearButton); 120 cp3.add(selectButton); 121 122 JPanel cp4 = new JPanel(); 123 cp4.setBorder(BorderFactory.createTitledBorder("")); 124 cp4.add(applyButton); 125 cp4.add(buildButton); 126 cp4.add(printButton); 127 cp4.add(runFileButton); 128 cp4.add(switchListsButton); 129 cp4.add(terminateButton); 130 131 JPanel cp5 = new JPanel(); 132 cp5.setBorder(BorderFactory.createTitledBorder("")); 133 cp5.add(activateButton); 134 cp5.add(saveButton); 135 136 pButtons.add(cp3); 137 pButtons.add(cp4); 138 pButtons.add(cp5); 139 140 // tool tips 141 selectButton.setToolTipText(Bundle.getMessage("SelectAllButtonTip")); 142 clearButton.setToolTipText(Bundle.getMessage("ClearAllButtonTip")); 143 applyButton.setToolTipText(Bundle.getMessage("ApplyButtonTip")); 144 buildButton.setToolTipText(Bundle.getMessage("BuildSelectedTip")); 145 runFileButton.setToolTipText(Bundle.getMessage("RunFileButtonTip")); 146 activateButton.setToolTipText(Bundle.getMessage("ActivateButtonTip")); 147 terminateButton.setToolTipText(Bundle.getMessage("TerminateSelectedTip")); 148 149 setPrintButtonText(); 150 setSwitchListButtonText(); 151 152 // place controls in scroll pane 153 JPanel controlPanel = new JPanel(); 154 controlPanel.setLayout(new BoxLayout(controlPanel, BoxLayout.Y_AXIS)); 155 controlPanel.add(pC); 156 controlPanel.add(cp1); 157 controlPanel.add(pButtons); 158 159 JScrollPane controlPane = new JScrollPane(controlPanel); 160 // make sure control panel is the right size 161 controlPane.setMinimumSize(new Dimension(500, 480)); 162 controlPane.setMaximumSize(new Dimension(2000, 500)); 163 controlPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); 164 165 getContentPane().add(trainsPane); 166 getContentPane().add(controlPane); 167 168 // show run button only if create CSV files is enabled 169 updateRunButton(); 170 171 // setup buttons 172 addButtonAction(clearButton); 173 addButtonAction(selectButton); 174 addButtonAction(applyButton); 175 addButtonAction(buildButton); 176 addButtonAction(printButton); 177 addButtonAction(runFileButton); 178 addButtonAction(switchListsButton); 179 addButtonAction(terminateButton); 180 addButtonAction(activateButton); 181 addButtonAction(saveButton); 182 183 ButtonGroup sortGroup = new ButtonGroup(); 184 sortGroup.add(sortByTime); 185 sortGroup.add(sortByName); 186 sortByTime.setSelected(true); 187 188 addRadioButtonAction(sortByTime); 189 addRadioButtonAction(sortByName); 190 191 addRadioButtonAction(noneButton); 192 addRadioButtonAction(anyButton); 193 194 // tips 195 noneButton.setToolTipText(Bundle.getMessage("NoActiveTip")); 196 anyButton.setToolTipText(Bundle.getMessage("AnyActiveTip")); 197 198 // build menu 199 JMenuBar menuBar = new JMenuBar(); 200 JMenu toolMenu = new JMenu(Bundle.getMessage("MenuTools")); 201 toolMenu.add(new TrainsScheduleEditAction()); 202 menuBar.add(toolMenu); 203 setJMenuBar(menuBar); 204 205 // add help menu to window 206 addHelpMenu("package.jmri.jmrit.operations.Operations_TrainSchedules", true); // NOI18N 207 208 setTitle(Bundle.getMessage("TitleScheduleTrains")); 209 210 initMinimumSize(new Dimension(Control.panelWidth700, Control.panelHeight500)); 211 212 addHorizontalScrollBarKludgeFix(controlPane, controlPanel); 213 214 Setup.getDefault().addPropertyChangeListener(this); 215 trainManager.addPropertyChangeListener(this); 216 trainScheduleManager.addPropertyChangeListener(this); 217 addPropertyChangeLocations(); 218 addPropertyChangeTrainSchedules(); 219 } 220 221 @Override 222 public void radioButtonActionPerformed(java.awt.event.ActionEvent ae) { 223 log.debug("radio button activated"); 224 // clear any sorts by column 225 clearTableSort(trainsScheduleTable); 226 if (ae.getSource() == sortByName) { 227 trainsScheduleModel.setSort(trainsScheduleModel.SORTBYNAME); 228 } else if (ae.getSource() == sortByTime) { 229 trainsScheduleModel.setSort(trainsScheduleModel.SORTBYTIME); 230 } else if (ae.getSource() == noneButton || ae.getSource() == anyButton) { 231 enableButtons(false); 232 commentTextArea.setText(""); // no text for the noneButton or anyButton 233 // must be one of the schedule radio buttons 234 } else { 235 enableButtons(true); 236 // update comment field 237 TrainSchedule ts = trainScheduleManager.getScheduleById(getSelectedScheduleId()); 238 commentTextArea.setText(ts.getComment()); 239 } 240 } 241 242 // add, build, print, switch lists, terminate, and save buttons 243 @Override 244 public void buttonActionPerformed(java.awt.event.ActionEvent ae) { 245 log.debug("button activated"); 246 if (ae.getSource() == clearButton) { 247 updateCheckboxes(false); 248 } 249 if (ae.getSource() == selectButton) { 250 updateCheckboxes(true); 251 } 252 if (ae.getSource() == applyButton) { 253 applySchedule(); 254 } 255 if (ae.getSource() == buildButton) { 256 switchListsButton.setEnabled(false); 257 runFileButton.setEnabled(false); 258 // uses a thread which allows table updates during build 259 trainManager.buildSelectedTrains(getSortByList()); 260 } 261 if (ae.getSource() == printButton) { 262 trainManager.printSelectedTrains(getSortByList()); 263 } 264 if (ae.getSource() == runFileButton) { 265 // Processes the CSV Manifest files using an external custom program. 266 if (!InstanceManager.getDefault(TrainCustomManifest.class).excelFileExists()) { 267 log.warn("Manifest creator file not found!, directory path: {}, file name: {}", 268 InstanceManager.getDefault(TrainCustomManifest.class).getDirectoryPathName(), 269 InstanceManager.getDefault(TrainCustomManifest.class).getFileName()); // NOI18N 270 JmriJOptionPane.showMessageDialog(this, 271 Bundle.getMessage("LoadDirectoryNameFileName", 272 InstanceManager.getDefault(TrainCustomManifest.class).getDirectoryPathName(), 273 InstanceManager.getDefault(TrainCustomManifest.class).getFileName()), 274 Bundle.getMessage("ManifestCreatorNotFound"), JmriJOptionPane.ERROR_MESSAGE); 275 return; 276 } 277 List<Train> trains = getSortByList(); 278 for (Train train : trains) { 279 if (train.isBuildEnabled()) { 280 if (!train.isBuilt()) { 281 JmriJOptionPane.showMessageDialog(this, 282 Bundle.getMessage("NeedToBuildBeforeRunFile", 283 train.getName()), 284 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 285 } else { 286 // Add csv manifest file to our collection to be processed. 287 InstanceManager.getDefault(TrainCustomManifest.class).addCsvFile(train.createCsvManifestFile()); 288 train.setPrinted(true); 289 } 290 } 291 } 292 293 // Now run the user specified custom Manifest processor program 294 InstanceManager.getDefault(TrainCustomManifest.class).process(); 295 } 296 if (ae.getSource() == switchListsButton) { 297 trainScheduleManager.buildSwitchLists(); 298 } 299 if (ae.getSource() == terminateButton) { 300 trainManager.terminateSelectedTrains(getSortByList()); 301 } 302 if (ae.getSource() == activateButton) { 303 trainScheduleManager.setTrainScheduleActiveId(getSelectedScheduleId()); 304 activateButton.setEnabled(false); 305 } 306 if (ae.getSource() == saveButton) { 307 storeValues(); 308 if (Setup.isCloseWindowOnSaveEnabled()) { 309 dispose(); 310 } 311 } 312 } 313 314 /* 315 * Update radio button names in the same order as the table 316 */ 317 private void updateControlPanel() { 318 schedule.removeAll(); 319 noneButton.setName(TrainSchedule.NONE); // Name holds schedule id for the selected radio button 320 noneButton.setSelected(true); 321 commentTextArea.setText(""); // no text for the noneButton or anyButton 322 enableButtons(false); 323 schedule.add(noneButton); 324 schGroup.add(noneButton); 325 326 for (int i = trainsScheduleModel.getFixedColumn(); i < trainsScheduleModel.getColumnCount(); i++) { 327 log.debug("Column name: {}", trainsScheduleTable.getColumnName(i)); 328 TrainSchedule ts = trainScheduleManager.getScheduleByName(trainsScheduleTable.getColumnName(i)); 329 if (ts != null) { 330 JRadioButton b = new JRadioButton(); 331 b.setText(ts.getName()); 332 b.setName(ts.getId()); 333 schedule.add(b); 334 schGroup.add(b); 335 addRadioButtonAction(b); 336 if (b.getName().equals(trainScheduleManager.getTrainScheduleActiveId())) { 337 b.setSelected(true); 338 enableButtons(true); 339 // update comment field 340 commentTextArea.setText(ts.getComment()); 341 } 342 } 343 } 344 anyButton.setName(TrainSchedule.ANY); // Name holds schedule id for the selected radio button 345 schedule.add(anyButton); 346 schGroup.add(anyButton); 347 anyButton.setSelected(trainScheduleManager.getTrainScheduleActiveId().equals(TrainSchedule.ANY)); 348 schedule.revalidate(); 349 } 350 351 private void updateCheckboxes(boolean selected) { 352 TrainSchedule ts = trainScheduleManager.getScheduleById(getSelectedScheduleId()); 353 if (ts != null) { 354 for (Train train : trainManager.getTrainsByIdList()) { 355 if (selected) { 356 ts.addTrainId(train.getId()); 357 } else { 358 ts.removeTrainId(train.getId()); 359 } 360 } 361 } 362 } 363 364 private void applySchedule() { 365 TrainSchedule ts = trainScheduleManager.getScheduleById(getSelectedScheduleId()); 366 if (ts != null) { 367 for (Train train : trainManager.getTrainsByIdList()) { 368 train.setBuildEnabled(ts.containsTrainId(train.getId())); 369 } 370 } 371 } 372 373 private String getSelectedScheduleId() { 374 AbstractButton b; 375 Enumeration<AbstractButton> en = schGroup.getElements(); 376 while (en.hasMoreElements()) { 377 b = en.nextElement(); 378 if (b.isSelected()) { 379 log.debug("schedule radio button {}", b.getText()); 380 return b.getName(); 381 } 382 } 383 return null; 384 } 385 386 private void enableButtons(boolean enable) { 387 selectButton.setEnabled(enable); 388 clearButton.setEnabled(enable); 389 applyButton.setEnabled(enable); 390 buildButton.setEnabled(enable); 391 printButton.setEnabled(enable); 392 runFileButton.setEnabled(enable); 393 switchListsButton.setEnabled(enable); 394 terminateButton.setEnabled(enable); 395 396 log.debug("Selected id: {}, Active id: {}", getSelectedScheduleId(), 397 trainScheduleManager.getTrainScheduleActiveId()); 398 399 activateButton.setEnabled(getSelectedScheduleId() != null && 400 !getSelectedScheduleId().equals(trainScheduleManager.getTrainScheduleActiveId())); 401 402 commentTextArea.setEnabled(enable); 403 } 404 405 private List<Train> getSortByList() { 406 if (sortByTime.isSelected()) { 407 return trainManager.getTrainsByTimeList(); 408 } else { 409 return trainManager.getTrainsByNameList(); 410 } 411 } 412 413 private void setSwitchListButtonText() { 414 if (!Setup.isSwitchListRealTime()) { 415 switchListsButton.setText(Bundle.getMessage("Update")); 416 } else if (trainManager.isPrintPreviewEnabled()) { 417 switchListsButton.setText(Bundle.getMessage("PreviewSwitchLists")); 418 } else { 419 switchListsButton.setText(Bundle.getMessage("PrintSwitchLists")); 420 } 421 } 422 423 // Modifies button text and tool tips 424 private void setPrintButtonText() { 425 if (trainManager.isPrintPreviewEnabled()) { 426 printButton.setText(Bundle.getMessage("Preview")); 427 printButton.setToolTipText(Bundle.getMessage("PreviewSelectedTip")); 428 } else { 429 printButton.setText(Bundle.getMessage("Print")); 430 printButton.setToolTipText(Bundle.getMessage("PrintSelectedTip")); 431 } 432 } 433 434 private void updateSwitchListButton() { 435 List<Location> locations = locationManager.getList(); 436 for (Location location : locations) { 437 if (location != null && location.isSwitchListEnabled() && location.getStatus().equals(Location.MODIFIED)) { 438 switchListsButton.setBackground(Color.RED); 439 return; 440 } 441 } 442 switchListsButton.setBackground(Color.GREEN); 443 } 444 445 private void updateRunButton() { 446 runFileButton.setVisible(Setup.isGenerateCsvManifestEnabled()); 447 } 448 449 @Override 450 protected void storeValues() { 451 // Save comment 452 TrainSchedule ts = trainScheduleManager.getScheduleById(getSelectedScheduleId()); 453 if (ts != null) { 454 ts.setComment(commentTextArea.getText()); 455 } 456 OperationsXml.save(); 457 } 458 459 @Override 460 public void dispose() { 461 Setup.getDefault().removePropertyChangeListener(this); 462 trainManager.removePropertyChangeListener(this); 463 trainScheduleManager.removePropertyChangeListener(this); 464 removePropertyChangeTrainSchedules(); 465 removePropertyChangeLocations(); 466 trainsScheduleModel.dispose(); 467 InstanceManager.getOptionalDefault(JTablePersistenceManager.class).ifPresent(tpm -> { 468 tpm.stopPersisting(trainsScheduleTable); 469 }); 470 super.dispose(); 471 } 472 473 private void addPropertyChangeLocations() { 474 for (Location location : locationManager.getList()) { 475 location.addPropertyChangeListener(this); 476 } 477 } 478 479 private void removePropertyChangeLocations() { 480 for (Location location : locationManager.getList()) { 481 location.removePropertyChangeListener(this); 482 } 483 } 484 485 private void addPropertyChangeTrainSchedules() { 486 List<TrainSchedule> trainSchedules = trainScheduleManager.getSchedulesByIdList(); 487 for (TrainSchedule ts : trainSchedules) { 488 ts.addPropertyChangeListener(this); 489 } 490 } 491 492 private void removePropertyChangeTrainSchedules() { 493 List<TrainSchedule> trainSchedules = trainScheduleManager.getSchedulesByIdList(); 494 for (TrainSchedule ts : trainSchedules) { 495 ts.removePropertyChangeListener(this); 496 } 497 } 498 499 @Override 500 public void propertyChange(PropertyChangeEvent e) { 501 if (Control.SHOW_PROPERTY) 502 log.debug("Property change {} old: {} new: {}", e.getPropertyName(), e.getOldValue(), e.getNewValue()); 503 if (e.getPropertyName().equals(TrainScheduleManager.LISTLENGTH_CHANGED_PROPERTY) || 504 e.getPropertyName().equals(TrainSchedule.NAME_CHANGED_PROPERTY)) { 505 updateControlPanel(); 506 } 507 if (e.getPropertyName().equals(TrainManager.PRINTPREVIEW_CHANGED_PROPERTY)) { 508 setPrintButtonText(); 509 setSwitchListButtonText(); 510 } 511 if (e.getPropertyName().equals(TrainManager.TRAINS_BUILT_CHANGED_PROPERTY)) { 512 switchListsButton.setEnabled(true); 513 runFileButton.setEnabled(true); 514 } 515 if (e.getPropertyName().equals(Setup.REAL_TIME_PROPERTY_CHANGE)) { 516 setSwitchListButtonText(); 517 } 518 if (e.getPropertyName().equals(Location.STATUS_CHANGED_PROPERTY) || 519 e.getPropertyName().equals(Location.SWITCHLIST_CHANGED_PROPERTY)) { 520 log.debug("update switch list button location ({})", e.getSource()); 521 updateSwitchListButton(); 522 } 523 if (e.getPropertyName().equals(Setup.MANIFEST_CSV_PROPERTY_CHANGE)) { 524 updateRunButton(); 525 } 526 } 527 528 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TrainsScheduleTableFrame.class); 529}