raze/source/common/console/c_console.cpp
Christoph Oelckers 4a87003408 - backend update from GZDoom.
* Vulkan backend updated.
* zlib replaced with miniz.
* FileReader is now 64 bit capable.
* jpeg replaced with stb-image.
* CMake project warnings fixed.
2023-09-23 09:56:27 +02:00

1187 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_eventbase.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 FConsoleCommand *Commands[FConsoleCommand::HASH_SIZE];
int ConWidth;
bool vidactive = false;
bool cursoron = false;
int ConBottom, ConScroll, RowAdjust;
uint64_t CursorTicker;
uint8_t ConsoleState = c_up;
DEFINE_GLOBAL(ConsoleState)
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(Int, con_scale, 0, CVAR_ARCHIVE)
{
if (self < 0) self = 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;
CVARD_NAMED(Int, msglevel, msg, 0, CVAR_ARCHIVE, "Filters HUD message by importance");
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 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 (*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)
{
if (printlevel >= msglevel)
{
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, conback, false, 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();
if (blines != nullptr)
{
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();
}