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 /**
20  * \file dial.c
21  *
22  *  This file is part of the XForms library package.
23  *  Copyright (c) 1996-2002  T.C. Zhao and Mark Overmars
24  *  All rights reserved.
25  *
26  *  Default 0 is at 6 oclock and clock-wise is positive.
27  */
28 
29 #define SIX_OCLOCK 1
30 
31 #ifdef HAVE_CONFIG_H
32 #include "config.h"
33 #endif
34 
35 #include "include/forms.h"
36 #include "flinternal.h"
37 #include "private/pdial.h"
38 
39 #include <math.h>
40 #include <sys/types.h>
41 #include <stdlib.h>
42 
43 
44 #ifndef M_PI
45 #define M_PI 3.14159265359
46 #endif
47 
48 
49 static double xo,
50               yo;
51 
52 
53 /***************************************
54  ***************************************/
55 
56 static void
rotate_it(FL_POINT * xp,double x,double y,double a)57 rotate_it( FL_POINT * xp,
58            double     x,
59            double     y,
60            double     a )
61 {
62     double sina = sin( a );
63     double cosa = cos( a );
64 
65     xp->x = FL_crnd( xo + ( x - xo ) * cosa + ( y - yo ) * sina );
66     xp->y = FL_crnd( yo - ( x - xo ) * sina + ( y - yo ) * cosa );
67 }
68 
69 
70 /***************************************
71  * Draws a dial
72  ***************************************/
73 
74 static void
draw_dial(FL_OBJECT * obj)75 draw_dial( FL_OBJECT * obj )
76 {
77     FL_Coord x,
78              y,
79              w,
80              h,
81              radius;
82     double dangle;
83     FLI_DIAL_SPEC *sp = obj->spec;
84     FL_POINT xp[ 5 ];             /* need one extra for closing of polygon! */
85     int boxtype,
86         iradius;
87 
88     /* Since rotate_it() always does the rotation in the math way, i.e. 0 at
89        three o'clock and CCW, need to translate the current theta into that
90        coordiante system */
91 
92     dangle = ( sp->val - sp->b ) / sp->a;
93 
94     if ( sp->direction == FL_DIAL_CW )
95         dangle = sp->origin - dangle;
96     else
97         dangle += sp->origin;
98 
99     if ( ( dangle = fmod( dangle, 360.0 ) ) < 0.0 )
100         dangle += 360.0;
101 
102     dangle *= M_PI / 180.0;
103 
104     w = obj->w - 3;
105     h = obj->h - 3;
106 
107     x = xo = obj->x + obj->w / 2;
108     y = yo = obj->y + obj->h / 2;
109 
110     if ( FL_IS_UPBOX( obj->boxtype ) )
111         boxtype = FL_OVAL3D_UPBOX;
112     else if ( FL_IS_DOWNBOX( obj->boxtype ) )
113         boxtype = FL_OVAL3D_DOWNBOX;
114     else if ( obj->boxtype == FL_FRAME_BOX )
115         boxtype = FL_OVAL3D_FRAMEBOX;
116     else if ( obj->boxtype == FL_EMBOSSED_BOX )
117         boxtype = FL_OVAL3D_EMBOSSEDBOX;
118     else
119         boxtype = FL_OVAL_BOX;
120 
121     /* the dial itself */
122 
123     radius = 0.5 * FL_min( w, h );
124     iradius = radius - 1;         /* internal radius */
125 
126     fl_draw_box( boxtype, x - radius, y - radius, 2 * radius, 2 * radius,
127                  obj->col1, obj->bw );
128 
129     /* the "hand" */
130 
131     if ( obj->type == FL_NORMAL_DIAL )
132     {
133         FL_Coord r;
134 
135         r = FL_min( 0.5 * iradius, 15 );
136 
137         rotate_it( xp,     x + iradius - 1,     y - 2, dangle );
138         rotate_it( xp + 1, x + iradius - 1 - r, y - 2, dangle );
139         rotate_it( xp + 2, x + iradius - 1 - r, y + 2, dangle );
140         rotate_it( xp + 3, x + iradius - 1,     y + 2, dangle );
141 
142         fl_polyf( xp, 4, obj->col2 );
143     }
144     else if ( obj->type == FL_LINE_DIAL )
145     {
146         double dx = 0.1 + 0.08 * iradius,
147                dy = 0.1 + 0.08 * iradius;
148 
149         rotate_it( xp,     x,               y,      dangle );
150         rotate_it( xp + 1, x + dx,          y - dy, dangle );
151         rotate_it( xp + 2, x + iradius - 2, y,      dangle );
152         rotate_it( xp + 3, x + dx,          y + dy, dangle );
153 
154         fl_polybound( xp, 4, obj->col2 );
155     }
156     else if ( obj->type == FL_FILL_DIAL )
157     {
158         double ti,
159                delta;
160 
161         delta = ( sp->val - sp->b ) / sp->a;
162         delta = FL_abs( sp->thetai - delta );
163         delta = sp->direction == FL_DIAL_CW ? -delta : delta;
164 
165         iradius -= boxtype != FL_OVAL_BOX;
166 
167         if ( sp->direction == FL_DIAL_CCW )
168             ti = sp->thetai + sp->origin;
169         else
170             ti = sp->origin - sp->thetai;
171 
172         if ( ( ti = fmod( ti, 360.0 ) ) < 0.0 )
173             ti += 360.0;
174 
175         fl_ovalarc( 1, xo - iradius, yo - iradius, 2 * iradius, 2 * iradius,
176                     ti * 10, delta * 10, obj->col2 );
177 
178         rotate_it( xp,     xo + iradius - 1, yo, dangle );
179         rotate_it( xp + 1, xo + iradius - 1, yo, ti * M_PI / 180.0 );
180         fl_simple_line( FL_crnd( xo ), FL_crnd( yo ),
181                         xp[ 0 ].x, xp[ 0 ].y, FL_BLACK );
182         fl_simple_line( FL_crnd( xo ), FL_crnd( yo ),
183                         xp[ 1 ].x, xp[ 1 ].y, FL_BLACK );
184 
185         if ( boxtype == FL_OVAL_BOX )
186             fl_circ( x, y, iradius, FL_BLACK );
187     }
188     else
189         M_err( "draw_dial", "Bad type" );
190 
191     fl_draw_text_beside( obj->align, obj->x, obj->y, obj->w, obj->h,
192                          obj->lcol, obj->lstyle, obj->lsize, obj->label );
193 }
194 
195 
196 /***************************************
197 * Handle a mouse position change
198  ***************************************/
199 
200 static int
handle_mouse(FL_OBJECT * obj,FL_Coord mousex,FL_Coord mousey)201 handle_mouse( FL_OBJECT * obj,
202               FL_Coord    mousex,
203               FL_Coord    mousey )
204 {
205     FLI_DIAL_SPEC *sp = obj->spec;
206     double oldv,
207            val,
208            olda;
209     double mx,
210            my,
211            angle,
212            range = sp->max - sp->min;
213 
214     oldv = sp->val;
215     olda = ( oldv - sp->b ) / sp->a;
216 
217     /* convert to sane FL_coordinate system, i.e., +y up */
218 
219     mx =   mousex - ( obj->x + obj->w * 0.5 );
220     my = - mousey + ( obj->y + obj->h * 0.5 );
221 
222     /* Don't react to clicks very close to center */
223 
224     if ( fabs( mx ) < 2 && fabs( my ) < 2 )
225         return FL_RETURN_NONE;
226 
227     /* Get angle and normalize to [0, 2 * PI] */
228 
229     angle = atan2( my, mx ) * 180.0 / M_PI;
230 
231     if ( sp->direction == FL_DIAL_CW )
232         angle = sp->origin - angle;
233     else
234         angle -= sp->origin;
235 
236     if ( ( angle = fmod( angle, 360.0 ) ) < 0.0 )
237         angle += 360.0;
238 
239     val = fli_clamp( sp->a * angle + sp->b, sp->min, sp->max );
240 
241     /* Check if crossed boundary. Fix it if it did. Fixing is necessary
242        otherwise might be unable to reach thetaf(360) */
243 
244     if ( ! sp->cross_over && fabs( oldv - val ) > 0.6 * range )
245     {
246         if ( fabs( olda - sp->thetai ) < fabs( olda - sp->thetaf ) )
247             angle = sp->thetai;
248         else
249             angle = sp->thetaf;
250         val = sp->a * angle + sp->b;
251     }
252 
253     if ( sp->step != 0.0 )
254         val = ( int ) ( val / sp->step + 0.5 ) * sp->step;
255 
256     /* Allow a resolution of about 0.2 degrees */
257 
258     if ( fabs( val - oldv ) > range / 1800.0 )
259     {
260         sp->val = val;
261         fl_redraw_object( obj );
262         return FL_RETURN_CHANGED;
263     }
264 
265     return FL_RETURN_NONE;
266 }
267 
268 
269 /***************************************
270  * Function for handling mouse wheel events
271  ***************************************/
272 
273 static int
handle_mouse_wheel(FL_OBJECT * obj,XEvent * xev,int key)274 handle_mouse_wheel( FL_OBJECT * obj,
275                     XEvent *    xev,
276                     int         key )
277 {
278     FLI_DIAL_SPEC *sp = obj->spec;
279     double val,
280            step,
281            oldv = sp->val,
282            range = sp->max - sp->min;
283 
284     if ( key != FL_MBUTTON4 && key != FL_MBUTTON5 )
285         return FL_RETURN_NONE;
286 
287     step = sp->step > 0.0 ? 10.0 * sp->step : 0.1 * range;
288 
289     if ( shiftkey_down( ( ( XButtonEvent * ) xev )->state ) )
290         step *= 0.1;
291     else if ( controlkey_down( ( ( XButtonEvent * ) xev )->state ) )
292         step *= 2.5;
293 
294     if ( sp->direction == FL_DIAL_CW )
295         step = key == FL_MBUTTON4 ? - step : step;
296     else
297         step = key == FL_MBUTTON4 ? step : - step;
298 
299     val = sp->val + step;
300 
301     if ( sp->cross_over )
302     {
303         while ( val > sp->max )
304             val -= range;
305         while ( val < sp->min )
306             val += range;
307     }
308     else
309     {
310         if ( val > sp->max )
311             val = sp->max;
312         else if ( val < sp->min )
313             val = sp->min;
314     }
315 
316     if ( val != oldv )
317     {
318         sp->val = val;
319         fl_redraw_object( obj );
320         return FL_RETURN_CHANGED;
321     }
322 
323     return FL_RETURN_NONE;
324 }
325 
326 
327 /***************************************
328  * Handles an event
329  ***************************************/
330 
331 static int
handle_dial(FL_OBJECT * obj,int event,FL_Coord mx,FL_Coord my,int key FL_UNUSED_ARG,void * ev)332 handle_dial( FL_OBJECT * obj,
333              int         event,
334              FL_Coord    mx,
335              FL_Coord    my,
336              int         key  FL_UNUSED_ARG,
337              void *      ev )
338 {
339     FLI_DIAL_SPEC *sp = obj->spec;
340     int ret = FL_RETURN_NONE;
341 
342     switch ( event )
343     {
344         case FL_ATTRIB :
345             obj->align = fl_to_outside_lalign( obj->align );
346             break;
347 
348         case FL_DRAW:
349             draw_dial( obj );
350             break;
351 
352         case FL_DRAWLABEL:
353             fl_draw_text_beside( obj->align, obj->x, obj->y, obj->w, obj->h,
354                                  obj->lcol, obj->lstyle, obj->lsize,
355                                  obj->label );
356             break;
357 
358         case FL_PUSH:
359             if ( key != FL_MBUTTON1 )
360                 break;
361             sp->start_val = sp->val;
362             /* fall through */
363 
364         case FL_MOTION:
365             if ( key != FL_MBUTTON1 )
366                 break;
367 
368             if (    ( ret = handle_mouse( obj, mx, my ) )
369                  && ! ( obj->how_return & FL_RETURN_END_CHANGED ) )
370                 sp->start_val = sp->val;
371             break;
372 
373         case FL_RELEASE:
374             if ( key == FL_MBUTTON2 || key == FL_MBUTTON3 )
375                 break;
376 
377             ret = handle_mouse_wheel( obj, ev, key ) | FL_RETURN_END;
378             if ( sp->start_val != sp->val )
379                 ret |= FL_RETURN_CHANGED;
380             break;
381 
382         case FL_FREEMEM:
383             fl_free( obj->spec );
384             break;
385     }
386 
387     return ret;
388 }
389 
390 
391 /***************************************
392  ***************************************/
393 
394 static void
get_mapping(FLI_DIAL_SPEC * sp)395 get_mapping( FLI_DIAL_SPEC *sp )
396 {
397     sp->a = ( sp->max - sp->min ) / ( sp->thetaf - sp->thetai );
398     sp->b = sp->max - sp->a * sp->thetaf;
399 }
400 
401 
402 /***************************************
403  * Creates an object
404  ***************************************/
405 
406 FL_OBJECT *
fl_create_dial(int type,FL_Coord x,FL_Coord y,FL_Coord w,FL_Coord h,const char * label)407 fl_create_dial( int          type,
408                 FL_Coord     x,
409                 FL_Coord     y,
410                 FL_Coord     w,
411                 FL_Coord     h,
412                 const char * label )
413 {
414     FL_OBJECT *obj;
415     FLI_DIAL_SPEC *sp;
416 
417     obj = fl_make_object( FL_DIAL, type, x, y, w, h, label, handle_dial );
418     obj->col1     = FL_DIAL_COL1;
419     obj->col2     = FL_DIAL_COL2;
420     obj->align    = FL_DIAL_ALIGN;
421     obj->lcol     = FL_DIAL_LCOL;
422     obj->boxtype  = FL_DIAL_BOXTYPE;
423     obj->spec     = sp = fl_calloc( 1, sizeof *sp );
424 
425     sp->min       = 0.0;
426     sp->max       = 1.0;
427     sp->val       = 0.5;
428     sp->step      = 0.0;
429     sp->thetai    = 0.0;
430     sp->thetaf    = 360.0;
431     sp->origin    = 270.0;
432     sp->direction = FL_DIAL_CW;
433     get_mapping( sp );
434 
435     return obj;
436 }
437 
438 
439 /***************************************
440  * Add an object
441 ***************************************/
442 
443 FL_OBJECT *
fl_add_dial(int type,FL_Coord x,FL_Coord y,FL_Coord w,FL_Coord h,const char * label)444 fl_add_dial( int          type,
445              FL_Coord     x,
446              FL_Coord     y,
447              FL_Coord     w,
448              FL_Coord     h,
449              const char * label )
450 {
451     FL_OBJECT *obj = fl_create_dial( type, x, y, w, h, label );
452 
453     /* Set default return policy for the object */
454 
455     fl_set_object_return( obj, FL_RETURN_END_CHANGED );
456 
457     fl_add_object( fl_current_form, obj );
458     fl_set_object_dblbuffer( obj, 1 );
459 
460     return obj;
461 }
462 
463 
464 /***************************************
465  ***************************************/
466 
467 void
fl_set_dial_value(FL_OBJECT * obj,double val)468 fl_set_dial_value( FL_OBJECT * obj,
469                    double      val )
470 {
471     FLI_DIAL_SPEC *sp = obj->spec;
472 
473     if ( sp->val != val )
474     {
475         sp->val = sp->start_val = val;
476         fl_redraw_object( obj );
477     }
478 }
479 
480 
481 /***************************************
482  ***************************************/
483 
484 void
fl_set_dial_bounds(FL_OBJECT * obj,double min,double max)485 fl_set_dial_bounds( FL_OBJECT * obj,
486                     double      min,
487                     double      max )
488 {
489     FLI_DIAL_SPEC *sp = obj->spec;
490 
491     if ( sp->min != min || sp->max != max )
492     {
493         sp->min = min;
494         sp->max = max;
495         get_mapping( sp );
496         sp->val = fli_clamp( sp->val, sp->min, sp->max );
497         fl_redraw_object( obj );
498     }
499 }
500 
501 
502 /***************************************
503  ***************************************/
504 
505 void
fl_set_dial_angles(FL_OBJECT * obj,double amin,double amax)506 fl_set_dial_angles( FL_OBJECT * obj,
507                     double      amin,
508                     double      amax )
509 {
510     FLI_DIAL_SPEC *sp = obj->spec;
511 
512     if ( ( amin = fmod( amin, 360.0 ) ) < 0.0 )
513         amin += 360.0;
514     if ( ( amax = fmod( amax, 360.0 ) ) <= 0.0 )
515         amax += 360.0;
516 
517     if ( sp->thetaf != amax || sp->thetai != amin )
518     {
519         sp->thetaf = amax;
520         sp->thetai = amin;
521         get_mapping( sp );
522         fl_redraw_object( obj );
523     }
524 }
525 
526 
527 /***************************************
528  ***************************************/
529 
530 void
fl_get_dial_angles(FL_OBJECT * obj,double * amin,double * amax)531 fl_get_dial_angles( FL_OBJECT * obj,
532                     double    * amin,
533                     double    * amax )
534 {
535     FLI_DIAL_SPEC *sp = obj->spec;
536 
537     *amin = sp->thetai;
538     *amax = sp->thetaf;
539 }
540 
541 
542 /***************************************
543  ***************************************/
544 
545 void
fl_set_dial_cross(FL_OBJECT * obj,int flag)546 fl_set_dial_cross( FL_OBJECT * obj,
547                    int         flag )
548 {
549     ( ( FLI_DIAL_SPEC * ) obj->spec )->cross_over = flag;
550 }
551 
552 
553 /***************************************
554  ***************************************/
555 
556 double
fl_get_dial_value(FL_OBJECT * obj)557 fl_get_dial_value( FL_OBJECT * obj )
558 {
559     return ( ( FLI_DIAL_SPEC * ) obj->spec )->val;
560 }
561 
562 
563 /***************************************
564  ***************************************/
565 
566 void
fl_get_dial_bounds(FL_OBJECT * obj,double * min,double * max)567 fl_get_dial_bounds( FL_OBJECT * obj,
568                     double *    min,
569                     double *    max )
570 {
571     *min = ( ( FLI_DIAL_SPEC * ) obj->spec )->min;
572     *max = ( ( FLI_DIAL_SPEC * ) obj->spec )->max;
573 }
574 
575 
576 /***************************************
577  * Sets under which conditions the object is to be returned to the
578  * application. This function should be regarded as deprecated and
579  * fl_set_object_return() should be used instead.
580  ***************************************/
581 
582 void
fl_set_dial_return(FL_OBJECT * obj,unsigned int when)583 fl_set_dial_return( FL_OBJECT    * obj,
584                     unsigned int   when )
585 {
586     fl_set_object_return( obj, when );
587 }
588 
589 
590 /***************************************
591  * Sets the step size to which values are rounded.
592  ***************************************/
593 
594 void
fl_set_dial_step(FL_OBJECT * obj,double value)595 fl_set_dial_step( FL_OBJECT * obj,
596                   double      value )
597 {
598     ( ( FLI_DIAL_SPEC * ) obj->spec )->step = value;
599 }
600 
601 
602 /***************************************
603  * Returns the step size to which values are rounded.
604  ***************************************/
605 
606 double
fl_get_dial_step(FL_OBJECT * obj)607 fl_get_dial_step( FL_OBJECT * obj )
608 {
609     return ( ( FLI_DIAL_SPEC * ) obj->spec )->step;
610 }
611 
612 
613 /***************************************
614  ***************************************/
615 
616 void
fl_set_dial_direction(FL_OBJECT * obj,int dir)617 fl_set_dial_direction( FL_OBJECT * obj,
618                        int         dir )
619 {
620     FLI_DIAL_SPEC *sp = obj->spec;
621 
622     if ( sp->direction != dir )
623     {
624         sp->direction = dir;
625         get_mapping( sp );
626         fl_redraw_object( obj );
627     }
628 }
629 
630 
631 /***************************************
632  ***************************************/
633 
634 int
fl_get_dial_direction(FL_OBJECT * obj)635 fl_get_dial_direction( FL_OBJECT * obj )
636 {
637     return ( ( FLI_DIAL_SPEC * ) obj->spec )->direction;
638 }
639 
640 
641 /*
642  * Local variables:
643  * tab-width: 4
644  * indent-tabs-mode: nil
645  * End:
646  */
647