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