1 /* Copyright (C) 2005-2011 Fabio Riccardi */ 2 3 package com.lightcrafts.ui.operation.generic; 4 5 import com.lightcrafts.ui.LightZoneSkin; 6 7 import javax.swing.*; 8 import javax.swing.event.ChangeEvent; 9 import javax.swing.event.ChangeListener; 10 import javax.swing.event.DocumentEvent; 11 import javax.swing.event.DocumentListener; 12 import javax.swing.text.Document; 13 import java.awt.*; 14 import java.awt.event.*; 15 import java.text.NumberFormat; 16 import java.text.DecimalFormat; 17 import java.text.ParseException; 18 19 /** This is a JTextField that listens on a ConfiguredBoundedRangeModel, 20 * updating its text when the model changes and pushing validated numeric 21 * text back into the model. 22 */ 23 24 class ConfiguredTextField 25 extends JTextField 26 implements MouseWheelListener, ChangeListener, DocumentListener 27 { 28 // Select-all and handle mouse wheel events when text fields gain focus: 29 private static final FocusListener FocusSelector = new FocusAdapter() { 30 public void focusGained(FocusEvent event) { 31 ConfiguredTextField text = (ConfiguredTextField) event.getSource(); 32 text.selectAll(); 33 text.addMouseWheelListener(text); 34 } 35 public void focusLost(FocusEvent event) { 36 ConfiguredTextField text = (ConfiguredTextField) event.getSource(); 37 text.select(0, 0); 38 text.removeMouseWheelListener(text); 39 } 40 }; 41 42 private ConfiguredBoundedRangeModel model; 43 private NumberFormat format; 44 45 private double min; // The minimum numeric value for the text 46 private double max; // The maximum numeric value for the text 47 48 private double inc; // An increment/decrement amount, for keystrokes 49 50 private boolean isUpdating; // A flag to detect our own model changes 51 ConfiguredTextField( ConfiguredBoundedRangeModel model, DecimalFormat format )52 ConfiguredTextField( 53 ConfiguredBoundedRangeModel model, DecimalFormat format 54 ) { 55 this.model = model; 56 this.format = format; 57 min = model.getConfiguredMinimum(); 58 max = model.getConfiguredMaximum(); 59 inc = model.getConfiguredIncrement(); 60 setInputVerifier(new IntervalVerifier(min, max)); 61 setHorizontalAlignment(RIGHT); 62 63 setFont(getFont()); // Figure out the maxiumum text width 64 65 addListeners(); 66 67 updateFromModel(); 68 } 69 setFont(Font font)70 public void setFont(Font font) { 71 super.setFont(font); 72 if (model == null) { 73 // called from base class constructor 74 return; 75 } 76 // Adjust our size to allow for the maximum value: 77 78 double max = model.getConfiguredMaximum(); 79 int widest = getWidestNumber(max); 80 81 String tempText = Integer.toString(widest); 82 int places = format.getMaximumFractionDigits(); 83 if (places > 0) { 84 tempText += "."; 85 for (int n=0; n<places; n++) { 86 tempText += "0"; 87 } 88 } 89 String text = getText(); 90 setText(tempText); 91 setPreferredSize(null); // wipe previous settings 92 Dimension size = getPreferredSize(); 93 setText(text); 94 95 setMinimumSize(size); 96 setPreferredSize(size); 97 } 98 99 // When the model changes, update our text: stateChanged(ChangeEvent event)100 public void stateChanged(ChangeEvent event) { 101 if (isUpdating) { 102 return; 103 } 104 Object source = event.getSource(); 105 if (! source.equals(model)) { 106 return; 107 } 108 updateFromModel(); 109 } 110 changedUpdate(DocumentEvent e)111 public void changedUpdate(DocumentEvent e) { 112 handleDocumentChange(); 113 } 114 insertUpdate(DocumentEvent e)115 public void insertUpdate(DocumentEvent e) { 116 handleDocumentChange(); 117 } 118 removeUpdate(DocumentEvent e)119 public void removeUpdate(DocumentEvent e) { 120 handleDocumentChange(); 121 } 122 updateFromModel()123 private void updateFromModel() { 124 double value = model.getConfiguredValue(); 125 String text = format.format(value); 126 if (! text.equals(getText())) { 127 setText(text); 128 selectAll(); 129 } 130 } 131 132 // When the document changes, give verification feedback and maybe update 133 // the model: handleDocumentChange()134 private void handleDocumentChange() { 135 InputVerifier verifier = getInputVerifier(); 136 String text = getText(); 137 boolean verified = verifier.verify(this); 138 if (! verified) { 139 setForeground(Color.red); 140 } 141 else { 142 try { 143 double value = format.parse(text).doubleValue(); 144 setForeground(LightZoneSkin.Colors.ToolPanesForeground); 145 isUpdating = true; 146 model.setConfiguredValue(value); 147 isUpdating = false; 148 } 149 catch (ParseException e) { 150 // Should never happen, because of the verifier. 151 System.err.println("Unparsable verified text: " + text); 152 setForeground(Color.red); 153 } 154 } 155 } 156 addListeners()157 private void addListeners() { 158 159 // Respond to document changes with verification and model changes: 160 Document doc = getDocument(); 161 doc.addDocumentListener(this); 162 163 // Update text when the model changes: 164 model.addChangeListener(this); 165 166 // Select-all when we gain focus, select-none when we lose: 167 addFocusListener(FocusSelector); 168 169 // Override the default input map for spacebar, because that key stroke 170 // is used globally to access the editor's pan mode. 171 InputMap input = getInputMap(WHEN_FOCUSED); 172 KeyStroke space = KeyStroke.getKeyStroke(new Character(' '), 0); 173 input.put(space, "none"); 174 175 // Increment by largeInc on up arrow events: 176 registerKeyboardAction( 177 new ActionListener() { 178 public void actionPerformed(ActionEvent event) { 179 double value = model.getConfiguredValue(); 180 value = getNextRoundValueUp(value); 181 if ((value >= min) && (value <= max)) { 182 model.setConfiguredValue(value); 183 } 184 } 185 }, 186 KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), 187 WHEN_FOCUSED 188 ); 189 190 // Decrement by largeInc on down arrow events: 191 registerKeyboardAction( 192 new ActionListener() { 193 public void actionPerformed(ActionEvent event) { 194 double value = model.getConfiguredValue(); 195 value = getNextRoundValueDown(value); 196 if ((value >= min) && (value <= max)) { 197 model.setConfiguredValue(value); 198 } 199 } 200 }, 201 KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), 202 WHEN_FOCUSED 203 ); 204 } 205 206 // Increment/decrement by smallInc on mouse wheel events, if we're focused: mouseWheelMoved(MouseWheelEvent event)207 public void mouseWheelMoved(MouseWheelEvent event) { 208 double value = model.getConfiguredValue(); 209 int count = event.getWheelRotation(); 210 int sign = (count > 0) ? 1 : -1; 211 for (int n=0; n<sign*count; n++) { 212 if (sign > 0) { 213 value = getNextRoundValueDown(value); 214 } 215 else { 216 value = getNextRoundValueUp(value); 217 } 218 if ((value >= min) && (value <= max)) { 219 model.setConfiguredValue(value); 220 } 221 else { 222 break; 223 } 224 } 225 } 226 getNextRoundValueDown(double value)227 private double getNextRoundValueDown(double value) { 228 return getRoundValue(value - inc); 229 } 230 getNextRoundValueUp(double value)231 private double getNextRoundValueUp(double value) { 232 return getRoundValue(value + inc); 233 } 234 getRoundValue(double value)235 private double getRoundValue(double value) { 236 return inc * Math.round(value / inc); 237 } 238 getWidestNumber(double max)239 private static int getWidestNumber(double max) { 240 double powTen = Math.pow(10, Math.ceil(Math.log(max) / Math.log(10))); 241 int widest = (int) Math.round(powTen); 242 if (widest == 1) { 243 // the one case where a power of ten is narrower than a 244 // smaller nonnegative integer 245 widest = 100; 246 } 247 return widest; 248 } 249 250 // An InputVerifier that checks our text is compatible with a number range: 251 252 private class IntervalVerifier extends InputVerifier { 253 254 private double min; 255 private double max; 256 IntervalVerifier(double min, double max)257 IntervalVerifier(double min, double max) { 258 this.min = min; 259 this.max = max; 260 } 261 verify(JComponent input)262 public boolean verify(JComponent input) { 263 JTextField textField = (JTextField) input; 264 String text = textField.getText(); 265 double x; 266 try { 267 x = format.parse(text).doubleValue(); 268 } 269 catch (ParseException e) { 270 return false; 271 } 272 return ((x >= min) && (x <= max)); 273 } 274 } 275 } 276