Permalink
Switch branches/tags
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1112 lines (884 sloc) 30.8 KB
/****************************************************************************
GLUI User Interface Toolkit
---------------------------
glui_textbox.cpp - GLUI_TextBox control class
--------------------------------------------------
Copyright (c) 1998 Paul Rademacher, 2004 John Kew
WWW: https://github.com/libglui/glui
Issues: https://github.com/libglui/glui/issues
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*****************************************************************************/
#include "glui_internal_control.h"
#include <algorithm>
#include <cmath>
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( std::min(sel_start,sel_end), std::max(sel_start,sel_end ));
insertion_pt = std::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( std::min(sel_start,sel_end), std::max(sel_start,sel_end ));
insertion_pt = std::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( std::min(sel_start,sel_end), std::max(sel_start,sel_end ));
insertion_pt = std::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( std::min(sel_start,sel_end), std::max(sel_start,sel_end ));
insertion_pt = std::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()
{
GLUI_Control::enable();
if (scrollbar) scrollbar->enable();
}
/****************************** GLUI_TextBox::disable() **********/
void GLUI_TextBox::disable()
{
GLUI_Control::disable();
if (scrollbar) scrollbar->disable();
}
/****************************** GLUI_TextBox::activate() **********/
void GLUI_TextBox::activate( int how )
{
if ( debug )
dump( stdout, "-> ACTIVATE" );
active = true;
orig_text = text;
if ( how == GLUI_ACTIVATE_MOUSE )
return; /* Don't select everything if activated with mouse */
sel_start = 0;
sel_end = text.length();
insertion_pt = 0;
if ( debug )
dump( stdout, "<- ACTIVATE" );
}
/****************************** GLUI_TextBox::deactivate() **********/
void GLUI_TextBox::deactivate()
{
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 = 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(std::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()
{
int box_width;
int text_len = 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, std::max(text_len-1,0) );
CLAMP( substring_start, 0, std::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()
{
}
/********************************* 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 = std::min(sel_start, sel_end );
sel_hi = std::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 = 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<text_length) { eol++; }
line++;
sol = eol;
}
curr_line = line;
// Now search to the end of this line for the closest insertion point
int prev_w=0,total_w=0,prev_eol=eol;
while (eol <= text_length
&& (total_w=substring_width(prev_eol,eol,prev_w))< x_off
&& (eol==text_length||text[eol]!='\n'))
{
prev_w=total_w;
eol++;
prev_eol=eol;
}
if (total_w>=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 std::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()
{
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 = 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()
{
//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 = 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, text.length()); /* Make sure insertion_pt
is in bounds */
CLAMP( sel_start, 0, text.length()); /* Make sure insertion_pt
is in bounds */
CLAMP( sel_end, 0, 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 = 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<num_break_chars; j++ ) {
if ( text[i] == breaks[j] ) {
/** character 'i' is a separating token, so we return i+1 **/
new_pt = i + 1;
CLAMP( new_pt, 0, text_len );
return new_pt;
}
}
}
if ( direction > 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()
{
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 GLUI_String &new_text )
{
text = new_text;
substring_start = 0;
substring_end = 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 GLUI_String &name )
{
fprintf( out,
"%s (edittext@%p): line:%d ins_pt:%d subs:%d/%d sel:%d/%d len:%lu\n",
name.c_str(), this, curr_line,
insertion_pt, substring_start, substring_end, sel_start, sel_end,
(long unsigned) 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 = dynamic_cast<GLUI_Scrollbar*>(my_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();
}