raze/source/common/console/c_console.cpp
Christoph Oelckers 84173ee09b - backend update from GZDoom.
The main bulk of this is the new start screen code. To make this work in Raze some more work on the startup procedure is needed.
What this does provide is support for the DOS end-of-game text screens in Duke and SW on non-Windows systems.
2022-06-06 11:45:34 +02:00

1192 lines
25 KiB
C++

/*
** c_console.cpp
** Implements the console itself
**
**---------------------------------------------------------------------------
** Copyright 1998-2006 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#include <string>
#include "version.h"
#include "c_bind.h"
#include "c_console.h"
#include "c_cvars.h"
#include "c_dispatch.h"
#include "gamestate.h"
#include "v_text.h"
#include "filesystem.h"
#include "d_gui.h"
#include "cmdlib.h"
#include "d_event.h"
#include "c_consolebuffer.h"
#include "utf8.h"
#include "v_2ddrawer.h"
#include "v_draw.h"
#include "v_font.h"
#include "printf.h"
#include "i_time.h"
#include "texturemanager.h"
#include "v_draw.h"
#include "i_interface.h"
#include "v_video.h"
#include "i_system.h"
#include "menu.h"
#include "menustate.h"
#include "v_2ddrawer.h"
#include "c_notifybufferbase.h"
#include "g_input.h"
#include "c_commandbuffer.h"
#include "vm.h"
#define LEFTMARGIN 8
#define RIGHTMARGIN 8
#define BOTTOMARGIN 12
extern bool AppActive;
CUSTOM_CVAR(Int, con_buffersize, -1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
// ensure a minimum size
if (self >= 0 && self < 128) self = 128;
}
double NotifyFontScale = 1;
DEFINE_GLOBAL(NotifyFontScale)
void C_SetNotifyFontScale(double scale)
{
NotifyFontScale = scale;
}
FConsoleBuffer *conbuffer;
static FTextureID conback;
static FTextureID conflat;
static uint32_t conshade;
static bool conline;
extern int chatmodeon;
extern FBaseCVar *CVars;
extern FConsoleCommand *Commands[FConsoleCommand::HASH_SIZE];
int ConWidth;
bool vidactive = false;
bool cursoron = false;
int ConBottom, ConScroll, RowAdjust;
uint64_t CursorTicker;
constate_e ConsoleState = c_up;
static int TopLine, InsertLine;
static void ClearConsole ();
struct GameAtExit
{
GameAtExit(FString str) : Command(str) {}
GameAtExit *Next;
FString Command;
};
static GameAtExit *ExitCmdList;
#define SCROLLUP 1
#define SCROLLDN 2
#define SCROLLNO 0
// Buffer for AddToConsole()
static char *work = NULL;
static int worklen = 0;
CUSTOM_CVAR(Float, con_alpha, 0.75f, CVAR_ARCHIVE)
{
if (self < 0.f) self = 0.f;
if (self > 1.f) self = 1.f;
}
// Show developer messages if true.
CUSTOM_CVAR(Int, developer, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
FScriptPosition::Developer = self;
}
// Command to run when Ctrl-D is pressed at start of line
CVAR(String, con_ctrl_d, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
struct History
{
struct History *Older;
struct History *Newer;
FString String;
};
#define MAXHISTSIZE 50
static struct History *HistHead = NULL, *HistTail = NULL, *HistPos = NULL;
static int HistSize;
static FNotifyBufferBase *NotifyStrings;
void C_SetNotifyBuffer(FNotifyBufferBase* nbb)
{
NotifyStrings = nbb;
}
int PrintColors[PRINTLEVELS+2] = { CR_UNTRANSLATED, CR_GOLD, CR_GRAY, CR_GREEN, CR_GREEN, CR_UNTRANSLATED };
static void setmsgcolor (int index, int color);
FILE *Logfile = NULL;
FIntCVar msglevel ("msg", 0, CVAR_ARCHIVE);
CUSTOM_CVAR (Int, msg0color, CR_UNTRANSLATED, CVAR_ARCHIVE)
{
setmsgcolor (0, self);
}
CUSTOM_CVAR (Int, msg1color, CR_GOLD, CVAR_ARCHIVE)
{
setmsgcolor (1, self);
}
CUSTOM_CVAR (Int, msg2color, CR_GRAY, CVAR_ARCHIVE)
{
setmsgcolor (2, self);
}
CUSTOM_CVAR (Int, msg3color, CR_GREEN, CVAR_ARCHIVE)
{
setmsgcolor (3, self);
}
CUSTOM_CVAR (Int, msg4color, CR_GREEN, CVAR_ARCHIVE)
{
setmsgcolor (4, self);
}
CUSTOM_CVAR (Int, msgmidcolor, CR_UNTRANSLATED, CVAR_ARCHIVE)
{
setmsgcolor (PRINTLEVELS, self);
}
CUSTOM_CVAR (Int, msgmidcolor2, CR_BROWN, CVAR_ARCHIVE)
{
setmsgcolor (PRINTLEVELS+1, self);
}
void C_InitConback(FTextureID fallback, bool tile, double brightness)
{
conback = TexMan.CheckForTexture ("CONBACK", ETextureType::MiscPatch);
conflat = fallback;
if (!conback.isValid())
{
conback.SetInvalid();
conshade = MAKEARGB(uint8_t(255 - 255*brightness),0,0,0);
conline = true;
if (!tile) conback = fallback;
}
else
{
conshade = 0;
conline = false;
}
}
void C_InitConsole (int width, int height, bool ingame)
{
int cwidth, cheight;
vidactive = ingame;
if (CurrentConsoleFont != NULL)
{
cwidth = CurrentConsoleFont->GetCharWidth ('M');
cheight = CurrentConsoleFont->GetHeight();
}
else
{
cwidth = cheight = 8;
}
ConWidth = (width - LEFTMARGIN - RIGHTMARGIN);
CmdLine.ConCols = ConWidth / cwidth;
if (conbuffer == NULL) conbuffer = new FConsoleBuffer;
}
//==========================================================================
//
// CCMD atexit
//
//==========================================================================
UNSAFE_CCMD (atexit)
{
if (argv.argc() == 1)
{
Printf ("Registered atexit commands:\n");
GameAtExit *record = ExitCmdList;
while (record != NULL)
{
Printf ("%s\n", record->Command.GetChars());
record = record->Next;
}
return;
}
for (int i = 1; i < argv.argc(); ++i)
{
GameAtExit *record = new GameAtExit(argv[i]);
record->Next = ExitCmdList;
ExitCmdList = record;
}
}
//==========================================================================
//
// C_DeinitConsole
//
// Executes the contents of the atexit cvar, if any, at quit time.
// Then releases all of the console's memory.
//
//==========================================================================
void C_DeinitConsole ()
{
GameAtExit *cmd = ExitCmdList;
while (cmd != NULL)
{
GameAtExit *next = cmd->Next;
AddCommandString (cmd->Command);
delete cmd;
cmd = next;
}
// Free command history
History *hist = HistTail;
while (hist != NULL)
{
History *next = hist->Newer;
delete hist;
hist = next;
}
HistTail = HistHead = HistPos = NULL;
// Free cvars allocated at runtime
FBaseCVar *var, *next, **nextp;
for (var = CVars, nextp = &CVars; var != NULL; var = next)
{
next = var->m_Next;
if (var->GetFlags() & CVAR_UNSETTABLE)
{
delete var;
*nextp = next;
}
else
{
nextp = &var->m_Next;
}
}
// Free alias commands. (i.e. The "commands" that can be allocated
// at runtime.)
for (size_t i = 0; i < countof(Commands); ++i)
{
FConsoleCommand *command = Commands[i];
while (command != NULL)
{
FConsoleCommand *nextcmd = command->m_Next;
if (command->IsAlias())
{
delete command;
}
command = nextcmd;
}
}
// Make sure all tab commands are cleared before the memory for
// their names is deallocated.
C_ClearTabCommands ();
C_ClearDynCCmds();
// Free AddToConsole()'s work buffer
if (work != NULL)
{
free (work);
work = NULL;
worklen = 0;
}
if (conbuffer != NULL)
{
delete conbuffer;
conbuffer = NULL;
}
}
static void ClearConsole ()
{
if (conbuffer != NULL)
{
conbuffer->Clear();
}
TopLine = InsertLine = 0;
}
static void setmsgcolor (int index, int color)
{
if ((unsigned)color >= (unsigned)NUM_TEXT_COLORS)
color = 0;
PrintColors[index] = color;
}
void AddToConsole (int printlevel, const char *text)
{
conbuffer->AddText(printlevel, MakeUTF8(text));
}
//==========================================================================
//
//
//
//==========================================================================
void WriteLineToLog(FILE *LogFile, const char *outline)
{
// Strip out any color escape sequences before writing to the log file
TArray<char> copy(strlen(outline) + 1);
const char * srcp = outline;
char * dstp = copy.Data();
while (*srcp != 0)
{
if (*srcp != TEXTCOLOR_ESCAPE)
{
*dstp++ = *srcp++;
}
else if (srcp[1] == '[')
{
srcp += 2;
while (*srcp != ']' && *srcp != 0) srcp++;
if (*srcp == ']') srcp++;
}
else
{
if (srcp[1] != 0) srcp += 2;
else break;
}
}
*dstp = 0;
fputs(copy.Data(), LogFile);
fflush(LogFile);
}
extern bool gameisdead;
int PrintString (int iprintlevel, const char *outline)
{
if (gameisdead)
return 0;
if (!conbuffer) return 0; // when called too early
int printlevel = iprintlevel & PRINT_TYPES;
if (printlevel < msglevel || *outline == '\0')
{
return 0;
}
if (printlevel != PRINT_LOG || Logfile != nullptr)
{
// Convert everything coming through here to UTF-8 so that all console text is in a consistent format
int count;
outline = MakeUTF8(outline, &count);
if (printlevel != PRINT_LOG)
{
I_PrintStr(outline);
conbuffer->AddText(printlevel, outline);
if (vidactive && screen && !(iprintlevel & PRINT_NONOTIFY) && NotifyStrings)
{
NotifyStrings->AddString(iprintlevel, outline);
}
}
if (Logfile != nullptr && !(iprintlevel & PRINT_NOLOG))
{
WriteLineToLog(Logfile, outline);
}
return count;
}
return 0; // Don't waste time on calculating this if nothing at all was printed...
}
int VPrintf (int printlevel, const char *format, va_list parms)
{
FString outline;
outline.VFormat (format, parms);
return PrintString (printlevel, outline.GetChars());
}
int Printf (int printlevel, const char *format, ...)
{
va_list argptr;
int count;
va_start (argptr, format);
count = VPrintf (printlevel, format, argptr);
va_end (argptr);
return count;
}
int Printf (const char *format, ...)
{
va_list argptr;
int count;
va_start (argptr, format);
count = VPrintf (PRINT_HIGH, format, argptr);
va_end (argptr);
return count;
}
int DPrintf (int level, const char *format, ...)
{
va_list argptr;
int count;
if (developer >= level)
{
va_start (argptr, format);
count = VPrintf (PRINT_HIGH, format, argptr);
va_end (argptr);
return count;
}
else
{
return 0;
}
}
void C_FlushDisplay ()
{
if (NotifyStrings) NotifyStrings->Clear();
}
void C_AdjustBottom ()
{
if (gamestate == GS_FULLCONSOLE || gamestate == GS_STARTUP)
ConBottom = twod->GetHeight();
else if (ConBottom > twod->GetHeight() / 2 || ConsoleState == c_down)
ConBottom = twod->GetHeight() / 2;
}
void C_NewModeAdjust ()
{
C_InitConsole (screen->GetWidth(), screen->GetHeight(), true);
C_FlushDisplay ();
C_AdjustBottom ();
}
int consoletic = 0;
void C_Ticker()
{
static int lasttic = 0;
consoletic++;
if (lasttic == 0)
lasttic = consoletic - 1;
if (con_buffersize > 0)
{
conbuffer->ResizeBuffer(con_buffersize);
}
if (ConsoleState != c_up)
{
if (ConsoleState == c_falling)
{
ConBottom += (consoletic - lasttic) * (twod->GetHeight() * 2 / 25);
if (ConBottom >= twod->GetHeight() / 2)
{
ConBottom = twod->GetHeight() / 2;
ConsoleState = c_down;
}
}
else if (ConsoleState == c_rising)
{
ConBottom -= (consoletic - lasttic) * (twod->GetHeight() * 2 / 25);
if (ConBottom <= 0)
{
ConsoleState = c_up;
ConBottom = 0;
}
}
}
lasttic = consoletic;
if (NotifyStrings) NotifyStrings->Tick();
}
void C_DrawConsole ()
{
static int oldbottom = 0;
int lines, left, offset;
int textScale = active_con_scale(twod);
left = LEFTMARGIN;
lines = (ConBottom/textScale-CurrentConsoleFont->GetHeight()*2)/CurrentConsoleFont->GetHeight();
if (-CurrentConsoleFont->GetHeight() + lines*CurrentConsoleFont->GetHeight() > ConBottom/textScale - CurrentConsoleFont->GetHeight()*7/2)
{
offset = -CurrentConsoleFont->GetHeight()/2;
lines--;
}
else
{
offset = -CurrentConsoleFont->GetHeight();
}
oldbottom = ConBottom;
if (ConsoleState == c_up && gamestate == GS_LEVEL)
{
if (NotifyStrings) NotifyStrings->Draw();
return;
}
else if (ConBottom)
{
int visheight;
visheight = ConBottom;
if (conback.isValid() && gamestate != GS_FULLCONSOLE)
{
DrawTexture (twod, TexMan.GetGameTexture(conback), 0, visheight - screen->GetHeight(),
DTA_DestWidth, twod->GetWidth(),
DTA_DestHeight, twod->GetHeight(),
DTA_ColorOverlay, conshade,
DTA_Alpha, (gamestate != GS_FULLCONSOLE) ? (double)con_alpha : 1.,
DTA_Masked, false,
TAG_DONE);
}
else
{
if (conflat.isValid() && gamestate != GS_FULLCONSOLE)
{
int conbright = 255 - APART(conshade);
PalEntry pe((uint8_t(255 * con_alpha)), conbright, conbright, conbright);
twod->AddFlatFill(0, visheight - screen->GetHeight(), screen->GetWidth(), visheight, TexMan.GetGameTexture(conflat), 1, CleanXfac, pe, STYLE_Shaded);
}
else
{
PalEntry pe((uint8_t)(con_alpha * 255), 0, 0, 0);
twod->AddColorOnlyQuad(0, 0, screen->GetWidth(), visheight, pe);
}
}
if (conline && visheight < screen->GetHeight())
{
twod->AddColorOnlyQuad(0, visheight, screen->GetWidth(), 1, 0xff000000);
}
if (ConBottom >= 12)
{
if (textScale == 1)
DrawText(twod, CurrentConsoleFont, CR_ORANGE, twod->GetWidth() - 8 -
CurrentConsoleFont->StringWidth (GetVersionString()),
ConBottom / textScale - CurrentConsoleFont->GetHeight() - 4,
GetVersionString(), TAG_DONE);
else
DrawText(twod, CurrentConsoleFont, CR_ORANGE, twod->GetWidth() / textScale - 8 -
CurrentConsoleFont->StringWidth(GetVersionString()),
ConBottom / textScale - CurrentConsoleFont->GetHeight() - 4,
GetVersionString(),
DTA_VirtualWidth, twod->GetWidth() / textScale,
DTA_VirtualHeight, twod->GetHeight() / textScale,
DTA_KeepRatio, true, TAG_DONE);
}
}
if (menuactive != MENU_Off)
{
return;
}
if (lines > 0)
{
// No more enqueuing because adding new text to the console won't touch the actual print data.
conbuffer->FormatText(CurrentConsoleFont, ConWidth / textScale);
unsigned int consolelines = conbuffer->GetFormattedLineCount();
FBrokenLines *blines = conbuffer->GetLines();
FBrokenLines *printline = blines + consolelines - 1 - RowAdjust;
int bottomline = ConBottom / textScale - CurrentConsoleFont->GetHeight()*2 - 4;
for(FBrokenLines *p = printline; p >= blines && lines > 0; p--, lines--)
{
if (textScale == 1)
{
DrawText(twod, CurrentConsoleFont, CR_TAN, LEFTMARGIN, offset + lines * CurrentConsoleFont->GetHeight(), p->Text, TAG_DONE);
}
else
{
DrawText(twod, CurrentConsoleFont, CR_TAN, LEFTMARGIN, offset + lines * CurrentConsoleFont->GetHeight(), p->Text,
DTA_VirtualWidth, twod->GetWidth() / textScale,
DTA_VirtualHeight, twod->GetHeight() / textScale,
DTA_KeepRatio, true, TAG_DONE);
}
}
if (ConBottom >= 20)
{
if (gamestate != GS_STARTUP)
{
auto now = I_msTime();
if (now > CursorTicker)
{
CursorTicker = now + 500;
cursoron = !cursoron;
}
CmdLine.Draw(left, bottomline, textScale, cursoron);
}
if (RowAdjust && ConBottom >= CurrentConsoleFont->GetHeight()*7/2)
{
// Indicate that the view has been scrolled up (10)
// and if we can scroll no further (12)
if (textScale == 1)
DrawChar(twod, CurrentConsoleFont, CR_GREEN, 0, bottomline, RowAdjust == conbuffer->GetFormattedLineCount() ? 12 : 10, TAG_DONE);
else
DrawChar(twod, CurrentConsoleFont, CR_GREEN, 0, bottomline, RowAdjust == conbuffer->GetFormattedLineCount() ? 12 : 10,
DTA_VirtualWidth, twod->GetWidth() / textScale,
DTA_VirtualHeight, twod->GetHeight() / textScale,
DTA_KeepRatio, true, TAG_DONE);
}
}
}
}
void C_FullConsole ()
{
ConsoleState = c_down;
HistPos = NULL;
TabbedLast = false;
TabbedList = false;
gamestate = GS_FULLCONSOLE;
C_AdjustBottom ();
}
void C_ToggleConsole ()
{
int togglestate;
if (gamestate == GS_INTRO) // blocked
{
return;
}
if (gamestate == GS_MENUSCREEN)
{
if (sysCallbacks.ToggleFullConsole) sysCallbacks.ToggleFullConsole();
togglestate = c_down;
}
else if (!chatmodeon && (ConsoleState == c_up || ConsoleState == c_rising) && menuactive == MENU_Off)
{
ConsoleState = c_falling;
HistPos = NULL;
TabbedLast = false;
TabbedList = false;
togglestate = c_falling;
}
else if (gamestate != GS_FULLCONSOLE && gamestate != GS_STARTUP)
{
ConsoleState = c_rising;
C_FlushDisplay();
togglestate = c_rising;
}
else return;
// This must be done as an event callback because the client code does not control the console toggling.
if (sysCallbacks.ConsoleToggled) sysCallbacks.ConsoleToggled(togglestate);
}
void C_HideConsole ()
{
if (gamestate != GS_FULLCONSOLE)
{
ConsoleState = c_up;
ConBottom = 0;
HistPos = NULL;
}
}
static bool C_HandleKey (event_t *ev, FCommandBuffer &buffer)
{
int data1 = ev->data1;
bool keepappending = false;
switch (ev->subtype)
{
default:
return false;
case EV_GUI_Char:
if (ev->data2)
{
// Bash-style shortcuts
if (data1 == 'b')
{
buffer.CursorWordLeft();
break;
}
else if (data1 == 'f')
{
buffer.CursorWordRight();
break;
}
}
// Add keypress to command line
buffer.AddChar(data1);
HistPos = NULL;
TabbedLast = false;
TabbedList = false;
break;
case EV_GUI_WheelUp:
case EV_GUI_WheelDown:
if (!(ev->data3 & GKM_SHIFT))
{
data1 = GK_PGDN + EV_GUI_WheelDown - ev->subtype;
}
else
{
data1 = GK_DOWN + EV_GUI_WheelDown - ev->subtype;
}
// Intentional fallthrough
case EV_GUI_KeyDown:
case EV_GUI_KeyRepeat:
switch (data1)
{
case '\t':
// Try to do tab-completion
C_TabComplete ((ev->data3 & GKM_SHIFT) ? false : true);
break;
case GK_PGUP:
if (ev->data3 & (GKM_SHIFT|GKM_CTRL))
{ // Scroll console buffer up one page
RowAdjust += (twod->GetHeight()-4)/active_con_scale(twod) /
((gamestate == GS_FULLCONSOLE || gamestate == GS_STARTUP) ? CurrentConsoleFont->GetHeight() : CurrentConsoleFont->GetHeight()*2) - 3;
}
else if (RowAdjust < conbuffer->GetFormattedLineCount())
{ // Scroll console buffer up
if (ev->subtype == EV_GUI_WheelUp)
{
RowAdjust += 3;
}
else
{
RowAdjust++;
}
if (RowAdjust > conbuffer->GetFormattedLineCount())
{
RowAdjust = conbuffer->GetFormattedLineCount();
}
}
break;
case GK_PGDN:
if (ev->data3 & (GKM_SHIFT|GKM_CTRL))
{ // Scroll console buffer down one page
const int scrollamt = (twod->GetHeight()-4)/active_con_scale(twod) /
((gamestate == GS_FULLCONSOLE || gamestate == GS_STARTUP) ? CurrentConsoleFont->GetHeight() : CurrentConsoleFont->GetHeight()*2) - 3;
if (RowAdjust < scrollamt)
{
RowAdjust = 0;
}
else
{
RowAdjust -= scrollamt;
}
}
else if (RowAdjust > 0)
{ // Scroll console buffer down
if (ev->subtype == EV_GUI_WheelDown)
{
RowAdjust = max (0, RowAdjust - 3);
}
else
{
RowAdjust--;
}
}
break;
case GK_HOME:
if (ev->data3 & GKM_CTRL)
{ // Move to top of console buffer
RowAdjust = conbuffer->GetFormattedLineCount();
}
else
{ // Move cursor to start of line
buffer.CursorStart();
}
break;
case GK_END:
if (ev->data3 & GKM_CTRL)
{ // Move to bottom of console buffer
RowAdjust = 0;
}
else
{ // Move cursor to end of line
buffer.CursorEnd();
}
break;
case GK_LEFT:
// Move cursor left one character
buffer.CursorLeft();
break;
case GK_RIGHT:
// Move cursor right one character
buffer.CursorRight();
break;
case '\b':
// Erase character to left of cursor
buffer.DeleteLeft();
TabbedLast = false;
TabbedList = false;
break;
case GK_DEL:
// Erase character under cursor
buffer.DeleteRight();
TabbedLast = false;
TabbedList = false;
break;
case GK_UP:
// Move to previous entry in the command history
if (HistPos == NULL)
{
HistPos = HistHead;
}
else if (HistPos->Older)
{
HistPos = HistPos->Older;
}
if (HistPos)
{
buffer.SetString(HistPos->String);
}
TabbedLast = false;
TabbedList = false;
break;
case GK_DOWN:
// Move to next entry in the command history
if (HistPos && HistPos->Newer)
{
HistPos = HistPos->Newer;
buffer.SetString(HistPos->String);
}
else
{
HistPos = NULL;
buffer.SetString("");
}
TabbedLast = false;
TabbedList = false;
break;
case 'X':
if (ev->data3 & GKM_CTRL)
{
buffer.SetString("");
TabbedLast = TabbedList = false;
}
break;
case 'D':
if (ev->data3 & GKM_CTRL && buffer.TextLength() == 0)
{ // Control-D pressed on an empty line
if (strlen(con_ctrl_d) == 0)
{
break; // Replacement is empty, so do nothing
}
buffer.SetString(*con_ctrl_d);
}
else
{
break;
}
// Intentional fall-through for command(s) added with Ctrl-D
[[fallthrough]];
case '\r':
{
// Execute command line (ENTER)
FString bufferText = buffer.GetText();
bufferText.StripLeftRight();
Printf(127, TEXTCOLOR_WHITE "]%s\n", bufferText.GetChars());
if (bufferText.Len() == 0)
{
// Command line is empty, so do nothing to the history
}
else if (HistHead && HistHead->String.CompareNoCase(bufferText) == 0)
{
// Command line was the same as the previous one,
// so leave the history list alone
}
else
{
// Command line is different from last command line,
// or there is nothing in the history list,
// so add it to the history list.
History *temp = new History;
temp->String = bufferText;
temp->Older = HistHead;
if (HistHead)
{
HistHead->Newer = temp;
}
temp->Newer = NULL;
HistHead = temp;
if (!HistTail)
{
HistTail = temp;
}
if (HistSize == MAXHISTSIZE)
{
HistTail = HistTail->Newer;
delete HistTail->Older;
HistTail->Older = NULL;
}
else
{
HistSize++;
}
}
HistPos = NULL;
buffer.SetString("");
AddCommandString(bufferText);
TabbedLast = false;
TabbedList = false;
break;
}
case '`':
// Check to see if we have ` bound to the console before accepting
// it as a way to close the console.
if (Bindings.GetBinding(KEY_GRAVE).CompareNoCase("toggleconsole"))
{
break;
}
[[fallthrough]];
case GK_ESCAPE:
// Close console and clear command line. But if we're in the
// fullscreen console mode, there's nothing to fall back on
// if it's closed, so open the main menu instead.
if (gamestate == GS_STARTUP || !AppActive)
{
return false;
}
else if (gamestate == GS_FULLCONSOLE)
{
C_DoCommand ("menu_main");
}
else
{
buffer.SetString("");
HistPos = NULL;
C_ToggleConsole ();
}
break;
case 'C':
case 'V':
TabbedLast = false;
TabbedList = false;
#ifdef __APPLE__
if (ev->data3 & GKM_META)
#else // !__APPLE__
if (ev->data3 & GKM_CTRL)
#endif // __APPLE__
{
if (data1 == 'C')
{ // copy to clipboard
if (buffer.TextLength() > 0)
{
I_PutInClipboard(buffer.GetText());
}
}
else
{ // paste from clipboard
buffer.AddString(I_GetFromClipboard(false));
HistPos = NULL;
}
break;
}
break;
// Bash-style shortcuts
case 'A':
if (ev->data3 & GKM_CTRL)
{
buffer.CursorStart();
}
break;
case 'E':
if (ev->data3 & GKM_CTRL)
{
buffer.CursorEnd();
}
break;
case 'W':
if (ev->data3 & GKM_CTRL)
{
buffer.DeleteWordLeft();
keepappending = true;
TabbedLast = false;
TabbedList = false;
}
break;
case 'U':
if (ev->data3 & GKM_CTRL)
{
buffer.DeleteLineLeft();
keepappending = true;
TabbedLast = false;
TabbedList = false;
}
break;
case 'K':
if (ev->data3 & GKM_CTRL)
{
buffer.DeleteLineRight();
keepappending = true;
TabbedLast = false;
TabbedList = false;
}
break;
case 'Y':
if (ev->data3 & GKM_CTRL)
{
buffer.AddYankBuffer();
TabbedLast = false;
TabbedList = false;
HistPos = NULL;
}
break;
}
break;
#ifdef __unix__
case EV_GUI_MButtonDown:
buffer.AddString(I_GetFromClipboard(true));
HistPos = NULL;
break;
#endif
}
buffer.AppendToYankBuffer = keepappending;
// Ensure that the cursor is always visible while typing
CursorTicker = I_msTime() + 500;
cursoron = 1;
return true;
}
bool C_Responder (event_t *ev)
{
if (ev->type != EV_GUI_Event ||
ConsoleState == c_up ||
ConsoleState == c_rising ||
menuactive != MENU_Off)
{
return false;
}
return C_HandleKey(ev, CmdLine);
}
CCMD (history)
{
struct History *hist = HistTail;
while (hist)
{
Printf (" %s\n", hist->String.GetChars());
hist = hist->Newer;
}
}
CCMD (clear)
{
C_FlushDisplay ();
ClearConsole ();
}
CCMD (echo)
{
int last = argv.argc()-1;
for (int i = 1; i <= last; ++i)
{
FString formatted = strbin1 (argv[i]);
Printf ("%s%s", formatted.GetChars(), i!=last ? " " : "\n");
}
}
CCMD(toggleconsole)
{
C_ToggleConsole();
}