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        // ed may have been disposed before this runOnGUIEventually callback fires
140        if (_loco != null || pt == null || !ed.isDisplayable()) {
141            return;
142        }
143        trainName = trainName.trim();
144        try {
145            _loco = new LocoLabel(ed);
146        } catch (Exception e) {
147            jmri.jmrit.logix.Warrant w = block.getWarrant();
148            log.error("Exception in setLocoIcon() in thread {} {} for block \"{}\", train \"{}\" \"{}\". state= {} at pt({}, {})",
149                    Thread.currentThread().getName(), Thread.currentThread().getId(), block.getDisplayName(), trainName,
150                    (w!=null? w.getDisplayName(): "no warrant"), block.getState(), pt.x, pt.y, e);
151            return;
152        }
153        Font font = block.getMarkerFont();
154        if (font == null) {
155            font = ed.getFont();
156        }
157        int width = ed.getFontMetrics(font).stringWidth(trainName);
158        int height = ed.getFontMetrics(ed.getFont()).getHeight();   // limit height to locoIcon height
159        _loco.setLineWidth(1);
160        _loco.setLineColor(Color.BLACK);
161        _loco.setFillColor(block.getMarkerBackground());
162        _loco.setBlock(block);
163        _loco.setWidth(width + height / 2);
164        _loco.setHeight(height + 2);
165        _loco.setCornerRadius(height);
166        _loco.setDisplayLevel(Editor.MARKERS);
167        _loco.updateSize();
168        pt.x += (size.width - _loco.maxWidth()) / 2;
169        pt.y += (size.height - _loco.maxHeight()) / 2;
170        _loco.setLocation(pt);
171        try {
172            ed.putItem(_loco);
173        } catch (Positionable.DuplicateIdException e) {
174            // This should never happen
175            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
176        }
177    }
178
179    /*
180     * Return track name for known state of occupancy sensor
181     */
182    protected String getStatus(int state) {
183        String status;
184        switch (state) {
185            case Sensor.ACTIVE:
186                status = "OccupiedTrack";
187                break;
188            case Sensor.INACTIVE:
189                status = "ClearTrack";
190                break;
191            case Sensor.UNKNOWN:
192                status = "DontUseTrack";
193                break;
194            default:
195                status = "ErrorTrack";
196                break;
197        }
198        return status;
199    }
200
201    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(IndicatorTrackPaths.class);
202
203}