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