001package jmri.jmrit.operations.locations.schedules; 002 003import java.awt.Dimension; 004import java.awt.GridBagLayout; 005 006import javax.swing.*; 007 008import jmri.InstanceManager; 009import jmri.jmrit.operations.*; 010import jmri.jmrit.operations.locations.*; 011import jmri.jmrit.operations.locations.schedules.tools.*; 012import jmri.jmrit.operations.rollingstock.cars.CarTypes; 013import jmri.jmrit.operations.setup.Control; 014import jmri.jmrit.operations.setup.Setup; 015import jmri.swing.JTablePersistenceManager; 016import jmri.util.swing.JmriJOptionPane; 017 018/** 019 * Frame for user edit of a schedule 020 * 021 * @author Dan Boudreau Copyright (C) 2008, 2011 022 */ 023public class ScheduleEditFrame extends OperationsFrame implements java.beans.PropertyChangeListener { 024 025 ScheduleTableModel scheduleModel = new ScheduleTableModel(); 026 JTable scheduleTable = new JTable(scheduleModel); 027 JScrollPane schedulePane; 028 029 ScheduleManager manager; 030 LocationManagerXml managerXml; 031 032 Schedule _schedule = null; 033 ScheduleItem _scheduleItem = null; 034 Location _location = null; 035 public Track _track = null; 036 037 // labels 038 // major buttons 039 JButton addTypeButton = new JButton(Bundle.getMessage("AddType")); 040 JButton saveScheduleButton = new JButton(Bundle.getMessage("SaveSchedule")); 041 JButton deleteScheduleButton = new JButton(Bundle.getMessage("DeleteSchedule")); 042 JButton addScheduleButton = new JButton(Bundle.getMessage("AddSchedule")); 043 044 // check boxes 045 JCheckBox checkBox; 046 047 // radio buttons 048 JRadioButton addLocAtTop = new JRadioButton(Bundle.getMessage("Top")); 049 JRadioButton addLocAtMiddle = new JRadioButton(Bundle.getMessage("Middle")); 050 JRadioButton addLocAtBottom = new JRadioButton(Bundle.getMessage("Bottom")); 051 JRadioButton sequentialRadioButton = new JRadioButton(Bundle.getMessage("Sequential")); 052 JRadioButton matchRadioButton = new JRadioButton(Bundle.getMessage("Match")); 053 054 // text field 055 JTextField scheduleNameTextField = new JTextField(20); 056 JTextField commentTextField = new JTextField(35); 057 058 // combo boxes 059 JComboBox<String> typeBox = new JComboBox<>(); 060 061 public static final int MAX_NAME_LENGTH = Control.max_len_string_location_name; 062 public static final String NAME = Bundle.getMessage("Name"); 063 public static final String DISPOSE = "dispose"; // NOI18N 064 065 public ScheduleEditFrame(Schedule schedule, Track track) { 066 super(); 067 068 _schedule = schedule; 069 _location = track.getLocation(); 070 _track = track; 071 072 // load managers 073 manager = InstanceManager.getDefault(ScheduleManager.class); 074 managerXml = InstanceManager.getDefault(LocationManagerXml.class); 075 076 // Set up the jtable in a Scroll Pane.. 077 schedulePane = new JScrollPane(scheduleTable); 078 schedulePane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); 079 080 scheduleModel.initTable(this, scheduleTable, schedule, _location, _track); 081 if (_schedule != null) { 082 scheduleNameTextField.setText(_schedule.getName()); 083 commentTextField.setText(_schedule.getComment()); 084 setTitle(Bundle.getMessage("TitleScheduleEdit", _track.getName())); 085 enableButtons(true); 086 } else { 087 setTitle(Bundle.getMessage("TitleScheduleAdd", _track.getName())); 088 enableButtons(false); 089 } 090 091 getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)); 092 093 // Layout the panel by rows 094 JPanel p1 = new JPanel(); 095 p1.setLayout(new BoxLayout(p1, BoxLayout.X_AXIS)); 096 097 JScrollPane p1Pane = new JScrollPane(p1); 098 p1Pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER); 099 p1Pane.setMinimumSize(new Dimension(300, 100 3 * scheduleNameTextField.getPreferredSize().height)); 101 p1Pane.setMaximumSize(new Dimension(2000, 200)); 102 103 // row 1a name 104 JPanel pName = new JPanel(); 105 pName.setLayout(new GridBagLayout()); 106 pName.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Name"))); 107 addItem(pName, scheduleNameTextField, 0, 0); 108 109 // row 1b comment 110 JPanel pC = new JPanel(); 111 pC.setLayout(new GridBagLayout()); 112 pC.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("Comment"))); 113 addItem(pC, commentTextField, 0, 0); 114 115 // row 1c mode 116 JPanel pMode = new JPanel(); 117 pMode.setLayout(new GridBagLayout()); 118 pMode.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("ScheduleMode"))); 119 addItem(pMode, sequentialRadioButton, 0, 0); 120 addItem(pMode, matchRadioButton, 1, 0); 121 122 sequentialRadioButton.setToolTipText(Bundle.getMessage("TipSequential")); 123 matchRadioButton.setToolTipText(Bundle.getMessage("TipMatch")); 124 ButtonGroup modeGroup = new ButtonGroup(); 125 modeGroup.add(sequentialRadioButton); 126 modeGroup.add(matchRadioButton); 127 128 sequentialRadioButton.setSelected(_track.getScheduleMode() == Track.SEQUENTIAL); 129 matchRadioButton.setSelected(_track.getScheduleMode() == Track.MATCH); 130 scheduleModel.setMatchMode(_track.getScheduleMode() == Track.MATCH); 131 132 p1.add(pName); 133 p1.add(pC); 134 p1.add(pMode); 135 136 // row 2 137 JPanel p3 = new JPanel(); 138 p3.setLayout(new GridBagLayout()); 139 p3.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("AddItem"))); 140 addItem(p3, typeBox, 0, 1); 141 addItem(p3, addTypeButton, 1, 1); 142 addItem(p3, addLocAtTop, 2, 1); 143 addItem(p3, addLocAtMiddle, 3, 1); 144 addItem(p3, addLocAtBottom, 4, 1); 145 ButtonGroup group = new ButtonGroup(); 146 group.add(addLocAtTop); 147 group.add(addLocAtMiddle); 148 group.add(addLocAtBottom); 149 addLocAtBottom.setSelected(true); 150 151 p3.setMaximumSize(new Dimension(2000, 200)); 152 153 // row 11 buttons 154 JPanel pB = new JPanel(); 155 pB.setLayout(new GridBagLayout()); 156 pB.setBorder(BorderFactory.createTitledBorder("")); 157 pB.setMaximumSize(new Dimension(2000, 200)); 158 159 // row 13 160 addItem(pB, deleteScheduleButton, 0, 0); 161 addItem(pB, addScheduleButton, 1, 0); 162 addItem(pB, saveScheduleButton, 3, 0); 163 164 getContentPane().add(p1Pane); 165 getContentPane().add(schedulePane); 166 getContentPane().add(p3); 167 getContentPane().add(pB); 168 169 // set up buttons 170 addButtonAction(addTypeButton); 171 addButtonAction(deleteScheduleButton); 172 addButtonAction(addScheduleButton); 173 addButtonAction(saveScheduleButton); 174 175 // set up radio buttons 176 addRadioButtonAction(sequentialRadioButton); 177 addRadioButtonAction(matchRadioButton); 178 179 // set up combobox 180 OperationsPanel.padComboBox(typeBox); 181 loadTypeComboBox(); 182 183 // build menu 184 JMenuBar menuBar = new JMenuBar(); 185 JMenu toolMenu = new JMenu(Bundle.getMessage("MenuTools")); 186 menuBar.add(toolMenu); 187 toolMenu.add(new ScheduleCopyAction(schedule)); 188 toolMenu.add(new ScheduleOptionsAction(this)); 189 toolMenu.add(new ScheduleResetHitsAction(schedule)); 190 toolMenu.addSeparator(); 191 toolMenu.add(new SchedulesByLoadAction()); 192 toolMenu.add(new SchedulesAndStagingAction()); 193 setJMenuBar(menuBar); 194 addHelpMenu("package.jmri.jmrit.operations.Operations_Schedules", true); // NOI18N 195 196 // get notified if car types or roads are changed 197 InstanceManager.getDefault(CarTypes.class).addPropertyChangeListener(this); 198 _location.addPropertyChangeListener(this); 199 _track.addPropertyChangeListener(this); 200 201 // set frame size and schedule for display 202 initMinimumSize(new Dimension(Control.panelWidth700, Control.panelHeight400)); 203 } 204 205 // Save, Delete, Add 206 @Override 207 public void buttonActionPerformed(java.awt.event.ActionEvent ae) { 208 if (ae.getSource() == addTypeButton) { 209 addNewScheduleItem(); 210 } 211 if (ae.getSource() == saveScheduleButton) { 212 log.debug("schedule save button activated"); 213 Schedule schedule = manager.getScheduleByName(scheduleNameTextField.getText()); 214 if (_schedule == null && schedule == null) { 215 saveNewSchedule(); 216 } else { 217 if (schedule != null && schedule != _schedule) { 218 reportScheduleExists(Bundle.getMessage("save")); 219 return; 220 } 221 saveSchedule(); 222 } 223 if (Setup.isCloseWindowOnSaveEnabled()) { 224 dispose(); 225 } 226 } 227 if (ae.getSource() == deleteScheduleButton) { 228 log.debug("schedule delete button activated"); 229 if (JmriJOptionPane.showConfirmDialog(this, 230 Bundle.getMessage("DoYouWantToDeleteSchedule", scheduleNameTextField.getText()), 231 Bundle.getMessage("DeleteSchedule?"), 232 JmriJOptionPane.YES_NO_OPTION) != JmriJOptionPane.YES_OPTION) { 233 return; 234 } 235 Schedule schedule = manager.getScheduleByName(scheduleNameTextField.getText()); 236 if (schedule == null) { 237 return; 238 } 239 240 if (_track != null) { 241 _track.setScheduleId(Track.NONE); 242 } 243 244 manager.deregister(schedule); 245 _schedule = null; 246 247 enableButtons(false); 248 // save schedule file 249 OperationsXml.save(); 250 } 251 if (ae.getSource() == addScheduleButton) { 252 Schedule schedule = manager.getScheduleByName(scheduleNameTextField.getText()); 253 if (schedule != null) { 254 reportScheduleExists(Bundle.getMessage("add")); 255 return; 256 } 257 saveNewSchedule(); 258 } 259 } 260 261 @Override 262 public void radioButtonActionPerformed(java.awt.event.ActionEvent ae) { 263 log.debug("Radio button action"); 264 scheduleModel.setMatchMode(ae.getSource() == matchRadioButton); 265 } 266 267 private void addNewScheduleItem() { 268 if (typeBox.getSelectedItem() == null) { 269 return; 270 } 271 // add item to this schedule 272 if (addLocAtTop.isSelected()) { 273 _schedule.addItem((String) typeBox.getSelectedItem(), 0); 274 } else if (addLocAtMiddle.isSelected()) { 275 if (scheduleTable.getSelectedRow() >= 0) { 276 int row = scheduleTable.getSelectedRow(); 277 log.debug("Selected row: {}", row); 278 _schedule.addItem((String) typeBox.getSelectedItem(), row); 279 // we need to reselect the table since the content has changed 280 scheduleTable.getSelectionModel().setSelectionInterval(row, row); 281 } else { 282 _schedule.addItem((String) typeBox.getSelectedItem(), _schedule.getSize() / 2); 283 } 284 } else { 285 _schedule.addItem((String) typeBox.getSelectedItem()); 286 } 287 if (_track.getScheduleMode() == Track.MATCH && typeBox.getSelectedIndex() < typeBox.getItemCount() - 1) { 288 typeBox.setSelectedIndex(typeBox.getSelectedIndex() + 1); 289 } 290 } 291 292 private void saveNewSchedule() { 293 if (!checkName(Bundle.getMessage("add"))) { 294 return; 295 } 296 Schedule schedule = manager.newSchedule(scheduleNameTextField.getText()); 297 scheduleModel.initTable(this, scheduleTable, schedule, _location, _track); 298 _schedule = schedule; 299 // enable checkboxes 300 enableButtons(true); 301 saveSchedule(); 302 } 303 304 private void saveSchedule() { 305 if (!checkName(Bundle.getMessage("save"))) { 306 return; 307 } 308 _schedule.setName(scheduleNameTextField.getText()); 309 _schedule.setComment(commentTextField.getText()); 310 311 if (scheduleTable.isEditing()) { 312 log.debug("schedule table edit true"); 313 scheduleTable.getCellEditor().stopCellEditing(); 314 scheduleTable.clearSelection(); 315 } 316 if (_track != null) { 317 if (!_track.getScheduleId().equals(_schedule.getId())) { 318 InstanceManager.getDefault(LocationManager.class).resetMoves(); 319 } 320 _track.setSchedule(_schedule); 321 if (sequentialRadioButton.isSelected()) { 322 _track.setScheduleMode(Track.SEQUENTIAL); 323 } else { 324 _track.setScheduleMode(Track.MATCH); 325 } 326 // check for errors, ignore no schedule items error when creating a 327 // new schedule 328 String status = _track.checkScheduleValid(); 329 if (_schedule.getItemsBySequenceList().size() != 0 && !status.equals(Schedule.SCHEDULE_OKAY)) { 330 JmriJOptionPane.showMessageDialog(this, status, Bundle.getMessage("ErrorTitle"), 331 JmriJOptionPane.ERROR_MESSAGE); 332 } 333 } 334 335 // save schedule file 336 OperationsXml.save(); 337 } 338 339 /* 340 * only load car types serviced by the track 341 */ 342 private void loadTypeComboBox() { 343 typeBox.removeAllItems(); 344 for (String typeName : InstanceManager.getDefault(CarTypes.class).getNames()) { 345 if (_track.isTypeNameAccepted(typeName)) { 346 typeBox.addItem(typeName); 347 } 348 } 349 } 350 351 /** 352 * @return true if name is less than 26 characters 353 */ 354 private boolean checkName(String s) { 355 if (scheduleNameTextField.getText().trim().isEmpty()) { 356 return false; 357 } 358 if (scheduleNameTextField.getText().length() > MAX_NAME_LENGTH) { 359 log.error("Schedule name must be less than 26 charaters"); 360 JmriJOptionPane.showMessageDialog(this, 361 Bundle.getMessage("ScheduleNameLengthMax", 362 Integer.toString(MAX_NAME_LENGTH + 1)), 363 Bundle.getMessage("CanNotSchedule", s), 364 JmriJOptionPane.ERROR_MESSAGE); 365 return false; 366 } 367 return true; 368 } 369 370 private void reportScheduleExists(String s) { 371 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ReportExists"), 372 Bundle.getMessage("CanNotSchedule", s), 373 JmriJOptionPane.ERROR_MESSAGE); 374 } 375 376 private void enableButtons(boolean enabled) { 377 typeBox.setEnabled(enabled); 378 addTypeButton.setEnabled(enabled); 379 addLocAtTop.setEnabled(enabled); 380 addLocAtMiddle.setEnabled(enabled); 381 addLocAtBottom.setEnabled(enabled); 382 saveScheduleButton.setEnabled(enabled); 383 deleteScheduleButton.setEnabled(enabled); 384 scheduleTable.setEnabled(enabled); 385 // the inverse! 386 addScheduleButton.setEnabled(!enabled); 387 } 388 389 @Override 390 public void dispose() { 391 InstanceManager.getDefault(CarTypes.class).removePropertyChangeListener(this); 392 _location.removePropertyChangeListener(this); 393 _track.removePropertyChangeListener(this); 394 InstanceManager.getOptionalDefault(JTablePersistenceManager.class).ifPresent(tpm -> { 395 tpm.stopPersisting(scheduleTable); 396 }); 397 scheduleModel.dispose(); 398 super.dispose(); 399 } 400 401 @Override 402 public void propertyChange(java.beans.PropertyChangeEvent e) { 403 if (Control.SHOW_PROPERTY) { 404 log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e 405 .getNewValue()); 406 } 407 if (e.getPropertyName().equals(CarTypes.CARTYPES_CHANGED_PROPERTY) || 408 e.getPropertyName().equals(Track.TYPES_CHANGED_PROPERTY) || 409 e.getPropertyName().equals(Location.TYPES_CHANGED_PROPERTY)) { 410 loadTypeComboBox(); 411 } 412 } 413 414 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ScheduleEditFrame.class); 415 416}