1 /*
2 ** c_console.cpp
3 ** Implements the console itself
4 **
5 **---------------------------------------------------------------------------
6 ** Copyright 1998-2006 Randy Heit
7 ** All rights reserved.
8 **
9 ** Redistribution and use in source and binary forms, with or without
10 ** modification, are permitted provided that the following conditions
11 ** are met:
12 **
13 ** 1. Redistributions of source code must retain the above copyright
14 **    notice, this list of conditions and the following disclaimer.
15 ** 2. Redistributions in binary form must reproduce the above copyright
16 **    notice, this list of conditions and the following disclaimer in the
17 **    documentation and/or other materials provided with the distribution.
18 ** 3. The name of the author may not be used to endorse or promote products
19 **    derived from this software without specific prior written permission.
20 **
21 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 **---------------------------------------------------------------------------
32 **
33 */
34 
35 #include "templates.h"
36 #include "p_setup.h"
37 #include <stdarg.h>
38 #include <string.h>
39 #include <math.h>
40 #include <stdlib.h>
41 
42 #include "version.h"
43 #include "g_game.h"
44 #include "c_bind.h"
45 #include "c_console.h"
46 #include "c_cvars.h"
47 #include "c_dispatch.h"
48 #include "hu_stuff.h"
49 #include "i_system.h"
50 #include "i_video.h"
51 #include "i_input.h"
52 #include "m_swap.h"
53 #include "v_palette.h"
54 #include "v_video.h"
55 #include "v_text.h"
56 #include "w_wad.h"
57 #include "sbar.h"
58 #include "s_sound.h"
59 #include "s_sndseq.h"
60 #include "doomstat.h"
61 #include "d_gui.h"
62 #include "v_video.h"
63 #include "cmdlib.h"
64 #include "d_net.h"
65 #include "g_level.h"
66 #include "d_event.h"
67 #include "d_player.h"
68 #include "c_consolebuffer.h"
69 
70 #include "gi.h"
71 
72 #define LEFTMARGIN 8
73 #define RIGHTMARGIN 8
74 #define BOTTOMARGIN 12
75 
76 
77 CUSTOM_CVAR(Int, con_buffersize, -1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
78 {
79 	// ensure a minimum size
80 	if (self >= 0 && self < 128) self = 128;
81 }
82 
83 FConsoleBuffer *conbuffer;
84 
85 static void C_TabComplete (bool goForward);
86 static bool C_TabCompleteList ();
87 static bool TabbedLast;		// True if last key pressed was tab
88 static bool TabbedList;		// True if tab list was shown
89 CVAR(Bool, con_notablist, false, CVAR_ARCHIVE)
90 
91 
92 static FTextureID conback;
93 static DWORD conshade;
94 static bool conline;
95 
96 extern int		gametic;
97 extern bool		automapactive;	// in AM_map.c
98 extern bool		advancedemo;
99 
100 extern FBaseCVar *CVars;
101 extern FConsoleCommand *Commands[FConsoleCommand::HASH_SIZE];
102 
103 int			ConCols, PhysRows;
104 int			ConWidth;
105 bool		vidactive = false;
106 bool		cursoron = false;
107 int			ConBottom, ConScroll, RowAdjust;
108 int			CursorTicker;
109 constate_e	ConsoleState = c_up;
110 
111 
112 static int TopLine, InsertLine;
113 
114 static void ClearConsole ();
115 static void C_PasteText(FString clip, BYTE *buffer, int len);
116 
117 struct GameAtExit
118 {
119 	GameAtExit *Next;
120 	char Command[1];
121 };
122 
123 static GameAtExit *ExitCmdList;
124 
125 #define SCROLLUP 1
126 #define SCROLLDN 2
127 #define SCROLLNO 0
128 
129 EXTERN_CVAR (Bool, show_messages)
130 
131 static unsigned int TickerAt, TickerMax;
132 static bool TickerPercent;
133 static const char *TickerLabel;
134 
135 static bool TickerVisible;
136 static bool ConsoleDrawing;
137 
138 // Buffer for AddToConsole()
139 static char *work = NULL;
140 static int worklen = 0;
141 
142 
143 struct History
144 {
145 	struct History *Older;
146 	struct History *Newer;
147 	char String[1];
148 };
149 
150 // CmdLine[0]  = # of chars on command line
151 // CmdLine[1]  = cursor position
152 // CmdLine[2+] = command line (max 255 chars + NULL)
153 // CmdLine[259]= offset from beginning of cmdline to display
154 static BYTE CmdLine[260];
155 
156 #define MAXHISTSIZE 50
157 static struct History *HistHead = NULL, *HistTail = NULL, *HistPos = NULL;
158 static int HistSize;
159 
160 CVAR (Float, con_notifytime, 3.f, CVAR_ARCHIVE)
CVAR(Bool,con_centernotify,false,CVAR_ARCHIVE)161 CVAR (Bool, con_centernotify, false, CVAR_ARCHIVE)
162 CUSTOM_CVAR (Int, con_scaletext, 0, CVAR_ARCHIVE)		// Scale notify text at high resolutions?
163 {
164 	if (self < 0) self = 0;
165 	if (self > 2) self = 2;
166 }
167 
168 CUSTOM_CVAR(Float, con_alpha, 0.75f, CVAR_ARCHIVE)
169 {
170 	if (self < 0.f) self = 0.f;
171 	if (self > 1.f) self = 1.f;
172 }
173 
174 // Command to run when Ctrl-D is pressed at start of line
175 CVAR (String, con_ctrl_d, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
176 
177 #define NUMNOTIFIES 4
178 #define NOTIFYFADETIME 6
179 
180 static struct NotifyText
181 {
182 	int TimeOut;
183 	int PrintLevel;
184 	FString Text;
185 } NotifyStrings[NUMNOTIFIES];
186 
187 static int NotifyTop, NotifyTopGoal;
188 
189 int PrintColors[PRINTLEVELS+2] = { CR_RED, CR_GOLD, CR_GRAY, CR_GREEN, CR_GREEN, CR_GOLD };
190 
191 static void setmsgcolor (int index, int color);
192 
193 FILE *Logfile = NULL;
194 
195 void C_AddNotifyString (int printlevel, const char *source);
196 
197 
198 FIntCVar msglevel ("msg", 0, CVAR_ARCHIVE);
199 
200 CUSTOM_CVAR (Int, msg0color, 6, CVAR_ARCHIVE)
201 {
202 	setmsgcolor (0, self);
203 }
204 
205 CUSTOM_CVAR (Int, msg1color, 5, CVAR_ARCHIVE)
206 {
207 	setmsgcolor (1, self);
208 }
209 
210 CUSTOM_CVAR (Int, msg2color, 2, CVAR_ARCHIVE)
211 {
212 	setmsgcolor (2, self);
213 }
214 
215 CUSTOM_CVAR (Int, msg3color, 3, CVAR_ARCHIVE)
216 {
217 	setmsgcolor (3, self);
218 }
219 
220 CUSTOM_CVAR (Int, msg4color, 3, CVAR_ARCHIVE)
221 {
222 	setmsgcolor (4, self);
223 }
224 
225 CUSTOM_CVAR (Int, msgmidcolor, 5, CVAR_ARCHIVE)
226 {
227 	setmsgcolor (PRINTLEVELS, self);
228 }
229 
230 CUSTOM_CVAR (Int, msgmidcolor2, 4, CVAR_ARCHIVE)
231 {
232 	setmsgcolor (PRINTLEVELS+1, self);
233 }
234 
maybedrawnow(bool tick,bool force)235 static void maybedrawnow (bool tick, bool force)
236 {
237 	// FIXME: Does not work right with hw2d
238 	if (ConsoleDrawing || screen == NULL || screen->IsLocked () || screen->Accel2D || ConFont == NULL)
239 	{
240 		return;
241 	}
242 
243 	if (vidactive &&
244 		(((tick || gameaction != ga_nothing) && ConsoleState == c_down)
245 		|| gamestate == GS_STARTUP))
246 	{
247 		static size_t lastprinttime = 0;
248 		size_t nowtime = I_GetTime(false);
249 
250 		if (nowtime - lastprinttime > 1 || force)
251 		{
252 			screen->Lock (false);
253 			C_DrawConsole (false);
254 			screen->Update ();
255 			lastprinttime = nowtime;
256 		}
257 	}
258 }
259 
260 struct TextQueue
261 {
TextQueueTextQueue262 	TextQueue (bool notify, int printlevel, const char *text)
263 		: Next(NULL), bNotify(notify), PrintLevel(printlevel), Text(text)
264 	{
265 	}
266 	TextQueue *Next;
267 	bool bNotify;
268 	int PrintLevel;
269 	FString Text;
270 };
271 
272 TextQueue *EnqueuedText, **EnqueuedTextTail = &EnqueuedText;
273 
EnqueueConsoleText(bool notify,int printlevel,const char * text)274 void EnqueueConsoleText (bool notify, int printlevel, const char *text)
275 {
276 	TextQueue *queued = new TextQueue (notify, printlevel, text);
277 	*EnqueuedTextTail = queued;
278 	EnqueuedTextTail = &queued->Next;
279 }
280 
DequeueConsoleText()281 void DequeueConsoleText ()
282 {
283 	TextQueue *queued = EnqueuedText;
284 
285 	while (queued != NULL)
286 	{
287 		TextQueue *next = queued->Next;
288 		if (queued->bNotify)
289 		{
290 			C_AddNotifyString (queued->PrintLevel, queued->Text);
291 		}
292 		else
293 		{
294 			AddToConsole (queued->PrintLevel, queued->Text);
295 		}
296 		delete queued;
297 		queued = next;
298 	}
299 	EnqueuedText = NULL;
300 	EnqueuedTextTail = &EnqueuedText;
301 }
302 
C_InitConback()303 void C_InitConback()
304 {
305 	conback = TexMan.CheckForTexture ("CONBACK", FTexture::TEX_MiscPatch);
306 
307 	if (!conback.isValid())
308 	{
309 		conback = TexMan.GetTexture (gameinfo.TitlePage, FTexture::TEX_MiscPatch);
310 		conshade = MAKEARGB(175,0,0,0);
311 		conline = true;
312 	}
313 	else
314 	{
315 		conshade = 0;
316 		conline = false;
317 	}
318 }
319 
C_InitConsole(int width,int height,bool ingame)320 void C_InitConsole (int width, int height, bool ingame)
321 {
322 	int cwidth, cheight;
323 
324 	vidactive = ingame;
325 	if (ConFont != NULL)
326 	{
327 		cwidth = ConFont->GetCharWidth ('M');
328 		cheight = ConFont->GetHeight();
329 	}
330 	else
331 	{
332 		cwidth = cheight = 8;
333 	}
334 	ConWidth = (width - LEFTMARGIN - RIGHTMARGIN);
335 	ConCols = ConWidth / cwidth;
336 	PhysRows = height / cheight;
337 
338 	if (conbuffer == NULL) conbuffer = new FConsoleBuffer;
339 }
340 
341 //==========================================================================
342 //
343 // CCMD atexit
344 //
345 //==========================================================================
346 
CCMD(atexit)347 CCMD (atexit)
348 {
349 	if (argv.argc() == 1)
350 	{
351 		Printf ("Registered atexit commands:\n");
352 		GameAtExit *record = ExitCmdList;
353 		while (record != NULL)
354 		{
355 			Printf ("%s\n", record->Command);
356 			record = record->Next;
357 		}
358 		return;
359 	}
360 	for (int i = 1; i < argv.argc(); ++i)
361 	{
362 		GameAtExit *record = (GameAtExit *)M_Malloc (
363 			sizeof(GameAtExit)+strlen(argv[i]));
364 		strcpy (record->Command, argv[i]);
365 		record->Next = ExitCmdList;
366 		ExitCmdList = record;
367 	}
368 }
369 
370 //==========================================================================
371 //
372 // C_DeinitConsole
373 //
374 // Executes the contents of the atexit cvar, if any, at quit time.
375 // Then releases all of the console's memory.
376 //
377 //==========================================================================
378 
C_DeinitConsole()379 void C_DeinitConsole ()
380 {
381 	GameAtExit *cmd = ExitCmdList;
382 
383 	while (cmd != NULL)
384 	{
385 		GameAtExit *next = cmd->Next;
386 		AddCommandString (cmd->Command);
387 		M_Free (cmd);
388 		cmd = next;
389 	}
390 
391 	// Free command history
392 	History *hist = HistTail;
393 
394 	while (hist != NULL)
395 	{
396 		History *next = hist->Newer;
397 		free (hist);
398 		hist = next;
399 	}
400 	HistTail = HistHead = HistPos = NULL;
401 
402 	// Free cvars allocated at runtime
403 	FBaseCVar *var, *next, **nextp;
404 	for (var = CVars, nextp = &CVars; var != NULL; var = next)
405 	{
406 		next = var->m_Next;
407 		if (var->GetFlags() & CVAR_UNSETTABLE)
408 		{
409 			delete var;
410 			*nextp = next;
411 		}
412 		else
413 		{
414 			nextp = &var->m_Next;
415 		}
416 	}
417 
418 	// Free alias commands. (i.e. The "commands" that can be allocated
419 	// at runtime.)
420 	for (size_t i = 0; i < countof(Commands); ++i)
421 	{
422 		FConsoleCommand *cmd = Commands[i];
423 
424 		while (cmd != NULL)
425 		{
426 			FConsoleCommand *next = cmd->m_Next;
427 			if (cmd->IsAlias())
428 			{
429 				delete cmd;
430 			}
431 			cmd = next;
432 		}
433 	}
434 
435 	// Make sure all tab commands are cleared before the memory for
436 	// their names is deallocated.
437 	C_ClearTabCommands ();
438 
439 	// Free AddToConsole()'s work buffer
440 	if (work != NULL)
441 	{
442 		free (work);
443 		work = NULL;
444 		worklen = 0;
445 	}
446 
447 	if (conbuffer != NULL)
448 	{
449 		delete conbuffer;
450 		conbuffer = NULL;
451 	}
452 }
453 
ClearConsole()454 static void ClearConsole ()
455 {
456 	if (conbuffer != NULL)
457 	{
458 		conbuffer->Clear();
459 	}
460 	TopLine = InsertLine = 0;
461 }
462 
setmsgcolor(int index,int color)463 static void setmsgcolor (int index, int color)
464 {
465 	if ((unsigned)color >= (unsigned)NUM_TEXT_COLORS)
466 		color = 0;
467 	PrintColors[index] = color;
468 }
469 
470 extern int DisplayWidth;
471 
C_AddNotifyString(int printlevel,const char * source)472 void C_AddNotifyString (int printlevel, const char *source)
473 {
474 	static enum
475 	{
476 		NEWLINE,
477 		APPENDLINE,
478 		REPLACELINE
479 	} addtype = NEWLINE;
480 
481 	FBrokenLines *lines;
482 	int i, len, width;
483 
484 	if ((printlevel != 128 && !show_messages) ||
485 		!(len = (int)strlen (source)) ||
486 		gamestate == GS_FULLCONSOLE ||
487 		gamestate == GS_DEMOSCREEN)
488 		return;
489 
490 	if (ConsoleDrawing)
491 	{
492 		EnqueueConsoleText (true, printlevel, source);
493 		return;
494 	}
495 
496 	width = con_scaletext > 1 ? DisplayWidth/2 : con_scaletext == 1 ? DisplayWidth / CleanXfac : DisplayWidth;
497 
498 	if (addtype == APPENDLINE && NotifyStrings[NUMNOTIFIES-1].PrintLevel == printlevel)
499 	{
500 		FString str = NotifyStrings[NUMNOTIFIES-1].Text + source;
501 		lines = V_BreakLines (SmallFont, width, str);
502 	}
503 	else
504 	{
505 		lines = V_BreakLines (SmallFont, width, source);
506 		addtype = (addtype == APPENDLINE) ? NEWLINE : addtype;
507 	}
508 
509 	if (lines == NULL)
510 		return;
511 
512 	for (i = 0; lines[i].Width >= 0; i++)
513 	{
514 		if (addtype == NEWLINE)
515 		{
516 			for (int j = 0; j < NUMNOTIFIES-1; ++j)
517 			{
518 				NotifyStrings[j] = NotifyStrings[j+1];
519 			}
520 		}
521 		NotifyStrings[NUMNOTIFIES-1].Text = lines[i].Text;
522 		NotifyStrings[NUMNOTIFIES-1].TimeOut = gametic + (int)(con_notifytime * TICRATE);
523 		NotifyStrings[NUMNOTIFIES-1].PrintLevel = printlevel;
524 		addtype = NEWLINE;
525 	}
526 
527 	V_FreeBrokenLines (lines);
528 	lines = NULL;
529 
530 	switch (source[len-1])
531 	{
532 	case '\r':	addtype = REPLACELINE;	break;
533 	case '\n':	addtype = NEWLINE;		break;
534 	default:	addtype = APPENDLINE;	break;
535 	}
536 
537 	NotifyTopGoal = 0;
538 }
539 
AddToConsole(int printlevel,const char * text)540 void AddToConsole (int printlevel, const char *text)
541 {
542 	conbuffer->AddText(printlevel, text, Logfile);
543 }
544 
545 /* Adds a string to the console and also to the notify buffer */
PrintString(int printlevel,const char * outline)546 int PrintString (int printlevel, const char *outline)
547 {
548 	if (printlevel < msglevel || *outline == '\0')
549 	{
550 		return 0;
551 	}
552 
553 	if (printlevel != PRINT_LOG)
554 	{
555 		I_PrintStr (outline);
556 
557 		AddToConsole (printlevel, outline);
558 		if (vidactive && screen && SmallFont)
559 		{
560 			C_AddNotifyString (printlevel, outline);
561 			maybedrawnow (false, false);
562 		}
563 	}
564 	else if (Logfile != NULL)
565 	{
566 		fputs (outline, Logfile);
567 		fflush (Logfile);
568 	}
569 	return (int)strlen (outline);
570 }
571 
572 extern bool gameisdead;
573 
VPrintf(int printlevel,const char * format,va_list parms)574 int VPrintf (int printlevel, const char *format, va_list parms)
575 {
576 	if (gameisdead)
577 		return 0;
578 
579 	FString outline;
580 	outline.VFormat (format, parms);
581 	return PrintString (printlevel, outline.GetChars());
582 }
583 
Printf(int printlevel,const char * format,...)584 int STACK_ARGS Printf (int printlevel, const char *format, ...)
585 {
586 	va_list argptr;
587 	int count;
588 
589 	va_start (argptr, format);
590 	count = VPrintf (printlevel, format, argptr);
591 	va_end (argptr);
592 
593 	return count;
594 }
595 
Printf(const char * format,...)596 int STACK_ARGS Printf (const char *format, ...)
597 {
598 	va_list argptr;
599 	int count;
600 
601 	va_start (argptr, format);
602 	count = VPrintf (PRINT_HIGH, format, argptr);
603 	va_end (argptr);
604 
605 	return count;
606 }
607 
DPrintf(const char * format,...)608 int STACK_ARGS DPrintf (const char *format, ...)
609 {
610 	va_list argptr;
611 	int count;
612 
613 	if (developer)
614 	{
615 		va_start (argptr, format);
616 		count = VPrintf (PRINT_HIGH, format, argptr);
617 		va_end (argptr);
618 		return count;
619 	}
620 	else
621 	{
622 		return 0;
623 	}
624 }
625 
C_FlushDisplay()626 void C_FlushDisplay ()
627 {
628 	int i;
629 
630 	for (i = 0; i < NUMNOTIFIES; i++)
631 		NotifyStrings[i].TimeOut = 0;
632 }
633 
C_AdjustBottom()634 void C_AdjustBottom ()
635 {
636 	if (gamestate == GS_FULLCONSOLE || gamestate == GS_STARTUP)
637 		ConBottom = SCREENHEIGHT;
638 	else if (ConBottom > SCREENHEIGHT / 2 || ConsoleState == c_down)
639 		ConBottom = SCREENHEIGHT / 2;
640 }
641 
C_NewModeAdjust()642 void C_NewModeAdjust ()
643 {
644 	C_InitConsole (SCREENWIDTH, SCREENHEIGHT, true);
645 	C_FlushDisplay ();
646 	C_AdjustBottom ();
647 }
648 
649 int consoletic = 0;
C_Ticker()650 void C_Ticker ()
651 {
652 	static int lasttic = 0;
653 	consoletic++;
654 
655 	if (lasttic == 0)
656 		lasttic = consoletic - 1;
657 
658 	if (con_buffersize > 0)
659 	{
660 		conbuffer->ResizeBuffer(con_buffersize);
661 	}
662 
663 	if (ConsoleState != c_up)
664 	{
665 		if (ConsoleState == c_falling)
666 		{
667 			ConBottom += (consoletic - lasttic) * (SCREENHEIGHT * 2 / 25);
668 			if (ConBottom >= SCREENHEIGHT / 2)
669 			{
670 				ConBottom = SCREENHEIGHT / 2;
671 				ConsoleState = c_down;
672 			}
673 		}
674 		else if (ConsoleState == c_rising)
675 		{
676 			ConBottom -= (consoletic - lasttic) * (SCREENHEIGHT * 2 / 25);
677 			if (ConBottom <= 0)
678 			{
679 				ConsoleState = c_up;
680 				ConBottom = 0;
681 			}
682 		}
683 	}
684 
685 	if (--CursorTicker <= 0)
686 	{
687 		cursoron ^= 1;
688 		CursorTicker = C_BLINKRATE;
689 	}
690 
691 	lasttic = consoletic;
692 
693 	if (NotifyTopGoal > NotifyTop)
694 	{
695 		NotifyTop++;
696 	}
697 	else if (NotifyTopGoal < NotifyTop)
698 	{
699 		NotifyTop--;
700 	}
701 }
702 
C_DrawNotifyText()703 static void C_DrawNotifyText ()
704 {
705 	bool center = (con_centernotify != 0.f);
706 	int i, line, lineadv, color, j, skip;
707 	bool canskip;
708 
709 	if (gamestate == GS_FULLCONSOLE || gamestate == GS_DEMOSCREEN/* || menuactive != MENU_Off*/)
710 		return;
711 
712 	line = NotifyTop;
713 	skip = 0;
714 	canskip = true;
715 
716 	lineadv = SmallFont->GetHeight ();
717 	if (con_scaletext == 1)
718 	{
719 		lineadv *= CleanYfac;
720 	}
721 
722 	BorderTopRefresh = screen->GetPageCount ();
723 
724 	for (i = 0; i < NUMNOTIFIES; i++)
725 	{
726 		if (NotifyStrings[i].TimeOut == 0)
727 			continue;
728 
729 		j = NotifyStrings[i].TimeOut - gametic;
730 		if (j > 0)
731 		{
732 			if (!show_messages && NotifyStrings[i].PrintLevel != 128)
733 				continue;
734 
735 			fixed_t alpha;
736 
737 			if (j < NOTIFYFADETIME)
738 			{
739 				alpha = OPAQUE * j / NOTIFYFADETIME;
740 			}
741 			else
742 			{
743 				alpha = OPAQUE;
744 			}
745 
746 			if (NotifyStrings[i].PrintLevel >= PRINTLEVELS)
747 				color = CR_UNTRANSLATED;
748 			else
749 				color = PrintColors[NotifyStrings[i].PrintLevel];
750 
751 			if (con_scaletext == 1)
752 			{
753 				if (!center)
754 					screen->DrawText (SmallFont, color, 0, line, NotifyStrings[i].Text,
755 						DTA_CleanNoMove, true, DTA_Alpha, alpha, TAG_DONE);
756 				else
757 					screen->DrawText (SmallFont, color, (SCREENWIDTH -
758 						SmallFont->StringWidth (NotifyStrings[i].Text)*CleanXfac)/2,
759 						line, NotifyStrings[i].Text, DTA_CleanNoMove, true,
760 						DTA_Alpha, alpha, TAG_DONE);
761 			}
762 			else if (con_scaletext == 0)
763 			{
764 				if (!center)
765 					screen->DrawText (SmallFont, color, 0, line, NotifyStrings[i].Text,
766 						DTA_Alpha, alpha, TAG_DONE);
767 				else
768 					screen->DrawText (SmallFont, color, (SCREENWIDTH -
769 						SmallFont->StringWidth (NotifyStrings[i].Text))/2,
770 						line, NotifyStrings[i].Text,
771 						DTA_Alpha, alpha, TAG_DONE);
772 			}
773 			else
774 			{
775 				if (!center)
776 					screen->DrawText (SmallFont, color, 0, line, NotifyStrings[i].Text,
777 						DTA_VirtualWidth, screen->GetWidth() / 2,
778 						DTA_VirtualHeight, screen->GetHeight() / 2,
779 						DTA_KeepRatio, true,
780 						DTA_Alpha, alpha, TAG_DONE);
781 				else
782 					screen->DrawText (SmallFont, color, (screen->GetWidth() / 2 -
783 						SmallFont->StringWidth (NotifyStrings[i].Text))/2,
784 						line, NotifyStrings[i].Text,
785 						DTA_VirtualWidth, screen->GetWidth() / 2,
786 						DTA_VirtualHeight, screen->GetHeight() / 2,
787 						DTA_KeepRatio, true,
788 						DTA_Alpha, alpha, TAG_DONE);
789 			}
790 			line += lineadv;
791 			canskip = false;
792 		}
793 		else
794 		{
795 			if (canskip)
796 			{
797 				NotifyTop += lineadv;
798 				line += lineadv;
799 				skip++;
800 			}
801 			NotifyStrings[i].TimeOut = 0;
802 		}
803 	}
804 	if (canskip)
805 	{
806 		NotifyTop = NotifyTopGoal;
807 	}
808 }
809 
C_InitTicker(const char * label,unsigned int max,bool showpercent)810 void C_InitTicker (const char *label, unsigned int max, bool showpercent)
811 {
812 	TickerPercent = showpercent;
813 	TickerMax = max;
814 	TickerLabel = label;
815 	TickerAt = 0;
816 	maybedrawnow (true, false);
817 }
818 
C_SetTicker(unsigned int at,bool forceUpdate)819 void C_SetTicker (unsigned int at, bool forceUpdate)
820 {
821 	TickerAt = at > TickerMax ? TickerMax : at;
822 	maybedrawnow (true, TickerVisible ? forceUpdate : false);
823 }
824 
C_DrawConsole(bool hw2d)825 void C_DrawConsole (bool hw2d)
826 {
827 	static int oldbottom = 0;
828 	int lines, left, offset;
829 
830 	left = LEFTMARGIN;
831 	lines = (ConBottom-ConFont->GetHeight()*2)/ConFont->GetHeight();
832 	if (-ConFont->GetHeight() + lines*ConFont->GetHeight() > ConBottom - ConFont->GetHeight()*7/2)
833 	{
834 		offset = -ConFont->GetHeight()/2;
835 		lines--;
836 	}
837 	else
838 	{
839 		offset = -ConFont->GetHeight();
840 	}
841 
842 	if ((ConBottom < oldbottom) &&
843 		(gamestate == GS_LEVEL || gamestate == GS_TITLELEVEL) &&
844 		(viewwindowx || viewwindowy) &&
845 		viewactive)
846 	{
847 		V_SetBorderNeedRefresh();
848 	}
849 
850 	oldbottom = ConBottom;
851 
852 	if (ConsoleState == c_up)
853 	{
854 		C_DrawNotifyText ();
855 		return;
856 	}
857 	else if (ConBottom)
858 	{
859 		int visheight;
860 		FTexture *conpic = TexMan[conback];
861 
862 		visheight = ConBottom;
863 
864 		screen->DrawTexture (conpic, 0, visheight - screen->GetHeight(),
865 			DTA_DestWidth, screen->GetWidth(),
866 			DTA_DestHeight, screen->GetHeight(),
867 			DTA_ColorOverlay, conshade,
868 			DTA_Alpha, (hw2d && gamestate != GS_FULLCONSOLE) ? FLOAT2FIXED(con_alpha) : FRACUNIT,
869 			DTA_Masked, false,
870 			TAG_DONE);
871 		if (conline && visheight < screen->GetHeight())
872 		{
873 			screen->Clear (0, visheight, screen->GetWidth(), visheight+1, 0, 0);
874 		}
875 
876 		if (ConBottom >= 12)
877 		{
878 			screen->DrawText (ConFont, CR_ORANGE, SCREENWIDTH - 8 -
879 				ConFont->StringWidth (GetVersionString()),
880 				ConBottom - ConFont->GetHeight() - 4,
881 				GetVersionString(), TAG_DONE);
882 			if (TickerMax)
883 			{
884 				char tickstr[256];
885 				const int tickerY = ConBottom - ConFont->GetHeight() - 4;
886 				size_t i;
887 				int tickend = ConCols - SCREENWIDTH / 90 - 6;
888 				int tickbegin = 0;
889 
890 				if (TickerLabel)
891 				{
892 					tickbegin = (int)strlen (TickerLabel) + 2;
893 					mysnprintf (tickstr, countof(tickstr), "%s: ", TickerLabel);
894 				}
895 				if (tickend > 256 - ConFont->GetCharWidth(0x12))
896 					tickend = 256 - ConFont->GetCharWidth(0x12);
897 				tickstr[tickbegin] = 0x10;
898 				memset (tickstr + tickbegin + 1, 0x11, tickend - tickbegin);
899 				tickstr[tickend + 1] = 0x12;
900 				tickstr[tickend + 2] = ' ';
901 				if (TickerPercent)
902 				{
903 					mysnprintf (tickstr + tickend + 3, countof(tickstr) - tickend - 3,
904 						"%d%%", Scale (TickerAt, 100, TickerMax));
905 				}
906 				else
907 				{
908 					tickstr[tickend+3] = 0;
909 				}
910 				screen->DrawText (ConFont, CR_BROWN, LEFTMARGIN, tickerY, tickstr, TAG_DONE);
911 
912 				// Draw the marker
913 				i = LEFTMARGIN+5+tickbegin*8 + Scale (TickerAt, (SDWORD)(tickend - tickbegin)*8, TickerMax);
914 				screen->DrawChar (ConFont, CR_ORANGE, (int)i, tickerY, 0x13, TAG_DONE);
915 
916 				TickerVisible = true;
917 			}
918 			else
919 			{
920 				TickerVisible = false;
921 			}
922 		}
923 
924 		// Apply palette blend effects
925 		if (StatusBar != NULL && !hw2d)
926 		{
927 			player_t *player = StatusBar->CPlayer;
928 			if (player->camera != NULL && player->camera->player != NULL)
929 			{
930 				player = player->camera->player;
931 			}
932 			if (player->BlendA != 0 && (gamestate == GS_LEVEL || gamestate == GS_TITLELEVEL))
933 			{
934 				screen->Dim (PalEntry ((unsigned char)(player->BlendR*255), (unsigned char)(player->BlendG*255), (unsigned char)(player->BlendB*255)),
935 					player->BlendA, 0, ConBottom, screen->GetWidth(), screen->GetHeight() - ConBottom);
936 				ST_SetNeedRefresh();
937 				V_SetBorderNeedRefresh();
938 			}
939 		}
940 	}
941 
942 	if (menuactive != MENU_Off)
943 	{
944 		return;
945 	}
946 
947 	if (lines > 0)
948 	{
949 		// No more enqueuing because adding new text to the console won't touch the actual print data.
950 		conbuffer->FormatText(ConFont, ConWidth);
951 		unsigned int consolelines = conbuffer->GetFormattedLineCount();
952 		FBrokenLines **blines = conbuffer->GetLines();
953 		FBrokenLines **printline = blines + consolelines - 1 - RowAdjust;
954 
955 		int bottomline = ConBottom - ConFont->GetHeight()*2 - 4;
956 
957 		ConsoleDrawing = true;
958 
959 		for(FBrokenLines **p = printline; p >= blines && lines > 0; p--, lines--)
960 		{
961 			screen->DrawText(ConFont, CR_TAN, LEFTMARGIN, offset + lines * ConFont->GetHeight(), (*p)->Text, TAG_DONE);
962 		}
963 
964 		ConsoleDrawing = false;
965 
966 		if (ConBottom >= 20)
967 		{
968 			if (gamestate != GS_STARTUP)
969 			{
970 				// Make a copy of the command line, in case an input event is handled
971 				// while we draw the console and it changes.
972 				CmdLine[2+CmdLine[0]] = 0;
973 				FString command((char *)&CmdLine[2+CmdLine[259]]);
974 				int cursorpos = CmdLine[1] - CmdLine[259];
975 
976 				screen->DrawChar (ConFont, CR_ORANGE, left, bottomline, '\x1c', TAG_DONE);
977 				screen->DrawText (ConFont, CR_ORANGE, left + ConFont->GetCharWidth(0x1c), bottomline,
978 					command, TAG_DONE);
979 
980 				if (cursoron)
981 				{
982 					screen->DrawChar (ConFont, CR_YELLOW, left + ConFont->GetCharWidth(0x1c) + cursorpos * ConFont->GetCharWidth(0xb),
983 						bottomline, '\xb', TAG_DONE);
984 				}
985 			}
986 			if (RowAdjust && ConBottom >= ConFont->GetHeight()*7/2)
987 			{
988 				// Indicate that the view has been scrolled up (10)
989 				// and if we can scroll no further (12)
990 				screen->DrawChar (ConFont, CR_GREEN, 0, bottomline, RowAdjust == conbuffer->GetFormattedLineCount() ? 12 : 10, TAG_DONE);
991 			}
992 		}
993 	}
994 }
995 
C_FullConsole()996 void C_FullConsole ()
997 {
998 	if (demoplayback)
999 		G_CheckDemoStatus ();
1000 	D_QuitNetGame ();
1001 	advancedemo = false;
1002 	ConsoleState = c_down;
1003 	HistPos = NULL;
1004 	TabbedLast = false;
1005 	TabbedList = false;
1006 	if (gamestate != GS_STARTUP)
1007 	{
1008 		gamestate = GS_FULLCONSOLE;
1009 		level.Music = "";
1010 		S_Start ();
1011 		P_FreeLevelData ();
1012 		V_SetBlend (0,0,0,0);
1013 	}
1014 	else
1015 	{
1016 		C_AdjustBottom ();
1017 	}
1018 }
1019 
C_ToggleConsole()1020 void C_ToggleConsole ()
1021 {
1022 	if (gamestate == GS_DEMOSCREEN || demoplayback)
1023 	{
1024 		gameaction = ga_fullconsole;
1025 	}
1026 	else if (!chatmodeon && (ConsoleState == c_up || ConsoleState == c_rising) && menuactive == MENU_Off)
1027 	{
1028 		ConsoleState = c_falling;
1029 		HistPos = NULL;
1030 		TabbedLast = false;
1031 		TabbedList = false;
1032 	}
1033 	else if (gamestate != GS_FULLCONSOLE && gamestate != GS_STARTUP)
1034 	{
1035 		ConsoleState = c_rising;
1036 		C_FlushDisplay ();
1037 	}
1038 }
1039 
C_HideConsole()1040 void C_HideConsole ()
1041 {
1042 	if (gamestate != GS_FULLCONSOLE)
1043 	{
1044 		ConsoleState = c_up;
1045 		ConBottom = 0;
1046 		HistPos = NULL;
1047 	}
1048 }
1049 
makestartposgood()1050 static void makestartposgood ()
1051 {
1052 	int n;
1053 	int pos = CmdLine[259];
1054 	int curs = CmdLine[1];
1055 	int len = CmdLine[0];
1056 
1057 	n = pos;
1058 
1059 	if (pos >= len)
1060 	{ // Start of visible line is beyond end of line
1061 		n = curs - ConCols + 2;
1062 	}
1063 	if ((curs - pos) >= ConCols - 2)
1064 	{ // The cursor is beyond the visible part of the line
1065 		n = curs - ConCols + 2;
1066 	}
1067 	if (pos > curs)
1068 	{ // The cursor is in front of the visible part of the line
1069 		n = curs;
1070 	}
1071 	if (n < 0)
1072 		n = 0;
1073 	CmdLine[259] = n;
1074 }
1075 
C_HandleKey(event_t * ev,BYTE * buffer,int len)1076 static bool C_HandleKey (event_t *ev, BYTE *buffer, int len)
1077 {
1078 	int i;
1079 	int data1 = ev->data1;
1080 
1081 	switch (ev->subtype)
1082 	{
1083 	default:
1084 		return false;
1085 
1086 	case EV_GUI_Char:
1087 		// Add keypress to command line
1088 		if (buffer[0] < len)
1089 		{
1090 			if (buffer[1] == buffer[0])
1091 			{
1092 				buffer[buffer[0] + 2] = BYTE(ev->data1);
1093 			}
1094 			else
1095 			{
1096 				char *c, *e;
1097 
1098 				e = (char *)&buffer[buffer[0] + 1];
1099 				c = (char *)&buffer[buffer[1] + 2];
1100 
1101 				for (; e >= c; e--)
1102 					*(e + 1) = *e;
1103 
1104 				*c = char(ev->data1);
1105 			}
1106 			buffer[0]++;
1107 			buffer[1]++;
1108 			makestartposgood ();
1109 			HistPos = NULL;
1110 		}
1111 		TabbedLast = false;
1112 		TabbedList = false;
1113 		break;
1114 
1115 	case EV_GUI_WheelUp:
1116 	case EV_GUI_WheelDown:
1117 		if (!(ev->data3 & GKM_SHIFT))
1118 		{
1119 			data1 = GK_PGDN + EV_GUI_WheelDown - ev->subtype;
1120 		}
1121 		else
1122 		{
1123 			data1 = GK_DOWN + EV_GUI_WheelDown - ev->subtype;
1124 		}
1125 		// Intentional fallthrough
1126 
1127 	case EV_GUI_KeyDown:
1128 	case EV_GUI_KeyRepeat:
1129 		switch (data1)
1130 		{
1131 		case '\t':
1132 			// Try to do tab-completion
1133 			C_TabComplete ((ev->data3 & GKM_SHIFT) ? false : true);
1134 			break;
1135 
1136 		case GK_PGUP:
1137 			if (ev->data3 & (GKM_SHIFT|GKM_CTRL))
1138 			{ // Scroll console buffer up one page
1139 				RowAdjust += (SCREENHEIGHT-4) /
1140 					((gamestate == GS_FULLCONSOLE || gamestate == GS_STARTUP) ? ConFont->GetHeight() : ConFont->GetHeight()*2) - 3;
1141 			}
1142 			else if (RowAdjust < conbuffer->GetFormattedLineCount())
1143 			{ // Scroll console buffer up
1144 				if (ev->subtype == EV_GUI_WheelUp)
1145 				{
1146 					RowAdjust += 3;
1147 				}
1148 				else
1149 				{
1150 					RowAdjust++;
1151 				}
1152 				if (RowAdjust > conbuffer->GetFormattedLineCount())
1153 				{
1154 					RowAdjust = conbuffer->GetFormattedLineCount();
1155 				}
1156 			}
1157 			break;
1158 
1159 		case GK_PGDN:
1160 			if (ev->data3 & (GKM_SHIFT|GKM_CTRL))
1161 			{ // Scroll console buffer down one page
1162 				const int scrollamt = (SCREENHEIGHT-4) /
1163 					((gamestate == GS_FULLCONSOLE || gamestate == GS_STARTUP) ? ConFont->GetHeight() : ConFont->GetHeight()*2) - 3;
1164 				if (RowAdjust < scrollamt)
1165 				{
1166 					RowAdjust = 0;
1167 				}
1168 				else
1169 				{
1170 					RowAdjust -= scrollamt;
1171 				}
1172 			}
1173 			else if (RowAdjust > 0)
1174 			{ // Scroll console buffer down
1175 				if (ev->subtype == EV_GUI_WheelDown)
1176 				{
1177 					RowAdjust = MAX (0, RowAdjust - 3);
1178 				}
1179 				else
1180 				{
1181 					RowAdjust--;
1182 				}
1183 			}
1184 			break;
1185 
1186 		case GK_HOME:
1187 			if (ev->data3 & GKM_CTRL)
1188 			{ // Move to top of console buffer
1189 				RowAdjust = conbuffer->GetFormattedLineCount();
1190 			}
1191 			else
1192 			{ // Move cursor to start of line
1193 				buffer[1] = buffer[len+4] = 0;
1194 			}
1195 			break;
1196 
1197 		case GK_END:
1198 			if (ev->data3 & GKM_CTRL)
1199 			{ // Move to bottom of console buffer
1200 				RowAdjust = 0;
1201 			}
1202 			else
1203 			{ // Move cursor to end of line
1204 				buffer[1] = buffer[0];
1205 				makestartposgood ();
1206 			}
1207 			break;
1208 
1209 		case GK_LEFT:
1210 			// Move cursor left one character
1211 			if (buffer[1])
1212 			{
1213 				buffer[1]--;
1214 				makestartposgood ();
1215 			}
1216 			break;
1217 
1218 		case GK_RIGHT:
1219 			// Move cursor right one character
1220 			if (buffer[1] < buffer[0])
1221 			{
1222 				buffer[1]++;
1223 				makestartposgood ();
1224 			}
1225 			break;
1226 
1227 		case '\b':
1228 			// Erase character to left of cursor
1229 			if (buffer[0] && buffer[1])
1230 			{
1231 				char *c, *e;
1232 
1233 				e = (char *)&buffer[buffer[0] + 2];
1234 				c = (char *)&buffer[buffer[1] + 2];
1235 
1236 				for (; c < e; c++)
1237 					*(c - 1) = *c;
1238 
1239 				buffer[0]--;
1240 				buffer[1]--;
1241 				if (buffer[len+4])
1242 					buffer[len+4]--;
1243 				makestartposgood ();
1244 			}
1245 			TabbedLast = false;
1246 			TabbedList = false;
1247 			break;
1248 
1249 		case GK_DEL:
1250 			// Erase character under cursor
1251 			if (buffer[1] < buffer[0])
1252 			{
1253 				char *c, *e;
1254 
1255 				e = (char *)&buffer[buffer[0] + 2];
1256 				c = (char *)&buffer[buffer[1] + 3];
1257 
1258 				for (; c < e; c++)
1259 					*(c - 1) = *c;
1260 
1261 				buffer[0]--;
1262 				makestartposgood ();
1263 			}
1264 			TabbedLast = false;
1265 			TabbedList = false;
1266 			break;
1267 
1268 		case GK_UP:
1269 			// Move to previous entry in the command history
1270 			if (HistPos == NULL)
1271 			{
1272 				HistPos = HistHead;
1273 			}
1274 			else if (HistPos->Older)
1275 			{
1276 				HistPos = HistPos->Older;
1277 			}
1278 
1279 			if (HistPos)
1280 			{
1281 				strcpy ((char *)&buffer[2], HistPos->String);
1282 				buffer[0] = buffer[1] = (BYTE)strlen ((char *)&buffer[2]);
1283 				buffer[len+4] = 0;
1284 				makestartposgood();
1285 			}
1286 
1287 			TabbedLast = false;
1288 			TabbedList = false;
1289 			break;
1290 
1291 		case GK_DOWN:
1292 			// Move to next entry in the command history
1293 			if (HistPos && HistPos->Newer)
1294 			{
1295 				HistPos = HistPos->Newer;
1296 
1297 				strcpy ((char *)&buffer[2], HistPos->String);
1298 				buffer[0] = buffer[1] = (BYTE)strlen ((char *)&buffer[2]);
1299 			}
1300 			else
1301 			{
1302 				HistPos = NULL;
1303 				buffer[0] = buffer[1] = 0;
1304 			}
1305 			buffer[len+4] = 0;
1306 			makestartposgood();
1307 			TabbedLast = false;
1308 			TabbedList = false;
1309 			break;
1310 
1311 		case 'X':
1312 			if (ev->data3 & GKM_CTRL)
1313 			{
1314 				buffer[1] = buffer[0] = 0;
1315 				TabbedLast = TabbedList = false;
1316 			}
1317 			break;
1318 
1319 		case 'D':
1320 			if (ev->data3 & GKM_CTRL && buffer[0] == 0)
1321 			{ // Control-D pressed on an empty line
1322 				int replen = (int)strlen (con_ctrl_d);
1323 
1324 				if (replen == 0)
1325 					break;	// Replacement is empty, so do nothing
1326 
1327 				if (replen > len)
1328 					replen = len;
1329 
1330 				memcpy (&buffer[2], con_ctrl_d, replen);
1331 				buffer[0] = buffer[1] = replen;
1332 			}
1333 			else
1334 			{
1335 				break;
1336 			}
1337 			// Intentional fall-through for command(s) added with Ctrl-D
1338 
1339 		case '\r':
1340 			// Execute command line (ENTER)
1341 
1342 			buffer[2 + buffer[0]] = 0;
1343 
1344 			for (i = 0; i < buffer[0] && isspace(buffer[2+i]); ++i)
1345 			{
1346 			}
1347 			if (i == buffer[0])
1348 			{
1349 				 // Command line is empty, so do nothing to the history
1350 			}
1351 			else if (HistHead && stricmp (HistHead->String, (char *)&buffer[2]) == 0)
1352 			{
1353 				// Command line was the same as the previous one,
1354 				// so leave the history list alone
1355 			}
1356 			else
1357 			{
1358 				// Command line is different from last command line,
1359 				// or there is nothing in the history list,
1360 				// so add it to the history list.
1361 
1362 				History *temp = (History *)M_Malloc (sizeof(struct History) + buffer[0]);
1363 
1364 				strcpy (temp->String, (char *)&buffer[2]);
1365 				temp->Older = HistHead;
1366 				if (HistHead)
1367 				{
1368 					HistHead->Newer = temp;
1369 				}
1370 				temp->Newer = NULL;
1371 				HistHead = temp;
1372 
1373 				if (!HistTail)
1374 				{
1375 					HistTail = temp;
1376 				}
1377 
1378 				if (HistSize == MAXHISTSIZE)
1379 				{
1380 					HistTail = HistTail->Newer;
1381 					M_Free (HistTail->Older);
1382 					HistTail->Older = NULL;
1383 				}
1384 				else
1385 				{
1386 					HistSize++;
1387 				}
1388 			}
1389 			HistPos = NULL;
1390 			Printf (127, TEXTCOLOR_WHITE "]%s\n", &buffer[2]);
1391 			buffer[0] = buffer[1] = buffer[len+4] = 0;
1392 			AddCommandString ((char *)&buffer[2]);
1393 			TabbedLast = false;
1394 			TabbedList = false;
1395 			break;
1396 
1397 		case '`':
1398 			// Check to see if we have ` bound to the console before accepting
1399 			// it as a way to close the console.
1400 			if (Bindings.GetBinding(KEY_GRAVE).CompareNoCase("toggleconsole"))
1401 			{
1402 				break;
1403 			}
1404 		case GK_ESCAPE:
1405 			// Close console and clear command line. But if we're in the
1406 			// fullscreen console mode, there's nothing to fall back on
1407 			// if it's closed, so open the main menu instead.
1408 			if (gamestate == GS_STARTUP)
1409 			{
1410 				return false;
1411 			}
1412 			else if (gamestate == GS_FULLCONSOLE)
1413 			{
1414 				C_DoCommand ("menu_main");
1415 			}
1416 			else
1417 			{
1418 				buffer[0] = buffer[1] = buffer[len+4] = 0;
1419 				HistPos = NULL;
1420 				C_ToggleConsole ();
1421 			}
1422 			break;
1423 
1424 		case 'C':
1425 		case 'V':
1426 			TabbedLast = false;
1427 			TabbedList = false;
1428 #ifdef __APPLE__
1429 			if (ev->data3 & GKM_META)
1430 #else // !__APPLE__
1431 			if (ev->data3 & GKM_CTRL)
1432 #endif // __APPLE__
1433 			{
1434 				if (data1 == 'C')
1435 				{ // copy to clipboard
1436 					if (buffer[0] > 0)
1437 					{
1438 						buffer[2 + buffer[0]] = 0;
1439 						I_PutInClipboard ((char *)&buffer[2]);
1440 					}
1441 				}
1442 				else
1443 				{ // paste from clipboard
1444 					C_PasteText(I_GetFromClipboard(false), buffer, len);
1445 				}
1446 				break;
1447 			}
1448 			break;
1449 		}
1450 		break;
1451 
1452 #ifdef __unix__
1453 	case EV_GUI_MButtonDown:
1454 		C_PasteText(I_GetFromClipboard(true), buffer, len);
1455 		break;
1456 #endif
1457 	}
1458 	// Ensure that the cursor is always visible while typing
1459 	CursorTicker = C_BLINKRATE;
1460 	cursoron = 1;
1461 	return true;
1462 }
1463 
C_PasteText(FString clip,BYTE * buffer,int len)1464 static void C_PasteText(FString clip, BYTE *buffer, int len)
1465 {
1466 	if (clip.IsNotEmpty())
1467 	{
1468 		// Only paste the first line.
1469 		long brk = clip.IndexOfAny("\r\n\b");
1470 		int cliplen = brk >= 0 ? brk : (int)clip.Len();
1471 
1472 		// Make sure there's room for the whole thing.
1473 		if (buffer[0] + cliplen > len)
1474 		{
1475 			cliplen = len - buffer[0];
1476 		}
1477 
1478 		if (cliplen > 0)
1479 		{
1480 			if (buffer[1] < buffer[0])
1481 			{
1482 				memmove (&buffer[2 + buffer[1] + cliplen],
1483 						 &buffer[2 + buffer[1]], buffer[0] - buffer[1]);
1484 			}
1485 			memcpy (&buffer[2 + buffer[1]], clip, cliplen);
1486 			buffer[0] += cliplen;
1487 			buffer[1] += cliplen;
1488 			makestartposgood ();
1489 			HistPos = NULL;
1490 		}
1491 	}
1492 }
1493 
C_Responder(event_t * ev)1494 bool C_Responder (event_t *ev)
1495 {
1496 	if (ev->type != EV_GUI_Event ||
1497 		ConsoleState == c_up ||
1498 		ConsoleState == c_rising ||
1499 		menuactive != MENU_Off)
1500 	{
1501 		return false;
1502 	}
1503 
1504 	return C_HandleKey (ev, CmdLine, 255);
1505 }
1506 
CCMD(history)1507 CCMD (history)
1508 {
1509 	struct History *hist = HistTail;
1510 
1511 	while (hist)
1512 	{
1513 		Printf ("   %s\n", hist->String);
1514 		hist = hist->Newer;
1515 	}
1516 }
1517 
CCMD(clear)1518 CCMD (clear)
1519 {
1520 	C_FlushDisplay ();
1521 	ClearConsole ();
1522 }
1523 
CCMD(echo)1524 CCMD (echo)
1525 {
1526 	int last = argv.argc()-1;
1527 	for (int i = 1; i <= last; ++i)
1528 	{
1529 		FString formatted = strbin1 (argv[i]);
1530 		Printf ("%s%s", formatted.GetChars(), i!=last ? " " : "\n");
1531 	}
1532 }
1533 
1534 /* Printing in the middle of the screen */
1535 
1536 CVAR (Float, con_midtime, 3.f, CVAR_ARCHIVE)
1537 
1538 static const char bar1[] = TEXTCOLOR_RED "\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36"
1539 						  "\36\36\36\36\36\36\36\36\36\36\36\36\37" TEXTCOLOR_TAN "\n";
1540 static const char bar2[] = TEXTCOLOR_RED "\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36"
1541 						  "\36\36\36\36\36\36\36\36\36\36\36\36\37" TEXTCOLOR_GREEN "\n";
1542 static const char bar3[] = TEXTCOLOR_RED "\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36"
1543 						  "\36\36\36\36\36\36\36\36\36\36\36\36\37" TEXTCOLOR_NORMAL "\n";
1544 static const char logbar[] = "\n<------------------------------->\n";
1545 
C_MidPrint(FFont * font,const char * msg)1546 void C_MidPrint (FFont *font, const char *msg)
1547 {
1548 	if (StatusBar == NULL || screen == NULL)
1549 		return;
1550 
1551 	if (msg != NULL)
1552 	{
1553 		AddToConsole (-1, bar1);
1554 		AddToConsole (-1, msg);
1555 		AddToConsole (-1, bar3);
1556 
1557 		StatusBar->AttachMessage (new DHUDMessage (font, msg, 1.5f, 0.375f, 0, 0,
1558 			(EColorRange)PrintColors[PRINTLEVELS], con_midtime), MAKE_ID('C','N','T','R'));
1559 	}
1560 	else
1561 	{
1562 		StatusBar->DetachMessage (MAKE_ID('C','N','T','R'));
1563 	}
1564 }
1565 
C_MidPrintBold(FFont * font,const char * msg)1566 void C_MidPrintBold (FFont *font, const char *msg)
1567 {
1568 	if (msg)
1569 	{
1570 		AddToConsole (-1, bar2);
1571 		AddToConsole (-1, msg);
1572 		AddToConsole (-1, bar3);
1573 
1574 		StatusBar->AttachMessage (new DHUDMessage (font, msg, 1.5f, 0.375f, 0, 0,
1575 			(EColorRange)PrintColors[PRINTLEVELS+1], con_midtime), MAKE_ID('C','N','T','R'));
1576 	}
1577 	else
1578 	{
1579 		StatusBar->DetachMessage (MAKE_ID('C','N','T','R'));
1580 	}
1581 }
1582 
1583 /****** Tab completion code ******/
1584 
1585 struct TabData
1586 {
1587 	int UseCount;
1588 	FName TabName;
1589 
TabDataTabData1590 	TabData()
1591 	: UseCount(0)
1592 	{
1593 	}
1594 
TabDataTabData1595 	TabData(const char *name)
1596 	: UseCount(1), TabName(name)
1597 	{
1598 	}
1599 
TabDataTabData1600 	TabData(const TabData &other)
1601 	: UseCount(other.UseCount), TabName(other.TabName)
1602 	{
1603 	}
1604 };
1605 
1606 static TArray<TabData> TabCommands (TArray<TabData>::NoInit);
1607 static int TabPos;				// Last TabCommand tabbed to
1608 static int TabStart;			// First char in CmdLine to use for tab completion
1609 static int TabSize;				// Size of tab string
1610 
FindTabCommand(const char * name,int * stoppos,int len)1611 static bool FindTabCommand (const char *name, int *stoppos, int len)
1612 {
1613 	FName aname(name);
1614 	unsigned int i;
1615 	int cval = 1;
1616 
1617 	for (i = 0; i < TabCommands.Size(); i++)
1618 	{
1619 		if (TabCommands[i].TabName == aname)
1620 		{
1621 			*stoppos = i;
1622 			return true;
1623 		}
1624 		cval = strnicmp (TabCommands[i].TabName.GetChars(), name, len);
1625 		if (cval >= 0)
1626 			break;
1627 	}
1628 
1629 	*stoppos = i;
1630 
1631 	return (cval == 0);
1632 }
1633 
C_AddTabCommand(const char * name)1634 void C_AddTabCommand (const char *name)
1635 {
1636 	int pos;
1637 
1638 	if (FindTabCommand (name, &pos, INT_MAX))
1639 	{
1640 		TabCommands[pos].UseCount++;
1641 	}
1642 	else
1643 	{
1644 		TabData tab(name);
1645 		TabCommands.Insert (pos, tab);
1646 	}
1647 }
1648 
C_RemoveTabCommand(const char * name)1649 void C_RemoveTabCommand (const char *name)
1650 {
1651 	FName aname(name, true);
1652 
1653 	if (aname == NAME_None)
1654 	{
1655 		return;
1656 	}
1657 	for (unsigned int i = 0; i < TabCommands.Size(); ++i)
1658 	{
1659 		if (TabCommands[i].TabName == aname)
1660 		{
1661 			if (--TabCommands[i].UseCount == 0)
1662 			{
1663 				TabCommands.Delete(i);
1664 			}
1665 			break;
1666 		}
1667 	}
1668 }
1669 
C_ClearTabCommands()1670 void C_ClearTabCommands ()
1671 {
1672 	TabCommands.Clear();
1673 }
1674 
FindDiffPoint(FName name1,const char * str2)1675 static int FindDiffPoint (FName name1, const char *str2)
1676 {
1677 	const char *str1 = name1.GetChars();
1678 	int i;
1679 
1680 	for (i = 0; tolower(str1[i]) == tolower(str2[i]); i++)
1681 		if (str1[i] == 0 || str2[i] == 0)
1682 			break;
1683 
1684 	return i;
1685 }
1686 
C_TabComplete(bool goForward)1687 static void C_TabComplete (bool goForward)
1688 {
1689 	int i;
1690 	int diffpoint;
1691 
1692 	if (!TabbedLast)
1693 	{
1694 		bool cancomplete;
1695 
1696 		// Skip any spaces at beginning of command line
1697 		if (CmdLine[2] == ' ')
1698 		{
1699 			for (i = 0; i < CmdLine[0]; i++)
1700 				if (CmdLine[2+i] != ' ')
1701 					break;
1702 
1703 			TabStart = i + 2;
1704 		}
1705 		else
1706 		{
1707 			TabStart = 2;
1708 		}
1709 
1710 		if (TabStart == CmdLine[0] + 2)
1711 			return;		// Line was nothing but spaces
1712 
1713 		TabSize = CmdLine[0] - TabStart + 2;
1714 
1715 		if (!FindTabCommand ((char *)(CmdLine + TabStart), &TabPos, TabSize))
1716 			return;		// No initial matches
1717 
1718 		// Show a list of possible completions, if more than one.
1719 		if (TabbedList || con_notablist)
1720 		{
1721 			cancomplete = true;
1722 		}
1723 		else
1724 		{
1725 			cancomplete = C_TabCompleteList ();
1726 			TabbedList = true;
1727 		}
1728 
1729 		if (goForward)
1730 		{ // Position just before the list of completions so that when TabPos
1731 		  // gets advanced below, it will be at the first one.
1732 			--TabPos;
1733 		}
1734 		else
1735 		{ // Find the last matching tab, then go one past it.
1736 			while (++TabPos < (int)TabCommands.Size())
1737 			{
1738 				if (FindDiffPoint (TabCommands[TabPos].TabName, (char *)(CmdLine + TabStart)) < TabSize)
1739 				{
1740 					break;
1741 				}
1742 			}
1743 		}
1744 		TabbedLast = true;
1745 		if (!cancomplete)
1746 		{
1747 			return;
1748 		}
1749 	}
1750 
1751 	if ((goForward && ++TabPos == (int)TabCommands.Size()) ||
1752 		(!goForward && --TabPos < 0))
1753 	{
1754 		TabbedLast = false;
1755 		CmdLine[0] = CmdLine[1] = TabSize;
1756 	}
1757 	else
1758 	{
1759 		diffpoint = FindDiffPoint (TabCommands[TabPos].TabName, (char *)(CmdLine + TabStart));
1760 
1761 		if (diffpoint < TabSize)
1762 		{
1763 			// No more matches
1764 			TabbedLast = false;
1765 			CmdLine[0] = CmdLine[1] = TabSize + TabStart - 2;
1766 		}
1767 		else
1768 		{
1769 			strcpy ((char *)(CmdLine + TabStart), TabCommands[TabPos].TabName.GetChars());
1770 			CmdLine[0] = CmdLine[1] = (BYTE)strlen ((char *)(CmdLine + 2)) + 1;
1771 			CmdLine[CmdLine[0] + 1] = ' ';
1772 		}
1773 	}
1774 
1775 	makestartposgood ();
1776 }
1777 
C_TabCompleteList()1778 static bool C_TabCompleteList ()
1779 {
1780 	int nummatches, i;
1781 	size_t maxwidth;
1782 	int commonsize = INT_MAX;
1783 
1784 	nummatches = 0;
1785 	maxwidth = 0;
1786 
1787 	for (i = TabPos; i < (int)TabCommands.Size(); ++i)
1788 	{
1789 		if (FindDiffPoint (TabCommands[i].TabName, (char *)(CmdLine + TabStart)) < TabSize)
1790 		{
1791 			break;
1792 		}
1793 		else
1794 		{
1795 			if (i > TabPos)
1796 			{
1797 				// This keeps track of the longest common prefix for all the possible
1798 				// completions, so we can fill in part of the command for the user if
1799 				// the longest common prefix is longer than what the user already typed.
1800 				int diffpt = FindDiffPoint (TabCommands[i-1].TabName, TabCommands[i].TabName.GetChars());
1801 				if (diffpt < commonsize)
1802 				{
1803 					commonsize = diffpt;
1804 				}
1805 			}
1806 			nummatches++;
1807 			maxwidth = MAX (maxwidth, strlen (TabCommands[i].TabName.GetChars()));
1808 		}
1809 	}
1810 	if (nummatches > 1)
1811 	{
1812 		size_t x = 0;
1813 		maxwidth += 3;
1814 		Printf (TEXTCOLOR_BLUE "Completions for %s:\n", CmdLine+2);
1815 		for (i = TabPos; nummatches > 0; ++i, --nummatches)
1816 		{
1817 			// [Dusk] Print console commands blue, CVars green, aliases red.
1818 			const char* colorcode = "";
1819 			FConsoleCommand* ccmd;
1820 			if (FindCVar (TabCommands[i].TabName, NULL))
1821 				colorcode = TEXTCOLOR_GREEN;
1822 			else if ((ccmd = FConsoleCommand::FindByName (TabCommands[i].TabName)) != NULL)
1823 			{
1824 				if (ccmd->IsAlias())
1825 					colorcode = TEXTCOLOR_RED;
1826 				else
1827 					colorcode = TEXTCOLOR_LIGHTBLUE;
1828 			}
1829 
1830 			Printf ("%s%-*s", colorcode, int(maxwidth), TabCommands[i].TabName.GetChars());
1831 			x += maxwidth;
1832 			if (x > ConCols - maxwidth)
1833 			{
1834 				x = 0;
1835 				Printf ("\n");
1836 			}
1837 		}
1838 		if (x != 0)
1839 		{
1840 			Printf ("\n");
1841 		}
1842 		// Fill in the longest common prefix, if it's longer than what was typed.
1843 		if (TabSize != commonsize)
1844 		{
1845 			TabSize = commonsize;
1846 			strncpy ((char *)CmdLine + TabStart, TabCommands[TabPos].TabName.GetChars(), commonsize);
1847 			CmdLine[0] = TabStart + commonsize - 2;
1848 			CmdLine[1] = CmdLine[0];
1849 		}
1850 		return false;
1851 	}
1852 	return true;
1853 }
1854