1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 package org.vectomatic.svg.edit.client.gxt.widget;
26
27 import java.util.ArrayList;
28 import java.util.List;
29
30 import com.extjs.gxt.ui.client.GXT;
31 import com.extjs.gxt.ui.client.event.ClickRepeaterEvent;
32 import com.extjs.gxt.ui.client.event.ComponentEvent;
33 import com.extjs.gxt.ui.client.event.Events;
34 import com.extjs.gxt.ui.client.event.FieldEvent;
35 import com.extjs.gxt.ui.client.event.Listener;
36 import com.extjs.gxt.ui.client.util.ClickRepeater;
37 import com.extjs.gxt.ui.client.util.Format;
38 import com.extjs.gxt.ui.client.util.KeyNav;
39 import com.extjs.gxt.ui.client.util.Size;
40 import com.extjs.gxt.ui.client.widget.form.NumberPropertyEditor;
41 import com.extjs.gxt.ui.client.widget.form.TwinTriggerField;
42 import com.extjs.gxt.ui.client.widget.form.Validator;
43 import com.google.gwt.dom.client.NativeEvent;
44 import com.google.gwt.i18n.client.LocaleInfo;
45 import com.google.gwt.i18n.client.NumberFormat;
46 import com.google.gwt.i18n.client.constants.NumberConstants;
47 import com.google.gwt.user.client.Element;
48
49
50
51
52
53
54
55
56
57
58 public class SpinnerFieldExt extends TwinTriggerField<Number> {
59
60
61
62
63 public class SpinnerFieldMessages extends TextFieldMessages {
64 private String maxText;
65 private String minText;
66 private String nanText;
67 private String negativeText = GXT.MESSAGES.numberField_negativeText();
68
69
70
71
72
73
74 public String getMaxText() {
75 return maxText;
76 }
77
78
79
80
81
82
83 public String getMinText() {
84 return minText;
85 }
86
87
88
89
90
91
92 public String getNanText() {
93 return nanText;
94 }
95
96
97
98
99
100
101 public String getNegativeText() {
102 return negativeText;
103 }
104
105
106
107
108
109
110
111 public void setMaxText(String maxText) {
112 this.maxText = maxText;
113 }
114
115
116
117
118
119
120
121 public void setMinText(String minText) {
122 this.minText = minText;
123 }
124
125
126
127
128
129
130
131
132 public void setNanText(String nanText) {
133 this.nanText = nanText;
134 }
135
136
137
138
139
140
141
142 public void setNegativeText(String negativeText) {
143 this.negativeText = negativeText;
144 }
145 }
146
147 protected List<Character> allowed;
148 protected NumberConstants constants;
149 protected String decimalSeparator = ".";
150 protected KeyNav<ComponentEvent> keyNav;
151 private boolean allowDecimals = true;
152 private boolean allowNegative = true;
153 private String baseChars = "0123456789";
154 private Number increment = 1d;
155 private int lastKeyCode;
156 private Number maxValue = Double.MAX_VALUE;
157 private Number minValue = Double.NEGATIVE_INFINITY;
158
159 private ClickRepeater repeater;
160 private ClickRepeater twinRepeater;
161
162
163
164
165
166 public SpinnerFieldExt() {
167 messages = new SpinnerFieldMessages();
168 propertyEditor = new NumberPropertyEditor();
169 constants = LocaleInfo.getCurrentLocale().getNumberConstants();
170 decimalSeparator = constants.decimalSeparator();
171 }
172
173
174
175
176
177
178 public boolean getAllowDecimals() {
179 return allowDecimals;
180 }
181
182
183
184
185
186
187 public boolean getAllowNegative() {
188 return allowNegative;
189 }
190
191
192
193
194
195
196 public String getBaseChars() {
197 return baseChars;
198 }
199
200
201
202
203
204
205 public NumberFormat getFormat() {
206 return getPropertyEditor().getFormat();
207 }
208
209
210
211
212
213
214 public Number getIncrement() {
215 return increment;
216 }
217
218
219
220
221
222
223 public Number getMaxValue() {
224 return maxValue;
225 }
226
227 @Override
228 public SpinnerFieldMessages getMessages() {
229 return (SpinnerFieldMessages) messages;
230 }
231
232
233
234
235
236
237 public Number getMinValue() {
238 return minValue;
239 }
240
241 @Override
242 public NumberPropertyEditor getPropertyEditor() {
243 return (NumberPropertyEditor) propertyEditor;
244 }
245
246
247
248
249
250
251
252 public Class<?> getPropertyEditorType() {
253 return getPropertyEditor().getType();
254 }
255
256
257
258
259
260
261 public void setAllowDecimals(boolean allowDecimals) {
262 this.allowDecimals = allowDecimals;
263 }
264
265
266
267
268
269
270 public void setAllowNegative(boolean allowNegative) {
271 this.allowNegative = allowNegative;
272 }
273
274
275
276
277
278
279
280 public void setBaseChars(String baseChars) {
281 assertPreRender();
282 this.baseChars = baseChars;
283 }
284
285
286
287
288
289
290 public void setFormat(NumberFormat format) {
291 getPropertyEditor().setFormat(format);
292 }
293
294
295
296
297
298
299 public void setIncrement(Number increment) {
300 this.increment = increment;
301 }
302
303
304
305
306
307
308 public void setMaxValue(Number maxValue) {
309 this.maxValue = maxValue.doubleValue();
310 if (rendered && maxValue.doubleValue() != Double.MAX_VALUE) {
311 getInputEl().dom.setAttribute("aria-valuemax", "" + maxValue);
312 }
313 }
314
315
316
317
318
319
320 public void setMinValue(Number minValue) {
321 this.minValue = minValue.doubleValue();
322 if (rendered && maxValue.doubleValue() != Double.NEGATIVE_INFINITY) {
323 getInputEl().dom.setAttribute("aria-valuemin", "" + minValue);
324 }
325 }
326
327
328
329
330
331
332
333 public void setPropertyEditorType(Class<?> type) {
334 getPropertyEditor().setType(type);
335 }
336
337 @Override
338 protected Size adjustInputSize() {
339 return new Size(isHideTrigger() ? 0 : trigger.getStyleSize().width, 0);
340 }
341
342 protected void afterRender() {
343 super.afterRender();
344 addStyleOnOver(trigger.dom, "x-form-spinner-overup");
345 addStyleOnOver(twinTrigger.dom, "x-form-spinner-overdown");
346 }
347
348 protected void doSpin(boolean up) {
349 if (!readOnly) {
350 Number n = getValue();
351 double d = n == null ? 0d : getValue().doubleValue();
352 if (up) {
353 setValue(Math.max(minValue.doubleValue(), Math.min(d + increment.doubleValue(), maxValue.doubleValue())));
354 } else {
355 setValue(Math.max(
356 minValue.doubleValue(),
357 Math.min(allowNegative ? d - increment.doubleValue() : Math.max(0, d - increment.doubleValue()),
358 maxValue.doubleValue())));
359 }
360 }
361 }
362
363 @Override
364 protected void onKeyDown(FieldEvent fe) {
365 super.onKeyDown(fe);
366
367 lastKeyCode = getKeyCode(fe.getEvent());
368 }
369
370 @Override
371 protected void onKeyPress(FieldEvent fe) {
372 super.onKeyPress(fe);
373 char key = getChar(fe.getEvent());
374
375 if (fe.isSpecialKey(lastKeyCode) || fe.isControlKey()) {
376 return;
377 }
378
379 if (!allowed.contains(key)) {
380 fe.stopEvent();
381 }
382 }
383
384 @Override
385 protected void onRender(Element target, int index) {
386 super.onRender(target, index);
387 allowed = new ArrayList<Character>();
388 for (int i = 0; i < baseChars.length(); i++) {
389 allowed.add(baseChars.charAt(i));
390 }
391
392 if (allowNegative) {
393 allowed.add('-');
394 }
395 if (allowDecimals) {
396 for (int i = 0; i < decimalSeparator.length(); i++) {
397 allowed.add(decimalSeparator.charAt(i));
398 }
399 }
400
401 Listener<ClickRepeaterEvent> listener = new Listener<ClickRepeaterEvent>() {
402 public void handleEvent(ClickRepeaterEvent be) {
403 if (SpinnerFieldExt.this.isEnabled()) {
404 if (!hasFocus) {
405 focus();
406 }
407 if (be.getType() == Events.OnClick) {
408 if (be.getEl() == trigger) {
409 onTriggerClick(null);
410 } else if (be.getEl() == twinTrigger) {
411 onTwinTriggerClick(null);
412 }
413 } else if (be.getType() == Events.OnMouseDown) {
414 if (be.getEl() == trigger) {
415 trigger.addStyleName("x-form-spinner-clickup");
416 } else if (be.getEl() == twinTrigger) {
417 twinTrigger.addStyleName("x-form-spinner-clickdown");
418 }
419
420 } else if (be.getType() == Events.OnMouseUp) {
421 if (be.getEl() == trigger) {
422 trigger.removeStyleName("x-form-spinner-clickup");
423 } else if (be.getEl() == twinTrigger) {
424 twinTrigger.removeStyleName("x-form-spinner-clickdown");
425 }
426 }
427 }
428 }
429 };
430
431 repeater = new ClickRepeater(trigger);
432 repeater.addListener(Events.OnClick, listener);
433 repeater.addListener(Events.OnMouseDown, listener);
434 repeater.addListener(Events.OnMouseUp, listener);
435 addAttachable(repeater);
436
437 twinRepeater = new ClickRepeater(twinTrigger);
438 twinRepeater.addListener(Events.OnClick, listener);
439 twinRepeater.addListener(Events.OnMouseDown, listener);
440 twinRepeater.addListener(Events.OnMouseUp, listener);
441 addAttachable(twinRepeater);
442
443 addStyleName("x-spinner-field");
444 trigger.addStyleName("x-form-spinner-up");
445 twinTrigger.addStyleName("x-form-spinner-down");
446
447 setMaxValue(maxValue);
448 setMinValue(minValue);
449 getInputEl().dom.setAttribute("role", "spinbutton");
450
451 keyNav = new KeyNav<ComponentEvent>(this) {
452 @Override
453 public void onDown(ComponentEvent ce) {
454 doSpin(false);
455 }
456
457 @Override
458 public void onUp(ComponentEvent ce) {
459 doSpin(true);
460 }
461 };
462 }
463
464 protected void onTriggerClick(ComponentEvent ce) {
465 super.onTriggerClick(ce);
466
467 if (ce == null) {
468 doSpin(true);
469 }
470 }
471
472 protected void onTwinTriggerClick(ComponentEvent ce) {
473 super.onTwinTriggerClick(ce);
474
475 if (ce == null) {
476 doSpin(false);
477 }
478 }
479
480 @Override
481 protected boolean validateValue(String value) {
482
483 Validator tv = validator;
484 validator = null;
485 if (!super.validateValue(value)) {
486 validator = tv;
487 return false;
488 }
489 validator = tv;
490 if (value.length() < 1) {
491
492 return true;
493 }
494
495 String v = value;
496
497 Number d = null;
498 try {
499 d = getPropertyEditor().convertStringValue(v);
500 } catch (Exception e) {
501 String error = "";
502 if (getMessages().getNanText() == null) {
503 error = GXT.MESSAGES.numberField_nanText(v);
504 } else {
505 error = Format.substitute(getMessages().getNanText(), v);
506 }
507 markInvalid(error);
508 return false;
509 }
510 if (d.doubleValue() < minValue.doubleValue()) {
511 String error = "";
512 if (getMessages().getMinText() == null) {
513 error = GXT.MESSAGES.numberField_minText(minValue.doubleValue());
514 } else {
515 error = Format.substitute(getMessages().getMinText(), minValue);
516 }
517 markInvalid(error);
518 return false;
519 }
520
521 if (d.doubleValue() > maxValue.doubleValue()) {
522 String error = "";
523 if (getMessages().getMaxText() == null) {
524 error = GXT.MESSAGES.numberField_maxText(maxValue.doubleValue());
525 } else {
526 error = Format.substitute(getMessages().getMaxText(), maxValue);
527 }
528 markInvalid(error);
529 return false;
530 }
531
532 if (!allowNegative && d.doubleValue() < 0) {
533 markInvalid(getMessages().getNegativeText());
534 return false;
535 }
536
537 if (validator != null) {
538 String msg = validator.validate(this, value);
539 if (msg != null) {
540 markInvalid(msg);
541 return false;
542 }
543 }
544
545 if (GXT.isAriaEnabled()) {
546 getInputEl().dom.setAttribute("aria-valuenow", "" + value);
547 }
548
549 return true;
550 }
551
552
553 private native char getChar(NativeEvent e)
554
555 ;
556
557
558 private native int getKeyCode(NativeEvent e)
559
560 ;
561
562
563
564
565
566 public ClickRepeater getRepeater() {
567 return repeater;
568 }
569
570
571
572 public ClickRepeater getTwinRepeater() {
573 return twinRepeater;
574 }
575
576 }