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