001package jmri.jmrit.display;
002
003import java.awt.event.ActionEvent;
004
005import javax.annotation.Nonnull;
006import javax.swing.AbstractAction;
007import javax.swing.JCheckBoxMenuItem;
008import javax.swing.JMenuItem;
009import javax.swing.JPopupMenu;
010
011import jmri.jmrit.catalog.NamedIcon;
012import jmri.jmrix.rps.Distributor;
013import jmri.jmrix.rps.Measurement;
014import jmri.jmrix.rps.MeasurementListener;
015import jmri.util.swing.JmriJOptionPane;
016
017/**
018 * An icon to display the position of an RPS input.
019 *
020 * In this initial version, it ignores the ID, so there's only one icon.
021 *
022 * @author Bob Jacobsen Copyright (C) 2007
023 */
024public class RpsPositionIcon extends PositionableLabel implements MeasurementListener {
025
026    public RpsPositionIcon(Editor editor) {
027        // super ctor call to make sure this is an icon label
028        super(new NamedIcon("resources/icons/smallschematics/tracksegments/circuit-error.gif",
029                "resources/icons/smallschematics/tracksegments/circuit-error.gif"), editor);
030        _control = true;
031        displayState();
032
033        // blow up default font
034        setFont(getFont().deriveFont(24.f));
035
036        // connect
037        Distributor.instance().addMeasurementListener(this);
038    }
039
040    // display icon for a correct reading
041    String activeName = "resources/icons/smallschematics/tracksegments/circuit-occupied.gif";
042    NamedIcon active = new NamedIcon(activeName, activeName);
043
044    // display icon if the last reading not OK
045    String errorName = "resources/icons/smallschematics/tracksegments/circuit-error.gif";
046    NamedIcon error = new NamedIcon(errorName, errorName);
047
048    public NamedIcon getActiveIcon() {
049        return active;
050    }
051
052    public void setActiveIcon(NamedIcon i) {
053        active = i;
054        displayState();
055    }
056
057    public NamedIcon getErrorIcon() {
058        return error;
059    }
060
061    public void setErrorIcon(NamedIcon i) {
062        error = i;
063        displayState();
064    }
065
066    @Override
067    @Nonnull
068    public String getTypeString() {
069        return Bundle.getMessage("PositionableType_RpsPositionIcon");
070    }
071
072    @Override
073    public String getNameString() {
074        return "RPS Position Readout";
075    }
076
077    @Override
078    public boolean setEditIconMenu(JPopupMenu popup) {
079        return false;
080    }
081
082    /**
083     * Pop-up contents
084     */
085    @Override
086    public boolean showPopUp(JPopupMenu popup) {
087
088        if (showIdItem == null) {
089            showIdItem = new JCheckBoxMenuItem("Show ID");
090            showIdItem.setSelected(false);
091            showIdItem.addActionListener(e -> toggleID(showIdItem.isSelected()));
092        }
093        popup.add(showIdItem);
094
095        popup.add(new AbstractAction("Set Origin") {
096            @Override
097            public void actionPerformed(ActionEvent e) {
098                setRpsOrigin();
099            }
100        });
101
102        popup.add(new AbstractAction("Set Current Location") {
103            @Override
104              public void actionPerformed(ActionEvent e) {
105                setRpsCurrentLocation();
106            }
107        });
108
109        notify = new Notifier();
110        popup.add(notify);
111
112        popup.add(new AbstractAction("Set Filter") {
113            @Override
114            public void actionPerformed(ActionEvent e) {
115                setFilterPopup();
116            }
117        });
118
119        // add help item
120        JMenuItem item = new JMenuItem("Help");
121        jmri.util.HelpUtil.addHelpToComponent(item, "package.jmri.jmrit.display.RpsIcon");
122        popup.add(item);
123
124        // update position
125        notify.setPosition(getX(), getY());
126        return false;
127    }
128
129    /**
130     * ****** popup AbstractAction.actionPerformed method overrides ********
131     */
132    @Override
133    protected void rotateOrthogonal() {
134        active.setRotation(active.getRotation() + 1, this);
135        error.setRotation(error.getRotation() + 1, this);
136        displayState();
137        //bug fix, must repaint icons that have same width and height
138        repaint();
139    }
140
141    @Override
142    public void setScale(double s) {
143        active.scale(s, this);
144        error.scale(s, this);
145        displayState();
146    }
147
148    @Override
149    public void rotate(int deg) {
150        active.rotate(deg, this);
151        error.rotate(deg, this);
152        displayState();
153    }
154
155    JCheckBoxMenuItem showIdItem = null;
156
157    /**
158     * Internal class to show position in the popup menu.
159     * <p>
160     * This is updated before the menu is shown, and then appears in the menu.
161     */
162    class Notifier extends AbstractAction {
163
164        public Notifier() {
165            super();
166        }
167
168        /**
169         * Does nothing, here to make this work
170         */
171        @Override
172        public void actionPerformed(ActionEvent e) {
173        }
174
175        /**
176         *
177         * @param x display coordinate
178         * @param y display coordinate
179         */
180        void setPosition(int x, int y) {
181            // convert to RPS coordinates
182            double epsilon = .00001;
183            if ((sxScale > -epsilon && sxScale < epsilon)
184                    || (syScale > -epsilon && syScale < epsilon)) {
185                putValue("Name", "Not Calibrated");
186                return;
187            }
188
189            double xn = (x - sxOrigin) / sxScale;
190            double yn = (y - syOrigin) / syScale;
191
192            putValue("Name", "At: " + xn + "," + yn);
193        }
194    }
195    Notifier notify;
196
197    JCheckBoxMenuItem momentaryItem;
198
199    // true if valid message received last
200    boolean state = false;
201
202    /**
203     * Drive the current state of the display from whether a valid measurement
204     * has been received
205     */
206    void displayState() {
207
208        if (state) {
209            if (isIcon()) {
210                super.setIcon(active);
211            }
212        } else {
213            if (isIcon()) {
214                super.setIcon(error);
215            }
216        }
217
218        updateSize();
219        revalidate();
220    }
221
222    @Override
223    public int maxHeight() {
224        return getPreferredSize().height;
225    }
226
227    @Override
228    public int maxWidth() {
229        return getPreferredSize().width;
230    }
231
232    boolean momentary = false;
233
234    public boolean getMomentary() {
235        return momentary;
236    }
237
238    public void setMomentary(boolean m) {
239        momentary = m;
240    }
241
242    void toggleID(boolean value) {
243        if (value) {
244            _text = true;
245        } else {
246
247            _text = false;
248            setText(null);
249        }
250        displayState();
251    }
252
253    public boolean isShowID() {
254        return _text;
255    }
256
257    public void setShowID(boolean mode) {
258        _text = mode;
259        displayState();
260    }
261
262    /**
263     * Respond to a measurement by moving to new position
264     */
265    @Override
266    public void notify(Measurement m) {
267        // only honor measurements to this icon if filtered
268        if (filterNumber != null && m.getReading() != null
269                && !filterNumber.equals(m.getReading().getId())) {
270            return;
271        }
272
273        // remember this measurement for last position, e.g. for
274        // alignment
275        lastMeasurement = m;
276
277        // update state based on if valid measurement, fiducial volume
278        if (!m.isOkPoint() || m.getZ() < -20 || m.getZ() > 20) {
279            state = false;
280        } else {
281            state = true;
282        }
283
284        if (_text) {
285            super.setText("" + m.getReading().getId());
286        }
287        displayState();
288
289        // if the state is bad, leave icon in last position
290        if (!state) {
291            return;
292        }
293
294        // Do a 2D, no-rotation conversion using the saved constants.
295        // xn, yn are the RPS coordinates; x, y are the display coordinates.
296        double xn = m.getX();
297        double yn = m.getY();
298
299        int x = sxOrigin + (int) (sxScale * xn);
300        int y = syOrigin + (int) (syScale * yn);
301
302        // and set position
303        setLocation(x, y);
304    }
305
306    public void setFilterPopup() {
307        // Popup menu has trigger request for filter value
308        String inputValue = JmriJOptionPane.showInputDialog(null, "Please enter a filter value", "");
309        if (inputValue == null) {
310            return; // cancelled
311        }
312        setFilter(inputValue);
313    }
314
315    public void setFilter(String val) {
316        filterNumber = val;
317    }
318
319    public String getFilter() {
320        return filterNumber;
321    }
322    String filterNumber = null;
323
324    @Override
325    public void dispose() {
326        Distributor.instance().removeMeasurementListener(this);
327        active = null;
328        error = null;
329
330        super.dispose();
331    }
332
333    /**
334     * Set the current icon position as the origin (0,0) of the RPS space.
335     */
336    public void setRpsOrigin() {
337        sxOrigin = getX();
338        syOrigin = getY();
339    }
340
341    public double getXScale() {
342        return sxScale;
343    }
344
345    public double getYScale() {
346        return syScale;
347    }
348
349    public int getXOrigin() {
350        return sxOrigin;
351    }
352
353    public int getYOrigin() {
354        return syOrigin;
355    }
356
357    public void setTransform(double sxScale, double syScale, int sxOrigin, int syOrigin) {
358        this.sxScale = sxScale;
359        this.syScale = syScale;
360        this.sxOrigin = sxOrigin;
361        this.syOrigin = syOrigin;
362    }
363
364    /**
365     * Matches the icon position on the screen to its position in the RPS
366     * coordinate system.
367     * <p>
368     * Typically invoked from the popup menu, you move the icon (e.g. via drag
369     * and drop) to the correct position on the screen for its current measured
370     * position, and then invoke this method.
371     * <p>
372     * Requires the origin to have been set, and some other measurement to have
373     * been made (and current).
374     */
375    public void setRpsCurrentLocation() {
376        if (lastMeasurement == null) {
377            return;
378        }
379
380        if (sxOrigin == getX()) {
381            return;
382        }
383        if (syOrigin == getY()) {
384            return;
385        }
386        // if (lastMeasurement.getX()<10. && lastMeasurement.getX()>-10) return;
387        // if (lastMeasurement.getY()<10. && lastMeasurement.getY()>-10) return;
388
389        sxScale = (getX() - sxOrigin) / lastMeasurement.getX();
390        syScale = (getY() - syOrigin) / lastMeasurement.getY();
391    }
392
393    // store coordinate system information
394    Measurement lastMeasurement;
395
396    double sxScale, syScale;
397    int sxOrigin, syOrigin;
398}