001package jmri.jmrit.operations.rollingstock.cars;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.util.List;
006
007import javax.swing.*;
008import javax.swing.table.TableCellEditor;
009
010import org.slf4j.Logger;
011import org.slf4j.LoggerFactory;
012
013import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
014import jmri.InstanceManager;
015import jmri.jmrit.operations.setup.Control;
016import jmri.jmrit.operations.setup.Setup;
017import jmri.jmrit.operations.trains.TrainCommon;
018import jmri.util.swing.XTableColumnModel;
019import jmri.util.table.ButtonEditor;
020import jmri.util.table.ButtonRenderer;
021
022/**
023 * Table Model for edit of cars used by operations
024 *
025 * @author Daniel Boudreau Copyright (C) 2008, 2011, 2012, 2016
026 */
027public class CarsTableModel extends javax.swing.table.AbstractTableModel implements PropertyChangeListener {
028
029    CarManager carManager = InstanceManager.getDefault(CarManager.class); // There is only one manager
030
031    // Defines the columns
032    private static final int SELECT_COLUMN = 0;
033    private static final int NUMBER_COLUMN = 1;
034    private static final int ROAD_COLUMN = 2;
035    private static final int TYPE_COLUMN = 3;
036    private static final int LENGTH_COLUMN = 4;
037    private static final int LOAD_COLUMN = 5;
038    private static final int RWE_LOAD_COLUMN = 6;
039    private static final int RWL_LOAD_COLUMN = 7;
040    private static final int COLOR_COLUMN = 8;
041    private static final int KERNEL_COLUMN = 9;
042    private static final int LOCATION_COLUMN = 10;
043    private static final int RFID_WHERE_LAST_SEEN_COLUMN = 11;
044    private static final int RFID_WHEN_LAST_SEEN_COLUMN = 12;
045    private static final int DESTINATION_COLUMN = 13;
046    private static final int FINAL_DESTINATION_COLUMN = 14;
047    private static final int RWE_DESTINATION_COLUMN = 15;
048    private static final int RWL_DESTINATION_COLUMN = 16;
049    private static final int PREVIOUS_LOCATION_COLUMN = 17;
050    private static final int DIVISION_COLUMN = 18;
051    private static final int TRAIN_COLUMN = 19;
052    private static final int MOVES_COLUMN = 20;
053    private static final int BUILT_COLUMN = 21;
054    private static final int OWNER_COLUMN = 22;
055    private static final int VALUE_COLUMN = 23;
056    private static final int RFID_COLUMN = 24;
057    private static final int WAIT_COLUMN = 25;
058    private static final int PICKUP_COLUMN = 26;
059    private static final int LAST_COLUMN = 27;
060    private static final int COMMENT_COLUMN = 28;
061    private static final int SET_COLUMN = 29;
062    private static final int EDIT_COLUMN = 30;
063
064    private static final int HIGHESTCOLUMN = EDIT_COLUMN + 1;
065
066    public final int SORTBY_NUMBER = 0;
067    public final int SORTBY_ROAD = 1;
068    public final int SORTBY_TYPE = 2;
069    public final int SORTBY_LOCATION = 3;
070    public final int SORTBY_DESTINATION = 4;
071    public final int SORTBY_TRAIN = 5;
072    public final int SORTBY_MOVES = 6;
073    public final int SORTBY_KERNEL = 7;
074    public final int SORTBY_LOAD = 8;
075    public final int SORTBY_COLOR = 9;
076    public final int SORTBY_BUILT = 10;
077    public final int SORTBY_OWNER = 11;
078    public final int SORTBY_RFID = 12;
079    public final int SORTBY_RWE = 13; // return when empty
080    public final int SORTBY_RWL = 14; // return when loaded
081    public final int SORTBY_DIVISION = 15;
082    public final int SORTBY_FINALDESTINATION = 16;
083    public final int SORTBY_VALUE = 17;
084    public final int SORTBY_WAIT = 18;
085    public final int SORTBY_PICKUP = 19;
086    public final int SORTBY_LAST = 20;
087    public final int SORTBY_COMMENT = 21; // also used by PrintCarRosterAction
088
089    private int _sort = SORTBY_NUMBER;
090
091    List<Car> carList = null; // list of cars
092    boolean showAllCars = true; // when true show all cars
093    public String locationName = null; // only show cars with this location
094    public String trackName = null; // only show cars with this track
095    JTable _table;
096    CarsTableFrame _frame;
097
098    public CarsTableModel(boolean showAllCars, String locationName, String trackName) {
099        super();
100        this.showAllCars = showAllCars;
101        this.locationName = locationName;
102        this.trackName = trackName;
103        carManager.addPropertyChangeListener(this);
104        updateList();
105    }
106
107    /**
108     * Not all columns in the Cars table are shown. This was done to limit the width
109     * of the table. Only one column from the following groups is shown at any one
110     * time.
111     * <p>
112     * Load, Color, and RWE Load are grouped together.
113     * <p>
114     * Destination, Final Destination, and RWE Destination are grouped together.
115     * <p>
116     * Moves, Built, Owner, Value, RFID, Wait, Pickup, and Last are grouped
117     * together.
118     * 
119     * @param sort The integer sort to use.
120     *
121     */
122    public void setSort(int sort) {
123        _sort = sort;
124        updateList();
125        XTableColumnModel tcm = (XTableColumnModel) _table.getColumnModel();
126        if (sort == SORTBY_COLOR || sort == SORTBY_LOAD || sort == SORTBY_RWE || sort == SORTBY_RWL) {
127            tcm.setColumnVisible(tcm.getColumnByModelIndex(LOAD_COLUMN), sort == SORTBY_LOAD);
128            tcm.setColumnVisible(tcm.getColumnByModelIndex(COLOR_COLUMN), sort == SORTBY_COLOR);
129            tcm.setColumnVisible(tcm.getColumnByModelIndex(RWE_LOAD_COLUMN), sort == SORTBY_RWE);
130            tcm.setColumnVisible(tcm.getColumnByModelIndex(RWL_LOAD_COLUMN), sort == SORTBY_RWL);
131        }
132        if (sort == SORTBY_DIVISION) {
133            tcm.setColumnVisible(tcm.getColumnByModelIndex(DIVISION_COLUMN), true);
134        }
135        if (sort == SORTBY_DESTINATION ||
136                sort == SORTBY_FINALDESTINATION ||
137                sort == SORTBY_RWE ||
138                sort == SORTBY_RWL) {
139            tcm.setColumnVisible(tcm.getColumnByModelIndex(DESTINATION_COLUMN), sort == SORTBY_DESTINATION);
140            tcm.setColumnVisible(tcm.getColumnByModelIndex(FINAL_DESTINATION_COLUMN), sort == SORTBY_FINALDESTINATION);
141            tcm.setColumnVisible(tcm.getColumnByModelIndex(RWE_DESTINATION_COLUMN), sort == SORTBY_RWE);
142            tcm.setColumnVisible(tcm.getColumnByModelIndex(RWL_DESTINATION_COLUMN), sort == SORTBY_RWL);
143            tcm.setColumnVisible(tcm.getColumnByModelIndex(RWE_LOAD_COLUMN), sort == SORTBY_RWE);
144            tcm.setColumnVisible(tcm.getColumnByModelIndex(RWL_LOAD_COLUMN), sort == SORTBY_RWL);
145            
146            // show load column if color column isn't visible.
147            tcm.setColumnVisible(tcm.getColumnByModelIndex(LOAD_COLUMN),
148                    sort != SORTBY_RWE &&
149                            sort != SORTBY_RWL &&
150                            !tcm.isColumnVisible(tcm.getColumnByModelIndex(COLOR_COLUMN)));
151        } else if (sort == SORTBY_MOVES ||
152                sort == SORTBY_BUILT ||
153                sort == SORTBY_OWNER ||
154                sort == SORTBY_VALUE ||
155                sort == SORTBY_RFID ||
156                sort == SORTBY_WAIT ||
157                sort == SORTBY_PICKUP ||
158                sort == SORTBY_LAST || 
159                sort == SORTBY_COMMENT) {
160            tcm.setColumnVisible(tcm.getColumnByModelIndex(MOVES_COLUMN), sort == SORTBY_MOVES);
161            tcm.setColumnVisible(tcm.getColumnByModelIndex(BUILT_COLUMN), sort == SORTBY_BUILT);
162            tcm.setColumnVisible(tcm.getColumnByModelIndex(OWNER_COLUMN), sort == SORTBY_OWNER);
163            tcm.setColumnVisible(tcm.getColumnByModelIndex(VALUE_COLUMN), sort == SORTBY_VALUE);
164            tcm.setColumnVisible(tcm.getColumnByModelIndex(RFID_COLUMN), sort == SORTBY_RFID);
165            tcm.setColumnVisible(tcm.getColumnByModelIndex(RFID_WHEN_LAST_SEEN_COLUMN), sort == SORTBY_RFID);
166            tcm.setColumnVisible(tcm.getColumnByModelIndex(RFID_WHERE_LAST_SEEN_COLUMN), sort == SORTBY_RFID);
167            tcm.setColumnVisible(tcm.getColumnByModelIndex(WAIT_COLUMN), sort == SORTBY_WAIT);
168            tcm.setColumnVisible(tcm.getColumnByModelIndex(PICKUP_COLUMN), sort == SORTBY_PICKUP);
169            tcm.setColumnVisible(tcm.getColumnByModelIndex(PREVIOUS_LOCATION_COLUMN), sort == SORTBY_LAST);
170            tcm.setColumnVisible(tcm.getColumnByModelIndex(LAST_COLUMN), sort == SORTBY_LAST);
171            tcm.setColumnVisible(tcm.getColumnByModelIndex(COMMENT_COLUMN), sort == SORTBY_COMMENT);
172        }
173        fireTableDataChanged();
174    }
175
176    public String getSortByName() {
177        return getSortByName(_sort);
178    }
179
180    public String getSortByName(int sort) {
181        switch (sort) {
182            case SORTBY_NUMBER:
183                return Bundle.getMessage("Number");
184            case SORTBY_ROAD:
185                return Bundle.getMessage("Road");
186            case SORTBY_TYPE:
187                return Bundle.getMessage("Type");
188            case SORTBY_COLOR:
189                return Bundle.getMessage("Color");
190            case SORTBY_LOAD:
191                return Bundle.getMessage("Load");
192            case SORTBY_KERNEL:
193                return Bundle.getMessage("Kernel");
194            case SORTBY_LOCATION:
195                return Bundle.getMessage("Location");
196            case SORTBY_DESTINATION:
197                return Bundle.getMessage("Destination");
198            case SORTBY_DIVISION:
199                return Bundle.getMessage("HomeDivision");
200            case SORTBY_TRAIN:
201                return Bundle.getMessage("Train");
202            case SORTBY_FINALDESTINATION:
203                return Bundle.getMessage("FinalDestination");
204            case SORTBY_RWE:
205                return Bundle.getMessage("ReturnWhenEmpty");
206            case SORTBY_RWL:
207                return Bundle.getMessage("ReturnWhenLoaded");
208            case SORTBY_MOVES:
209                return Bundle.getMessage("Moves");
210            case SORTBY_BUILT:
211                return Bundle.getMessage("Built");
212            case SORTBY_OWNER:
213                return Bundle.getMessage("Owner");
214            case SORTBY_VALUE:
215                return Setup.getValueLabel();
216            case SORTBY_RFID:
217                return Setup.getRfidLabel();
218            case SORTBY_WAIT:
219                return Bundle.getMessage("Wait");
220            case SORTBY_PICKUP:
221                return Bundle.getMessage("Pickup");
222            case SORTBY_LAST:
223                return Bundle.getMessage("Last");
224            case SORTBY_COMMENT:
225                return Bundle.getMessage("Comment");
226            default:
227                return "Error"; // NOI18N
228        }
229    }
230
231    public void toggleSelectVisible() {
232        XTableColumnModel tcm = (XTableColumnModel) _table.getColumnModel();
233        tcm.setColumnVisible(tcm.getColumnByModelIndex(SELECT_COLUMN),
234                !tcm.isColumnVisible(tcm.getColumnByModelIndex(SELECT_COLUMN)));
235    }
236
237    public void resetCheckboxes() {
238        for (Car car : carList) {
239            car.setSelected(false);
240        }
241    }
242
243    String _roadNumber = "";
244    int _index = 0;
245
246    /**
247     * Search for car by road number
248     * 
249     * @param roadNumber The string road number to search for.
250     *
251     * @return -1 if not found, table row number if found
252     */
253    public int findCarByRoadNumber(String roadNumber) {
254        if (carList != null) {
255            if (!roadNumber.equals(_roadNumber)) {
256                return getIndex(0, roadNumber);
257            }
258            int index = getIndex(_index, roadNumber);
259            if (index > 0) {
260                return index;
261            }
262            return getIndex(0, roadNumber);
263        }
264        return -1;
265    }
266
267    private int getIndex(int start, String roadNumber) {
268        for (int index = start; index < carList.size(); index++) {
269            Car car = carList.get(index);
270            if (car != null) {
271                String[] number = car.getNumber().split(TrainCommon.HYPHEN);
272                // check for wild card '*'
273                if (roadNumber.startsWith("*") && roadNumber.endsWith("*")) {
274                    String rN = roadNumber.substring(1, roadNumber.length() - 1);
275                    if (car.getNumber().contains(rN)) {
276                        _roadNumber = roadNumber;
277                        _index = index + 1;
278                        return index;
279                    }
280                } else if (roadNumber.startsWith("*")) {
281                    String rN = roadNumber.substring(1);
282                    if (car.getNumber().endsWith(rN) || number[0].endsWith(rN)) {
283                        _roadNumber = roadNumber;
284                        _index = index + 1;
285                        return index;
286                    }
287                } else if (roadNumber.endsWith("*")) {
288                    String rN = roadNumber.substring(0, roadNumber.length() - 1);
289                    if (car.getNumber().startsWith(rN)) {
290                        _roadNumber = roadNumber;
291                        _index = index + 1;
292                        return index;
293                    }
294                } else if (car.getNumber().equals(roadNumber) || number[0].equals(roadNumber)) {
295                    _roadNumber = roadNumber;
296                    _index = index + 1;
297                    return index;
298                }
299            }
300        }
301        _roadNumber = "";
302        return -1;
303    }
304
305    public Car getCarAtIndex(int index) {
306        return carList.get(index);
307    }
308
309    private void updateList() {
310        // first, remove listeners from the individual objects
311        removePropertyChangeCars();
312        carList = getSelectedCarList();
313        // and add listeners back in
314        addPropertyChangeCars();
315    }
316
317    public List<Car> getSelectedCarList() {
318        return getCarList(_sort);
319    }
320
321    @SuppressFBWarnings(value = "DB_DUPLICATE_SWITCH_CLAUSES", justification = "default case is sort by number") // NOI18N
322    public List<Car> getCarList(int sort) {
323        List<Car> list;
324        switch (sort) {
325            case SORTBY_NUMBER:
326                list = carManager.getByNumberList();
327                break;
328            case SORTBY_ROAD:
329                list = carManager.getByRoadNameList();
330                break;
331            case SORTBY_TYPE:
332                list = carManager.getByTypeList();
333                break;
334            case SORTBY_COLOR:
335                list = carManager.getByColorList();
336                break;
337            case SORTBY_LOAD:
338                list = carManager.getByLoadList();
339                break;
340            case SORTBY_KERNEL:
341                list = carManager.getByKernelList();
342                break;
343            case SORTBY_LOCATION:
344                list = carManager.getByLocationList();
345                break;
346            case SORTBY_DESTINATION:
347                list = carManager.getByDestinationList();
348                break;
349            case SORTBY_TRAIN:
350                list = carManager.getByTrainList();
351                break;
352            case SORTBY_FINALDESTINATION:
353                list = carManager.getByFinalDestinationList();
354                break;
355            case SORTBY_RWE:
356                list = carManager.getByRweList();
357                break;
358            case SORTBY_RWL:
359                list = carManager.getByRwlList();
360                break;
361            case SORTBY_DIVISION:
362                list = carManager.getByDivisionList();
363                break;
364            case SORTBY_MOVES:
365                list = carManager.getByMovesList();
366                break;
367            case SORTBY_BUILT:
368                list = carManager.getByBuiltList();
369                break;
370            case SORTBY_OWNER:
371                list = carManager.getByOwnerList();
372                break;
373            case SORTBY_VALUE:
374                list = carManager.getByValueList();
375                break;
376            case SORTBY_RFID:
377                list = carManager.getByRfidList();
378                break;
379            case SORTBY_WAIT:
380                list = carManager.getByWaitList();
381                break;
382            case SORTBY_PICKUP:
383                list = carManager.getByPickupList();
384                break;
385            case SORTBY_LAST:
386                list = carManager.getByLastDateList();
387                break;
388            case SORTBY_COMMENT:
389                list = carManager.getByCommentList();
390                break;
391            default:
392                list = carManager.getByNumberList();
393        }
394        filterList(list);
395        return list;
396    }
397
398    private void filterList(List<Car> list) {
399        if (showAllCars) {
400            return;
401        }
402        for (int i = 0; i < list.size(); i++) {
403            Car car = list.get(i);
404            if (car.getLocation() == null) {
405                list.remove(i--);
406                continue;
407            }
408            // filter out cars that don't have a location name that matches
409            if (locationName != null) {
410                if (!car.getLocationName().equals(locationName)) {
411                    list.remove(i--);
412                    continue;
413                }
414                if (trackName != null) {
415                    if (!car.getTrackName().equals(trackName)) {
416                        list.remove(i--);
417                    }
418                }
419            }
420        }
421    }
422
423    void initTable(JTable table, CarsTableFrame frame) {
424        _table = table;
425        _frame = frame;
426        initTable();
427    }
428
429    // Cars frame table column widths, starts with Select column and ends with Edit
430    private final int[] tableColumnWidths = { 60, 60, 60, 65, 35, 75, 75, 75, 75, 65, 190, 190, 140, 190, 190, 190, 190,
431            190, 190, 65, 50, 50, 50, 50, 100, 50, 100, 100, 100, 65, 70 };
432
433    void initTable() {
434        // Use XTableColumnModel so we can control which columns are visible
435        XTableColumnModel tcm = new XTableColumnModel();
436        _table.setColumnModel(tcm);
437        _table.createDefaultColumnsFromModel();
438
439        // Install the button handlers
440        ButtonRenderer buttonRenderer = new ButtonRenderer();
441        tcm.getColumn(SET_COLUMN).setCellRenderer(buttonRenderer);
442        TableCellEditor buttonEditor = new ButtonEditor(new javax.swing.JButton());
443        tcm.getColumn(SET_COLUMN).setCellEditor(buttonEditor);
444        tcm.getColumn(EDIT_COLUMN).setCellRenderer(buttonRenderer);
445        tcm.getColumn(EDIT_COLUMN).setCellEditor(buttonEditor);
446
447        // set column preferred widths
448        for (int i = 0; i < tcm.getColumnCount(); i++) {
449            tcm.getColumn(i).setPreferredWidth(tableColumnWidths[i]);
450        }
451        _frame.loadTableDetails(_table);
452
453        // turn off columns
454        tcm.setColumnVisible(tcm.getColumnByModelIndex(COLOR_COLUMN), false);
455
456        tcm.setColumnVisible(tcm.getColumnByModelIndex(FINAL_DESTINATION_COLUMN), false);
457        tcm.setColumnVisible(tcm.getColumnByModelIndex(RWE_DESTINATION_COLUMN), false);
458        tcm.setColumnVisible(tcm.getColumnByModelIndex(RWE_LOAD_COLUMN), false);
459        tcm.setColumnVisible(tcm.getColumnByModelIndex(RWL_DESTINATION_COLUMN), false);
460        tcm.setColumnVisible(tcm.getColumnByModelIndex(RWL_LOAD_COLUMN), false);
461        
462        tcm.setColumnVisible(tcm.getColumnByModelIndex(BUILT_COLUMN), false);
463        tcm.setColumnVisible(tcm.getColumnByModelIndex(OWNER_COLUMN), false);
464        tcm.setColumnVisible(tcm.getColumnByModelIndex(VALUE_COLUMN), false);
465        tcm.setColumnVisible(tcm.getColumnByModelIndex(RFID_COLUMN), false);
466        tcm.setColumnVisible(tcm.getColumnByModelIndex(RFID_WHEN_LAST_SEEN_COLUMN), false);
467        tcm.setColumnVisible(tcm.getColumnByModelIndex(RFID_WHERE_LAST_SEEN_COLUMN), false);
468        tcm.setColumnVisible(tcm.getColumnByModelIndex(WAIT_COLUMN), false);
469        tcm.setColumnVisible(tcm.getColumnByModelIndex(PICKUP_COLUMN), false);
470        tcm.setColumnVisible(tcm.getColumnByModelIndex(PREVIOUS_LOCATION_COLUMN), false);
471        tcm.setColumnVisible(tcm.getColumnByModelIndex(LAST_COLUMN), false);
472        tcm.setColumnVisible(tcm.getColumnByModelIndex(COMMENT_COLUMN), false);
473
474        // turn on defaults
475        tcm.setColumnVisible(tcm.getColumnByModelIndex(LOAD_COLUMN), true);
476        tcm.setColumnVisible(tcm.getColumnByModelIndex(DESTINATION_COLUMN), true);
477        tcm.setColumnVisible(tcm.getColumnByModelIndex(MOVES_COLUMN), true);
478        
479        tcm.setColumnVisible(tcm.getColumnByModelIndex(DIVISION_COLUMN), carManager.isThereDivisions());
480    }
481
482    @Override
483    public int getRowCount() {
484        return carList.size();
485    }
486
487    @Override
488    public int getColumnCount() {
489        return HIGHESTCOLUMN;
490    }
491
492    @Override
493    public String getColumnName(int col) {
494        switch (col) {
495            case SELECT_COLUMN:
496                return Bundle.getMessage("ButtonSelect");
497            case NUMBER_COLUMN:
498                return Bundle.getMessage("Number");
499            case ROAD_COLUMN:
500                return Bundle.getMessage("Road");
501            case LOAD_COLUMN:
502                return Bundle.getMessage("Load");
503            case COLOR_COLUMN:
504                return Bundle.getMessage("Color");
505            case TYPE_COLUMN:
506                return Bundle.getMessage("Type");
507            case LENGTH_COLUMN:
508                return Bundle.getMessage("Len");
509            case KERNEL_COLUMN:
510                return Bundle.getMessage("Kernel");
511            case LOCATION_COLUMN:
512                return Bundle.getMessage("Location");
513            case RFID_WHERE_LAST_SEEN_COLUMN:
514                return Bundle.getMessage("WhereLastSeen");
515            case RFID_WHEN_LAST_SEEN_COLUMN:
516                return Bundle.getMessage("WhenLastSeen");
517            case DESTINATION_COLUMN:
518                return Bundle.getMessage("Destination");
519            case FINAL_DESTINATION_COLUMN:
520                return Bundle.getMessage("FinalDestination");
521            case RWE_DESTINATION_COLUMN:
522                return Bundle.getMessage("RWELocation");
523            case RWE_LOAD_COLUMN:
524                return Bundle.getMessage("RWELoad");
525            case RWL_DESTINATION_COLUMN:
526                return Bundle.getMessage("RWLLocation");
527            case RWL_LOAD_COLUMN:
528                return Bundle.getMessage("RWLLoad");
529            case PREVIOUS_LOCATION_COLUMN:
530                return Bundle.getMessage("LastLocation");
531            case DIVISION_COLUMN:
532                return Bundle.getMessage("HomeDivision");
533            case TRAIN_COLUMN:
534                return Bundle.getMessage("Train");
535            case MOVES_COLUMN:
536                return Bundle.getMessage("Moves");
537            case BUILT_COLUMN:
538                return Bundle.getMessage("Built");
539            case OWNER_COLUMN:
540                return Bundle.getMessage("Owner");
541            case VALUE_COLUMN:
542                return Setup.getValueLabel();
543            case RFID_COLUMN:
544                return Setup.getRfidLabel();
545            case WAIT_COLUMN:
546                return Bundle.getMessage("Wait");
547            case PICKUP_COLUMN:
548                return Bundle.getMessage("Pickup");
549            case LAST_COLUMN:
550                return Bundle.getMessage("LastMoved");
551            case COMMENT_COLUMN:
552                return Bundle.getMessage("Comment");
553            case SET_COLUMN:
554                return Bundle.getMessage("Set");
555            case EDIT_COLUMN:
556                return Bundle.getMessage("ButtonEdit"); // titles above all columns
557            default:
558                return "unknown"; // NOI18N
559        }
560    }
561
562    @Override
563    public Class<?> getColumnClass(int col) {
564        switch (col) {
565            case SELECT_COLUMN:
566                return Boolean.class;
567            case SET_COLUMN:
568            case EDIT_COLUMN:
569                return JButton.class;
570            case LENGTH_COLUMN:
571            case MOVES_COLUMN:
572            case WAIT_COLUMN:
573                return Integer.class;
574            case LAST_COLUMN:
575                return Object.class; // to disable sorting
576            default:
577                return String.class;
578        }
579    }
580
581    @Override
582    public boolean isCellEditable(int row, int col) {
583        switch (col) {
584            case SELECT_COLUMN:
585            case SET_COLUMN:
586            case EDIT_COLUMN:
587            case MOVES_COLUMN:
588            case WAIT_COLUMN:
589            case VALUE_COLUMN:
590            case RFID_COLUMN:
591                return true;
592            default:
593                return false;
594        }
595    }
596
597    @Override
598    public Object getValueAt(int row, int col) {
599        if (row >= getRowCount()) {
600            return "ERROR row " + row; // NOI18N
601        }
602        Car car = carList.get(row);
603        if (car == null) {
604            return "ERROR car unknown " + row; // NOI18N
605        }
606        switch (col) {
607            case SELECT_COLUMN:
608                return car.isSelected();
609            case NUMBER_COLUMN:
610                return car.getNumber();
611            case ROAD_COLUMN:
612                return car.getRoadName();
613            case LOAD_COLUMN:
614                return getLoadNameString(car);
615            case COLOR_COLUMN:
616                return car.getColor();
617            case LENGTH_COLUMN:
618                return car.getLengthInteger();
619            case TYPE_COLUMN:
620                return car.getTypeName() + car.getTypeExtensions();
621            case KERNEL_COLUMN:
622                if (car.isLead()) {
623                    return car.getKernelName() + "*";
624                }
625                return car.getKernelName();
626            case LOCATION_COLUMN:
627                if (car.getLocation() != null) {
628                    return car.getStatus() + car.getLocationName() + " (" + car.getTrackName() + ")";
629                }
630                return car.getStatus();
631            case RFID_WHERE_LAST_SEEN_COLUMN:
632                return car.getWhereLastSeenName() +
633                        (car.getTrackLastSeenName().equals(Car.NONE) ? "" : " (" + car.getTrackLastSeenName() + ")");
634            case RFID_WHEN_LAST_SEEN_COLUMN: {
635                return car.getWhenLastSeenDate();
636            }
637            case DESTINATION_COLUMN:
638            case FINAL_DESTINATION_COLUMN: {
639                String s = "";
640                if (car.getDestination() != null) {
641                    s = car.getDestinationName() + " (" + car.getDestinationTrackName() + ")";
642                }
643                if (car.getFinalDestination() != null) {
644                    s = s + "->" + car.getFinalDestinationName(); // NOI18N
645                }
646                if (car.getFinalDestinationTrack() != null) {
647                    s = s + " (" + car.getFinalDestinationTrackName() + ")";
648                }
649                if (log.isDebugEnabled() &&
650                        car.getFinalDestinationTrack() != null &&
651                        car.getFinalDestinationTrack().getSchedule() != null) {
652                    s = s + " " + car.getScheduleItemId();
653                }
654                return s;
655            }
656            case RWE_DESTINATION_COLUMN: {
657                String s = car.getReturnWhenEmptyDestinationName();
658                if (car.getReturnWhenEmptyDestTrack() != null) {
659                    s = s + " (" + car.getReturnWhenEmptyDestTrackName() + ")";
660                }
661                return s;
662            }
663            case RWE_LOAD_COLUMN:
664                return car.getReturnWhenEmptyLoadName();
665            case RWL_DESTINATION_COLUMN: {
666                String s = car.getReturnWhenLoadedDestinationName();
667                if (car.getReturnWhenLoadedDestTrack() != null) {
668                    s = s + " (" + car.getReturnWhenLoadedDestTrackName() + ")";
669                }
670                return s;
671            }
672            case RWL_LOAD_COLUMN:
673                return car.getReturnWhenLoadedLoadName();
674            case DIVISION_COLUMN:
675                return car.getDivisionName();
676            case PREVIOUS_LOCATION_COLUMN: {
677                String s = "";
678                if (!car.getLastLocationName().equals(Car.NONE)) {
679                    s = car.getLastLocationName() + " (" + car.getLastTrackName() + ")";
680                }
681                return s;
682            }
683            case TRAIN_COLUMN: {
684                // if train was manually set by user add an asterisk
685                if (car.getTrain() != null && car.getRouteLocation() == null) {
686                    return car.getTrainName() + "*";
687                }
688                return car.getTrainName();
689            }
690            case MOVES_COLUMN:
691                return car.getMoves();
692            case BUILT_COLUMN:
693                return car.getBuilt();
694            case OWNER_COLUMN:
695                return car.getOwnerName();
696            case VALUE_COLUMN:
697                return car.getValue();
698            case RFID_COLUMN:
699                return car.getRfid();
700            case WAIT_COLUMN:
701                return car.getWait();
702            case PICKUP_COLUMN:
703                return car.getPickupScheduleName();
704            case LAST_COLUMN:
705                return car.getLastDate();
706            case COMMENT_COLUMN:
707                return car.getComment();
708            case SET_COLUMN:
709                return Bundle.getMessage("Set");
710            case EDIT_COLUMN:
711                return Bundle.getMessage("ButtonEdit");
712            default:
713                return "unknown " + col; // NOI18N
714        }
715    }
716    
717    private String getLoadNameString(Car car) {
718        StringBuffer sb = new StringBuffer(car.getLoadName());
719        if (car.getLoadPriority().equals(CarLoad.PRIORITY_HIGH)) {
720            sb.append(" " + Bundle.getMessage("(P)"));
721        } else if (car.getLoadPriority().equals(CarLoad.PRIORITY_MEDIUM)) {
722            sb.append(" " + Bundle.getMessage("(M)"));
723        }
724        if (car.isCarLoadHazardous()) {
725            sb.append(" " + Bundle.getMessage("(H)"));
726        }
727        return sb.toString();
728    }
729
730    CarEditFrame cef = null;
731    CarSetFrame csf = null;
732
733    @Override
734    public void setValueAt(Object value, int row, int col) {
735        Car car = carList.get(row);
736        switch (col) {
737            case SELECT_COLUMN:
738                car.setSelected(((Boolean) value).booleanValue());
739                break;
740            case SET_COLUMN:
741                log.debug("Set car");
742                if (csf != null) {
743                    csf.dispose();
744                }
745                // use invokeLater so new window appears on top
746                SwingUtilities.invokeLater(() -> {
747                    csf = new CarSetFrame();
748                    csf.initComponents();
749                    csf.load(car);
750                });
751                break;
752            case EDIT_COLUMN:
753                log.debug("Edit car");
754                if (cef != null) {
755                    cef.dispose();
756                }
757                // use invokeLater so new window appears on top
758                SwingUtilities.invokeLater(() -> {
759                    cef = new CarEditFrame();
760                    cef.initComponents();
761                    cef.load(car);
762                });
763                break;
764            case MOVES_COLUMN:
765                try {
766                    car.setMoves(Integer.parseInt(value.toString()));
767                } catch (NumberFormatException e) {
768                    log.error("move count must be a number");
769                }
770                break;
771            case VALUE_COLUMN:
772                car.setValue(value.toString());
773                break;
774            case RFID_COLUMN:
775                car.setRfid(value.toString());
776                break;
777            case WAIT_COLUMN:
778                try {
779                    car.setWait(Integer.parseInt(value.toString()));
780                } catch (NumberFormatException e) {
781                    log.error("wait count must be a number");
782                }
783                break;
784            default:
785                break;
786        }
787    }
788
789    public void dispose() {
790        carManager.removePropertyChangeListener(this);
791        removePropertyChangeCars();
792        if (csf != null) {
793            csf.dispose();
794        }
795        if (cef != null) {
796            cef.dispose();
797        }
798    }
799
800    private void addPropertyChangeCars() {
801        for (Car car : carManager.getList()) {
802            car.addPropertyChangeListener(this);
803        }
804    }
805
806    private void removePropertyChangeCars() {
807        for (Car car : carManager.getList()) {
808            car.removePropertyChangeListener(this);
809        }
810    }
811
812    @Override
813    public void propertyChange(PropertyChangeEvent e) {
814        if (Control.SHOW_PROPERTY) {
815            log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(),
816                    e.getNewValue());
817        }
818        if (e.getPropertyName().equals(CarManager.LISTLENGTH_CHANGED_PROPERTY)) {
819            updateList();
820            fireTableDataChanged();
821        } // must be a car change
822        else if (e.getSource().getClass().equals(Car.class)) {
823            Car car = (Car) e.getSource();
824            int row = carList.indexOf(car);
825            if (Control.SHOW_PROPERTY) {
826                log.debug("Update car table row: {}", row);
827            }
828            if (row >= 0) {
829                fireTableRowsUpdated(row, row);
830                // next is needed when only showing cars at a location or track
831            } else if (e.getPropertyName().equals(Car.TRACK_CHANGED_PROPERTY)) {
832                updateList();
833                fireTableDataChanged();
834            }
835        }
836    }
837
838    private final static Logger log = LoggerFactory.getLogger(CarsTableModel.class);
839}