Initialize libbullet git in 2.0_beta.
[platform/upstream/libbullet.git] / Extras / glui / glui_textbox.cpp
1 /****************************************************************************
2   
3   GLUI User Interface Toolkit
4   ---------------------------
5
6      glui_textbox.cpp - GLUI_TextBox control class
7
8
9           --------------------------------------------------
10
11   Copyright (c) 1998 Paul Rademacher, 2004 John Kew
12
13   WWW:    http://sourceforge.net/projects/glui/
14   Forums: http://sourceforge.net/forum/?group_id=92496
15
16   This library is free software; you can redistribute it and/or
17   modify it under the terms of the GNU Lesser General Public
18   License as published by the Free Software Foundation; either
19   version 2.1 of the License, or (at your option) any later version.
20
21   This library is distributed in the hope that it will be useful,
22   but WITHOUT ANY WARRANTY; without even the implied warranty of
23   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
24   Lesser General Public License for more details.
25
26   You should have received a copy of the GNU Lesser General Public
27   License along with this library; if not, write to the Free Software
28   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
29
30 *****************************************************************************/
31
32 #include "glui_internal_control.h"
33 #include <cmath>
34
35
36 static const int LINE_HEIGHT = 15;
37
38 /****************************** GLUI_TextBox::GLUI_TextBox() **********/
39
40 GLUI_TextBox::GLUI_TextBox(GLUI_Node *parent, GLUI_String &live_var,
41                            bool scroll, int id, GLUI_CB callback )
42 {
43   common_construct(parent, &live_var, scroll, id, callback);
44 }
45
46 /****************************** GLUI_TextBox::GLUI_TextBox() **********/
47
48 GLUI_TextBox::GLUI_TextBox( GLUI_Node *parent, bool scroll, int id,
49                             GLUI_CB callback )
50 {
51   common_construct(parent, NULL, scroll, id, callback);
52 }
53
54 /****************************** GLUI_TextBox::common_construct() **********/
55 void GLUI_TextBox::common_construct(
56   GLUI_Node *parent, GLUI_String *data, 
57   bool scroll, int id, GLUI_CB callback)
58 {
59   common_init();
60
61   GLUI_Node *tb_panel = parent;
62
63   if (scroll) {
64     GLUI_Panel *p = new GLUI_Panel(parent,"",GLUI_PANEL_NONE);
65     p->x_off = 1;
66     tb_panel = p;
67   }
68   this->ptr_val     = data;
69   if (data) {
70     this->live_type = GLUI_LIVE_STRING;
71   } else {
72     this->live_type = GLUI_LIVE_NONE;
73   }
74   this->user_id     = id;
75   this->callback    = callback;
76   this->name        = "textbox";
77   tb_panel->add_control( this );
78   if (scroll) {
79     new GLUI_Column(tb_panel, false);
80     scrollbar = 
81       new GLUI_Scrollbar(tb_panel,
82                          "scrollbar",
83                          GLUI_SCROLL_VERTICAL,
84                          GLUI_SCROLL_INT);
85     scrollbar->set_object_callback(GLUI_TextBox::scrollbar_callback, this);
86     scrollbar->set_alignment(GLUI_ALIGN_LEFT);
87     // scrollbar->can_activate = false; //kills ability to mouse drag too
88   }
89   init_live();
90 }
91
92 /****************************** GLUI_TextBox::mouse_down_handler() **********/
93
94 int    GLUI_TextBox::mouse_down_handler( int local_x, int local_y )
95 {
96   int tmp_insertion_pt;
97
98   if ( debug )    dump( stdout, "-> MOUSE DOWN" );
99
100   tmp_insertion_pt = find_insertion_pt( local_x, local_y );  
101   if ( tmp_insertion_pt == -1 ) {
102     if ( glui )
103       glui->deactivate_current_control(  );
104     return false;
105   }
106
107   insertion_pt = tmp_insertion_pt;
108
109   sel_start = sel_end = insertion_pt;
110  
111   keygoal_x = insert_x;
112
113   if ( can_draw())
114     update_and_draw_text();
115
116   if ( debug )    dump( stdout, "<- MOUSE UP" );
117
118   return true;
119 }
120
121
122 /******************************** GLUI_TextBox::mouse_up_handler() **********/
123
124 int    GLUI_TextBox::mouse_up_handler( int local_x, int local_y, bool inside )
125 {
126   return false;
127 }
128
129
130 /***************************** GLUI_TextBox::mouse_held_down_handler() ******/
131
132 int    GLUI_TextBox::mouse_held_down_handler( int local_x, int local_y,
133                            bool new_inside)
134 {
135   int tmp_pt;
136
137   if ( NOT new_inside )     return false;
138
139   if ( debug )    dump( stdout, "-> HELD DOWN" );
140   
141   tmp_pt = find_insertion_pt( local_x, local_y );
142   keygoal_x = insert_x;
143   
144   if ( tmp_pt == -1 AND sel_end != 0 ) {    /* moved mouse past left edge */
145     special_handler( GLUT_KEY_LEFT, GLUT_ACTIVE_SHIFT );
146   }
147   else if ( tmp_pt == substring_end+1 AND sel_end != (int) text.length()) {    
148     /* moved mouse past right edge */
149     special_handler( GLUT_KEY_RIGHT, GLUT_ACTIVE_SHIFT );    
150   }
151   else if ( tmp_pt != -1 AND tmp_pt != sel_end ) {
152     sel_end = insertion_pt = tmp_pt;
153     
154     update_and_draw_text();
155   }
156
157   if ( debug )
158     dump( stdout, "<- HELD DOWN" );
159
160   return false;
161 }
162
163
164 /****************************** GLUI_TextBox::key_handler() **********/
165 int    GLUI_TextBox::key_handler( unsigned char key,int modifiers )
166 {
167   int regular_key;
168   /* int has_selection;              */
169
170   if ( NOT glui )
171     return false;
172
173   if ( debug )
174     dump( stdout, "-> KEY HANDLER" );
175
176   regular_key = false;
177   bool ctrl_down = (modifiers & GLUT_ACTIVE_CTRL)!=0;
178   /*  has_selection = (sel_start != sel_end);              */
179
180   if ( key  == CTRL('[')) {         /* ESCAPE */
181     glui->deactivate_current_control();
182     return true;
183   }
184   else if ( (key == 127 AND !ctrl_down) OR  /* FORWARD DELETE */
185             ( key == CTRL('d') AND modifiers == GLUT_ACTIVE_CTRL) ) 
186   {
187     if ( sel_start == sel_end ) {   /* no selection */
188       if ( insertion_pt < (int)text.length() ) {
189         text.erase(insertion_pt,1);
190       }
191     }
192     else {                         /* There is a selection */
193       clear_substring( MIN(sel_start,sel_end), MAX(sel_start,sel_end ));
194       insertion_pt = MIN(sel_start,sel_end);
195       sel_start = sel_end = insertion_pt;
196     }
197   }
198   else if ( ((key == 127) AND ctrl_down) OR   // Delete word forward
199             ((key == 'd') AND (modifiers == GLUT_ACTIVE_ALT)) )
200   {
201     if ( sel_start == sel_end ) {   /* no selection */
202       sel_start = insertion_pt;
203       sel_end = find_word_break( insertion_pt, +1 );
204     }
205
206     clear_substring( MIN(sel_start,sel_end), MAX(sel_start,sel_end ));
207     insertion_pt = MIN(sel_start,sel_end);
208     sel_start = sel_end = insertion_pt;
209   }
210   else if ( key == CTRL('h') ) {       /* BACKSPACE */
211     if ( sel_start == sel_end ) {   /* no selection */
212       if ( insertion_pt > 0 ) {
213         insertion_pt--;
214         text.erase(insertion_pt,1);
215       }
216     }
217     else {                         /* There is a selection */
218       clear_substring( MIN(sel_start,sel_end), MAX(sel_start,sel_end ));
219       insertion_pt = MIN(sel_start,sel_end);
220       sel_start = sel_end = insertion_pt;
221     }
222   }
223   else if ( modifiers == GLUT_ACTIVE_CTRL )  /* CTRL ONLY */ 
224   {
225     /* Ctrl-key bindings */
226     if ( key == CTRL('a') ) {
227       return special_handler( GLUT_KEY_HOME, 0 );
228     }
229     else if ( key == CTRL('e') ) {
230       return special_handler( GLUT_KEY_END, 0 );
231     }
232     else if ( key == CTRL('b') ) {
233       return special_handler( GLUT_KEY_LEFT, 0 );
234     }
235     else if ( key == CTRL('f') ) {
236       return special_handler( GLUT_KEY_RIGHT, 0 );
237     }
238     else if ( key == CTRL('p') ) {
239       return special_handler( GLUT_KEY_UP, 0 );
240     }
241     else if ( key == CTRL('n') ) {
242       return special_handler( GLUT_KEY_DOWN, 0 );
243     }
244     else if ( key == CTRL('u') ) { /* ERASE LINE */
245       insertion_pt = 0;  
246       text.erase(0,text.length());
247       sel_start = sel_end = 0;
248     }
249     else if ( key == CTRL('k') ) { /* KILL TO END OF LINE */
250       sel_start = sel_end = insertion_pt;
251       text.erase(insertion_pt,GLUI_String::npos);
252     }
253   }
254   else if ( modifiers == GLUT_ACTIVE_ALT ) /* ALT ONLY */
255   {
256     if ( key == 'b' ) { // Backward word
257       return special_handler ( GLUT_KEY_LEFT, GLUT_ACTIVE_CTRL );
258     }
259     if ( key == 'f' ) { // Forward word
260       return special_handler ( GLUT_KEY_RIGHT, GLUT_ACTIVE_CTRL );
261     }
262   }
263   else if ( (modifiers & GLUT_ACTIVE_CTRL) OR
264             (modifiers & GLUT_ACTIVE_ALT) ) 
265   {
266     /** ignore other keys with modifiers */
267     return true;
268   }
269   else { /* Regular key */    
270     if ( key == 13 )           /* RETURNS are written as newlines*/
271       key = '\n';
272
273     regular_key = true;
274
275     /** This is just to get rid of warnings - the flag regular_key is 
276         set if the key was not a backspace, return, whatever.  But I
277         believe if we're here, we know it was a regular key anyway */
278     if ( regular_key ) {
279     }
280
281     /**** If there's a current selection, erase it ******/
282     if ( sel_start != sel_end ) {
283       clear_substring( MIN(sel_start,sel_end), MAX(sel_start,sel_end ));
284       insertion_pt = MIN(sel_start,sel_end);
285       sel_start = sel_end = insertion_pt;
286     }
287
288     /******** We insert the character into the string ***/
289
290     text.insert(insertion_pt,1,key);
291
292     /******** Move the insertion point and substring_end one over ******/
293     insertion_pt++;
294     substring_end++;
295
296     sel_start = sel_end = insertion_pt;
297   }
298
299   /******** Now redraw text ***********/
300   /* Hack to prevent text box from being cleared first **/  
301   /**  int substring_change =  update_substring_bounds();
302        draw_text_only = 
303        (NOT substring_change AND NOT has_selection AND regular_key ); 
304   */
305
306   draw_text_only = false;  /** Well, hack is not yet working **/
307   update_and_draw_text();
308   draw_text_only = false;
309
310
311   if ( debug )
312     dump( stdout, "<- KEY HANDLER" );
313
314   return true;
315 }
316
317 /****************************** GLUI_TextBox::enable() **********/
318
319 void GLUI_TextBox::enable( void )
320 {
321   GLUI_Control::enable();
322   scrollbar->enable();
323 }
324
325 /****************************** GLUI_TextBox::disable() **********/
326
327 void GLUI_TextBox::disable( void )
328 {
329   GLUI_Control::disable();
330   scrollbar->disable();
331 }
332
333 /****************************** GLUI_TextBox::activate() **********/
334
335 void    GLUI_TextBox::activate( int how )
336 {
337   if ( debug )
338     dump( stdout, "-> ACTIVATE" );
339   active = true;
340
341   if ( how == GLUI_ACTIVATE_MOUSE )
342     return;  /* Don't select everything if activated with mouse */
343
344   orig_text = text;
345
346   sel_start    = 0;
347   sel_end      = int(text.length());
348   insertion_pt = 0;
349   if ( debug )
350     dump( stdout, "<- ACTIVATE" );
351 }
352
353
354 /****************************** GLUI_TextBox::deactivate() **********/
355
356 void    GLUI_TextBox::deactivate( void )
357 {
358   active = false;
359
360   if ( NOT glui )
361     return;
362
363   if ( debug )
364     dump( stdout, "-> DISACTIVATE" );
365
366   sel_start = sel_end = insertion_pt = -1; 
367
368   /***** Retrieve the current value from the text *****/
369   /***** The live variable will be updated by set_text() ****/
370   set_text(text.c_str()); /* This will force callbacks and gfx refresh */
371
372   update_substring_bounds();
373
374   /******** redraw text without insertion point ***********/
375   redraw();
376
377   /***** Now do callbacks if value changed ******/
378   if ( orig_text != text ) {
379     this->execute_callback();
380     
381
382   }
383
384
385   if ( debug )
386     dump( stdout, "<- DISACTIVATE" );
387 }
388
389 /****************************** GLUI_TextBox::draw() **********/
390
391 void    GLUI_TextBox::draw( int x, int y )
392 {
393   GLUI_DRAWINGSENTINAL_IDIOM
394   int line = 0;
395   int text_length;
396   int box_width;
397   int i;
398
399   /* Bevelled Border */
400   glBegin( GL_LINES );
401   glColor3f( .5, .5, .5 );
402   glVertex2i( 0, 0 );     glVertex2i( w, 0 );
403   glVertex2i( 0, 0 );     glVertex2i( 0, h );     
404
405   glColor3f( 1., 1., 1. );
406   glVertex2i( 0, h );     glVertex2i( w, h );
407   glVertex2i( w, h );     glVertex2i( w, 0 );
408
409   if ( enabled )
410     glColor3f( 0., 0., 0. );
411   else
412     glColor3f( .25, .25, .25 );
413   glVertex2i( 1, 1 );     glVertex2i( w-1, 1 );
414   glVertex2i( 1, 1 );     glVertex2i( 1, h-1 );
415
416   glColor3f( .75, .75, .75 );
417   glVertex2i( 1, h-1 );     glVertex2i( w-1, h-1 );
418   glVertex2i( w-1, h-1 );   glVertex2i( w-1, 1 );
419   glEnd();
420
421   /* Draw Background if enabled*/
422   if (enabled) {
423     glColor3f( 1., 1., 1. );
424     glDisable( GL_CULL_FACE );
425     glBegin( GL_QUADS );
426     glVertex2i( 2, 2 );     glVertex2i( w-2, 2 );
427     glVertex2i( w-2, h-2 );               glVertex2i(2, h-2 );
428     glEnd();
429   } else {
430     glColor3f( .8, .8, .8 );
431     glDisable( GL_CULL_FACE );
432     glBegin( GL_QUADS );
433     glVertex2i( 2, 2 );     glVertex2i( w-2, 2 );
434     glVertex2i( w-2, h-2 );               glVertex2i(2, h-2 );
435     glEnd();
436   }
437
438   /* Begin Drawing Lines of Text */
439   substring_start = 0;
440   substring_end = 0;
441   text_length = int(text.length())-1;
442
443   /* Figure out how wide the box is */
444   box_width = get_box_width();
445
446   /* Get the first line substring */
447   while (substring_width(substring_start, substring_end+1 ) < box_width && 
448          substring_end < text_length && text[substring_end+1] != '\n')
449     substring_end++;
450
451   /* Figure out which lines are visible*/
452
453   visible_lines = (int)(h-20)/LINE_HEIGHT;
454   if (start_line < (curr_line-visible_lines)) {
455     for (i = 0; ((curr_line-i)*LINE_HEIGHT+20) > h; i++);
456     start_line = i;
457   } else if ( start_line > curr_line) {
458     start_line = curr_line;
459   }
460   line = 0;
461   do {
462     if (line && substring_end < text_length) {
463       substring_start = substring_end+1;
464       while (substring_width(substring_start, substring_end+1 ) < box_width && 
465              substring_end < text_length && text[substring_end+1] != '\n')
466         substring_end++; 
467     }
468     if (text[substring_end+1] == '\n') { /* Skip newline */
469       substring_end++;
470     }
471     if (line < start_line || (line > curr_line && curr_line > (start_line + visible_lines))) {
472       line++;
473       continue;
474     }
475     if ((line - start_line) <= visible_lines)
476       draw_text(0,(line - start_line)*LINE_HEIGHT); /* tabs and other nasties are handled by substring_width */
477     line++;
478   } while (substring_end < text_length);
479
480   num_lines = line;
481
482   draw_insertion_pt();
483   if (scrollbar) {
484     scrollbar->set_int_limits(MAX(0,num_lines/*-1*/-visible_lines),0);
485     glPushMatrix();
486     glTranslatef(scrollbar->x_abs-x_abs, scrollbar->y_abs-y_abs,0.0);
487     scrollbar->draw_scroll();
488     glPopMatrix();
489   }
490 }
491
492
493
494 /************************** GLUI_TextBox::update_substring_bounds() *********/
495
496 int    GLUI_TextBox::update_substring_bounds( void )
497 {
498   int box_width;
499   int text_len = int(text.length());
500   int old_start, old_end;
501
502   old_start = substring_start;
503   old_end = substring_end;
504
505   /*** Calculate the width of the usable area of the edit box ***/
506   box_width = get_box_width();
507
508   CLAMP( substring_end, 0, MAX(text_len-1,0) );
509   CLAMP( substring_start, 0, MAX(text_len-1,0) );
510
511   if ( debug )    dump( stdout, "-> UPDATE SS" );
512
513   if ( insertion_pt >= 0 AND 
514        insertion_pt < substring_start ) {   /* cursor moved left */
515     substring_start = insertion_pt;
516
517     while ( substring_width( substring_start, substring_end ) > box_width )
518       substring_end--;
519   }
520   else if ( insertion_pt > substring_end ) {  /* cursor moved right */
521     substring_end = insertion_pt-1;
522
523     while ( substring_width( substring_start, substring_end ) > box_width )
524       substring_start++;
525   }
526   else {   /* cursor is within old substring bounds */
527     if ( last_insertion_pt > insertion_pt ) {  /* cursor moved left */
528     }
529     else {
530       while ( substring_width( substring_start, substring_end ) > box_width )
531     substring_end--;
532
533       while(substring_width( substring_start, substring_end+1 ) <= box_width
534         AND substring_end < text_len-1 )
535         substring_end++;
536     }
537   }
538
539   while ( substring_width( substring_start, substring_end ) > box_width )
540     substring_end--;
541
542   last_insertion_pt = insertion_pt;
543
544   /*** No selection if not enabled ***/
545   if ( NOT enabled ) {
546     sel_start = sel_end = 0;
547   }
548
549   if ( debug )    dump( stdout, "<- UPDATE SS" );
550
551   if ( substring_start == old_start AND substring_end == old_end )
552     return false;  /*** bounds did not change ***/
553   else 
554     return true;   /*** bounds did change ***/
555   
556 }
557
558
559 /********************************* GLUI_TextBox::update_x_offsets() *********/
560
561 void    GLUI_TextBox::update_x_offsets( void )
562 {
563 }
564  
565
566 /********************************* GLUI_TextBox::draw_text() ****************/
567
568 void    GLUI_TextBox::draw_text( int x, int y )
569 {
570   GLUI_DRAWINGSENTINAL_IDIOM
571   int text_x, i, sel_lo, sel_hi, x_pos;
572
573   if ( debug )    dump( stdout, "-> DRAW_TEXT" );
574
575   /** Find where to draw the text **/
576
577   text_x = 2 + GLUI_TEXTBOX_BOXINNERMARGINX;
578
579   /** Find lower and upper selection bounds **/
580   sel_lo = MIN(sel_start, sel_end );
581   sel_hi = MAX(sel_start, sel_end );
582
583   int sel_x_start, sel_x_end, delta;
584
585   /** Draw selection area dark **/
586   if ( sel_start != sel_end ) {
587     sel_x_start = text_x;
588     sel_x_end   = text_x;
589     delta = 0;
590     for( i=substring_start; sel_x_end < (w - text_x) && i<=substring_end; i++ ) {
591       delta = 0;
592       if (text[i] == '\t') // Character is a tab, go to next tab stop
593         while (((delta + sel_x_end) < (w - text_x)) && 
594           (delta == 0 || delta % tab_width))
595           delta++;
596         else
597           delta = char_width( text[i] );
598         
599         if ( i < sel_lo ) {
600           sel_x_start += delta;
601           sel_x_end   += delta;
602         }
603         else if ( i < sel_hi ) {
604           sel_x_end   += delta;
605         }
606     }
607     
608     glColor3f( 0.0f, 0.0f, .6f );
609     glRecti(sel_x_start, y+5, sel_x_end, y+20);
610   }
611   
612
613   if ( sel_start == sel_end ) {   // No current selection 
614     x_pos = text_x;
615     if ( enabled )
616       glColor3b( 0, 0, 0 );
617     else
618       glColor3b( 32, 32, 32 );
619       
620     glRasterPos2i( text_x, y+LINE_HEIGHT);
621     for( i=substring_start; i<=substring_end; i++ ) {
622       if (this->text[i] == '\t') { // Character is a tab, go to next tab stop
623         x_pos = ((x_pos-text_x)/tab_width)*tab_width+tab_width+text_x;
624         glRasterPos2i( x_pos, y+LINE_HEIGHT); // Reposition pen after tab
625       } else {
626         glutBitmapCharacter( get_font(), this->text[i] );
627         x_pos += char_width( this->text[i] );
628       }
629     }
630   }
631   else {                        // There is a selection
632     x_pos = text_x;
633     for( i=substring_start; i<=substring_end; i++ ) {
634       if ( IN_BOUNDS( i, sel_lo, sel_hi-1)) { // This character is selected
635         glColor3f( 1., 1., 1. );
636         glRasterPos2i( x_pos, y+LINE_HEIGHT);
637         if (this->text[i] == '\t') { // Character is a tab, go to next tab stop
638          x_pos = ((x_pos-text_x)/tab_width)*tab_width+tab_width+text_x;
639         } 
640         else
641           glutBitmapCharacter( get_font(), this->text[i] );
642       }
643       else {
644         glColor3f( 0., 0., 0. );
645         glRasterPos2i( x_pos, y+LINE_HEIGHT);
646         if (this->text[i] == '\t') { // Character is a tab, go to next tab stop
647           x_pos = ((x_pos-text_x)/tab_width)*tab_width+tab_width+text_x;
648           glRasterPos2i( x_pos, y+LINE_HEIGHT); // Reposition pen after tab 
649         } else
650           glutBitmapCharacter( get_font(), this->text[i] );
651       }
652       
653       x_pos += char_width( text[i] );
654     }
655   }
656
657   if ( debug )    dump( stdout, "<- DRAW_TEXT" );  
658 }
659
660
661 /******************************** GLUI_TextBox::find_insertion_pt() *********/
662 /* This function returns the character number *before which* the insertion  */
663 /* point goes                                                               */
664
665 int  GLUI_TextBox::find_insertion_pt( int x, int y )
666 {
667   /*** See if we clicked outside box ***/
668   if ( x < this->x_abs || y < this->y_abs)
669     return -1;
670   
671   /*** See if we clicked in an empty box ***/
672   if ( text.empty() ) 
673     return 0;
674   
675   /* update insert variables */
676   insert_x = x;
677   insert_y = y;
678
679   int text_length = int(text.length())-1;
680   int box_width = get_box_width();
681
682   int sol = 0;
683   int eol = 0;
684   int line = 0;
685
686   int y_off = y - (y_abs + 2 + GLUI_TEXTBOX_BOXINNERMARGINX);
687   int x_off = x - (x_abs + 2 + GLUI_TEXTBOX_BOXINNERMARGINX);
688
689   /* Find the line clicked, 
690      The possibility of long lines getting wrapped complicates this. */
691   while ((line-start_line+1)*LINE_HEIGHT < y_off && eol < text_length) 
692   {
693     while (eol < text_length && text[eol] != '\n' && 
694            substring_width(sol, eol+1) <= box_width)
695     {
696       eol++;
697     }
698     if (text[eol]=='\n' && eol<text_length) { eol++; }
699     line++;
700     sol = eol;
701   }
702   curr_line = line;
703   // Now search to the end of this line for the closest insertion point
704   int prev_w=0,total_w=0,prev_eol=eol;
705   while (eol <= text_length 
706          && (total_w=substring_width(prev_eol,eol,prev_w))< x_off 
707          && (eol==text_length||text[eol]!='\n')) 
708   {
709     prev_w=total_w;
710     eol++;
711     prev_eol=eol;
712   }
713   if (total_w>=x_off) {  
714     // did we go far enough? (see if click was >1/2 width of last char)
715     int decision_pt = prev_w+(total_w-prev_w)/2;
716     if (x_off>decision_pt) eol++;
717   }
718   return eol;
719
720 #if 0
721   while (eol < text_length && text[eol] != '\n' && 
722          substring_width(sol, eol+1) < box_width )
723   {
724     eol++;
725   }
726
727
728   /* We move from right to left, looking to see if the mouse was clicked
729      to the right of the ith character */
730 #if 0
731   int curr_x = this->x_abs 
732     + substring_width( sol, eol )
733     + 2                             /* The edittext box has a 2-pixel margin */
734     + GLUI_TEXTBOX_BOXINNERMARGINX;   /** plus this many pixels blank space
735                      between the text and the box       **/
736 #endif
737   
738   /** find mouse click in text **/
739
740   if (x_off > substring_width(sol, eol))
741     return eol;
742
743   for(i = sol; i <= eol+1; i++) {
744     if (x_off <= substring_width(sol, i))
745             return i+1;
746   }
747   return 0;
748 #endif
749 }
750
751
752 int GLUI_TextBox::get_box_width() 
753 {
754   return MAX( this->w 
755               - 4     /*  2 * the two-line box border */ 
756               - 2 * GLUI_TEXTBOX_BOXINNERMARGINX, 0 );
757
758 }
759
760 /******************************** GLUI_TextBox::draw_insertion_pt() *********/
761
762 void     GLUI_TextBox::draw_insertion_pt( void )
763 {
764   int curr_x, box_width, text_length, eol, sol, line;
765
766   if ( NOT can_draw() )
767     return;
768
769   /*** Don't draw insertion pt if control is disabled ***/
770   if ( NOT enabled )
771     return;
772
773   if ( sel_start != sel_end OR insertion_pt < 0 ) {
774     return;  /* Don't draw insertion point if there is a current selection */
775   }
776
777   if ( debug )    dump( stdout, "-> DRAW_INS_PT" );
778
779   /*    printf( "insertion pt: %d\n", insertion_pt );              */
780
781   box_width = get_box_width();
782
783   // This function is unable to distinguish whether an insertion
784   // point on a line break should be drawn on the line before or the line after.
785   // This depends on the sequence of operations used to get there, and this
786   // function just doesn't have that information.  If curr_line were kept up
787   // to date elsewhere that could be used here to disambiguate, but arrow keys
788   // and such do not update it.
789
790   sol = 0;
791   eol = 0;
792   text_length = int(text.length())-1;
793
794   //while (eol < text_length && text[eol] != '\n' 
795   //       && substring_width(sol, eol + 1) < box_width )
796   //  eol++;
797   line = 0;
798   while (eol < insertion_pt && eol <= text_length) 
799   {
800     if (text[eol] == '\n' || substring_width(sol, eol + 1) >= box_width) 
801     {
802       eol++;
803       if (text[eol]=='\n'||eol!=insertion_pt
804           ||(eol==insertion_pt && eol>0 && text[eol-1]=='\n')) {
805         sol = eol;
806         line++;
807       }
808     } 
809     else {
810       eol++;
811     }
812   }
813
814   //glColor3f(1,0,0);
815   //glRecti(0, curr_line*LINE_HEIGHT, 3, (curr_line+1)*LINE_HEIGHT);
816
817   curr_line = line;
818
819   if (scrollbar)
820     scrollbar->set_int_val(start_line);
821   if (curr_line < start_line || curr_line > (start_line + visible_lines)) /* Insertion pt out of draw area */
822     return;
823
824   curr_x = this->x_abs 
825     + 2                               /* The edittext box has a 2-pixel margin */
826     + GLUI_TEXTBOX_BOXINNERMARGINX;   /** plus this many pixels blank space
827                                           between the text and the box       **/
828   
829   curr_x += substring_width(sol,insertion_pt-1);
830   if (insertion_pt == text.length() && text[text.length()-1] == '\n'
831       || curr_x-this->x_abs > (w - 2 - GLUI_TEXTBOX_BOXINNERMARGINX)) { // Insert on the next line 
832     curr_x = this->x_abs + GLUI_TEXTBOX_BOXINNERMARGINX;
833     line++;
834   } 
835   /* update insertion coordinates */
836   insert_x = curr_x+5; /* I hate magic numbers too, these offset the imagined insertion point */
837   insert_y = (curr_line-start_line+2)*LINE_HEIGHT;
838
839
840   glColor3f( 0.0, 0.0, 0.0 );
841   glBegin( GL_LINE_LOOP );
842
843   curr_x -= x_abs;
844   glVertex2i( curr_x+1, (curr_line-start_line)*LINE_HEIGHT + 4 );
845   glVertex2i( curr_x,   (curr_line-start_line)*LINE_HEIGHT + 4 );
846   glVertex2i( curr_x+1, (curr_line-start_line)*LINE_HEIGHT + 16 );
847   glVertex2i( curr_x,   (curr_line-start_line)*LINE_HEIGHT + 16 );
848   glEnd();
849
850
851   if ( debug )    dump( stdout, "-> DRAW_INS_PT" );
852 }
853
854
855
856
857 /******************************** GLUI_TextBox::substring_width() *********/
858 int  GLUI_TextBox::substring_width( int start, int end, int initial_width )
859 {
860   // This function only works properly if start is really the start of a line.
861   // Otherwise tabs will be messed up.
862   int i, width = initial_width;
863
864   for( i=start; i<=end; i++ )
865     if (text[i] == '\t') { // Character is a tab, jump to next tab stop
866       width += tab_width-(width%tab_width);
867       //while (width == 0 || width % tab_width) 
868             //  width++;
869     }
870     else
871       width += char_width( text[i] ); 
872
873   return width;
874 }
875  
876
877 /***************************** GLUI_TextBox::update_and_draw_text() ********/
878
879 void   GLUI_TextBox::update_and_draw_text( void )
880 {
881   //update_substring_bounds();
882   /*  printf( "ss: %d/%d\n", substring_start, substring_end );                  */
883
884   redraw();
885 }
886
887
888 /********************************* GLUI_TextBox::special_handler() **********/
889
890 int    GLUI_TextBox::special_handler( int key,int modifiers )
891 {
892   int tmp_insertion_pt;
893   if ( NOT glui )
894     return false;
895
896   if ( debug )
897     printf( "SPECIAL:%d - mod:%d   subs:%d/%d  ins:%d  sel:%d/%d\n", 
898         key, modifiers, substring_start, substring_end,insertion_pt,
899         sel_start, sel_end );    
900
901   if ( key == GLUT_KEY_DOWN ) {
902     if (insert_x == -1 || insert_y == -1)
903       return false;
904     tmp_insertion_pt = find_insertion_pt( keygoal_x, insert_y+LINE_HEIGHT);
905     if (tmp_insertion_pt < 0)
906       return false;
907     insertion_pt = tmp_insertion_pt;
908     sel_end = insertion_pt;
909     if (!(modifiers & GLUT_ACTIVE_SHIFT)) {
910       sel_start = sel_end;
911     }
912     if ( can_draw())
913       update_and_draw_text();    
914   } else if ( key == GLUT_KEY_UP ) {
915     if (insert_x == -1 || insert_y == -1)
916       return false;
917     tmp_insertion_pt = find_insertion_pt( keygoal_x, insert_y-LINE_HEIGHT);  
918     if (tmp_insertion_pt < 0)
919       return false;
920     insertion_pt = tmp_insertion_pt;
921     sel_end = insertion_pt;
922     if (!(modifiers & GLUT_ACTIVE_SHIFT)) {
923       sel_start = sel_end;
924     }
925     if ( can_draw())
926       update_and_draw_text();    
927   } else if ( key == GLUT_KEY_LEFT ) {
928     if ( (modifiers & GLUT_ACTIVE_CTRL) != 0 ) {
929       insertion_pt = find_word_break( insertion_pt, -1 );
930     }
931     else {
932       insertion_pt--;
933     }
934     // update keygoal_x!
935   }
936   else if ( key == GLUT_KEY_RIGHT ) {
937     if ( (modifiers & GLUT_ACTIVE_CTRL) != 0 ) {
938       insertion_pt = find_word_break( insertion_pt, +1 );
939     }
940     else {
941       insertion_pt++;
942     }
943     // update keygoal_x!
944   }
945   else if ( key == GLUT_KEY_HOME ) {
946     insertion_pt = 0;
947     // update keygoal_x!
948   }
949   else if ( key == GLUT_KEY_END ) {
950     insertion_pt = int(text.length());
951     // update keygoal_x!
952   }
953
954   /*** Update selection if shift key is down ***/
955   if ( (modifiers & GLUT_ACTIVE_SHIFT ) != 0 )
956     sel_end = insertion_pt;
957   else 
958     sel_start = sel_end = insertion_pt;
959   
960
961   CLAMP( insertion_pt, 0, (int)text.length()); /* Make sure insertion_pt 
962                            is in bounds */
963   CLAMP( sel_start, 0, (int)text.length()); /* Make sure insertion_pt 
964                         is in bounds */
965   CLAMP( sel_end, 0, (int)text.length()); /* Make sure insertion_pt 
966                           is in bounds */
967
968   /******** Now redraw text ***********/
969   if ( can_draw())
970     update_and_draw_text();
971
972   return true;
973 }
974
975
976 /****************************** GLUI_TextBox::find_word_break() **********/
977 /* It looks either left or right (depending on value of 'direction'       */
978 /* for the beginning of the next 'word', where word are characters        */
979 /* separated by one of the following tokens:  " :-.,"                     */
980 /* If there is no next word in the specified direction, this returns      */
981 /* the beginning of 'text', or the very end.                              */
982
983 int    GLUI_TextBox::find_word_break( int start, int direction )
984 {
985   int    i, j;
986   char    breaks[] = " \n\t:-.,";
987   int     num_break_chars = (int)strlen(breaks), text_len = int(text.length());
988   int     new_pt;
989
990   /** If we're moving left, we have to start two back, in case we're either
991     already at the beginning of a word, or on a separating token.  
992     Otherwise, this function would just return the word we're already at **/
993   if ( direction == -1 ) {
994     start -= 2;
995   }
996
997   /***** Iterate over text in the specified direction *****/
998   for ( i=start; i >= 0 AND i < text_len; i += direction ) {
999
1000     /** For each character in text, iterate over list of separating tokens **/
1001     for( j=0; j<num_break_chars; j++ ) {
1002       if ( text[i] == breaks[j] ) {
1003
1004     /** character 'i' is a separating token, so we return i+1 **/
1005     new_pt = i + 1;
1006
1007     CLAMP( new_pt, 0, text_len );
1008
1009     return new_pt;
1010       }
1011     }
1012   }
1013
1014   if ( direction > 0 )  /* Return the end of string */
1015     return text_len;
1016   else                  /* Return the beginning of the text */
1017     return 0;
1018 }
1019
1020
1021 /********************************** GLUI_TextBox::clear_substring() ********/
1022
1023 void   GLUI_TextBox::clear_substring( int start, int end )
1024 {
1025   text.erase(start,end-start);
1026 }
1027
1028
1029
1030 /************************************ GLUI_TextBox::update_size() **********/
1031
1032 void   GLUI_TextBox::update_size( void )
1033 {
1034   if ( NOT glui )
1035     return;
1036
1037   if ( w < GLUI_TEXTBOX_MIN_TEXT_WIDTH )
1038       w = GLUI_TEXTBOX_MIN_TEXT_WIDTH;
1039 }
1040
1041
1042 /****************************** GLUI_TextBox::set_text() **********/
1043
1044 void    GLUI_TextBox::set_text( const char *new_text )
1045 {
1046   text = new_text;
1047
1048   substring_start = 0;
1049   substring_end   = int(text.length()) - 1;
1050   insertion_pt    = -1;
1051   sel_start       = 0;
1052   sel_end         = 0;
1053   visible_lines   = 0;
1054   start_line      = 0;
1055   curr_line       = 0;
1056   num_lines       = 0;
1057
1058   if ( can_draw() )
1059     update_and_draw_text();
1060
1061   /*** Now update the live variable ***/
1062   output_live(true);
1063 }
1064
1065
1066 /*************************************** GLUI_TextBox::dump() **************/
1067
1068 void   GLUI_TextBox::dump( FILE *out, const char *name )
1069 {
1070   fprintf( out, 
1071        "%s (edittext@%p):   line:%d ins_pt:%d  subs:%d/%d  sel:%d/%d   len:%zu\n",
1072        name, this, curr_line,
1073        insertion_pt, substring_start, substring_end, sel_start, sel_end,
1074        text.length());
1075 }
1076
1077
1078 /**************************************** GLUI_TextBox::mouse_over() ********/
1079
1080 int    GLUI_TextBox::mouse_over( int state, int x, int y )
1081 {
1082   if ( state && enabled) {
1083     /*  curr_cursor = GLUT_CURSOR_TEXT;              */
1084     glutSetCursor( GLUT_CURSOR_TEXT );
1085   }
1086   else {
1087     /*    printf( "OUT\n" );              */
1088     glutSetCursor( GLUT_CURSOR_LEFT_ARROW );
1089   }
1090
1091   return true;
1092 }
1093
1094 void GLUI_TextBox::scrollbar_callback(GLUI_Control *my_scrollbar) {
1095         GLUI_Scrollbar *sb = my_scrollbar->dynamicCastGLUI_Scrollbar();
1096   if (!sb) return;
1097   GLUI_TextBox* me = (GLUI_TextBox*) sb->associated_object;
1098   if (me->scrollbar == NULL)
1099     return;
1100   int new_start_line = sb->get_int_val(); // ??
1101   me->start_line = new_start_line;
1102   if (new_start_line < (me->curr_line - me->visible_lines))
1103     me->curr_line = new_start_line + me->visible_lines;
1104   if (new_start_line > me->curr_line)
1105     me->curr_line = new_start_line;
1106   if ( me->can_draw() )
1107     me->update_and_draw_text();
1108 }