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