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