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