Since I've been having trouble with it and there doesn't seem to be any examples using the Allegro 5 API, I thought I would ask here: is there a tidy way to implement a function that alters a character string in real-time?
Allegro timing is a pain, and I'm trying to display a character string on the screen in real-time as it's being edited through keyboard input. Any help would be appreciated.
How about ex_font_justify?
What exactly does ex_font_justify do? It couldn't possibly do all of what I'm asking by itself.
See TextEntry class in nihgui.cpp
You can either use one of the GUI libraries on the Depot (I'm not sure if any of them supports A5 yet though), or you can whip up your own text entry function. A simple one would, in pseudocode, look something like this:
string read_user_input(string initial = "") { // Set the current string buffer to the initial value string str = initial; // Loop until the user presses Esc or Enter do { int k = readkey(); switch (k) { // a few special keys: // Esc cancels case Escape: return initial; // Return commits case Return: return str; // Backspace deletes last character case Backspace: if (str.length > 0) str.remove_last_char(); break; // Any other key gets appended to the buffer... default: // but only if it's a regular key if (!is_special_key(k)) str += key_to_ascii(k); break; } // After each keypress, update the display display_the_string(str); } while (1); }
This is a very simple example, you'll need to flesh it out a lot more to get the functionality of a typical textbox, but you get the idea. One of the first steps after getting this one working, you might want to add a cursor position, and instead of manipulating the end of the string only, you'd insert and delete at the cursor position, and display a cursor in the textbox.
Tobis: That would not really be real-time text input though. It would halt execution and wait for the function to return.
Is there a version of keycode_to_ascii in Allegro 5? If not, this will get messy fast. Anyway, I can't seem to shorten the string with backspace. The closest I can get is inserting a space in the string, but then I get a bunch of spaces tacked onto the end. Is there a simpler way to implement a backspace feature?
Did you not look at TextEntry in nihgui.cpp? It shows how to do it all quite clearly.
Oh, I forgot to mention: I have no idea what nihgui.cpp is and can't find it anywhere. So yeah.
The examples and that file are in the source code package.
I was working on a comprehensive typeable textarea some time ago. I've attached the program.
{"name":"603392","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/7\/1\/71eff6d499bf8da1ea16356015e10ae1.png","w":1053,"h":627,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/7\/1\/71eff6d499bf8da1ea16356015e10ae1"}
I put this on hold because I'm waiting on the clipboard stuff ( ) to come in. But as of now it does one line of text and you can hilight, delete hilighted text, ctrl-jump across words, ctrl-shift-jump across words, etc. Ideally, it should support multiple lines and copy/paste/undo operations.
Though it's not finished, perhaps you can get some help by looking at it.
The thing is, I need to draw the text to the screen in a very specific way. I just need a function that returns a string, nothing more.
The thing is, I need to draw the text to the screen in a very specific way. I just need a function that returns a string, nothing more.
How would that work? all you get from The OS or Allegro is a sequence of events from the keyboard. Not any kind of string. So handling the actual input is far more complex than I think you think it is.
Now you can always just make a function that returns a string of the current contents of the text entry, but the code that actually handles the keyboard events is going to be a bit more complex.
The thing is, I need to draw the text to the screen in a very specific way. I just need a function that returns a string, nothing more.
You can also do something like this:
I'm using Unicode but you can change that.
It's very simple.
BTW can you download the example .exe (attached) and check if the beeps sounds on your machine?
That would be perfect if not for the fact that I'm using cstrings and not using fonts. ALLEGRO_USTR won't convert to cstrings, and I'm using my own functions to draw strings, not the font plugin. No beeps either.
I think the only thing I really need now is a reliable way to... er... un-append the string. Shorten it by one character.
I think the only thing I really need now is a reliable way to... er... un-append the string. Shorten it by one character.
Set the currently last character to '\0'
ALLEGRO_USTR won't convert to cstrings
Yes it will. What makes you think it didn't?
I think the only thing I really need now is a reliable way to... er... un-append the string. Shorten it by one character.
With C strings, just write a 0 at the last character. No need to reallocate or anything, just write a 0. Might want to keep track of the length though. Thats where the USTR stuff would help out, it does dynamic allocation for you, and can handle UTF8 so you don't have to think about it. But you're free to use c strings if you wish. Its just harder.
That would be perfect if not for the fact that I'm using cstrings and not using fonts. ALLEGRO_USTR won't convert to cstrings, and I'm using my own functions to draw strings, not the font plugin. No beeps either.
un-append the string
You can always write a 0 at the end. There's a bunch of good stuff here:
http://www.cplusplus.com/reference/clibrary/cstring/
Yes it will. What makes you think it didn't?
My compiler telling me that there is no conversion to char * from ALLEGRO_USTR.
You can always write a 0 at the end. There's a bunch of good stuff here:
http://www.cplusplus.com/reference/clibrary/cstring/
Tried it.
Set the currently last character to '\0'
Tried it. Look, if I had a question about basic c++ operations, I wouldn't be asking here. I just assumed it might have something to do with Allegro. If you can alter the code I posted above to work with any of those suggestions, I'd be grateful and happily use it. But it won't work for me.
Too late to go back now.
My compiler telling me that there is no conversion to char * from ALLEGRO_USTR.
Of course there's no implicit conversion. It's the same as trying to use a C++ string when a C string is expected. That doesn't mean there isn't a way to convert from one form to the other. Use this.
Tried it. Look, if I had a question about basic c++ operations, I wouldn't be asking here. I just assumed it might have something to do with Allegro.
It has nothing to do with Allegro. The only way to reduce the length of C strings is to move the position of the terminator. Perhaps you should ask questions about the basics since it appears you don't know them.
edit: Here's a working example
It prints "Hello World". Note the removal of the !.
Tried it.
Tried it.
If you can alter the code I posted above to work with any of those suggestions, I'd be grateful and happily use it. But it won't work for me.
You gotta be responsible for your own code, man. We can't do it for you.
You gotta be responsible for your own code, man. We can't do it for you.
No, I wasn't asking you to write my code for me. You gave suggestions, they didn't work. I accept that I may have misunderstood what you were suggesting, so I wanted to see your implementation in case you meant something different than I thought you did.
Never assume someone's just asking you to write their code for them. Most people want help with their code, not a codesitter.
My original question was whether there was an efficient way to do this with Allegro timing, but I guess there isn't. I can figure it out on my own from here, I think. Unless someone has another suggestion.
Most people want help with their code, not a codesitter.
You'd be surprised.
My original question was whether there was an efficient way to do this with Allegro timing, but I guess there isn't.
The question itself doesn't make much sense to me. Timing has nothing to do with keyboard input for the most part. Or an editable text entry.
I think the same... I can't understand you. "an efficient way to do this with Allegro timing" .
You're talking about separating the Keyboard inputs from the drawing?
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)
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:
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.
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.
You're asking allegro to store the keyboard state in an uninitialized pointer. It should be :
ALLEGRO_KEYBOARD_STATE state; al_get_keyboard_state(&state);
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.
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, . (also, some helper functions to clear/set the string would be nice... )
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
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.
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?
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.
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.
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.
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.
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.
Agreed. Just to play it safe I'll make the array one larger.
Mine uses Unichar and uses UTF-8 and std::string, much cleaner & more effective imho.
Unicode does simply down the code, but now its too simplified.
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?
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)).
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.