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