001package jmri.jmrit.operations.routes;
002
003import java.awt.BorderLayout;
004import java.awt.FlowLayout;
005import java.awt.event.ActionEvent;
006import java.awt.event.ActionListener;
007import java.beans.PropertyChangeEvent;
008import java.beans.PropertyChangeListener;
009import java.util.ArrayList;
010import java.util.List;
011
012import javax.swing.*;
013import javax.swing.colorchooser.AbstractColorChooserPanel;
014import javax.swing.table.TableCellEditor;
015
016import jmri.jmrit.operations.setup.Control;
017import jmri.jmrit.operations.setup.Setup;
018import jmri.util.swing.*;
019import jmri.util.table.ButtonEditor;
020import jmri.util.table.ButtonRenderer;
021
022/**
023 * Table Model for edit of route locations used by operations
024 *
025 * @author Daniel Boudreau Copyright (C) 2008, 2013, 2025
026 */
027public class RouteEditTableModel extends javax.swing.table.AbstractTableModel implements PropertyChangeListener {
028
029    // Defines the columns
030    private static final int ID_COLUMN = 0;
031    private static final int NAME_COLUMN = ID_COLUMN + 1;
032    private static final int TRAIN_DIRECTION_COLUMN = NAME_COLUMN + 1;
033    private static final int MAXMOVES_COLUMN = TRAIN_DIRECTION_COLUMN + 1;
034    private static final int RANDOM_CONTROL_COLUMN = MAXMOVES_COLUMN + 1;
035    private static final int PICKUP_COLUMN = RANDOM_CONTROL_COLUMN + 1;
036    private static final int DROP_COLUMN = PICKUP_COLUMN + 1;
037    private static final int LOCAL_COLUMN = DROP_COLUMN + 1;
038    private static final int TRAVEL_COLUMN = LOCAL_COLUMN + 1;
039    private static final int TIME_COLUMN = TRAVEL_COLUMN + 1;
040    private static final int MAXLENGTH_COLUMN = TIME_COLUMN + 1;
041    private static final int GRADE = MAXLENGTH_COLUMN + 1;
042    private static final int TRAINICONX = GRADE + 1;
043    private static final int TRAINICONY = TRAINICONX + 1;
044    private static final int COMMENT_COLUMN = TRAINICONY + 1;
045    private static final int UP_COLUMN = COMMENT_COLUMN + 1;
046    private static final int DOWN_COLUMN = UP_COLUMN + 1;
047    private static final int DELETE_COLUMN = DOWN_COLUMN + 1;
048
049    private static final int HIGHEST_COLUMN = DELETE_COLUMN + 1;
050
051    private JTable _table;
052    private Route _route;
053    private RouteEditFrame _frame;
054    List<RouteLocation> _routeList = new ArrayList<>();
055
056    public RouteEditTableModel() {
057        super();
058    }
059
060    public void setWait(boolean showWait) {
061        XTableColumnModel tcm = (XTableColumnModel) _table.getColumnModel();
062        tcm.setColumnVisible(tcm.getColumnByModelIndex(TRAVEL_COLUMN), showWait);
063        tcm.setColumnVisible(tcm.getColumnByModelIndex(TIME_COLUMN), !showWait);
064    }
065
066    private void updateList() {
067        if (_route == null) {
068            return;
069        }
070        // first, remove listeners from the individual objects
071        removePropertyChangeRouteLocations();
072        _routeList = _route.getLocationsBySequenceList();
073        // and add them back in
074        for (RouteLocation rl : _routeList) {
075            rl.addPropertyChangeListener(this);
076        }
077    }
078
079    protected void initTable(RouteEditFrame frame, JTable table, Route route) {
080        _frame = frame;
081        _table = table;
082        _route = route;
083        if (_route != null) {
084            _route.addPropertyChangeListener(this);
085        }
086        Setup.getDefault().addPropertyChangeListener(this);
087        initTable(table);
088    }
089
090    private void initTable(JTable table) {
091        // Use XTableColumnModel so we can control which columns are visible
092        XTableColumnModel tcm = new XTableColumnModel();
093        _table.setColumnModel(tcm);
094        _table.createDefaultColumnsFromModel();
095        // Install the button handlers
096        ButtonRenderer buttonRenderer = new ButtonRenderer();
097        TableCellEditor buttonEditor = new ButtonEditor(new javax.swing.JButton());
098        tcm.getColumn(COMMENT_COLUMN).setCellRenderer(buttonRenderer);
099        tcm.getColumn(COMMENT_COLUMN).setCellEditor(buttonEditor);
100        tcm.getColumn(UP_COLUMN).setCellRenderer(buttonRenderer);
101        tcm.getColumn(UP_COLUMN).setCellEditor(buttonEditor);
102        tcm.getColumn(DOWN_COLUMN).setCellRenderer(buttonRenderer);
103        tcm.getColumn(DOWN_COLUMN).setCellEditor(buttonEditor);
104        tcm.getColumn(DELETE_COLUMN).setCellRenderer(buttonRenderer);
105        tcm.getColumn(DELETE_COLUMN).setCellEditor(buttonEditor);
106        table.setDefaultRenderer(JComboBox.class, new jmri.jmrit.symbolicprog.ValueRenderer());
107        table.setDefaultEditor(JComboBox.class, new jmri.jmrit.symbolicprog.ValueEditor());
108
109        // set column preferred widths
110        table.getColumnModel().getColumn(ID_COLUMN).setPreferredWidth(40);
111        table.getColumnModel().getColumn(NAME_COLUMN).setPreferredWidth(150);
112        table.getColumnModel().getColumn(TRAIN_DIRECTION_COLUMN).setPreferredWidth(95);
113        table.getColumnModel().getColumn(MAXMOVES_COLUMN).setPreferredWidth(50);
114        table.getColumnModel().getColumn(RANDOM_CONTROL_COLUMN).setPreferredWidth(65);
115        table.getColumnModel().getColumn(PICKUP_COLUMN).setPreferredWidth(65);
116        table.getColumnModel().getColumn(DROP_COLUMN).setPreferredWidth(65);
117        table.getColumnModel().getColumn(LOCAL_COLUMN).setPreferredWidth(75);
118        table.getColumnModel().getColumn(TRAVEL_COLUMN).setPreferredWidth(65);
119        table.getColumnModel().getColumn(TIME_COLUMN).setPreferredWidth(65);
120        table.getColumnModel().getColumn(MAXLENGTH_COLUMN).setPreferredWidth(75);
121        table.getColumnModel().getColumn(GRADE).setPreferredWidth(50);
122        table.getColumnModel().getColumn(TRAINICONX).setPreferredWidth(35);
123        table.getColumnModel().getColumn(TRAINICONY).setPreferredWidth(35);
124        table.getColumnModel().getColumn(COMMENT_COLUMN).setPreferredWidth(70);
125        table.getColumnModel().getColumn(UP_COLUMN).setPreferredWidth(60);
126        table.getColumnModel().getColumn(DOWN_COLUMN).setPreferredWidth(70);
127        table.getColumnModel().getColumn(DELETE_COLUMN).setPreferredWidth(80);
128
129        _frame.loadTableDetails(table);
130        // does not use a table sorter
131        table.setRowSorter(null);
132
133        // turn off columns
134        tcm.setColumnVisible(tcm.getColumnByModelIndex(TIME_COLUMN), false);
135
136        updateList();
137    }
138
139    @Override
140    public int getRowCount() {
141        return _routeList.size();
142    }
143
144    @Override
145    public int getColumnCount() {
146        return HIGHEST_COLUMN;
147    }
148
149    @Override
150    public String getColumnName(int col) {
151        switch (col) {
152            case ID_COLUMN:
153                return Bundle.getMessage("Id");
154            case NAME_COLUMN:
155                return Bundle.getMessage("Location");
156            case TRAIN_DIRECTION_COLUMN:
157                return Bundle.getMessage("TrainDirection");
158            case MAXMOVES_COLUMN:
159                return Bundle.getMessage("MaxMoves");
160            case RANDOM_CONTROL_COLUMN:
161                return Bundle.getMessage("Random");
162            case PICKUP_COLUMN:
163                return Bundle.getMessage("Pickups");
164            case DROP_COLUMN:
165                return Bundle.getMessage("Drops");
166            case LOCAL_COLUMN:
167                return Bundle.getMessage("LocalMoves");
168            case TRAVEL_COLUMN:
169                return Bundle.getMessage("Travel");
170            case TIME_COLUMN:
171                return Bundle.getMessage("Time");
172            case MAXLENGTH_COLUMN:
173                return Bundle.getMessage("MaxLength");
174            case GRADE:
175                return Bundle.getMessage("Grade");
176            case TRAINICONX:
177                return Bundle.getMessage("X");
178            case TRAINICONY:
179                return Bundle.getMessage("Y");
180            case COMMENT_COLUMN:
181                return Bundle.getMessage("Comment");
182            case UP_COLUMN:
183                return Bundle.getMessage("Up");
184            case DOWN_COLUMN:
185                return Bundle.getMessage("Down");
186            case DELETE_COLUMN:
187                return Bundle.getMessage("ButtonDelete"); // titles above all columns
188            default:
189                return "unknown"; // NOI18N
190        }
191    }
192
193    @Override
194    public Class<?> getColumnClass(int col) {
195        switch (col) {
196            case ID_COLUMN:
197            case NAME_COLUMN:
198                return String.class;
199            case TRAVEL_COLUMN:
200            case MAXLENGTH_COLUMN:
201            case MAXMOVES_COLUMN:
202            case TRAINICONX:
203            case TRAINICONY:
204                return Integer.class;
205            case GRADE:
206                return Double.class;
207            case TRAIN_DIRECTION_COLUMN:
208            case RANDOM_CONTROL_COLUMN:
209            case PICKUP_COLUMN:
210            case DROP_COLUMN:
211            case LOCAL_COLUMN:
212            case TIME_COLUMN:
213                return JComboBox.class;
214            case COMMENT_COLUMN:
215            case UP_COLUMN:
216            case DOWN_COLUMN:
217            case DELETE_COLUMN:
218                return JButton.class;
219            default:
220                return null;
221        }
222    }
223
224    @Override
225    public boolean isCellEditable(int row, int col) {
226        switch (col) {
227            case DELETE_COLUMN:
228            case TRAIN_DIRECTION_COLUMN:
229            case MAXMOVES_COLUMN:
230            case RANDOM_CONTROL_COLUMN:
231            case PICKUP_COLUMN:
232            case DROP_COLUMN:
233            case LOCAL_COLUMN:
234            case TRAVEL_COLUMN:
235            case TIME_COLUMN:
236            case MAXLENGTH_COLUMN:
237            case GRADE:
238            case TRAINICONX:
239            case TRAINICONY:
240            case COMMENT_COLUMN:
241            case UP_COLUMN:
242            case DOWN_COLUMN:
243                return true;
244            default:
245                return false;
246        }
247    }
248
249    @Override
250    public Object getValueAt(int row, int col) {
251        if (row >= getRowCount()) {
252            return "ERROR unknown " + row; // NOI18N
253        }
254        RouteLocation rl = _routeList.get(row);
255        if (rl == null) {
256            return "ERROR unknown route location " + row; // NOI18N
257        }
258        switch (col) {
259            case ID_COLUMN:
260                return rl.getId();
261            case NAME_COLUMN:
262                return rl.getName();
263            case TRAIN_DIRECTION_COLUMN: {
264                JComboBox<String> cb = Setup.getTrainDirectionComboBox();
265                cb.setSelectedItem(rl.getTrainDirectionString());
266                return cb;
267            }
268            case MAXMOVES_COLUMN:
269                return rl.getMaxCarMoves();
270            case RANDOM_CONTROL_COLUMN: {
271                JComboBox<String> cb = getRandomControlComboBox();
272                cb.setSelectedItem(rl.getRandomControl());
273                return cb;
274            }
275            case PICKUP_COLUMN: {
276                JComboBox<String> cb = getYesNoComboBox();
277                cb.setSelectedItem(rl.isPickUpAllowed() ? Bundle.getMessage("yes") : Bundle.getMessage("no"));
278                return cb;
279            }
280            case DROP_COLUMN: {
281                JComboBox<String> cb = getYesNoComboBox();
282                cb.setSelectedItem(rl.isDropAllowed() ? Bundle.getMessage("yes") : Bundle.getMessage("no"));
283                return cb;
284            }
285            case LOCAL_COLUMN: {
286                JComboBox<String> cb = getYesNoComboBox();
287                cb.setSelectedItem(rl.isLocalMovesAllowed() ? Bundle.getMessage("yes") : Bundle.getMessage("no"));
288                return cb;
289            }
290            case TRAVEL_COLUMN: {
291                return rl.getWait() + Setup.getTravelTime();
292            }
293            case TIME_COLUMN: {
294                JComboBox<String> cb = getTimeComboBox();
295                cb.setSelectedItem(rl.getDepartureTime());
296                return cb;
297            }
298            case MAXLENGTH_COLUMN:
299                return rl.getMaxTrainLength();
300            case GRADE:
301                return rl.getGrade();
302            case TRAINICONX:
303                return rl.getTrainIconX();
304            case TRAINICONY:
305                return rl.getTrainIconY();
306            case COMMENT_COLUMN: {
307                if (rl.getComment().equals(RouteLocation.NONE)) {
308                    return Bundle.getMessage("Add");
309                } else {
310                    return Bundle.getMessage("ButtonEdit");
311                }
312            }
313            case UP_COLUMN:
314                return Bundle.getMessage("Up");
315            case DOWN_COLUMN:
316                return Bundle.getMessage("Down");
317            case DELETE_COLUMN:
318                return Bundle.getMessage("ButtonDelete");
319            default:
320                return "unknown " + col; // NOI18N
321        }
322    }
323
324    @Override
325    public void setValueAt(Object value, int row, int col) {
326        if (value == null) {
327            log.debug("Warning route table row {} still in edit", row);
328            return;
329        }
330        RouteLocation rl = _routeList.get(row);
331        if (rl == null) {
332            log.error("ERROR unknown route location for row: {}", row); // NOI18N
333        }
334        switch (col) {
335            case COMMENT_COLUMN:
336                setComment(rl);
337                break;
338            case UP_COLUMN:
339                moveUpRouteLocation(rl);
340                break;
341            case DOWN_COLUMN:
342                moveDownRouteLocation(rl);
343                break;
344            case DELETE_COLUMN:
345                deleteRouteLocation(rl);
346                break;
347            case TRAIN_DIRECTION_COLUMN:
348                setTrainDirection(value, rl);
349                break;
350            case MAXMOVES_COLUMN:
351                setMaxTrainMoves(value, rl);
352                break;
353            case RANDOM_CONTROL_COLUMN:
354                setRandomControlValue(value, rl);
355                break;
356            case PICKUP_COLUMN:
357                setPickup(value, rl);
358                break;
359            case DROP_COLUMN:
360                setDrop(value, rl);
361                break;
362            case LOCAL_COLUMN:
363                setLocal(value, rl);
364                break;
365            case TRAVEL_COLUMN:
366                setTravel(value, rl);
367                break;
368            case TIME_COLUMN:
369                setDepartureTime(value, rl);
370                break;
371            case MAXLENGTH_COLUMN:
372                setMaxTrainLength(value, rl);
373                break;
374            case GRADE:
375                setGrade(value, rl);
376                break;
377            case TRAINICONX:
378                setTrainIconX(value, rl);
379                break;
380            case TRAINICONY:
381                setTrainIconY(value, rl);
382                break;
383            default:
384                break;
385        }
386    }
387
388    private void moveUpRouteLocation(RouteLocation rl) {
389        log.debug("move location up");
390        _route.moveLocationUp(rl);
391    }
392
393    private void moveDownRouteLocation(RouteLocation rl) {
394        log.debug("move location down");
395        _route.moveLocationDown(rl);
396    }
397
398    private void deleteRouteLocation(RouteLocation rl) {
399        log.debug("Delete location");
400        _route.deleteLocation(rl);
401    }
402
403    private int _trainDirection = Setup.getDirectionInt(Setup.getTrainDirectionList().get(0));
404
405    public int getLastTrainDirection() {
406        return _trainDirection;
407    }
408
409    private void setTrainDirection(Object value, RouteLocation rl) {
410        _trainDirection = Setup.getDirectionInt((String) ((JComboBox<?>) value).getSelectedItem());
411        rl.setTrainDirection(_trainDirection);
412        // update train icon
413        rl.setTrainIconCoordinates();
414    }
415
416    private int _maxTrainMoves = Setup.getCarMoves();
417
418    public int getLastMaxTrainMoves() {
419        return _maxTrainMoves;
420    }
421
422    private void setMaxTrainMoves(Object value, RouteLocation rl) {
423        int moves = (int) value;
424        if (moves <= 500) {
425            rl.setMaxCarMoves(moves);
426            _maxTrainMoves = moves;
427        } else {
428            JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("MaximumLocationMoves"), Bundle
429                    .getMessage("CanNotChangeMoves"), JmriJOptionPane.ERROR_MESSAGE);
430        }
431    }
432
433    private void setRandomControlValue(Object value, RouteLocation rl) {
434        rl.setRandomControl((String) ((JComboBox<?>) value).getSelectedItem());
435    }
436
437    private void setDrop(Object value, RouteLocation rl) {
438        rl.setDropAllowed(
439                ((String) ((JComboBox<?>) value).getSelectedItem()).equals(Bundle.getMessage("yes")));
440    }
441
442    private void setPickup(Object value, RouteLocation rl) {
443        rl.setPickUpAllowed(
444                ((String) ((JComboBox<?>) value).getSelectedItem()).equals(Bundle.getMessage("yes")));
445    }
446    
447    private void setLocal(Object value, RouteLocation rl) {
448        rl.setLocalMovesAllowed(
449                ((String) ((JComboBox<?>) value).getSelectedItem()).equals(Bundle.getMessage("yes")));
450    }
451
452    private int _maxTrainLength = Setup.getMaxTrainLength();
453
454    public int getLastMaxTrainLength() {
455        return _maxTrainLength;
456    }
457
458    private void setTravel(Object value, RouteLocation rl) {
459        int wait = (int) value;
460        rl.setWait(wait - Setup.getTravelTime());
461    }
462
463    private void setDepartureTime(Object value, RouteLocation rl) {
464        rl.setDepartureTime(((String) ((JComboBox<?>) value).getSelectedItem()));
465    }
466
467    private void setMaxTrainLength(Object value, RouteLocation rl) {
468        int length = (int) value;
469        if (length < 0) {
470            log.error("Maximum departure length must be a postive number");
471            return;
472        }
473        if (length > Setup.getMaxTrainLength()) {
474            log.error("Maximum departure length can not exceed maximum train length");
475            JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("DepartureLengthNotExceed",
476                    length, Setup.getMaxTrainLength()), Bundle.getMessage("CanNotChangeMaxLength"),
477                    JmriJOptionPane.ERROR_MESSAGE);
478            return;
479        }
480        if (rl != _route.getTerminatesRouteLocation() &&
481                (length < 500 && Setup.getLengthUnit().equals(Setup.FEET) ||
482                        length < 160 && Setup.getLengthUnit().equals(Setup.METER))) {
483            // warn that train length might be too short
484            if (JmriJOptionPane.showConfirmDialog(null, Bundle.getMessage("LimitTrainLength",
485                    length, Setup.getLengthUnit().toLowerCase(), rl.getName()),
486                    Bundle.getMessage("WarningTooShort"),
487                    JmriJOptionPane.OK_CANCEL_OPTION) != JmriJOptionPane.OK_OPTION) {
488                return;
489            }
490        }
491        rl.setMaxTrainLength(length);
492        _maxTrainLength = length;
493    }
494
495    private void setGrade(Object value, RouteLocation rl) {
496        double grade = (Double) value;
497        if (grade <= 6 && grade >= -6) {
498            rl.setGrade(grade);
499        } else {
500            log.error("Maximum grade is 6 percent");
501            JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("MaxGrade"),
502                    Bundle.getMessage("CanNotChangeGrade"),
503                    JmriJOptionPane.ERROR_MESSAGE);
504        }
505    }
506
507    private void setTrainIconX(Object value, RouteLocation rl) {
508        int x = (int) value;
509        rl.setTrainIconX(x);
510    }
511
512    private void setTrainIconY(Object value, RouteLocation rl) {
513        int y = (int) value;
514        rl.setTrainIconY(y);
515    }
516
517    private void setComment(RouteLocation rl) {
518        // Create comment panel
519        final JDialog dialog = new JDialog();
520        dialog.setLayout(new BorderLayout());
521        dialog.setTitle(Bundle.getMessage("Comment") + " " + rl.getName());
522        final JTextArea commentTextArea = new JTextArea(5, 100);
523        JScrollPane commentScroller = new JScrollPane(commentTextArea);
524        dialog.add(commentScroller, BorderLayout.CENTER);
525        commentTextArea.setText(rl.getComment());
526
527        JPanel buttonPane = new JPanel();
528        buttonPane.setLayout(new FlowLayout(FlowLayout.CENTER));
529        dialog.add(buttonPane, BorderLayout.SOUTH);
530
531        // text color chooser
532        JPanel pTextColor = new JPanel();
533        pTextColor.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("TextColor")));
534        JColorChooser commentColorChooser = new JColorChooser(rl.getCommentColor());
535        AbstractColorChooserPanel commentColorPanels[] = {new SplitButtonColorChooserPanel()};
536        commentColorChooser.setChooserPanels(commentColorPanels);
537        commentColorChooser.setPreviewPanel(new JPanel());
538        pTextColor.add(commentColorChooser);
539        buttonPane.add(pTextColor);
540
541        JButton okayButton = new JButton(Bundle.getMessage("ButtonOK"));
542        okayButton.addActionListener(new ActionListener() {
543            @Override
544            public void actionPerformed(ActionEvent arg0) {
545                rl.setComment(commentTextArea.getText());
546                rl.setCommentColor(commentColorChooser.getColor());
547                dialog.dispose();
548                return;
549            }
550        });
551        buttonPane.add(okayButton);
552
553        JButton cancelButton = new JButton(Bundle.getMessage("ButtonCancel"));
554        cancelButton.addActionListener(new ActionListener() {
555            @Override
556            public void actionPerformed(ActionEvent arg0) {
557                dialog.dispose();
558                return;
559            }
560        });
561        buttonPane.add(cancelButton);
562
563        dialog.setModal(true);
564        dialog.pack();
565        dialog.setVisible(true);
566    }
567
568    private JComboBox<String> getYesNoComboBox() {
569        JComboBox<String> cb = new JComboBox<>();
570        cb.addItem(Bundle.getMessage("yes"));
571        cb.addItem(Bundle.getMessage("no"));
572        return cb;
573    }
574
575    private JComboBox<String> getRandomControlComboBox() {
576        JComboBox<String> cb = new JComboBox<>();
577        cb.addItem(RouteLocation.DISABLED);
578        // 10 to 100 by 10
579        for (int i = 10; i < 101; i = i + 10) {
580            cb.addItem(Integer.toString(i));
581        }
582        return cb;
583    }
584
585    protected JComboBox<String> getTimeComboBox() {
586        JComboBox<String> timeBox = new JComboBox<>();
587        String hour;
588        String minute;
589        timeBox.addItem("");
590        for (int i = 0; i < 24; i++) {
591            if (i < 10) {
592                hour = "0" + Integer.toString(i);
593            } else {
594                hour = Integer.toString(i);
595            }
596            for (int j = 0; j < 60; j += 1) {
597                if (j < 10) {
598                    minute = "0" + Integer.toString(j);
599                } else {
600                    minute = Integer.toString(j);
601                }
602
603                timeBox.addItem(hour + ":" + minute);
604            }
605        }
606        return timeBox;
607    }
608
609    // this table listens for changes to a route and it's locations
610    @Override
611    public void propertyChange(PropertyChangeEvent e) {
612        if (Control.SHOW_PROPERTY) {
613            log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e
614                    .getNewValue());
615        }
616        if (e.getPropertyName().equals(Route.LISTCHANGE_CHANGED_PROPERTY)) {
617            updateList();
618            fireTableDataChanged();
619        }
620        if (e.getPropertyName().equals(Setup.TRAIN_DIRECTION_PROPERTY_CHANGE) ||
621                e.getPropertyName().equals(Setup.TRAVEL_TIME_PROPERTY_CHANGE)) {
622            fireTableDataChanged();
623        }
624        if (e.getSource().getClass().equals(RouteLocation.class)) {
625            RouteLocation rl = (RouteLocation) e.getSource();
626            int row = _routeList.indexOf(rl);
627            if (Control.SHOW_PROPERTY) {
628                log.debug("Update route table row: {} id: {}", row, rl.getId());
629            }
630            if (row >= 0) {
631                fireTableRowsUpdated(row, row);
632            }
633        }
634    }
635
636    private void removePropertyChangeRouteLocations() {
637        for (RouteLocation rl : _routeList) {
638            rl.removePropertyChangeListener(this);
639        }
640    }
641
642    public void dispose() {
643        removePropertyChangeRouteLocations();
644        if (_route != null) {
645            _route.removePropertyChangeListener(this);
646        }
647        Setup.getDefault().removePropertyChangeListener(this);
648        _routeList.clear();
649        fireTableDataChanged();
650    }
651
652    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(RouteEditTableModel.class);
653}