]> icculus.org git repositories - btb/d2x.git/blob - main/console.c
Replace cvar.archive with flags.
[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", CVAR_NONE };
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 opening/closing speed
64 #define CON_OPENCLOSE_SPEED 50
65
66
67 /* The console's data */
68 static int Visible;             // Enum that tells which visible state we are in CON_HIDE, CON_SHOW, CON_RAISE, CON_LOWER
69 static int RaiseOffset;         // Offset used when scrolling in the console
70 static char **ConsoleLines;     // List of all the past lines
71 static int TotalConsoleLines;   // Total number of lines in the console
72 static int ConsoleScrollBack;   // How much the user scrolled back in the console
73 static int LineBuffer;          // The number of visible lines in the console (autocalculated)
74 static int VChars;              // The number of visible characters in one console line (autocalculated)
75 static grs_canvas *ConsoleSurface;  // Canvas of the console
76 static grs_bitmap *BackgroundImage; // Background image for the console
77 static grs_bitmap *InputBackground; // Dirty rectangle to draw over behind the users background
78
79 /* console is ready to be written to */
80 static int con_initialized;
81
82
83 /* Internals */
84 static void con_update_offset(void);
85 /* Frees all the memory loaded by the console */
86 static void con_free(void);
87 static int con_background(grs_bitmap *image);
88 /* Sets font info for the console */
89 static void con_font(grs_font *font, int fg, int bg);
90 /* makes newline (same as printf("\n") or CON_Out("\n") ) */
91 static void con_newline(void);
92 /* updates console after resize etc. */
93 static void con_update(void);
94 /* Called if you press Ctrl-L (deletes the History) */
95 static void con_clear(void);
96
97
98 /* Takes keys from the keyboard and inputs them to the console
99  * If the event was not handled (i.e. WM events or unknown ctrl-shift
100  * sequences) the function returns the event for further processing. */
101 int con_key_handler(int key)
102 {
103         unsigned char character = key_to_ascii(key);
104
105         if (!con_is_visible())
106                 return key;
107
108         switch (key) {
109                 case KEY_LAPOSTRO:
110                 case KEY_ESC:
111                 case KEY_SHIFTED + KEY_ESC:
112                         con_hide();
113                         break;
114                 case KEY_CTRLED + KEY_L:
115                         con_clear();
116                         con_update();
117                         break;
118                 case KEY_SHIFTED + KEY_HOME:
119                         ConsoleScrollBack = LineBuffer-1;
120                         con_update();
121                         break;
122                 case KEY_SHIFTED + KEY_END:
123                         ConsoleScrollBack = 0;
124                         con_update();
125                         break;
126                 case KEY_PAGEUP:
127                         ConsoleScrollBack += CON_LINE_SCROLL;
128                         if(ConsoleScrollBack > LineBuffer-1)
129                                 ConsoleScrollBack = LineBuffer-1;
130                         con_update();
131                         break;
132                 case KEY_PAGEDOWN:
133                         ConsoleScrollBack -= CON_LINE_SCROLL;
134                         if(ConsoleScrollBack < 0)
135                                 ConsoleScrollBack = 0;
136                         con_update();
137                         break;
138                 case KEY_CTRLED + KEY_A:
139                 case KEY_HOME:              cli_cursor_home();      break;
140                 case KEY_END:
141                 case KEY_CTRLED + KEY_E:    cli_cursor_end();       break;
142                 case KEY_CTRLED + KEY_C:    cli_clear();            break;
143                 case KEY_LEFT:              cli_cursor_left();      break;
144                 case KEY_RIGHT:             cli_cursor_right();     break;
145                 case KEY_BACKSP:            cli_cursor_backspace(); break;
146                 case KEY_CTRLED + KEY_D:
147                 case KEY_DELETE:            cli_cursor_del();       break;
148                 case KEY_UP:                cli_history_prev();     break;
149                 case KEY_DOWN:              cli_history_next();     break;
150                 case KEY_TAB:               cli_autocomplete();     break;
151                 case KEY_ENTER:             cli_execute();          break;
152                 case KEY_INSERT:
153                         CLI_insert_mode = !CLI_insert_mode;
154                         break;
155                 default:
156                         if (character == 255)
157                                 break;
158                         cli_add_character(character);
159                         break;
160         }
161         return 0;
162 }
163
164
165 /* Updates the console buffer */
166 static void con_update(void)
167 {
168         int loop;
169         int loop2;
170         int Screenlines;
171         grs_canvas *canv_save;
172
173         /* Due to the Blits, the update is not very fast: So only update if it's worth it */
174         if (!con_is_visible())
175                 return;
176
177         Screenlines = ConsoleSurface->cv_h / (CON_LINE_SPACE + ConsoleSurface->cv_font->ft_h);
178
179         canv_save = grd_curcanv;
180         gr_set_current_canvas(ConsoleSurface);
181
182         /* draw the background image if there is one */
183         if (BackgroundImage)
184                 gr_bitmap(0, 0, BackgroundImage);
185
186         // now draw text from last but second line to top
187         for (loop = 0; loop < Screenlines-1 && loop < LineBuffer - ConsoleScrollBack; loop++) {
188                 if (ConsoleScrollBack != 0 && loop == 0)
189                         for (loop2 = 0; loop2 < (VChars / 5) + 1; loop2++)
190                         {
191                                 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);
192                         }
193                 else
194                 {
195                         gr_string(CON_CHAR_BORDER, (Screenlines - loop - 2) * (CON_LINE_SPACE + ConsoleSurface->cv_font->ft_h), ConsoleLines[ConsoleScrollBack + loop]);
196                 }
197         }
198
199         gr_set_current_canvas(canv_save);
200 }
201
202
203 static void con_update_offset(void)
204 {
205         switch (Visible) {
206                 case CON_CLOSING:
207                         RaiseOffset -= CON_OPENCLOSE_SPEED;
208                         if(RaiseOffset <= 0) {
209                                 RaiseOffset = 0;
210                                 Visible = CON_CLOSED;
211                         }
212                         break;
213                 case CON_OPENING:
214                         RaiseOffset += CON_OPENCLOSE_SPEED;
215                         if(RaiseOffset >= ConsoleSurface->cv_h) {
216                                 RaiseOffset = ConsoleSurface->cv_h;
217                                 Visible = CON_OPEN;
218                         }
219                         break;
220                 case CON_OPEN:
221                 case CON_CLOSED:
222                         break;
223         }
224 }
225
226
227 /* Draws the console buffer to the screen if the console is "visible" */
228 void con_draw(void)
229 {
230         grs_canvas *canv_save;
231         grs_bitmap *clip;
232
233         /* only draw if console is visible: here this means, that the console is not CON_CLOSED */
234         if (Visible == CON_CLOSED)
235                 return;
236
237         /* Update the scrolling offset */
238         con_update_offset();
239
240         canv_save = grd_curcanv;
241
242         /* Update the command line since it has a blinking cursor */
243         gr_set_current_canvas(ConsoleSurface);
244
245         // restore InputBackground
246         gr_bitmap(0, ConsoleSurface->cv_h - ConsoleSurface->cv_font->ft_h, InputBackground);
247
248         cli_draw(ConsoleSurface->cv_h);
249
250         gr_set_current_canvas(&grd_curscreen->sc_canvas);
251
252         clip = gr_create_sub_bitmap(&ConsoleSurface->cv_bitmap, 0, ConsoleSurface->cv_h - RaiseOffset, ConsoleSurface->cv_w, RaiseOffset);
253
254         gr_bitmap(0, 0, clip);
255         gr_free_sub_bitmap(clip);
256
257         gr_set_current_canvas(canv_save);
258 }
259
260
261 void con_cmd_toggleconsole(int argc, char **argv)
262 {
263         if (argc > 1) {
264                 cmd_appendf("help %s", argv[0]);
265                 return;
266         }
267
268         if (con_is_visible())
269                 con_hide();
270         else
271                 con_show();
272 }
273
274
275 /* Initializes the console */
276 void con_init()
277 {
278         int loop;
279
280         Visible = CON_CLOSED;
281         RaiseOffset = 0;
282         ConsoleLines = NULL;
283         TotalConsoleLines = 0;
284         ConsoleScrollBack = 0;
285         BackgroundImage = NULL;
286
287         /* load the console surface */
288         ConsoleSurface = NULL;
289
290         /* Load the dirty rectangle for user input */
291         InputBackground = NULL;
292
293         VChars = CON_CHARS_PER_LINE - 1;
294         LineBuffer = CON_NUM_LINES;
295
296         ConsoleLines = (char **)d_malloc(sizeof(char *) * LineBuffer);
297         for (loop = 0; loop <= LineBuffer - 1; loop++) {
298                 ConsoleLines[loop] = (char *)d_calloc(CON_CHARS_PER_LINE, sizeof(char));
299         }
300
301         cli_init();
302         cmd_init();
303         cvar_init();
304
305         cmd_addcommand("toggleconsole", con_cmd_toggleconsole, "toggleconsole\n" "    show or hide the console");
306         cvar_registervariable (&con_threshold);
307
308         con_initialized = 1;
309
310         atexit(con_free);
311 }
312
313
314 void gr_init_bitmap_alloc( grs_bitmap *bm, int mode, int x, int y, int w, int h, int bytesperline);
315 void con_init_gfx(int w, int h)
316 {
317         int pcx_error;
318         grs_bitmap bmp;
319         ubyte pal[256*3];
320
321         if (ConsoleSurface) {
322                 /* resize console surface */
323                 gr_free_bitmap_data(&ConsoleSurface->cv_bitmap);
324                 gr_init_bitmap_alloc(&ConsoleSurface->cv_bitmap, BM_LINEAR, 0, 0, w, h, w);
325         } else {
326                 /* load the console surface */
327                 ConsoleSurface = gr_create_canvas(w, h);
328         }
329
330         /* Load the consoles font */
331         con_font(SMALL_FONT, gr_find_closest_color(29,29,47), -1);
332
333         /* make sure that the size of the console is valid */
334         if (w > grd_curscreen->sc_w || w < ConsoleSurface->cv_font->ft_w * 32)
335                 w = grd_curscreen->sc_w;
336         if (h > grd_curscreen->sc_h || h < ConsoleSurface->cv_font->ft_h)
337                 h = grd_curscreen->sc_h;
338
339         /* Load the dirty rectangle for user input */
340         if (InputBackground)
341                 gr_free_bitmap(InputBackground);
342         InputBackground = gr_create_bitmap(w, ConsoleSurface->cv_font->ft_h);
343
344         /* calculate the number of visible characters in the command line */
345 #if 0 // doesn't work because proportional font
346         VChars = (w - CON_CHAR_BORDER) / ConsoleSurface->cv_font->ft_w;
347         if (VChars >= CON_CHARS_PER_LINE)
348                 VChars = CON_CHARS_PER_LINE - 1;
349 #endif
350
351         gr_init_bitmap_data(&bmp);
352         pcx_error = pcx_read_bitmap(CON_BG, &bmp, BM_LINEAR, pal);
353         Assert(pcx_error == PCX_ERROR_NONE);
354         gr_remap_bitmap_good(&bmp, pal, -1, -1);
355         con_background(&bmp);
356         gr_free_bitmap_data(&bmp);
357 }
358
359
360 /* Makes the console visible */
361 void con_show(void)
362 {
363         Visible = CON_OPENING;
364         con_update();
365 }
366
367
368 /* Hides the console (make it invisible) */
369 void con_hide(void)
370 {
371         Visible = CON_CLOSING;
372         key_flush();
373 }
374
375
376 /* tells wether the console is visible or not */
377 int con_is_visible(void)
378 {
379         return((Visible == CON_OPEN) || (Visible == CON_OPENING));
380 }
381
382
383 /* Frees all the memory loaded by the console */
384 static void con_free(void)
385 {
386         int i;
387
388         for (i = 0; i <= LineBuffer - 1; i++) {
389                 d_free(ConsoleLines[i]);
390         }
391         d_free(ConsoleLines);
392
393         ConsoleLines = NULL;
394
395         if (ConsoleSurface)
396                 gr_free_canvas(ConsoleSurface);
397         ConsoleSurface = NULL;
398
399         if (BackgroundImage)
400                 gr_free_bitmap(BackgroundImage);
401         BackgroundImage = NULL;
402
403         if (InputBackground)
404                 gr_free_bitmap(InputBackground);
405         InputBackground = NULL;
406
407         con_initialized = 0;
408 }
409
410
411 /* Increments the console lines */
412 static void con_newline(void)
413 {
414         int loop;
415         char *temp;
416
417         temp = ConsoleLines[LineBuffer - 1];
418
419         for (loop = LineBuffer - 1; loop > 0; loop--)
420                 ConsoleLines[loop] = ConsoleLines[loop - 1];
421
422         ConsoleLines[0] = temp;
423
424         memset(ConsoleLines[0], 0, CON_CHARS_PER_LINE);
425         if (TotalConsoleLines < LineBuffer - 1)
426                 TotalConsoleLines++;
427         
428         //Now adjust the ConsoleScrollBack
429         //dont scroll if not at bottom
430         if(ConsoleScrollBack != 0)
431                 ConsoleScrollBack++;
432         //boundaries
433         if(ConsoleScrollBack > LineBuffer-1)
434                 ConsoleScrollBack = LineBuffer-1;
435 }
436
437
438 static inline int con_get_width(void)
439 {
440         if (!ConsoleSurface)
441                 return 0;
442
443         return ConsoleSurface->cv_bitmap.bm_w - CON_CHAR_BORDER;
444 }
445
446
447 static inline int con_get_string_width(char *string)
448 {
449         grs_canvas *canv_save;
450         int w = 0, h, aw;
451
452         if (!ConsoleSurface)
453                 return 0;
454
455         canv_save = grd_curcanv;
456         gr_set_current_canvas(ConsoleSurface);
457         gr_get_string_size(string, &w, &h, &aw);
458         gr_set_current_canvas(canv_save);
459
460         return w;
461 }
462
463
464 #ifdef _MSC_VER
465 # define vsnprintf _vsnprintf
466 #endif
467
468 /* Outputs text to the console (in game), up to CON_CHARS_PER_LINE chars can be entered */
469 static void con_out(const char *str, ...)
470 {
471         va_list marker;
472         //keep some space free for stuff like CON_Out("blablabla %s", Command);
473         char temp[CON_CHARS_PER_LINE + 128];
474         char* ptemp;
475
476         va_start(marker, str);
477         vsnprintf(temp, CON_CHARS_PER_LINE + 127, str, marker);
478         va_end(marker);
479
480         ptemp = temp;
481
482         // temp now contains the complete string we want to output
483         // the only problem is that temp is maybe longer than the console
484         // width so we have to cut it into several pieces
485
486         if (ConsoleLines) {
487                 char *p = ptemp;
488
489                 while (*p) {
490                         if (*p == '\n') {
491                                 *p = '\0';
492                                 con_newline();
493                                 strcat(ConsoleLines[0], ptemp);
494                                 ptemp = p+1;
495                         } else if (p - ptemp > VChars - strlen(ConsoleLines[0]) ||
496                                            con_get_string_width(ptemp) > con_get_width()) {
497                                 con_newline();
498                                 strncat(ConsoleLines[0], ptemp, VChars - strlen(ConsoleLines[0]));
499                                 ConsoleLines[0][VChars] = '\0';
500                                 ptemp = p;
501                         }
502                         p++;
503                 }
504                 if (strlen(ptemp)) {
505                         strncat(ConsoleLines[0], ptemp, VChars - strlen(ConsoleLines[0]));
506                         ConsoleLines[0][VChars] = '\0';
507                 }
508                 con_update();
509         }
510 }
511
512
513 /* Adds background image to the console, scaled to size of console*/
514 static int con_background(grs_bitmap *image)
515 {
516         /* Free the background from the console */
517         if (image == NULL) {
518                 if (BackgroundImage)
519                         gr_free_bitmap(BackgroundImage);
520                 BackgroundImage = NULL;
521                 return 0;
522         }
523
524         /* Load a new background */
525         if (BackgroundImage)
526                 gr_free_bitmap(BackgroundImage);
527         BackgroundImage = gr_create_bitmap(ConsoleSurface->cv_w, ConsoleSurface->cv_h);
528         gr_bitmap_scale_to(image, BackgroundImage);
529
530         gr_bm_bitblt(BackgroundImage->bm_w, InputBackground->bm_h, 0, 0, 0, ConsoleSurface->cv_h - ConsoleSurface->cv_font->ft_h, BackgroundImage, InputBackground);
531
532         return 0;
533 }
534
535
536 /* Sets font info for the console */
537 static void con_font(grs_font *font, int fg, int bg)
538 {
539         grs_canvas *canv_save;
540
541         canv_save = grd_curcanv;
542         gr_set_current_canvas(ConsoleSurface);
543         gr_set_curfont(font);
544         gr_set_fontcolor(fg, bg);
545         gr_set_current_canvas(canv_save);
546 }
547
548
549 static void con_clear(void)
550 {
551         int loop;
552         
553         for (loop = 0; loop <= LineBuffer - 1; loop++)
554                 memset(ConsoleLines[loop], 0, CON_CHARS_PER_LINE);
555 }
556
557
558 /* convert to ansi rgb colors 17-231 */
559 #define PAL2ANSI(x) ((36*gr_palette[(x)*3]/11) + (6*gr_palette[(x)*3+1]/11) + (gr_palette[(x)*3+2]/11) + 16)
560
561 /* Print a message to the console */
562 void con_printf(int priority, char *fmt, ...)
563 {
564         va_list arglist;
565         char buffer[2048];
566
567         if (priority <= (con_threshold.intval))
568         {
569                 va_start (arglist, fmt);
570                 vsprintf (buffer,  fmt, arglist);
571                 va_end (arglist);
572
573                 if (con_initialized)
574                         con_out(buffer);
575
576                 if (!text_console_enabled)
577                         return;
578
579                 if (isatty(fileno(stdout))) {
580                         char *buf, *p;
581                         unsigned char color, spacing, underline;
582
583                         p = buf = buffer;
584                         do
585                                 switch (*p)
586                                 {
587                                 case CC_COLOR:
588                                         *p++ = 0;
589                                         printf("%s", buf);
590                                         color = *p++;
591                                         printf("\x1B[38;5;%dm", PAL2ANSI(color));
592                                         buf = p;
593                                         break;
594                                 case CC_LSPACING:
595                                         *p++ = 0;
596                                         printf("%s", buf);
597                                         spacing = *p++;
598                                         //printf("<SPACING %d>", color);
599                                         buf = p;
600                                         break;
601                                 case CC_UNDERLINE:
602                                         *p++ = 0;
603                                         printf("%s", buf);
604                                         underline = 1;
605                                         //printf("<UNDERLINE>");
606                                         buf = p;
607                                         break;
608                                 default:
609                                         p++;
610                                 }
611                         while (*p);
612
613                         printf("%s", buf);
614
615                 } else {
616                         /* Produce a sanitised version and send it to the standard output */
617                         char *p1, *p2;
618
619                         p1 = p2 = buffer;
620                         do
621                                 switch (*p1)
622                                 {
623                                 case CC_COLOR:
624                                 case CC_LSPACING:
625                                         p1++;
626                                 case CC_UNDERLINE:
627                                         p1++;
628                                         break;
629                                 default:
630                                         *p2++ = *p1++;
631                                 }
632                         while (*p1);
633                         *p2 = 0;
634
635                         printf("%s", buffer);
636                 }
637         }
638 }