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