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