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