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.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 private void loadTypeComboBox() { 340 typeBox.removeAllItems(); 341 for (String typeName : InstanceManager.getDefault(CarTypes.class).getNames()) { 342 if (_track.isTypeNameAccepted(typeName)) { 343 typeBox.addItem(typeName); 344 } 345 } 346 } 347 348 /** 349 * @return true if name is less than 26 characters 350 */ 351 private boolean checkName(String s) { 352 if (scheduleNameTextField.getText().trim().isEmpty()) { 353 return false; 354 } 355 if (scheduleNameTextField.getText().length() > MAX_NAME_LENGTH) { 356 log.error("Schedule name must be less than 26 charaters"); 357 JmriJOptionPane.showMessageDialog(this, 358 Bundle.getMessage("ScheduleNameLengthMax", 359 Integer.toString(MAX_NAME_LENGTH + 1)), 360 Bundle.getMessage("CanNotSchedule", s), 361 JmriJOptionPane.ERROR_MESSAGE); 362 return false; 363 } 364 return true; 365 } 366 367 private void reportScheduleExists(String s) { 368 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("ReportExists"), 369 Bundle.getMessage("CanNotSchedule", s), 370 JmriJOptionPane.ERROR_MESSAGE); 371 } 372 373 private void enableButtons(boolean enabled) { 374 typeBox.setEnabled(enabled); 375 addTypeButton.setEnabled(enabled); 376 addLocAtTop.setEnabled(enabled); 377 addLocAtMiddle.setEnabled(enabled); 378 addLocAtBottom.setEnabled(enabled); 379 saveScheduleButton.setEnabled(enabled); 380 deleteScheduleButton.setEnabled(enabled); 381 scheduleTable.setEnabled(enabled); 382 // the inverse! 383 addScheduleButton.setEnabled(!enabled); 384 } 385 386 @Override 387 public void dispose() { 388 InstanceManager.getDefault(CarTypes.class).removePropertyChangeListener(this); 389 _location.removePropertyChangeListener(this); 390 _track.removePropertyChangeListener(this); 391 InstanceManager.getOptionalDefault(JTablePersistenceManager.class).ifPresent(tpm -> { 392 tpm.stopPersisting(scheduleTable); 393 }); 394 scheduleModel.dispose(); 395 super.dispose(); 396 } 397 398 @Override 399 public void propertyChange(java.beans.PropertyChangeEvent e) { 400 if (Control.SHOW_PROPERTY) { 401 log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e 402 .getNewValue()); 403 } 404 if (e.getPropertyName().equals(CarTypes.CARTYPES_CHANGED_PROPERTY) || 405 e.getPropertyName().equals(Track.TYPES_CHANGED_PROPERTY) || 406 e.getPropertyName().equals(Location.TYPES_CHANGED_PROPERTY)) { 407 loadTypeComboBox(); 408 } 409 } 410 411 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ScheduleEditFrame.class); 412 413}