001package jmri.jmrix.can.cbus.swing.modules;
002
003import java.awt.*;
004import javax.swing.*;
005import javax.swing.border.*;
006import javax.swing.event.ChangeEvent;
007import javax.swing.event.ChangeListener;
008
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011
012/**
013 * JSpinner with titled border
014 *
015 * @author Andrew Crosland Copyright (C) 2022
016 */
017public class TitledSpinner extends JPanel implements ChangeListener {
018        
019    protected JSpinner tSpin;
020    protected int _index;
021    protected String _title;
022    protected UpdateNV _update;
023    protected Object lastValue;
024
025
026    /**
027     * Construct a new titledSpinner
028     * 
029     * @param title to be displayed
030     * @param index of the associated NV 
031     * @param update callback funtion to apply new value
032     */
033    public TitledSpinner(String title, int index, UpdateNV update) {
034        super();
035        _title = title;
036        _index = index;
037        _update = update;
038        tSpin = new JSpinner();
039    }
040
041    /**
042     * Initialise with float values
043     * 
044     * @param init  Initial value
045     * @param min   Minimum value
046     * @param max   Maximum value
047     * @param step  Step
048     */
049    public void init(double init, double min, double max, double step) {
050        SpinnerNumberModel spinModel;
051        if (init >= 0.0) {
052            spinModel = new SpinnerNumberModel(init, min, max, step);
053        } else {
054            // NV hasn't been initialsed yet (maybe still reading from hardware) so init to min value.
055            spinModel = new SpinnerNumberModel(min, min, max, step);
056        }
057        init(spinModel);
058    }
059
060    /**
061     * Initialise with int values
062     * 
063     * @param init Initial value for spinner
064     * @param min  Minimum value for spinner
065     * @param max  Maximum value fro spinner
066     * @param step Step size for spinner adjustments
067     */
068    public void init(int init, int min, int max, int step) {
069        SpinnerNumberModel spinModel;
070        if (init >= 0.0) {
071            spinModel = new SpinnerNumberModel(init, min, max, step);
072        } else {
073            // NV hasn't been initialsed yet (maybe still reading from hardware) so init to min value.
074            spinModel = new SpinnerNumberModel(min, min, max, step);
075        }
076        init(spinModel);
077    }
078
079    /**
080     * Initialise from the spin model
081     * 
082     * @param spinModel 
083     */
084    private void init(SpinnerNumberModel spinModel) {
085        GridLayout grid = new GridLayout(1, 1);
086        setLayout(grid);
087
088        Border border = BorderFactory.createEtchedBorder(EtchedBorder.LOWERED);
089        TitledBorder titled = BorderFactory.createTitledBorder(border, _title);
090        setBorder(titled);
091
092        tSpin.setModel(spinModel);
093        tSpin.addChangeListener(this);
094
095        add(tSpin);
096    }
097
098    /**
099     * Set the tool tip
100     * 
101     * @param tt tooltip text
102     */
103    public void setToolTip(String tt) {
104        tSpin.setToolTipText(tt);
105    }
106
107    /**
108     * Enable the spinner
109     * 
110     * @param b boolean to enable (true) or disable (false)
111     */
112    @Override
113    public void setEnabled(boolean b) {
114        tSpin.setEnabled(b);
115    }
116
117    /**
118     * Is the spinner enabled
119     * 
120     * @return true or false 
121     */
122    @Override
123    public boolean isEnabled() {
124        return tSpin.isEnabled();
125    }
126
127    /**
128     * Set the current spinner value
129     * 
130     * Check that the supplied value is the correct type for the current spinner,
131     * otherwise we seem to get more stateChange events that can update the gui
132     * that change the spinner again, ... leading to an endless code loop
133     * 
134     * @param val Number which should contain a Double or an Integer
135     */
136    public void setValue(Number val) {
137        if (val.getClass() == (((SpinnerNumberModel)tSpin.getModel()).getValue()).getClass()) {
138            tSpin.getModel().setValue(val);
139        } else {
140            log.error("Expected {} given {}", (((SpinnerNumberModel)tSpin.getModel()).getValue()).getClass(), val.getClass());
141        }
142    }
143
144    /**
145     * Get the Double representation of the spinner value
146     * 
147     * @return Spinner value as Double
148     */
149    public Double getDoubleValue() {
150        return ((SpinnerNumberModel)tSpin.getModel()).getNumber().doubleValue();
151    }
152
153    /**
154     * Get the int representation of the spinner value
155     * 
156     * @return Spinner values as int
157     */
158    public int getIntegerValue() {
159        return ((SpinnerNumberModel)tSpin.getModel()).getNumber().intValue();
160    }
161
162    /**
163     * Call back with updated value
164     * 
165     * @param e the spinner change event
166     */
167    @Override
168    public void stateChanged(ChangeEvent e) {
169        _update.setNewVal(_index);
170    }
171
172    /**
173     ** The preferred width on the panel must consider the width of the text
174     ** used on the TitledBorder
175     * 
176     * from <a href=https://stackoverflow.com/questions/43425939/how-to-get-the-titledborders-title-to-display-properly-in-the-gui></a>
177     */
178    @Override
179    public Dimension getPreferredSize() {
180
181        Dimension preferredSize = super.getPreferredSize();
182
183        Border border = getBorder();
184        int borderWidth = 0;
185
186        if (border instanceof TitledBorder) {
187            Insets insets = getInsets();
188            TitledBorder titledBorder = (TitledBorder)border;
189            borderWidth = titledBorder.getMinimumSize(this).width + insets.left + insets.right;
190        }
191
192        int preferredWidth = Math.max(preferredSize.width, borderWidth);
193
194        return new Dimension(preferredWidth, preferredSize.height);
195    }
196
197    private final static Logger log = LoggerFactory.getLogger(TitledSpinner.class);
198
199}