]> icculus.org git repositories - btb/d2x.git/blob - main/console.c
move ins/del logic to cli_add_character
[btb/d2x.git] / main / console.c
1 /*
2  *  Code for controlling the console
3  *  Based on an early version of SDL_Console
4  *
5  *  Written By: Garrett Banuk <mongoose@mongeese.org>
6  *  Code Cleanup and heavily extended by: Clemens Wacha <reflex-2000@gmx.net>
7  *  Ported to use native Descent interfaces by: Bradley Bell <btb@icculus.org>
8  *
9  *  This is free, just be sure to give us credit when using it
10  *  in any of your programs.
11  */
12
13 #ifdef HAVE_CONFIG_H
14 #include <conf.h>
15 #endif
16
17 #include <stdlib.h>
18 #include <stdio.h>
19 #include <string.h>
20 #include <stdarg.h>
21 #ifndef _WIN32_WCE
22 #include <fcntl.h>
23 #endif
24 #include <ctype.h>
25 #include <unistd.h>
26
27 #include "inferno.h"
28 #include "u_mem.h"
29 #include "gr.h"
30 #include "key.h"
31 #include "timer.h"
32 #include "pstypes.h"
33 #include "error.h"
34 #include "cfile.h"
35
36
37 #ifndef __MSDOS__
38 int text_console_enabled = 1;
39 #else
40 int isvga();
41 #define text_console_enabled (!isvga())
42 #endif
43
44
45 /* How discriminating we are about which messages are displayed */
46 cvar_t con_threshold = {"con_threshold", "0",};
47
48 #define CON_BG_HIRES (cfexist("scoresb.pcx")?"scoresb.pcx":"scores.pcx")
49 #define CON_BG_LORES (cfexist("scores.pcx")?"scores.pcx":"scoresb.pcx") // Mac datafiles only have scoresb.pcx
50 #define CON_BG ((SWIDTH>=640)?CON_BG_HIRES:CON_BG_LORES)
51
52 #define CON_NUM_LINES           128
53 // Cut the buffer line if it becomes longer than this
54 #define CON_CHARS_PER_LINE      128
55 // Border in pixels from the most left to the first letter
56 #define CON_CHAR_BORDER         4
57 // Spacing in pixels between lines
58 #define CON_LINE_SPACE          1
59 // Scroll this many lines at a time (when pressing PGUP or PGDOWN)
60 #define CON_LINE_SCROLL         2
61 // Indicator showing that you scrolled up the history
62 #define CON_SCROLL_INDICATOR    "^"
63 // Defines the default hide key (Hide() the console if pressed)
64 #define CON_DEFAULT_HIDEKEY     KEY_ESC
65 // Defines the opening/closing speed
66 #define CON_OPENCLOSE_SPEED 50
67
68
69 /* The console's data */
70 static int Visible;             // Enum that tells which visible state we are in CON_HIDE, CON_SHOW, CON_RAISE, CON_LOWER
71 static int RaiseOffset;         // Offset used when scrolling in the console
72 static int HideKey;             // The key that can hide the console
73 static char **ConsoleLines;     // List of all the past lines
74 static int TotalConsoleLines;   // Total number of lines in the console
75 static int ConsoleScrollBack;   // How much the user scrolled back in the console
76 static int LineBuffer;          // The number of visible lines in the console (autocalculated)
77 static int VChars;              // The number of visible characters in one console line (autocalculated)
78 static grs_canvas *ConsoleSurface;  // Canvas of the console
79 static grs_bitmap *BackgroundImage; // Background image for the console
80 static grs_bitmap *InputBackground; // Dirty rectangle to draw over behind the users background
81
82 /* console is ready to be written to */
83 static int con_initialized;
84
85
86 /* Internals */
87 static void con_update_offset(void);
88 /* Frees all the memory loaded by the console */
89 static void con_free(void);
90 static int con_background(grs_bitmap *image);
91 /* Sets font info for the console */
92 static void con_font(grs_font *font, int fg, int bg);
93 /* Set the key, that invokes a CON_Hide() after press. default is ESCAPE and you can always hide using
94  ESCAPE and the HideKey. compared against event->key.keysym.sym !! */
95 static void con_set_hide_key(int key);
96 /* makes newline (same as printf("\n") or CON_Out("\n") ) */
97 static void con_newline(void);
98 /* updates console after resize etc. */
99 static void con_update(void);
100 /* Called if you press Ctrl-L (deletes the History) */
101 static void con_clear(void);
102
103
104 /* Takes keys from the keyboard and inputs them to the console
105  * If the event was not handled (i.e. WM events or unknown ctrl-shift
106  * sequences) the function returns the event for further processing. */
107 int con_key_handler(int key)
108 {
109         if (!con_is_visible())
110                 return key;
111
112         if (key & KEY_CTRLED)
113         {
114                 // CTRL pressed
115                 switch (key & ~KEY_CTRLED)
116                 {
117                         case KEY_A:
118                                 cli_cursor_home();
119                                 break;
120                         case KEY_E:
121                                 cli_cursor_end();
122                                 break;
123                         case KEY_C:
124                                 cli_clear();
125                                 break;
126                         case KEY_L:
127                                 con_clear();
128                                 con_update();
129                                 break;
130                         default:
131                                 return key;
132                 }
133         }
134         else if (key & KEY_ALTED)
135         {
136                 // the console does not handle ALT combinations!
137                 return key;
138         }
139         else
140         {
141                 // first of all, check if the console hide key was pressed
142                 if (key == HideKey)
143                 {
144                         con_hide();
145                         return 0;
146                 }
147                 switch (key & 0xff)
148                 {
149                         case KEY_LSHIFT:
150                         case KEY_RSHIFT:
151                                 return key;
152                         case KEY_HOME:
153                                 if(key & KEY_SHIFTED)
154                                 {
155                                         ConsoleScrollBack = LineBuffer-1;
156                                         con_update();
157                                 } else {
158                                         cli_cursor_home();
159                                 }
160                                 break;
161                         case KEY_END:
162                                 if(key & KEY_SHIFTED)
163                                 {
164                                         ConsoleScrollBack = 0;
165                                         con_update();
166                                 } else {
167                                         cli_cursor_end();
168                                 }
169                                 break;
170                         case KEY_PAGEUP:
171                                 ConsoleScrollBack += CON_LINE_SCROLL;
172                                 if(ConsoleScrollBack > LineBuffer-1)
173                                         ConsoleScrollBack = LineBuffer-1;
174                                 con_update();
175                                 break;
176                         case KEY_PAGEDOWN:
177                                 ConsoleScrollBack -= CON_LINE_SCROLL;
178                                 if(ConsoleScrollBack < 0)
179                                         ConsoleScrollBack = 0;
180                                 con_update();
181                                 break;
182                         case KEY_UP:
183                                 cli_history_prev();
184                                 break;
185                         case KEY_DOWN:
186                                 cli_history_next();
187                                 break;
188                         case KEY_LEFT:
189                                 cli_cursor_left();
190                                 break;
191                         case KEY_RIGHT:
192                                 cli_cursor_right();
193                                 break;
194                         case KEY_BACKSP:
195                                 cli_cursor_backspace();
196                                 break;
197                         case KEY_DELETE:
198                                 cli_cursor_del();
199                                 break;
200                         case KEY_INSERT:
201                                 CLI_insert_mode = !CLI_insert_mode;
202                                 break;
203                         case KEY_TAB:
204                                 cli_autocomplete();
205                                 break;
206                         case KEY_ENTER:
207                                 cli_execute();
208                                 break;
209                         case KEY_LAPOSTRO:
210                                 // deactivate Console
211                                 con_hide();
212                                 return 0;
213                         default:
214                         {
215                                 unsigned char character = key_to_ascii(key);
216
217                                 if (character == 255)
218                                         break;
219                                 cli_add_character(character);
220                         }
221                 }
222         }
223         return 0;
224 }
225
226
227 /* Updates the console buffer */
228 void con_update(void)
229 {
230         int loop;
231         int loop2;
232         int Screenlines;
233         grs_canvas *canv_save;
234
235         /* Due to the Blits, the update is not very fast: So only update if it's worth it */
236         if (!con_is_visible())
237                 return;
238
239         Screenlines = ConsoleSurface->cv_h / (CON_LINE_SPACE + ConsoleSurface->cv_font->ft_h);
240
241         canv_save = grd_curcanv;
242         gr_set_current_canvas(ConsoleSurface);
243
244         /* draw the background image if there is one */
245         if (BackgroundImage)
246                 gr_bitmap(0, 0, BackgroundImage);
247
248         // now draw text from last but second line to top
249         for (loop = 0; loop < Screenlines-1 && loop < LineBuffer - ConsoleScrollBack; loop++) {
250                 if (ConsoleScrollBack != 0 && loop == 0)
251                         for (loop2 = 0; loop2 < (VChars / 5) + 1; loop2++)
252                         {
253                                 gr_string(CON_CHAR_BORDER + (loop2*5*ConsoleSurface->cv_font->ft_w), (Screenlines - loop - 2) * (CON_LINE_SPACE + ConsoleSurface->cv_font->ft_h), CON_SCROLL_INDICATOR);
254                         }
255                 else
256                 {
257                         gr_string(CON_CHAR_BORDER, (Screenlines - loop - 2) * (CON_LINE_SPACE + ConsoleSurface->cv_font->ft_h), ConsoleLines[ConsoleScrollBack + loop]);
258                 }
259         }
260
261         gr_set_current_canvas(canv_save);
262 }
263
264
265 void con_update_offset(void)
266 {
267         switch (Visible) {
268                 case CON_CLOSING:
269                         RaiseOffset -= CON_OPENCLOSE_SPEED;
270                         if(RaiseOffset <= 0) {
271                                 RaiseOffset = 0;
272                                 Visible = CON_CLOSED;
273                         }
274                         break;
275                 case CON_OPENING:
276                         RaiseOffset += CON_OPENCLOSE_SPEED;
277                         if(RaiseOffset >= ConsoleSurface->cv_h) {
278                                 RaiseOffset = ConsoleSurface->cv_h;
279                                 Visible = CON_OPEN;
280                         }
281                         break;
282                 case CON_OPEN:
283                 case CON_CLOSED:
284                         break;
285         }
286 }
287
288
289 /* Draws the console buffer to the screen if the console is "visible" */
290 void con_draw(void)
291 {
292         grs_canvas *canv_save;
293         grs_bitmap *clip;
294
295         /* only draw if console is visible: here this means, that the console is not CON_CLOSED */
296         if (Visible == CON_CLOSED)
297                 return;
298
299         /* Update the scrolling offset */
300         con_update_offset();
301
302         canv_save = grd_curcanv;
303
304         /* Update the command line since it has a blinking cursor */
305         gr_set_current_canvas(ConsoleSurface);
306
307         // restore InputBackground
308         gr_bitmap(0, ConsoleSurface->cv_h - ConsoleSurface->cv_font->ft_h, InputBackground);
309
310         cli_draw(ConsoleSurface->cv_h);
311
312         gr_set_current_canvas(&grd_curscreen->sc_canvas);
313
314         clip = gr_create_sub_bitmap(&ConsoleSurface->cv_bitmap, 0, ConsoleSurface->cv_h - RaiseOffset, ConsoleSurface->cv_w, RaiseOffset);
315
316         gr_bitmap(0, 0, clip);
317         gr_free_sub_bitmap(clip);
318
319         gr_set_current_canvas(canv_save);
320 }
321
322
323 /* Initializes the console */
324 void con_init()
325 {
326         int loop;
327
328         Visible = CON_CLOSED;
329         RaiseOffset = 0;
330         ConsoleLines = NULL;
331         TotalConsoleLines = 0;
332         ConsoleScrollBack = 0;
333         BackgroundImage = NULL;
334         HideKey = CON_DEFAULT_HIDEKEY;
335
336         /* load the console surface */
337         ConsoleSurface = NULL;
338
339         /* Load the dirty rectangle for user input */
340         InputBackground = NULL;
341
342         VChars = CON_CHARS_PER_LINE - 1;
343         LineBuffer = CON_NUM_LINES;
344
345         ConsoleLines = (char **)d_malloc(sizeof(char *) * LineBuffer);
346         for (loop = 0; loop <= LineBuffer - 1; loop++) {
347                 ConsoleLines[loop] = (char *)d_calloc(CON_CHARS_PER_LINE, sizeof(char));
348         }
349
350         cli_init();
351         cmd_init();
352
353         /* Initialise the cvars */
354         cvar_registervariable (&con_threshold);
355
356         con_initialized = 1;
357
358         atexit(con_free);
359 }
360
361
362 void gr_init_bitmap_alloc( grs_bitmap *bm, int mode, int x, int y, int w, int h, int bytesperline);
363 void con_init_gfx(int w, int h)
364 {
365         int pcx_error;
366         grs_bitmap bmp;
367         ubyte pal[256*3];
368
369         if (ConsoleSurface) {
370                 /* resize console surface */
371                 gr_free_bitmap_data(&ConsoleSurface->cv_bitmap);
372                 gr_init_bitmap_alloc(&ConsoleSurface->cv_bitmap, BM_LINEAR, 0, 0, w, h, w);
373         } else {
374                 /* load the console surface */
375                 ConsoleSurface = gr_create_canvas(w, h);
376         }
377
378         /* Load the consoles font */
379         con_font(SMALL_FONT, gr_find_closest_color(29,29,47), -1);
380
381         /* make sure that the size of the console is valid */
382         if (w > grd_curscreen->sc_w || w < ConsoleSurface->cv_font->ft_w * 32)
383                 w = grd_curscreen->sc_w;
384         if (h > grd_curscreen->sc_h || h < ConsoleSurface->cv_font->ft_h)
385                 h = grd_curscreen->sc_h;
386
387         /* Load the dirty rectangle for user input */
388         if (InputBackground)
389                 gr_free_bitmap(InputBackground);
390         InputBackground = gr_create_bitmap(w, ConsoleSurface->cv_font->ft_h);
391
392         /* calculate the number of visible characters in the command line */
393 #if 0 // doesn't work because proportional font
394         VChars = (w - CON_CHAR_BORDER) / ConsoleSurface->cv_font->ft_w;
395         if (VChars >= CON_CHARS_PER_LINE)
396                 VChars = CON_CHARS_PER_LINE - 1;
397 #endif
398
399         gr_init_bitmap_data(&bmp);
400         pcx_error = pcx_read_bitmap(CON_BG, &bmp, BM_LINEAR, pal);
401         Assert(pcx_error == PCX_ERROR_NONE);
402         gr_remap_bitmap_good(&bmp, pal, -1, -1);
403         con_background(&bmp);
404         gr_free_bitmap_data(&bmp);
405 }
406
407
408 /* Makes the console visible */
409 void con_show(void)
410 {
411         Visible = CON_OPENING;
412         con_update();
413 }
414
415
416 /* Hides the console (make it invisible) */
417 void con_hide(void)
418 {
419         Visible = CON_CLOSING;
420         key_flush();
421 }
422
423
424 /* tells wether the console is visible or not */
425 int con_is_visible(void)
426 {
427         return((Visible == CON_OPEN) || (Visible == CON_OPENING));
428 }
429
430
431 /* Frees all the memory loaded by the console */
432 static void con_free(void)
433 {
434         int i;
435
436         for (i = 0; i <= LineBuffer - 1; i++) {
437                 d_free(ConsoleLines[i]);
438         }
439         d_free(ConsoleLines);
440
441         ConsoleLines = NULL;
442
443         if (ConsoleSurface)
444                 gr_free_canvas(ConsoleSurface);
445         ConsoleSurface = NULL;
446
447         if (BackgroundImage)
448                 gr_free_bitmap(BackgroundImage);
449         BackgroundImage = NULL;
450
451         if (InputBackground)
452                 gr_free_bitmap(InputBackground);
453         InputBackground = NULL;
454
455         con_initialized = 0;
456 }
457
458
459 /* Increments the console lines */
460 void con_newline(void)
461 {
462         int loop;
463         char *temp;
464
465         temp = ConsoleLines[LineBuffer - 1];
466
467         for (loop = LineBuffer - 1; loop > 0; loop--)
468                 ConsoleLines[loop] = ConsoleLines[loop - 1];
469
470         ConsoleLines[0] = temp;
471
472         memset(ConsoleLines[0], 0, CON_CHARS_PER_LINE);
473         if (TotalConsoleLines < LineBuffer - 1)
474                 TotalConsoleLines++;
475         
476         //Now adjust the ConsoleScrollBack
477         //dont scroll if not at bottom
478         if(ConsoleScrollBack != 0)
479                 ConsoleScrollBack++;
480         //boundaries
481         if(ConsoleScrollBack > LineBuffer-1)
482                 ConsoleScrollBack = LineBuffer-1;
483 }
484
485
486 static inline int con_get_width(void)
487 {
488         if (!ConsoleSurface)
489                 return 0;
490
491         return ConsoleSurface->cv_bitmap.bm_w - CON_CHAR_BORDER;
492 }
493
494
495 static inline int con_get_string_width(char *string)
496 {
497         grs_canvas *canv_save;
498         int w = 0, h, aw;
499
500         if (!ConsoleSurface)
501                 return 0;
502
503         canv_save = grd_curcanv;
504         gr_set_current_canvas(ConsoleSurface);
505         gr_get_string_size(string, &w, &h, &aw);
506         gr_set_current_canvas(canv_save);
507
508         return w;
509 }
510
511
512 #ifdef _MSC_VER
513 # define vsnprintf _vsnprintf
514 #endif
515
516 /* Outputs text to the console (in game), up to CON_CHARS_PER_LINE chars can be entered */
517 void con_out(const char *str, ...)
518 {
519         va_list marker;
520         //keep some space free for stuff like CON_Out("blablabla %s", Command);
521         char temp[CON_CHARS_PER_LINE + 128];
522         char* ptemp;
523
524         va_start(marker, str);
525         vsnprintf(temp, CON_CHARS_PER_LINE + 127, str, marker);
526         va_end(marker);
527
528         ptemp = temp;
529
530         // temp now contains the complete string we want to output
531         // the only problem is that temp is maybe longer than the console
532         // width so we have to cut it into several pieces
533
534         if (ConsoleLines) {
535                 char *p = ptemp;
536
537                 while (*p) {
538                         if (*p == '\n') {
539                                 *p = '\0';
540                                 con_newline();
541                                 strcat(ConsoleLines[0], ptemp);
542                                 ptemp = p+1;
543                         } else if (p - ptemp > VChars - strlen(ConsoleLines[0]) ||
544                                            con_get_string_width(ptemp) > con_get_width()) {
545                                 con_newline();
546                                 strncat(ConsoleLines[0], ptemp, VChars - strlen(ConsoleLines[0]));
547                                 ConsoleLines[0][VChars] = '\0';
548                                 ptemp = p;
549                         }
550                         p++;
551                 }
552                 if (strlen(ptemp)) {
553                         strncat(ConsoleLines[0], ptemp, VChars - strlen(ConsoleLines[0]));
554                         ConsoleLines[0][VChars] = '\0';
555                 }
556                 con_update();
557         }
558 }
559
560
561 /* Adds background image to the console, scaled to size of console*/
562 int con_background(grs_bitmap *image)
563 {
564         /* Free the background from the console */
565         if (image == NULL) {
566                 if (BackgroundImage)
567                         gr_free_bitmap(BackgroundImage);
568                 BackgroundImage = NULL;
569                 return 0;
570         }
571
572         /* Load a new background */
573         if (BackgroundImage)
574                 gr_free_bitmap(BackgroundImage);
575         BackgroundImage = gr_create_bitmap(ConsoleSurface->cv_w, ConsoleSurface->cv_h);
576         gr_bitmap_scale_to(image, BackgroundImage);
577
578         gr_bm_bitblt(BackgroundImage->bm_w, InputBackground->bm_h, 0, 0, 0, ConsoleSurface->cv_h - ConsoleSurface->cv_font->ft_h, BackgroundImage, InputBackground);
579
580         return 0;
581 }
582
583
584 /* Sets font info for the console */
585 void con_font(grs_font *font, int fg, int bg)
586 {
587         grs_canvas *canv_save;
588
589         canv_save = grd_curcanv;
590         gr_set_current_canvas(ConsoleSurface);
591         gr_set_curfont(font);
592         gr_set_fontcolor(fg, bg);
593         gr_set_current_canvas(canv_save);
594 }
595
596
597 /* resizes the console, has to reset alot of stuff
598  * returns 1 on error */
599 void con_resize(int w, int h)
600 {
601         /* make sure that the size of the console is valid */
602         if(w > grd_curscreen->sc_w || w < ConsoleSurface->cv_font->ft_w * 32)
603                 w = grd_curscreen->sc_w;
604         if(h > grd_curscreen->sc_h || h < ConsoleSurface->cv_font->ft_h)
605                 h = grd_curscreen->sc_h;
606
607         /* resize console surface */
608         gr_free_bitmap_data(&ConsoleSurface->cv_bitmap);
609         gr_init_bitmap_alloc(&ConsoleSurface->cv_bitmap, BM_LINEAR, 0, 0, w, h, w);
610
611         /* Load the dirty rectangle for user input */
612         gr_free_bitmap(InputBackground);
613         InputBackground = gr_create_bitmap(w, ConsoleSurface->cv_font->ft_h);
614
615         /* Now reset some stuff dependent on the previous size */
616         ConsoleScrollBack = 0;
617
618         /* Reload the background image (for the input text area) in the console */
619         if (BackgroundImage) {
620                 gr_bm_bitblt(BackgroundImage->bm_w, InputBackground->bm_h, 0, 0, 0, ConsoleSurface->cv_h - ConsoleSurface->cv_font->ft_h, BackgroundImage, InputBackground);
621         }
622 }
623
624
625 /* Sets the key that deactivates (hides) the console. */
626 void con_set_hide_key(int key)
627 {
628         HideKey = key;
629 }
630
631
632 void con_clear(void)
633 {
634         int loop;
635         
636         for (loop = 0; loop <= LineBuffer - 1; loop++)
637                 memset(ConsoleLines[loop], 0, CON_CHARS_PER_LINE);
638 }
639
640
641 /* convert to ansi rgb colors 17-231 */
642 #define PAL2ANSI(x) ((36*gr_palette[(x)*3]/11) + (6*gr_palette[(x)*3+1]/11) + (gr_palette[(x)*3+2]/11) + 16)
643
644 /* Print a message to the console */
645 void con_printf(int priority, char *fmt, ...)
646 {
647         va_list arglist;
648         char buffer[2048];
649
650         if (priority <= (con_threshold.intval))
651         {
652                 va_start (arglist, fmt);
653                 vsprintf (buffer,  fmt, arglist);
654                 va_end (arglist);
655
656                 if (con_initialized)
657                         con_out(buffer);
658
659                 if (!text_console_enabled)
660                         return;
661
662                 if (isatty(fileno(stdout))) {
663                         char *buf, *p;
664                         unsigned char color, spacing, underline;
665
666                         p = buf = buffer;
667                         do
668                                 switch (*p)
669                                 {
670                                 case CC_COLOR:
671                                         *p++ = 0;
672                                         printf("%s", buf);
673                                         color = *p++;
674                                         printf("\x1B[38;5;%dm", PAL2ANSI(color));
675                                         buf = p;
676                                         break;
677                                 case CC_LSPACING:
678                                         *p++ = 0;
679                                         printf("%s", buf);
680                                         spacing = *p++;
681                                         //printf("<SPACING %d>", color);
682                                         buf = p;
683                                         break;
684                                 case CC_UNDERLINE:
685                                         *p++ = 0;
686                                         printf("%s", buf);
687                                         underline = 1;
688                                         //printf("<UNDERLINE>");
689                                         buf = p;
690                                         break;
691                                 default:
692                                         p++;
693                                 }
694                         while (*p);
695
696                         printf("%s", buf);
697
698                 } else {
699                         /* Produce a sanitised version and send it to the standard output */
700                         char *p1, *p2;
701
702                         p1 = p2 = buffer;
703                         do
704                                 switch (*p1)
705                                 {
706                                 case CC_COLOR:
707                                 case CC_LSPACING:
708                                         p1++;
709                                 case CC_UNDERLINE:
710                                         p1++;
711                                         break;
712                                 default:
713                                         *p2++ = *p1++;
714                                 }
715                         while (*p1);
716                         *p2 = 0;
717
718                         printf("%s", buffer);
719                 }
720         }
721 }