Source code: com/port80/eclipse/util/HexColorFieldEditor.java
1 package com.port80.eclipse.util;
2
3 import org.eclipse.jface.preference.FieldEditor;
4 import org.eclipse.jface.resource.StringConverter;
5 import org.eclipse.swt.SWT;
6 import org.eclipse.swt.events.DisposeEvent;
7 import org.eclipse.swt.events.DisposeListener;
8 import org.eclipse.swt.events.FocusAdapter;
9 import org.eclipse.swt.events.FocusEvent;
10 import org.eclipse.swt.events.KeyAdapter;
11 import org.eclipse.swt.events.KeyEvent;
12 import org.eclipse.swt.graphics.GC;
13 import org.eclipse.swt.graphics.Point;
14 import org.eclipse.swt.graphics.RGB;
15 import org.eclipse.swt.layout.GridData;
16 import org.eclipse.swt.widgets.Composite;
17 import org.eclipse.swt.widgets.Text;
18
19 import com.port80.util.Sprint;
20
21 /**
22 * A preference field editor to enter color RGB value in hex.
23 * Modified from StringFieldEditor. Unfortunately, StringFieldEditor.oldValue is private
24 * and make it difficult to just extend StringFieldEditor and override doLoad() ... etc.
25 *
26 * @author chrisl
27 */
28 public class HexColorFieldEditor extends FieldEditor {
29
30 ////////////////////////////////////////////////////////////////////////
31
32 /**
33 * Text limit constant (value <code>-1</code>) indicating unlimited
34 * text limit and width.
35 */
36 public static int UNLIMITED = -1;
37
38 /**
39 * Old text value.
40 */
41 protected String oldValue;
42
43 /**
44 * Cached valid state.
45 */
46 private boolean isValid;
47
48 /**
49 * The text field, or <code>null</code> if none.
50 */
51 private Text textField;
52
53 /**
54 * Width of text field in characters; initially unlimited.
55 */
56 private int widthInChars = UNLIMITED;
57
58 /**
59 * Text limit of text field in characters; initially unlimited.
60 */
61 private int textLimit = UNLIMITED;
62
63 /**
64 * The error message, or <code>null</code> if none.
65 */
66 private String errorMessage;
67
68 /**
69 * Indicates whether the empty string is legal;
70 * <code>true</code> by default.
71 */
72 private boolean emptyStringAllowed = false;
73
74 ////////////////////////////////////////////////////////////////////////
75
76 /**
77 * Constructor for HexColorFieldEditor.
78 */
79 public HexColorFieldEditor() {
80 }
81
82 /**
83 * Creates a string field editor.
84 * Use the method <code>setTextLimit</code> to limit the text.
85 *
86 * @param name the name of the preference this field editor works on
87 * @param labelText the label text of the field editor
88 * @param width the width of the text input field in characters,
89 * or <code>UNLIMITED</code> for no limit
90 * @param parent the parent of the field editor's control
91 * @since 2.0
92 */
93 public HexColorFieldEditor(String name, String labelText, int width, Composite parent) {
94 init(name, labelText);
95 widthInChars = width;
96 isValid = false;
97 errorMessage = UtilPlugin.getResourceString("HexColorFieldEditor.errorMessage"); //$NON-NLS-1$
98 createControl(parent);
99 }
100
101 /**
102 * Creates a string field editor of unlimited width.
103 * Use the method <code>setTextLimit</code> to limit the text.
104 *
105 * @param name the name of the preference this field editor works on
106 * @param labelText the label text of the field editor
107 * @param parent the parent of the field editor's control
108 */
109 public HexColorFieldEditor(String name, String labelText, Composite parent) {
110 this(name, labelText, UNLIMITED, parent);
111 }
112
113 ////////////////////////////////////////////////////////////////////////
114
115 /**
116 * Returns the error message that will be displayed when and if
117 * an error occurs.
118 *
119 * @return the error message, or <code>null</code> if none
120 */
121 public String getErrorMessage() {
122 return errorMessage;
123 }
124
125 /* (non-Javadoc)
126 * Method declared on FieldEditor.
127 */
128 public int getNumberOfControls() {
129 return 2;
130 }
131
132 /**
133 * Returns the field editor's value.
134 *
135 * @return the current value
136 */
137 public String getStringValue() {
138 if (textField != null)
139 return textField.getText();
140 else
141 return getPreferenceStore().getString(getPreferenceName());
142 }
143
144 /**
145 * Returns this field editor's text control.
146 * <p>
147 * The control is created if it does not yet exist
148 * </p>
149 *
150 * @param parent the parent
151 * @return the text control
152 */
153 public Text getTextControl(Composite parent) {
154 if (textField == null) {
155 textField = new Text(parent, SWT.SINGLE | SWT.BORDER);
156 textField.setFont(parent.getFont());
157 textField.addKeyListener(new KeyAdapter() {
158 public void keyPressed(KeyEvent e) {
159 valueChanged();
160 }
161 });
162 textField.addFocusListener(new FocusAdapter() {
163 public void focusGained(FocusEvent e) {
164 refreshValidState();
165 }
166 public void focusLost(FocusEvent e) {
167 handleFocusLost();
168 }
169 });
170 textField.addDisposeListener(new DisposeListener() {
171 public void widgetDisposed(DisposeEvent event) {
172 dispose();
173 }
174 });
175 if (textLimit > 0) { //Only set limits above 0 - see SWT spec
176 textField.setTextLimit(textLimit);
177 }
178 } else {
179 checkParent(textField, parent);
180 }
181 return textField;
182 }
183
184 /**
185 * Returns whether an empty string is a valid value.
186 *
187 * @return <code>true</code> if an empty string is a valid value, and
188 * <code>false</code> if an empty string is invalid
189 * @see #setEmptyStringAllowed
190 */
191 public boolean isEmptyStringAllowed() {
192 return emptyStringAllowed;
193 }
194 /* (non-Javadoc)
195 * Method declared on FieldEditor.
196 */
197 public boolean isValid() {
198 return isValid;
199 }
200
201 /**
202 * Sets whether the empty string is a valid value or not.
203 *
204 * @param b <code>true</code> if the empty string is allowed,
205 * and <code>false</code> if it is considered invalid
206 */
207 public void setEmptyStringAllowed(boolean b) {
208 emptyStringAllowed = b;
209 }
210
211 /**
212 * Sets the error message that will be displayed when and if
213 * an error occurs.
214 *
215 * @param message the error message
216 */
217 public void setErrorMessage(String message) {
218 errorMessage = message;
219 }
220 /* (non-Javadoc)
221 * Method declared on FieldEditor.
222 */
223 public void setFocus() {
224 if (textField != null) {
225 textField.setFocus();
226 }
227 }
228
229 /**
230 * Sets this field editor's value.
231 *
232 * @param value the new value, or <code>null</code> meaning the empty string
233 */
234 public void setStringValue(String value) {
235 if (textField != null) {
236 if (value == null)
237 value = ""; //$NON-NLS-1$
238 oldValue = textField.getText();
239 if (!oldValue.equals(value)) {
240 textField.setText(value);
241 valueChanged();
242 }
243 }
244 }
245
246 /**
247 * Sets this text field's text limit.
248 *
249 * @param limit the limit on the number of character in the text
250 * input field, or <code>UNLIMITED</code> for no limit
251
252 */
253 public void setTextLimit(int limit) {
254 textLimit = limit;
255 if (textField != null)
256 textField.setTextLimit(limit);
257 }
258
259 /**
260 * Shows the error message set via <code>setErrorMessage</code>.
261 */
262 public void showErrorMessage() {
263 showErrorMessage(errorMessage);
264 }
265
266 public void dispose() {
267 super.dispose();
268 textField = null;
269 }
270
271 ////////////////////////////////////////////////////////////////////////
272
273 /* (non-Javadoc)
274 * Method declared on FieldEditor.
275 */
276 protected void adjustForNumColumns(int numColumns) {
277 GridData gd = (GridData) textField.getLayoutData();
278 gd.horizontalSpan = numColumns - 1;
279 // We only grab excess space if we have to
280 // If another field editor has more columns then
281 // we assume it is setting the width.
282 gd.grabExcessHorizontalSpace = gd.horizontalSpan == 1;
283 }
284
285 /**
286 * Checks whether the text input field contains a valid value or not.
287 *
288 * @return <code>true</code> if the field value is valid,
289 * and <code>false</code> if invalid
290 */
291 protected boolean checkState() {
292 boolean result = false;
293 if (textField == null)
294 result = false;
295 String text = textField.getText();
296 if(text.startsWith("#")) text=text.substring(1);
297 if (text == null)
298 result = false;
299 result = (text.trim().length() > 0) || emptyStringAllowed;
300 //
301 // Check for non-hex. digits.
302 char c;
303 for (int i = 0; i < text.length(); ++i) {
304 c = text.charAt(i);
305 if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'))) {
306 result = false;
307 break;
308 }
309 }
310 //
311 // call hook for subclasses
312 result = result && doCheckState();
313 //
314 if (result)
315 clearErrorMessage();
316 else
317 showErrorMessage(errorMessage);
318 return result;
319 }
320
321 /**
322 * Hook for subclasses to do additional specific state checks.
323 * <p>
324 * The default implementation of this framework method does
325 * nothing and returns <code>true</code>. Subclasses should
326 * override this method to specific state checks.
327 * </p>
328 *
329 * @return <code>true</code> if the field value is valid,
330 * and <code>false</code> if invalid
331 */
332 protected boolean doCheckState() {
333 return true;
334 }
335
336 /**
337 * Fills this field editor's basic controls into the given parent.
338 * <p>
339 * The string field implementation of this <code>FieldEditor</code>
340 * framework method contributes the text field. Subclasses may override
341 * but must call <code>super.doFillIntoGrid</code>.
342 * </p>
343 */
344 protected void doFillIntoGrid(Composite parent, int numColumns) {
345 getLabelControl(parent);
346
347 textField = getTextControl(parent);
348 GridData gd = new GridData();
349 gd.horizontalSpan = numColumns - 1;
350 if (widthInChars != UNLIMITED) {
351 GC gc = new GC(textField);
352 try {
353 Point extent = gc.textExtent("X"); //$NON-NLS-1$
354 gd.widthHint = widthInChars * extent.x;
355 } finally {
356 gc.dispose();
357 }
358 } else {
359 gd.horizontalAlignment = GridData.FILL;
360 gd.grabExcessHorizontalSpace = true;
361 }
362 textField.setLayoutData(gd);
363 }
364 /* (non-Javadoc)
365 * Method declared on FieldEditor.
366 */
367 protected void doLoad() {
368 if (textField != null) {
369 String value = getPreferenceStore().getString(getPreferenceName());
370 RGB rgb = StringConverter.asRGB(value);
371 value = Sprint.f("#%06X").a(rgb.red << 16 | rgb.green << 8 | rgb.blue).end();
372 textField.setText(value);
373 oldValue = value;
374 }
375 }
376 /* (non-Javadoc)
377 * Method declared on FieldEditor.
378 */
379 protected void doLoadDefault() {
380 if (textField != null) {
381 String value = getPreferenceStore().getDefaultString(getPreferenceName());
382 RGB rgb = StringConverter.asRGB(value);
383 value = Sprint.f("#%06X").a(rgb.red << 16 | rgb.green << 8 | rgb.blue).end();
384 textField.setText(value);
385 }
386 valueChanged();
387 }
388 /* (non-Javadoc)
389 * Method declared on FieldEditor.
390 */
391 protected void doStore() {
392 String text = textField.getText();
393 if(text.startsWith("#")) text=text.substring(1);
394 int n = -1;
395 try {
396 n = Integer.parseInt(text, 16);
397 } catch (NumberFormatException e) {
398 }
399 if (n < 0) {
400 text = getPreferenceStore().getDefaultString(getPreferenceName());
401 } else {
402 text = StringConverter.asString(new RGB((n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff));
403 }
404 getPreferenceStore().setValue(getPreferenceName(), text);
405 }
406
407 /**
408 * Returns this field editor's text control.
409 *
410 * @param parent the parent
411 * @return the text control, or <code>null</code> if no
412 * text field is created yet
413 */
414 protected Text getTextControl() {
415 return textField;
416 }
417
418 /* (non-Javadoc)
419 * Method declared on FieldEditor.
420 */
421 protected void refreshValidState() {
422 isValid = checkState();
423 }
424
425 protected void setValid(boolean isvalid) {
426 isValid = isvalid;
427 }
428
429 /**
430 * Informs this field editor's listener, if it has one, about a change
431 * to the value (<code>VALUE</code> property) provided that the old and
432 * new values are different.
433 * <p>
434 * This hook is <em>not</em> called when the text is initialized
435 * (or reset to the default value) from the preference store.
436 * </p>
437 */
438 protected void valueChanged() {
439 setPresentsDefaultValue(false);
440 boolean oldState = isValid;
441 refreshValidState();
442
443 if (isValid != oldState)
444 fireStateChanged(IS_VALID, oldState, isValid);
445
446 String newValue = textField.getText();
447 if (!newValue.equals(oldValue)) {
448 fireValueChanged(VALUE, oldValue, newValue);
449 oldValue = newValue;
450 }
451 }
452
453 void handleFocusLost() {
454 clearErrorMessage();
455 }
456
457 ////////////////////////////////////////////////////////////////////////
458
459 }