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        cb.setSelectedItem(si.getShipLoadName());
474        if (!cb.getSelectedItem().equals(si.getShipLoadName())) {
475            String notValid = MessageFormat
476                    .format(Bundle.getMessage("NotValid"), new Object[]{si.getShipLoadName()});
477            cb.addItem(notValid);
478            cb.setSelectedItem(notValid);
479        }
480        return cb;
481    }
482
483    protected JComboBox<Location> getDestComboBox(ScheduleItem si) {
484        // log.debug("getDestComboBox for ScheduleItem "+si.getType());
485        JComboBox<Location> cb = InstanceManager.getDefault(LocationManager.class).getComboBox();
486        filterDestinations(cb, si.getTypeName());
487        cb.setSelectedItem(si.getDestination());
488        if (si.getDestination() != null && cb.getSelectedIndex() == -1) {
489            // user deleted destination, this is self correcting, when user restarts program, destination
490            // assignment will be gone.
491            cb.addItem(si.getDestination());
492            cb.setSelectedItem(si.getDestination());
493        }
494        return cb;
495    }
496
497    protected JComboBox<Track> getTrackComboBox(ScheduleItem si) {
498        // log.debug("getTrackComboBox for ScheduleItem "+si.getType());
499        JComboBox<Track> cb = new JComboBox<>();
500        if (si.getDestination() != null) {
501            Location dest = si.getDestination();
502            dest.updateComboBox(cb);
503            filterTracks(dest, cb, si.getTypeName(), si.getRoadName(), si.getShipLoadName());
504            cb.setSelectedItem(si.getDestinationTrack());
505            if (si.getDestinationTrack() != null && cb.getSelectedIndex() == -1) {
506                // user deleted track at destination, this is self correcting, when user restarts program, track
507                // assignment will be gone.
508                cb.addItem(si.getDestinationTrack());
509                cb.setSelectedItem(si.getDestinationTrack());
510            }
511        }
512        return cb;
513    }
514    
515    private void setCurrent(int row) {
516        ScheduleItem si = _list.get(row);
517        _track.setScheduleItemId(si.getId());
518    }
519
520    // set the count or hits if in match mode
521    private void setCount(Object value, int row) {
522        ScheduleItem si = _list.get(row);
523        int count;
524        try {
525            count = Integer.parseInt(value.toString());
526        } catch (NumberFormatException e) {
527            log.error("Schedule count must be a number");
528            return;
529        }
530        if (count < 1) {
531            log.error("Schedule count must be greater than 0");
532            return;
533        }
534        if (count > 100) {
535            log.warn("Schedule count must be 100 or less");
536            count = 100;
537        }
538        si.setCount(count);
539    }
540
541    // set the count or hits if in match mode
542    private void setHit(Object value, int row) {
543        ScheduleItem si = _list.get(row);
544        int count;
545        try {
546            count = Integer.parseInt(value.toString());
547        } catch (NumberFormatException e) {
548            log.error("Schedule hits must be a number");
549            return;
550        }
551        // we don't care what value the user sets the hit count
552        si.setHits(count);
553    }
554
555    private void setWait(Object value, int row) {
556        ScheduleItem si = _list.get(row);
557        int wait;
558        try {
559            wait = Integer.parseInt(value.toString());
560        } catch (NumberFormatException e) {
561            log.error("Schedule wait must be a number");
562            return;
563        }
564        if (wait < 0) {
565            log.error("Schedule wait must be a positive number");
566            return;
567        }
568        if (wait > 100) {
569            log.warn("Schedule wait must be 100 or less");
570            wait = 100;
571        }
572        si.setWait(wait);
573    }
574
575    private void setRandom(Object value, int row) {
576        ScheduleItem si = _list.get(row);
577        String random = (String) ((JComboBox<?>) value).getSelectedItem();
578        si.setRandom(random);
579
580    }
581
582    private void setSetoutDay(Object value, int row) {
583        ScheduleItem si = _list.get(row);
584        Object obj = ((JComboBox<?>) value).getSelectedItem();
585        if (obj == null) {
586            si.setSetoutTrainScheduleId(ScheduleItem.NONE);
587        } else if (obj.getClass().equals(TrainSchedule.class)) {
588            si.setSetoutTrainScheduleId(((TrainSchedule) obj).getId());
589        }
590    }
591
592    private void setPickupDay(Object value, int row) {
593        ScheduleItem si = _list.get(row);
594        Object obj = ((JComboBox<?>) value).getSelectedItem();
595        if (obj == null) {
596            si.setPickupTrainScheduleId(ScheduleItem.NONE);
597        } else if (obj.getClass().equals(TrainSchedule.class)) {
598            si.setPickupTrainScheduleId(((TrainSchedule) obj).getId());
599        }
600    }
601
602    // note this method looks for String "Not Valid <>"
603    private void setRoad(Object value, int row) {
604        ScheduleItem si = _list.get(row);
605        String road = (String) ((JComboBox<?>) value).getSelectedItem();
606        if (checkForNotValidString(road)) {
607            si.setRoadName(road);
608        }
609    }
610
611    // note this method looks for String "Not Valid <>"
612    private void setLoad(Object value, int row) {
613        ScheduleItem si = _list.get(row);
614        String load = (String) ((JComboBox<?>) value).getSelectedItem();
615        if (checkForNotValidString(load)) {
616            si.setReceiveLoadName(load);
617        }
618    }
619
620    // note this method looks for String "Not Valid <>"
621    private void setShip(Object value, int row) {
622        ScheduleItem si = _list.get(row);
623        String load = (String) ((JComboBox<?>) value).getSelectedItem();
624        if (checkForNotValidString(load)) {
625            si.setShipLoadName(load);
626        }
627    }
628
629    /*
630     * Returns true if string is okay, doesn't have the string "Not Valid <>".
631     */
632    private boolean checkForNotValidString(String s) {
633        if (s.length() < 12) {
634            return true;
635        }
636        String test = s.substring(0, 11);
637        if (test.equals(Bundle.getMessage("NotValid").substring(0, 11))) {
638            return false;
639        }
640        return true;
641    }
642
643    private void setDestination(Object value, int row) {
644        ScheduleItem si = _list.get(row);
645        si.setDestinationTrack(null);
646        Location dest = (Location) ((JComboBox<?>) value).getSelectedItem();
647        si.setDestination(dest);
648        fireTableCellUpdated(row, TRACK_COLUMN);
649    }
650
651    private void setTrack(Object value, int row) {
652        ScheduleItem si = _list.get(row);
653        Track track = (Track) ((JComboBox<?>) value).getSelectedItem();
654        si.setDestinationTrack(track);
655    }
656
657    private void moveUpScheduleItem(int row) {
658        log.debug("move schedule item up");
659        _schedule.moveItemUp(_list.get(row));
660    }
661
662    private void moveDownScheduleItem(int row) {
663        log.debug("move schedule item down");
664        _schedule.moveItemDown(_list.get(row));
665    }
666
667    private void deleteScheduleItem(int row) {
668        log.debug("Delete schedule item");
669        _schedule.deleteItem(_list.get(row));
670    }
671
672    // remove destinations that don't service the car's type
673    private void filterDestinations(JComboBox<Location> cb, String carType) {
674        for (int i = 1; i < cb.getItemCount(); i++) {
675            Location dest = cb.getItemAt(i);
676            if (!dest.acceptsTypeName(carType)) {
677                cb.removeItem(dest);
678                i--;
679            }
680        }
681    }
682
683    // remove destination tracks that don't service the car's type, road, or load
684    private void filterTracks(Location loc, JComboBox<Track> cb, String carType, String carRoad, String carLoad) {
685        List<Track> tracks = loc.getTracksList();
686        for (Track track : tracks) {
687            if (!track.isTypeNameAccepted(carType) ||
688                    track.isStaging() ||
689                    (!carRoad.equals(ScheduleItem.NONE) && !track.isRoadNameAccepted(carRoad)) ||
690                    (!carLoad.equals(ScheduleItem.NONE) && !track.isLoadNameAndCarTypeAccepted(carLoad, carType))) {
691                cb.removeItem(track);
692            }
693        }
694    }
695
696    // remove receive loads not serviced by track
697    private void filterLoads(ScheduleItem si, JComboBox<String> cb) {
698        for (int i = cb.getItemCount() - 1; i > 0; i--) {
699            String loadName = cb.getItemAt(i);
700            if (!loadName.equals(CarLoads.NONE) && !_track.isLoadNameAndCarTypeAccepted(loadName, si.getTypeName())) {
701                cb.removeItem(loadName);
702            }
703        }
704    }
705
706    public void setMatchMode(boolean mode) {
707        if (mode != _matchMode) {
708            _matchMode = mode;
709            XTableColumnModel tcm = (XTableColumnModel) _table.getColumnModel();
710            tcm.setColumnVisible(tcm.getColumnByModelIndex(HIT_COLUMN), mode);
711            tcm.setColumnVisible(tcm.getColumnByModelIndex(COUNT_COLUMN), !mode);
712        }
713    }
714
715    // this table listens for changes to a schedule and its car types
716    @Override
717    public void propertyChange(PropertyChangeEvent e) {
718        if (Control.SHOW_PROPERTY) {
719            log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e
720                    .getNewValue());
721        }
722        if (e.getPropertyName().equals(Schedule.LISTCHANGE_CHANGED_PROPERTY)) {
723            updateList();
724            fireTableDataChanged();
725        }
726        if (e.getPropertyName().equals(Track.TYPES_CHANGED_PROPERTY) ||
727                e.getPropertyName().equals(Track.ROADS_CHANGED_PROPERTY) ||
728                e.getPropertyName().equals(Track.LOADS_CHANGED_PROPERTY) ||
729                e.getPropertyName().equals(Track.SCHEDULE_CHANGED_PROPERTY) ||
730                e.getPropertyName().equals(Location.TYPES_CHANGED_PROPERTY) ||
731                e.getPropertyName().equals(Location.DISPOSE_CHANGED_PROPERTY)) {
732            fireTableDataChanged();
733        }
734        // update hit count or other schedule item?
735        if (e.getSource().getClass().equals(ScheduleItem.class)) {
736            ScheduleItem item = (ScheduleItem) e.getSource();
737            int row = _list.indexOf(item);
738            if (Control.SHOW_PROPERTY) {
739                log.debug("Update schedule item table row: {}", row);
740            }
741            if (row >= 0) {
742                fireTableRowsUpdated(row, row);
743            }
744        }
745    }
746
747    private void removePropertyChangeScheduleItems() {
748        for (ScheduleItem si : _list) {
749            si.removePropertyChangeListener(this);
750            if (si.getDestination() != null) {
751                si.getDestination().removePropertyChangeListener(this);
752            }
753            if (si.getDestinationTrack() != null) {
754                si.getDestinationTrack().removePropertyChangeListener(this);
755            }
756        }
757    }
758
759    public void dispose() {
760        if (_schedule != null) {
761            removePropertyChangeScheduleItems();
762            _schedule.removePropertyChangeListener(this);
763        }
764        _location.removePropertyChangeListener(this);
765        _track.removePropertyChangeListener(this);
766
767    }
768
769    private final static Logger log = LoggerFactory.getLogger(ScheduleTableModel.class);
770}