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