r25048: From the archives (patch found in one of my old working trees):
[jelmer/samba4-debian.git] / webapps / qooxdoo-0.6.5-sdk / frontend / framework / source / class / qx / ui / form / Spinner.js
1 /* ************************************************************************
2
3    qooxdoo - the new era of web development
4
5    http://qooxdoo.org
6
7    Copyright:
8      2004-2007 1&1 Internet AG, Germany, http://www.1and1.org
9
10    License:
11      LGPL: http://www.gnu.org/licenses/lgpl.html
12      EPL: http://www.eclipse.org/org/documents/epl-v10.php
13      See the LICENSE file in the project's top-level directory for details.
14
15    Authors:
16      * Sebastian Werner (wpbasti)
17      * Andreas Ecker (ecker)
18
19 ************************************************************************ */
20
21 /* ************************************************************************
22
23 #module(ui_form)
24 #embed(qx.widgettheme/arrows/up_small.gif)
25 #embed(qx.widgettheme/arrows/down_small.gif)
26
27 ************************************************************************ */
28
29 /**
30  * @event change {qx.event.type.Event}
31  */
32 qx.OO.defineClass("qx.ui.form.Spinner", qx.ui.layout.HorizontalBoxLayout,
33 function(vMin, vValue, vMax)
34 {
35   qx.ui.layout.HorizontalBoxLayout.call(this);
36
37   // ************************************************************************
38   //   BEHAVIOR
39   // ************************************************************************
40   this.setTabIndex(-1);
41
42   if (qx.core.Client.getInstance().isMshtml()) {
43     this.setStyleProperty("fontSize", "0px");
44   }
45
46
47   // ************************************************************************
48   //   MANAGER
49   // ************************************************************************
50   this._manager = new qx.type.Range();
51
52
53   // ************************************************************************
54   //   TEXTFIELD
55   // ************************************************************************
56   this._textfield = new qx.ui.form.TextField;
57   this._textfield.setAppearance("spinner-field");
58   this._textfield.setValue(String(this._manager.getValue()));
59
60   this.add(this._textfield);
61
62
63   // ************************************************************************
64   //   BUTTON LAYOUT
65   // ************************************************************************
66   this._buttonlayout = new qx.ui.layout.VerticalBoxLayout;
67   this._buttonlayout.setWidth("auto");
68   this.add(this._buttonlayout);
69
70
71   // ************************************************************************
72   //   UP-BUTTON
73   // ************************************************************************
74   this._upbutton = new qx.ui.basic.Image("widget/arrows/up_small.gif");
75   this._upbutton.setAppearance("spinner-button-up");
76   this._buttonlayout.add(this._upbutton);
77
78
79   // ************************************************************************
80   //   DOWN-BUTTON
81   // ************************************************************************
82   this._downbutton = new qx.ui.basic.Image("widget/arrows/down_small.gif");
83   this._downbutton.setAppearance("spinner-button-down");
84   this._buttonlayout.add(this._downbutton);
85
86
87   // ************************************************************************
88   //   TIMER
89   // ************************************************************************
90   this._timer = new qx.client.Timer(this.getInterval());
91
92
93   // ************************************************************************
94   //   EVENTS
95   // ************************************************************************
96   this.addEventListener("keypress", this._onkeypress, this);
97   this.addEventListener("keydown", this._onkeydown, this);
98   this.addEventListener("keyup", this._onkeyup, this);
99   this.addEventListener("mousewheel", this._onmousewheel, this);
100
101   this._textfield.addEventListener("input", this._oninput, this);
102   this._textfield.addEventListener("blur", this._onblur, this);
103   this._upbutton.addEventListener("mousedown", this._onmousedown, this);
104   this._downbutton.addEventListener("mousedown", this._onmousedown, this);
105   this._manager.addEventListener("change", this._onchange, this);
106   this._timer.addEventListener("interval", this._oninterval, this);
107
108
109   // ************************************************************************
110   //   INITIALIZATION
111   // ************************************************************************
112
113   if(vMin != null) {
114     this.setMin(vMin);
115   }
116
117   if(vMax != null) {
118     this.setMax(vMax);
119   }
120
121   if(vValue != null) {
122     this.setValue(vValue);
123   }
124 });
125
126
127
128 /*
129 ---------------------------------------------------------------------------
130   PROPERTIES
131 ---------------------------------------------------------------------------
132 */
133
134 qx.OO.changeProperty({ name : "appearance", type : "string", defaultValue : "spinner" });
135
136 /*!
137   The amount to increment on each event (keypress or mousedown).
138 */
139 qx.OO.addProperty({ name : "incrementAmount", type : "number", defaultValue : 1 });
140
141 /*!
142   The amount to increment on each event (keypress or mousedown).
143 */
144 qx.OO.addProperty({ name : "wheelIncrementAmount", type : "number", defaultValue : 1 });
145
146 /*!
147   The amount to increment on each pageup / pagedown keypress
148 */
149 qx.OO.addProperty({ name : "pageIncrementAmount", type : "number", defaultValue : 10 });
150
151 /*!
152   The current value of the interval (this should be used internally only).
153 */
154 qx.OO.addProperty({ name : "interval", type : "number", defaultValue : 100 });
155
156 /*!
157   The first interval on event based shrink/growth of the value.
158 */
159 qx.OO.addProperty({ name : "firstInterval", type : "number", defaultValue : 500 });
160
161 /*!
162   This configures the minimum value for the timer interval.
163 */
164 qx.OO.addProperty({ name : "minTimer", type : "number", defaultValue : 20 });
165
166 /*!
167   Decrease of the timer on each interval (for the next interval) until minTimer reached.
168 */
169 qx.OO.addProperty({ name : "timerDecrease", type : "number", defaultValue : 2 });
170
171 /*!
172   If minTimer was reached, how much the amount of each interval should growth (in relation to the previous interval).
173 */
174 qx.OO.addProperty({ name : "amountGrowth", type : "number", defaultValue : 1.01 });
175
176
177 qx.Proto._modifyIncrementAmount = function(propValue, propOldValue, propData)
178 {
179   this._computedIncrementAmount = propValue;
180   return true;
181 };
182
183
184
185
186
187 /*
188 ---------------------------------------------------------------------------
189   PREFERRED DIMENSIONS
190 ---------------------------------------------------------------------------
191 */
192
193 qx.Proto._computePreferredInnerWidth = function() {
194   return 50;
195 }
196
197 qx.Proto._computePreferredInnerHeight = function() {
198   return 14;
199 }
200
201
202
203
204
205 /*
206 ---------------------------------------------------------------------------
207   KEY EVENT-HANDLING
208 ---------------------------------------------------------------------------
209 */
210
211 qx.Proto._onkeypress = function(e)
212 {
213   var vIdentifier = e.getKeyIdentifier();
214
215   if (vIdentifier == "Enter" && !e.isAltPressed())
216   {
217     this._checkValue(true, false, false);
218     this._textfield.selectAll();
219   }
220   else
221   {
222     switch (vIdentifier)
223     {
224       case "Up":
225       case "Down":
226
227       case "Left":
228       case "Right":
229
230       case "Shift":
231       case "Control":
232       case "Alt":
233
234       case "Escape":
235       case "Delete":
236       case "Backspace":
237
238       case "Insert":
239
240       case "Home":
241       case "End":
242
243       case "PageUp":
244       case "PageDown":
245
246       case "NumLock":
247       case "Tab":
248         break;
249
250       default:
251         if (vIdentifier >= "0" && vIdentifier <= "9") {
252           return;
253         }
254
255         e.preventDefault();
256     }
257   }
258 }
259
260 qx.Proto._onkeydown = function(e)
261 {
262   var vIdentifier = e.getKeyIdentifier();
263
264   if (this._intervalIncrease == null)
265   {
266     switch(vIdentifier)
267     {
268       case "Up":
269       case "Down":
270         this._intervalIncrease = vIdentifier == "Up";
271         this._intervalMode = "single";
272
273         this._resetIncrements();
274         this._checkValue(true, false, false);
275
276         this._increment();
277         this._timer.startWith(this.getFirstInterval());
278
279         break;
280
281       case "PageUp":
282       case "PageDown":
283         this._intervalIncrease = vIdentifier == "PageUp";
284         this._intervalMode = "page";
285
286         this._resetIncrements();
287         this._checkValue(true, false, false);
288
289         this._pageIncrement();
290         this._timer.startWith(this.getFirstInterval());
291
292         break;
293     }
294   }
295 }
296
297 qx.Proto._onkeyup = function(e)
298 {
299   if (this._intervalIncrease != null)
300   {
301     switch(e.getKeyIdentifier())
302     {
303       case "Up":
304       case "Down":
305       case "PageUp":
306       case "PageDown":
307         this._timer.stop();
308
309         this._intervalIncrease = null;
310         this._intervalMode = null;
311     }
312   }
313 }
314
315
316
317
318
319 /*
320 ---------------------------------------------------------------------------
321   MOUSE EVENT-HANDLING
322 ---------------------------------------------------------------------------
323 */
324
325 qx.Proto._onmousedown = function(e)
326 {
327   if (!e.isLeftButtonPressed()) {
328     return;
329   }
330
331   this._checkValue(true);
332
333   var vButton = e.getCurrentTarget();
334
335   vButton.addState("pressed");
336
337   vButton.addEventListener("mouseup", this._onmouseup, this);
338   vButton.addEventListener("mouseout", this._onmouseup, this);
339
340   this._intervalIncrease = vButton == this._upbutton;
341   this._resetIncrements();
342   this._increment();
343
344   this._textfield.selectAll();
345
346   this._timer.setInterval(this.getFirstInterval());
347   this._timer.start();
348 }
349
350 qx.Proto._onmouseup = function(e)
351 {
352   var vButton = e.getCurrentTarget();
353
354   vButton.removeState("pressed");
355
356   vButton.removeEventListener("mouseup", this._onmouseup, this);
357   vButton.removeEventListener("mouseout", this._onmouseup, this);
358
359   this._textfield.selectAll();
360   this._textfield.setFocused(true);
361
362   this._timer.stop();
363   this._intervalIncrease = null;
364 }
365
366 qx.Proto._onmousewheel = function(e)
367 {
368   this._manager.setValue(this._manager.getValue() + this.getWheelIncrementAmount() * e.getWheelDelta());
369   this._textfield.selectAll();
370 }
371
372
373
374
375 /*
376 ---------------------------------------------------------------------------
377   OTHER EVENT-HANDLING
378 ---------------------------------------------------------------------------
379 */
380
381 qx.Proto._oninput = function(e) {
382   this._checkValue(true, true);
383 }
384
385 qx.Proto._onchange = function(e)
386 {
387   var vValue = this._manager.getValue();
388
389   this._textfield.setValue(String(vValue));
390
391   if (vValue == this.getMin())
392   {
393     this._downbutton.removeState("pressed");
394     this._downbutton.setEnabled(false);
395     this._timer.stop();
396   }
397   else
398   {
399     this._downbutton.setEnabled(true);
400   }
401
402   if (vValue == this.getMax())
403   {
404     this._upbutton.removeState("pressed");
405     this._upbutton.setEnabled(false);
406     this._timer.stop();
407   }
408   else
409   {
410     this._upbutton.setEnabled(true);
411   }
412
413   if (this.hasEventListeners("change")) {
414     this.dispatchEvent(new qx.event.type.Event("change"), true);
415   }
416 }
417
418 qx.Proto._onblur = function(e) {
419   this._checkValue(false);
420 }
421
422
423
424
425
426
427 /*
428 ---------------------------------------------------------------------------
429   MAPPING TO RANGE MANAGER
430 ---------------------------------------------------------------------------
431 */
432
433 qx.Proto.setValue = function(nValue) {
434   this._manager.setValue(nValue);
435 }
436
437 qx.Proto.getValue = function() {
438   this._checkValue(true);
439   return this._manager.getValue();
440 }
441
442 qx.Proto.resetValue = function() {
443   return this._manager.resetValue();
444 }
445
446 qx.Proto.setMax = function(vMax) {
447   return this._manager.setMax(vMax);
448 }
449
450 qx.Proto.getMax = function() {
451   return this._manager.getMax();
452 }
453
454 qx.Proto.setMin = function(vMin) {
455   return this._manager.setMin(vMin);
456 }
457
458 qx.Proto.getMin = function() {
459   return this._manager.getMin();
460 }
461
462
463
464
465
466
467
468
469
470 /*
471 ---------------------------------------------------------------------------
472   INTERVAL HANDLING
473 ---------------------------------------------------------------------------
474 */
475
476 qx.Proto._intervalIncrease = null;
477
478 qx.Proto._oninterval = function(e)
479 {
480   this._timer.stop();
481   this.setInterval(Math.max(this.getMinTimer(), this.getInterval()-this.getTimerDecrease()));
482
483   if (this._intervalMode == "page")
484   {
485     this._pageIncrement();
486   }
487   else
488   {
489     if (this.getInterval() == this.getMinTimer()) {
490       this._computedIncrementAmount = this.getAmountGrowth() * this._computedIncrementAmount;
491     }
492
493     this._increment();
494   }
495
496   switch(this._intervalIncrease)
497   {
498     case true:
499       if (this.getValue() == this.getMax()) {
500         return;
501       }
502
503     case false:
504       if (this.getValue() == this.getMin()) {
505         return;
506       }
507   }
508
509   this._timer.restartWith(this.getInterval());
510 }
511
512
513
514
515
516 /*
517 ---------------------------------------------------------------------------
518   UTILITY
519 ---------------------------------------------------------------------------
520 */
521
522 qx.Proto._checkValue = function(acceptEmpty, acceptEdit)
523 {
524   var el = this._textfield.getElement();
525
526   if (!el) {
527     return;
528   }
529
530   if (el.value == "")
531   {
532     if (!acceptEmpty)
533     {
534       el.value = this.resetValue();
535       this._textfield.selectAll();
536
537       return;
538     }
539   }
540   else
541   {
542     // cache working variable
543     var val = el.value;
544
545     // fix leading '0'
546     if (val.length > 1)
547     {
548       while(val.charAt(0) == "0") {
549         val = val.substr(1, val.length);
550       }
551
552       var f1 = parseInt(val) || 0;
553
554       if (f1 != el.value) {
555         el.value = f1;
556         return;
557       }
558     }
559
560     // fix for negative integer handling
561     if (val == "-" && acceptEmpty && this.getMin() < 0)
562     {
563       if (el.value != val) {
564         el.value = val;
565       }
566
567       return;
568     }
569
570     // parse the string
571     val = parseInt(val);
572
573     // main check routine
574     var doFix = true;
575     var fixedVal = this._manager._checkValue(val);
576
577     if (isNaN(fixedVal)) {
578       fixedVal = this._manager.getValue();
579     }
580
581     // handle empty string
582     if (acceptEmpty && val == "")
583     {
584       doFix = false;
585     }
586     else if (!isNaN(val))
587     {
588       // check for editmode in keypress events
589       if (acceptEdit)
590       {
591         // fix min/max values
592         if (val > fixedVal && !(val > 0 && fixedVal <= 0) && String(val).length < String(fixedVal).length)
593         {
594           doFix = false;
595         }
596         else if (val < fixedVal && !(val < 0 && fixedVal >= 0) && String(val).length < String(fixedVal).length)
597         {
598           doFix = false;
599         }
600       }
601     }
602
603     // apply value fix
604     if (doFix && el.value != fixedVal) {
605       el.value = fixedVal;
606     }
607
608     // inform manager
609     if (!acceptEdit) {
610       this._manager.setValue(fixedVal);
611     }
612   }
613 }
614
615 qx.Proto._increment = function() {
616   this._manager.setValue(this._manager.getValue() + ((this._intervalIncrease ? 1 : - 1) * this._computedIncrementAmount));
617 }
618
619 qx.Proto._pageIncrement = function() {
620   this._manager.setValue(this._manager.getValue() + ((this._intervalIncrease ? 1 : - 1) * this.getPageIncrementAmount()));
621 }
622
623 qx.Proto._resetIncrements = function()
624 {
625   this._computedIncrementAmount = this.getIncrementAmount();
626   this.resetInterval();
627 }
628
629
630
631
632
633 /*
634 ---------------------------------------------------------------------------
635   DISPOSER
636 ---------------------------------------------------------------------------
637 */
638
639 qx.Proto.dispose = function()
640 {
641   if (this.getDisposed()) {
642     return;
643   }
644
645   this.removeEventListener("keypress", this._onkeypress, this);
646   this.removeEventListener("keydown", this._onkeydown, this);
647   this.removeEventListener("keyup", this._onkeyup, this);
648   this.removeEventListener("mousewheel", this._onmousewheel, this);
649
650   if (this._textfield)
651   {
652     this._textfield.removeEventListener("blur", this._onblur, this);
653     this._textfield.removeEventListener("input", this._oninput, this);
654     this._textfield.dispose();
655     this._textfield = null;
656   }
657
658   if (this._buttonlayout)
659   {
660     this._buttonlayout.dispose();
661     this._buttonlayout = null;
662   }
663
664   if (this._upbutton)
665   {
666     this._upbutton.removeEventListener("mousedown", this._onmousedown, this);
667     this._upbutton.dispose();
668     this._upbutton = null;
669   }
670
671   if (this._downbutton)
672   {
673     this._downbutton.removeEventListener("mousedown", this._onmousedown, this);
674     this._downbutton.dispose();
675     this._downbutton = null;
676   }
677
678   if (this._timer)
679   {
680     this._timer.removeEventListener("interval", this._oninterval, this);
681     this._timer.stop();
682     this._timer.dispose();
683     this._timer = null;
684   }
685
686   if (this._manager)
687   {
688     this._manager.removeEventListener("change", this._onchange, this);
689     this._manager.dispose();
690     this._manager = null;
691   }
692
693   return qx.ui.layout.HorizontalBoxLayout.prototype.dispose.call(this);
694 }