001package jmri.jmrit.logix;
002
003import java.awt.Color;
004import java.util.ArrayList;
005import java.util.Iterator;
006
007import javax.annotation.Nonnull;
008import javax.swing.ButtonGroup;
009import javax.swing.JButton;
010import javax.swing.JRadioButton;
011import javax.swing.JTable;
012import javax.swing.JTextField;
013
014import jmri.InstanceManager;
015import jmri.Manager;
016import jmri.jmrit.catalog.NamedIcon;
017import jmri.util.swing.JmriJOptionPane;
018import jmri.util.ThreadingUtil;
019
020/**
021 * Table Model for the Warrant List
022 * <br>
023 * <hr>
024 * This file is part of JMRI.
025 * <p>
026 * JMRI is free software; you can redistribute it and/or modify it under the
027 * terms of version 2 of the GNU General Public License as published by the Free
028 * Software Foundation. See the "COPYING" file for a copy of this license.
029 * <p>
030 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
031 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
032 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
033 *
034 * @author Pete Cressman Copyright (C) 2009, 2010
035 */
036
037class WarrantTableModel extends jmri.jmrit.beantable.BeanTableDataModel<Warrant> {
038    public static final int WARRANT_COLUMN = 0;
039    public static final int ROUTE_COLUMN = 1;
040    public static final int TRAIN_NAME_COLUMN = 2;
041    public static final int ADDRESS_COLUMN = 3;
042    public static final int ALLOCATE_COLUMN = 4;
043    public static final int DEALLOC_COLUMN = 5;
044    public static final int AUTO_RUN_COLUMN = 6;
045    public static final int MANUAL_RUN_COLUMN = 7;
046    public static final int CONTROL_COLUMN = 8;
047    public static final int EDIT_COLUMN = 9;
048    public static final int DELETE_COLUMN = 10;
049    public static final int NUMCOLS = 11;
050
051    WarrantManager _manager;
052    WarrantTableFrame _frame;
053    private ArrayList<Warrant> _warList;
054    private final ArrayList<Warrant> _warNX; // temporary warrants appended to table
055    static Color myGreen = new Color(0, 100, 0);
056    static Color myGold = new Color(200, 100, 0);
057
058    public WarrantTableModel(WarrantTableFrame frame) {
059        super();
060        _frame = frame;
061        _manager = InstanceManager.getDefault(WarrantManager.class);
062        _manager.addPropertyChangeListener(WarrantTableModel.this); // for adds and deletes
063        _warList = new ArrayList<>();
064        _warNX = new ArrayList<>();
065    }
066
067    public void addHeaderListener(JTable table) {
068        addMouseListenerToHeader(table);
069    }
070
071    @Override
072    public Manager<Warrant> getManager() {
073        _manager = InstanceManager.getDefault(WarrantManager.class);
074        return _manager;
075    }
076
077    @Override
078    public Warrant getBySystemName(@Nonnull String name) {
079        return _manager.getBySystemName(name);
080    }
081
082    @Override
083    public String getValue(String name) {
084        Warrant w = _manager.getBySystemName(name);
085        if (w == null) {
086            return null;
087        }
088        return w.getDisplayName();
089    }
090
091    @Override
092    public Warrant getByUserName(@Nonnull String name) {
093        return _manager.getByUserName(name);
094    }
095
096    @Override
097    protected String getBeanType() {
098        return "Warrant";
099    }
100
101    @Override
102    public void clickOn(Warrant t) {
103    }
104
105    @Override
106    protected String getMasterClassName() {
107        return WarrantTableModel.class.getName();
108    }
109
110    /**
111     * Preserve current listeners so that there is no gap to miss a
112     * propertyChange
113     */
114    public synchronized void init() {
115        ArrayList<Warrant> tempList = new ArrayList<>();
116        // copy over warrants still listed
117        for (Warrant w : _manager.getNamedBeanSet()) {
118            if (!_warList.contains(w)) { // new warrant
119                w.addPropertyChangeListener(this);
120            } else {
121                _warList.remove(w);
122            }
123            cleanBlockOrderList(w); // removes bad BlockOrders
124            tempList.add(w); // add old or any new warrants
125        }
126        // remove listeners from any deleted warrants
127        for (int i = 0; i < _warList.size(); i++) {
128            Warrant w = _warList.get(i);
129            if (!_warNX.contains(w)) { // don't touch current running NXWarrant
130                w.removePropertyChangeListener(this);
131            }
132        }
133        // add in current temporary NX warrants
134        for (int i = 0; i < _warNX.size(); i++) {
135            tempList.add(_warNX.get(i));
136        }
137        _warList = tempList;
138    }
139
140    private void cleanBlockOrderList(Warrant warrant) {
141        ArrayList<BlockOrder> valid = new ArrayList<>();
142        Iterator<BlockOrder> iter = warrant.getBlockOrders().iterator();
143        while (iter.hasNext()) {
144            BlockOrder bo = iter.next();
145            if (WarrantRoute.pathIsValid(bo.getBlock(), bo.getPathName()) == null) {
146                valid.add(bo);
147            }
148        }
149        warrant.setBlockOrders(valid);
150    }
151
152    protected void haltAllTrains() {
153        ArrayList<Warrant> abortList = new ArrayList<>();
154        Iterator<Warrant> iter = _warList.iterator();
155        while (iter.hasNext()) {
156            Warrant w = iter.next();
157            if (w.getState() >= 0) {
158                abortList.add(w);
159            }
160        }
161        iter = abortList.iterator();
162        while (iter.hasNext()) {
163            iter.next().controlRunTrain(Warrant.STOP);
164        }
165        fireTableUpdate();
166    }
167
168    protected void addNXWarrant(Warrant w) {
169        _warList.add(w);
170        _warNX.add(w);
171        w.addPropertyChangeListener(this);
172        fireTableUpdate();
173    }
174
175    /**
176     * Removes any warrant, not just NXWarrant
177     * @param w Warrant
178     * @param deregister deregister warrant
179     */
180    public void removeWarrant(Warrant w, boolean deregister) {
181        log.debug("removeWarrant {}", w.getDisplayName());
182        _warList.remove(w);
183        _warNX.remove(w);
184        if (deregister) {
185            _manager.deregister(w);
186        }
187        w.dispose();
188    }
189
190    public Warrant getWarrantAt(int index) {
191        if (index >= _warList.size()) {
192            return null;
193        }
194        return _warList.get(index);
195    }
196    
197    protected Warrant getWarrant(String name) {
198        if (name==null || name.length()==0) {
199            return null;
200        }
201        for (Warrant w: _warList) {
202            if (name.equals(w.getUserName()) || name.equals(w.getSystemName())) {
203                return w;
204            }
205        }
206        return null;
207    }
208     
209    protected String checkAddressInUse(Warrant warrant) {
210        jmri.DccLocoAddress address = warrant.getSpeedUtil().getDccAddress();
211
212        if (address ==null) {
213            return Bundle.getMessage("NoLoco");
214        }
215        for (Warrant w :_warList) {
216            if (w.equals(warrant)) {
217                continue;
218            }
219            if (w._runMode != Warrant.MODE_NONE) {
220                if (address.equals(w.getSpeedUtil().getDccAddress())) {
221                    return Bundle.getMessage("AddressInUse", address, w.getDisplayName(), w.getTrainName());
222                }
223            }
224        }
225        return null;
226    }
227
228    @Override
229    public int getRowCount() {
230        return _warList.size();
231    }
232    
233    protected int getRow(Warrant w) {
234        int row = -1;
235        Iterator<Warrant> iter = _warList.iterator();
236        while (iter.hasNext()) {
237            row++;
238            Warrant war = iter.next();
239            if (war.equals(w)) {
240                return row;
241            }
242        }
243        return -1;
244    }
245
246    @Override
247    public int getColumnCount() {
248        return NUMCOLS;
249    }
250
251    @Override
252    public String getColumnName(int col) {
253        switch (col) {
254        case WARRANT_COLUMN:
255            return Bundle.getMessage("Warrant");
256        case ROUTE_COLUMN:
257            return Bundle.getMessage("Route");
258        case TRAIN_NAME_COLUMN:
259            return Bundle.getMessage("TrainName");
260        case ADDRESS_COLUMN:
261            return Bundle.getMessage("DccAddress");
262        case ALLOCATE_COLUMN:
263            return Bundle.getMessage("Allocate");
264        case DEALLOC_COLUMN:
265            return Bundle.getMessage("Deallocate");
266        case AUTO_RUN_COLUMN:
267            return Bundle.getMessage("ARun");
268        case MANUAL_RUN_COLUMN:
269            return Bundle.getMessage("MRun");
270        case CONTROL_COLUMN:
271            return Bundle.getMessage("Control");
272        default:
273            // fall out
274            break;
275        }
276        return "";
277    }
278
279    @Override
280    public boolean isCellEditable(int row, int col) {
281        switch (col) {
282        case WARRANT_COLUMN:
283            return false;
284        case TRAIN_NAME_COLUMN:
285        case ADDRESS_COLUMN:
286        case ROUTE_COLUMN:
287        case ALLOCATE_COLUMN:
288        case DEALLOC_COLUMN:
289        case AUTO_RUN_COLUMN:
290        case MANUAL_RUN_COLUMN:
291        case CONTROL_COLUMN:
292        case EDIT_COLUMN:
293        case DELETE_COLUMN:
294            return true;
295        default:
296            // fall out
297            break;
298        }
299        return false;
300    }
301
302    @Override
303    public Class<?> getColumnClass(int col) {
304        switch (col) {
305        case WARRANT_COLUMN:
306            return String.class;
307        case ROUTE_COLUMN:
308            return String.class; // JComboBox.class;
309        case TRAIN_NAME_COLUMN:
310            return String.class;
311        case ADDRESS_COLUMN:
312            return String.class;
313        case ALLOCATE_COLUMN:
314            return JButton.class;
315        case DEALLOC_COLUMN:
316            return JButton.class;
317        case AUTO_RUN_COLUMN:
318            return JButton.class;
319        case MANUAL_RUN_COLUMN:
320            return JButton.class;
321        case CONTROL_COLUMN:
322            return String.class; // JComboBox.class;
323        case EDIT_COLUMN:
324            return JButton.class;
325        case DELETE_COLUMN:
326            return JButton.class;
327        default:
328            // fall out
329            break;
330        }
331        return String.class;
332    }
333
334    @Override
335    public int getPreferredWidth(int col) {
336        switch (col) {
337        case WARRANT_COLUMN:
338        case TRAIN_NAME_COLUMN:
339        case ADDRESS_COLUMN:
340            return new JTextField(13).getPreferredSize().width;
341        case ROUTE_COLUMN:
342            return new JTextField(25).getPreferredSize().width;
343        case ALLOCATE_COLUMN:
344        case DEALLOC_COLUMN:
345        case AUTO_RUN_COLUMN:
346        case MANUAL_RUN_COLUMN:
347            return new JButton("Xxxx").getPreferredSize().width;
348        case CONTROL_COLUMN:
349            return new JTextField(60).getPreferredSize().width;
350        case EDIT_COLUMN:
351            return new JButton(Bundle.getMessage("ButtonEdit")).getPreferredSize().width;
352        case DELETE_COLUMN:
353            return new JButton(Bundle.getMessage("ButtonDelete")).getPreferredSize().width;
354        default:
355            // fall out
356            break;
357        }
358        return new JTextField(10).getPreferredSize().width;
359    }
360
361    static String GREEN_LED = "resources/icons/smallschematics/tracksegments/circuit-green.gif";
362    static String YELLOW_LED = "resources/icons/smallschematics/tracksegments/circuit-occupied.gif";
363    static String OFF_LED = "resources/icons/smallschematics/tracksegments/circuit-empty.gif";
364    static String RED_LED = "resources/icons/smallschematics/tracksegments/circuit-error.gif";
365
366    @Override
367    public Object getValueAt(int row, int col) {
368        Warrant w = getWarrantAt(row);
369        // some error checking
370        if (w == null) { // if NXWarrant is aborted while save/edit frame is open, closing frame causes update
371//            log.warn("getValueAt row= {}, Warrant is null!", row);
372            return "";
373        }
374        JRadioButton allocButton = new JRadioButton();
375        JRadioButton deallocButton = new JRadioButton();
376        ButtonGroup group = new ButtonGroup();
377        group.add(allocButton);
378        group.add(deallocButton);
379        switch (col) {
380        case WARRANT_COLUMN:
381            return w.getDisplayName();
382        case ROUTE_COLUMN:
383            BlockOrder bo0 = w.getfirstOrder();
384            BlockOrder bo1 = w.getLastOrder();
385            return Bundle.getMessage("WarrantRoute", 
386                        (bo0==null?"?":bo0.getBlock().getDisplayName()),
387                        (bo1==null?"?":bo1.getBlock().getDisplayName()));
388        case TRAIN_NAME_COLUMN:
389            return w.getTrainName();
390        case ADDRESS_COLUMN:
391            return w.getSpeedUtil().getRosterId();
392        case ALLOCATE_COLUMN:
393            NamedIcon icon;
394            if (w.isTotalAllocated()) {
395                icon = new NamedIcon(GREEN_LED, "green");
396            } else if (w.isAllocated()) {
397                icon = new NamedIcon(YELLOW_LED, "yellow");
398            } else {
399                icon = new NamedIcon(OFF_LED, "off");
400            }
401            return icon;
402        case DEALLOC_COLUMN:
403            if (w.isAllocated()) {
404                icon = new NamedIcon(OFF_LED, "off");
405            } else {
406                icon = new NamedIcon(YELLOW_LED, "occupied");
407            }
408            return icon;
409        case AUTO_RUN_COLUMN:
410            if (w.getRunMode() == Warrant.MODE_RUN) {
411                icon = new NamedIcon(RED_LED, "red");
412            } else {
413                icon = new NamedIcon(OFF_LED, "off");
414            }
415            return icon;
416        case MANUAL_RUN_COLUMN:
417            if (w.getRunMode() == Warrant.MODE_MANUAL) {
418                return new NamedIcon(
419                        "resources/icons/smallschematics/tracksegments/circuit-error.gif",
420                        "red");
421            }
422            return new NamedIcon(
423                    "resources/icons/smallschematics/tracksegments/circuit-empty.gif",
424                    "off");
425        case CONTROL_COLUMN:
426            String msg = w.getRunningMessage();
427            return msg;
428        case EDIT_COLUMN:
429            if (w.isNXWarrant()) {
430                return Bundle.getMessage("ButtonSave");
431            } else {
432                return Bundle.getMessage("ButtonEdit");
433            }
434        case DELETE_COLUMN:
435            return Bundle.getMessage("ButtonDelete");
436        default:
437            // fall out
438            break;
439        }
440        return "";
441    }
442
443    @Override
444    public void setValueAt(Object value, int row, int col) {
445        Warrant w = getWarrantAt(row);
446        if (w == null) {
447            log.warn("setValueAt row= {}, Warrant is null!", row);
448            return;
449        }
450        String msg = null;
451        switch (col) {
452        case WARRANT_COLUMN:
453        case ROUTE_COLUMN:
454            return;
455        case TRAIN_NAME_COLUMN:
456            w.setTrainName((String) value);
457            break;
458        case ADDRESS_COLUMN:
459            if (w.getRunMode() == Warrant.MODE_NONE) {
460                String addr = (String) value;
461                if (!w.getSpeedUtil().setAddress(addr)) {
462                    msg = Bundle.getMessage("BadDccAddress", addr);                
463                }
464            } else {
465                msg = w.getRunModeMessage();
466                msg = Bundle.getMessage("CannotChangeAddress", w.getDisplayName(), msg);
467            }
468            break;
469        case ALLOCATE_COLUMN:
470            if (w.getRunMode() == Warrant.MODE_NONE) {
471                msg = w.allocateRoute(true, null);
472                if (msg == null) {
473                    setFrameStatusText(
474                            Bundle.getMessage("completeAllocate",
475                                    w.getDisplayName()), myGreen, false);
476                } else {
477                    setFrameStatusText(Bundle.getMessage("UnableToAllocate",
478                            w.getDisplayName()) + msg, myGold, false);
479                    msg = null;
480                }
481                this.fireTableRowsUpdated(row, row);
482            }
483            break;
484        case DEALLOC_COLUMN:
485            if (w.getRunMode() == Warrant.MODE_NONE) {
486                w.deAllocate();
487                setFrameStatusText("", myGreen, false);
488                this.fireTableRowsUpdated(row, row);
489            } else {
490                setFrameStatusText(w.getRunModeMessage(), myGold, false);
491            }
492            break;
493        case AUTO_RUN_COLUMN:
494            msg = frameRunTrain(w, Warrant.MODE_RUN); 
495            this.fireTableRowsUpdated(row, row);
496            break;
497        case MANUAL_RUN_COLUMN:
498            msg = frameRunTrain(w, Warrant.MODE_MANUAL);
499            this.fireTableRowsUpdated(row, row);
500            break;
501        case CONTROL_COLUMN:
502            // Message is set when propertyChangeEvent (below) is received from
503            // a warrant change. fireTableRows then causes getValueAt() which
504            // calls getRunningMessage()
505            int mode = w.getRunMode();
506            if (log.isTraceEnabled()) {
507                log.debug("setValueAt({}) for warrant {}", value, w.getDisplayName());
508            }
509            if (mode == Warrant.MODE_LEARN) {
510                msg = Bundle.getMessage("Learning", w.getCurrentBlockName());
511            } else if (value!=null) {
512                String setting = (String) value;
513                if (mode == Warrant.MODE_RUN || mode == Warrant.MODE_MANUAL) {
514                    int s = -1;
515                    if (setting.equals(WarrantTableFrame.stop)) {
516                        s = Warrant.STOP;
517                    } else if (setting.equals(WarrantTableFrame.ramp)) {
518                        s = Warrant.HALT;
519                    } else if (setting.equals(WarrantTableFrame.resume)) {
520                        s = Warrant.RESUME;
521                    } else if (setting.equals(WarrantTableFrame.speedup)) {
522                        s = Warrant.SPEED_UP;
523                    } else if (setting.equals(WarrantTableFrame.retryfwd)) {
524                        s = Warrant.RETRY_FWD;
525                    } else if (setting.equals(WarrantTableFrame.estop)) {
526                        s = Warrant.ESTOP;
527                    } else if (setting.equals(WarrantTableFrame.abort)) {
528                        s = Warrant.ABORT;
529                    } else if (setting.isEmpty()) {
530                        s = Warrant.DEBUG;
531                    } else if (setting.equals(WarrantTableFrame.retrybkwd)) {
532                        s = Warrant.RETRY_BKWD;
533                    }
534                    if (s >= 0) {
535                        w.controlRunTrain(s);
536                    }
537                }
538            }
539            break;
540        case EDIT_COLUMN:
541            if (w.isNXWarrant()) {
542                saveNXWarrant(w);                
543            } else {
544                openWarrantFrame(w);                
545            }
546            break;
547        case DELETE_COLUMN:
548            if (w.getRunMode() == Warrant.MODE_NONE) {
549                w.deAllocate();
550                fireTableRowDeleted(w, row, true);
551            } else {
552                w.controlRunTrain(Warrant.ABORT);
553                if (_warNX.contains(w)) { // don't remove regular warrants
554                    fireTableRowDeleted(w, row, false);
555                }
556            }
557            break;
558        default:
559            log.error("Invalid Column {} requested.", col);
560           throw new java.lang.IllegalArgumentException("Invalid Column " + col + " requested.");
561        }
562        if (msg != null) {
563            showMessageDialog(msg);
564        }
565    }
566
567    private void showMessageDialog(String msg) {
568        ThreadingUtil.runOnGUIEventually(() ->
569            JmriJOptionPane.showMessageDialog(_frame, msg,
570                    Bundle.getMessage("WarningTitle"),
571                    JmriJOptionPane.WARNING_MESSAGE));
572    }
573
574    private void openWarrantFrame(Warrant warrant) {
575        for (Warrant w : _warList) {
576            if (warrant.equals(w)) {
577                WarrantTableAction.getDefault().editWarrantFrame(warrant);
578                break;
579            }
580        }
581    }
582
583    private void saveNXWarrant(Warrant warrant) {
584        for (Warrant w : _warNX) {
585            if (warrant.equals(w)) {
586                Warrant war = cloneWarrant(warrant);
587                WarrantTableAction.getDefault().makeWarrantFrame(war, null);
588                break;
589            }
590        }
591    }
592
593    private Warrant cloneWarrant(Warrant warrant) {
594        Warrant w = new Warrant(InstanceManager.getDefault(WarrantManager.class).getAutoSystemName(), null);
595        w.setTrainName(warrant.getTrainName());
596        w.setRunBlind(warrant.getRunBlind());
597        w.setShareRoute(warrant.getShareRoute());
598        w.setAddTracker(warrant.getAddTracker());
599        w.setNoRamp(warrant.getNoRamp());
600
601        for (BlockOrder bo : warrant.getBlockOrders()) {
602            w.addBlockOrder(new BlockOrder(bo));
603        }
604        w.setViaOrder(warrant.getViaOrder());
605        w.setAvoidOrder(warrant.getAvoidOrder());
606        for (ThrottleSetting ts : warrant.getThrottleCommands()) {
607            w.addThrottleCommand(ts);
608        }
609        SpeedUtil copySU = w.getSpeedUtil();
610        SpeedUtil su = warrant.getSpeedUtil();
611        copySU.setDccAddress(su.getDccAddress());
612        copySU.setRosterId(su.getRosterId());
613        return w;
614    }
615
616    private String frameRunTrain(Warrant w, int mode) {
617        return jmri.util.ThreadingUtil.runOnGUIwithReturn(() -> {
618            return ( _frame.runTrain(w, mode));
619        });
620    }
621
622    private void setFrameStatusText(String m, Color c, boolean save) {
623        ThreadingUtil.runOnGUIEventually(()-> _frame.setStatusText(m, c, true));
624    }
625
626    private void fireCellUpdate(int row, int col) {
627        ThreadingUtil.runOnGUIEventually(()-> {
628            if (row < _warList.size()) {  // when Aborted, row may be gone by now
629                fireTableCellUpdated(row, col);
630            }
631        });
632    }
633
634    private void fireTableUpdate() {
635        ThreadingUtil.runOnGUIEventually(this::fireTableDataChanged);
636    }
637
638    private void fireTableRowDeleted(Warrant w, int row, boolean all) {
639        ThreadingUtil.runOnGUIEventually(()-> {
640            removeWarrant(w, all);  // true any warrant, false NX only  
641            if (row < _warList.size()) {
642                fireTableRowsDeleted(row, row);
643            }
644        });
645    }
646
647    private void fireTableRowUpdated(Warrant w, int row) {
648        if (row < _warList.size()) {
649            ThreadingUtil.runOnGUIEventually(()-> fireTableRowsUpdated(row, row));
650        }
651    }
652
653    private String _lastProperty;
654    private long _propertyTime;
655
656    @Override
657    public void propertyChange(java.beans.PropertyChangeEvent e) {
658        String property = e.getPropertyName();
659        long time = _propertyTime;
660        _propertyTime = System.currentTimeMillis();
661        if ((_propertyTime-time)<20 && property.equals(_lastProperty) && !property.equals("length")) {
662            return;
663        }
664        _lastProperty = property;
665        
666        if (property.equals("length")) {
667            // a NamedBean added or deleted
668            init();
669            fireTableUpdate();
670        } else if (e.getSource() instanceof Warrant) {
671            // a value changed. Find it, to avoid complete redraw
672            Warrant bean = (Warrant) e.getSource();
673            log.debug("source is warrant {}", bean.getDisplayName());
674            int row = getRow(bean);
675            if (row < 0) { // warrant deleted
676                return;
677            }
678            fireTableRowUpdated(bean, row);
679
680            if (property.equals("blockChange")) {
681                OBlock oldBlock = (OBlock) e.getOldValue();
682                OBlock newBlock = (OBlock) e.getNewValue();
683                if (newBlock == null) {
684                    setFrameStatusText(Bundle.getMessage("ChangedRoute",
685                            bean.getTrainName(), oldBlock.getDisplayName(),
686                            bean.getDisplayName()), Color.red, true);
687                } else {
688                    setFrameStatusText(Bundle.getMessage("TrackerBlockEnter",
689                            bean.getTrainName(), 
690                            newBlock.getDisplayName()), myGreen, true);
691                }
692            } else if (property.equals("SpeedChange")) {
693                fireCellUpdate(row, CONTROL_COLUMN);
694            } else if (property.equals("WaitForSync")) {
695                fireCellUpdate(row, CONTROL_COLUMN);
696            } else if (property.equals("cannotRun")) {
697                fireCellUpdate(row, CONTROL_COLUMN);
698                setFrameStatusText(Bundle.getMessage("trainWaiting", bean.getTrainName(),
699                        e.getNewValue(), e.getOldValue()), Color.red, true);
700            } else if (property.equals("SignalOverrun")) {
701                String name = (String) e.getOldValue();
702                String speed = (String) e.getNewValue();
703                setFrameStatusText(Bundle.getMessage("SignalOverrun",
704                        bean.getTrainName(), speed, name), Color.red, true);
705            } else if (property.equals("OccupyOverrun")) {
706                String blkName = (String) e.getOldValue();
707                String occuppier = (String) e.getNewValue();
708                setFrameStatusText(Bundle.getMessage("OccupyOverrun",
709                        bean.getTrainName(), blkName, occuppier), Color.red, true);
710            } else if (property.equals("WarrantOverrun")) {
711                String blkName = (String) e.getOldValue();
712                String warName = (String) e.getNewValue();
713                setFrameStatusText(Bundle.getMessage("WarrantOverrun",
714                        bean.getTrainName(), blkName, warName), Color.red, true);
715            } else if (property.equals("WarrantStart")) {
716                setFrameStatusText(Bundle.getMessage("warrantStart",
717                        bean.getTrainName(), bean.getDisplayName(),
718                        bean.getCurrentBlockName()), myGreen, true);
719            } else if (property.equals("StopWarrant")) {
720                String blkName = (String) e.getOldValue();
721                String bundleKey = (String) e.getNewValue();
722                if (blkName == null) {
723                    setFrameStatusText(Bundle.getMessage(bundleKey,
724                            bean.getTrainName(), bean.getDisplayName()),
725                            myGreen, true);                        
726                } else {
727                    setFrameStatusText(Bundle.getMessage(bundleKey,
728                            bean.getTrainName(), bean.getDisplayName(), 
729                            blkName), myGold, true);
730                }
731                if (_warNX.contains(bean)) {
732                    fireTableRowDeleted(bean, row, false);
733                }
734            } else if (property.equals("RampDone")) {
735                boolean halt = ((Boolean) e.getOldValue());
736                String speed = (String) e.getNewValue();
737                if (halt || speed.equals(Warrant.EStop))  {
738                    setFrameStatusText(Bundle.getMessage("RampHalt",
739                            bean.getTrainName(), bean.getCurrentBlockName()), myGreen, true);
740                } else {
741                    setFrameStatusText(Bundle.getMessage("RampSpeed", bean.getTrainName(), 
742                            speed, bean.getCurrentBlockName()), myGreen, true);
743                }
744                fireCellUpdate(row, CONTROL_COLUMN);
745            } else if (property.equals("RampBegin")) {
746                String reason = (String) e.getOldValue();
747                String blkName = (String) e.getNewValue();
748                setFrameStatusText(Bundle.getMessage("RampBegin", bean.getTrainName(),
749                        reason, blkName), myGreen, true);
750            } else if (property.equals("ReadyToRun")) {
751                setFrameStatusText(Bundle.getMessage("TrainReady",
752                    bean.getTrainName(), bean.getCurrentBlockName()), myGreen, true);
753            } else if (property.equals("controlChange")) {
754                String blkName = bean.getCurrentBlockName();
755                int newCntrl = ((Integer) e.getNewValue());
756                setFrameStatusText(Bundle.getMessage("controlChange", bean.getTrainName(),
757                        Bundle.getMessage(Warrant.CNTRL_CMDS[newCntrl]), blkName),
758                        myGold, true);
759                fireCellUpdate(row, CONTROL_COLUMN);
760            } else if (property.equals("controlFailed")) {
761                String blkName = bean.getCurrentBlockName();
762                String stateStr;
763                if (e.getOldValue()==null) {
764                    stateStr = blkName;
765                } else {
766                    stateStr = ((String) e.getOldValue());
767                }
768                int newCntrl = ((Integer) e.getNewValue());
769                setFrameStatusText(Bundle.getMessage("controlFailed",
770                        bean.getTrainName(), stateStr,
771                        Bundle.getMessage(Warrant.CNTRL_CMDS[newCntrl])),
772                        Color.red, true);
773                fireCellUpdate(row, CONTROL_COLUMN);
774            } else if (property.equals("SensorSetCommand")) {
775                String action = (String) e.getOldValue();
776                String sensorName = (String) e.getNewValue();
777                setFrameStatusText(Bundle.getMessage("setSensor",
778                            bean.getTrainName(), sensorName, action), myGreen, true);
779            } else if (property.equals("SensorWaitCommand")) {
780                String action = (String) e.getOldValue();
781                String sensorName = (String) e.getNewValue();
782                if (action != null) {
783                    setFrameStatusText(Bundle.getMessage("waitSensor",
784                            bean.getTrainName(), sensorName, action), myGreen, true);
785                } else {
786                    setFrameStatusText(Bundle.getMessage("waitSensorChange",
787                            bean.getTrainName(), sensorName), myGreen, true);
788                }
789                fireCellUpdate(row, CONTROL_COLUMN);                    
790            } else if (property.equals("throttleFail")) {
791                setFrameStatusText(Bundle.getMessage("ThrottleFail",
792                        bean.getTrainName(), e.getNewValue()), Color.red, true);
793            }
794            if (log.isDebugEnabled()) {
795                log.debug("propertyChange of \"{}\" done for warrant \"{}\"",
796                        property, bean.getDisplayName());
797            }
798        }
799    }
800
801    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(WarrantTableModel.class);
802
803}