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