]> icculus.org git repositories - taylor/freespace2.git/blob - src/ui/inputbox.cpp
SDL2 port - stage 2
[taylor/freespace2.git] / src / ui / inputbox.cpp
1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell 
5  * or otherwise commercially exploit the source or things you created based on
6  * the source.
7  */
8
9 /*
10  * $Logfile: /Freespace2/code/Ui/INPUTBOX.cpp $
11  * $Revision$
12  * $Date$
13  * $Author$
14  *
15  * Code to implement input boxes
16  *
17  * $Log$
18  * Revision 1.3  2004/09/20 01:31:45  theoddone33
19  * GCC 3.4 fixes.
20  *
21  * Revision 1.2  2002/06/09 04:41:29  relnev
22  * added copyright header
23  *
24  * Revision 1.1.1.1  2002/05/03 03:28:11  root
25  * Initial import.
26  *
27  * 
28  * 8     9/07/99 4:01p Dave
29  * Fixed up a string.tbl paroblem (self destruct message). Make sure IPX
30  * does everything properly (setting up address when binding). Remove
31  * black rectangle background from UI_INPUTBOX.
32  * 
33  * 7     7/15/99 7:14p Jefff
34  * 
35  * 6     6/25/99 11:59a Dave
36  * Multi options screen.
37  * 
38  * 5     2/11/99 3:08p Dave
39  * PXO refresh button. Very preliminary squad war support.
40  * 
41  * 4     12/02/98 5:47p Dave
42  * Put in interface xstr code. Converted barracks screen to new format.
43  * 
44  * 3     10/13/98 9:29a Dave
45  * Started neatening up freespace.h. Many variables renamed and
46  * reorganized. Added AlphaColors.[h,cpp]
47  * 
48  * 2     10/07/98 10:54a Dave
49  * Initial checkin.
50  * 
51  * 1     10/07/98 10:51a Dave
52  * 
53  * 43    5/14/98 6:29p Hoffoss
54  * Fixed some warnings a release rebuild all turned up.
55  * 
56  * 42    4/14/98 5:06p Dave
57  * Don't load or send invalid pilot pics. Fixed chatbox graphic errors.
58  * Made chatbox display team icons in a team vs. team game. Fixed up pause
59  * and endgame sequencing issues.
60  * 
61  * 41    4/12/98 5:31p Lawrance
62  * use timer_get_milliseconds() instead of gettime()
63  * 
64  * 40    4/10/98 5:36p Dave
65  * Put in user notification of illegal values in multi host options
66  * screen. Fixed server respawn ship class problem.
67  * 
68  * 39    4/09/98 3:10p Mike
69  * Fix shockingly stupid typo.
70  * 
71  * 38    4/09/98 2:46p Mike
72  * Fix bug preventing first letter in pilot name being capitalized.
73  * 
74  * 37    4/09/98 12:12p Mike
75  * Separate versioning for demo and full versions.
76  * Fix inputbox bugs.
77  * 
78  * 36    4/06/98 5:14p Hoffoss
79  * Added some needed asserts.
80  * 
81  * 35    4/06/98 4:25p Allender
82  * Fix strncpy bug in inputbox
83  * 
84  * 34    4/06/98 3:58p Frank
85  * added initialization to try and fix a bug.
86  * 
87  * 33    4/01/98 10:26a Hoffoss
88  * Changed input box code to not draw a cursor if the input box is
89  * disabled.
90  * 
91  * 32    3/27/98 4:01p Hoffoss
92  * Fixed bug where input box text wsn't being null terminated!  Bad!
93  * 
94  * 31    3/27/98 3:04p Mitri
95  * Fixed bug in set_text().  'length' is the number of characters allowed,
96  * EXCLUDING the null terminator.
97  * 
98  * 30    3/27/98 11:20a Hoffoss
99  * Changed input to use bright white text instead of bright green.
100  * 
101  * 29    3/10/98 11:29a Hoffoss
102  * Make first_time ignored and text not disappear after a return is
103  * pressed.
104  * 
105  * 28    2/26/98 4:21p Dave
106  * More robust multiplayer voice.
107  * 
108  * 27    1/23/98 5:43p Dave
109  * Finished bringing standalone up to speed. Coded in new host options
110  * screen.
111  * 
112  * 26    1/20/98 10:36a Hoffoss
113  * Fixed optimized warnings.
114  * 
115  * 25    1/17/98 5:51p Dave
116  * Bug fixes for bugs generated by multiplayer testing.
117  * 
118  * 24    1/16/98 7:57p Lawrance
119  * support animating input box cursor
120  * 
121  * 23    1/15/98 5:12p Hoffoss
122  * Fixed inputbox so clicking on it gives it focus.
123  * 
124  * 22    1/15/98 5:10p Allender
125  * ton of interface changes.  chatbox in multiplayer now behaves
126  * differently than before.  It's always active in any screen that uses
127  * it.  Only non-printatble characters will get passed back out from
128  * chatbox
129  * 
130  * 21    1/14/98 6:43p Hoffoss
131  * Massive changes to UI code.  A lot cleaner and better now.  Did all
132  * this to get the new UI_DOT_SLIDER to work properly, which the old code
133  * wasn't flexible enough to handle.
134  * 
135  * 20    1/05/98 1:15p John
136  * Made inputbox skip the initial "selected" phase when you first tab to
137  * it.
138  * 
139  * 19    12/22/97 5:08p Hoffoss
140  * Changed inputbox class to be able to accept only certain keys, changed
141  * pilot screens to utilize this feature.  Added to assert with pilot file
142  * saving.
143  * 
144  * 18    12/11/97 8:15p Dave
145  * Put in network options screen. Xed out olf protocol selection screen.
146  * 
147  * 17    12/08/97 6:22p Lawrance
148  * blink cursor on inputbox
149  * 
150  * 16    12/06/97 4:27p Dave
151  * Another load of interface and multiplayer bug fixes.
152  * 
153  * 15    11/25/97 3:51p Hoffoss
154  * Changed edit background rect position slightly.
155  * 
156  * 14    10/24/97 10:58p Hoffoss
157  * Made some changes to the UI code to do some things I need it to do.
158  * 
159  * 13    10/09/97 11:05a Allender
160  * ignore controled and alted keys in input box
161  * 
162  * 12    10/01/97 4:39p Lawrance
163  * null out text when free'ed
164  * 
165  * 11    9/09/97 3:39p Sandeep
166  * warning level 4 bugs
167  * 
168  * 10    8/21/97 12:13p Dave
169  * Made it possible for input box to ignore esc to lose focus.
170  * 
171  * 9     8/19/97 1:28p Dave
172  * Made it possible to limit characters by pixel width.
173  * 
174  * 8     8/15/97 8:21p Dave
175  * Modified UI_INPUTBOX so that it is possible to draw it invisibly. That
176  * is, only the text is displayed.
177  * 
178  * 7     8/11/97 9:48p Lawrance
179  * reset clip after drawing
180  * 
181  * 6     7/01/97 2:10p Dave
182  * Fixed bug which caused the box to stop responding after user called
183  * set_text(...) with a 0 length string.
184  * 
185  * 5     6/12/97 12:39p John
186  * made ui use freespace colors
187  * 
188  * 4     6/11/97 1:13p John
189  * Started fixing all the text colors in the game.
190  * 
191  * 3     12/03/96 3:46p Lawrance
192  * added ability to set contents of input box
193  * 
194  * 2     11/15/96 11:43a John
195  * 
196  * 1     11/15/96 8:21a John
197  *
198  * $NoKeywords: $
199  */
200
201 #include <ctype.h>
202 #include "uidefs.h"
203 #include "ui.h"
204 #include "bmpman.h"
205 #include "timer.h"
206 #include "alphacolors.h"
207
208
209 #define INPUTBOX_PASSWD_CHAR        '*'   // the password protected char
210
211 //      Retuen true if c is a letter, else return false.
212 int is_letter(char c)
213 {
214         return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'));
215 }
216
217 // if the passed key is keypad number, return the ascii value, otherwise -1
218 int keypad_to_ascii(int c)
219 {
220         switch(c){
221         case SDLK_KP_0:
222                 return key_to_ascii(SDLK_0);
223                 break;
224         case SDLK_KP_1:
225                 return key_to_ascii(SDLK_1);
226                 break;
227         case SDLK_KP_2:
228                 return key_to_ascii(SDLK_2);
229                 break;
230         case SDLK_KP_3:
231                 return key_to_ascii(SDLK_3);
232                 break;
233         case SDLK_KP_4:
234                 return key_to_ascii(SDLK_4);
235                 break;
236         case SDLK_KP_5:
237                 return key_to_ascii(SDLK_5);
238                 break;
239         case SDLK_KP_6:
240                 return key_to_ascii(SDLK_6);
241                 break;
242         case SDLK_KP_7:
243                 return key_to_ascii(SDLK_7);
244                 break;
245         case SDLK_KP_8:
246                 return key_to_ascii(SDLK_8);
247                 break;
248         case SDLK_KP_9:
249                 return key_to_ascii(SDLK_9);
250                 break;
251         case SDLK_KP_PERIOD:
252                 return key_to_ascii(SDLK_PERIOD);
253                 break;
254         default :
255                 return -1;
256                 break;
257         }
258 }
259
260 // insert character c into string s at position p.
261 void strcins(char *s, int p, char c)
262 {
263         int n;
264         for (n=strlen(s)-p; n>=0; n-- )
265                 *(s+p+n+1) = *(s+p+n);   // Move everything over        
266         *(s+p) = c;         // then insert the character
267 }
268
269 // delete n character from string s starting at position p
270
271 void strndel(char *s, int p, int n)
272 {
273         for (; (*(s+p) = *(s+p+n)) != '\0'; s++ )
274                 *(s+p+n) = '\0';    // Delete and zero fill
275 }
276
277 void UI_INPUTBOX::init_cursor()
278 {
279         cursor_first_frame = bm_load_animation("cursor1", &cursor_nframes, &cursor_fps);
280         if ( cursor_first_frame < 0 ) {
281                 Warning(LOCATION,"Cannot load input box cursor: cursor1.ani\n");
282                 return;
283         }
284         cursor_elapsed_time=0;
285         cursor_current_frame=0;
286 }
287
288 void UI_INPUTBOX::create(UI_WINDOW *wnd, int _x, int _y, int _w, int _text_len, const char *_text, int _flags, int pixel_lim, color *clr)
289 {
290         int tw, th;
291
292         Assert(_text_len >= 0);
293         Assert((int) strlen(_text) <= _text_len);
294         gr_set_font(wnd->f_id);
295         gr_get_string_size( &tw, &th, "*" );
296
297         // check to see if the user passed in a text color otherwise use the default green color
298         if (clr){
299                 text_color = clr;
300         } else {
301                 text_color = &CBRIGHT;
302         }
303
304         base_create( wnd, UI_KIND_INPUTBOX, _x, _y, _w, th+4 );
305         text = (char *) malloc( _text_len + 1);
306
307         // input boxes no longer use background
308         _flags |= UI_INPUTBOX_FLAG_NO_BACK;
309         
310         // if its in "password" mode, allocate a second string
311         // and copy it
312         if (_flags & UI_INPUTBOX_FLAG_PASSWD) {
313                 passwd_text = (char *) malloc(_text_len + 1);
314                 memset(passwd_text, INPUTBOX_PASSWD_CHAR, strlen(_text));
315                 passwd_text[strlen(_text)] = 0;
316
317         } else {
318                 passwd_text = NULL;
319         }
320
321         init_cursor();
322
323         if ( _text_len > 0 ) {
324                 strncpy( text, _text, _text_len );
325         }
326         text[_text_len] = 0;
327         position = strlen(_text);
328         oldposition = position;
329         length = _text_len;
330         pressed_down = 0;
331 //      first_time = 1;
332         changed_flag = 0;
333         flags = _flags;
334         pixel_limit = pixel_lim;
335         locked = 0;
336         valid_chars = NULL;
337         invalid_chars = NULL;
338 };
339
340 void UI_INPUTBOX::set_valid_chars(const char *vchars)
341 {
342         // free up any existing string
343         if(valid_chars != NULL){
344                 free(valid_chars);
345                 valid_chars = NULL;
346         }
347
348         valid_chars = strdup(vchars);
349 }
350
351 void UI_INPUTBOX::set_invalid_chars(const char *ichars)
352 {
353         // free up any existing string
354         if(invalid_chars != NULL){
355                 free(invalid_chars);
356                 invalid_chars = NULL;
357         }
358         
359         invalid_chars = strdup(ichars);
360 }
361
362 void UI_INPUTBOX::destroy()
363 {
364         if (text) {
365                 free(text);
366                 text = NULL;
367         }
368
369         // free any valid chars
370         if(valid_chars != NULL){
371                 free(valid_chars);
372                 valid_chars = NULL;
373         }
374
375         // free any invalid chars
376         if(invalid_chars != NULL){
377                 free(invalid_chars);
378                 invalid_chars = NULL;
379         }
380
381         if ((flags & UI_INPUTBOX_FLAG_PASSWD) && passwd_text) {
382                 free(passwd_text);
383                 passwd_text = NULL;
384         }
385
386         UI_GADGET::destroy();
387 }
388
389 void UI_INPUTBOX::draw()
390 {
391         int invis, w1, h1, tw, th;
392         int text_x, text_y;
393
394         if (hidden){
395                 return;
396         }
397
398         w1 = w;
399         h1 = h;
400         invis = flags & UI_INPUTBOX_FLAG_INVIS;
401
402         gr_set_font(my_wnd->f_id);
403         gr_reset_clip();
404         if (!invis && !(flags & UI_INPUTBOX_FLAG_NO_BACK)) {
405                 // draw the entire text box region
406                 ui_draw_sunken_border( x-2, y-2, x+w+1, y+h+1 );
407                 gr_set_color_fast( &CBLACK );
408                 gr_rect( 0, 0, w, h );
409                 w1 -= 4;
410                 h1 -= 4;
411                 gr_set_clip( x + 1, y + 1, w1 + 1, h1 + 1 );
412         } else {
413                 gr_set_clip( x - 1, y - 1, w1 + 1, h1 + 1 );
414         }
415
416         if (flags & UI_INPUTBOX_FLAG_PASSWD){
417                 gr_get_string_size(&tw, &th, passwd_text);
418         } else {
419                 gr_get_string_size(&tw, &th, text);
420         }
421
422         // If first_time is set, that means this input box got
423         // focus, but nothing is typed yet, so all the text is
424         // selected, if you type a character it will replace the
425         // text, if you type an arrow it will unselect it.
426         // So it needs to be colored differently to show this.
427         if (!disabled_flag && !(flags & UI_INPUTBOX_FLAG_NO_BACK)) {
428 //              if ( (my_wnd->selected_gadget == this) && first_time ) {
429 //                      gr_set_color_fast( text_color );
430
431 //              } else {
432                         gr_set_color_fast( &CBLACK );
433 //              }
434
435                 // color the background behind the text 
436                 gr_rect( 0, 0, tw + 1, th );
437         }
438
439         if      ( (my_wnd->selected_gadget == this) || disabled_flag ) {                
440                 gr_set_color_fast(text_color);
441         } else {
442                 gr_set_color_fast(&CWHITE);
443         }
444
445         // coords of where to draw the text
446         text_x = 1;
447         text_y = 1;
448         if(flags & UI_INPUTBOX_FLAG_TEXT_CEN){
449                 // if we fit within the text area, draw it centered
450                 if(tw <= w1 - 5){
451                         text_x += (w1 - tw)/2;
452                 }               
453         }
454
455         // draw the text
456         if (flags & UI_INPUTBOX_FLAG_PASSWD){
457                 gr_string(text_x, text_y, passwd_text);
458         } else {
459                 gr_string(text_x, text_y, text);
460         }
461
462         // draw the "cursor"
463         if (!disabled_flag) {
464                 if (my_wnd->selected_gadget == this) {
465                         if (cursor_first_frame == -1) {
466                                 gr_set_color_fast(text_color);
467                                 ui_vline(1, h1, text_x + tw + 4);
468                                 ui_vline(1, h1, text_x + tw + 5);
469                         } else {
470                                 // draw animating cursor
471                                 int time_delta = timer_get_milliseconds() - cursor_elapsed_time;
472
473                                 if ( (time_delta / 1000.0f) > (1.0f / cursor_fps) ) {
474                                         // advance frame
475                                         cursor_elapsed_time += time_delta;
476                                         cursor_current_frame++;
477                                         if (cursor_current_frame >= cursor_nframes) {
478                                                 cursor_current_frame = 0;
479                                         }
480                                 }
481
482                                 // draw current frame
483                                 gr_set_bitmap(cursor_first_frame + cursor_current_frame, GR_ALPHABLEND_NONE, GR_BITBLT_MODE_NORMAL, 1.0f, -1, -1);
484                                 gr_bitmap(text_x + tw + 4, 1);
485                         }
486                 }
487         }
488
489         gr_reset_clip();
490 }
491
492 int UI_INPUTBOX::validate_input(int chr)
493 {
494         if (chr < 32) {  // weed out control characters
495                 return 0;
496         }
497
498         // if we're disallowing letters altogether
499         if((flags & UI_INPUTBOX_FLAG_NO_LETTERS) && isalpha(chr)){
500                 return 0;
501         }
502
503         // if we're disallowing numbers altogether
504         if((flags & UI_INPUTBOX_FLAG_NO_NUMERALS) && isdigit(chr)){
505                 return 0;
506         }
507
508         // otherwise allow numbers and alpha chars by
509         if (isdigit(chr) || isalpha(chr)){
510                 return chr;
511         }
512
513         // if we have specified no valid or invalid chars, accept everything
514         if(!valid_chars && !invalid_chars){
515                 return chr;
516         }
517
518         // otherwise compare against the valid chars list
519         if((valid_chars) && strchr(valid_chars, chr)){
520                 return chr;
521         }
522
523         // otherwise compare against the invalid chars list0
524         if((invalid_chars) && !strchr(invalid_chars,chr)){
525                 return chr;
526         }
527
528         return 0;
529 }
530
531 void UI_INPUTBOX::process(int focus)
532 {
533         int ascii, clear_lastkey, key, key_used, key_check;     
534
535         // check if mouse is pressed
536         if (B1_PRESSED && is_mouse_on()) {
537                 set_focus();
538 //              first_time = 1;
539         }
540
541         if (disabled_flag)
542                 return;
543
544         if (my_wnd->selected_gadget == this)
545                 focus = 1;
546 //      else
547 //              first_time = 0;
548
549         key_used = 0;
550         changed_flag = 0;
551         oldposition = position;
552         pressed_down = 0;
553         clear_lastkey = (flags & UI_INPUTBOX_FLAG_KEYTHRU) ? 0 : 1;
554
555         if (focus) {
556                 key = my_wnd->keypress;
557                 switch (key) {
558                         case 0:
559                                 break;
560
561                         //case SDLK_LEFT:
562                         case SDLK_BACKSPACE:
563                                 if (position > 0)
564                                         position--;
565
566                                 text[position] = 0;
567                                 if (flags & UI_INPUTBOX_FLAG_PASSWD) {
568                                         passwd_text[position] = 0;
569                                 }
570
571                                 changed_flag = 1;
572                                 key_used = 1;
573 //                              if (first_time)
574 //                                      first_time = 0;
575
576                                 break;
577
578                         case SDLK_RETURN:
579                                 pressed_down = 1;
580                                 locked = 0;
581                                 changed_flag = 1;
582                                 key_used = 1;
583 //                              if (first_time)
584 //                                      first_time = 0;
585
586 //                              should_reset = 1;
587                                 break;
588
589                         case SDLK_ESCAPE:
590                                 if (flags & UI_INPUTBOX_FLAG_ESC_CLR){
591                                         if (position > 0) {
592                                                 set_text("");
593                                                 key_used = 1;                                           
594
595                                         } else {
596                                                 key_used = 0;
597                                                 clear_lastkey = 0;
598                                         }
599                                 }
600
601                                 if (flags & UI_INPUTBOX_FLAG_ESC_FOC) {
602                                         clear_focus();
603                                 }
604
605                                 break;
606
607                         default:
608                                 if (!locked) {
609                                         // MWA -- determine if alt or ctrl held down on this key and don't process if it is.  We
610                                         // need to be able to pass these keys back to the top level.  (And anyway -- ctrl-a shouldn't
611                                         // print out an A in the input window
612                                         if ( key & (KEY_ALTED | KEY_CTRLED) ) {
613                                                 clear_lastkey = 0;
614                                                 break;
615                                         }
616
617                                         // get an ascii char from the input if possible
618                                         key_check = keypad_to_ascii(key);
619                                         if(key_check == -1){
620                                                 key_check = key_to_ascii(key);
621                                         }
622
623                                         ascii = validate_input(key_check);
624                                         if ((ascii > 0) && (ascii < 255)) {
625                                                 if (flags & UI_INPUTBOX_FLAG_LETTER_FIRST) {
626                                                         if ((position == 0) && !is_letter((char) ascii))
627                                                                 break;
628                                                 }
629
630                                                 key_used = 1;
631 //                                              if (should_reset) {
632 //                                                      should_reset = 0;
633 //                                                      position = 0;
634 //                                              }
635
636 //                                              if (first_time) {
637 //                                                      first_time = 0;
638 //                                                      position = 0;
639 //                                              }
640
641                                                 if ( position < length ) {
642                                                         text[position] = (char) ascii;
643                                                         text[position + 1] = 0;
644
645                                                         if (flags & UI_INPUTBOX_FLAG_PASSWD) {
646                                                                 passwd_text[position] = (char) INPUTBOX_PASSWD_CHAR;
647                                                                 passwd_text[position + 1] = 0;
648                                                         }
649
650                                                         position++;
651
652                                                         // check to see if we should limit by pixel width
653                                                         if (pixel_limit > -1) {
654                                                                 int w;
655
656                                                                 if (flags & UI_INPUTBOX_FLAG_PASSWD) {
657                                                                         gr_get_string_size(&w, NULL, passwd_text);                                                                      
658
659                                                                 } else {
660                                                                         gr_get_string_size(&w, NULL, text);                                                             
661                                                                 }
662
663                                                                 if (w > pixel_limit) {
664                                                                         position--;
665                                                                         locked = 1;
666                                                                         text[position] = 0;
667
668                                                                         if (flags & UI_INPUTBOX_FLAG_PASSWD) {
669                                                                                 passwd_text[position] = 0;
670                                                                         }
671                                                                 }
672                                                         }
673                                                 }
674
675                                                 changed_flag = 1;
676                                         }
677                                 }
678
679                                 break;
680                 }
681
682                 if (clear_lastkey || (key_used && (flags & UI_INPUTBOX_FLAG_EAT_USED)) )
683                         my_wnd->last_keypress=0;
684
685 //      } else {
686 //              first_time = 1;
687         }       
688 }
689
690 int UI_INPUTBOX::changed()
691 {               
692         return changed_flag;
693 }
694
695 int UI_INPUTBOX::pressed()
696 {       
697         return pressed_down;
698 }
699
700 void UI_INPUTBOX::get_text(char *out)
701 {
702         strncpy(out, text, length);
703         out[length] = 0;
704 }
705
706 void UI_INPUTBOX::set_text(const char *in)
707 {
708         int in_length;
709         
710         in_length = strlen(in);
711         if (in_length > length)
712                 Assert(0);      // tried to force text into an input box that won't fit into allocated memory
713
714         strcpy(text, in);
715         
716         if (flags & UI_INPUTBOX_FLAG_PASSWD) {
717                 memset(passwd_text, INPUTBOX_PASSWD_CHAR, strlen(text));
718                 passwd_text[strlen(text)] = 0;
719         }
720
721         position = in_length;  // fixes the zero-length-I-don't-think-so bug
722 }
723
724