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