001package jmri.jmrit.operations.rollingstock.engines;
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 jmri.InstanceManager;
014import jmri.jmrit.operations.rollingstock.RollingStock;
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 engines used by operations
024 *
025 * @author Daniel Boudreau Copyright (C) 2008, 2012, 2025
026 */
027public class EnginesTableModel extends javax.swing.table.AbstractTableModel implements PropertyChangeListener {
028
029    EngineManager engineManager = InstanceManager.getDefault(EngineManager.class); // There is only one manager
030
031    // Defines the columns
032    private static final int SELECT_COLUMN = 0;
033    private static final int NUM_COLUMN = 1;
034    private static final int ROAD_COLUMN = 2;
035    private static final int MODEL_COLUMN = 3;
036    private static final int HP_COLUMN = 4;
037    private static final int WEIGHT_COLUMN = 5;
038    private static final int TYPE_COLUMN = 6;
039    private static final int LENGTH_COLUMN = 7;
040    private static final int CONSIST_COLUMN = 8;
041    private static final int LOCATION_COLUMN = 9;
042    private static final int RFID_WHERE_LAST_SEEN_COLUMN = 10;
043    private static final int RFID_WHEN_LAST_SEEN_COLUMN = 11;
044    private static final int DESTINATION_COLUMN = 12;
045    private static final int PREVIOUS_LOCATION_COLUMN = 13;
046    private static final int TRAIN_COLUMN = 14;
047    private static final int MOVES_COLUMN = 15;
048    private static final int BUILT_COLUMN = 16;
049    private static final int OWNER_COLUMN = 17;
050    private static final int VALUE_COLUMN = 18;
051    private static final int RFID_COLUMN = 19;
052    private static final int LAST_COLUMN = 20;
053    private static final int DCC_ADDRESS_COLUMN = 21;
054    private static final int COMMENT_COLUMN = 22;
055    private static final int SET_COLUMN = 23;
056    private static final int EDIT_COLUMN = 24;
057
058    private static final int HIGHEST_COLUMN = EDIT_COLUMN + 1;
059
060    public EnginesTableModel() {
061        super();
062        engineManager.addPropertyChangeListener(this);
063        updateList();
064    }
065
066    public final int SORTBY_NUMBER = 0;
067    public final int SORTBY_ROAD = 1;
068    public final int SORTBY_MODEL = 2;
069    public final int SORTBY_LOCATION = 3;
070    public final int SORTBY_DESTINATION = 4;
071    public final int SORTBY_TRAIN = 5;
072    public final int SORTBY_MOVES = 6;
073    public final int SORTBY_CONSIST = 7;
074    public final int SORTBY_BUILT = 8;
075    public final int SORTBY_OWNER = 9;
076    public final int SORTBY_VALUE = 10;
077    public final int SORTBY_RFID = 11;
078    public final int SORTBY_LAST = 12;
079    public final int SORTBY_HP = 13;
080    public final int SORTBY_DCC_ADDRESS = 14;
081    public final int SORTBY_COMMENT = 15;
082
083    private int _sort = SORTBY_NUMBER;
084
085    /**
086     * Not all columns are visible at the same time.
087     *
088     * @param sort which sort is active
089     */
090    public void setSort(int sort) {
091        _sort = sort;
092        updateList();
093        if (sort == SORTBY_MOVES ||
094                sort == SORTBY_BUILT ||
095                sort == SORTBY_OWNER ||
096                sort == SORTBY_VALUE ||
097                sort == SORTBY_RFID ||
098                sort == SORTBY_LAST ||
099                sort == SORTBY_DCC_ADDRESS ||
100                sort == SORTBY_COMMENT) {
101            XTableColumnModel tcm = (XTableColumnModel) _table.getColumnModel();
102            tcm.setColumnVisible(tcm.getColumnByModelIndex(MOVES_COLUMN), sort == SORTBY_MOVES);
103            tcm.setColumnVisible(tcm.getColumnByModelIndex(BUILT_COLUMN), sort == SORTBY_BUILT);
104            tcm.setColumnVisible(tcm.getColumnByModelIndex(OWNER_COLUMN), sort == SORTBY_OWNER);
105            tcm.setColumnVisible(tcm.getColumnByModelIndex(VALUE_COLUMN), sort == SORTBY_VALUE);
106            tcm.setColumnVisible(tcm.getColumnByModelIndex(RFID_COLUMN), sort == SORTBY_RFID);
107            tcm.setColumnVisible(tcm.getColumnByModelIndex(RFID_WHEN_LAST_SEEN_COLUMN), sort == SORTBY_RFID);
108            tcm.setColumnVisible(tcm.getColumnByModelIndex(RFID_WHERE_LAST_SEEN_COLUMN), sort == SORTBY_RFID);
109            tcm.setColumnVisible(tcm.getColumnByModelIndex(PREVIOUS_LOCATION_COLUMN), sort == SORTBY_LAST);
110            tcm.setColumnVisible(tcm.getColumnByModelIndex(LAST_COLUMN), sort == SORTBY_LAST);
111            tcm.setColumnVisible(tcm.getColumnByModelIndex(DCC_ADDRESS_COLUMN), sort == SORTBY_DCC_ADDRESS);
112            tcm.setColumnVisible(tcm.getColumnByModelIndex(COMMENT_COLUMN), sort == SORTBY_COMMENT);
113        }
114        fireTableDataChanged();
115    }
116
117    public void toggleSelectVisible() {
118        XTableColumnModel tcm = (XTableColumnModel) _table.getColumnModel();
119        tcm.setColumnVisible(tcm.getColumnByModelIndex(SELECT_COLUMN),
120                !tcm.isColumnVisible(tcm.getColumnByModelIndex(SELECT_COLUMN)));
121    }
122
123    public void resetCheckboxes() {
124        for (Engine engine : engineList) {
125            engine.setSelected(false);
126        }
127    }
128
129    public String getSortByName() {
130        return getSortByName(_sort);
131    }
132
133    public String getSortByName(int sort) {
134        switch (sort) {
135            case SORTBY_NUMBER:
136                return Bundle.getMessage("Number");
137            case SORTBY_ROAD:
138                return Bundle.getMessage("Road");
139            case SORTBY_MODEL:
140                return Bundle.getMessage("Model");
141            case SORTBY_LOCATION:
142                return Bundle.getMessage("Location");
143            case SORTBY_DESTINATION:
144                return Bundle.getMessage("Destination");
145            case SORTBY_TRAIN:
146                return Bundle.getMessage("Train");
147            case SORTBY_MOVES:
148                return Bundle.getMessage("Moves");
149            case SORTBY_CONSIST:
150                return Bundle.getMessage("Consist");
151            case SORTBY_BUILT:
152                return Bundle.getMessage("Built");
153            case SORTBY_OWNER:
154                return Bundle.getMessage("Owner");
155            case SORTBY_DCC_ADDRESS:
156                return Bundle.getMessage("DccAddress");
157            case SORTBY_HP:
158                return Bundle.getMessage("HP");
159            case SORTBY_VALUE:
160                return Setup.getValueLabel();
161            case SORTBY_RFID:
162                return Setup.getRfidLabel();
163            case SORTBY_LAST:
164                return Bundle.getMessage("Last");
165            case SORTBY_COMMENT:
166                return Bundle.getMessage("Comment");
167            default:
168                return "Error"; // NOI18N
169        }
170    }
171
172    String _roadNumber = "";
173    int _index = 0;
174
175    /**
176     * Search for engine by road number
177     * 
178     * @param roadNumber The string road number to search for.
179     *
180     * @return -1 if not found, table row number if found
181     */
182    public int findEngineByRoadNumber(String roadNumber) {
183        if (engineList != null) {
184            if (!roadNumber.equals(_roadNumber)) {
185                return getIndex(0, roadNumber);
186            }
187            int index = getIndex(_index, roadNumber);
188            if (index > 0) {
189                return index;
190            }
191            return getIndex(0, roadNumber);
192        }
193        return -1;
194    }
195
196    private int getIndex(int start, String roadNumber) {
197        for (int index = start; index < engineList.size(); index++) {
198            Engine e = engineList.get(index);
199            if (e != null) {
200                String[] number = e.getNumber().split(TrainCommon.HYPHEN);
201                // check for wild card '*'
202                if (roadNumber.startsWith("*") && roadNumber.endsWith("*")) {
203                    String rN = roadNumber.substring(1, roadNumber.length() - 1);
204                    if (e.getNumber().contains(rN)) {
205                        _roadNumber = roadNumber;
206                        _index = index + 1;
207                        return index;
208                    }
209                } else if (roadNumber.startsWith("*")) {
210                    String rN = roadNumber.substring(1);
211                    if (e.getNumber().endsWith(rN) || number[0].endsWith(rN)) {
212                        _roadNumber = roadNumber;
213                        _index = index + 1;
214                        return index;
215                    }
216                } else if (roadNumber.endsWith("*")) {
217                    String rN = roadNumber.substring(0, roadNumber.length() - 1);
218                    if (e.getNumber().startsWith(rN)) {
219                        _roadNumber = roadNumber;
220                        _index = index + 1;
221                        return index;
222                    }
223                } else if (e.getNumber().equals(roadNumber) || number[0].equals(roadNumber)) {
224                    _roadNumber = roadNumber;
225                    _index = index + 1;
226                    return index;
227                }
228            }
229        }
230        _roadNumber = "";
231        return -1;
232    }
233
234    public Engine getEngineAtIndex(int index) {
235        return engineList.get(index);
236    }
237
238    private void updateList() {
239        // first, remove listeners from the individual objects
240        removePropertyChangeEngines();
241        engineList = getSelectedEngineList();
242        // and add listeners back in
243        for (RollingStock rs : engineList) {
244            rs.addPropertyChangeListener(this);
245        }
246    }
247
248    public List<Engine> getSelectedEngineList() {
249        return getEngineList(_sort);
250    }
251
252    public List<Engine> getEngineList(int sort) {
253        List<Engine> list;
254        switch (sort) {
255            case SORTBY_ROAD:
256                list = engineManager.getByRoadNameList();
257                break;
258            case SORTBY_MODEL:
259                list = engineManager.getByModelList();
260                break;
261            case SORTBY_LOCATION:
262                list = engineManager.getByLocationList();
263                break;
264            case SORTBY_DESTINATION:
265                list = engineManager.getByDestinationList();
266                break;
267            case SORTBY_TRAIN:
268                list = engineManager.getByTrainList();
269                break;
270            case SORTBY_MOVES:
271                list = engineManager.getByMovesList();
272                break;
273            case SORTBY_CONSIST:
274                list = engineManager.getByConsistList();
275                break;
276            case SORTBY_OWNER:
277                list = engineManager.getByOwnerList();
278                break;
279            case SORTBY_BUILT:
280                list = engineManager.getByBuiltList();
281                break;
282            case SORTBY_VALUE:
283                list = engineManager.getByValueList();
284                break;
285            case SORTBY_RFID:
286                list = engineManager.getByRfidList();
287                break;
288            case SORTBY_LAST:
289                list = engineManager.getByLastDateList();
290                break;
291            case SORTBY_COMMENT:
292                list = engineManager.getByCommentList();
293                break;
294            case SORTBY_NUMBER:
295            default:
296                list = engineManager.getByNumberList();
297        }
298        return list;
299    }
300
301    List<Engine> engineList = null;
302
303    JTable _table;
304    EnginesTableFrame _frame;
305
306    void initTable(JTable table, EnginesTableFrame frame) {
307        _table = table;
308        _frame = frame;
309        initTable();
310    }
311
312    // Default engines frame table column widths, starts with Number column and ends with Edit
313    private final int[] _enginesTableColumnWidths =
314            {60, 60, 60, 65, 50, 65, 65, 35, 75, 190, 190, 190, 140, 190, 65, 50, 50, 50, 50, 100, 130, 50, 100, 65,
315                    70};
316
317    void initTable() {
318        // Use XTableColumnModel so we can control which columns are visible
319        XTableColumnModel tcm = new XTableColumnModel();
320        _table.setColumnModel(tcm);
321        _table.createDefaultColumnsFromModel();
322
323        // Install the button handlers
324        ButtonRenderer buttonRenderer = new ButtonRenderer();
325        tcm.getColumn(SET_COLUMN).setCellRenderer(buttonRenderer);
326        TableCellEditor buttonEditor = new ButtonEditor(new javax.swing.JButton());
327        tcm.getColumn(SET_COLUMN).setCellEditor(buttonEditor);
328        tcm.getColumn(EDIT_COLUMN).setCellRenderer(buttonRenderer);
329        tcm.getColumn(EDIT_COLUMN).setCellEditor(buttonEditor);
330
331        // set column preferred widths
332        // load defaults, xml file data not found
333        for (int i = 0; i < tcm.getColumnCount(); i++) {
334            tcm.getColumn(i).setPreferredWidth(_enginesTableColumnWidths[i]);
335        }
336        _frame.loadTableDetails(_table);
337
338        // turn off columns
339        tcm.setColumnVisible(tcm.getColumnByModelIndex(BUILT_COLUMN), false);
340        tcm.setColumnVisible(tcm.getColumnByModelIndex(OWNER_COLUMN), false);
341        tcm.setColumnVisible(tcm.getColumnByModelIndex(VALUE_COLUMN), false);
342        tcm.setColumnVisible(tcm.getColumnByModelIndex(RFID_COLUMN), false);
343        tcm.setColumnVisible(tcm.getColumnByModelIndex(RFID_WHEN_LAST_SEEN_COLUMN), false);
344        tcm.setColumnVisible(tcm.getColumnByModelIndex(RFID_WHERE_LAST_SEEN_COLUMN), false);
345        tcm.setColumnVisible(tcm.getColumnByModelIndex(PREVIOUS_LOCATION_COLUMN), false);
346        tcm.setColumnVisible(tcm.getColumnByModelIndex(LAST_COLUMN), false);
347        tcm.setColumnVisible(tcm.getColumnByModelIndex(DCC_ADDRESS_COLUMN), false);
348        tcm.setColumnVisible(tcm.getColumnByModelIndex(COMMENT_COLUMN), false);
349
350        // turn on default
351        tcm.setColumnVisible(tcm.getColumnByModelIndex(MOVES_COLUMN), true);
352    }
353
354    @Override
355    public int getRowCount() {
356        return engineList.size();
357    }
358
359    @Override
360    public int getColumnCount() {
361        return HIGHEST_COLUMN;
362    }
363
364    @Override
365    public String getColumnName(int col) {
366        switch (col) {
367            case SELECT_COLUMN:
368                return Bundle.getMessage("ButtonSelect");
369            case NUM_COLUMN:
370                return Bundle.getMessage("Number");
371            case ROAD_COLUMN:
372                return Bundle.getMessage("Road");
373            case MODEL_COLUMN:
374                return Bundle.getMessage("Model");
375            case HP_COLUMN:
376                return Bundle.getMessage("HP");
377            case TYPE_COLUMN:
378                return Bundle.getMessage("Type");
379            case LENGTH_COLUMN:
380                return Bundle.getMessage("Len");
381            case WEIGHT_COLUMN:
382                return Bundle.getMessage("Weight");
383            case CONSIST_COLUMN:
384                return Bundle.getMessage("Consist");
385            case LOCATION_COLUMN:
386                return Bundle.getMessage("Location");
387            case RFID_WHERE_LAST_SEEN_COLUMN:
388                return Bundle.getMessage("WhereLastSeen");
389            case RFID_WHEN_LAST_SEEN_COLUMN:
390                return Bundle.getMessage("WhenLastSeen");
391            case DESTINATION_COLUMN:
392                return Bundle.getMessage("Destination");
393            case PREVIOUS_LOCATION_COLUMN:
394                return Bundle.getMessage("LastLocation");
395            case TRAIN_COLUMN:
396                return Bundle.getMessage("Train");
397            case MOVES_COLUMN:
398                return Bundle.getMessage("Moves");
399            case BUILT_COLUMN:
400                return Bundle.getMessage("Built");
401            case OWNER_COLUMN:
402                return Bundle.getMessage("Owner");
403            case VALUE_COLUMN:
404                return Setup.getValueLabel();
405            case RFID_COLUMN:
406                return Setup.getRfidLabel();
407            case LAST_COLUMN:
408                return Bundle.getMessage("LastMoved");
409            case DCC_ADDRESS_COLUMN:
410                return Bundle.getMessage("DccAddress");
411            case COMMENT_COLUMN:
412                return Bundle.getMessage("Comment");
413            case SET_COLUMN:
414                return Bundle.getMessage("Set");
415            case EDIT_COLUMN:
416                return Bundle.getMessage("ButtonEdit"); // titles above all columns
417            default:
418                return "unknown"; // NOI18N
419        }
420    }
421
422    @Override
423    public Class<?> getColumnClass(int col) {
424        switch (col) {
425            case SELECT_COLUMN:
426                return Boolean.class;
427            case SET_COLUMN:
428            case EDIT_COLUMN:
429                return JButton.class;
430            case LENGTH_COLUMN:
431            case MOVES_COLUMN:
432                return Integer.class;
433            default:
434                return String.class;
435        }
436    }
437
438    @Override
439    public boolean isCellEditable(int row, int col) {
440        switch (col) {
441            case SELECT_COLUMN:
442            case SET_COLUMN:
443            case EDIT_COLUMN:
444            case MOVES_COLUMN:
445            case VALUE_COLUMN:
446            case RFID_COLUMN:
447                return true;
448            default:
449                return false;
450        }
451    }
452
453    @Override
454    public Object getValueAt(int row, int col) {
455        if (row >= getRowCount()) {
456            return "ERROR row " + row; // NOI18N
457        }
458        Engine engine = engineList.get(row);
459        if (engine == null) {
460            return "ERROR engine unknown " + row; // NOI18N
461        }
462        switch (col) {
463            case SELECT_COLUMN:
464                return engine.isSelected();
465            case NUM_COLUMN:
466                return engine.getNumber();
467            case ROAD_COLUMN:
468                return engine.getRoadName();
469            case LENGTH_COLUMN:
470                return engine.getLengthInteger();
471            case MODEL_COLUMN:
472                return engine.getModel();
473            case HP_COLUMN:
474                return engine.getHp();
475            case TYPE_COLUMN: {
476                if (engine.isBunit()) {
477                    return engine.getTypeName() + " " + Bundle.getMessage("(B)");
478                }
479                return engine.getTypeName();
480            }
481            case WEIGHT_COLUMN:
482                return engine.getWeightTons();
483            case CONSIST_COLUMN: {
484                if (engine.isLead()) {
485                    return engine.getConsistName() + "*";
486                }
487                return engine.getConsistName();
488            }
489            case LOCATION_COLUMN: {
490                String s = engine.getStatus();
491                if (!engine.getLocationName().equals(Engine.NONE)) {
492                    s = engine.getStatus() + engine.getLocationName() + " (" + engine.getTrackName() + ")";
493                }
494                return s;
495            }
496            case RFID_WHERE_LAST_SEEN_COLUMN: {
497                return engine.getWhereLastSeenName() +
498                        (engine.getTrackLastSeenName().equals(Engine.NONE) ? "" : " (" + engine.getTrackLastSeenName() + ")");
499            }
500            case RFID_WHEN_LAST_SEEN_COLUMN: {
501                return engine.getWhenLastSeenDate();
502            }
503            case DESTINATION_COLUMN: {
504                String s = "";
505                if (!engine.getDestinationName().equals(Engine.NONE)) {
506                    s = engine.getDestinationName() + " (" + engine.getDestinationTrackName() + ")";
507                }
508                return s;
509            }
510            case PREVIOUS_LOCATION_COLUMN: {
511                String s = "";
512                if (!engine.getLastLocationName().equals(Engine.NONE)) {
513                    s = engine.getLastLocationName() + " (" + engine.getLastTrackName() + ")";
514                }
515                return s;
516            }
517            case TRAIN_COLUMN: {
518                // if train was manually set by user add an asterisk
519                if (engine.getTrain() != null && engine.getRouteLocation() == null) {
520                    return engine.getTrainName() + "*";
521                }
522                return engine.getTrainName();
523            }
524            case MOVES_COLUMN:
525                return engine.getMoves();
526            case BUILT_COLUMN:
527                return engine.getBuilt();
528            case OWNER_COLUMN:
529                return engine.getOwnerName();
530            case VALUE_COLUMN:
531                return engine.getValue();
532            case RFID_COLUMN:
533                return engine.getRfid();
534            case LAST_COLUMN:
535                return engine.getSortDate();
536            case DCC_ADDRESS_COLUMN:
537                return engine.getDccAddress();
538            case COMMENT_COLUMN:
539                return engine.getComment();
540            case SET_COLUMN:
541                return Bundle.getMessage("Set");
542            case EDIT_COLUMN:
543                return Bundle.getMessage("ButtonEdit");
544            default:
545                return "unknown " + col; // NOI18N
546        }
547    }
548
549    EngineEditFrame engineEditFrame = null;
550    EngineSetFrame engineSetFrame = null;
551
552    @Override
553    public void setValueAt(Object value, int row, int col) {
554        Engine engine = engineList.get(row);
555        switch (col) {
556            case SELECT_COLUMN:
557                engine.setSelected(((Boolean) value).booleanValue());
558                break;
559            case MOVES_COLUMN:
560                try {
561                    engine.setMoves(Integer.parseInt(value.toString()));
562                } catch (NumberFormatException e) {
563                    log.error("move count must be a number");
564                }
565                break;
566            case BUILT_COLUMN:
567                engine.setBuilt(value.toString());
568                break;
569            case OWNER_COLUMN:
570                engine.setOwnerName(value.toString());
571                break;
572            case VALUE_COLUMN:
573                engine.setValue(value.toString());
574                break;
575            case RFID_COLUMN:
576                engine.setRfid(value.toString());
577                break;
578            case SET_COLUMN:
579                log.debug("Set engine location");
580                if (engineSetFrame != null) {
581                    engineSetFrame.dispose();
582                }
583                // use invokeLater so new window appears on top
584                SwingUtilities.invokeLater(() -> {
585                    engineSetFrame = new EngineSetFrame();
586                    engineSetFrame.initComponents();
587                    engineSetFrame.load(engine);
588                });
589                break;
590            case EDIT_COLUMN:
591                log.debug("Edit engine");
592                if (engineEditFrame != null) {
593                    engineEditFrame.dispose();
594                }
595                // use invokeLater so new window appears on top
596                SwingUtilities.invokeLater(() -> {
597                    engineEditFrame = new EngineEditFrame();
598                    engineEditFrame.initComponents();
599                    engineEditFrame.load(engine);
600                });
601                break;
602            default:
603                break;
604        }
605    }
606
607    public void dispose() {
608        log.debug("dispose EngineTableModel");
609        engineManager.removePropertyChangeListener(this);
610        removePropertyChangeEngines();
611        if (engineSetFrame != null) {
612            engineSetFrame.dispose();
613        }
614        if (engineEditFrame != null) {
615            engineEditFrame.dispose();
616        }
617    }
618
619    private void removePropertyChangeEngines() {
620        if (engineList != null) {
621            for (RollingStock rs : engineList) {
622                rs.removePropertyChangeListener(this);
623            }
624        }
625    }
626
627    @Override
628    public void propertyChange(PropertyChangeEvent e) {
629        if (Control.SHOW_PROPERTY) {
630            log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e
631                    .getNewValue());
632        }
633        if (e.getPropertyName().equals(EngineManager.LISTLENGTH_CHANGED_PROPERTY) ||
634                e.getPropertyName().equals(ConsistManager.LISTLENGTH_CHANGED_PROPERTY)) {
635            updateList();
636            fireTableDataChanged();
637        }
638        // Engine length, type, and HP are based on model, so multiple changes
639        else if (e.getPropertyName().equals(Engine.LENGTH_CHANGED_PROPERTY) ||
640                e.getPropertyName().equals(Engine.TYPE_CHANGED_PROPERTY) ||
641                e.getPropertyName().equals(Engine.HP_CHANGED_PROPERTY)) {
642            fireTableDataChanged();
643        }
644        // must be a engine change
645        else if (e.getSource().getClass().equals(Engine.class)) {
646            Engine engine = (Engine) e.getSource();
647            int row = engineList.indexOf(engine);
648            if (Control.SHOW_PROPERTY) {
649                log.debug("Update engine table row: {}", row);
650            }
651            if (row >= 0) {
652                fireTableRowsUpdated(row, row);
653            }
654        }
655    }
656
657    private final static Logger log = LoggerFactory.getLogger(EnginesTableModel.class);
658}