001package jmri.jmrit.operations.locations.schedules;
002
003import java.awt.Color;
004import java.beans.PropertyChangeEvent;
005import java.beans.PropertyChangeListener;
006import java.text.MessageFormat;
007import java.util.ArrayList;
008import java.util.List;
009
010import javax.swing.*;
011import javax.swing.table.TableCellEditor;
012
013import org.slf4j.Logger;
014import org.slf4j.LoggerFactory;
015
016import jmri.InstanceManager;
017import jmri.jmrit.operations.OperationsTableModel;
018import jmri.jmrit.operations.locations.*;
019import jmri.jmrit.operations.rollingstock.cars.*;
020import jmri.jmrit.operations.setup.Control;
021import jmri.jmrit.operations.trains.schedules.TrainSchedule;
022import jmri.jmrit.operations.trains.schedules.TrainScheduleManager;
023import jmri.util.swing.XTableColumnModel;
024import jmri.util.table.ButtonEditor;
025import jmri.util.table.ButtonRenderer;
026
027/**
028 * Table Model for edit of a schedule used by operations
029 *
030 * @author Daniel Boudreau Copyright (C) 2009, 2014
031 */
032public class ScheduleTableModel extends OperationsTableModel implements PropertyChangeListener {
033    
034    protected static final String POINTER = "    -->";
035
036    // Defines the columns
037    private static final int ID_COLUMN = 0;
038    private static final int CURRENT_COLUMN = ID_COLUMN + 1;
039    private static final int TYPE_COLUMN = CURRENT_COLUMN + 1;
040    private static final int RANDOM_COLUMN = TYPE_COLUMN + 1;
041    private static final int SETOUT_DAY_COLUMN = RANDOM_COLUMN + 1;
042    private static final int ROAD_COLUMN = SETOUT_DAY_COLUMN + 1;
043    private static final int LOAD_COLUMN = ROAD_COLUMN + 1;
044    private static final int SHIP_COLUMN = LOAD_COLUMN + 1;
045    private static final int DEST_COLUMN = SHIP_COLUMN + 1;
046    private static final int TRACK_COLUMN = DEST_COLUMN + 1;
047    private static final int PICKUP_DAY_COLUMN = TRACK_COLUMN + 1;
048    private static final int COUNT_COLUMN = PICKUP_DAY_COLUMN + 1;
049    private static final int HIT_COLUMN = COUNT_COLUMN + 1;
050    private static final int WAIT_COLUMN = HIT_COLUMN + 1;
051    private static final int UP_COLUMN = WAIT_COLUMN + 1;
052    private static final int DOWN_COLUMN = UP_COLUMN + 1;
053    private static final int DELETE_COLUMN = DOWN_COLUMN + 1;
054
055    private static final int HIGHEST_COLUMN = DELETE_COLUMN + 1;
056
057    public ScheduleTableModel() {
058        super();
059    }
060
061    Schedule _schedule;
062    Location _location;
063    Track _track;
064    JTable _table;
065    ScheduleEditFrame _frame;
066    boolean _matchMode = false;
067
068    private void updateList() {
069        if (_schedule == null) {
070            return;
071        }
072        // first, remove listeners from the individual objects
073        removePropertyChangeScheduleItems();
074        _list = _schedule.getItemsBySequenceList();
075        // and add them back in
076        for (ScheduleItem si : _list) {
077            si.addPropertyChangeListener(this);
078            // TODO the following two property changes could be moved to ScheduleItem
079            // covers the cases where destination or track is deleted
080            if (si.getDestination() != null) {
081                si.getDestination().addPropertyChangeListener(this);
082            }
083            if (si.getDestinationTrack() != null) {
084                si.getDestinationTrack().addPropertyChangeListener(this);
085            }
086        }
087    }
088
089    List<ScheduleItem> _list = new ArrayList<>();
090
091    protected void initTable(ScheduleEditFrame frame, JTable table, Schedule schedule, Location location, Track track) {
092        super.initTable(table);
093        _schedule = schedule;
094        _location = location;
095        _track = track;
096        _table = table;
097        _frame = frame;
098
099        // add property listeners
100        if (_schedule != null) {
101            _schedule.addPropertyChangeListener(this);
102        }
103        _location.addPropertyChangeListener(this);
104        _track.addPropertyChangeListener(this);
105        initTable();
106    }
107
108    private void initTable() {
109        // Use XTableColumnModel so we can control which columns are visible
110        XTableColumnModel tcm = new XTableColumnModel();
111        _table.setColumnModel(tcm);
112        _table.createDefaultColumnsFromModel();
113
114        // Install the button handlers
115        ButtonRenderer buttonRenderer = new ButtonRenderer();
116        TableCellEditor buttonEditor = new ButtonEditor(new javax.swing.JButton());
117        tcm.getColumn(UP_COLUMN).setCellRenderer(buttonRenderer);
118        tcm.getColumn(UP_COLUMN).setCellEditor(buttonEditor);
119        tcm.getColumn(DOWN_COLUMN).setCellRenderer(buttonRenderer);
120        tcm.getColumn(DOWN_COLUMN).setCellEditor(buttonEditor);
121        tcm.getColumn(DELETE_COLUMN).setCellRenderer(buttonRenderer);
122        tcm.getColumn(DELETE_COLUMN).setCellEditor(buttonEditor);
123
124        // set column preferred widths
125        _table.getColumnModel().getColumn(ID_COLUMN).setPreferredWidth(35);
126        _table.getColumnModel().getColumn(CURRENT_COLUMN).setPreferredWidth(50);
127        _table.getColumnModel().getColumn(TYPE_COLUMN).setPreferredWidth(90);
128        _table.getColumnModel().getColumn(RANDOM_COLUMN).setPreferredWidth(60);
129        _table.getColumnModel().getColumn(SETOUT_DAY_COLUMN).setPreferredWidth(90);
130        _table.getColumnModel().getColumn(ROAD_COLUMN).setPreferredWidth(90);
131        _table.getColumnModel().getColumn(LOAD_COLUMN).setPreferredWidth(90);
132        _table.getColumnModel().getColumn(SHIP_COLUMN).setPreferredWidth(90);
133        _table.getColumnModel().getColumn(DEST_COLUMN).setPreferredWidth(130);
134        _table.getColumnModel().getColumn(TRACK_COLUMN).setPreferredWidth(130);
135        _table.getColumnModel().getColumn(PICKUP_DAY_COLUMN).setPreferredWidth(90);
136        _table.getColumnModel().getColumn(COUNT_COLUMN).setPreferredWidth(45);
137        _table.getColumnModel().getColumn(HIT_COLUMN).setPreferredWidth(45);
138        _table.getColumnModel().getColumn(WAIT_COLUMN).setPreferredWidth(40);
139        _table.getColumnModel().getColumn(UP_COLUMN).setPreferredWidth(60);
140        _table.getColumnModel().getColumn(DOWN_COLUMN).setPreferredWidth(70);
141        _table.getColumnModel().getColumn(DELETE_COLUMN).setPreferredWidth(70);
142
143        _frame.loadTableDetails(_table);
144        // setup columns
145        tcm.setColumnVisible(tcm.getColumnByModelIndex(HIT_COLUMN), _matchMode);
146        tcm.setColumnVisible(tcm.getColumnByModelIndex(COUNT_COLUMN), !_matchMode);
147
148        // does not use a table sorter
149        _table.setRowSorter(null);
150
151        updateList();
152    }
153
154    @Override
155    public int getRowCount() {
156        return _list.size();
157    }
158
159    @Override
160    public int getColumnCount() {
161        return HIGHEST_COLUMN;
162    }
163
164    @Override
165    public String getColumnName(int col) {
166        switch (col) {
167            case ID_COLUMN:
168                return Bundle.getMessage("Id");
169            case CURRENT_COLUMN:
170                return Bundle.getMessage("Current");
171            case TYPE_COLUMN:
172                return Bundle.getMessage("Type");
173            case RANDOM_COLUMN:
174                return Bundle.getMessage("Random");
175            case SETOUT_DAY_COLUMN:
176                return Bundle.getMessage("Delivery");
177            case ROAD_COLUMN:
178                return Bundle.getMessage("Road");
179            case LOAD_COLUMN:
180                return Bundle.getMessage("Receive");
181            case SHIP_COLUMN:
182                return Bundle.getMessage("Ship");
183            case DEST_COLUMN:
184                return Bundle.getMessage("Destination");
185            case TRACK_COLUMN:
186                return Bundle.getMessage("Track");
187            case PICKUP_DAY_COLUMN:
188                return Bundle.getMessage("Pickup");
189            case COUNT_COLUMN:
190                return Bundle.getMessage("Count");
191            case HIT_COLUMN:
192                return Bundle.getMessage("Hits");
193            case WAIT_COLUMN:
194                return Bundle.getMessage("Wait");
195            case UP_COLUMN:
196                return Bundle.getMessage("Up");
197            case DOWN_COLUMN:
198                return Bundle.getMessage("Down");
199            case DELETE_COLUMN:
200                return Bundle.getMessage("ButtonDelete");
201            default:
202                return "unknown"; // NOI18N
203        }
204    }
205
206    @Override
207    public Class<?> getColumnClass(int col) {
208        switch (col) {
209            case ID_COLUMN:
210            case CURRENT_COLUMN:
211            case TYPE_COLUMN:
212                return String.class;
213            case RANDOM_COLUMN:
214            case SETOUT_DAY_COLUMN:
215            case ROAD_COLUMN:
216            case LOAD_COLUMN:
217            case SHIP_COLUMN:
218            case DEST_COLUMN:
219            case TRACK_COLUMN:
220            case PICKUP_DAY_COLUMN:
221                return JComboBox.class;
222            case COUNT_COLUMN:
223            case HIT_COLUMN:
224            case WAIT_COLUMN:
225                return Integer.class;
226            case UP_COLUMN:
227            case DOWN_COLUMN:
228            case DELETE_COLUMN:
229                return JButton.class;
230            default:
231                return null;
232        }
233    }
234
235    @Override
236    public boolean isCellEditable(int row, int col) {
237        switch (col) {
238            case CURRENT_COLUMN:
239            case RANDOM_COLUMN:
240            case SETOUT_DAY_COLUMN:
241            case ROAD_COLUMN:
242            case LOAD_COLUMN:
243            case DEST_COLUMN:
244            case TRACK_COLUMN:
245            case PICKUP_DAY_COLUMN:
246            case COUNT_COLUMN:
247            case HIT_COLUMN:
248            case WAIT_COLUMN:
249            case UP_COLUMN:
250            case DOWN_COLUMN:
251            case DELETE_COLUMN:
252                return true;
253            case SHIP_COLUMN:
254                return !_track.isDisableLoadChangeEnabled();
255            default:
256                return false;
257        }
258    }
259
260    @Override
261    public Object getValueAt(int row, int col) {
262        if (row >= getRowCount()) {
263            return "ERROR row " + row; // NOI18N
264        }
265        ScheduleItem si = _list.get(row);
266        if (si == null) {
267            return "ERROR schedule item unknown " + row; // NOI18N
268        }
269        switch (col) {
270            case ID_COLUMN:
271                return si.getId();
272            case CURRENT_COLUMN:
273                return getCurrentPointer(si);
274            case TYPE_COLUMN:
275                return getType(si);
276            case RANDOM_COLUMN:
277                return getRandomComboBox(si);
278            case SETOUT_DAY_COLUMN:
279                return getSetoutDayComboBox(si);
280            case ROAD_COLUMN:
281                return getRoadComboBox(si);
282            case LOAD_COLUMN:
283                return getLoadComboBox(si);
284            case SHIP_COLUMN:
285                return getShipComboBox(si);
286            case DEST_COLUMN:
287                return getDestComboBox(si);
288            case TRACK_COLUMN:
289                return getTrackComboBox(si);
290            case PICKUP_DAY_COLUMN:
291                return getPickupDayComboBox(si);
292            case COUNT_COLUMN:
293                return si.getCount();
294            case HIT_COLUMN:
295                return si.getHits();
296            case WAIT_COLUMN:
297                return si.getWait();
298            case UP_COLUMN:
299                return Bundle.getMessage("Up");
300            case DOWN_COLUMN:
301                return Bundle.getMessage("Down");
302            case DELETE_COLUMN:
303                return Bundle.getMessage("ButtonDelete");
304            default:
305                return "unknown " + col; // NOI18N
306        }
307    }
308
309    @Override
310    public void setValueAt(Object value, int row, int col) {
311        if (value == null) {
312            log.debug("Warning schedule table row {} still in edit", row);
313            return;
314        }
315        switch (col) {
316            case CURRENT_COLUMN:
317                setCurrent(row);
318                break;
319            case RANDOM_COLUMN:
320                setRandom(value, row);
321                break;
322            case SETOUT_DAY_COLUMN:
323                setSetoutDay(value, row);
324                break;
325            case ROAD_COLUMN:
326                setRoad(value, row);
327                break;
328            case LOAD_COLUMN:
329                setLoad(value, row);
330                break;
331            case SHIP_COLUMN:
332                setShip(value, row);
333                break;
334            case DEST_COLUMN:
335                setDestination(value, row);
336                break;
337            case TRACK_COLUMN:
338                setTrack(value, row);
339                break;
340            case PICKUP_DAY_COLUMN:
341                setPickupDay(value, row);
342                break;
343            case COUNT_COLUMN:
344                setCount(value, row);
345                break;
346            case HIT_COLUMN:
347                setHit(value, row);
348                break;
349            case WAIT_COLUMN:
350                setWait(value, row);
351                break;
352            case UP_COLUMN:
353                moveUpScheduleItem(row);
354                break;
355            case DOWN_COLUMN:
356                moveDownScheduleItem(row);
357                break;
358            case DELETE_COLUMN:
359                deleteScheduleItem(row);
360                break;
361            default:
362                break;
363        }
364    }
365
366    @Override
367    protected Color getForegroundColor(int row) {
368        ScheduleItem si = _list.get(row);
369        if (!_schedule.checkScheduleItemValid(si, _track).equals(Schedule.SCHEDULE_OKAY)) {
370            return Color.red;
371        }
372        return super.getForegroundColor(row);
373    }
374
375    private String getCurrentPointer(ScheduleItem si) {
376        if (_track.getCurrentScheduleItem() == si) {
377            if (_track.getScheduleMode() == Track.SEQUENTIAL && si.getCount() > 1) {
378                return " " + _track.getScheduleCount() + POINTER; // NOI18N
379            } else {
380                return POINTER; // NOI18N
381            }
382        } else {
383            return "";
384        }
385    }
386
387    private String getType(ScheduleItem si) {
388        if (_track.isTypeNameAccepted(si.getTypeName())) {
389            return si.getTypeName();
390        } else {
391            return Bundle.getMessage("NotValid", si.getTypeName());
392        }
393    }
394
395    private JComboBox<String> getRoadComboBox(ScheduleItem si) {
396        // log.debug("getRoadComboBox for ScheduleItem "+si.getType());
397        JComboBox<String> cb = new JComboBox<>();
398        cb.addItem(ScheduleItem.NONE);
399        for (String roadName : InstanceManager.getDefault(CarRoads.class).getNames()) {
400            if (_track.isRoadNameAccepted(roadName) &&
401                    InstanceManager.getDefault(CarManager.class).getByTypeAndRoad(si.getTypeName(), roadName) != null) {
402                cb.addItem(roadName);
403            }
404        }
405        cb.setSelectedItem(si.getRoadName());
406        if (!cb.getSelectedItem().equals(si.getRoadName())) {
407            String notValid = Bundle.getMessage("NotValid", si.getRoadName());
408            cb.addItem(notValid);
409            cb.setSelectedItem(notValid);
410        }
411        return cb;
412    }
413
414    String[] randomValues = {ScheduleItem.NONE, "50", "30", "25", "20", "15", "10", "5", "2", "1"}; // NOI18N
415
416    protected JComboBox<String> getRandomComboBox(ScheduleItem si) {
417        JComboBox<String> cb = new JComboBox<>();
418        for (String item : randomValues) {
419            cb.addItem(item);
420        }
421        cb.setSelectedItem(si.getRandom());
422        return cb;
423    }
424
425    private JComboBox<TrainSchedule> getSetoutDayComboBox(ScheduleItem si) {
426        JComboBox<TrainSchedule> cb = InstanceManager.getDefault(TrainScheduleManager.class).getSelectComboBox();
427        TrainSchedule sch =
428                InstanceManager.getDefault(TrainScheduleManager.class).getScheduleById(si.getSetoutTrainScheduleId());
429        if (sch != null) {
430            cb.setSelectedItem(sch);
431        } else if (!si.getSetoutTrainScheduleId().equals(ScheduleItem.NONE)) {
432            // error user deleted this set out day
433            String notValid = Bundle.getMessage("NotValid", si.getSetoutTrainScheduleId());
434            TrainSchedule errorSchedule = new TrainSchedule(si.getSetoutTrainScheduleId(), notValid);
435            cb.addItem(errorSchedule);
436            cb.setSelectedItem(errorSchedule);
437        }
438        return cb;
439    }
440
441    private JComboBox<TrainSchedule> getPickupDayComboBox(ScheduleItem si) {
442        JComboBox<TrainSchedule> cb = InstanceManager.getDefault(TrainScheduleManager.class).getSelectComboBox();
443        TrainSchedule sch =
444                InstanceManager.getDefault(TrainScheduleManager.class).getScheduleById(si.getPickupTrainScheduleId());
445        if (sch != null) {
446            cb.setSelectedItem(sch);
447        } else if (!si.getPickupTrainScheduleId().equals(ScheduleItem.NONE)) {
448            // error user deleted this pick up day
449            String notValid = Bundle.getMessage("NotValid", si.getPickupTrainScheduleId());
450            TrainSchedule errorSchedule = new TrainSchedule(si.getSetoutTrainScheduleId(), notValid);
451            cb.addItem(errorSchedule);
452            cb.setSelectedItem(errorSchedule);
453        }
454        return cb;
455    }
456
457    protected JComboBox<String> getLoadComboBox(ScheduleItem si) {
458        // log.debug("getLoadComboBox for ScheduleItem "+si.getType());
459        JComboBox<String> cb = InstanceManager.getDefault(CarLoads.class).getSelectComboBox(si.getTypeName());
460        filterLoads(si, cb); // remove loads not accepted by this track
461        cb.setSelectedItem(si.getReceiveLoadName());
462        if (!cb.getSelectedItem().equals(si.getReceiveLoadName())) {
463            String notValid = Bundle.getMessage("NotValid", si.getReceiveLoadName());
464            cb.addItem(notValid);
465            cb.setSelectedItem(notValid);
466        }
467        return cb;
468    }
469
470    protected JComboBox<String> getShipComboBox(ScheduleItem si) {
471        // log.debug("getShipComboBox for ScheduleItem "+si.getType());
472        JComboBox<String> cb = InstanceManager.getDefault(CarLoads.class).getSelectComboBox(si.getTypeName());
473        // if load change disabled, return receive load name
474        if (_track.isDisableLoadChangeEnabled()) {
475            cb.setSelectedItem(si.getReceiveLoadName());
476        } else {
477            cb.setSelectedItem(si.getShipLoadName());
478            if (!cb.getSelectedItem().equals(si.getShipLoadName())) {
479                String notValid = MessageFormat
480                        .format(Bundle.getMessage("NotValid"), new Object[]{si.getShipLoadName()});
481                cb.addItem(notValid);
482                cb.setSelectedItem(notValid);
483            }
484        }
485        return cb;
486    }
487
488    protected JComboBox<Location> getDestComboBox(ScheduleItem si) {
489        // log.debug("getDestComboBox for ScheduleItem "+si.getType());
490        JComboBox<Location> cb = InstanceManager.getDefault(LocationManager.class).getComboBox();
491        filterDestinations(cb, si.getTypeName());
492        cb.setSelectedItem(si.getDestination());
493        if (si.getDestination() != null && cb.getSelectedIndex() == -1) {
494            // user deleted destination, this is self correcting, when user restarts program, destination
495            // assignment will be gone.
496            cb.addItem(si.getDestination());
497            cb.setSelectedItem(si.getDestination());
498        }
499        return cb;
500    }
501
502    protected JComboBox<Track> getTrackComboBox(ScheduleItem si) {
503        // log.debug("getTrackComboBox for ScheduleItem "+si.getType());
504        JComboBox<Track> cb = new JComboBox<>();
505        if (si.getDestination() != null) {
506            Location dest = si.getDestination();
507            dest.updateComboBox(cb);
508            filterTracks(dest, cb, si.getTypeName(), si.getRoadName(), si.getShipLoadName());
509            cb.setSelectedItem(si.getDestinationTrack());
510            if (si.getDestinationTrack() != null && cb.getSelectedIndex() == -1) {
511                // user deleted track at destination, this is self correcting, when user restarts program, track
512                // assignment will be gone.
513                cb.addItem(si.getDestinationTrack());
514                cb.setSelectedItem(si.getDestinationTrack());
515            }
516        }
517        return cb;
518    }
519    
520    private void setCurrent(int row) {
521        ScheduleItem si = _list.get(row);
522        _track.setScheduleItemId(si.getId());
523    }
524
525    // set the count or hits if in match mode
526    private void setCount(Object value, int row) {
527        ScheduleItem si = _list.get(row);
528        int count;
529        try {
530            count = Integer.parseInt(value.toString());
531        } catch (NumberFormatException e) {
532            log.error("Schedule count must be a number");
533            return;
534        }
535        if (count < 1) {
536            log.error("Schedule count must be greater than 0");
537            return;
538        }
539        if (count > 100) {
540            log.warn("Schedule count must be 100 or less");
541            count = 100;
542        }
543        si.setCount(count);
544    }
545
546    // set the count or hits if in match mode
547    private void setHit(Object value, int row) {
548        ScheduleItem si = _list.get(row);
549        int count;
550        try {
551            count = Integer.parseInt(value.toString());
552        } catch (NumberFormatException e) {
553            log.error("Schedule hits must be a number");
554            return;
555        }
556        // we don't care what value the user sets the hit count
557        si.setHits(count);
558    }
559
560    private void setWait(Object value, int row) {
561        ScheduleItem si = _list.get(row);
562        int wait;
563        try {
564            wait = Integer.parseInt(value.toString());
565        } catch (NumberFormatException e) {
566            log.error("Schedule wait must be a number");
567            return;
568        }
569        if (wait < 0) {
570            log.error("Schedule wait must be a positive number");
571            return;
572        }
573        if (wait > 100) {
574            log.warn("Schedule wait must be 100 or less");
575            wait = 100;
576        }
577        si.setWait(wait);
578    }
579
580    private void setRandom(Object value, int row) {
581        ScheduleItem si = _list.get(row);
582        String random = (String) ((JComboBox<?>) value).getSelectedItem();
583        si.setRandom(random);
584
585    }
586
587    private void setSetoutDay(Object value, int row) {
588        ScheduleItem si = _list.get(row);
589        Object obj = ((JComboBox<?>) value).getSelectedItem();
590        if (obj == null) {
591            si.setSetoutTrainScheduleId(ScheduleItem.NONE);
592        } else if (obj.getClass().equals(TrainSchedule.class)) {
593            si.setSetoutTrainScheduleId(((TrainSchedule) obj).getId());
594        }
595    }
596
597    private void setPickupDay(Object value, int row) {
598        ScheduleItem si = _list.get(row);
599        Object obj = ((JComboBox<?>) value).getSelectedItem();
600        if (obj == null) {
601            si.setPickupTrainScheduleId(ScheduleItem.NONE);
602        } else if (obj.getClass().equals(TrainSchedule.class)) {
603            si.setPickupTrainScheduleId(((TrainSchedule) obj).getId());
604        }
605    }
606
607    // note this method looks for String "Not Valid <>"
608    private void setRoad(Object value, int row) {
609        ScheduleItem si = _list.get(row);
610        String road = (String) ((JComboBox<?>) value).getSelectedItem();
611        if (checkForNotValidString(road)) {
612            si.setRoadName(road);
613        }
614    }
615
616    // note this method looks for String "Not Valid <>"
617    private void setLoad(Object value, int row) {
618        ScheduleItem si = _list.get(row);
619        String load = (String) ((JComboBox<?>) value).getSelectedItem();
620        if (checkForNotValidString(load)) {
621            si.setReceiveLoadName(load);
622        }
623    }
624
625    // note this method looks for String "Not Valid <>"
626    private void setShip(Object value, int row) {
627        ScheduleItem si = _list.get(row);
628        String load = (String) ((JComboBox<?>) value).getSelectedItem();
629        if (checkForNotValidString(load)) {
630            si.setShipLoadName(load);
631        }
632    }
633
634    /*
635     * Returns true if string is okay, doesn't have the string "Not Valid <>".
636     */
637    private boolean checkForNotValidString(String s) {
638        if (s.length() < 12) {
639            return true;
640        }
641        String test = s.substring(0, 11);
642        if (test.equals(Bundle.getMessage("NotValid").substring(0, 11))) {
643            return false;
644        }
645        return true;
646    }
647
648    private void setDestination(Object value, int row) {
649        ScheduleItem si = _list.get(row);
650        si.setDestinationTrack(null);
651        Location dest = (Location) ((JComboBox<?>) value).getSelectedItem();
652        si.setDestination(dest);
653        fireTableCellUpdated(row, TRACK_COLUMN);
654    }
655
656    private void setTrack(Object value, int row) {
657        ScheduleItem si = _list.get(row);
658        Track track = (Track) ((JComboBox<?>) value).getSelectedItem();
659        si.setDestinationTrack(track);
660    }
661
662    private void moveUpScheduleItem(int row) {
663        log.debug("move schedule item up");
664        _schedule.moveItemUp(_list.get(row));
665    }
666
667    private void moveDownScheduleItem(int row) {
668        log.debug("move schedule item down");
669        _schedule.moveItemDown(_list.get(row));
670    }
671
672    private void deleteScheduleItem(int row) {
673        log.debug("Delete schedule item");
674        _schedule.deleteItem(_list.get(row));
675    }
676
677    // remove destinations that don't service the car's type
678    private void filterDestinations(JComboBox<Location> cb, String carType) {
679        for (int i = 1; i < cb.getItemCount(); i++) {
680            Location dest = cb.getItemAt(i);
681            if (!dest.acceptsTypeName(carType)) {
682                cb.removeItem(dest);
683                i--;
684            }
685        }
686    }
687
688    // remove destination tracks that don't service the car's type, road, or load
689    private void filterTracks(Location loc, JComboBox<Track> cb, String carType, String carRoad, String carLoad) {
690        List<Track> tracks = loc.getTracksList();
691        for (Track track : tracks) {
692            if (!track.isTypeNameAccepted(carType) ||
693                    track.isStaging() ||
694                    (!carRoad.equals(ScheduleItem.NONE) && !track.isRoadNameAccepted(carRoad)) ||
695                    (!carLoad.equals(ScheduleItem.NONE) && !track.isLoadNameAndCarTypeAccepted(carLoad, carType))) {
696                cb.removeItem(track);
697            }
698        }
699    }
700
701    // remove receive loads not serviced by track
702    private void filterLoads(ScheduleItem si, JComboBox<String> cb) {
703        for (int i = cb.getItemCount() - 1; i > 0; i--) {
704            String loadName = cb.getItemAt(i);
705            if (!loadName.equals(CarLoads.NONE) && !_track.isLoadNameAndCarTypeAccepted(loadName, si.getTypeName())) {
706                cb.removeItem(loadName);
707            }
708        }
709    }
710
711    public void setMatchMode(boolean mode) {
712        if (mode != _matchMode) {
713            _matchMode = mode;
714            XTableColumnModel tcm = (XTableColumnModel) _table.getColumnModel();
715            tcm.setColumnVisible(tcm.getColumnByModelIndex(HIT_COLUMN), mode);
716            tcm.setColumnVisible(tcm.getColumnByModelIndex(COUNT_COLUMN), !mode);
717        }
718    }
719
720    // this table listens for changes to a schedule and its car types
721    @Override
722    public void propertyChange(PropertyChangeEvent e) {
723        if (Control.SHOW_PROPERTY) {
724            log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e
725                    .getNewValue());
726        }
727        if (e.getPropertyName().equals(Schedule.LISTCHANGE_CHANGED_PROPERTY)) {
728            updateList();
729            fireTableDataChanged();
730        }
731        if (e.getPropertyName().equals(Track.TYPES_CHANGED_PROPERTY) ||
732                e.getPropertyName().equals(Track.ROADS_CHANGED_PROPERTY) ||
733                e.getPropertyName().equals(Track.LOADS_CHANGED_PROPERTY) ||
734                e.getPropertyName().equals(Track.SCHEDULE_CHANGED_PROPERTY) ||
735                e.getPropertyName().equals(Location.TYPES_CHANGED_PROPERTY) ||
736                e.getPropertyName().equals(Location.DISPOSE_CHANGED_PROPERTY)) {
737            fireTableDataChanged();
738        }
739        // update hit count or other schedule item?
740        if (e.getSource().getClass().equals(ScheduleItem.class)) {
741            ScheduleItem item = (ScheduleItem) e.getSource();
742            int row = _list.indexOf(item);
743            if (Control.SHOW_PROPERTY) {
744                log.debug("Update schedule item table row: {}", row);
745            }
746            if (row >= 0) {
747                fireTableRowsUpdated(row, row);
748            }
749        }
750    }
751
752    private void removePropertyChangeScheduleItems() {
753        for (ScheduleItem si : _list) {
754            si.removePropertyChangeListener(this);
755            if (si.getDestination() != null) {
756                si.getDestination().removePropertyChangeListener(this);
757            }
758            if (si.getDestinationTrack() != null) {
759                si.getDestinationTrack().removePropertyChangeListener(this);
760            }
761        }
762    }
763
764    public void dispose() {
765        if (_schedule != null) {
766            removePropertyChangeScheduleItems();
767            _schedule.removePropertyChangeListener(this);
768        }
769        _location.removePropertyChangeListener(this);
770        _track.removePropertyChangeListener(this);
771
772    }
773
774    private final static Logger log = LoggerFactory.getLogger(ScheduleTableModel.class);
775}