Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Real-Time Text Input

This thread is locked; no one can reply to it. rss feed Print
 1   2 
Real-Time Text Input
Arthur Kalliokoski
Second in Command
February 2005
avatar

You'd be surprised.

Yep, right now I'd give my right arm a Pepsi Cola for some cut'n'paste code to disable resizable X windows. It's just a matter of the correct incantations.
(I did look at the A5 and SDL code, and my version doesn't work)

“Throughout history, poverty is the normal condition of man. Advances which permit this norm to be exceeded — here and there, now and then — are the work of an extremely small minority, frequently despised, often condemned, and almost always opposed by all right-thinking people. Whenever this tiny minority is kept from creating, or (as sometimes happens) is driven out of a society, the people then slip back into abject poverty. This is known as "bad luck.”

― Robert A. Heinlein

Nazerith
Member #12,551
February 2011

I'm already building a class for this that I'll gladly post when I'm done. Should be later tonight, possibly tomorrow.

[EDIT]

Here is the first version of the class. I've not had a chance to test (or even compile it), so use at your own risk.

I'm assuming you know how to set up an event queue and capture events.

So assuming your variables are declared as follows:

ALLEGRO_EVENT ev;
ALLEGRO_EVENT_SOURCE* keyboard_source = al_get_keyboard_event_source();
TextField text;
bool success;

You should be able to capture and process keyboard actions with the following. It returns true if it succeeds, false if it fails (static field is full, can't delete due to cursor position, ect).

It should properly handle SHIFT key, and returns null if you hold CTRL/ALT or press a command key (function keys, pause, break, prtsc, sysrq, esc, ect). That way you can handle those cases separately if you want.

if(ev.source == keyboard_source)
  success = text.ProcessKey(ev);

The following will return the current string in the input field. It returns a character array, which should be easy to render with al_draw_text or similar.

str = text.CurrentString();

The class has lots of different helper functions. It support both dynamic and static string lengths. Obviously static is faster in some regards. Dynamic strings will grow automatically, but you can also use text.SetConserve(true) which will shrink the array if there is lots of dead space at the end (20+). By default the array doesn't shrink once its been expanded.

So without further ado, the TextField class:

#SelectExpand
1class TextField 2{ 3 char * tf_string; //current string 4 int tf_position; //cursor position 5 int tf_size; //size allocated for array 6 bool tf_isStatic; //defaults to false (dynamic allocation) 7 bool tf_conserveMemory; //if set dynamic arrays are shortened to conserve memory 8 9 bool AddCharacter(char c); //add character at current cursor position. (return true if success) 10 bool DeleteCharacter(bool reverse = false); //delete character at current position. (return true if success) 11 12public: 13 TextField(int maxLength=0); //initialize empty field 14 TextField(char * StartString, int StartPosition, int maxLength=0); //initialize field with starting string and cursor position 15 16 char * GetString(); //returns current string 17 int GetPosition(); //returns current cursor position 18 int GetSize(); //returns current size of array 19 bool IsConserve(); //returns if dynamic arrays are resized to conserve memory 20 bool IsStatic(); //returns if field has static length 21 22 bool SetPosition(int p); //set cursor position 23 void SetConserve(bool conserve); //set dynamic string memory conservation 24 25 bool ProcessKey(ALLEGRO_EVENT ev); //processes a key press (returns true if success, false if failure, null if ignored) 26}; 27 28TextField::TextField(int maxLength=0) 29{ 30 if(maxLength > 0) //if MaxLength was set greater than zero, array is Static length 31 { 32 tf_size = maxLength; 33 tf_isStatic = true; 34 } 35 else //set as dynamic with a starting 10 character storage 36 { 37 tf_size = 10; 38 tf_isStatic = false; 39 } 40 tf_string = new char[tf_size]; 41 tf_position = 0; 42 tf_conserveMemory = false; 43} 44 45TextField::TextField(char * StartString, int StartPosition, int MaxLength=0) 46{ 47 if(MaxLength > 0) //if MaxLength was set greater than zero, array is Static length 48 { 49 tf_size = MaxLength; 50 tf_string = new char[tf_size]; 51 tf_isStatic = true; 52 } 53 else //set as Dynamic length larger than starting string 54 { 55 tf_size = strlen(StartString) + 10; 56 tf_string = new char[tf_size]; 57 tf_isStatic = false; 58 } 59 60 strcpy(tf_string,StartString); 61 tf_string[strlen(tf_string)] = '\0'; 62 63 tf_position = StartPosition; 64 tf_conserveMemory = false; 65} 66 67char * TextField::GetString() 68{ 69 return tf_string; 70} 71 72int TextField::GetPosition() 73{ 74 return tf_position; 75} 76 77int TextField::GetSize() 78{ 79 return tf_size; 80} 81 82bool TextField::IsConserve() 83{ 84 return tf_conserveMemory; 85} 86 87bool TextField::IsStatic() 88{ 89 return tf_isStatic; 90} 91 92bool TextField::SetPosition(int p) 93{ 94 if(p<0) 95 return false; 96 else if (p > strlen(tf_string)) 97 return false; 98 else 99 tf_position = p; 100 101 return true; 102} 103 104void TextField::SetConserve(bool conserve) 105{ 106 tf_conserveMemory = conserve; 107} 108 109bool TextField::AddCharacter(char c) 110{ 111 int currentLength = strlen(tf_string); 112 113 if(tf_position>currentLength) //catches error if position out of bounds 114 tf_position=currentLength; 115 116 if(tf_position<0) //catches error if position out of bounds 117 tf_position=0; 118 119 if(tf_position==tf_size) //if field is full 120 { 121 if(tf_isStatic) //cannot add any characters 122 return false; 123 else //resize dynamic array (adds 10) 124 { 125 tf_size+=10; 126 char* temp = new char [tf_size]; 127 strcpy(temp, tf_string); 128 delete [] tf_string; 129 tf_string=temp; 130 } 131 } 132 133 if(tf_position == strlen(tf_string)) //if position is at end of string 134 strcat(tf_string, c); 135 else if(tf_position == 0) //if position is beginning of string 136 { 137 char * temp = new char [tf_size]; 138 temp[0] = c; 139 strcpy(tf_string,strcat(temp, tf_string)); 140 delete [] temp; 141 } 142 else 143 { 144 char * temp = new char [tf_size]; 145 strncpy(temp,tf_string,tf_position); 146 temp[strlen(temp)] = c; 147 for(int i=tf_position; i<strlen(tf_string);i++) 148 temp[i+1]=tf_string[i]; 149 delete [] temp; 150 } 151 152 tf_position++; 153 return true; 154} 155 156bool TextField::DeleteCharacter(bool reverse=false) 157{ 158 int currentLength = strlen(tf_string); 159 160 if(tf_position < 0) //catches out of range error 161 tf_position=0; 162 if(tf_position > currentLength) //catches out of range error 163 tf_position = currentLength; 164 165 if(tf_position == 0) 166 { 167 if(!reverse) //cursor at start of field and backspace pressed 168 return false; 169 else //delete character at start of field 170 { 171 char * temp = new char [tf_size]; 172 for(int i=1; i<currentLength; i++) 173 temp[i-1]=tf_string[i]; 174 delete [] tf_string; 175 tf_string = temp; 176 } 177 } 178 else if(tf_position == currentLength) 179 { 180 if(reverse) //cursor at end of field and delete key pressed 181 return false; 182 else //delete character at end of field 183 strncpy(tf_string,tf_string,currentLength-1); 184 } 185 else 186 { 187 char * temp = new char [tf_size]; 188 if(!reverse) //delete character before cursor (BACKSPACE) 189 { 190 strncpy(temp,tf_string,tf_position-1); 191 for(int i=tf_position;i<currentLength;i++) 192 temp[i-1]=tf_string[i]; 193 tf_position--; 194 } 195 else //delete character after cursor (DELETE) 196 { 197 strncpy(temp,tf_string,tf_position); 198 for(int i=tf_position+1;i<currentLength;i++) 199 temp[i-1]=tf_string[i]; 200 } 201 delete [] tf_string; 202 tf_string=temp; 203 } 204 205 if(!tf_isStatic && tf_conserveMemory && tf_size>20 && currentLength+19<=tf_size) //resizes array to conserve memory (removes 10) 206 { 207 tf_size-=10; 208 char* temp = new char [tf_size]; 209 strcpy(temp, tf_string); 210 delete [] tf_string; 211 tf_string=temp; 212 } 213 214 return true; 215} 216 217bool TextField::ProcessKey(ALLEGRO_EVENT ev) 218{ 219 ALLEGRO_KEYBOARD_STATE *state; 220 al_get_keyboard_state(state); 221 222 if(ev.type == ALLEGRO_EVENT_KEY_DOWN && !al_key_down(state, ALLEGRO_KEY_ALT) && !al_key_down(state, ALLEGRO_KEY_ALTGR) && !al_key_down(state, ALLEGRO_KEY_RCTRL) && !al_key_down(state, ALLEGRO_KEY_LCTRL)) 223 { 224 if(ev.keyboard.keycode >= ALLEGRO_KEY_A && ev.keyboard.keycode <= ALLEGRO_KEY_Z) 225 { 226 if(al_key_down(state, ALLEGRO_KEY_LSHIFT) || al_key_down(state, ALLEGRO_KEY_RSHIFT)) 227 return AddCharacter('A'+ev.keyboard.keycode-ALLEGRO_KEY_A); 228 else 229 return AddCharacter('a'+ev.keyboard.keycode-ALLEGRO_KEY_A); 230 } 231 else if (ev.keyboard.keycode >= ALLEGRO_KEY_0 && ev.keyboard.keycode <= ALLEGRO_KEY_9) 232 { 233 if(al_key_down(state, ALLEGRO_KEY_LSHIFT) || al_key_down(state, ALLEGRO_KEY_RSHIFT)) 234 return AddCharacter('A'+ev.keyboard.keycode-ALLEGRO_KEY_A); 235 else 236 return AddCharacter('a'+ev.keyboard.keycode-ALLEGRO_KEY_A); 237 } 238 else if (ev.keyboard.keycode >= ALLEGRO_KEY_PAD_0 && ev.keyboard.keycode <= ALLEGRO_KEY_PAD_9) 239 { 240 return AddCharacter('0'+ev.keyboard.keycode-ALLEGRO_KEY_PAD_0); 241 } 242 else 243 { 244 switch(ev.keyboard.keycode) 245 { 246 case ALLEGRO_KEY_TILDE : 247 if(al_key_down(state, ALLEGRO_KEY_LSHIFT) || al_key_down(state, ALLEGRO_KEY_RSHIFT)) 248 return AddCharacter('~'); 249 else 250 return AddCharacter('`'); 251 break; 252 case ALLEGRO_KEY_MINUS : 253 if(al_key_down(state, ALLEGRO_KEY_LSHIFT) || al_key_down(state, ALLEGRO_KEY_RSHIFT)) 254 return AddCharacter('_'); 255 else 256 return AddCharacter('-'); 257 break; 258 case ALLEGRO_KEY_EQUALS : 259 if(al_key_down(state, ALLEGRO_KEY_LSHIFT) || al_key_down(state, ALLEGRO_KEY_RSHIFT)) 260 return AddCharacter('+'); 261 else 262 return AddCharacter('='); 263 break; 264 case ALLEGRO_KEY_BACKSPACE : 265 return DeleteCharacter(); 266 break; 267 case ALLEGRO_KEY_OPENBRACE : 268 if(al_key_down(state, ALLEGRO_KEY_LSHIFT) || al_key_down(state, ALLEGRO_KEY_RSHIFT)) 269 return AddCharacter('{'); 270 else 271 return AddCharacter('['); 272 break; 273 case ALLEGRO_KEY_CLOSEBRACE : 274 if(al_key_down(state, ALLEGRO_KEY_LSHIFT) || al_key_down(state, ALLEGRO_KEY_RSHIFT)) 275 return AddCharacter('}'); 276 else 277 return AddCharacter(']'); 278 break; 279 case ALLEGRO_KEY_SEMICOLON : 280 if(al_key_down(state, ALLEGRO_KEY_LSHIFT) || al_key_down(state, ALLEGRO_KEY_RSHIFT)) 281 return AddCharacter(':'); 282 else 283 return AddCharacter(';'); 284 break; 285 case ALLEGRO_KEY_QUOTE : 286 if(al_key_down(state, ALLEGRO_KEY_LSHIFT) || al_key_down(state, ALLEGRO_KEY_RSHIFT)) 287 return AddCharacter('\"'); 288 else 289 return AddCharacter('\''); 290 break; 291 case ALLEGRO_KEY_BACKSLASH : 292 if(al_key_down(state, ALLEGRO_KEY_LSHIFT) || al_key_down(state, ALLEGRO_KEY_RSHIFT)) 293 return AddCharacter('|'); 294 else 295 return AddCharacter('\\'); 296 break; 297 case ALLEGRO_KEY_BACKSLASH2 : /* DirectInput calls this DIK_OEM_102: "< > | on UK/Germany keyboards" */ 298 if(al_key_down(state, ALLEGRO_KEY_LSHIFT) || al_key_down(state, ALLEGRO_KEY_RSHIFT)) 299 return AddCharacter('|'); 300 else 301 return AddCharacter('\\'); 302 break; 303 case ALLEGRO_KEY_COMMA : 304 if(al_key_down(state, ALLEGRO_KEY_LSHIFT) || al_key_down(state, ALLEGRO_KEY_RSHIFT)) 305 return AddCharacter('<'); 306 else 307 return AddCharacter(','); 308 break; 309 case ALLEGRO_KEY_FULLSTOP : 310 if(al_key_down(state, ALLEGRO_KEY_LSHIFT) || al_key_down(state, ALLEGRO_KEY_RSHIFT)) 311 return AddCharacter('>'); 312 else 313 return AddCharacter('.'); 314 break; 315 case ALLEGRO_KEY_SLASH : 316 if(al_key_down(state, ALLEGRO_KEY_LSHIFT) || al_key_down(state, ALLEGRO_KEY_RSHIFT)) 317 return AddCharacter('?'); 318 else 319 return AddCharacter('/'); 320 break; 321 case ALLEGRO_KEY_SPACE : 322 return AddCharacter(' '); 323 break; 324 325 case ALLEGRO_KEY_LEFT : 326 if(tf_position > 0) 327 tf_position--; 328 break; 329 case ALLEGRO_KEY_RIGHT : 330 if(tf_position < strlen(tf_string)) 331 tf_position++; 332 break; 333 case ALLEGRO_KEY_UP : 334 //if you want to add a keyboard buffer of previous tf_strings 335 break; 336 case ALLEGRO_KEY_DOWN : 337 //same as key up 338 break; 339 340 case ALLEGRO_KEY_PAD_SLASH : 341 return AddCharacter('/'); 342 break; 343 case ALLEGRO_KEY_PAD_ASTERISK : 344 return AddCharacter('*'); 345 break; 346 case ALLEGRO_KEY_PAD_MINUS : 347 return AddCharacter('-'); 348 break; 349 case ALLEGRO_KEY_PAD_PLUS : 350 return AddCharacter('+'); 351 break; 352 case ALLEGRO_KEY_PAD_DELETE : 353 return DeleteCharacter(true); 354 break; 355 356 case ALLEGRO_KEY_COLON2 : 357 return AddCharacter(':'); 358 break; 359 360 case ALLEGRO_KEY_PAD_EQUALS : 361 return AddCharacter('='); 362 break; 363 case ALLEGRO_KEY_BACKQUOTE : 364 return AddCharacter('\''); 365 break; 366 case ALLEGRO_KEY_SEMICOLON2 : 367 if(al_key_down(state, ALLEGRO_KEY_LSHIFT) || al_key_down(state, ALLEGRO_KEY_RSHIFT)) 368 return AddCharacter(':'); 369 else 370 return AddCharacter(';'); 371 break; 372 } 373 } 374 } 375 376 return NULL; 377}

Edgar Reynaldo
Member #8,592
May 2007
avatar

Nazerith said:

#SelectExpand
1TextField::TextField(char * StartString, int StartPosition, int MaxLength=0) 2{ 3 if(MaxLength > 0) //if MaxLength was set greater than zero, array is Static length 4 {
5 tf_size = MaxLength;
6 tf_string = new char[tf_size]; 7 tf_isStatic = true; 8 } 9 else //set as Dynamic length larger than starting string 10 { 11 tf_size = strlen(StartString) + 10; 12 tf_string = new char[tf_size]; 13 tf_isStatic = false; 14 } 15
16 strcpy(tf_string,StartString);
17 tf_string[strlen(tf_string)] = '\0';
18 19 tf_position = StartPosition; 20 tf_conserveMemory = false; 21}

If MaxLength is less than or equal to the number of characters in StartString, then your strcpy call will overrun tf_string. Also, you overwrite the last character of the string with NULL when you use tf_string[strlen(tf_string)] = '\0'; Something else, strcpy writes a null character at the end of the string, so you don't need to do it manually.

Nazerith said:

#SelectExpand
1bool TextField::AddCharacter(char c) 2{ 3 int currentLength = strlen(tf_string); 4 5 if(tf_position>currentLength) //catches error if position out of bounds 6 tf_position=currentLength; 7 8 if(tf_position<0) //catches error if position out of bounds 9 tf_position=0; 10 11/// Problem here, this checks the position, not the size
12
13 if(tf_position==tf_size) //if field is full 14 { 15 if(tf_isStatic) //cannot add any characters 16 return false; 17 else //resize dynamic array (adds 10) 18 { 19 tf_size+=10; 20 char* temp = new char [tf_size]; 21 strcpy(temp, tf_string); 22 delete [] tf_string; 23 tf_string=temp; 24 } 25 } 26 27 if(tf_position == strlen(tf_string)) //if position is at end of string
28 strcat(tf_string, c);
29 else if(tf_position == 0) //if position is beginning of string 30 { 31 char * temp = new char [tf_size]; 32 temp[0] = c; 33 strcpy(tf_string,strcat(temp, tf_string)); 34 delete [] temp; 35 } 36 else 37 { 38 char * temp = new char [tf_size]; 39 strncpy(temp,tf_string,tf_position); 40 temp[strlen(temp)] = c; 41 for(int i=tf_position; i<strlen(tf_string);i++) 42 temp[i+1]=tf_string[i]; 43 delete [] temp; 44 } 45 46 tf_position++; 47 return true; 48}

c is a character - the second argument of strcat should be a const char*. You're also not checking the size of the string when you add a character to it, so you could overwrite it.

Nazerith said:

bool TextField::ProcessKey(ALLEGRO_EVENT ev)
{


You're asking allegro to store the keyboard state in an uninitialized pointer. It should be :

You're using C++ - why don't you just use a std::string? It would make manipulation of the string easier, and would manage the memory for you. See also string::erase and string::insert.

Nazerith
Member #12,551
February 2011

If MaxLength is less than or equal to the number of characters in StartString, then your strcpy call will overrun tf_string. Also, you overwrite the last character of the string with NULL when you use tf_string[strlen(tf_string)] = '\0'; Something else, strcpy writes a null character at the end of the string, so you don't need to do it manually.

True, I was half awake when I wrote much of this. But I'm glad for the comments, makes it easier to find errors. I've already noticed a half dozen myself since posting, ;D. (also, some helper functions to clear/set the string would be nice... ;))

#SelectExpand
1bool TextField::AddCharacter(char c) 2{ 3 int currentLength = strlen(tf_string); 4 5 if(tf_position>currentLength) //catches error if position out of bounds 6 tf_position=currentLength; 7 8 if(tf_position<0) //catches error if position out of bounds 9 tf_position=0; 10 11/// Problem here, this checks the position, not the size 12 if(tf_position==tf_size) //if field is full 13 { 14 if(tf_isStatic) //cannot add any characters 15 return false; 16 else //resize dynamic array (adds 10) 17 { 18 tf_size+=10; 19 char* temp = new char [tf_size]; 20 strcpy(temp, tf_string); 21 delete [] tf_string; 22 tf_string=temp; 23 } 24 } 25 26 if(tf_position == strlen(tf_string)) //if position is at end of string 27 strcat(tf_string, c); 28 else if(tf_position == 0) //if position is beginning of string 29 { 30 char * temp = new char [tf_size]; 31 temp[0] = c; 32 strcpy(tf_string,strcat(temp, tf_string)); 33 delete [] temp; 34 } 35 else 36 { 37 char * temp = new char [tf_size]; 38 strncpy(temp,tf_string,tf_position); 39 temp[strlen(temp)] = c; 40 for(int i=tf_position; i<strlen(tf_string);i++) 41 temp[i+1]=tf_string[i]; 42 delete [] temp; 43 } 44 45 tf_position++; 46 return true; 47}

c is a character - the second argument of strcat should be a const char*.

oddly my compiler doesn't seem to mind this, easy enough to fix.

You're also not checking the size of the string when you add a character to it, so you could overwrite it.

blarg. line 12 in the example should have been:

if(tf_size<=currentLength)

Less than should be impossible in theory, so I could get away with an == but nothing is really lost by making it <=.

Tomorrow after some sleep I'll throw a wrapper around this thing to render it and do some basic testing, work out the kinks. Too sleepy now. Nights.

[EDIT]

So insomnia got me once again, no sleep. sigh...

On the bright side, I build a wrapper to test everything. So here is the next version. I'll call this Alpha (why not?)

Changes:

  • Fixed compiler errors

  • Implemented keyboard state correctly

  • Made the number keys work (which didn't before due to a bad copy/paste)

  • Recoded the AddCharacter and DeleteCharacter functions. They are now much more compact and faster. (speed is why I'm not using std::String)

  • Added END and HOME functionality.

  • There are now SetString and Clear functions.

  • Added a destructor to deallocate the string when finished.

  • Removed nearly all of the strlen calls to tf_string. A new integer keeps tracks of the current string length (incremented/decremented as appropriate).

So here is TextField Alpha:

TextField.h

#SelectExpand
1#ifndef TEXTFIELD 2#define TEXTFIELD 3 4#include <string.h> 5 6#include <allegro5\allegro.h> 7 8class TextField 9{ 10 char * tf_string; //current string 11 int tf_position; //cursor position 12 int tf_array_size; //size allocated for array 13 int tf_string_size; //size of current string 14 bool tf_isStatic; //defaults to false (dynamic allocation) 15 bool tf_conserveMemory; //if set dynamic arrays are shortened to conserve memory 16 17 bool AddCharacter(char c); //add character at current cursor position. (return true if success) 18 bool DeleteCharacter(bool reverse = false); //delete character at current position. (return true if success) 19 20public: 21 TextField(int maxLength=0); //initialize empty field 22 TextField(char * StartString, int StartPosition, int maxLength=0); //initialize field with starting string and cursor position 23 24 ~TextField(); //destructor, deallocates tf_string; 25 26 char * GetString(); //returns current string 27 int GetPosition(); //returns current cursor position 28 int GetSize(); //returns current size of string 29 int GetArraySize(); //returns current size of array 30 bool IsConserve(); //returns if dynamic arrays are resized to conserve memory 31 bool IsStatic(); //returns if field has static length 32 33 bool SetPosition(int p); //set cursor position 34 void SetConserve(bool conserve); //set dynamic string memory conservation 35 bool SetString(const char* s); 36 37 bool Clear(); 38 39 bool ProcessKey(ALLEGRO_EVENT ev); //processes a key press (returns true if success, false if failure, null if ignored) 40}; 41 42TextField::TextField(int maxLength) 43{ 44 if(maxLength > 0) //if MaxLength was set greater than zero, array is Static length 45 { 46 tf_array_size = maxLength; 47 tf_isStatic = true; 48 } 49 else //set as dynamic with a starting 10 character storage 50 { 51 tf_array_size = 10; 52 tf_isStatic = false; 53 } 54 tf_string = new char[tf_array_size]; 55 tf_string[0] = '\0'; 56 tf_string_size = 0; 57 tf_position = 0; 58 tf_conserveMemory = false; 59} 60 61TextField::TextField(char * StartString, int StartPosition, int MaxLength) 62{ 63 tf_string_size = strlen(StartString); 64 65 if(MaxLength > 0) //if MaxLength was set greater than zero, array is Static length 66 { 67 tf_array_size = MaxLength; 68 tf_string = new char[tf_array_size]; 69 tf_isStatic = true; 70 if(tf_string_size > tf_array_size) 71 { 72 tf_string_size = tf_array_size; 73 strncpy(tf_string,StartString,tf_array_size); 74 } 75 else 76 strcpy(tf_string,StartString); 77 } 78 else //set as Dynamic length larger than starting string 79 { 80 tf_array_size = tf_string_size + 10; 81 tf_string = new char[tf_array_size]; 82 tf_isStatic = false; 83 strcpy(tf_string,StartString); 84 } 85 86 if(StartPosition<0) 87 tf_position=0; 88 else if(StartPosition>tf_string_size) 89 tf_position=tf_string_size; 90 else 91 tf_position = StartPosition; 92 tf_conserveMemory = false; 93} 94 95TextField::~TextField() 96{ 97 delete [] tf_string; 98} 99 100char * TextField::GetString() 101{ 102 return tf_string; 103} 104 105int TextField::GetPosition() 106{ 107 return tf_position; 108} 109 110int TextField::GetSize() 111{ 112 return tf_string_size; 113} 114 115int TextField::GetArraySize() 116{ 117 return tf_array_size; 118} 119 120bool TextField::IsConserve() 121{ 122 return tf_conserveMemory; 123} 124 125bool TextField::IsStatic() 126{ 127 return tf_isStatic; 128} 129 130bool TextField::SetPosition(int p) 131{ 132 if(p<0) 133 return false; 134 else if (p > tf_string_size) 135 return false; 136 else 137 tf_position = p; 138 139 return true; 140} 141 142void TextField::SetConserve(bool conserve) 143{ 144 tf_conserveMemory = conserve; 145} 146 147bool TextField::SetString(const char* NewString) 148{ 149 tf_string_size = strlen(NewString); 150 151 if(tf_isStatic) 152 { 153 if(tf_string_size > tf_array_size) 154 { 155 tf_string_size = tf_array_size; 156 strncpy(tf_string,NewString,tf_array_size); 157 } 158 else 159 strcpy(tf_string,NewString); 160 } 161 else //set as Dynamic length larger than starting string 162 { 163 tf_array_size = tf_string_size + 10; 164 delete [] tf_string; 165 tf_string = new char[tf_array_size]; 166 strcpy(tf_string,NewString); 167 } 168 169 if(tf_position<0) 170 tf_position=0; 171 else if(tf_position>tf_string_size) 172 tf_position=tf_string_size; 173 174 return(true); 175} 176 177bool TextField::Clear() 178{ 179 180 if(!tf_isStatic) 181 tf_array_size = 10; 182 183 tf_string[0] = '\0'; 184 tf_string_size = 0; 185 tf_position = 0; 186 187 return(true); 188} 189 190bool TextField::AddCharacter(char c) 191{ 192 if(tf_position>tf_string_size) //catches error if position out of bounds 193 tf_position=tf_string_size; 194 195 if(tf_position<0) //catches error if position out of bounds 196 tf_position=0; 197 198 if(tf_array_size<=tf_string_size+1) //if field is full 199 { 200 if(tf_isStatic) //cannot add any characters 201 return false; 202 else //resize dynamic array (adds 10) 203 { 204 tf_array_size+=10; 205 char* temp = new char [tf_array_size]; 206 strcpy(temp, tf_string); 207 delete [] tf_string; 208 tf_string=temp; 209 } 210 } 211 212 memmove(tf_string+tf_position+1, tf_string+tf_position, tf_string_size-tf_position+1); 213 tf_string[tf_position] = c; 214 tf_position++; 215 tf_string_size++; 216 217 return true; 218} 219 220bool TextField::DeleteCharacter(bool reverse) 221{ 222 if(tf_position < 0) //catches out of range error 223 tf_position=0; 224 if(tf_position > tf_string_size) //catches out of range error 225 tf_position = tf_string_size; 226 227 if(reverse) 228 { 229 if(tf_position==tf_string_size) 230 return false; 231 else 232 memmove(tf_string+tf_position, tf_string+tf_position+1, tf_string_size-tf_position); 233 } 234 else 235 { 236 if(tf_position==0) 237 return false; 238 else 239 { 240 memmove(tf_string+tf_position-1, tf_string+tf_position, tf_string_size-tf_position+1); 241 tf_position--; 242 } 243 } 244 245 tf_string_size--; 246 247 if(!tf_isStatic && tf_conserveMemory && tf_array_size>=20 && tf_string_size+19<=tf_array_size) //resizes array to conserve memory (removes 10) 248 { 249 tf_array_size-=10; 250 char* temp = new char [tf_array_size]; 251 strcpy(temp, tf_string); 252 delete [] tf_string; 253 tf_string=temp; 254 } 255 256 return true; 257} 258 259bool TextField::ProcessKey(ALLEGRO_EVENT ev) 260{ 261 ALLEGRO_KEYBOARD_STATE state; 262 bool shiftDown=false; 263 264 al_get_keyboard_state(&state); 265 266 if(ev.type != ALLEGRO_EVENT_KEY_DOWN || 267 al_key_down(&state, ALLEGRO_KEY_ALT) || al_key_down(&state, ALLEGRO_KEY_ALTGR) || 268 al_key_down(&state, ALLEGRO_KEY_RCTRL) || al_key_down(&state, ALLEGRO_KEY_LCTRL)) 269 return NULL; 270 else 271 { 272 if(al_key_down(&state, ALLEGRO_KEY_LSHIFT) || al_key_down(&state, ALLEGRO_KEY_RSHIFT)) 273 shiftDown=true; 274 275 if(ev.keyboard.keycode >= ALLEGRO_KEY_A && ev.keyboard.keycode <= ALLEGRO_KEY_Z) 276 { 277 if(shiftDown) 278 return AddCharacter('A'+ev.keyboard.keycode-ALLEGRO_KEY_A); 279 else 280 return AddCharacter('a'+ev.keyboard.keycode-ALLEGRO_KEY_A); 281 } 282 else if (ev.keyboard.keycode >= ALLEGRO_KEY_0 && ev.keyboard.keycode <= ALLEGRO_KEY_9) 283 { 284 if(shiftDown) 285 switch(ev.keyboard.keycode) 286 { 287 case ALLEGRO_KEY_0 : 288 AddCharacter(')'); 289 break; 290 case ALLEGRO_KEY_1 : 291 AddCharacter('!'); 292 break; 293 case ALLEGRO_KEY_2 : 294 AddCharacter('@'); 295 break; 296 case ALLEGRO_KEY_3 : 297 AddCharacter('#'); 298 break; 299 case ALLEGRO_KEY_4 : 300 AddCharacter('$'); 301 break; 302 case ALLEGRO_KEY_5 : 303 AddCharacter('%'); 304 break; 305 case ALLEGRO_KEY_6 : 306 AddCharacter('^'); 307 break; 308 case ALLEGRO_KEY_7 : 309 AddCharacter('&'); 310 break; 311 case ALLEGRO_KEY_8 : 312 AddCharacter('*'); 313 break; 314 case ALLEGRO_KEY_9 : 315 AddCharacter('('); 316 break; 317 } 318 else 319 return AddCharacter('0'+ev.keyboard.keycode-ALLEGRO_KEY_0); 320 } 321 else if (ev.keyboard.keycode >= ALLEGRO_KEY_PAD_0 && ev.keyboard.keycode <= ALLEGRO_KEY_PAD_9) 322 { 323 return AddCharacter('0'+ev.keyboard.keycode-ALLEGRO_KEY_PAD_0); 324 } 325 else 326 { 327 switch(ev.keyboard.keycode) 328 { 329 case ALLEGRO_KEY_TILDE : 330 if(shiftDown) 331 return AddCharacter('~'); 332 else 333 return AddCharacter('`'); 334 break; 335 case ALLEGRO_KEY_MINUS : 336 if(shiftDown) 337 return AddCharacter('_'); 338 else 339 return AddCharacter('-'); 340 break; 341 case ALLEGRO_KEY_EQUALS : 342 if(shiftDown) 343 return AddCharacter('+'); 344 else 345 return AddCharacter('='); 346 break; 347 case ALLEGRO_KEY_BACKSPACE : 348 return DeleteCharacter(); 349 break; 350 case ALLEGRO_KEY_OPENBRACE : 351 if(shiftDown) 352 return AddCharacter('{'); 353 else 354 return AddCharacter('['); 355 break; 356 case ALLEGRO_KEY_CLOSEBRACE : 357 if(shiftDown) 358 return AddCharacter('}'); 359 else 360 return AddCharacter(']'); 361 break; 362 case ALLEGRO_KEY_SEMICOLON : 363 if(shiftDown) 364 return AddCharacter(':'); 365 else 366 return AddCharacter(';'); 367 break; 368 case ALLEGRO_KEY_QUOTE : 369 if(shiftDown) 370 return AddCharacter('\"'); 371 else 372 return AddCharacter('\''); 373 break; 374 case ALLEGRO_KEY_BACKSLASH : 375 if(shiftDown) 376 return AddCharacter('|'); 377 else 378 return AddCharacter('\\'); 379 break; 380 case ALLEGRO_KEY_BACKSLASH2 : /* DirectInput calls this DIK_OEM_102: "< > | on UK/Germany keyboards" */ 381 if(shiftDown) 382 return AddCharacter('|'); 383 else 384 return AddCharacter('\\'); 385 break; 386 case ALLEGRO_KEY_COMMA : 387 if(shiftDown) 388 return AddCharacter('<'); 389 else 390 return AddCharacter(','); 391 break; 392 case ALLEGRO_KEY_FULLSTOP : 393 if(shiftDown) 394 return AddCharacter('>'); 395 else 396 return AddCharacter('.'); 397 break; 398 case ALLEGRO_KEY_SLASH : 399 if(shiftDown) 400 return AddCharacter('?'); 401 else 402 return AddCharacter('/'); 403 break; 404 case ALLEGRO_KEY_SPACE : 405 return AddCharacter(' '); 406 break; 407 408 case ALLEGRO_KEY_DELETE : 409 return DeleteCharacter(true); 410 break; 411 case ALLEGRO_KEY_HOME : 412 tf_position = 0; 413 break; 414 case ALLEGRO_KEY_END : 415 tf_position = tf_string_size; 416 break; 417 case ALLEGRO_KEY_LEFT : 418 if(tf_position > 0) 419 tf_position--; 420 break; 421 case ALLEGRO_KEY_RIGHT : 422 if(tf_position < tf_string_size) 423 tf_position++; 424 break; 425 case ALLEGRO_KEY_UP : 426 //if you want to add a keyboard buffer of previous tf_strings 427 break; 428 case ALLEGRO_KEY_DOWN : 429 //same as key up 430 break; 431 432 case ALLEGRO_KEY_PAD_SLASH : 433 return AddCharacter('/'); 434 break; 435 case ALLEGRO_KEY_PAD_ASTERISK : 436 return AddCharacter('*'); 437 break; 438 case ALLEGRO_KEY_PAD_MINUS : 439 return AddCharacter('-'); 440 break; 441 case ALLEGRO_KEY_PAD_PLUS : 442 return AddCharacter('+'); 443 break; 444 case ALLEGRO_KEY_PAD_DELETE : 445 return DeleteCharacter(true); 446 break; 447 448 case ALLEGRO_KEY_COLON2 : 449 return AddCharacter(':'); 450 break; 451 452 case ALLEGRO_KEY_PAD_EQUALS : 453 return AddCharacter('='); 454 break; 455 case ALLEGRO_KEY_BACKQUOTE : 456 return AddCharacter('\''); 457 break; 458 case ALLEGRO_KEY_SEMICOLON2 : 459 if(shiftDown) 460 return AddCharacter(':'); 461 else 462 return AddCharacter(';'); 463 break; 464 } 465 } 466 } 467 468 return NULL; 469} 470 471 472#endif

Edgar Reynaldo
Member #8,592
May 2007
avatar

Nazerith said:

speed is why I'm not using std::String

This may be counter intuitive, but you'll probably save more time in development by using std::string. And also, the standard library has probably been optimized better than you could do with char* yourself. There's no way that at 60 updates per second that you will see any difference in speed of any kind by using char*'s manually instead of using std::string.

Nazerith said:

   tf_array_size = MaxLength;
//...
   strncpy(tf_string,StartString,tf_array_size);

tf_array_size should be MaxLength plus one, to account for the null terminator.
Also, if you copy X characters into an array with X elements, where will the null terminator go?

Peter Wang
Member #23
April 2000

Ugh.

bool TextField::ProcessKey(ALLEGRO_EVENT ev)
{
  ALLEGRO_KEYBOARD_STATE state;
  bool shiftDown=false;

  al_get_keyboard_state(&state);

  if(ev.type != ALLEGRO_EVENT_KEY_DOWN || 
    al_key_down(&state, ALLEGRO_KEY_ALT) || al_key_down(&state, ALLEGRO_KEY_ALTGR) || 
    al_key_down(&state, ALLEGRO_KEY_RCTRL) || al_key_down(&state, ALLEGRO_KEY_LCTRL))
      return NULL;
  else

ev.keyboard.modifiers already has this information.

    if(ev.keyboard.keycode >= ALLEGRO_KEY_A && ev.keyboard.keycode <= ALLEGRO_KEY_Z)
    {
      if(shiftDown)
        return AddCharacter('A'+ev.keyboard.keycode-ALLEGRO_KEY_A);
      else
        return AddCharacter('a'+ev.keyboard.keycode-ALLEGRO_KEY_A);
    }

The ev.keyboard.unichar field already has the information you need. Your code is needlessly verbose and will only "work" on US keyboard layouts.

If something is very repetitious, alarms should be sounding off in your head. Programmers hate repetition.

Nazerith
Member #12,551
February 2011

This may be counter intuitive, but you'll probably save more time in development by using std::string. And also, the standard library has probably been optimized better than you could do with char* yourself. There's no way that at 60 updates per second that you will see any difference in speed of any kind by using char*'s manually instead of using std::string.

Could I create a general purpose string library thats quicker than std:string. most likely not. But in the very specific usage I'm implementing here, its already faster than std:string for various reasons.

1) Every time you concatenate or lengthen a std:string, new memory is allocated. I'm only reallocating at a minimum of every ten concatenations.
2) Allocating new memory and using char * copy functions is faster than doing the same with std:string.

std:string has a bit of overhead that makes it very flexible. I don't need any of that flexibility, so I've sacrificed the the overhead. You can find some places on the internet where people have run comparison tests. Depending on the exact test, char* operates roughly 2x to 10x faster than std::string but you have to be willing to handle your own memory management.

I'm not worried about handling it 60 times a minute, but in a game there are frequently lots of events between frame renders. The less time it spends processing here, the more time it has to do other things. Since I'm working up these components for an online game, I expect lots of other things happening as well.

Edgar Reynaldo said:

tf_array_size = MaxLength;
//...
   strncpy(tf_string,StartString,tf_array_size);

tf_array_size should be MaxLength plus one, to account for the null terminator.
Also, if you copy X characters into an array with X elements, where will the null terminator go?

I've been testing dynamic strings mostly. Guess I'll need to make the array for fixed strings one larger. However on the strncpy I don't need to account for the null character in the length, since the function adds a null at the end anyways (as you pointed out previously).

ev.keyboard.modifiers already has this information.

Yep. Originally I was using the Key Down event for my class, but have already switched to Key Char events. So in my next version I've eliminated the need to test state (which should get rid of any timing errors for rapid key presses), and it also lets me handle when someone holds down a key.

Peter Wang said:

The ev.keyboard.unichar field already has the information you need. Your code is needlessly verbose and will only "work" on US keyboard layouts.

If something is very repetitious, alarms should be sounding off in your head. Programmers hate repetition.

I'm still learning all the things in the Allegro library. I'll take a look at the unicode stuff and see about implementing it.

Thanks for the feedback guys. Til Laters.

Edgar Reynaldo
Member #8,592
May 2007
avatar

Nazerith said:

I've been testing dynamic strings mostly. Guess I'll need to make the array for fixed strings one larger. However on the strncpy I don't need to account for the null character in the length, since the function adds a null at the end anyways (as you pointed out previously).

strcpy adds a null, if there is space for it, but I can't say for sure that strncpy does the same thing. And there's still the problem of using strncpy to copy X elements into an array that only has X elements. If it does add a trailing null, there will still be an overflow. If it does not, you still have a string that is not null terminated.

Nazerith
Member #12,551
February 2011

Agreed. Just to play it safe I'll make the array one larger.

jmasterx
Member #11,410
October 2009

Mine uses Unichar and uses UTF-8 and std::string, much cleaner & more effective imho.

Nazerith
Member #12,551
February 2011

Unicode does simply down the code, but now its too simplified.

Allegro API Docs said:

keyboard.unichar (int)
A Unicode code point (character). This may be zero or negative if the event was generated for a non-visible "character", such as an arrow key. In that case you can act upon the keycode field.

So a simple test of "ev.keyboard.unichar > 0" should do right? Well it does for arrow keys and delete, but not for esc, backspace and a number of other keys that are "non-visible". So now I have the opposite issue...I've got to test for every key that doesn't belong. While obviously easier in the long run (if you are shooting for international keyboards), its still a pain.

Urg.

For those out their who have messed with the unicode field before, you have a simple way to filter out the garbage?

jmasterx
Member #11,410
October 2009

#SelectExpand
1void AguiTextBox::handleKeyboard( const AguiKeyEventArgs &keyArgs ) 2{ 3 4 forceShowCaret(); 5 resetCaretBlinkTime(); 6 if(keyArgs.getExtendedKey() == AGUI_EXT_KEY_UP) 7 { 8 keyPositionCaret(getCaretColumn(),getCaretRow() - 1); 9 return; 10 } 11 else if(keyArgs.getExtendedKey() == AGUI_EXT_KEY_DOWN) 12 { 13 keyPositionCaret(getCaretColumn(),getCaretRow() + 1); 14 return; 15 } 16 else if(keyArgs.getExtendedKey() == AGUI_EXT_KEY_LEFT) 17 { 18 keyPositionCaret(getCaretColumn() - 1,getCaretRow()); 19 return; 20 } 21 else if(keyArgs.getExtendedKey() == AGUI_EXT_KEY_RIGHT) 22 { 23 keyPositionCaret(getCaretColumn() + 1,getCaretRow()); 24 return; 25 } 26 else if(keyArgs.getKey() == AGUI_KEY_BACKSPACE) 27 { 28 removeLastCharacter(); 29 } 30 else if(keyArgs.getKey() == AGUI_KEY_DELETE) 31 { 32 removeNextCharacter(); 33 } 34 else if(keyArgs.getKey() == AGUI_KEY_ENTER) 35 { 36 addToNextCharacter('\n'); 37 } 38 else if(keyArgs.getUnichar() >= ' ') 39 { 40 addToNextCharacter(keyArgs.getUnichar()); 41 } 42 43 44}

Tobias Dammers
Member #2,604
August 2002
avatar

Nazerith said:

1) Every time you concatenate or lengthen a std:string, new memory is allocated. I'm only reallocating at a minimum of every ten concatenations.
2) Allocating new memory and using char * copy functions is faster than doing the same with std:string.

Those are assumptions you're making, and they're both wrong.
1) A string is backed by a std::vector internally, which means that it only resizes when its capacity would otherwise be exceeded, and when it does, it grows exponentially (e.g., double its capacity on every overflow). You are growing every ten concatenations, which is O(n); std::string grows at O(log n).
2) Although std::string looks horribly complicated when you look at the source code, most of that complexity is executed at compile time, not at run time. The result is pretty efficient. Using things like strcat(), however, is horribly inefficient due to an inherent problem with null-terminated strings. Here's a bit of reading for you: http://en.wikipedia.org/wiki/Schlemiel_the_Painter%27s_algorithm (or might as well read the original article: http://www.joelonsoftware.com/articles/fog0000000319.html). In other words, a naive C-string implementation is almost certainly less efficient than a naive std::string implementation (std::string maintains a string length counter internally, so getting the length or finding the 'past-the-end' position is O(1)).

---
Me make music: Triofobie
---
"We need Tobias and his awesome trombone, too." - Johan Halmén

jmasterx
Member #11,410
October 2009

Since it uses a std::vector you should also be able to reserve x, that way if you know about how much you will need you can preallocate it.

 1   2 


Go to: