1 /****************************************************************************
2 
3   GLUI User Interface Toolkit
4   ---------------------------
5 
6      glui_spinner.cpp - GLUI_Spinner class
7 
8 
9   notes:
10      spinner does not explicitly keep track of the current value - this is all
11         handled by the underlying edittext control
12         -> thus, spinner->sync_live() has no meaning, nor spinner->output_live
13 	-> BUT, edittext will alter this spinner's float_val and int_val,
14 	   so that spinner->get/set will work
15 
16 
17 FIXME: there's a heck of a lot of duplication between this and glui_scrollbar.cpp.
18   (OSL, 2006/06)
19 
20 
21           --------------------------------------------------
22 
23   Copyright (c) 1998 Paul Rademacher
24 
25   WWW:    http://sourceforge.net/projects/glui/
26   Forums: http://sourceforge.net/forum/?group_id=92496
27 
28   This library is free software; you can redistribute it and/or
29   modify it under the terms of the GNU Lesser General Public
30   License as published by the Free Software Foundation; either
31   version 2.1 of the License, or (at your option) any later version.
32 
33   This library is distributed in the hope that it will be useful,
34   but WITHOUT ANY WARRANTY; without even the implied warranty of
35   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
36   Lesser General Public License for more details.
37 
38   You should have received a copy of the GNU Lesser General Public
39   License along with this library; if not, write to the Free Software
40   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
41 
42 *****************************************************************************/
43 
44 #include "glui_internal_control.h"
45 #include <cmath>
46 #include <cassert>
47 
48 /*static int __debug=0;              */
49 
50 #define  GLUI_SPINNER_GROWTH_STEPS         800
51 #define  GLUI_SPINNER_MIN_GROWTH_STEPS     100
52 #define  GLUI_SPINNER_CALLBACK_INTERVAL    1
53 
54 
55 /****************************** spinner_edittext_callback() ******************/
56 /*   This function is not used anymore.  It has been replaced by directly    */
57 /*   Including an optional pointer to a spinner from an edittext box         */
58 
spinner_edittext_callback(int id)59 void  spinner_edittext_callback( int id )
60 {
61   GLUI_Spinner *spinner;
62 
63   putchar( '.' ); flushout;
64 
65   spinner = (GLUI_Spinner*) id;
66 
67   if ( NOT spinner )
68     return;
69 
70   spinner->do_callbacks();
71 }
72 
73 
74 /****************************** GLUI_Spinner::GLUI_Spinner() ****************/
75 
GLUI_Spinner(GLUI_Node * parent,const char * name,int data_type,int id,GLUI_CB callback)76 GLUI_Spinner::GLUI_Spinner( GLUI_Node* parent, const char *name,
77                             int data_type, int id, GLUI_CB callback )
78 {
79   common_construct(parent, name, data_type, NULL, id, callback);
80 }
81 
82 /****************************** GLUI_Spinner::GLUI_Spinner() ****************/
83 
GLUI_Spinner(GLUI_Node * parent,const char * name,int * live_var,int id,GLUI_CB callback)84 GLUI_Spinner::GLUI_Spinner( GLUI_Node* parent, const char *name,
85                             int *live_var, int id, GLUI_CB callback )
86 {
87   common_construct(parent, name, GLUI_SPINNER_INT, live_var, id, callback);
88 }
89 
90 /****************************** GLUI_Spinner::GLUI_Spinner() ****************/
91 
GLUI_Spinner(GLUI_Node * parent,const char * name,float * live_var,int id,GLUI_CB callback)92 GLUI_Spinner::GLUI_Spinner( GLUI_Node* parent, const char *name,
93              float *live_var, int id, GLUI_CB callback )
94 {
95   common_construct(parent, name, GLUI_SPINNER_FLOAT, live_var, id, callback);
96 }
97 
98 /****************************** GLUI_Spinner::GLUI_Spinner() ****************/
99 
GLUI_Spinner(GLUI_Node * parent,const char * name,int data_t,void * live_var,int id,GLUI_CB callback)100 GLUI_Spinner::GLUI_Spinner( GLUI_Node *parent, const char *name,
101                             int data_t, void *live_var,
102                             int id, GLUI_CB callback )
103 {
104   common_construct(parent, name, data_t, live_var, id, callback);
105 }
106 
107 /****************************** GLUI_Spinner::common_construct() ************/
108 
common_construct(GLUI_Node * parent,const char * name,int data_t,void * data,int id,GLUI_CB cb)109 void GLUI_Spinner::common_construct( GLUI_Node* parent, const char *name,
110                                      int data_t, void *data,
111                                      int id, GLUI_CB cb )
112 {
113   common_init();
114 
115   if ( NOT strcmp( name, "Spinner Test" ))
116     id=id;
117 
118   int text_type;
119   if ( data_t == GLUI_SPINNER_INT ) {
120     text_type = GLUI_EDITTEXT_INT;
121   }
122   else if ( data_t == GLUI_SPINNER_FLOAT ) {
123     text_type = GLUI_EDITTEXT_FLOAT;
124   }
125   else {
126     assert(0); /* Did not pass in a valid data type */
127   }
128 
129   user_id     = id;
130   data_type   = data_t;
131   callback    = cb;
132   set_name( name );
133   //glui        = parent->get_glui();
134 
135   parent->add_control( this );
136 
137   GLUI_EditText *txt =
138     new GLUI_EditText( this, name, text_type, data, id, cb);
139 
140   edittext    = txt;  /* Link the edittext to the spinner */
141   /*      control->ptr_val     = data;               */
142 
143   edittext->spinner    = this; /* Link the spinner to the edittext */
144 
145 }
146 
147 /****************************** GLUI_Spinner::mouse_down_handler() **********/
148 
mouse_down_handler(int local_x,int local_y)149 int    GLUI_Spinner::mouse_down_handler( int local_x, int local_y )
150 {
151   this->state = find_arrow( local_x, local_y );
152   GLUI_Master.glui_setIdleFuncIfNecessary();
153 
154   /*  printf( "spinner: mouse down  : %d/%d   arrow:%d\n", local_x, local_y,
155       find_arrow( local_x, local_y ));
156       */
157 
158   if ( state != GLUI_SPINNER_STATE_UP AND state != GLUI_SPINNER_STATE_DOWN )
159     return true;
160 
161   reset_growth();
162   redraw();
163 
164   /*** ints and floats behave a bit differently.  When you click on
165     an int spinner, you expect the value to immediately go up by 1, whereas
166     for a float it'll go up only by a fractional amount.  Therefore, we
167     go ahead and increment by one for int spinners ***/
168   if ( data_type == GLUI_SPINNER_INT ) {
169     if ( state == GLUI_SPINNER_STATE_UP )
170       edittext->set_float_val( edittext->float_val + 1.0 );
171     else if ( state == GLUI_SPINNER_STATE_DOWN )
172       edittext->set_float_val( edittext->float_val - .9 );
173   }
174 
175   do_click();
176 
177   return false;
178 }
179 
180 
181 /******************************** GLUI_Spinner::mouse_up_handler() **********/
182 
mouse_up_handler(int local_x,int local_y,bool inside)183 int    GLUI_Spinner::mouse_up_handler( int local_x, int local_y, bool inside )
184 {
185   state = GLUI_SPINNER_STATE_NONE;
186   GLUI_Master.glui_setIdleFuncIfNecessary();
187 
188   /*  printf("spinner: mouse up  : %d/%d    inside: %d\n",local_x,local_y,inside);              */
189 
190   /*glutSetCursor( GLUT_CURSOR_INHERIT );              */
191   glutSetCursor( GLUT_CURSOR_LEFT_ARROW );
192   redraw();
193 
194   /*  do_callbacks(); --- stub               */
195   /*  if ( callback )               */
196   /*  callback( this->user_id );              */
197 
198   return false;
199 }
200 
201 
202 /***************************** GLUI_Spinner::mouse_held_down_handler() ******/
203 
mouse_held_down_handler(int local_x,int local_y,bool new_inside)204 int    GLUI_Spinner::mouse_held_down_handler( int local_x, int local_y,
205 					      bool new_inside)
206 {
207   int new_state;
208 
209   if ( state == GLUI_SPINNER_STATE_NONE )
210     return false;
211 
212   /*  printf("spinner: mouse held: %d/%d    inside: %d\n",local_x,local_y,
213       new_inside);
214       */
215 
216   if ( state == GLUI_SPINNER_STATE_BOTH ) {   /* dragging? */
217     do_drag( local_x, local_y );
218   }
219   else {                                      /* not dragging */
220     new_state = find_arrow( local_x, local_y );
221 
222     if ( new_state == state ) {
223       /** Still in same arrow **/
224       do_click();
225     }
226     else {
227       if ( new_inside OR 1) {
228 	/** The state changed, but we're still inside - that
229 	  means we moved off the arrow: begin dragging **/
230 	state = GLUI_SPINNER_STATE_BOTH;
231       }
232       else {
233 	/*** Here check y of mouse position to determine whether to
234 	  drag ***/
235 
236 	/* ... */
237       }
238     }
239 
240     /*** We switched to up/down dragging ***/
241     if ( state == GLUI_SPINNER_STATE_BOTH ) {
242       glutSetCursor( GLUT_CURSOR_UP_DOWN );
243       last_x = local_x;
244       last_y = local_y;
245 
246       /** If the spinner has limits, we reset the growth value, since
247 	reset_growth() will compute a new growth value for dragging
248 	vs. clicking.  If the spinner has no limits, then we just let the
249 	growth remain at whatever the user has incremented it up to **/
250       if ( edittext->has_limits != GLUI_LIMIT_NONE )
251 	reset_growth();
252     }
253 
254     redraw();
255   }
256 
257   return false;
258 }
259 
260 
261 /****************************** GLUI_Spinner::key_handler() **********/
262 
key_handler(unsigned char key,int modifiers)263 int    GLUI_Spinner::key_handler( unsigned char key,int modifiers )
264 {
265 
266 
267   return true;
268 }
269 
270 
271 /****************************** GLUI_Spinner::draw() **********/
272 
draw(int x,int y)273 void    GLUI_Spinner::draw( int x, int y )
274 {
275   GLUI_DRAWINGSENTINAL_IDIOM
276 
277   if ( enabled ) {
278     /*** Draw the up arrow either pressed or unrpessed ***/
279     if ( state == GLUI_SPINNER_STATE_UP OR state == GLUI_SPINNER_STATE_BOTH )
280       glui->std_bitmaps.draw( GLUI_STDBITMAP_SPINNER_UP_ON,
281 			      w-GLUI_SPINNER_ARROW_WIDTH-1,
282 			      GLUI_SPINNER_ARROW_Y);
283     else
284       glui->std_bitmaps.draw( GLUI_STDBITMAP_SPINNER_UP_OFF,
285 			      w-GLUI_SPINNER_ARROW_WIDTH-1,
286 			      GLUI_SPINNER_ARROW_Y);
287 
288     /*** Draw the down arrow either pressed or unrpessed ***/
289     if (state == GLUI_SPINNER_STATE_DOWN OR state == GLUI_SPINNER_STATE_BOTH)
290       glui->std_bitmaps.draw( GLUI_STDBITMAP_SPINNER_DOWN_ON,
291 			      w-GLUI_SPINNER_ARROW_WIDTH-1,
292 			      GLUI_SPINNER_ARROW_HEIGHT+GLUI_SPINNER_ARROW_Y);
293     else
294       glui->std_bitmaps.draw( GLUI_STDBITMAP_SPINNER_DOWN_OFF,
295 			      w-GLUI_SPINNER_ARROW_WIDTH-1,
296 			      GLUI_SPINNER_ARROW_HEIGHT+GLUI_SPINNER_ARROW_Y);
297   }
298   else {  /**** The spinner is disabled ****/
299     glui->std_bitmaps.draw( GLUI_STDBITMAP_SPINNER_UP_DIS,
300 			    w-GLUI_SPINNER_ARROW_WIDTH-1,
301 			    GLUI_SPINNER_ARROW_Y);
302     glui->std_bitmaps.draw( GLUI_STDBITMAP_SPINNER_DOWN_DIS,
303 			    w-GLUI_SPINNER_ARROW_WIDTH-1,
304 			    GLUI_SPINNER_ARROW_HEIGHT+GLUI_SPINNER_ARROW_Y);
305   }
306 
307   if ( active ) {
308     glColor3ub( 0, 0, 0 );
309     glEnable( GL_LINE_STIPPLE );
310     glLineStipple( 1, 0x5555 );
311   }
312   else {
313     glColor3ub( glui->bkgd_color.r,glui->bkgd_color.g,glui->bkgd_color.b );
314   }
315 
316   glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
317   glDisable( GL_CULL_FACE );
318   glBegin( GL_QUADS );
319   glVertex2i( w-GLUI_SPINNER_ARROW_WIDTH-2, 0 );
320   glVertex2i( w, 0 );
321   glVertex2i( w, h );
322   glVertex2i( w-GLUI_SPINNER_ARROW_WIDTH-2, h );
323   glEnd();
324   glDisable( GL_LINE_STIPPLE );
325   glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
326 }
327 
328 
329 /********************************* GLUI_Spinner::special_handler() **********/
330 
special_handler(int key,int modifiers)331 int    GLUI_Spinner::special_handler( int key,int modifiers )
332 {
333   if ( key == GLUT_KEY_UP ) {    /** Simulate a click in the up arrow **/
334     mouse_down_handler( x_abs + w - GLUI_SPINNER_ARROW_WIDTH + 1,
335 			y_abs + GLUI_SPINNER_ARROW_Y+1 );
336     mouse_up_handler( x_abs + w - GLUI_SPINNER_ARROW_WIDTH + 1,
337 		      y_abs + GLUI_SPINNER_ARROW_Y+1, true );
338   }
339   else if ( key == GLUT_KEY_DOWN ) {  /** Simulate a click in the up arrow **/
340     mouse_down_handler(x_abs + w - GLUI_SPINNER_ARROW_WIDTH + 1,
341 		       y_abs+GLUI_SPINNER_ARROW_Y+1+GLUI_SPINNER_ARROW_HEIGHT);
342     mouse_up_handler( x_abs + w - GLUI_SPINNER_ARROW_WIDTH + 1,
343 		      y_abs+GLUI_SPINNER_ARROW_Y+1 +GLUI_SPINNER_ARROW_HEIGHT,
344 		      true );
345   }
346   else if ( key == GLUT_KEY_HOME ) {  /** Set value to limit top -
347 					or increment by 10 **/
348   }
349   else if ( key == GLUT_KEY_END ) {
350   }
351 
352   return true;
353 }
354 
355 
356 /******************************* GLUI_Spinner::set_float_val() ************/
357 
set_float_val(float new_val)358 void   GLUI_Spinner::set_float_val( float new_val )
359 {
360   if ( NOT edittext )
361     return;
362 
363   edittext->set_float_val( new_val );
364 }
365 
366 
367 /********************************** GLUI_Spinner::set_int_val() ************/
368 
set_int_val(int new_val)369 void   GLUI_Spinner::set_int_val( int new_val )
370 {
371   if ( NOT edittext )
372     return;
373 
374   edittext->set_int_val( new_val );
375 }
376 
377 
378 /************************************ GLUI_Spinner::update_size() **********/
379 
update_size(void)380 void   GLUI_Spinner::update_size( void )
381 {
382   if (!edittext) return;
383   /*edittext->w = this->w - GLUI_SPINNER_ARROW_WIDTH-3;              */
384   this->w = edittext->w + GLUI_SPINNER_ARROW_WIDTH + 3;
385 }
386 
387 
388 /************************************ GLUI_Spinner::find_arrow() ************/
389 
find_arrow(int local_x,int local_y)390 int    GLUI_Spinner::find_arrow( int local_x, int local_y )
391 {
392   local_x -= x_abs;
393   local_y -= y_abs;
394 
395   if ( local_x >= (w - GLUI_SPINNER_ARROW_WIDTH) AND
396        local_x <= w ) {
397 
398     if ( local_y >= GLUI_SPINNER_ARROW_Y AND
399 	 local_y <= (GLUI_SPINNER_ARROW_Y+GLUI_SPINNER_ARROW_HEIGHT) )
400       return GLUI_SPINNER_STATE_UP;
401 
402     if ( local_y >= GLUI_SPINNER_ARROW_Y+GLUI_SPINNER_ARROW_HEIGHT AND
403 	 local_y <= (GLUI_SPINNER_ARROW_Y+GLUI_SPINNER_ARROW_HEIGHT*2) )
404       return GLUI_SPINNER_STATE_DOWN;
405 
406   }
407 
408   return GLUI_SPINNER_STATE_NONE;
409 }
410 
411 
412 /***************************************** GLUI_Spinner::do_click() **********/
413 
do_click(void)414 void    GLUI_Spinner::do_click( void )
415 {
416   int    direction = 0;
417   float  incr;
418   float  modifier_factor;
419 
420   if ( state == GLUI_SPINNER_STATE_UP )
421     direction = +1;
422   else if ( state == GLUI_SPINNER_STATE_DOWN )
423     direction = -1;
424 
425   increase_growth();
426 
427   modifier_factor = 1.0;
428   if ( glui ) {
429     if ( glui->curr_modifiers & GLUT_ACTIVE_SHIFT )
430       modifier_factor = 100.0f;
431     else if ( glui->curr_modifiers & GLUT_ACTIVE_CTRL )
432       modifier_factor = .01f;
433   }
434 
435   if ( this->data_type == GLUI_SPINNER_FLOAT OR 1) {
436     incr = growth * direction * modifier_factor * user_speed;
437     edittext->set_float_val( edittext->float_val + incr );
438     /** Remember, edittext mirrors the float and int values ***/
439   }
440 
441   /*** Now update live variable and do callback.  We don't want
442     to do the callback on each iteration of this function, just on every
443     i^th iteration, where i is given by GLUI_SPINNER_CALLBACK_INTERVAL ****/
444   callback_count++;
445   if ( (callback_count % GLUI_SPINNER_CALLBACK_INTERVAL ) == 0 )
446     do_callbacks();
447 }
448 
449 
450 /***************************************** GLUI_Spinner::do_drag() **********/
451 
do_drag(int x,int y)452 void    GLUI_Spinner::do_drag( int x, int y )
453 {
454   int   delta_y;
455   float incr, modifier_factor;
456   /* int delta_x;              */
457 
458   modifier_factor = 1.0f;
459   if ( glui ) {
460     if ( glui->curr_modifiers & GLUT_ACTIVE_SHIFT )
461       modifier_factor = 100.0f;
462     else if ( glui->curr_modifiers & GLUT_ACTIVE_CTRL )
463       modifier_factor = .01f;
464   }
465 
466   /*  delta_x = x - last_x;              */
467   delta_y = -(y - last_y);
468 
469   if ( this->data_type == GLUI_SPINNER_FLOAT OR 1 ) {
470     incr = growth * delta_y * modifier_factor * user_speed;
471     edittext->set_float_val( edittext->float_val + incr );
472     /** Remember, edittext mirrors the float and int values ***/
473   }
474 
475   last_x = x;
476   last_y = y;
477 
478   /*** Now update live variable and do callback.  We don't want
479     to do the callback on each iteration of this function, just on every
480     i^th iteration, where i is given by GLUI_SPINNER_CALLBACK_INTERVAL ****/
481 
482   callback_count++;
483   if ( (callback_count % GLUI_SPINNER_CALLBACK_INTERVAL ) == 0 )
484     do_callbacks();
485 }
486 
487 
488 /***************************************** GLUI_Spinner::needs_idle() ******/
489 
needs_idle(void) const490 bool GLUI_Spinner::needs_idle( void ) const
491 {
492   if  (state == GLUI_SPINNER_STATE_UP OR state == GLUI_SPINNER_STATE_DOWN ) {
493     return true;
494   }
495   else {
496     return false;
497   }
498 }
499 
500 /***************************************** GLUI_Spinner::idle() **********/
501 
idle(void)502 void    GLUI_Spinner::idle( void )
503 {
504   if ( NOT needs_idle() )
505     return;
506   else
507     do_click();
508 }
509 
510 
511 /************************************ GLUI_Spinner::do_callbacks() **********/
512 
do_callbacks(void)513 void    GLUI_Spinner::do_callbacks( void )
514 {
515   /*** This is not necessary, b/c edittext automatically updates us ***/
516   if ( NOT edittext )
517     return;
518   this->float_val = edittext->float_val;
519   this->int_val   = edittext->int_val;
520   /*    *******************************************/
521 
522   if ( NOT first_callback ) {
523     if ( data_type == GLUI_SPINNER_INT AND int_val == last_int_val ) {
524       return;
525     }
526 
527     if ( data_type == GLUI_SPINNER_FLOAT AND float_val == last_float_val ) {
528       return;
529     }
530   }
531 
532   this->execute_callback();
533 
534   last_int_val   = int_val;
535   last_float_val = float_val;
536   first_callback = false;
537 }
538 
539 
540 /********************************* GLUI_Spinner::set_float_limits() *********/
541 
set_float_limits(float low,float high,int limit_type)542 void GLUI_Spinner::set_float_limits( float low, float high, int limit_type )
543 {
544   if ( NOT edittext )
545     return;
546 
547   edittext->set_float_limits( low, high, limit_type );
548 }
549 
550 
551 /*********************************** GLUI_Spinner::set_int_limits() *********/
552 
set_int_limits(int low,int high,int limit_type)553 void   GLUI_Spinner::set_int_limits( int low, int high, int limit_type )
554 {
555   if ( NOT edittext )
556     return;
557 
558   edittext->set_int_limits( low, high, limit_type );
559 }
560 
561 
562 /*********************************** GLUI_Spinner:reset_growth() *************/
563 
reset_growth(void)564 void    GLUI_Spinner::reset_growth( void )
565 {
566   float lo, hi;
567 
568   if ( edittext->has_limits == GLUI_LIMIT_NONE ) {
569     if ( data_type == GLUI_SPINNER_FLOAT )
570       growth = sqrt(ABS(edittext->float_val)) * .05f;
571     else if ( data_type == GLUI_SPINNER_INT )
572       growth = .4f;
573   }
574   else {
575     if ( data_type == GLUI_SPINNER_FLOAT ) {
576       lo = edittext->float_low;
577       hi = edittext->float_high;
578       growth = (hi-lo) / GLUI_SPINNER_GROWTH_STEPS;
579     }
580     else if ( data_type == GLUI_SPINNER_INT ) {
581       lo = (float) edittext->int_low;
582       hi = (float) edittext->int_high;
583 
584       growth = (hi-lo) / GLUI_SPINNER_GROWTH_STEPS;
585     }
586   }
587 
588   if ( growth == 0.0f )
589     growth = .001f;
590 }
591 
592 
593 /******************************* GLUI_Spinner:increase_growth() *************/
594 
increase_growth(void)595 void    GLUI_Spinner::increase_growth( void )
596 {
597   float hi = 0.0,lo = 0.0;
598 
599   if ( data_type == GLUI_SPINNER_FLOAT ) {
600     lo = edittext->float_low;
601     hi = edittext->float_high;
602   }
603   else if ( data_type == GLUI_SPINNER_INT ) {
604     lo = (float) edittext->int_low;
605     hi = (float) edittext->int_high;
606   }
607 
608   if ( growth < (hi-lo) / GLUI_SPINNER_MIN_GROWTH_STEPS )
609     growth *= growth_exp;
610 
611   /*  printf( "growth: %f\n", growth );              */
612 }
613 
614 
615 /*************************************** GLUI_Spinner:get_text() *************/
616 
get_text(void)617 const char    *GLUI_Spinner::get_text( void )
618 {
619   if (edittext)
620     return edittext->text.c_str();
621   else
622     return "";
623 }
624 
625 
626 /********************************** GLUI_Spinner:get_float_val() *************/
627 
get_float_val(void)628 float    GLUI_Spinner::get_float_val( void )
629 {
630   if (edittext)
631     return edittext->float_val;
632   else
633     return 0.0f;
634 }
635 
636 
637 /********************************** GLUI_Spinner:get_int_val() *************/
638 
get_int_val(void)639 int    GLUI_Spinner::get_int_val( void )
640 {
641   if (edittext)
642     return edittext->int_val;
643   else
644     return 0;
645 }
646 
647 
648