]> icculus.org git repositories - divverent/darkplaces.git/blob - console.c
use vid.conheight instead of scr_conlines in history scrolling
[divverent/darkplaces.git] / console.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20 // console.c
21
22 #if !defined(WIN32) || defined(__MINGW32__)
23 # include <unistd.h>
24 #endif
25 #include <time.h>
26 #include "quakedef.h"
27
28 int con_linewidth;
29
30 float con_cursorspeed = 4;
31
32 #define         CON_TEXTSIZE    131072
33
34 // total lines in console scrollback
35 int con_totallines;
36 // lines up from bottom to display
37 int con_backscroll;
38 // where next message will be printed
39 int con_current;
40 // offset in current line for next print
41 int con_x;
42 char *con_text = 0;
43
44 //seconds
45 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3"};
46 cvar_t con_notify = {CVAR_SAVE, "con_notify","4"};
47
48 #define MAX_NOTIFYLINES 32
49 // cl.time time the line was generated for transparent notify lines
50 float con_times[MAX_NOTIFYLINES];
51
52 int con_vislines;
53
54 #define MAXCMDLINE      256
55 extern char key_lines[32][MAXCMDLINE];
56 extern int edit_line;
57 extern int key_linepos;
58 extern int key_insert;
59
60
61 qboolean con_initialized;
62
63 mempool_t *console_mempool;
64
65
66 /*
67 ==============================================================================
68
69 LOGGING
70
71 ==============================================================================
72 */
73
74 cvar_t log_file = {0, "log_file",""};
75 cvar_t log_sync = {0, "log_sync","0"};
76 char crt_log_file [MAX_OSPATH] = "";
77 qfile_t* logfile = NULL;
78
79 qbyte* logqueue = NULL;
80 size_t logq_ind = 0;
81 size_t logq_size = 0;
82
83 void Log_ConPrint (const char *msg);
84
85 /*
86 ====================
87 Log_Timestamp
88 ====================
89 */
90 const char* Log_Timestamp (const char *desc)
91 {
92         static char timestamp [128];
93         time_t crt_time;
94         const struct tm *crt_tm;
95         char timestring [64];
96
97         // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
98         time (&crt_time);
99         crt_tm = localtime (&crt_time);
100         strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
101
102         if (desc != NULL)
103                 snprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
104         else
105                 snprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
106
107         return timestamp;
108 }
109
110
111 /*
112 ====================
113 Log_Init
114 ====================
115 */
116 void Log_Init (void)
117 {
118         // Allocate a log queue
119         logq_size = 512;
120         logqueue = Mem_Alloc (tempmempool, logq_size);
121         logq_ind = 0;
122
123         Cvar_RegisterVariable (&log_file);
124         Cvar_RegisterVariable (&log_sync);
125
126         // support for the classic Quake option
127 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log with sync on (so it keeps every message up to a crash), see also log_file and log_sync
128         if (COM_CheckParm ("-condebug") != 0)
129         {
130                 Cvar_SetQuick (&log_file, "qconsole.log");
131                 Cvar_SetValueQuick (&log_sync, 1);
132         }
133 }
134
135
136 /*
137 ====================
138 Log_Open
139 ====================
140 */
141 void Log_Open (void)
142 {
143         if (logfile != NULL || log_file.string[0] == '\0')
144                 return;
145
146         logfile = FS_Open (log_file.string, "at", false);
147         if (logfile != NULL)
148         {
149                 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
150                 FS_Print (logfile, Log_Timestamp ("Log started"));
151         }
152 }
153
154
155 /*
156 ====================
157 Log_Close
158 ====================
159 */
160 void Log_Close (void)
161 {
162         if (logfile == NULL)
163                 return;
164
165         FS_Print (logfile, Log_Timestamp ("Log stopped"));
166         FS_Print (logfile, "\n");
167         FS_Close (logfile);
168
169         logfile = NULL;
170         crt_log_file[0] = '\0';
171 }
172
173
174 /*
175 ====================
176 Log_Start
177 ====================
178 */
179 void Log_Start (void)
180 {
181         Log_Open ();
182
183         // Dump the contents of the log queue into the log file and free it
184         if (logqueue != NULL)
185         {
186                 if (logfile != NULL && logq_ind != 0)
187                         FS_Write (logfile, logqueue, logq_ind);
188                 Mem_Free (logqueue);
189                 logqueue = NULL;
190                 logq_ind = 0;
191                 logq_size = 0;
192         }
193 }
194
195
196 /*
197 ================
198 Log_ConPrint
199 ================
200 */
201 void Log_ConPrint (const char *msg)
202 {
203         static qboolean inprogress = false;
204         // don't allow feedback loops with memory error reports
205         if (inprogress)
206                 return;
207         inprogress = true;
208         // Until the host is completely initialized, we maintain a log queue
209         // to store the messages, since the log can't be started before
210         if (logqueue != NULL)
211         {
212                 size_t remain = logq_size - logq_ind;
213                 size_t len = strlen (msg);
214
215                 // If we need to enlarge the log queue
216                 if (len > remain)
217                 {
218                         unsigned int factor = ((logq_ind + len) / logq_size) + 1;
219                         qbyte* newqueue;
220
221                         logq_size *= factor;
222                         newqueue = Mem_Alloc (tempmempool, logq_size);
223                         memcpy (newqueue, logqueue, logq_ind);
224                         Mem_Free (logqueue);
225                         logqueue = newqueue;
226                         remain = logq_size - logq_ind;
227                 }
228                 memcpy (&logqueue[logq_ind], msg, len);
229                 logq_ind += len;
230
231                 inprogress = false;
232                 return;
233         }
234
235         // Check if log_file has changed
236         if (strcmp (crt_log_file, log_file.string) != 0)
237         {
238                 Log_Close ();
239                 Log_Open ();
240         }
241
242         // If a log file is available
243         if (logfile != NULL)
244         {
245                 FS_Print (logfile, msg);
246                 if (log_sync.integer)
247                         FS_Flush (logfile);
248         }
249         inprogress = false;
250 }
251
252
253 /*
254 ================
255 Log_Print
256 ================
257 */
258 void Log_Print (const char *logfilename, const char *msg)
259 {
260         qfile_t *file;
261         file = FS_Open(logfilename, "at", true);
262         if (file)
263         {
264                 FS_Print(file, msg);
265                 FS_Close(file);
266         }
267 }
268
269 /*
270 ================
271 Log_Printf
272 ================
273 */
274 void Log_Printf (const char *logfilename, const char *fmt, ...)
275 {
276         qfile_t *file;
277
278         file = FS_Open (logfilename, "at", true);
279         if (file != NULL)
280         {
281                 va_list argptr;
282
283                 va_start (argptr, fmt);
284                 FS_VPrintf (file, fmt, argptr);
285                 va_end (argptr);
286
287                 FS_Close (file);
288         }
289 }
290
291
292 /*
293 ==============================================================================
294
295 CONSOLE
296
297 ==============================================================================
298 */
299
300 /*
301 ================
302 Con_ToggleConsole_f
303 ================
304 */
305 void Con_ToggleConsole_f (void)
306 {
307         // toggle the 'user wants console' bit
308         key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
309         memset (con_times, 0, sizeof(con_times));
310 }
311
312 /*
313 ================
314 Con_Clear_f
315 ================
316 */
317 void Con_Clear_f (void)
318 {
319         if (con_text)
320                 memset (con_text, ' ', CON_TEXTSIZE);
321 }
322
323
324 /*
325 ================
326 Con_ClearNotify
327 ================
328 */
329 void Con_ClearNotify (void)
330 {
331         int i;
332
333         for (i=0 ; i<MAX_NOTIFYLINES ; i++)
334                 con_times[i] = 0;
335 }
336
337
338 /*
339 ================
340 Con_MessageMode_f
341 ================
342 */
343 void Con_MessageMode_f (void)
344 {
345         key_dest = key_message;
346         chat_team = false;
347 }
348
349
350 /*
351 ================
352 Con_MessageMode2_f
353 ================
354 */
355 void Con_MessageMode2_f (void)
356 {
357         key_dest = key_message;
358         chat_team = true;
359 }
360
361
362 /*
363 ================
364 Con_CheckResize
365
366 If the line width has changed, reformat the buffer.
367 ================
368 */
369 void Con_CheckResize (void)
370 {
371         int i, j, width, oldwidth, oldtotallines, numlines, numchars;
372         char tbuf[CON_TEXTSIZE];
373
374         width = (vid.conwidth >> 3);
375
376         if (width == con_linewidth)
377                 return;
378
379         if (width < 1)                  // video hasn't been initialized yet
380         {
381                 width = 80;
382                 con_linewidth = width;
383                 con_totallines = CON_TEXTSIZE / con_linewidth;
384                 memset (con_text, ' ', CON_TEXTSIZE);
385         }
386         else
387         {
388                 oldwidth = con_linewidth;
389                 con_linewidth = width;
390                 oldtotallines = con_totallines;
391                 con_totallines = CON_TEXTSIZE / con_linewidth;
392                 numlines = oldtotallines;
393
394                 if (con_totallines < numlines)
395                         numlines = con_totallines;
396
397                 numchars = oldwidth;
398
399                 if (con_linewidth < numchars)
400                         numchars = con_linewidth;
401
402                 memcpy (tbuf, con_text, CON_TEXTSIZE);
403                 memset (con_text, ' ', CON_TEXTSIZE);
404
405                 for (i=0 ; i<numlines ; i++)
406                 {
407                         for (j=0 ; j<numchars ; j++)
408                         {
409                                 con_text[(con_totallines - 1 - i) * con_linewidth + j] =
410                                                 tbuf[((con_current - i + oldtotallines) %
411                                                           oldtotallines) * oldwidth + j];
412                         }
413                 }
414
415                 Con_ClearNotify ();
416         }
417
418         con_backscroll = 0;
419         con_current = con_totallines - 1;
420 }
421
422 /*
423 ================
424 Con_Init
425 ================
426 */
427 void Con_Init (void)
428 {
429         console_mempool = Mem_AllocPool("console", 0, NULL);
430         con_text = Mem_Alloc(console_mempool, CON_TEXTSIZE);
431         memset (con_text, ' ', CON_TEXTSIZE);
432         con_linewidth = -1;
433         Con_CheckResize ();
434
435         // register our cvars
436         Cvar_RegisterVariable (&con_notifytime);
437         Cvar_RegisterVariable (&con_notify);
438
439         // register our commands
440         Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f);
441         Cmd_AddCommand ("messagemode", Con_MessageMode_f);
442         Cmd_AddCommand ("messagemode2", Con_MessageMode2_f);
443         Cmd_AddCommand ("clear", Con_Clear_f);
444
445         con_initialized = true;
446         Con_Print("Console initialized.\n");
447 }
448
449
450 /*
451 ===============
452 Con_Linefeed
453 ===============
454 */
455 void Con_Linefeed (void)
456 {
457         if (con_backscroll)
458                 con_backscroll++;
459
460         con_x = 0;
461         con_current++;
462         memset (&con_text[(con_current%con_totallines)*con_linewidth], ' ', con_linewidth);
463 }
464
465 /*
466 ================
467 Con_PrintToHistory
468
469 Handles cursor positioning, line wrapping, etc
470 All console printing must go through this in order to be displayed
471 If no console is visible, the notify window will pop up.
472 ================
473 */
474 void Con_PrintToHistory(const char *txt)
475 {
476         int y, c, l, mask;
477         static int cr;
478
479         if (txt[0] == 1)
480         {
481                 mask = 128;             // go to colored text
482                 S_LocalSound ("sound/misc/talk.wav");
483         // play talk wav
484                 txt++;
485         }
486         else if (txt[0] == 2)
487         {
488                 mask = 128;             // go to colored text
489                 txt++;
490         }
491         else
492                 mask = 0;
493
494
495         while ( (c = *txt) )
496         {
497         // count word length
498                 for (l=0 ; l< con_linewidth ; l++)
499                         if ( txt[l] <= ' ')
500                                 break;
501
502         // word wrap
503                 if (l != con_linewidth && (con_x + l > con_linewidth) )
504                         con_x = 0;
505
506                 txt++;
507
508                 if (cr)
509                 {
510                         con_current--;
511                         cr = false;
512                 }
513
514
515                 if (!con_x)
516                 {
517                         Con_Linefeed ();
518                 // mark time for transparent overlay
519                         if (con_current >= 0)
520                         {
521                                 if (con_notify.integer < 0)
522                                         Cvar_SetValueQuick(&con_notify, 0);
523                                 if (con_notify.integer > MAX_NOTIFYLINES)
524                                         Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
525                                 if (con_notify.integer > 0)
526                                         con_times[con_current % con_notify.integer] = cl.time;
527                         }
528                 }
529
530                 switch (c)
531                 {
532                 case '\n':
533                         con_x = 0;
534                         break;
535
536                 case '\r':
537                         con_x = 0;
538                         cr = 1;
539                         break;
540
541                 default:        // display character and advance
542                         y = con_current % con_totallines;
543                         con_text[y*con_linewidth+con_x] = c | mask;
544                         con_x++;
545                         if (con_x >= con_linewidth)
546                                 con_x = 0;
547                         break;
548                 }
549
550         }
551 }
552
553 /* The translation table between the graphical font and plain ASCII  --KB */
554 static char qfont_table[256] = {
555         '\0', '#',  '#',  '#',  '#',  '.',  '#',  '#',
556         '#',  9,    10,   '#',  ' ',  13,   '.',  '.',
557         '[',  ']',  '0',  '1',  '2',  '3',  '4',  '5',
558         '6',  '7',  '8',  '9',  '.',  '<',  '=',  '>',
559         ' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'',
560         '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
561         '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
562         '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
563         '@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',
564         'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
565         'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
566         'X',  'Y',  'Z',  '[',  '\\', ']',  '^',  '_',
567         '`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',
568         'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
569         'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
570         'x',  'y',  'z',  '{',  '|',  '}',  '~',  '<',
571
572         '<',  '=',  '>',  '#',  '#',  '.',  '#',  '#',
573         '#',  '#',  ' ',  '#',  ' ',  '>',  '.',  '.',
574         '[',  ']',  '0',  '1',  '2',  '3',  '4',  '5',
575         '6',  '7',  '8',  '9',  '.',  '<',  '=',  '>',
576         ' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'',
577         '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
578         '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
579         '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
580         '@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',
581         'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
582         'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
583         'X',  'Y',  'Z',  '[',  '\\', ']',  '^',  '_',
584         '`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',
585         'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
586         'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
587         'x',  'y',  'z',  '{',  '|',  '}',  '~',  '<'
588 };
589
590 /*
591 ================
592 Con_Print
593
594 Prints to all appropriate console targets, and adds timestamps
595 ================
596 */
597 extern cvar_t timestamps;
598 extern cvar_t timeformat;
599 extern qboolean sys_nostdout;
600 void Con_Print(const char *msg)
601 {
602         static int index = 0;
603         static char line[16384];
604
605         for (;*msg;msg++)
606         {
607                 if (index == 0)
608                 {
609                         // if this is the beginning of a new line, print timestamp
610                         char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
611                         // special color codes for chat messages must always come first
612                         // for Con_PrintToHistory to work properly
613                         if (*msg <= 2)
614                                 line[index++] = *msg++;
615                         // store timestamp
616                         for (;*timestamp;index++, timestamp++)
617                                 if (index < sizeof(line) - 2)
618                                         line[index] = *timestamp;
619                 }
620                 // append the character
621                 line[index++] = *msg;
622                 // if this is a newline character, we have a complete line to print
623                 if (*msg == '\n' || index >= 16000)
624                 {
625                         // terminate the line
626                         line[index] = 0;
627                         // send to log file
628                         Log_ConPrint(line);
629                         // send to scrollable buffer
630                         if (con_initialized && cls.state != ca_dedicated)
631                                 Con_PrintToHistory(line);
632                         // send to terminal or dedicated server window
633                         if (!sys_nostdout)
634                         {
635                                 unsigned char *p;
636                                 for (p = (unsigned char *) line;*p; p++)
637                                         *p = qfont_table[*p];
638                                 Sys_PrintToTerminal(line);
639                         }
640                         // empty the line buffer
641                         index = 0;
642                 }
643         }
644 }
645
646
647 // LordHavoc: increased from 4096 to 16384
648 #define MAXPRINTMSG     16384
649
650 /*
651 ================
652 Con_Printf
653
654 Prints to all appropriate console targets
655 ================
656 */
657 void Con_Printf(const char *fmt, ...)
658 {
659         va_list argptr;
660         char msg[MAXPRINTMSG];
661
662         va_start(argptr,fmt);
663         vsnprintf(msg,sizeof(msg),fmt,argptr);
664         va_end(argptr);
665
666         Con_Print(msg);
667 }
668
669 /*
670 ================
671 Con_DPrint
672
673 A Con_Print that only shows up if the "developer" cvar is set
674 ================
675 */
676 void Con_DPrint(const char *msg)
677 {
678         if (!developer.integer)
679                 return;                 // don't confuse non-developers with techie stuff...
680         Con_Print(msg);
681 }
682
683 /*
684 ================
685 Con_DPrintf
686
687 A Con_Printf that only shows up if the "developer" cvar is set
688 ================
689 */
690 void Con_DPrintf(const char *fmt, ...)
691 {
692         va_list argptr;
693         char msg[MAXPRINTMSG];
694
695         if (!developer.integer)
696                 return;                 // don't confuse non-developers with techie stuff...
697
698         va_start(argptr,fmt);
699         vsnprintf(msg,sizeof(msg),fmt,argptr);
700         va_end(argptr);
701
702         Con_Print(msg);
703 }
704
705
706 /*
707 ==============================================================================
708
709 DRAWING
710
711 ==============================================================================
712 */
713
714
715 /*
716 ================
717 Con_DrawInput
718
719 The input line scrolls horizontally if typing goes beyond the right edge
720
721 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
722 ================
723 */
724 void Con_DrawInput (void)
725 {
726         int             y;
727         int             i;
728         char editlinecopy[257], *text;
729
730         if (!key_consoleactive)
731                 return;         // don't draw anything
732
733         text = strcpy(editlinecopy, key_lines[edit_line]);
734
735         // Advanced Console Editing by Radix radix@planetquake.com
736         // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
737         // use strlen of edit_line instead of key_linepos to allow editing
738         // of early characters w/o erasing
739
740         y = strlen(text);
741
742 // fill out remainder with spaces
743         for (i = y; i < 256; i++)
744                 text[i] = ' ';
745
746         // add the cursor frame
747         if ((int)(realtime*con_cursorspeed) & 1)                // cursor is visible
748                 text[key_linepos] = 11 + 130 * key_insert;      // either solid or triangle facing right
749
750 //      text[key_linepos + 1] = 0;
751
752         // prestep if horizontally scrolling
753         if (key_linepos >= con_linewidth)
754                 text += 1 + key_linepos - con_linewidth;
755
756         // draw it
757         DrawQ_String(0, con_vislines - 16, text, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
758
759         // remove cursor
760 //      key_lines[edit_line][key_linepos] = 0;
761 }
762
763
764 /*
765 ================
766 Con_DrawNotify
767
768 Draws the last few lines of output transparently over the game top
769 ================
770 */
771 void Con_DrawNotify (void)
772 {
773         int             x, v;
774         char    *text;
775         int             i;
776         float   time;
777         extern char chat_buffer[];
778         char    temptext[256];
779
780         if (con_notify.integer < 0)
781                 Cvar_SetValueQuick(&con_notify, 0);
782         if (con_notify.integer > MAX_NOTIFYLINES)
783                 Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
784         if (gamemode == GAME_TRANSFUSION)
785                 v = 8;
786         else
787                 v = 0;
788         for (i= con_current-con_notify.integer+1 ; i<=con_current ; i++)
789         {
790                 if (i < 0)
791                         continue;
792                 time = con_times[i % con_notify.integer];
793                 if (time == 0)
794                         continue;
795                 time = cl.time - time;
796                 if (time > con_notifytime.value)
797                         continue;
798                 text = con_text + (i % con_totallines)*con_linewidth;
799
800                 if (gamemode == GAME_NEXUIZ) {
801                         int linewidth;
802
803                         for (linewidth = con_linewidth; linewidth && text[linewidth-1] == ' '; linewidth--);
804                         x = (vid.conwidth - linewidth * 8) / 2;
805                 } else 
806                         x = 0;
807
808                 DrawQ_String(x, v, text, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
809
810                 v += 8;
811         }
812
813
814         if (key_dest == key_message)
815         {
816                 x = 0;
817
818                 // LordHavoc: speedup, and other improvements
819                 if (chat_team)
820                         sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
821                 else
822                         sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
823                 while (strlen(temptext) >= (size_t) con_linewidth)
824                 {
825                         DrawQ_String (0, v, temptext, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
826                         strcpy(temptext, &temptext[con_linewidth]);
827                         v += 8;
828                 }
829                 if (strlen(temptext) > 0)
830                 {
831                         DrawQ_String (0, v, temptext, 0, 8, 8, 1, 1, 1, 1, 0);
832                         v += 8;
833                 }
834         }
835 }
836
837 /*
838 ================
839 Con_DrawConsole
840
841 Draws the console with the solid background
842 The typing input line at the bottom should only be drawn if typing is allowed
843 ================
844 */
845 extern char engineversion[40];
846 void Con_DrawConsole (int lines)
847 {
848         int i, y, rows, j;
849         char *text;
850
851         if (lines <= 0)
852                 return;
853
854 // draw the background
855         if (scr_conbrightness.value >= 0.01f)
856                 DrawQ_Pic(0, lines - vid.conheight, "gfx/conback", vid.conwidth, vid.conheight, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, scr_conalpha.value, 0);
857         else
858                 DrawQ_Fill(0, lines - vid.conheight, vid.conwidth, vid.conheight, 0, 0, 0, scr_conalpha.value, 0);
859         DrawQ_String(vid.conwidth - strlen(engineversion) * 8 - 8, lines - 8, engineversion, 0, 8, 8, 1, 0, 0, 1, 0);
860
861 // draw the text
862         con_vislines = lines;
863
864         rows = (lines-16)>>3;           // rows of text to draw
865         y = lines - 16 - (rows<<3);     // may start slightly negative
866
867         for (i = con_current - rows + 1;i <= con_current;i++, y += 8)
868         {
869                 j = max(i - con_backscroll, 0);
870                 text = con_text + (j % con_totallines)*con_linewidth;
871
872                 DrawQ_String(0, y, text, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
873         }
874
875 // draw the input prompt, user text, and cursor if desired
876         Con_DrawInput ();
877 }
878
879 /*
880         Con_DisplayList
881
882         New function for tab-completion system
883         Added by EvilTypeGuy
884         MEGA Thanks to Taniwha
885
886 */
887 void Con_DisplayList(const char **list)
888 {
889         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
890         const char **walk = list;
891
892         while (*walk) {
893                 len = strlen(*walk);
894                 if (len > maxlen)
895                         maxlen = len;
896                 walk++;
897         }
898         maxlen += 1;
899
900         while (*list) {
901                 len = strlen(*list);
902                 if (pos + maxlen >= width) {
903                         Con_Print("\n");
904                         pos = 0;
905                 }
906
907                 Con_Print(*list);
908                 for (i = 0; i < (maxlen - len); i++)
909                         Con_Print(" ");
910
911                 pos += maxlen;
912                 list++;
913         }
914
915         if (pos)
916                 Con_Print("\n\n");
917 }
918
919 /*
920         Con_CompleteCommandLine
921
922         New function for tab-completion system
923         Added by EvilTypeGuy
924         Thanks to Fett erich@heintz.com
925         Thanks to taniwha
926
927 */
928 void Con_CompleteCommandLine (void)
929 {
930         const char *cmd = "", *s;
931         const char **list[3] = {0, 0, 0};
932         int c, v, a, i, cmd_len;
933
934         s = key_lines[edit_line] + 1;
935         // Count number of possible matches
936         c = Cmd_CompleteCountPossible(s);
937         v = Cvar_CompleteCountPossible(s);
938         a = Cmd_CompleteAliasCountPossible(s);
939
940         if (!(c + v + a))       // No possible matches
941                 return;
942
943         if (c + v + a == 1) {
944                 if (c)
945                         list[0] = Cmd_CompleteBuildList(s);
946                 else if (v)
947                         list[0] = Cvar_CompleteBuildList(s);
948                 else
949                         list[0] = Cmd_CompleteAliasBuildList(s);
950                 cmd = *list[0];
951                 cmd_len = strlen (cmd);
952         } else {
953                 if (c)
954                         cmd = *(list[0] = Cmd_CompleteBuildList(s));
955                 if (v)
956                         cmd = *(list[1] = Cvar_CompleteBuildList(s));
957                 if (a)
958                         cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
959
960                 cmd_len = strlen (s);
961                 do {
962                         for (i = 0; i < 3; i++) {
963                                 char ch = cmd[cmd_len];
964                                 const char **l = list[i];
965                                 if (l) {
966                                         while (*l && (*l)[cmd_len] == ch)
967                                                 l++;
968                                         if (*l)
969                                                 break;
970                                 }
971                         }
972                         if (i == 3)
973                                 cmd_len++;
974                 } while (i == 3);
975                 // 'quakebar'
976                 Con_Print("\n\35");
977                 for (i = 0; i < con_linewidth - 4; i++)
978                         Con_Print("\36");
979                 Con_Print("\37\n");
980
981                 // Print Possible Commands
982                 if (c) {
983                         Con_Printf("%i possible command%s\n", c, (c > 1) ? "s: " : ":");
984                         Con_DisplayList(list[0]);
985                 }
986
987                 if (v) {
988                         Con_Printf("%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
989                         Con_DisplayList(list[1]);
990                 }
991
992                 if (a) {
993                         Con_Printf("%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
994                         Con_DisplayList(list[2]);
995                 }
996         }
997
998         if (cmd) {
999                 strncpy(key_lines[edit_line] + 1, cmd, cmd_len);
1000                 key_linepos = cmd_len + 1;
1001                 if (c + v + a == 1) {
1002                         key_lines[edit_line][key_linepos] = ' ';
1003                         key_linepos++;
1004                 }
1005                 key_lines[edit_line][key_linepos] = 0;
1006         }
1007         for (i = 0; i < 3; i++)
1008                 if (list[i])
1009                         Mem_Free((void *)list[i]);
1010 }
1011