/**************************************************************************** GLUI User Interface Toolkit --------------------------- glui_textbox.cpp - GLUI_TextBox control class -------------------------------------------------- Copyright (c) 1998 Paul Rademacher, 2004 John Kew WWW: http://sourceforge.net/projects/glui/ Forums: http://sourceforge.net/forum/?group_id=92496 This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *****************************************************************************/ #include "glui_internal_control.h" #include static const int LINE_HEIGHT = 15; /****************************** GLUI_TextBox::GLUI_TextBox() **********/ GLUI_TextBox::GLUI_TextBox(GLUI_Node *parent, GLUI_String &live_var, bool scroll, int id, GLUI_CB callback ) { common_construct(parent, &live_var, scroll, id, callback); } /****************************** GLUI_TextBox::GLUI_TextBox() **********/ GLUI_TextBox::GLUI_TextBox( GLUI_Node *parent, bool scroll, int id, GLUI_CB callback ) { common_construct(parent, NULL, scroll, id, callback); } /****************************** GLUI_TextBox::common_construct() **********/ void GLUI_TextBox::common_construct( GLUI_Node *parent, GLUI_String *data, bool scroll, int id, GLUI_CB callback) { common_init(); GLUI_Node *tb_panel = parent; if (scroll) { GLUI_Panel *p = new GLUI_Panel(parent,"",GLUI_PANEL_NONE); p->x_off = 1; tb_panel = p; } this->ptr_val = data; if (data) { this->live_type = GLUI_LIVE_STRING; } else { this->live_type = GLUI_LIVE_NONE; } this->user_id = id; this->callback = callback; this->name = "textbox"; tb_panel->add_control( this ); if (scroll) { new GLUI_Column(tb_panel, false); scrollbar = new GLUI_Scrollbar(tb_panel, "scrollbar", GLUI_SCROLL_VERTICAL, GLUI_SCROLL_INT); scrollbar->set_object_callback(GLUI_TextBox::scrollbar_callback, this); scrollbar->set_alignment(GLUI_ALIGN_LEFT); // scrollbar->can_activate = false; //kills ability to mouse drag too } init_live(); } /****************************** GLUI_TextBox::mouse_down_handler() **********/ int GLUI_TextBox::mouse_down_handler( int local_x, int local_y ) { int tmp_insertion_pt; if ( debug ) dump( stdout, "-> MOUSE DOWN" ); tmp_insertion_pt = find_insertion_pt( local_x, local_y ); if ( tmp_insertion_pt == -1 ) { if ( glui ) glui->deactivate_current_control( ); return false; } insertion_pt = tmp_insertion_pt; sel_start = sel_end = insertion_pt; keygoal_x = insert_x; if ( can_draw()) update_and_draw_text(); if ( debug ) dump( stdout, "<- MOUSE UP" ); return true; } /******************************** GLUI_TextBox::mouse_up_handler() **********/ int GLUI_TextBox::mouse_up_handler( int local_x, int local_y, bool inside ) { return false; } /***************************** GLUI_TextBox::mouse_held_down_handler() ******/ int GLUI_TextBox::mouse_held_down_handler( int local_x, int local_y, bool new_inside) { int tmp_pt; if ( NOT new_inside ) return false; if ( debug ) dump( stdout, "-> HELD DOWN" ); tmp_pt = find_insertion_pt( local_x, local_y ); keygoal_x = insert_x; if ( tmp_pt == -1 AND sel_end != 0 ) { /* moved mouse past left edge */ special_handler( GLUT_KEY_LEFT, GLUT_ACTIVE_SHIFT ); } else if ( tmp_pt == substring_end+1 AND sel_end != (int) text.length()) { /* moved mouse past right edge */ special_handler( GLUT_KEY_RIGHT, GLUT_ACTIVE_SHIFT ); } else if ( tmp_pt != -1 AND tmp_pt != sel_end ) { sel_end = insertion_pt = tmp_pt; update_and_draw_text(); } if ( debug ) dump( stdout, "<- HELD DOWN" ); return false; } /****************************** GLUI_TextBox::key_handler() **********/ int GLUI_TextBox::key_handler( unsigned char key,int modifiers ) { int regular_key; /* int has_selection; */ if ( NOT glui ) return false; if ( debug ) dump( stdout, "-> KEY HANDLER" ); regular_key = false; bool ctrl_down = (modifiers & GLUT_ACTIVE_CTRL)!=0; /* has_selection = (sel_start != sel_end); */ if ( key == CTRL('[')) { /* ESCAPE */ glui->deactivate_current_control(); return true; } else if ( (key == 127 AND !ctrl_down) OR /* FORWARD DELETE */ ( key == CTRL('d') AND modifiers == GLUT_ACTIVE_CTRL) ) { if ( sel_start == sel_end ) { /* no selection */ if ( insertion_pt < (int)text.length() ) { text.erase(insertion_pt,1); } } else { /* There is a selection */ clear_substring( MIN(sel_start,sel_end), MAX(sel_start,sel_end )); insertion_pt = MIN(sel_start,sel_end); sel_start = sel_end = insertion_pt; } } else if ( ((key == 127) AND ctrl_down) OR // Delete word forward ((key == 'd') AND (modifiers == GLUT_ACTIVE_ALT)) ) { if ( sel_start == sel_end ) { /* no selection */ sel_start = insertion_pt; sel_end = find_word_break( insertion_pt, +1 ); } clear_substring( MIN(sel_start,sel_end), MAX(sel_start,sel_end )); insertion_pt = MIN(sel_start,sel_end); sel_start = sel_end = insertion_pt; } else if ( key == CTRL('h') ) { /* BACKSPACE */ if ( sel_start == sel_end ) { /* no selection */ if ( insertion_pt > 0 ) { insertion_pt--; text.erase(insertion_pt,1); } } else { /* There is a selection */ clear_substring( MIN(sel_start,sel_end), MAX(sel_start,sel_end )); insertion_pt = MIN(sel_start,sel_end); sel_start = sel_end = insertion_pt; } } else if ( modifiers == GLUT_ACTIVE_CTRL ) /* CTRL ONLY */ { /* Ctrl-key bindings */ if ( key == CTRL('a') ) { return special_handler( GLUT_KEY_HOME, 0 ); } else if ( key == CTRL('e') ) { return special_handler( GLUT_KEY_END, 0 ); } else if ( key == CTRL('b') ) { return special_handler( GLUT_KEY_LEFT, 0 ); } else if ( key == CTRL('f') ) { return special_handler( GLUT_KEY_RIGHT, 0 ); } else if ( key == CTRL('p') ) { return special_handler( GLUT_KEY_UP, 0 ); } else if ( key == CTRL('n') ) { return special_handler( GLUT_KEY_DOWN, 0 ); } else if ( key == CTRL('u') ) { /* ERASE LINE */ insertion_pt = 0; text.erase(0,text.length()); sel_start = sel_end = 0; } else if ( key == CTRL('k') ) { /* KILL TO END OF LINE */ sel_start = sel_end = insertion_pt; text.erase(insertion_pt,GLUI_String::npos); } } else if ( modifiers == GLUT_ACTIVE_ALT ) /* ALT ONLY */ { if ( key == 'b' ) { // Backward word return special_handler ( GLUT_KEY_LEFT, GLUT_ACTIVE_CTRL ); } if ( key == 'f' ) { // Forward word return special_handler ( GLUT_KEY_RIGHT, GLUT_ACTIVE_CTRL ); } } else if ( (modifiers & GLUT_ACTIVE_CTRL) OR (modifiers & GLUT_ACTIVE_ALT) ) { /** ignore other keys with modifiers */ return true; } else { /* Regular key */ if ( key == 13 ) /* RETURNS are written as newlines*/ key = '\n'; regular_key = true; /** This is just to get rid of warnings - the flag regular_key is set if the key was not a backspace, return, whatever. But I believe if we're here, we know it was a regular key anyway */ if ( regular_key ) { } /**** If there's a current selection, erase it ******/ if ( sel_start != sel_end ) { clear_substring( MIN(sel_start,sel_end), MAX(sel_start,sel_end )); insertion_pt = MIN(sel_start,sel_end); sel_start = sel_end = insertion_pt; } /******** We insert the character into the string ***/ text.insert(insertion_pt,1,key); /******** Move the insertion point and substring_end one over ******/ insertion_pt++; substring_end++; sel_start = sel_end = insertion_pt; } /******** Now redraw text ***********/ /* Hack to prevent text box from being cleared first **/ /** int substring_change = update_substring_bounds(); draw_text_only = (NOT substring_change AND NOT has_selection AND regular_key ); */ draw_text_only = false; /** Well, hack is not yet working **/ update_and_draw_text(); draw_text_only = false; if ( debug ) dump( stdout, "<- KEY HANDLER" ); return true; } /****************************** GLUI_TextBox::enable() **********/ void GLUI_TextBox::enable( void ) { GLUI_Control::enable(); scrollbar->enable(); } /****************************** GLUI_TextBox::disable() **********/ void GLUI_TextBox::disable( void ) { GLUI_Control::disable(); scrollbar->disable(); } /****************************** GLUI_TextBox::activate() **********/ void GLUI_TextBox::activate( int how ) { if ( debug ) dump( stdout, "-> ACTIVATE" ); active = true; if ( how == GLUI_ACTIVATE_MOUSE ) return; /* Don't select everything if activated with mouse */ orig_text = text; sel_start = 0; sel_end = int(text.length()); insertion_pt = 0; if ( debug ) dump( stdout, "<- ACTIVATE" ); } /****************************** GLUI_TextBox::deactivate() **********/ void GLUI_TextBox::deactivate( void ) { active = false; if ( NOT glui ) return; if ( debug ) dump( stdout, "-> DISACTIVATE" ); sel_start = sel_end = insertion_pt = -1; /***** Retrieve the current value from the text *****/ /***** The live variable will be updated by set_text() ****/ set_text(text.c_str()); /* This will force callbacks and gfx refresh */ update_substring_bounds(); /******** redraw text without insertion point ***********/ redraw(); /***** Now do callbacks if value changed ******/ if ( orig_text != text ) { this->execute_callback(); } if ( debug ) dump( stdout, "<- DISACTIVATE" ); } /****************************** GLUI_TextBox::draw() **********/ void GLUI_TextBox::draw( int x, int y ) { GLUI_DRAWINGSENTINAL_IDIOM int line = 0; int text_length; int box_width; int i; /* Bevelled Border */ glBegin( GL_LINES ); glColor3f( .5, .5, .5 ); glVertex2i( 0, 0 ); glVertex2i( w, 0 ); glVertex2i( 0, 0 ); glVertex2i( 0, h ); glColor3f( 1., 1., 1. ); glVertex2i( 0, h ); glVertex2i( w, h ); glVertex2i( w, h ); glVertex2i( w, 0 ); if ( enabled ) glColor3f( 0., 0., 0. ); else glColor3f( .25, .25, .25 ); glVertex2i( 1, 1 ); glVertex2i( w-1, 1 ); glVertex2i( 1, 1 ); glVertex2i( 1, h-1 ); glColor3f( .75, .75, .75 ); glVertex2i( 1, h-1 ); glVertex2i( w-1, h-1 ); glVertex2i( w-1, h-1 ); glVertex2i( w-1, 1 ); glEnd(); /* Draw Background if enabled*/ if (enabled) { glColor3f( 1., 1., 1. ); glDisable( GL_CULL_FACE ); glBegin( GL_QUADS ); glVertex2i( 2, 2 ); glVertex2i( w-2, 2 ); glVertex2i( w-2, h-2 ); glVertex2i(2, h-2 ); glEnd(); } else { glColor3f( .8, .8, .8 ); glDisable( GL_CULL_FACE ); glBegin( GL_QUADS ); glVertex2i( 2, 2 ); glVertex2i( w-2, 2 ); glVertex2i( w-2, h-2 ); glVertex2i(2, h-2 ); glEnd(); } /* Begin Drawing Lines of Text */ substring_start = 0; substring_end = 0; text_length = int(text.length())-1; /* Figure out how wide the box is */ box_width = get_box_width(); /* Get the first line substring */ while (substring_width(substring_start, substring_end+1 ) < box_width && substring_end < text_length && text[substring_end+1] != '\n') substring_end++; /* Figure out which lines are visible*/ visible_lines = (int)(h-20)/LINE_HEIGHT; if (start_line < (curr_line-visible_lines)) { for (i = 0; ((curr_line-i)*LINE_HEIGHT+20) > h; i++); start_line = i; } else if ( start_line > curr_line) { start_line = curr_line; } line = 0; do { if (line && substring_end < text_length) { substring_start = substring_end+1; while (substring_width(substring_start, substring_end+1 ) < box_width && substring_end < text_length && text[substring_end+1] != '\n') substring_end++; } if (text[substring_end+1] == '\n') { /* Skip newline */ substring_end++; } if (line < start_line || (line > curr_line && curr_line > (start_line + visible_lines))) { line++; continue; } if ((line - start_line) <= visible_lines) draw_text(0,(line - start_line)*LINE_HEIGHT); /* tabs and other nasties are handled by substring_width */ line++; } while (substring_end < text_length); num_lines = line; draw_insertion_pt(); if (scrollbar) { scrollbar->set_int_limits(MAX(0,num_lines/*-1*/-visible_lines),0); glPushMatrix(); glTranslatef(scrollbar->x_abs-x_abs, scrollbar->y_abs-y_abs,0.0); scrollbar->draw_scroll(); glPopMatrix(); } } /************************** GLUI_TextBox::update_substring_bounds() *********/ int GLUI_TextBox::update_substring_bounds( void ) { int box_width; int text_len = int(text.length()); int old_start, old_end; old_start = substring_start; old_end = substring_end; /*** Calculate the width of the usable area of the edit box ***/ box_width = get_box_width(); CLAMP( substring_end, 0, MAX(text_len-1,0) ); CLAMP( substring_start, 0, MAX(text_len-1,0) ); if ( debug ) dump( stdout, "-> UPDATE SS" ); if ( insertion_pt >= 0 AND insertion_pt < substring_start ) { /* cursor moved left */ substring_start = insertion_pt; while ( substring_width( substring_start, substring_end ) > box_width ) substring_end--; } else if ( insertion_pt > substring_end ) { /* cursor moved right */ substring_end = insertion_pt-1; while ( substring_width( substring_start, substring_end ) > box_width ) substring_start++; } else { /* cursor is within old substring bounds */ if ( last_insertion_pt > insertion_pt ) { /* cursor moved left */ } else { while ( substring_width( substring_start, substring_end ) > box_width ) substring_end--; while(substring_width( substring_start, substring_end+1 ) <= box_width AND substring_end < text_len-1 ) substring_end++; } } while ( substring_width( substring_start, substring_end ) > box_width ) substring_end--; last_insertion_pt = insertion_pt; /*** No selection if not enabled ***/ if ( NOT enabled ) { sel_start = sel_end = 0; } if ( debug ) dump( stdout, "<- UPDATE SS" ); if ( substring_start == old_start AND substring_end == old_end ) return false; /*** bounds did not change ***/ else return true; /*** bounds did change ***/ } /********************************* GLUI_TextBox::update_x_offsets() *********/ void GLUI_TextBox::update_x_offsets( void ) { } /********************************* GLUI_TextBox::draw_text() ****************/ void GLUI_TextBox::draw_text( int x, int y ) { GLUI_DRAWINGSENTINAL_IDIOM int text_x, i, sel_lo, sel_hi, x_pos; if ( debug ) dump( stdout, "-> DRAW_TEXT" ); /** Find where to draw the text **/ text_x = 2 + GLUI_TEXTBOX_BOXINNERMARGINX; /** Find lower and upper selection bounds **/ sel_lo = MIN(sel_start, sel_end ); sel_hi = MAX(sel_start, sel_end ); int sel_x_start, sel_x_end, delta; /** Draw selection area dark **/ if ( sel_start != sel_end ) { sel_x_start = text_x; sel_x_end = text_x; delta = 0; for( i=substring_start; sel_x_end < (w - text_x) && i<=substring_end; i++ ) { delta = 0; if (text[i] == '\t') // Character is a tab, go to next tab stop while (((delta + sel_x_end) < (w - text_x)) && (delta == 0 || delta % tab_width)) delta++; else delta = char_width( text[i] ); if ( i < sel_lo ) { sel_x_start += delta; sel_x_end += delta; } else if ( i < sel_hi ) { sel_x_end += delta; } } glColor3f( 0.0f, 0.0f, .6f ); glRecti(sel_x_start, y+5, sel_x_end, y+20); } if ( sel_start == sel_end ) { // No current selection x_pos = text_x; if ( enabled ) glColor3b( 0, 0, 0 ); else glColor3b( 32, 32, 32 ); glRasterPos2i( text_x, y+LINE_HEIGHT); for( i=substring_start; i<=substring_end; i++ ) { if (this->text[i] == '\t') { // Character is a tab, go to next tab stop x_pos = ((x_pos-text_x)/tab_width)*tab_width+tab_width+text_x; glRasterPos2i( x_pos, y+LINE_HEIGHT); // Reposition pen after tab } else { glutBitmapCharacter( get_font(), this->text[i] ); x_pos += char_width( this->text[i] ); } } } else { // There is a selection x_pos = text_x; for( i=substring_start; i<=substring_end; i++ ) { if ( IN_BOUNDS( i, sel_lo, sel_hi-1)) { // This character is selected glColor3f( 1., 1., 1. ); glRasterPos2i( x_pos, y+LINE_HEIGHT); if (this->text[i] == '\t') { // Character is a tab, go to next tab stop x_pos = ((x_pos-text_x)/tab_width)*tab_width+tab_width+text_x; } else glutBitmapCharacter( get_font(), this->text[i] ); } else { glColor3f( 0., 0., 0. ); glRasterPos2i( x_pos, y+LINE_HEIGHT); if (this->text[i] == '\t') { // Character is a tab, go to next tab stop x_pos = ((x_pos-text_x)/tab_width)*tab_width+tab_width+text_x; glRasterPos2i( x_pos, y+LINE_HEIGHT); // Reposition pen after tab } else glutBitmapCharacter( get_font(), this->text[i] ); } x_pos += char_width( text[i] ); } } if ( debug ) dump( stdout, "<- DRAW_TEXT" ); } /******************************** GLUI_TextBox::find_insertion_pt() *********/ /* This function returns the character number *before which* the insertion */ /* point goes */ int GLUI_TextBox::find_insertion_pt( int x, int y ) { /*** See if we clicked outside box ***/ if ( x < this->x_abs || y < this->y_abs) return -1; /*** See if we clicked in an empty box ***/ if ( text.empty() ) return 0; /* update insert variables */ insert_x = x; insert_y = y; int text_length = int(text.length())-1; int box_width = get_box_width(); int sol = 0; int eol = 0; int line = 0; int y_off = y - (y_abs + 2 + GLUI_TEXTBOX_BOXINNERMARGINX); int x_off = x - (x_abs + 2 + GLUI_TEXTBOX_BOXINNERMARGINX); /* Find the line clicked, The possibility of long lines getting wrapped complicates this. */ while ((line-start_line+1)*LINE_HEIGHT < y_off && eol < text_length) { while (eol < text_length && text[eol] != '\n' && substring_width(sol, eol+1) <= box_width) { eol++; } if (text[eol]=='\n' && eol=x_off) { // did we go far enough? (see if click was >1/2 width of last char) int decision_pt = prev_w+(total_w-prev_w)/2; if (x_off>decision_pt) eol++; } return eol; #if 0 while (eol < text_length && text[eol] != '\n' && substring_width(sol, eol+1) < box_width ) { eol++; } /* We move from right to left, looking to see if the mouse was clicked to the right of the ith character */ #if 0 int curr_x = this->x_abs + substring_width( sol, eol ) + 2 /* The edittext box has a 2-pixel margin */ + GLUI_TEXTBOX_BOXINNERMARGINX; /** plus this many pixels blank space between the text and the box **/ #endif /** find mouse click in text **/ if (x_off > substring_width(sol, eol)) return eol; for(i = sol; i <= eol+1; i++) { if (x_off <= substring_width(sol, i)) return i+1; } return 0; #endif } int GLUI_TextBox::get_box_width() { return MAX( this->w - 4 /* 2 * the two-line box border */ - 2 * GLUI_TEXTBOX_BOXINNERMARGINX, 0 ); } /******************************** GLUI_TextBox::draw_insertion_pt() *********/ void GLUI_TextBox::draw_insertion_pt( void ) { int curr_x, box_width, text_length, eol, sol, line; if ( NOT can_draw() ) return; /*** Don't draw insertion pt if control is disabled ***/ if ( NOT enabled ) return; if ( sel_start != sel_end OR insertion_pt < 0 ) { return; /* Don't draw insertion point if there is a current selection */ } if ( debug ) dump( stdout, "-> DRAW_INS_PT" ); /* printf( "insertion pt: %d\n", insertion_pt ); */ box_width = get_box_width(); // This function is unable to distinguish whether an insertion // point on a line break should be drawn on the line before or the line after. // This depends on the sequence of operations used to get there, and this // function just doesn't have that information. If curr_line were kept up // to date elsewhere that could be used here to disambiguate, but arrow keys // and such do not update it. sol = 0; eol = 0; text_length = int(text.length())-1; //while (eol < text_length && text[eol] != '\n' // && substring_width(sol, eol + 1) < box_width ) // eol++; line = 0; while (eol < insertion_pt && eol <= text_length) { if (text[eol] == '\n' || substring_width(sol, eol + 1) >= box_width) { eol++; if (text[eol]=='\n'||eol!=insertion_pt ||(eol==insertion_pt && eol>0 && text[eol-1]=='\n')) { sol = eol; line++; } } else { eol++; } } //glColor3f(1,0,0); //glRecti(0, curr_line*LINE_HEIGHT, 3, (curr_line+1)*LINE_HEIGHT); curr_line = line; if (scrollbar) scrollbar->set_int_val(start_line); if (curr_line < start_line || curr_line > (start_line + visible_lines)) /* Insertion pt out of draw area */ return; curr_x = this->x_abs + 2 /* The edittext box has a 2-pixel margin */ + GLUI_TEXTBOX_BOXINNERMARGINX; /** plus this many pixels blank space between the text and the box **/ curr_x += substring_width(sol,insertion_pt-1); if (insertion_pt == text.length() && text[text.length()-1] == '\n' || curr_x-this->x_abs > (w - 2 - GLUI_TEXTBOX_BOXINNERMARGINX)) { // Insert on the next line curr_x = this->x_abs + GLUI_TEXTBOX_BOXINNERMARGINX; line++; } /* update insertion coordinates */ insert_x = curr_x+5; /* I hate magic numbers too, these offset the imagined insertion point */ insert_y = (curr_line-start_line+2)*LINE_HEIGHT; glColor3f( 0.0, 0.0, 0.0 ); glBegin( GL_LINE_LOOP ); curr_x -= x_abs; glVertex2i( curr_x+1, (curr_line-start_line)*LINE_HEIGHT + 4 ); glVertex2i( curr_x, (curr_line-start_line)*LINE_HEIGHT + 4 ); glVertex2i( curr_x+1, (curr_line-start_line)*LINE_HEIGHT + 16 ); glVertex2i( curr_x, (curr_line-start_line)*LINE_HEIGHT + 16 ); glEnd(); if ( debug ) dump( stdout, "-> DRAW_INS_PT" ); } /******************************** GLUI_TextBox::substring_width() *********/ int GLUI_TextBox::substring_width( int start, int end, int initial_width ) { // This function only works properly if start is really the start of a line. // Otherwise tabs will be messed up. int i, width = initial_width; for( i=start; i<=end; i++ ) if (text[i] == '\t') { // Character is a tab, jump to next tab stop width += tab_width-(width%tab_width); //while (width == 0 || width % tab_width) // width++; } else width += char_width( text[i] ); return width; } /***************************** GLUI_TextBox::update_and_draw_text() ********/ void GLUI_TextBox::update_and_draw_text( void ) { //update_substring_bounds(); /* printf( "ss: %d/%d\n", substring_start, substring_end ); */ redraw(); } /********************************* GLUI_TextBox::special_handler() **********/ int GLUI_TextBox::special_handler( int key,int modifiers ) { int tmp_insertion_pt; if ( NOT glui ) return false; if ( debug ) printf( "SPECIAL:%d - mod:%d subs:%d/%d ins:%d sel:%d/%d\n", key, modifiers, substring_start, substring_end,insertion_pt, sel_start, sel_end ); if ( key == GLUT_KEY_DOWN ) { if (insert_x == -1 || insert_y == -1) return false; tmp_insertion_pt = find_insertion_pt( keygoal_x, insert_y+LINE_HEIGHT); if (tmp_insertion_pt < 0) return false; insertion_pt = tmp_insertion_pt; sel_end = insertion_pt; if (!(modifiers & GLUT_ACTIVE_SHIFT)) { sel_start = sel_end; } if ( can_draw()) update_and_draw_text(); } else if ( key == GLUT_KEY_UP ) { if (insert_x == -1 || insert_y == -1) return false; tmp_insertion_pt = find_insertion_pt( keygoal_x, insert_y-LINE_HEIGHT); if (tmp_insertion_pt < 0) return false; insertion_pt = tmp_insertion_pt; sel_end = insertion_pt; if (!(modifiers & GLUT_ACTIVE_SHIFT)) { sel_start = sel_end; } if ( can_draw()) update_and_draw_text(); } else if ( key == GLUT_KEY_LEFT ) { if ( (modifiers & GLUT_ACTIVE_CTRL) != 0 ) { insertion_pt = find_word_break( insertion_pt, -1 ); } else { insertion_pt--; } // update keygoal_x! } else if ( key == GLUT_KEY_RIGHT ) { if ( (modifiers & GLUT_ACTIVE_CTRL) != 0 ) { insertion_pt = find_word_break( insertion_pt, +1 ); } else { insertion_pt++; } // update keygoal_x! } else if ( key == GLUT_KEY_HOME ) { insertion_pt = 0; // update keygoal_x! } else if ( key == GLUT_KEY_END ) { insertion_pt = int(text.length()); // update keygoal_x! } /*** Update selection if shift key is down ***/ if ( (modifiers & GLUT_ACTIVE_SHIFT ) != 0 ) sel_end = insertion_pt; else sel_start = sel_end = insertion_pt; CLAMP( insertion_pt, 0, (int)text.length()); /* Make sure insertion_pt is in bounds */ CLAMP( sel_start, 0, (int)text.length()); /* Make sure insertion_pt is in bounds */ CLAMP( sel_end, 0, (int)text.length()); /* Make sure insertion_pt is in bounds */ /******** Now redraw text ***********/ if ( can_draw()) update_and_draw_text(); return true; } /****************************** GLUI_TextBox::find_word_break() **********/ /* It looks either left or right (depending on value of 'direction' */ /* for the beginning of the next 'word', where word are characters */ /* separated by one of the following tokens: " :-.," */ /* If there is no next word in the specified direction, this returns */ /* the beginning of 'text', or the very end. */ int GLUI_TextBox::find_word_break( int start, int direction ) { int i, j; char breaks[] = " \n\t:-.,"; int num_break_chars = (int)strlen(breaks), text_len = int(text.length()); int new_pt; /** If we're moving left, we have to start two back, in case we're either already at the beginning of a word, or on a separating token. Otherwise, this function would just return the word we're already at **/ if ( direction == -1 ) { start -= 2; } /***** Iterate over text in the specified direction *****/ for ( i=start; i >= 0 AND i < text_len; i += direction ) { /** For each character in text, iterate over list of separating tokens **/ for( j=0; j 0 ) /* Return the end of string */ return text_len; else /* Return the beginning of the text */ return 0; } /********************************** GLUI_TextBox::clear_substring() ********/ void GLUI_TextBox::clear_substring( int start, int end ) { text.erase(start,end-start); } /************************************ GLUI_TextBox::update_size() **********/ void GLUI_TextBox::update_size( void ) { if ( NOT glui ) return; if ( w < GLUI_TEXTBOX_MIN_TEXT_WIDTH ) w = GLUI_TEXTBOX_MIN_TEXT_WIDTH; } /****************************** GLUI_TextBox::set_text() **********/ void GLUI_TextBox::set_text( const char *new_text ) { text = new_text; substring_start = 0; substring_end = int(text.length()) - 1; insertion_pt = -1; sel_start = 0; sel_end = 0; visible_lines = 0; start_line = 0; curr_line = 0; num_lines = 0; if ( can_draw() ) update_and_draw_text(); /*** Now update the live variable ***/ output_live(true); } /*************************************** GLUI_TextBox::dump() **************/ void GLUI_TextBox::dump( FILE *out, const char *name ) { fprintf( out, "%s (edittext@%p): line:%d ins_pt:%d subs:%d/%d sel:%d/%d len:%zu\n", name, this, curr_line, insertion_pt, substring_start, substring_end, sel_start, sel_end, text.length()); } /**************************************** GLUI_TextBox::mouse_over() ********/ int GLUI_TextBox::mouse_over( int state, int x, int y ) { if ( state && enabled) { /* curr_cursor = GLUT_CURSOR_TEXT; */ glutSetCursor( GLUT_CURSOR_TEXT ); } else { /* printf( "OUT\n" ); */ glutSetCursor( GLUT_CURSOR_LEFT_ARROW ); } return true; } void GLUI_TextBox::scrollbar_callback(GLUI_Control *my_scrollbar) { GLUI_Scrollbar *sb = my_scrollbar->dynamicCastGLUI_Scrollbar(); if (!sb) return; GLUI_TextBox* me = (GLUI_TextBox*) sb->associated_object; if (me->scrollbar == NULL) return; int new_start_line = sb->get_int_val(); // ?? me->start_line = new_start_line; if (new_start_line < (me->curr_line - me->visible_lines)) me->curr_line = new_start_line + me->visible_lines; if (new_start_line > me->curr_line) me->curr_line = new_start_line; if ( me->can_draw() ) me->update_and_draw_text(); }