001package jmri.jmrit.operations.locations.gui;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.util.*;
006
007import javax.swing.*;
008import javax.swing.event.*;
009import javax.swing.table.TableColumn;
010
011import org.slf4j.Logger;
012import org.slf4j.LoggerFactory;
013
014import jmri.InstanceManager;
015import jmri.jmrit.operations.OperationsPanel;
016import jmri.jmrit.operations.OperationsTableModel;
017import jmri.jmrit.operations.locations.*;
018import jmri.jmrit.operations.locations.tools.TrackEditCommentsFrame;
019import jmri.jmrit.operations.setup.Control;
020import jmri.jmrit.operations.setup.Setup;
021import jmri.util.swing.XTableColumnModel;
022import jmri.util.table.ButtonEditor;
023import jmri.util.table.ButtonRenderer;
024
025/**
026 * Table Model for edit of tracks used by operations
027 *
028 * @author Daniel Boudreau Copyright (C) 2008, 2011, 2012, 2025
029 */
030public abstract class TrackTableModel extends OperationsTableModel implements PropertyChangeListener, TableColumnModelListener {
031
032    protected Location _location;
033    protected List<Track> _tracksList = new ArrayList<>();
034    protected String _trackType;
035    protected boolean _dirty = false;
036
037    // Defines the columns
038    protected static final int ID_COLUMN = 0;
039    protected static final int NAME_COLUMN = 1;
040    protected static final int LENGTH_COLUMN = 2;
041    protected static final int USED_LENGTH_COLUMN = 3;
042    protected static final int RESERVED_COLUMN = 4;
043    protected static final int MOVES_COLUMN = 5;
044    protected static final int CARS_COLUMN = 6;
045    protected static final int LOCOS_COLUMN = 7;
046    protected static final int PICKUPS_COLUMN = 8;
047    protected static final int SETOUT_COLUMN = 9;
048    protected static final int SCHEDULE_COLUMN = 10;
049    protected static final int ROAD_COLUMN = 11;
050    protected static final int LOAD_COLUMN = 12;
051    protected static final int DEFAULT_LOAD_COLUMN = 13;
052    protected static final int CUSTOM_LOAD_COLUMN = 14;
053    protected static final int DISABLE_LOAD_CHANGE_COLUMN = 15;
054    protected static final int QUICK_SERVICE_COLUMN = 16;
055    protected static final int SHIP_COLUMN = 17;
056    protected static final int RESTRICTION_COLUMN = 18;
057    protected static final int DESTINATION_COLUMN = 19;
058    protected static final int ROUTED_COLUMN = 20;
059    protected static final int HOLD_COLUMN = 21;
060    protected static final int POOL_COLUMN = 22;
061    protected static final int PLANPICKUP_COLUMN = 23;
062    protected static final int ALT_TRACK_COLUMN = 24;
063    protected static final int TRACK_PRIORITY_COLUMN = 25;
064    protected static final int ORDER_COLUMN = 26;
065    protected static final int TRAIN_DIRECTION_COLUMN = 27;
066    protected static final int REPORTER_COLUMN = 28;
067    protected static final int COMMENT_COLUMN = 29;
068    protected static final int EDIT_COLUMN = 30;
069
070    protected static final int HIGHESTCOLUMN = EDIT_COLUMN + 1;
071
072    public TrackTableModel() {
073        super();
074    }
075
076    protected void initTable(JTable table, Location location, String trackType) {
077        _table = table;
078        _location = location;
079        _trackType = trackType;
080        if (_location != null) {
081            _location.addPropertyChangeListener(this);
082        }
083        Setup.getDefault().addPropertyChangeListener(this);
084        updateList();
085        initTable();
086    }
087
088    private void updateList() {
089        if (_location == null) {
090            return;
091        }
092        // first, remove listeners from the individual objects
093        removePropertyChangeTracks();
094
095        _tracksList = _location.getTracksByNameList(_trackType);
096        // and add them back in
097        for (Track track : _tracksList) {
098            track.addPropertyChangeListener(this);
099        }
100    }
101
102    private void initTable() {
103        // Use XTableColumnModel so we can control which columns are visible
104        XTableColumnModel tcm = new XTableColumnModel();
105        _table.setColumnModel(tcm);
106        _table.createDefaultColumnsFromModel();
107
108        // set column preferred widths
109        tcm.getColumn(ID_COLUMN).setPreferredWidth(40);
110        tcm.getColumn(NAME_COLUMN).setPreferredWidth(200);
111        tcm.getColumn(LENGTH_COLUMN).setPreferredWidth(
112                Math.max(50, new JLabel(getColumnName(LENGTH_COLUMN)).getPreferredSize().width + 10));
113        tcm.getColumn(USED_LENGTH_COLUMN).setPreferredWidth(50);
114        tcm.getColumn(RESERVED_COLUMN).setPreferredWidth(
115                Math.max(65, new JLabel(getColumnName(RESERVED_COLUMN)).getPreferredSize().width + 10));
116        tcm.getColumn(MOVES_COLUMN).setPreferredWidth(60);
117        tcm.getColumn(LOCOS_COLUMN).setPreferredWidth(60);
118        tcm.getColumn(CARS_COLUMN).setPreferredWidth(60);
119        tcm.getColumn(PICKUPS_COLUMN).setPreferredWidth(
120                Math.max(60, new JLabel(getColumnName(PICKUPS_COLUMN)).getPreferredSize().width + 10));
121        tcm.getColumn(SETOUT_COLUMN).setPreferredWidth(
122                Math.max(60, new JLabel(getColumnName(SETOUT_COLUMN)).getPreferredSize().width + 10));
123        tcm.getColumn(SCHEDULE_COLUMN).setPreferredWidth(
124                Math.max(90, new JLabel(getColumnName(SCHEDULE_COLUMN)).getPreferredSize().width + 10));
125        tcm.getColumn(RESTRICTION_COLUMN).setPreferredWidth(90);
126        tcm.getColumn(LOAD_COLUMN).setPreferredWidth(50);
127        tcm.getColumn(DEFAULT_LOAD_COLUMN).setPreferredWidth(60);
128        tcm.getColumn(CUSTOM_LOAD_COLUMN).setPreferredWidth(90);
129        tcm.getColumn(DISABLE_LOAD_CHANGE_COLUMN).setPreferredWidth(50);
130        tcm.getColumn(QUICK_SERVICE_COLUMN).setPreferredWidth(50);
131        tcm.getColumn(SHIP_COLUMN).setPreferredWidth(50);
132        tcm.getColumn(ROAD_COLUMN).setPreferredWidth(50);
133        tcm.getColumn(DESTINATION_COLUMN).setPreferredWidth(50);
134        tcm.getColumn(ROUTED_COLUMN).setPreferredWidth(50);
135        tcm.getColumn(HOLD_COLUMN).setPreferredWidth(50);
136        tcm.getColumn(POOL_COLUMN).setPreferredWidth(70);
137        tcm.getColumn(PLANPICKUP_COLUMN).setPreferredWidth(70);
138        tcm.getColumn(ALT_TRACK_COLUMN).setPreferredWidth(120);
139        tcm.getColumn(TRACK_PRIORITY_COLUMN).setPreferredWidth(50);
140        tcm.getColumn(ORDER_COLUMN)
141                .setPreferredWidth(Math.max(50, new JLabel(getColumnName(ORDER_COLUMN)).getPreferredSize().width + 10));
142        tcm.getColumn(TRAIN_DIRECTION_COLUMN).setPreferredWidth(30);
143        tcm.getColumn(REPORTER_COLUMN).setPreferredWidth(70);
144        tcm.getColumn(COMMENT_COLUMN).setPreferredWidth(80);
145        tcm.getColumn(EDIT_COLUMN).setPreferredWidth(80);
146
147        tcm.getColumn(COMMENT_COLUMN).setCellRenderer(new ButtonRenderer());
148        tcm.getColumn(COMMENT_COLUMN).setCellEditor(new ButtonEditor(new JButton()));
149        tcm.getColumn(EDIT_COLUMN).setCellRenderer(new ButtonRenderer());
150        tcm.getColumn(EDIT_COLUMN).setCellEditor(new ButtonEditor(new JButton()));
151
152        OperationsPanel.loadTableDetails(_table, "Location");
153        setColumnsVisible();
154        addTableColumnListeners();
155    }
156
157    // only show columns if they are needed
158    private void setColumnsVisible() {
159        XTableColumnModel tcm = (XTableColumnModel) _table.getColumnModel();
160        tcm.setColumnVisible(tcm.getColumnByModelIndex(ID_COLUMN),
161                InstanceManager.getDefault(LocationManager.class).isShowIdEnabled());
162        tcm.setColumnVisible(tcm.getColumnByModelIndex(SCHEDULE_COLUMN),
163                _location.hasSchedules() && _trackType.equals(Track.SPUR));
164        tcm.setColumnVisible(tcm.getColumnByModelIndex(RESTRICTION_COLUMN),
165                _location.hasServiceRestrictions() && !_trackType.equals(Track.YARD));
166        tcm.setColumnVisible(tcm.getColumnByModelIndex(LOAD_COLUMN), _location.hasLoadRestrictions());
167        tcm.setColumnVisible(tcm.getColumnByModelIndex(DEFAULT_LOAD_COLUMN), _trackType.equals(Track.STAGING));
168        tcm.setColumnVisible(tcm.getColumnByModelIndex(CUSTOM_LOAD_COLUMN), _trackType.equals(Track.STAGING));
169        tcm.setColumnVisible(tcm.getColumnByModelIndex(DISABLE_LOAD_CHANGE_COLUMN),
170                _location.hasDisableLoadChange() && _trackType.equals(Track.SPUR));
171        tcm.setColumnVisible(tcm.getColumnByModelIndex(QUICK_SERVICE_COLUMN),
172                _location.hasQuickService() && (_trackType.equals(Track.SPUR) || _trackType.equals(Track.INTERCHANGE)));
173        tcm.setColumnVisible(tcm.getColumnByModelIndex(SHIP_COLUMN), _location.hasShipLoadRestrictions());
174        tcm.setColumnVisible(tcm.getColumnByModelIndex(ROAD_COLUMN), _location.hasRoadRestrictions());
175        tcm.setColumnVisible(tcm.getColumnByModelIndex(DESTINATION_COLUMN), _location.hasDestinationRestrictions() &&
176                (_trackType.equals(Track.INTERCHANGE) || _trackType.equals(Track.STAGING)));
177        tcm.setColumnVisible(tcm.getColumnByModelIndex(ROUTED_COLUMN), _trackType.equals(Track.INTERCHANGE) ||
178                _trackType.equals(Track.STAGING));
179        tcm.setColumnVisible(tcm.getColumnByModelIndex(HOLD_COLUMN),
180                _location.hasSchedules() && _location.hasAlternateTracks() && _trackType.equals(Track.SPUR));
181        tcm.setColumnVisible(tcm.getColumnByModelIndex(PLANPICKUP_COLUMN), _location.hasPlannedPickups());
182        tcm.setColumnVisible(tcm.getColumnByModelIndex(POOL_COLUMN), _location.hasPools());
183        tcm.setColumnVisible(tcm.getColumnByModelIndex(ALT_TRACK_COLUMN), _location.hasAlternateTracks());
184        tcm.setColumnVisible(tcm.getColumnByModelIndex(TRACK_PRIORITY_COLUMN), _location.hasTrackPriortyChanges());
185        tcm.setColumnVisible(tcm.getColumnByModelIndex(ORDER_COLUMN),
186                _location.hasOrderRestrictions() && !_trackType.equals(Track.SPUR));
187        tcm.setColumnVisible(tcm.getColumnByModelIndex(TRAIN_DIRECTION_COLUMN),
188                _location.hasTracksWithRestrictedTrainDirections());
189        tcm.setColumnVisible(tcm.getColumnByModelIndex(REPORTER_COLUMN),
190                Setup.isRfidEnabled() && _location.hasReporters());
191        tcm.setColumnVisible(tcm.getColumnByModelIndex(MOVES_COLUMN), Setup.isShowTrackMovesEnabled());
192        tcm.setColumnVisible(tcm.getColumnByModelIndex(COMMENT_COLUMN), _location.hasTrackMessages());
193    }
194
195    /*
196     * Persisting using JmriJTablePersistenceManager doesn't quite work since
197     * the same table name is used for each track type; spur, yard, interchange,
198     * and staging. Plus multiple edit locations can be open at the same time,
199     * again using the same table name. The goal is to have a single change
200     * affect every table for all edit locations. Therefore any changes to
201     * column width or position is saved when the edit location window is
202     * closed.
203     */
204    private void addTableColumnListeners() {
205        Enumeration<TableColumn> e = _table.getColumnModel().getColumns();
206        while (e.hasMoreElements()) {
207            TableColumn column = e.nextElement();
208            column.addPropertyChangeListener(this);
209        }
210        _table.getColumnModel().addColumnModelListener(this);
211    }
212
213    /**
214     * Table is dirty when a column is moved or width is changed.
215     * 
216     * @param dirty set true if dirty
217     */
218    private void setDirty(boolean dirty) {
219        _dirty = dirty;
220    }
221
222    private boolean isDirty() {
223        return _dirty;
224    }
225
226    @Override
227    public int getRowCount() {
228        return _tracksList.size();
229    }
230
231    @Override
232    public int getColumnCount() {
233        return HIGHESTCOLUMN;
234    }
235
236    @Override
237    public String getColumnName(int col) {
238        switch (col) {
239            case ID_COLUMN:
240                return Bundle.getMessage("Id");
241            case NAME_COLUMN:
242                return Bundle.getMessage("TrackName");
243            case LENGTH_COLUMN:
244                return Bundle.getMessage("Length");
245            case USED_LENGTH_COLUMN:
246                return Bundle.getMessage("Used");
247            case RESERVED_COLUMN:
248                return Bundle.getMessage("Reserved");
249            case MOVES_COLUMN:
250                return Bundle.getMessage("Moves");
251            case LOCOS_COLUMN:
252                return Bundle.getMessage("Engines");
253            case CARS_COLUMN:
254                return Bundle.getMessage("Cars");
255            case PICKUPS_COLUMN:
256                return Bundle.getMessage("Pickups");
257            case SETOUT_COLUMN:
258                return Bundle.getMessage("Drop");
259            case SCHEDULE_COLUMN:
260                return Bundle.getMessage("Schedule");
261            case RESTRICTION_COLUMN:
262                return Bundle.getMessage("Restrictions");
263            case LOAD_COLUMN:
264                return Bundle.getMessage("Load");
265            case DEFAULT_LOAD_COLUMN:
266                return Bundle.getMessage("LoadDefaultAbv");
267            case CUSTOM_LOAD_COLUMN:
268                return Bundle.getMessage("LoadCustomAbv");
269            case DISABLE_LOAD_CHANGE_COLUMN:
270                return Bundle.getMessage("DisableLoadChange");
271            case QUICK_SERVICE_COLUMN:
272                return Bundle.getMessage("QuickService");
273            case SHIP_COLUMN:
274                return Bundle.getMessage("Ship");
275            case ROAD_COLUMN:
276                return Bundle.getMessage("Road");
277            case DESTINATION_COLUMN:
278                return Bundle.getMessage("Dest");
279            case ROUTED_COLUMN:
280                return Bundle.getMessage("Routed");
281            case HOLD_COLUMN:
282                return Bundle.getMessage("Hold");
283            case POOL_COLUMN:
284                return Bundle.getMessage("Pool");
285            case PLANPICKUP_COLUMN:
286                return Bundle.getMessage("PlanPickUp");
287            case ALT_TRACK_COLUMN:
288                return Bundle.getMessage("AlternateTrack");
289            case TRACK_PRIORITY_COLUMN:
290                return Bundle.getMessage("Priority");
291            case ORDER_COLUMN:
292                return Bundle.getMessage("ServiceOrder");
293            case TRAIN_DIRECTION_COLUMN:
294                return Bundle.getMessage("AbbrevationDirection");
295            case REPORTER_COLUMN:
296                return Bundle.getMessage("Reporters");
297            case COMMENT_COLUMN:
298                return Bundle.getMessage("Comment");
299            case EDIT_COLUMN:
300                return Bundle.getMessage("ButtonEdit");
301            default:
302                return "unknown"; // NOI18N
303        }
304    }
305
306    @Override
307    public Class<?> getColumnClass(int col) {
308        switch (col) {
309            case ID_COLUMN:
310            case NAME_COLUMN:
311            case SCHEDULE_COLUMN:
312            case RESTRICTION_COLUMN:
313            case LOAD_COLUMN:
314            case DEFAULT_LOAD_COLUMN:
315            case CUSTOM_LOAD_COLUMN:
316            case SHIP_COLUMN:
317            case ROAD_COLUMN:
318            case DESTINATION_COLUMN:
319            case POOL_COLUMN:
320            case PLANPICKUP_COLUMN:
321            case ALT_TRACK_COLUMN:
322            case TRACK_PRIORITY_COLUMN:
323            case ORDER_COLUMN:
324            case TRAIN_DIRECTION_COLUMN:
325            case REPORTER_COLUMN:
326                return String.class;
327            case LENGTH_COLUMN:
328            case USED_LENGTH_COLUMN:
329            case RESERVED_COLUMN:
330            case MOVES_COLUMN:
331            case LOCOS_COLUMN:
332            case CARS_COLUMN:
333            case PICKUPS_COLUMN:
334            case SETOUT_COLUMN:
335                return Integer.class;
336            case COMMENT_COLUMN:
337            case EDIT_COLUMN:
338                return JButton.class;
339            case DISABLE_LOAD_CHANGE_COLUMN:
340            case QUICK_SERVICE_COLUMN:
341            case ROUTED_COLUMN:
342            case HOLD_COLUMN:
343                return Boolean.class;
344            default:
345                return null;
346        }
347    }
348
349    @Override
350    public boolean isCellEditable(int row, int col) {
351        switch (col) {
352            case MOVES_COLUMN:
353            case DISABLE_LOAD_CHANGE_COLUMN:
354            case QUICK_SERVICE_COLUMN:
355            case ROUTED_COLUMN:
356            case COMMENT_COLUMN:
357            case EDIT_COLUMN:
358                return true;
359            case HOLD_COLUMN:
360                return isHoldCarEnabled(row);
361            default:
362                return false;
363        }
364    }
365
366    private boolean isHoldCarEnabled(int row) {
367        Track track = _tracksList.get(row);
368        return track.getAlternateTrack() != null && track.getSchedule() != null;
369    }
370
371    @Override
372    public Object getValueAt(int row, int col) {
373        if (row >= getRowCount()) {
374            return "ERROR row " + row; // NOI18N
375        }
376        Track track = _tracksList.get(row);
377        if (track == null) {
378            return "ERROR track unknown " + row; // NOI18N
379        }
380        switch (col) {
381            case ID_COLUMN:
382                return track.getId();
383            case NAME_COLUMN:
384                return track.getName();
385            case LENGTH_COLUMN:
386                return track.getLength();
387            case USED_LENGTH_COLUMN:
388                return track.getUsedLength();
389            case RESERVED_COLUMN:
390                return track.getReserved();
391            case MOVES_COLUMN:
392                return track.getMoves();
393            case LOCOS_COLUMN:
394                return track.getNumberEngines();
395            case CARS_COLUMN:
396                return track.getNumberCars();
397            case PICKUPS_COLUMN:
398                return track.getPickupRS();
399            case SETOUT_COLUMN:
400                return track.getDropRS();
401            case SCHEDULE_COLUMN:
402                return track.getScheduleName();
403            case RESTRICTION_COLUMN:
404                return getRestrictions(track);
405            case LOAD_COLUMN:
406                return getModifiedString(track.getLoadNames().length, track.getLoadOption().equals(Track.ALL_LOADS),
407                        track.getLoadOption().equals(Track.INCLUDE_LOADS));
408            case DEFAULT_LOAD_COLUMN:
409                return getDefaultLoadString(track);
410            case CUSTOM_LOAD_COLUMN:
411                return getCustomLoadString(track);
412            case DISABLE_LOAD_CHANGE_COLUMN:
413                return track.isDisableLoadChangeEnabled();
414            case QUICK_SERVICE_COLUMN:
415                return track.isQuickServiceEnabled();
416            case SHIP_COLUMN:
417                return getModifiedString(track.getShipLoadNames().length,
418                        track.getShipLoadOption().equals(Track.ALL_LOADS),
419                        track.getShipLoadOption().equals(Track.INCLUDE_LOADS));
420            case ROAD_COLUMN:
421                return getModifiedString(track.getRoadNames().length, track.getRoadOption().equals(Track.ALL_ROADS),
422                        track.getRoadOption().equals(Track.INCLUDE_ROADS));
423            case DESTINATION_COLUMN: {
424                return getDestinationString(track);
425            }
426            case ROUTED_COLUMN:
427                return track.isOnlyCarsWithFinalDestinationEnabled();
428            case HOLD_COLUMN:
429                return track.isHoldCarsWithCustomLoadsEnabled();
430            case POOL_COLUMN:
431                return track.getPoolName();
432            case PLANPICKUP_COLUMN:
433                if (track.getIgnoreUsedLengthPercentage() > Track.IGNORE_0) {
434                    return track.getIgnoreUsedLengthPercentage() + "%";
435                }
436                return "";
437            case ALT_TRACK_COLUMN:
438                if (track.getAlternateTrack() != null) {
439                    return track.getAlternateTrack().getName();
440                }
441                if (track.isAlternate()) {
442                    return Bundle.getMessage("ButtonYes");
443                }
444                return "";
445            case TRACK_PRIORITY_COLUMN:
446                return track.getTrackPriority();
447            case ORDER_COLUMN:
448                return track.getServiceOrder();
449            case TRAIN_DIRECTION_COLUMN:
450                return getDirection(track);
451            case REPORTER_COLUMN:
452                return track.getReporterName();
453            case COMMENT_COLUMN:
454                if (track.hasMessages()) {
455                    return Bundle.getMessage("ButtonEdit");
456                }
457                return Bundle.getMessage("Add");
458            case EDIT_COLUMN:
459                return Bundle.getMessage("ButtonEdit");
460            default:
461                return "unknown " + col; // NOI18N
462        }
463    }
464
465    private String getRestrictions(Track track) {
466        StringBuffer restrictions = new StringBuffer();
467        if (!track.getDropOption().equals(Track.ANY)) {
468            String suffix = " ";
469            if (track.getDropOption().equals(Track.EXCLUDE_ROUTES) ||
470                    track.getDropOption().equals(Track.EXCLUDE_TRAINS)) {
471                suffix = "x";
472            }
473            restrictions.append(Bundle.getMessage("AbbreviationSetOut") + suffix + track.getDropIds().length);
474        }
475        if (!track.getPickupOption().equals(Track.ANY)) {
476            String suffix = " ";
477            if (track.getPickupOption().equals(Track.EXCLUDE_ROUTES) ||
478                    track.getPickupOption().equals(Track.EXCLUDE_TRAINS)) {
479                suffix = "x";
480            }
481            restrictions.append(" " + Bundle.getMessage("AbbreviationPickUp") + suffix + track.getPickupIds().length);
482        }
483        return restrictions.toString();
484    }
485
486    private String getModifiedString(int number, boolean all, boolean accept) {
487        if (all) {
488            return "";
489        }
490        if (accept) {
491            return "A " + Integer.toString(number); // NOI18N
492        }
493        return "E " + Integer.toString(number); // NOI18N
494    }
495    
496    private String getDefaultLoadString(Track track) {
497        String defaultLoad = "";
498        if (track.isLoadSwapEnabled()) {
499            defaultLoad = Bundle.getMessage("ABV_SwapDefaultLoads");
500        }
501        if (track.isLoadEmptyEnabled()) {
502            defaultLoad = Bundle.getMessage("ABV_EmptyDefaultLoads");
503        }
504        return defaultLoad;
505    }
506    
507    private String getCustomLoadString(Track track) {
508        StringBuffer customLoad = new StringBuffer();
509        if (track.isRemoveCustomLoadsEnabled()) {
510            customLoad.append(Bundle.getMessage("ABV_EmptyCustomLoads") + " ");
511        }
512        if (track.isAddCustomLoadsEnabled()) {
513            customLoad.append(Bundle.getMessage("ABV_GenerateCustomLoad") + " ");
514        }
515        if (track.isAddCustomLoadsAnySpurEnabled()) {
516            customLoad.append(Bundle.getMessage("ABV_GenerateCustomLoadAnySpur") + " ");
517        }
518        if (track.isAddCustomLoadsAnyStagingTrackEnabled()) {
519            customLoad.append(Bundle.getMessage("ABV_GereateCustomLoadStaging"));
520        }
521        if (track.isBlockCarsEnabled()) {
522            customLoad.append(Bundle.getMessage("ABV_CarBlocking"));
523        }
524        return customLoad.toString();
525    }
526
527    private String getDestinationString(Track track) {
528        int size = track.getDestinationListSize();
529        if (track.getDestinationOption().equals(Track.EXCLUDE_DESTINATIONS)) {
530            size = InstanceManager.getDefault(LocationManager.class).getNumberOfLocations() - size;
531        } else if (size == 1 && track.getDestinationOption().equals(Track.INCLUDE_DESTINATIONS)) {
532            // if there's only one destination return the destination name
533            Location loc =
534                    InstanceManager.getDefault(LocationManager.class).getLocationById(track.getDestinationIds()[0]);
535            if (loc != null) {
536                return loc.getName();
537            }
538        }
539        return getModifiedString(size, track.getDestinationOption().equals(Track.ALL_DESTINATIONS),
540                track.getDestinationOption().equals(Track.INCLUDE_DESTINATIONS));
541    }
542
543    private String getDirection(Track track) {
544        int trainDirections = track.getLocation().getTrainDirections() & Setup.getTrainDirection();
545        if (trainDirections != (track.getTrainDirections() & trainDirections)) {
546            switch (track.getTrainDirections() & trainDirections) {
547                case Track.EAST:
548                    return Setup.EAST_DIR;
549                case Track.WEST:
550                    return Setup.WEST_DIR;
551                case Track.SOUTH:
552                    return Setup.SOUTH_DIR;
553                case Track.NORTH:
554                    return Setup.NORTH_DIR;
555                case 0:
556                    return Bundle.getMessage("None");
557                default:
558                    return "X";
559            }
560        }
561        return "";
562    }
563
564    @Override
565    public void setValueAt(Object value, int row, int col) {
566        switch (col) {
567            case DISABLE_LOAD_CHANGE_COLUMN:
568                setDisableLoadChange(row, value);
569                break;
570            case QUICK_SERVICE_COLUMN:
571                setQuickService(row, value);
572                break;
573            case ROUTED_COLUMN:
574                setRouted(row, value);
575                break;
576            case HOLD_COLUMN:
577                setHold(row, value);
578                break;
579            case COMMENT_COLUMN:
580                trackComment(row);
581                break;
582            case EDIT_COLUMN:
583                editTrack(row);
584                break;
585            case MOVES_COLUMN:
586                setMoves(row, value);
587                break;
588            default:
589                break;
590        }
591    }
592
593    TrackEditFrame tef = null;
594
595    abstract protected void editTrack(int row);
596
597    private void setDisableLoadChange(int row, Object value) {
598        Track track = _tracksList.get(row);
599        track.setDisableLoadChangeEnabled(((Boolean) value).booleanValue());
600    }
601
602    private void setQuickService(int row, Object value) {
603        Track track = _tracksList.get(row);
604        track.setQuickServiceEnabled(((Boolean) value).booleanValue());
605    }
606
607    private void setRouted(int row, Object value) {
608        Track track = _tracksList.get(row);
609        track.setOnlyCarsWithFinalDestinationEnabled(((Boolean) value).booleanValue());
610    }
611
612    private void setHold(int row, Object value) {
613        Track track = _tracksList.get(row);
614        track.setHoldCarsWithCustomLoadsEnabled(((Boolean) value).booleanValue());
615    }
616
617    private void setMoves(int row, Object value) {
618        Track track = _tracksList.get(row);
619        track.setMoves((int) value);
620    }
621
622    private void trackComment(int row) {
623        new TrackEditCommentsFrame(_tracksList.get(row));
624    }
625
626    private void removePropertyChangeTracks() {
627        for (Track track : _tracksList) {
628            track.removePropertyChangeListener(this);
629        }
630    }
631
632    public void dispose() {
633        if (_table != null) {
634            _table.getRowSorter().setSortKeys(null);
635            if (isDirty()) {
636                OperationsPanel.cacheState(_table);
637                OperationsPanel.saveTableState();
638                setDirty(false);
639            }
640        }
641        removePropertyChangeTracks();
642        if (_location != null) {
643            _location.removePropertyChangeListener(this);
644        }
645        if (tef != null) {
646            tef.dispose();
647        }
648        Setup.getDefault().removePropertyChangeListener(this);
649        _tracksList.clear();
650        fireTableDataChanged();
651    }
652
653    // this table listens for changes to a location and it's tracks
654    @Override
655    public void propertyChange(PropertyChangeEvent e) {
656        if (Control.SHOW_PROPERTY) {
657            log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(),
658                    e.getNewValue());
659        }
660        if (e.getPropertyName().equals(Location.TRACK_LISTLENGTH_CHANGED_PROPERTY) ||
661                e.getPropertyName().equals(Location.TRAIN_DIRECTION_CHANGED_PROPERTY)) {
662            updateList();
663            fireTableDataChanged();
664        }
665        if (e.getPropertyName().equals(Setup.SHOW_TRACK_MOVES_PROPERTY_CHANGE) ||
666                e.getPropertyName().equals(Location.TRAIN_DIRECTION_CHANGED_PROPERTY) ||
667                e.getPropertyName().equals(Setup.ROUTING_STAGING_PROPERTY_CHANGE)) {
668            setColumnsVisible();
669        }
670        if (e.getSource().getClass().equals(Track.class) &&
671                (e.getPropertyName().equals(Track.DROP_CHANGED_PROPERTY) ||
672                        e.getPropertyName().equals(Track.PICKUP_CHANGED_PROPERTY) ||
673                        e.getPropertyName().equals(Track.SCHEDULE_ID_CHANGED_PROPERTY) ||
674                        e.getPropertyName().equals(Track.LOADS_CHANGED_PROPERTY) ||
675                        e.getPropertyName().equals(Track.ROADS_CHANGED_PROPERTY) ||
676                        e.getPropertyName().equals(Track.DESTINATION_OPTIONS_CHANGED_PROPERTY) ||
677                        e.getPropertyName().equals(Track.POOL_CHANGED_PROPERTY) ||
678                        e.getPropertyName().equals(Track.PLANNED_PICKUPS_CHANGED_PROPERTY) ||
679                        e.getPropertyName().equals(Track.ALTERNATE_TRACK_CHANGED_PROPERTY) ||
680                        e.getPropertyName().equals(Track.SERVICE_ORDER_CHANGED_PROPERTY) ||
681                        e.getPropertyName().equals(Track.LOAD_OPTIONS_CHANGED_PROPERTY) ||
682                        e.getPropertyName().equals(Track.TRAIN_DIRECTION_CHANGED_PROPERTY) ||
683                        e.getPropertyName().equals(Track.TRACK_COMMENT_CHANGED_PROPERTY) ||
684                        e.getPropertyName().equals(Track.TRACK_REPORTER_CHANGED_PROPERTY) ||
685                        e.getPropertyName().equals(Track.PRIORITY_CHANGED_PROPERTY))) {
686            setColumnsVisible();
687        }
688        if (e.getSource() instanceof TableColumn && e.getPropertyName().equals("preferredWidth")) {
689            log.debug("Column width change");
690            setDirty(true);
691        }
692    }
693
694    @Override
695    public void columnAdded(TableColumnModelEvent e) {
696        // do nothing
697    }
698
699    @Override
700    public void columnRemoved(TableColumnModelEvent e) {
701        // do nothing
702    }
703
704    @Override
705    public void columnMoved(TableColumnModelEvent e) {
706        if (e.getFromIndex() != e.getToIndex()) {
707            log.debug("Table Column Moved");
708            setDirty(true);
709        }
710    }
711
712    @Override
713    public void columnMarginChanged(ChangeEvent e) {
714        // do nothing
715    }
716
717    @Override
718    public void columnSelectionChanged(ListSelectionEvent e) {
719        // do nothing
720    }
721
722    private final static Logger log = LoggerFactory.getLogger(TrackTableModel.class);
723}