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