added con_notify cvar, which controls how many console notify lines are displayed...
[xonotic/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 #ifdef NeXT
23 #include <libc.h>
24 #endif
25 #ifndef _MSC_VER
26 #ifndef __BORLANDC__
27 #include <unistd.h>
28 #endif
29 #endif
30 #ifdef WIN32
31 #include <io.h>
32 #endif
33 #include <fcntl.h>
34 #include "quakedef.h"
35
36 int con_linewidth;
37
38 float con_cursorspeed = 4;
39
40 #define         CON_TEXTSIZE    131072
41
42 // total lines in console scrollback
43 int con_totallines;
44 // lines up from bottom to display
45 int con_backscroll;
46 // where next message will be printed
47 int con_current;
48 // offset in current line for next print
49 int con_x;
50 char *con_text = 0;
51
52 //seconds
53 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3"};
54 cvar_t con_notify = {CVAR_SAVE, "con_notify","4"};
55 cvar_t logfile = {0, "logfile","0"};
56
57 #define MAX_NOTIFYLINES 32
58 // realtime time the line was generated for transparent notify lines
59 float con_times[MAX_NOTIFYLINES];
60
61 int con_vislines;
62
63 qboolean con_debuglog;
64
65 #define MAXCMDLINE      256
66 extern char key_lines[32][MAXCMDLINE];
67 extern int edit_line;
68 extern int key_linepos;
69 extern int key_insert;
70
71
72 qboolean con_initialized;
73
74 mempool_t *console_mempool;
75
76 // scan lines to clear for notify lines
77 int con_notifylines;
78
79 extern void M_Menu_Main_f (void);
80
81 /*
82 ================
83 Con_ToggleConsole_f
84 ================
85 */
86 void Con_ToggleConsole_f (void)
87 {
88         // toggle the 'user wants console' bit
89         key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
90         memset (con_times, 0, sizeof(con_times));
91 }
92
93 /*
94 ================
95 Con_Clear_f
96 ================
97 */
98 void Con_Clear_f (void)
99 {
100         if (con_text)
101                 memset (con_text, ' ', CON_TEXTSIZE);
102 }
103
104
105 /*
106 ================
107 Con_ClearNotify
108 ================
109 */
110 void Con_ClearNotify (void)
111 {
112         int i;
113
114         for (i=0 ; i<MAX_NOTIFYLINES ; i++)
115                 con_times[i] = 0;
116 }
117
118
119 /*
120 ================
121 Con_MessageMode_f
122 ================
123 */
124 void Con_MessageMode_f (void)
125 {
126         key_dest = key_message;
127         chat_team = false;
128 }
129
130
131 /*
132 ================
133 Con_MessageMode2_f
134 ================
135 */
136 void Con_MessageMode2_f (void)
137 {
138         key_dest = key_message;
139         chat_team = true;
140 }
141
142
143 /*
144 ================
145 Con_CheckResize
146
147 If the line width has changed, reformat the buffer.
148 ================
149 */
150 void Con_CheckResize (void)
151 {
152         int i, j, width, oldwidth, oldtotallines, numlines, numchars;
153         char tbuf[CON_TEXTSIZE];
154
155         width = (vid.conwidth >> 3);
156
157         if (width == con_linewidth)
158                 return;
159
160         if (width < 1)                  // video hasn't been initialized yet
161         {
162                 width = 80;
163                 con_linewidth = width;
164                 con_totallines = CON_TEXTSIZE / con_linewidth;
165                 memset (con_text, ' ', CON_TEXTSIZE);
166         }
167         else
168         {
169                 oldwidth = con_linewidth;
170                 con_linewidth = width;
171                 oldtotallines = con_totallines;
172                 con_totallines = CON_TEXTSIZE / con_linewidth;
173                 numlines = oldtotallines;
174
175                 if (con_totallines < numlines)
176                         numlines = con_totallines;
177
178                 numchars = oldwidth;
179
180                 if (con_linewidth < numchars)
181                         numchars = con_linewidth;
182
183                 memcpy (tbuf, con_text, CON_TEXTSIZE);
184                 memset (con_text, ' ', CON_TEXTSIZE);
185
186                 for (i=0 ; i<numlines ; i++)
187                 {
188                         for (j=0 ; j<numchars ; j++)
189                         {
190                                 con_text[(con_totallines - 1 - i) * con_linewidth + j] =
191                                                 tbuf[((con_current - i + oldtotallines) %
192                                                           oldtotallines) * oldwidth + j];
193                         }
194                 }
195
196                 Con_ClearNotify ();
197         }
198
199         con_backscroll = 0;
200         con_current = con_totallines - 1;
201 }
202
203
204 /*
205 ================
206 Con_Init
207 ================
208 */
209 void Con_Init (void)
210 {
211 #define MAXGAMEDIRLEN 1000
212         char temp[MAXGAMEDIRLEN+1];
213         char *t2 = "/qconsole.log";
214
215         Cvar_RegisterVariable(&logfile);
216         con_debuglog = COM_CheckParm("-condebug");
217
218         if (con_debuglog)
219         {
220                 if (strlen (com_gamedir) < (MAXGAMEDIRLEN - strlen (t2)))
221                 {
222                         sprintf (temp, "%s%s", com_gamedir, t2);
223                         unlink (temp);
224                 }
225                 logfile.integer = 1;
226         }
227
228         console_mempool = Mem_AllocPool("console");
229         con_text = Mem_Alloc(console_mempool, CON_TEXTSIZE);
230         memset (con_text, ' ', CON_TEXTSIZE);
231         con_linewidth = -1;
232         Con_CheckResize ();
233
234         Con_Printf ("Console initialized.\n");
235
236 //
237 // register our commands
238 //
239         Cvar_RegisterVariable (&con_notifytime);
240         Cvar_RegisterVariable (&con_notify);
241
242         Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f);
243         Cmd_AddCommand ("messagemode", Con_MessageMode_f);
244         Cmd_AddCommand ("messagemode2", Con_MessageMode2_f);
245         Cmd_AddCommand ("clear", Con_Clear_f);
246         con_initialized = true;
247 }
248
249
250 /*
251 ===============
252 Con_Linefeed
253 ===============
254 */
255 void Con_Linefeed (void)
256 {
257         con_x = 0;
258         con_current++;
259         memset (&con_text[(con_current%con_totallines)*con_linewidth], ' ', con_linewidth);
260 }
261
262 /*
263 ================
264 Con_Print
265
266 Handles cursor positioning, line wrapping, etc
267 All console printing must go through this in order to be logged to disk
268 If no console is visible, the notify window will pop up.
269 ================
270 */
271 void Con_Print (const char *txt)
272 {
273         int y, c, l, mask;
274         static int cr;
275
276         con_backscroll = 0;
277
278         if (txt[0] == 1)
279         {
280                 mask = 128;             // go to colored text
281                 S_LocalSound ("misc/talk.wav");
282         // play talk wav
283                 txt++;
284         }
285         else if (txt[0] == 2)
286         {
287                 mask = 128;             // go to colored text
288                 txt++;
289         }
290         else
291                 mask = 0;
292
293
294         while ( (c = *txt) )
295         {
296         // count word length
297                 for (l=0 ; l< con_linewidth ; l++)
298                         if ( txt[l] <= ' ')
299                                 break;
300
301         // word wrap
302                 if (l != con_linewidth && (con_x + l > con_linewidth) )
303                         con_x = 0;
304
305                 txt++;
306
307                 if (cr)
308                 {
309                         con_current--;
310                         cr = false;
311                 }
312
313
314                 if (!con_x)
315                 {
316                         Con_Linefeed ();
317                 // mark time for transparent overlay
318                         if (con_current >= 0)
319                         {
320                                 if (con_notify.integer < 0)
321                                         Cvar_SetValueQuick(&con_notify, 0);
322                                 if (con_notifylines > MAX_NOTIFYLINES)
323                                         Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
324                                 if (con_notify.integer > 0)
325                                         con_times[con_current % con_notify.integer] = realtime;
326                         }
327                 }
328
329                 switch (c)
330                 {
331                 case '\n':
332                         con_x = 0;
333                         break;
334
335                 case '\r':
336                         con_x = 0;
337                         cr = 1;
338                         break;
339
340                 default:        // display character and advance
341                         y = con_current % con_totallines;
342                         con_text[y*con_linewidth+con_x] = c | mask;
343                         con_x++;
344                         if (con_x >= con_linewidth)
345                                 con_x = 0;
346                         break;
347                 }
348
349         }
350 }
351
352
353 /*
354 ================
355 Con_DebugLog
356 ================
357 */
358 void Con_DebugLog(const char *file, const char *fmt, ...)
359 {
360     va_list argptr;
361     FILE* fd;
362
363     fd = fopen(file, "at");
364     va_start(argptr, fmt);
365     vfprintf (fd, fmt, argptr);
366     va_end(argptr);
367     fclose(fd);
368 }
369
370
371 /*
372 ================
373 Con_Printf
374
375 Handles cursor positioning, line wrapping, etc
376 ================
377 */
378 // LordHavoc: increased from 4096 to 16384
379 #define MAXPRINTMSG     16384
380 // FIXME: make a buffer size safe vsprintf?
381 void Con_Printf (const char *fmt, ...)
382 {
383         va_list argptr;
384         char msg[MAXPRINTMSG];
385
386         va_start (argptr,fmt);
387         vsprintf (msg,fmt,argptr);
388         va_end (argptr);
389
390 // also echo to debugging console
391         Sys_Printf ("%s", msg);
392
393 // log all messages to file
394         if (con_debuglog)
395         {
396                 // can't use va() here because it might overwrite other important things
397                 char logname[MAX_OSPATH];
398                 sprintf(logname, "%s/qconsole.log", com_gamedir);
399                 Con_DebugLog(logname, "%s", msg);
400         }
401
402         if (!con_initialized)
403                 return;
404
405         if (cls.state == ca_dedicated)
406                 return;         // no graphics mode
407
408 // write it to the scrollable buffer
409         Con_Print (msg);
410 }
411
412 /*
413 ================
414 Con_DPrintf
415
416 A Con_Printf that only shows up if the "developer" cvar is set
417 ================
418 */
419 void Con_DPrintf (const char *fmt, ...)
420 {
421         va_list argptr;
422         char msg[MAXPRINTMSG];
423
424         if (!developer.integer)
425                 return;                 // don't confuse non-developers with techie stuff...
426
427         va_start (argptr,fmt);
428         vsprintf (msg,fmt,argptr);
429         va_end (argptr);
430
431         Con_Printf ("%s", msg);
432 }
433
434
435 /*
436 ==================
437 Con_SafePrintf
438
439 Okay to call even when the screen can't be updated
440 ==================
441 */
442 void Con_SafePrintf (const char *fmt, ...)
443 {
444         va_list         argptr;
445         char            msg[1024];
446
447         va_start (argptr,fmt);
448         vsprintf (msg,fmt,argptr);
449         va_end (argptr);
450
451         Con_Printf ("%s", msg);
452 }
453
454
455 /*
456 ==============================================================================
457
458 DRAWING
459
460 ==============================================================================
461 */
462
463
464 /*
465 ================
466 Con_DrawInput
467
468 The input line scrolls horizontally if typing goes beyond the right edge
469
470 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
471 ================
472 */
473 void Con_DrawInput (void)
474 {
475         char editlinecopy[256], *text;
476
477         if (!key_consoleactive)
478                 return;         // don't draw anything
479
480         text = strcpy(editlinecopy, key_lines[edit_line]);
481
482         // Advanced Console Editing by Radix radix@planetquake.com
483         // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
484         // use strlen of edit_line instead of key_linepos to allow editing
485         // of early characters w/o erasing
486
487         // add the cursor frame
488         if ((int)(realtime*con_cursorspeed) & 1)                // cursor is visible
489                 text[key_linepos] = 11 + 130 * key_insert;      // either solid or triangle facing right
490
491         text[key_linepos + 1] = 0;
492
493         // prestep if horizontally scrolling
494         if (key_linepos >= con_linewidth)
495                 text += 1 + key_linepos - con_linewidth;
496
497         // draw it
498         DrawQ_String(0, con_vislines - 16, text, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
499
500         // remove cursor
501         key_lines[edit_line][key_linepos] = 0;
502 }
503
504
505 /*
506 ================
507 Con_DrawNotify
508
509 Draws the last few lines of output transparently over the game top
510 ================
511 */
512 void Con_DrawNotify (void)
513 {
514         int             x, v;
515         char    *text;
516         int             i;
517         float   time;
518         extern char chat_buffer[];
519         char    temptext[256];
520
521         if (con_notify.integer < 0)
522                 Cvar_SetValueQuick(&con_notify, 0);
523         if (con_notifylines > MAX_NOTIFYLINES)
524                 Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
525         v = 0;
526         for (i= con_current-con_notify.integer+1 ; i<=con_current ; i++)
527         {
528                 if (i < 0)
529                         continue;
530                 time = con_times[i % con_notify.integer];
531                 if (time == 0)
532                         continue;
533                 time = realtime - time;
534                 if (time > con_notifytime.value)
535                         continue;
536                 text = con_text + (i % con_totallines)*con_linewidth;
537
538                 clearnotify = 0;
539
540                 DrawQ_String(0, v, text, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
541
542                 v += 8;
543         }
544
545
546         if (key_dest == key_message)
547         {
548                 clearnotify = 0;
549
550                 x = 0;
551
552                 // LordHavoc: speedup, and other improvements
553                 if (chat_team)
554                         sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
555                 else
556                         sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
557                 while (strlen(temptext) >= (size_t) con_linewidth)
558                 {
559                         DrawQ_String (0, v, temptext, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
560                         strcpy(temptext, &temptext[con_linewidth]);
561                         v += 8;
562                 }
563                 if (strlen(temptext) > 0)
564                 {
565                         DrawQ_String (0, v, temptext, 0, 8, 8, 1, 1, 1, 1, 0);
566                         v += 8;
567                 }
568         }
569
570         if (con_notifylines < v)
571                 con_notifylines = v;
572 }
573
574 /*
575 ================
576 Con_DrawConsole
577
578 Draws the console with the solid background
579 The typing input line at the bottom should only be drawn if typing is allowed
580 ================
581 */
582 extern cvar_t scr_conalpha;
583 extern char engineversion[40];
584 void Con_DrawConsole (int lines)
585 {
586         int i, y, rows, j;
587         char *text;
588
589         if (lines <= 0)
590                 return;
591
592 // draw the background
593         DrawQ_Pic(0, lines - vid.conheight, "gfx/conback", vid.conwidth, vid.conheight, 1, 1, 1, scr_conalpha.value * lines / vid.conheight, 0);
594         DrawQ_String(vid.conwidth - strlen(engineversion) * 8 - 8, lines - 8, engineversion, 0, 8, 8, 1, 0, 0, 1, 0);
595
596 // draw the text
597         con_vislines = lines;
598
599         rows = (lines-16)>>3;           // rows of text to draw
600         y = lines - 16 - (rows<<3);     // may start slightly negative
601
602         for (i = con_current - rows + 1;i <= con_current;i++, y += 8)
603         {
604                 j = max(i - con_backscroll, 0);
605                 text = con_text + (j % con_totallines)*con_linewidth;
606
607                 DrawQ_String(0, y, text, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
608         }
609
610 // draw the input prompt, user text, and cursor if desired
611         Con_DrawInput ();
612 }
613
614 /*
615         Con_DisplayList
616
617         New function for tab-completion system
618         Added by EvilTypeGuy
619         MEGA Thanks to Taniwha
620
621 */
622 void Con_DisplayList(const char **list)
623 {
624         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
625         const char **walk = list;
626
627         while (*walk) {
628                 len = strlen(*walk);
629                 if (len > maxlen)
630                         maxlen = len;
631                 walk++;
632         }
633         maxlen += 1;
634
635         while (*list) {
636                 len = strlen(*list);
637                 if (pos + maxlen >= width) {
638                         Con_Printf("\n");
639                         pos = 0;
640                 }
641
642                 Con_Printf("%s", *list);
643                 for (i = 0; i < (maxlen - len); i++)
644                         Con_Printf(" ");
645
646                 pos += maxlen;
647                 list++;
648         }
649
650         if (pos)
651                 Con_Printf("\n\n");
652 }
653
654 /*
655         Con_CompleteCommandLine
656
657         New function for tab-completion system
658         Added by EvilTypeGuy
659         Thanks to Fett erich@heintz.com
660         Thanks to taniwha
661
662 */
663 void Con_CompleteCommandLine (void)
664 {
665         const char *cmd = "", *s;
666         const char **list[3] = {0, 0, 0};
667         int c, v, a, i, cmd_len;
668
669         s = key_lines[edit_line] + 1;
670         // Count number of possible matches
671         c = Cmd_CompleteCountPossible(s);
672         v = Cvar_CompleteCountPossible(s);
673         a = Cmd_CompleteAliasCountPossible(s);
674
675         if (!(c + v + a))       // No possible matches
676                 return;
677
678         if (c + v + a == 1) {
679                 if (c)
680                         list[0] = Cmd_CompleteBuildList(s);
681                 else if (v)
682                         list[0] = Cvar_CompleteBuildList(s);
683                 else
684                         list[0] = Cmd_CompleteAliasBuildList(s);
685                 cmd = *list[0];
686                 cmd_len = strlen (cmd);
687         } else {
688                 if (c)
689                         cmd = *(list[0] = Cmd_CompleteBuildList(s));
690                 if (v)
691                         cmd = *(list[1] = Cvar_CompleteBuildList(s));
692                 if (a)
693                         cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
694
695                 cmd_len = strlen (s);
696                 do {
697                         for (i = 0; i < 3; i++) {
698                                 char ch = cmd[cmd_len];
699                                 const char **l = list[i];
700                                 if (l) {
701                                         while (*l && (*l)[cmd_len] == ch)
702                                                 l++;
703                                         if (*l)
704                                                 break;
705                                 }
706                         }
707                         if (i == 3)
708                                 cmd_len++;
709                 } while (i == 3);
710                 // 'quakebar'
711                 Con_Printf("\n\35");
712                 for (i = 0; i < con_linewidth - 4; i++)
713                         Con_Printf("\36");
714                 Con_Printf("\37\n");
715
716                 // Print Possible Commands
717                 if (c) {
718                         Con_Printf("%i possible command%s\n", c, (c > 1) ? "s: " : ":");
719                         Con_DisplayList(list[0]);
720                 }
721
722                 if (v) {
723                         Con_Printf("%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
724                         Con_DisplayList(list[1]);
725                 }
726
727                 if (a) {
728                         Con_Printf("%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
729                         Con_DisplayList(list[2]);
730                 }
731         }
732
733         if (cmd) {
734                 strncpy(key_lines[edit_line] + 1, cmd, cmd_len);
735                 key_linepos = cmd_len + 1;
736                 if (c + v + a == 1) {
737                         key_lines[edit_line][key_linepos] = ' ';
738                         key_linepos++;
739                 }
740                 key_lines[edit_line][key_linepos] = 0;
741         }
742         for (i = 0; i < 3; i++)
743                 if (list[i])
744                         Mem_Free((void *)list[i]);
745 }
746