1 // $Id: Flu_Spinner.cxx 900 2009-08-13 20:00:45Z larry $
2 
3 /***************************************************************
4  *                FLU - FLTK Utility Widgets
5  *  Copyright (C) 2002 Ohio Supercomputer Center, Ohio State University
6  *
7  * This file and its content is protected by a software license.
8  * You should have received a copy of this license with this file.
9  * If not, please contact the Ohio Supercomputer Center immediately:
10  * Attn: Jason Bryan Re: FLU 1224 Kinnear Rd, Columbus, Ohio 43212
11  *
12  ***************************************************************/
13 
14 
15 
16 #include <stdio.h>
17 #include <string.h>
18 #include <FL/Fl.H>
19 #include <FL/fl_draw.H>
20 #include <stdlib.h>
21 #include <FL/math.h>
22 
23 #include "Flu_Spinner.h"
24 
25 #define ABS( x ) ( (x)>0 ? (x) : -(x) )
26 
NoTabInput(Flu_Spinner * s,int x,int y,int w,int h,const char * l)27 Flu_Spinner::NoTabInput::NoTabInput (Flu_Spinner * s, int x, int y, int w, int h,
28 				     const char *l)
29     :
30 Fl_Input (x, y, w, h, l)
31 {
32     spinner = s;
33 }
34 
35 int
handle(int event)36 Flu_Spinner::NoTabInput::handle (int event)
37 {
38     switch (event) {
39     case FL_KEYDOWN:
40 	{
41 	    switch (Fl::event_key ()) {
42 	    case FL_Tab:
43 		redraw ();
44 		return 0;
45 
46 	    case FL_Enter:
47 	    case FL_KP_Enter:
48 		Fl_Input::handle (event);
49 		spinner->value (spinner->clamp (atof (value ())));
50 		spinner->do_callback ();
51 		return 1;
52 
53 	    case FL_Down:
54 	    case FL_Up:
55 		spinner->handle (event);
56 		return 1;
57 	    }
58 	}
59 	break;
60 
61     case FL_FOCUS:
62     case FL_UNFOCUS:
63 	redraw ();
64 	break;
65     }
66 
67     return Fl_Input::handle (event);
68 }
69 
70 void
draw()71 Flu_Spinner::NoTabInput::draw ()
72 {
73     if (spinner->active ())
74 	activate ();
75     else
76 	deactivate ();
77 
78     if (spinner->_dragging) {
79 	if ((spinner->align () & FL_ALIGN_INSIDE) || !spinner->editable ())
80 	    position (size ());
81 	else
82 	    position (0, size ());
83     }
84 
85     Fl_Input::draw ();
86     if (Fl::focus () == this
87 	&& ((spinner->align () & FL_ALIGN_INSIDE) || !spinner->editable ()))
88 	draw_focus (box (), x (), y (), w (), h ());
89 }
90 
Flu_Spinner(int X,int Y,int W,int H,const char * l)91 Flu_Spinner::Flu_Spinner (int X, int Y, int W, int H, const char *l)
92     :
93 Fl_Valuator (X, Y, W, H, l),
94 _input (this, X, Y, W, H, 0)
95 {
96     // we always want the buttons to be square and half the height of the widget
97     int wid = W * 15 / 100;
98 
99     if (wid < H / 2)
100 	wid = H / 2;
101 
102     _dragging = false;
103     _editable = true;
104     _totalTime = 0.0f;
105     _initialDelay = 0.5f;
106     _repeatTime[0] = 0.1f;
107     _repeatTime[1] = 0.02f;
108     _rapidDelay = 2.0f;
109     _doRepeat = true;
110     _pushed = false;
111     _valbox[0] = _valbox[1] = FL_UP_BOX;
112 
113     box (FL_DOWN_BOX);
114     align (FL_ALIGN_LEFT);
115     when (FL_WHEN_CHANGED);
116     precision (2);
117     range (0, 1);
118     value (0);
119 
120     {
121 	_input.callback (input_cb, this);
122 	_input.resize (X, Y, W - wid - 1, H);
123 	_input.color (FL_WHITE, FL_SELECTION_COLOR);
124 	_input.textfont (FL_HELVETICA);
125 	_input.textsize (FL_NORMAL_SIZE);
126 	_input.textcolor (FL_FOREGROUND_COLOR);
127 	_input.type (FL_FLOAT_INPUT);
128 	value_damage ();
129     }
130 }
131 
~Flu_Spinner()132 Flu_Spinner::~Flu_Spinner ()
133 {
134     Fl::remove_timeout (repeat_callback, this);
135 }
136 
137 // taken from Fl_Counter.cxx
138 void
input_cb(Fl_Widget *,void * v)139 Flu_Spinner::input_cb (Fl_Widget *, void *v)
140 {
141     Flu_Spinner & t = *(Flu_Spinner *) v;
142     if (t.align () & FL_ALIGN_INSIDE)
143 	return;
144     double nv;
145 
146     if ((t.step () - floor (t.step ())) > 0.0 || t.step () == 0.0)
147 	nv = strtod (t._input.value (), 0);
148     else
149 	nv = strtol (t._input.value (), 0, 0);
150     if (nv != t.value () || t._input.when () & FL_WHEN_NOT_CHANGED) {
151 	if (nv < t.minimum ()) {
152 	    t.set_value (t.minimum ());
153 	    t.value_damage ();
154 	} else if (nv > t.maximum ()) {
155 	    t.set_value (t.maximum ());
156 	    t.value_damage ();
157 	} else
158 	    t.set_value (nv);
159 
160 	if (t.when ()) {
161 	    t.clear_changed ();
162 	    t.do_callback ();
163 	} else {
164 	    t.set_changed ();
165 	}
166     }
167 
168     t.value_damage ();
169 }
170 
171 void
resize(int X,int Y,int W,int H)172 Flu_Spinner::resize (int X, int Y, int W, int H)
173 {
174     // we always want the buttons to be square and half the height of the widget
175     Fl_Valuator::resize (X, Y, W, H);
176 }
177 
178 void
value_damage()179 Flu_Spinner::value_damage ()
180 {
181     char *buf;
182 
183     if (align () & FL_ALIGN_INSIDE) {
184 	int len = strlen (label ());
185 
186 	buf = (char *) malloc (len + 128);
187 	sprintf (buf, "%s", label ());
188 	format (buf + len);
189     } else {
190 	buf = (char *) malloc (128);
191 	format (buf);
192     }
193     _input.value (buf);
194 
195     if (align () == FL_ALIGN_INSIDE || !_editable)
196 	_input.position (_input.size ());
197     else
198 	_input.position (0, _input.size ());
199 
200     free (buf);
201 }
202 
203 void
draw()204 Flu_Spinner::draw ()
205 {
206     int W = w () * 15 / 100;
207 
208     if (W < h () / 2)
209 	W = h () / 2;
210     int X = x () + w () - W, Y = y ();
211 
212     // fltk 2.0 behavior
213     bool refresh;
214 
215     if (step () >= 1.0) {
216 	refresh = (_input.type () != FL_INT_INPUT);
217 	_input.type (FL_INT_INPUT);
218     } else {
219 	refresh = (_input.type () != FL_FLOAT_INPUT);
220 	_input.type (FL_FLOAT_INPUT);
221     }
222     if (refresh)
223 	value_damage ();
224 
225     // draw the up/down arrow buttons
226     fl_draw_box ((Fl_Boxtype) _valbox[0], X, Y, W, h () / 2, color ());
227     fl_draw_box ((Fl_Boxtype) _valbox[1], X, Y + h () / 2, W, h () / 2, color ());
228     fl_color (active_r ()? FL_FOREGROUND_COLOR : fl_inactive (FL_FOREGROUND_COLOR));
229     fl_polygon (X + 4, Y + h () / 2 - 4, X + W / 2, Y + 4, X + W - 4, Y + h () / 2 - 4);
230     Y += h () / 2;
231     fl_polygon (X + 4, Y + 4, X + W / 2, Y + h () / 2 - 4, X + W - 4, Y + 4);
232 
233     _input.resize (x (), y (), w () - h () / 2 - 1, h ());
234     _input.redraw ();
235 }
236 
237 void
increment_cb()238 Flu_Spinner::increment_cb ()
239 {
240     int oldWhen = when ();
241 
242     int amt = Fl::event_state (FL_SHIFT | FL_CTRL | FL_ALT) ? 10 : 1;
243 
244     if (_up)
245 	handle_drag (clamp (increment (value (), 1 * amt)));
246     else
247 	handle_drag (clamp (increment (value (), -1 * amt)));
248     when (oldWhen);
249     _lastValue = value ();
250 }
251 
252 void
repeat_callback(void * arg)253 Flu_Spinner::repeat_callback (void *arg)
254 {
255     Flu_Spinner *c = (Flu_Spinner *) arg;
256 
257     if (!c->_pushed)
258 	return;
259     c->increment_cb ();
260 
261     float delay = c->_repeatTime[0];
262 
263     if (c->_pushed && c->_totalTime >= c->_rapidDelay)
264 	delay = c->_repeatTime[1];
265 
266     c->_totalTime += delay;
267 
268     Fl::repeat_timeout (delay, repeat_callback, c);
269 }
270 
271 int
handle(int event)272 Flu_Spinner::handle (int event)
273 {
274     int W = w () * 15 / 100;
275 
276     if (W < h () / 2)
277 	W = h () / 2;
278     int X = x () + w () - W, Y = y ();
279 
280     if ((align () & FL_ALIGN_INSIDE) || !_editable) {
281 	_input.readonly (true);
282 	_input.cursor_color (FL_WHITE);
283     } else {
284 	_input.readonly (false);
285 	_input.cursor_color (FL_BLACK);
286     }
287 
288     switch (event) {
289     case FL_PUSH:
290 	_dragging = true;
291 	if (Fl::visible_focus () && handle (FL_FOCUS))
292 	    Fl::focus (this);
293 	_lastValue = value ();
294 	_lastY = Fl::event_y ();
295 	Fl::remove_timeout (repeat_callback, this);
296 	if (Fl::event_inside (X, Y, W, h () / 2))	// up button
297 	{
298 	    _pushed = true;
299 	    _valbox[0] = FL_DOWN_BOX;
300 	    _up = true;
301 	}
302 	if (Fl::event_inside (X, Y + h () / 2, W, h () / 2))	// down button
303 	{
304 	    _pushed = true;
305 	    _valbox[1] = FL_DOWN_BOX;
306 	    _up = false;
307 	}
308 	if (_pushed) {
309 	    increment_cb ();
310 	    _totalTime = _initialDelay;
311 	    if (_doRepeat)
312 		Fl::add_timeout (_initialDelay, repeat_callback, this);
313 	    handle_push ();
314 	    take_focus ();
315 	    redraw ();
316 	    return 1;
317 	}
318 	break;
319 
320     case FL_DRAG:
321 	{
322 	    // only do the dragging if the last Y differs from the current Y by more than 3 pixels
323 	    if (ABS (_lastY - Fl::event_y ()) < 3)
324 		break;
325 	    _dragging = true;
326 	    _pushed = false;
327 	    Fl::remove_timeout (repeat_callback, this);
328 	    int oldWhen = when ();
329 
330 	    handle_drag (clamp
331 			 (increment
332 			  (_lastValue,
333 			   (_lastY -
334 			    Fl::event_y ()) *
335 			   (Fl::event_state (FL_SHIFT | FL_CTRL | FL_ALT) ? 10 : 1))));
336 	    _valbox[0] = _valbox[1] = FL_DOWN_BOX;
337 	    when (oldWhen);
338 	    fl_cursor ((Fl_Cursor) 22);
339 	    _input.redraw ();
340 	    redraw ();
341 	}
342 	break;
343 
344     case FL_RELEASE:
345 	{
346 	    bool doCB = ((when () & FL_WHEN_RELEASE)
347 			 || (when () & FL_WHEN_RELEASE_ALWAYS)) && (_pushed
348 								    ||
349 								    ((_valbox[0] ==
350 								      FL_DOWN_BOX) ^
351 								     (_valbox[1] ==
352 								      FL_DOWN_BOX)));
353 	    _pushed = false;
354 	    _dragging = false;
355 	    Fl::remove_timeout (repeat_callback, this);
356 	    _valbox[0] = _valbox[1] = FL_UP_BOX;
357 	    fl_cursor (FL_CURSOR_DEFAULT);
358 	    redraw ();
359 	    handle_release ();
360 	    if (doCB)
361 		do_callback ();
362 	    _input.take_focus ();
363 	}
364 	break;
365 
366     case FL_FOCUS:
367     case FL_UNFOCUS:
368 	redraw ();
369 	_input.take_focus ();
370 	return 0;
371 
372     case FL_ENTER:
373 	if (Fl::event_inside (&_input))
374 	    return _input.handle (event);
375 	else if (active_r ()) {
376 	    fl_cursor (FL_CURSOR_DEFAULT);
377 	    return 1;
378 	}
379 	break;
380 
381     case FL_MOVE:
382 	return 1;
383 	break;
384 
385     case FL_LEAVE:
386 	if (Fl::event_inside (&_input))
387 	    return _input.handle (event);
388 	else if (active_r ()) {
389 	    fl_cursor (FL_CURSOR_DEFAULT);
390 	    return 1;
391 	}
392 	break;
393 
394     case FL_KEYBOARD:
395 	switch (Fl::event_key ()) {
396 	case FL_Down:
397 	    {
398 		int oldWhen = when ();
399 
400 		when (FL_WHEN_CHANGED);
401 		handle_drag (clamp
402 			     (increment
403 			      (value (),
404 			       -1 *
405 			       (Fl::
406 				event_state (FL_SHIFT | FL_CTRL | FL_ALT) ? 10 : 1))));
407 		when (oldWhen);
408 		redraw ();
409 		return 1;
410 	    }
411 	case FL_Up:
412 	    {
413 		int oldWhen = when ();
414 
415 		when (FL_WHEN_CHANGED);
416 		handle_drag (clamp
417 			     (increment
418 			      (value (),
419 			       1 *
420 			       (Fl::
421 				event_state (FL_SHIFT | FL_CTRL | FL_ALT) ? 10 : 1))));
422 		when (oldWhen);
423 		redraw ();
424 		return 1;
425 	    }
426 	}
427 	break;
428     }
429 
430     return _input.handle (event);
431 }
432