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    @jmri.InvokeOnLayoutThread
129    synchronized protected void setLocoIcon(OBlock block, Point pt, Dimension size, Editor ed) {
130        if (!_showTrain) {
131            removeLocoIcon();
132            return;
133        }
134        String trainName = (String) block.getValue();
135        if (trainName == null || trainName.isEmpty()) {
136            removeLocoIcon();
137            return;
138        }
139        if ((block.getState() & (OBlock.OCCUPIED | OBlock.RUNNING)) == 0) {
140            // during delay of runOnLayoutEventually, state has changed
141            // don't paint loco icon 
142            return;
143        }
144        if (_loco != null || pt == null) {
145            return;
146        }
147        trainName = trainName.trim();
148        try {
149            _loco = new LocoLabel(ed);
150        } catch (Exception e) {
151            jmri.jmrit.logix.Warrant w = block.getWarrant();
152            log.error("Exception in setLocoIcon() in thread {} {} for block \"{}\", train \"{}\" \"{}\". state= {} at pt({}, {})",
153                    Thread.currentThread().getName(), Thread.currentThread().getId(), block.getDisplayName(), trainName,
154                    (w!=null? w.getDisplayName(): "no warrant"), block.getState(), pt.x, pt.y);
155            return;
156        }
157        Font font = block.getMarkerFont();
158        if (font == null) {
159            font = ed.getFont();
160        }
161        int width = ed.getFontMetrics(font).stringWidth(trainName);
162        int height = ed.getFontMetrics(ed.getFont()).getHeight();   // limit height to locoIcon height
163        _loco.setLineWidth(1);
164        _loco.setLineColor(Color.BLACK);
165        _loco.setFillColor(block.getMarkerBackground());
166        _loco.setBlock(block);
167        _loco.setWidth(width + height / 2);
168        _loco.setHeight(height + 2);
169        _loco.setCornerRadius(height);
170        _loco.setDisplayLevel(Editor.MARKERS);
171        _loco.updateSize();
172        pt.x = pt.x + (size.width - _loco.maxWidth()) / 2;
173        pt.y = pt.y + (size.height - _loco.maxHeight()) / 2;
174        _loco.setLocation(pt);
175        try {
176            ed.putItem(_loco);
177        } catch (Positionable.DuplicateIdException e) {
178            // This should never happen
179            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
180        }
181    }
182
183    /*
184     * Return track name for known state of occupancy sensor
185     */
186    protected String getStatus(int state) {
187        String status;
188        switch (state) {
189            case Sensor.ACTIVE:
190                status = "OccupiedTrack";
191                break;
192            case Sensor.INACTIVE:
193                status = "ClearTrack";
194                break;
195            case Sensor.UNKNOWN:
196                status = "DontUseTrack";
197                break;
198            default:
199                status = "ErrorTrack";
200                break;
201        }
202        return status;
203    }
204
205    private final static Logger log = LoggerFactory.getLogger(IndicatorTrackPaths.class);
206}