001package jmri.jmrit.display;
002
003import java.awt.Color;
004import java.awt.Dimension;
005import java.awt.Font;
006import java.awt.Point;
007import java.util.ArrayList;
008import jmri.Sensor;
009import jmri.jmrit.display.controlPanelEditor.shape.LocoLabel;
010import jmri.jmrit.logix.OBlock;
011import org.slf4j.Logger;
012import org.slf4j.LoggerFactory;
013
014/**
015 * A utility class replacing common methods formerly implementing the
016 * IndicatorTrack interface.
017 *
018 * @author Pete Cressman Copyright (c) 2012
019 */
020public class IndicatorTrackPaths {
021
022    protected ArrayList<String> _paths;      // list of paths that this icon displays
023    private boolean _showTrain;         // this track icon should display _loco when occupied
024    private LocoLabel _loco = null;
025
026    protected IndicatorTrackPaths() {
027    }
028
029    protected IndicatorTrackPaths deepClone() {
030        IndicatorTrackPaths p = new IndicatorTrackPaths();
031        if (_paths != null) {
032            p._paths = new ArrayList<>();
033            for (int i = 0; i < _paths.size(); i++) {
034                p._paths.add(_paths.get(i));
035            }
036        }
037        p._showTrain = _showTrain;
038        return p;
039    }
040
041    protected ArrayList<String> getPaths() {
042        return _paths;
043    }
044
045    protected void setPaths(ArrayList<String> paths) {
046        _paths = paths;
047    }
048
049    protected void addPath(String path) {
050        if (_paths == null) {
051            _paths = new ArrayList<>();
052        }
053        if (path != null && path.length() > 0) {
054            path = path.trim();
055            if (!_paths.contains(path)) {
056                _paths.add(path);
057            }
058        }
059        if (log.isDebugEnabled()) {
060            log.debug("addPath \"{}\" #paths= {}", path, _paths.size());
061        }
062    }
063
064    protected void removePath(String path) {
065        if (_paths != null) {
066            if (path != null && path.length() > 0) {
067                path = path.trim();
068                _paths.remove(path);
069            }
070        }
071    }
072
073    protected void setShowTrain(boolean set) {
074        _showTrain = set;
075    }
076
077    protected boolean showTrain() {
078        return _showTrain;
079    }
080
081    synchronized protected String getStatus(OBlock block, int state) {
082        String pathName = block.getAllocatedPathName();
083        String status;
084        removeLocoIcon();
085        if ((state & OBlock.TRACK_ERROR) != 0) {
086            status = "ErrorTrack";
087        } else if ((state & OBlock.OUT_OF_SERVICE) != 0) {
088            status = "DontUseTrack";
089        } else if ((state & OBlock.ALLOCATED) != 0) {
090            if (_paths != null && _paths.contains(pathName)) {
091                if ((state & OBlock.RUNNING) != 0) {
092                    status = "PositionTrack";   //occupied by train on a warrant
093                } else if ((state & OBlock.OCCUPIED) != 0) {
094                    status = "OccupiedTrack";   // occupied by rouge train
095                } else {
096                    status = "AllocatedTrack";
097                }
098            } else {
099                status = "ClearTrack";     // icon not on path
100            }
101        } else if ((state & OBlock.OCCUPIED) != 0) {
102            status = "OccupiedTrack";
103//        } else if ((state & Sensor.UNKNOWN)!=0) {
104//            status = "DontUseTrack";
105        } else {
106            status = "ClearTrack";
107        }
108        return status;
109    }
110
111    public void removeLocoIcon() {
112        if (_loco != null) {
113            _loco.remove();
114            _loco = null;
115        }
116    }
117
118    /**
119     * @param block OBlock occupied by train
120     * @param pt    position of track icon
121     * @param size  size of track icon
122     * @param ed    editor
123     * LocoLabel ctor causes editor to draw a graphic. Must be done on GUI
124     * Called from IndicatorTrackIcon.setStatus and IndicatorTurnoutIcon.setStatus
125     * Each wraps this method with ThreadingUtil.runOnLayoutEventually, so there is
126     * a time lag for when track icon changes and display of the change.
127     */
128    @SuppressWarnings("deprecation")    // The method getId() from the type Thread is deprecated since version 19
129                                        // The replacement Thread.threadId() isn't available before version 19
130    @jmri.InvokeOnLayoutThread
131    synchronized protected void setLocoIcon(OBlock block, Point pt, Dimension size, Editor ed) {
132        if (!_showTrain) {
133            removeLocoIcon();
134            return;
135        }
136        String trainName = (String) block.getValue();
137        if (trainName == null || trainName.isEmpty()) {
138            removeLocoIcon();
139            return;
140        }
141        if ((block.getState() & (OBlock.OCCUPIED | OBlock.RUNNING)) == 0) {
142            // during delay of runOnLayoutEventually, state has changed
143            // don't paint loco icon 
144            return;
145        }
146        if (_loco != null || pt == null) {
147            return;
148        }
149        trainName = trainName.trim();
150        try {
151            _loco = new LocoLabel(ed);
152        } catch (Exception e) {
153            jmri.jmrit.logix.Warrant w = block.getWarrant();
154            log.error("Exception in setLocoIcon() in thread {} {} for block \"{}\", train \"{}\" \"{}\". state= {} at pt({}, {})",
155                    Thread.currentThread().getName(), Thread.currentThread().getId(), block.getDisplayName(), trainName,
156                    (w!=null? w.getDisplayName(): "no warrant"), block.getState(), pt.x, pt.y);
157            return;
158        }
159        Font font = block.getMarkerFont();
160        if (font == null) {
161            font = ed.getFont();
162        }
163        int width = ed.getFontMetrics(font).stringWidth(trainName);
164        int height = ed.getFontMetrics(ed.getFont()).getHeight();   // limit height to locoIcon height
165        _loco.setLineWidth(1);
166        _loco.setLineColor(Color.BLACK);
167        _loco.setFillColor(block.getMarkerBackground());
168        _loco.setBlock(block);
169        _loco.setWidth(width + height / 2);
170        _loco.setHeight(height + 2);
171        _loco.setCornerRadius(height);
172        _loco.setDisplayLevel(Editor.MARKERS);
173        _loco.updateSize();
174        pt.x = pt.x + (size.width - _loco.maxWidth()) / 2;
175        pt.y = pt.y + (size.height - _loco.maxHeight()) / 2;
176        _loco.setLocation(pt);
177        try {
178            ed.putItem(_loco);
179        } catch (Positionable.DuplicateIdException e) {
180            // This should never happen
181            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
182        }
183    }
184
185    /*
186     * Return track name for known state of occupancy sensor
187     */
188    protected String getStatus(int state) {
189        String status;
190        switch (state) {
191            case Sensor.ACTIVE:
192                status = "OccupiedTrack";
193                break;
194            case Sensor.INACTIVE:
195                status = "ClearTrack";
196                break;
197            case Sensor.UNKNOWN:
198                status = "DontUseTrack";
199                break;
200            default:
201                status = "ErrorTrack";
202                break;
203        }
204        return status;
205    }
206
207    private final static Logger log = LoggerFactory.getLogger(IndicatorTrackPaths.class);
208}