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