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