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