1 /*
2  *  This file is part of the XForms library package.
3  *
4  *  XForms is free software; you can redistribute it and/or modify it
5  *  under the terms of the GNU Lesser General Public License as
6  *  published by the Free Software Foundation; either version 2.1, or
7  *  (at your option) any later version.
8  *
9  *  XForms is distributed in the hope that it will be useful, but
10  *  WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  *  Lesser General Public License for more details.
13  *
14  *  You should have received a copy of the GNU Lesser General Public License
15  *  along with XForms.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22 
23 #include "include/forms.h"
24 #include "flinternal.h"
25 #include "private/pspinner.h"
26 
27 #include <string.h>
28 #include <stdlib.h>
29 #include <float.h>
30 
31 
32 static void
33 set_spinner_return( FL_OBJECT *,
34                     unsigned int );
35 
36 
37 /***************************************
38  * This function got to be called before a redraw, at least if
39  * the form the spinner belongs to has been resized or proper-
40  * ties of the spinner have been changed. It calculates the
41  * new positions and sizes of the objects the spinner widget
42  * is made up from.
43  ***************************************/
44 
45 static void
set_geom(FL_OBJECT * obj)46 set_geom( FL_OBJECT * obj )
47 {
48     FLI_SPINNER_SPEC *sp = obj->spec;
49     FL_Coord bwh;
50 
51     if ( obj->w >= obj->h )
52     {
53         bwh = obj->h / 2;
54         bwh = FL_max( bwh, 1 );
55         obj->h = 2 * bwh;
56 
57         sp->input->x = obj->x;
58         sp->input->y = obj->y;
59         sp->input->w = obj->w - bwh - 1;
60         sp->input->h = obj->h;
61 
62         sp->up->x = sp->down->x = obj->x + obj->w - bwh - 1;
63         sp->up->y = obj->y;
64         sp->down->y = obj->y + bwh;
65         sp->up->w = sp->up->h = sp->down->w = sp->down->h = bwh;
66 
67         if ( sp->orient == 1 )
68         {
69             sp->orient = 0;
70             fl_set_object_label( sp->up,   "@8>" );
71             fl_set_object_label( sp->down, "@2>" );
72         }
73     }
74     else
75     {
76         bwh = obj->w / 2;
77         bwh = FL_max( bwh, 1 );
78         obj->w = 2 * bwh;
79 
80         sp->input->x = obj->x;
81         sp->input->y = obj->y;
82         sp->input->w = obj->w;
83         sp->input->h = obj->h - bwh - 1;
84 
85         sp->up->y = sp->down->y = obj->y + obj->h - bwh - 1;
86         sp->up->x = obj->x + bwh;
87         sp->down->x = obj->x;
88         sp->up->w = sp->up->h = sp->down->w = sp->down->h = bwh;
89 
90         if ( sp->orient == 0 )
91         {
92             sp->orient = 1;
93             fl_set_object_label( sp->up,   "@6>" );
94             fl_set_object_label( sp->down, "@4>" );
95         }
96     }
97 
98     /* Also avoid changes of the box type and check for color changes
99        (that must be passed on to the child input object) */
100 
101     obj->boxtype = FL_NO_BOX;
102     sp->input->col1 = obj->col1;
103     sp->input->col2 = obj->col2;
104 }
105 
106 
107 /***************************************
108  * There aren't many types of events a spinner object must directly
109  * react to. If it got resized the positions and sizes of the child
110  * objects it's made up from need to be recalculated, but that can
111  * be deferred until it's redrawn. Drawing it works mostly auto-
112  * matically except the redraw of the label. And on deletion only
113  * the FLI_SPINNER_SPEC needs to be deallocated (the child objects
114  * referenced there-in have already been deallocated before).
115  ***************************************/
116 
117 static int
handle_spinner(FL_OBJECT * obj,int event,FL_Coord mx FL_UNUSED_ARG,FL_Coord my FL_UNUSED_ARG,int key FL_UNUSED_ARG,void * ev FL_UNUSED_ARG)118 handle_spinner( FL_OBJECT * obj,
119                 int         event,
120                 FL_Coord    mx   FL_UNUSED_ARG,
121                 FL_Coord    my   FL_UNUSED_ARG,
122                 int         key  FL_UNUSED_ARG,
123                 void      * ev   FL_UNUSED_ARG )
124 {
125     FLI_SPINNER_SPEC *sp = obj->spec;
126 
127     switch ( event )
128     {
129         case FL_ATTRIB :
130         case FL_RESIZED :
131             obj->align = fl_to_outside_lalign( obj->align );
132             sp->attrib = 1;
133             break;
134 
135         case FL_DRAW :
136             if ( sp->attrib )
137             {
138                 set_geom( obj );
139                 sp->attrib = 0;
140             }
141 
142             /* fall through */
143 
144         case FL_DRAWLABEL :
145             fl_draw_object_label_outside( obj );
146             break;
147 
148         case FL_FREEMEM :
149             fli_safe_free( obj->spec );
150             break;
151     }
152 
153     return 0;
154 }
155 
156 
157 /***************************************
158  * Callback for the input and button objects of the spinner,
159  * called with 'data' set to 0 for the input object and with
160  * 'data' being 1 or -1 for the up and down buttons
161  ***************************************/
162 
163 static void
spinner_callback(FL_OBJECT * obj,long data)164 spinner_callback( FL_OBJECT * obj,
165                   long        data )
166 {
167     FLI_SPINNER_SPEC *sp = obj->parent->spec;
168     const char *s_val = fl_get_input( sp->input );
169     int max_len = 4 + sp->prec + log10( DBL_MAX );
170     char *buf = NULL;
171 
172     /* Don't react to editing the input field unless user ended interaction */
173 
174     if ( data == 0 && ! ( obj->returned & FL_RETURN_END ) )
175         return;
176 
177     obj->parent->returned = FL_RETURN_NONE;
178 
179     if ( obj->parent->type == FL_INT_SPINNER )
180     {
181         int old_i_val = sp->i_val;
182 
183         if ( data == 0 )    /* spinners input object was modified */
184         {
185             char *eptr;
186             long i_val = strtol( s_val, &eptr, 10 );
187 
188             /* Check for an invalid value entered */
189 
190             if ( eptr == s_val || i_val > sp->i_max || i_val < sp->i_min )
191             {
192                 if ( ! ( obj->returned & FL_RETURN_END ) )
193                     return;
194 
195                 if ( i_val > sp->i_max )
196                     sp->i_val = sp->i_max;
197                 else if ( i_val < sp->i_min )
198                     sp->i_val = sp->i_min;
199             }
200             else
201                 sp->i_val = i_val;
202         }
203         else if ( data == 1 && obj->returned & FL_RETURN_CHANGED )
204         {
205             if ( sp->i_val <= sp->i_max - sp->i_incr )
206                 sp->i_val += sp->i_incr;
207             else
208                 sp->i_val = sp->i_max;
209         }
210         else if ( data == -1 && obj->returned & FL_RETURN_CHANGED )
211         {
212             if ( sp->i_val >= sp->i_min + sp->i_incr )
213                 sp->i_val -= sp->i_incr;
214             else
215                 sp->i_val = sp->i_min;
216         }
217 
218         buf = fl_malloc( max_len );
219         sprintf( buf, "%d", sp->i_val );
220         fl_set_input( sp->input, buf );
221         fl_free( buf );
222         buf = NULL;
223 
224         if ( data != 0 )
225         {
226             if ( sp->i_val != old_i_val )
227                 obj->parent->returned |= FL_RETURN_CHANGED;
228         }
229         else if ( obj->returned & FL_RETURN_END && sp->i_val != sp->old_ival )
230             obj->parent->returned |= FL_RETURN_CHANGED;
231 
232         if ( obj->returned & FL_RETURN_END )
233         {
234             obj->parent->returned |= FL_RETURN_END;
235             sp->old_ival = sp->i_val;
236         }
237     }
238     else
239     {
240         double old_f_val = sp->f_val;
241 
242         if ( data == 0 )
243         {
244             char *eptr;
245             double f_val = strtod( s_val, &eptr );
246 
247             if ( eptr == s_val || f_val > sp->f_max || f_val < sp->f_min )
248             {
249                 if ( ! ( obj->returned & FL_RETURN_END ) )
250                     return;
251 
252                 if ( f_val > sp->f_max )
253                     sp->f_val = sp->f_max;
254                 if ( f_val < sp->f_min )
255                     sp->f_val = sp->f_min;
256             }
257             else
258                 sp->f_val = f_val;
259         }
260         else if ( data == 1 && obj->returned & FL_RETURN_CHANGED )
261         {
262             if ( sp->f_val <= sp->f_max - sp->f_incr )
263                 sp->f_val += sp->f_incr;
264             else
265                 sp->f_val = sp->f_max;
266         }
267         else if ( data == -1 && obj->returned & FL_RETURN_CHANGED )
268         {
269             if ( sp->f_val >= sp->f_min + sp->f_incr )
270                 sp->f_val -= sp->f_incr;
271             else
272                 sp->f_val = sp->f_min;
273         }
274 
275         buf = malloc( max_len );
276         sprintf( buf, "%.*f", sp->prec, sp->f_val );
277         fl_set_input( sp->input, buf );
278         fl_free( buf );
279         buf = NULL;
280 
281         if ( data != 0 )
282         {
283             if ( sp->f_val != old_f_val )
284                 obj->parent->returned |= FL_RETURN_CHANGED;
285         }
286         else if ( obj->returned & FL_RETURN_END && sp->f_val != sp->old_fval )
287             obj->parent->returned |= FL_RETURN_CHANGED;
288 
289         if ( obj->returned & FL_RETURN_END )
290         {
291             obj->parent->returned |= FL_RETURN_END;
292             sp->old_fval = sp->f_val;
293         }
294     }
295 }
296 
297 
298 /***************************************
299  ***************************************/
300 
301 FL_OBJECT *
fl_create_spinner(int type,FL_Coord x,FL_Coord y,FL_Coord w,FL_Coord h,const char * label)302 fl_create_spinner( int          type,
303                    FL_Coord     x,
304                    FL_Coord     y,
305                    FL_Coord     w,
306                    FL_Coord     h,
307                    const char * label )
308 {
309     FL_OBJECT *obj;
310     FLI_SPINNER_SPEC *sp;
311     int orient = ( w < h );
312 
313     obj = fl_make_object( FL_SPINNER, type, x, y, w, h, label, handle_spinner );
314     obj->boxtype    = FL_NO_BOX;
315     obj->align      = FL_ALIGN_LEFT;
316     obj->set_return = set_spinner_return;
317     obj->spec       = sp = malloc( sizeof *sp );
318 
319     sp->input = fl_create_input( type == FL_INT_SPINNER ?
320                                  FL_INT_INPUT : FL_FLOAT_INPUT,
321                                  0, 0, 0, 0, NULL );
322     sp->up = fl_create_button( FL_TOUCH_BUTTON, 0, 0, 0, 0,
323                                orient == 0 ? "@8>" : "@6>" );
324     sp->down = fl_create_button( FL_TOUCH_BUTTON, 0, 0, 0, 0,
325                                  orient == 0 ? "@2>" : "@4>" );
326 
327     fl_set_object_callback( sp->input, spinner_callback,  0 );
328     fl_set_object_callback( sp->up,    spinner_callback,  1 );
329     fl_set_object_callback( sp->down,  spinner_callback, -1 );
330 
331     fl_set_button_mouse_buttons( sp->up,   1 );
332     fl_set_button_mouse_buttons( sp->down, 1 );
333 
334     fl_set_object_lcolor( sp->up,   FL_BLUE );
335     fl_set_object_lcolor( sp->down, FL_BLUE );
336 
337     obj->col1  = sp->input->col1;
338     obj->col2  = sp->input->col2;
339 
340     sp->i_val  = sp->old_ival = 0;
341     sp->i_min  = - 10000;
342     sp->i_max  = 10000;
343     sp->i_incr = 1;
344 
345     sp->f_val  = sp->old_fval = 0.0;
346     sp->f_min  = - 10000.0;
347     sp->f_max  = 10000.0;
348     sp->f_incr = 1.0;
349 
350     sp->orient = orient;
351     sp->prec   = DEFAULT_SPINNER_PRECISION;
352     sp->attrib = 1;
353 
354     fl_add_child( obj, sp->input );
355     fl_add_child( obj, sp->up );
356     fl_add_child( obj, sp->down );
357 
358     fl_set_input( sp->input, type == FL_INT_SPINNER ? "0" : "0.0" );
359 
360     /* Set default return policy for a spinner object */
361 
362     fl_set_object_return( obj, FL_RETURN_CHANGED );
363 
364     return obj;
365 }
366 
367 
368 /***************************************
369  ***************************************/
370 
371 FL_OBJECT *
fl_add_spinner(int type,FL_Coord x,FL_Coord y,FL_Coord w,FL_Coord h,const char * label)372 fl_add_spinner( int          type,
373                 FL_Coord     x,
374                 FL_Coord     y,
375                 FL_Coord     w,
376                 FL_Coord     h,
377                 const char * label )
378 {
379     FL_OBJECT *obj = fl_create_spinner( type, x, y, w, h, label );
380 
381     fl_add_object( fl_current_form, obj );
382 
383     return obj;
384 }
385 
386 
387 /***************************************
388  * Returns the spinner value
389  ***************************************/
390 
391 double
fl_get_spinner_value(FL_OBJECT * obj)392 fl_get_spinner_value( FL_OBJECT * obj )
393 {
394     FLI_SPINNER_SPEC *sp = obj->spec;
395     const char *s_val = fl_get_input( sp->input );
396     char *eptr;
397 
398     if ( obj->type == FL_INT_SPINNER )
399     {
400         long i_val = strtol( s_val, &eptr, 10 );
401 
402         if ( eptr == s_val || i_val > sp->i_max || i_val < sp->i_min )
403             i_val = sp->i_val;
404 
405         return sp->i_val = i_val;
406     }
407     else
408     {
409         double f_val = strtod( s_val, &eptr );
410 
411         if (    ( *eptr && *eptr != 'e' && *eptr != 'E' )
412              || errno == ERANGE
413              || f_val > sp->f_max
414              || f_val < sp->f_min )
415             f_val = sp->f_val;
416 
417         if ( *eptr )
418         {
419             int max_len = 4 + sp->prec + log10( DBL_MAX );
420             char *buf = fl_malloc( max_len );
421 
422             sprintf( buf, "%.*f", sp->prec, f_val );
423             fl_set_input( sp->input, buf );
424             fl_free( buf );
425         }
426 
427         return sp->f_val = f_val;
428     }
429 }
430 
431 
432 /***************************************
433  * Sets the spinner value
434  ***************************************/
435 
436 double
fl_set_spinner_value(FL_OBJECT * obj,double val)437 fl_set_spinner_value( FL_OBJECT * obj,
438                       double      val )
439 {
440     FLI_SPINNER_SPEC *sp = obj->spec;
441 
442     if ( obj->type == FL_INT_SPINNER )
443     {
444         sp->i_val = FL_nint( val );
445 
446         if ( val > sp->i_max )
447             sp->i_val = sp->i_max;
448         else if ( val < sp->i_min )
449             sp->i_val = sp->i_min;
450 
451         fl_set_input_f( sp->input, "%d", sp->i_val );
452 
453         return sp->old_ival = sp->i_val;
454     }
455 
456     sp->f_val = val;
457 
458     if ( val > sp->f_max )
459         sp->f_val = sp->f_max;
460     else if ( val < sp->f_min )
461         sp->f_val = sp->f_min;
462 
463     fl_set_input_f( sp->input, "%.*f", sp->prec, sp->f_val );
464 
465     return sp->old_fval = sp->f_val;
466 }
467 
468 
469 /***************************************
470  * Sets the lower and upper bound of the spinner value
471  ***************************************/
472 
473 void
fl_set_spinner_bounds(FL_OBJECT * obj,double min,double max)474 fl_set_spinner_bounds( FL_OBJECT * obj,
475                        double      min,
476                        double      max )
477 {
478     FLI_SPINNER_SPEC *sp = obj->spec;
479 
480     if ( min > max )
481     {
482         double tmp = min;
483         min = max;
484         max = tmp;
485     }
486 
487     if ( obj->type == FL_INT_SPINNER )
488     {
489         sp->i_min = FL_nint( min );
490         sp->i_max = FL_nint( max );
491 
492         if ( min < INT_MIN )
493             sp->i_min = INT_MIN;
494         else if ( min > INT_MAX )
495             sp->i_min = INT_MAX;
496 
497         if ( max < INT_MIN )
498             sp->i_max = INT_MIN;
499         else if ( max > INT_MAX )
500             sp->i_max = INT_MAX;
501 
502         fl_get_spinner_value( obj );
503         fl_set_spinner_value( obj, sp->i_val );
504     }
505     else
506     {
507         sp->f_min = min;
508         sp->f_max = max;
509 
510         if ( min < - DBL_MAX )
511             sp->f_min = - DBL_MAX;
512         else if ( min > DBL_MAX )
513             sp->f_min = DBL_MAX;
514 
515         if ( max < - DBL_MAX )
516             sp->f_max = - DBL_MAX;
517         else if ( max > DBL_MAX )
518             sp->f_max = DBL_MAX;
519 
520         fl_get_spinner_value( obj );
521         fl_set_spinner_value( obj, sp->f_val );
522     }
523 }
524 
525 
526 /***************************************
527  * Returns the lower and upper limit of the spinner value
528  ***************************************/
529 
530 void
fl_get_spinner_bounds(FL_OBJECT * obj,double * min,double * max)531 fl_get_spinner_bounds( FL_OBJECT * obj,
532                        double    * min,
533                        double    * max )
534 {
535     FLI_SPINNER_SPEC *sp = obj->spec;
536 
537     if ( obj->type == FL_INT_SPINNER )
538     {
539         *min = sp->i_min;
540         *max = sp->i_max;
541     }
542     else
543     {
544         *min = sp->f_min;
545         *max = sp->f_max;
546     }
547 }
548 
549 
550 /***************************************
551  * Sets the step size (increment or decrement value when the up
552  * or down button is clicked on) for the spinner. For FL_INT_SPINNER
553  * objects the number is rounded to the nearest integer.
554  ***************************************/
555 
556 void
fl_set_spinner_step(FL_OBJECT * obj,double step)557 fl_set_spinner_step( FL_OBJECT * obj,
558                      double      step )
559 {
560     FLI_SPINNER_SPEC *sp = obj->spec;
561 
562     if ( step <= 0.0 )
563         return;
564 
565     if ( obj->type == FL_INT_SPINNER )
566     {
567         int istep = FL_nint( step );
568 
569         if ( istep == 0 )
570             istep = 1;
571 
572         if ( istep > sp->i_max - sp->i_min )
573             sp->i_incr = sp->i_max - sp->i_min;
574         else
575             sp->i_incr = istep;
576     }
577     else
578     {
579         if ( step > sp->f_max - sp->f_min )
580             sp->f_incr = sp->f_max - sp->f_min;
581         else
582             sp->f_incr = step;
583     }
584 }
585 
586 
587 /***************************************
588  * Returns the step size (increment or decrement value when the up
589  * or down button is clicked on) for the spinner
590  ***************************************/
591 
592 double
fl_get_spinner_step(FL_OBJECT * obj)593 fl_get_spinner_step( FL_OBJECT * obj )
594 {
595     FLI_SPINNER_SPEC *sp = obj->spec;
596 
597     return obj->type == FL_INT_SPINNER ? sp->i_incr : sp->f_incr;
598 }
599 
600 
601 /***************************************
602  * Sets the number of digits to be shown agter the decimal point (works
603  * on FL_FLOAT_SPINNER objects only)
604  ***************************************/
605 
606 void
fl_set_spinner_precision(FL_OBJECT * obj,int prec)607 fl_set_spinner_precision( FL_OBJECT * obj,
608                           int         prec )
609 {
610     FLI_SPINNER_SPEC *sp = obj->spec;
611 
612     if ( obj->type == FL_INT_SPINNER || prec < 0 )
613         return;
614 
615     if ( prec > DBL_DIG )
616         prec = DBL_DIG;
617     if ( prec < 0 )
618         prec = 0;
619 
620     if ( sp->prec != prec )
621     {
622         sp->prec = prec;
623         fl_set_spinner_value( obj, fl_get_spinner_value( obj ) );
624     }
625 }
626 
627 
628 /***************************************
629  * Returns the number of digits shown after the decimal point
630  * (always returns 0 for FL_INT_SPINNER objects)
631  ***************************************/
632 
633 int
fl_get_spinner_precision(FL_OBJECT * obj)634 fl_get_spinner_precision( FL_OBJECT * obj )
635 {
636     if ( obj->type == FL_INT_SPINNER )
637         return 0;
638 
639     return ( ( FLI_SPINNER_SPEC * ) obj->spec )->prec;
640 }
641 
642 
643 /***************************************
644  * Returns the input sub-object
645  ***************************************/
646 
647 FL_OBJECT *
fl_get_spinner_input(FL_OBJECT * obj)648 fl_get_spinner_input( FL_OBJECT * obj )
649 {
650     return ( ( FLI_SPINNER_SPEC * ) obj->spec )->input;
651 }
652 
653 
654 /***************************************
655  * Returns the button sub-object for raising the spinner value
656  ***************************************/
657 
658 FL_OBJECT *
fl_get_spinner_up_button(FL_OBJECT * obj)659 fl_get_spinner_up_button( FL_OBJECT * obj )
660 {
661     return ( ( FLI_SPINNER_SPEC * ) obj->spec )->up;
662 }
663 
664 
665 /***************************************
666  * Returns the button sub-object for lowering the spinner value
667  ***************************************/
668 
669 FL_OBJECT *
fl_get_spinner_down_button(FL_OBJECT * obj)670 fl_get_spinner_down_button( FL_OBJECT * obj )
671 {
672     return ( ( FLI_SPINNER_SPEC * ) obj->spec )->down;
673 }
674 
675 
676 /***************************************
677  * Sets under which conditions the object is to be returned to the
678  * application. This function is for internal use only and to set
679  * the return policy for a spinner fl_set_object_return() should
680  * be used instead (which then will call this function).
681  ***************************************/
682 
683 static void
set_spinner_return(FL_OBJECT * obj,unsigned int when)684 set_spinner_return( FL_OBJECT    * obj,
685                     unsigned int   when )
686 {
687     FLI_SPINNER_SPEC *sp = obj->spec;
688 
689     obj->how_return = when;
690     fl_set_object_return( sp->input, FL_RETURN_ALWAYS );
691     fl_set_object_return( sp->up,    FL_RETURN_CHANGED | FL_RETURN_END );
692     fl_set_object_return( sp->down,  FL_RETURN_CHANGED | FL_RETURN_END );
693 }
694 
695 
696 /*
697  * Local variables:
698  * tab-width: 4
699  * indent-tabs-mode: nil
700  * End:
701  */
702