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}