From d5f59f06e129c368ca4cde5c612f536d978d5663 Mon Sep 17 00:00:00 2001 From: Justin Marshall Date: Thu, 14 May 2026 19:16:41 -0700 Subject: [PATCH] Switched ConsoleDlg over to richtext --- neo/engine/tools/radiant/ConsoleDlg.cpp | 732 +++++++++++++++++++----- neo/engine/tools/radiant/ConsoleDlg.h | 59 +- 2 files changed, 624 insertions(+), 167 deletions(-) diff --git a/neo/engine/tools/radiant/ConsoleDlg.cpp b/neo/engine/tools/radiant/ConsoleDlg.cpp index 10c743cb..86b14bd0 100644 --- a/neo/engine/tools/radiant/ConsoleDlg.cpp +++ b/neo/engine/tools/radiant/ConsoleDlg.cpp @@ -2,26 +2,9 @@ =========================================================================== IceTech GPL Source Code -Copyright (C) 2026 Justin Marshall +Copyright (C) 2026 Justin Marshall -This file is part of the IceTech GPL Source Code (?IceTech Source Code?). - -IceTech Source Code is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -IceTech Source Code is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with IceTech Source Code. If not, see . - -In addition, the IceTech Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the IceTech Source Code. If not, please request a copy in writing from id Software at the address below. - -If you have questions concerning this license or the applicable additional terms, you may contact in writing Justin Marshall, justinmarshall20@gmail.com +This file is part of the IceTech GPL Source Code (?IceTech Source Code?). =========================================================================== */ @@ -37,52 +20,441 @@ If you have questions concerning this license or the applicable additional terms // CConsoleDlg dialog IMPLEMENT_DYNCREATE(CConsoleDlg, CDialog) + CConsoleDlg::CConsoleDlg(CWnd* pParent /*=NULL*/) - : CDialog(CConsoleDlg::IDD) + : CDialog(CConsoleDlg::IDD, pParent) { - currentHistoryPosition = -1; - currentCommand = ""; + currentHistoryPosition = -1; + currentCommand = ""; saveCurrentCommand = true; } CConsoleDlg::~CConsoleDlg() { + if (blackBrush.GetSafeHandle() != NULL) { + blackBrush.DeleteObject(); + } } void CConsoleDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); - DDX_Control(pDX, IDC_EDIT_CONSOLE, editConsole); + + // Do not DDX IDC_EDIT_CONSOLE here. + // The resource control can stay as a normal EDIT placeholder. + // OnInitDialog destroys it and creates the RichEdit in its place. DDX_Control(pDX, IDC_EDIT_INPUT, editInput); } -void CConsoleDlg::AddText( const char *msg ) { - idStr work; - CString work2; - - work = msg; - work.RemoveColors(); - work = CEntityDlg::TranslateString( work.c_str() ); - editConsole.GetWindowText( work2 ); - int len = work2.GetLength(); - if ( len + work.Length() > (int)editConsole.GetLimitText() ) { - work2 = work2.Right( editConsole.GetLimitText() * 0.75 ); - len = work2.GetLength(); - editConsole.SetWindowText(work2); - } - editConsole.SetSel( len, len ); - editConsole.ReplaceSel( work ); -} - - BEGIN_MESSAGE_MAP(CConsoleDlg, CDialog) ON_WM_SIZE() ON_WM_SETFOCUS() ON_WM_ACTIVATE() + ON_WM_CTLCOLOR() END_MESSAGE_MAP() -// CConsoleDlg message handlers +BOOL CConsoleDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + // Needed before creating CRichEditCtrl. + // Safe if already initialized elsewhere. + AfxInitRichEdit2(); + + if (blackBrush.GetSafeHandle() == NULL) { + blackBrush.CreateSolidBrush(RGB(0, 0, 0)); + } + + CreateRichConsoleFromPlaceholder(); + SetupRichConsoleDefaults(); + + if (editInput.GetSafeHwnd() != NULL) { + editInput.SetLimitText(1024); + editInput.SetWindowText(""); + } + + return TRUE; +} + +void CConsoleDlg::CreateRichConsoleFromPlaceholder() +{ + CWnd* oldConsole = GetDlgItem(IDC_EDIT_CONSOLE); + if (oldConsole == NULL || oldConsole->GetSafeHwnd() == NULL) { + return; + } + + CRect rc; + oldConsole->GetWindowRect(&rc); + ScreenToClient(&rc); + + oldConsole->DestroyWindow(); + + DWORD style = + WS_CHILD | + WS_VISIBLE | + WS_TABSTOP | + WS_VSCROLL | + ES_MULTILINE | + ES_AUTOVSCROLL | + ES_READONLY | + ES_NOHIDESEL | + ES_SAVESEL; + + // Older MFC CRichEditCtrl::Create only takes 4 args. + // Do not use CreateEx here. + editConsole.Create( + style, + rc, + this, + IDC_EDIT_CONSOLE + ); +} + +void CConsoleDlg::SetupRichConsoleDefaults() +{ + if (editConsole.GetSafeHwnd() == NULL) { + return; + } + + editConsole.SetEventMask(0); + editConsole.SetOptions(ECOOP_OR, ECO_AUTOVSCROLL); + editConsole.SetReadOnly(TRUE); + + // CRichEditCtrl uses LimitText(), not SetLimitText(). + editConsole.LimitText(1024 * 1024); + + editConsole.SetBackgroundColor(FALSE, RGB(0, 0, 0)); + + CHARFORMAT cf; + memset(&cf, 0, sizeof(cf)); + cf.cbSize = sizeof(cf); + cf.dwMask = CFM_COLOR | CFM_FACE | CFM_SIZE; + cf.crTextColor = ICE_CONSOLE_DEFAULT_COLOR; + cf.yHeight = 180; // 9pt + + lstrcpyn(cf.szFaceName, "Consolas", LF_FACESIZE); + + editConsole.SetDefaultCharFormat(cf); + + editConsole.SetSel(0, -1); + editConsole.SetSelectionCharFormat(cf); + editConsole.SetSel(-1, -1); +} + +CString CConsoleDlg::NormalizeNewlines(const CString& in) const +{ + CString out; + out.Preallocate(in.GetLength() + 16); + + for (int i = 0; i < in.GetLength(); i++) { + TCHAR c = in[i]; + + if (c == '\r') { + if (i + 1 < in.GetLength() && in[i + 1] == '\n') { + out += "\r\n"; + i++; + } + else { + out += "\r\n"; + } + } + else if (c == '\n') { + out += "\r\n"; + } + else { + out += c; + } + } + + return out; +} + +COLORREF CConsoleDlg::DoomColorForIndex(char c) const +{ + switch (c) { + case '0': + return RGB(0, 0, 0); // black + + case '1': + return RGB(255, 64, 64); // red + + case '2': + return RGB(64, 255, 64); // green + + case '3': + return RGB(255, 255, 64); // yellow + + case '4': + return RGB(80, 140, 255); // blue + + case '5': + return RGB(64, 255, 255); // cyan + + case '6': + return RGB(255, 64, 255); // magenta + + case '7': + return RGB(230, 230, 230); // white + + case '8': + return RGB(160, 160, 160); // gray + + case '9': + return RGB(255, 150, 32); // orange + + case 'r': + case 'R': + return RGB(255, 64, 64); + + case 'g': + case 'G': + return RGB(64, 255, 64); + + case 'y': + case 'Y': + return RGB(255, 255, 64); + + case 'b': + case 'B': + return RGB(80, 140, 255); + + case 'c': + case 'C': + return RGB(64, 255, 255); + + case 'm': + case 'M': + return RGB(255, 64, 255); + + case 'w': + case 'W': + return RGB(230, 230, 230); + + default: + return ICE_CONSOLE_DEFAULT_COLOR; + } +} + +static bool ConsoleStartsWithNoCase(const char* s, const char* prefix) +{ + if (s == NULL || prefix == NULL) { + return false; + } + + while (*prefix != '\0') { + char a = *s; + char b = *prefix; + + if (a >= 'A' && a <= 'Z') { + a = a - 'A' + 'a'; + } + + if (b >= 'A' && b <= 'Z') { + b = b - 'A' + 'a'; + } + + if (a != b) { + return false; + } + + s++; + prefix++; + } + + return true; +} + +COLORREF CConsoleDlg::DoomColorForName(const char* name, int& consumed) const +{ + consumed = 0; + + struct colorName_t { + const char* name; + COLORREF color; + }; + + static const colorName_t names[] = { + { "S_COLOR_BLACK", RGB(0, 0, 0) }, + { "S_COLOR_RED", RGB(255, 64, 64) }, + { "S_COLOR_GREEN", RGB(64, 255, 64) }, + { "S_COLOR_YELLOW", RGB(255, 255, 64) }, + { "S_COLOR_BLUE", RGB(80, 140, 255) }, + { "S_COLOR_CYAN", RGB(64, 255, 255) }, + { "S_COLOR_MAGENTA", RGB(255, 64, 255) }, + { "S_COLOR_WHITE", RGB(230, 230, 230) }, + { "S_COLOR_GRAY", RGB(160, 160, 160) }, + { "S_COLOR_GREY", RGB(160, 160, 160) }, + { "S_COLOR_ORANGE", RGB(255, 150, 32) }, + + { "COLOR_BLACK", RGB(0, 0, 0) }, + { "COLOR_RED", RGB(255, 64, 64) }, + { "COLOR_GREEN", RGB(64, 255, 64) }, + { "COLOR_YELLOW", RGB(255, 255, 64) }, + { "COLOR_BLUE", RGB(80, 140, 255) }, + { "COLOR_CYAN", RGB(64, 255, 255) }, + { "COLOR_MAGENTA", RGB(255, 64, 255) }, + { "COLOR_WHITE", RGB(230, 230, 230) }, + { "COLOR_GRAY", RGB(160, 160, 160) }, + { "COLOR_GREY", RGB(160, 160, 160) }, + { "COLOR_ORANGE", RGB(255, 150, 32) } + }; + + for (int i = 0; i < sizeof(names) / sizeof(names[0]); i++) { + if (ConsoleStartsWithNoCase(name, names[i].name)) { + consumed = (int)strlen(names[i].name); + return names[i].color; + } + } + + return ICE_CONSOLE_DEFAULT_COLOR; +} + +bool CConsoleDlg::TryConsumeColorToken(const char* s, int& consumed, COLORREF& color) const +{ + consumed = 0; + color = ICE_CONSOLE_DEFAULT_COLOR; + + if (s == NULL || s[0] == '\0') { + return false; + } + + // Doom/idTech style color escape: ^1, ^2, ^7, etc. + if (s[0] == '^' && s[1] != '\0') { + // ^^ means literal caret, not a color. + if (s[1] == '^') { + return false; + } + + color = DoomColorForIndex(s[1]); + consumed = 2; + return true; + } + + // Optional support if some code emits literal token names. + int namedConsumed = 0; + COLORREF namedColor = DoomColorForName(s, namedConsumed); + if (namedConsumed > 0) { + color = namedColor; + consumed = namedConsumed; + return true; + } + + return false; +} + +void CConsoleDlg::AppendConsoleRun(const CString& text, COLORREF color) +{ + if (text.IsEmpty() || editConsole.GetSafeHwnd() == NULL) { + return; + } + + CString normalized = NormalizeNewlines(text); + + editConsole.SetReadOnly(FALSE); + + int len = editConsole.GetTextLength(); + editConsole.SetSel(len, len); + + CHARFORMAT cf; + memset(&cf, 0, sizeof(cf)); + cf.cbSize = sizeof(cf); + cf.dwMask = CFM_COLOR | CFM_FACE | CFM_SIZE; + cf.crTextColor = color; + cf.yHeight = 180; + lstrcpyn(cf.szFaceName, "Consolas", LF_FACESIZE); + + editConsole.SetSelectionCharFormat(cf); + editConsole.ReplaceSel(normalized, FALSE); + + editConsole.SetReadOnly(TRUE); +} + +void CConsoleDlg::TrimConsoleIfNeeded() +{ + if (editConsole.GetSafeHwnd() == NULL) { + return; + } + + const int maxChars = 512 * 1024; + const int trimToChars = 384 * 1024; + + int len = editConsole.GetTextLength(); + if (len <= maxChars) { + return; + } + + int removeCount = len - trimToChars; + + editConsole.SetReadOnly(FALSE); + editConsole.SetSel(0, removeCount); + editConsole.ReplaceSel("", FALSE); + editConsole.SetReadOnly(TRUE); +} + +void CConsoleDlg::AppendConsoleTextColored(const char* text) +{ + if (text == NULL || editConsole.GetSafeHwnd() == NULL) { + return; + } + + COLORREF currentColor = ICE_CONSOLE_DEFAULT_COLOR; + CString run; + + const char* p = text; + + while (*p != '\0') { + int consumed = 0; + COLORREF newColor = ICE_CONSOLE_DEFAULT_COLOR; + + if (TryConsumeColorToken(p, consumed, newColor)) { + if (!run.IsEmpty()) { + AppendConsoleRun(run, currentColor); + run.Empty(); + } + + currentColor = newColor; + p += consumed; + continue; + } + + if (p[0] == '^' && p[1] == '^') { + run += '^'; + p += 2; + continue; + } + + run += *p; + p++; + } + + if (!run.IsEmpty()) { + AppendConsoleRun(run, currentColor); + } + + TrimConsoleIfNeeded(); + + int len = editConsole.GetTextLength(); + editConsole.SetSel(len, len); + editConsole.SendMessage(EM_SCROLLCARET, 0, 0); +} + +void CConsoleDlg::AddText(const char* msg) +{ + if (msg == NULL || editConsole.GetSafeHwnd() == NULL) { + return; + } + + idStr work; + + work = msg; + + // DO NOT call RemoveColors() here. + // The RichEdit append path consumes Doom color codes and applies colors. + work = CEntityDlg::TranslateString(work.c_str()); + + AppendConsoleTextColored(work.c_str()); +} void CConsoleDlg::OnSize(UINT nType, int cx, int cy) { @@ -92,158 +464,212 @@ void CConsoleDlg::OnSize(UINT nType, int cx, int cy) return; } - CRect rect, crect; - GetWindowRect(rect); - editInput.GetWindowRect(crect); + CRect clientRect; + GetClientRect(&clientRect); - editInput.SetWindowPos(NULL, 4, rect.Height() - 4 - crect.Height(), rect.Width() - 8, crect.Height(), SWP_SHOWWINDOW); - editConsole.SetWindowPos(NULL, 4, 4, rect.Width() - 8, rect.Height() - crect.Height() - 8, SWP_SHOWWINDOW); + CRect inputRect; + editInput.GetWindowRect(&inputRect); + + const int margin = 4; + const int inputHeight = inputRect.Height(); + + editInput.SetWindowPos( + NULL, + margin, + clientRect.Height() - margin - inputHeight, + clientRect.Width() - margin * 2, + inputHeight, + SWP_SHOWWINDOW + ); + + if (editConsole.GetSafeHwnd() != NULL) { + editConsole.SetWindowPos( + NULL, + margin, + margin, + clientRect.Width() - margin * 2, + clientRect.Height() - inputHeight - margin * 3, + SWP_SHOWWINDOW + ); + } } BOOL CConsoleDlg::PreTranslateMessage(MSG* pMsg) { - if (pMsg->hwnd == editInput.GetSafeHwnd()) { - if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_ESCAPE ) { + if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_ESCAPE) { Select_Deselect(); - g_pParentWnd->SetFocus (); + g_pParentWnd->SetFocus(); return TRUE; } - if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_RETURN ) { + if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_RETURN) { ExecuteCommand(); return TRUE; } - if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_ESCAPE ) { - if (pMsg->wParam == VK_ESCAPE) { - g_pParentWnd->GetCamera()->SetFocus(); - Select_Deselect(); - } - - return TRUE; - } - - if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_UP ) { - //save off the current in-progress command so we can get back to it - if ( saveCurrentCommand == true ) { - CString str; - editInput.GetWindowText ( str ); - currentCommand = str.GetBuffer ( 0 ); + if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_UP) { + // Save off the current in-progress command so we can get back to it. + if (saveCurrentCommand == true) { + CString str; + editInput.GetWindowText(str); + currentCommand = str.GetBuffer(0); saveCurrentCommand = false; - } - - if ( consoleHistory.Num () > 0 ) { - editInput.SetWindowText ( consoleHistory[currentHistoryPosition] ); - - int selLocation = consoleHistory[currentHistoryPosition].Length (); - editInput.SetSel ( selLocation , selLocation + 1); -} - - if ( currentHistoryPosition > 0) { - --currentHistoryPosition; - } - - return TRUE; - } - - if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_DOWN ) { - int selLocation = 0; - if ( currentHistoryPosition < consoleHistory.Num () - 1 ) { - ++currentHistoryPosition; - editInput.SetWindowText ( consoleHistory[currentHistoryPosition] ); - selLocation = consoleHistory[currentHistoryPosition].Length (); - } - else { - editInput.SetWindowText ( currentCommand ); - selLocation = currentCommand.Length (); - currentCommand.Clear (); - saveCurrentCommand = true; - } - - editInput.SetSel ( selLocation , selLocation + 1); - - return TRUE; - } - if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_TAB ) { - common->Printf ( "Command History\n----------------\n" ); - for ( int i = 0 ; i < consoleHistory.Num ();i++ ) -{ - common->Printf ( "[cmd %d]: %s\n" , i , consoleHistory[i].c_str() ); } - common->Printf ( "----------------\n" ); - } - if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_NEXT) { - editConsole.LineScroll ( 10 ); + + if (consoleHistory.Num() > 0) { + if (currentHistoryPosition < 0) { + currentHistoryPosition = consoleHistory.Num() - 1; + } + if (currentHistoryPosition >= consoleHistory.Num()) { + currentHistoryPosition = consoleHistory.Num() - 1; + } + + editInput.SetWindowText(consoleHistory[currentHistoryPosition]); + + int selLocation = consoleHistory[currentHistoryPosition].Length(); + editInput.SetSel(selLocation, selLocation); + + if (currentHistoryPosition > 0) { + --currentHistoryPosition; + } + } + + return TRUE; } - if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_PRIOR ) { - editConsole.LineScroll ( -10 ); + if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_DOWN) { + int selLocation = 0; + + if (currentHistoryPosition < consoleHistory.Num() - 1) { + ++currentHistoryPosition; + editInput.SetWindowText(consoleHistory[currentHistoryPosition]); + selLocation = consoleHistory[currentHistoryPosition].Length(); + } + else { + editInput.SetWindowText(currentCommand); + selLocation = currentCommand.Length(); + currentCommand.Clear(); + saveCurrentCommand = true; + } + + editInput.SetSel(selLocation, selLocation); + return TRUE; } - if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_HOME ) { - editConsole.LineScroll ( -editConsole.GetLineCount() ); + if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_TAB) { + common->Printf("Command History\n----------------\n"); + + for (int i = 0; i < consoleHistory.Num(); i++) { + common->Printf("[cmd %d]: %s\n", i, consoleHistory[i].c_str()); + } + + common->Printf("----------------\n"); + return TRUE; } - if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_END ) { - editConsole.LineScroll ( editConsole.GetLineCount() ); + if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_NEXT) { + if (editConsole.GetSafeHwnd() != NULL) { + editConsole.LineScroll(10); + } + return TRUE; + } + + if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_PRIOR) { + if (editConsole.GetSafeHwnd() != NULL) { + editConsole.LineScroll(-10); + } + return TRUE; + } + + if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_HOME) { + if (editConsole.GetSafeHwnd() != NULL) { + editConsole.LineScroll(-editConsole.GetLineCount()); + } + return TRUE; + } + + if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_END) { + if (editConsole.GetSafeHwnd() != NULL) { + editConsole.LineScroll(editConsole.GetLineCount()); + } + return TRUE; } } return CDialog::PreTranslateMessage(pMsg); } -void CConsoleDlg::OnSetFocus(CWnd* pOldWnd) { +void CConsoleDlg::OnSetFocus(CWnd* pOldWnd) +{ CDialog::OnSetFocus(pOldWnd); - editInput.SetFocus(); + + if (editInput.GetSafeHwnd() != NULL) { + editInput.SetFocus(); + } } -void CConsoleDlg::SetConsoleText ( const idStr& text ) { - editInput.Clear (); - editInput.SetWindowText ( text.c_str() ); +void CConsoleDlg::SetConsoleText(const idStr& text) +{ + if (editInput.GetSafeHwnd() != NULL) { + editInput.Clear(); + editInput.SetWindowText(text.c_str()); + } } -void CConsoleDlg::ExecuteCommand ( const idStr& cmd ) { +void CConsoleDlg::ExecuteCommand(const idStr& cmd) +{ CString str; - if ( cmd.Length() > 0 ) { + + if (cmd.Length() > 0) { str = cmd; } else { editInput.GetWindowText(str); } - if ( str != "" ) { + if (str != "") { editInput.SetWindowText(""); - common->Printf("%s\n", str.GetBuffer(0)); - //avoid adding multiple identical commands in a row - int index = consoleHistory.Num (); + common->Printf("%s\n", str.GetBuffer(0)); - if ( index == 0 || str.GetBuffer(0) != consoleHistory[index-1]) { - //keep the history to 16 commands, removing the oldest command - if ( consoleHistory.Num () > 16 ) { - consoleHistory.RemoveIndex ( 0 ); + // Avoid adding multiple identical commands in a row. + int index = consoleHistory.Num(); + + if (index == 0 || str.GetBuffer(0) != consoleHistory[index - 1]) { + // Keep the history to 16 commands, removing the oldest command. + if (consoleHistory.Num() > 16) { + consoleHistory.RemoveIndex(0); } - currentHistoryPosition = consoleHistory.Append ( str.GetBuffer (0) ); + + currentHistoryPosition = consoleHistory.Append(str.GetBuffer(0)); } else { - currentHistoryPosition = consoleHistory.Num () - 1; + currentHistoryPosition = consoleHistory.Num() - 1; } - currentCommand.Clear (); + currentCommand.Clear(); + saveCurrentCommand = true; bool propogateCommand = true; - //process some of our own special commands - if ( str.CompareNoCase ( "clear" ) == 0) { - editConsole.SetSel ( 0 , -1 ); - editConsole.Clear (); - } - else if ( str.CompareNoCase ( "edit" ) == 0) { + // Process some of our own special commands. + if (str.CompareNoCase("clear") == 0) { + if (editConsole.GetSafeHwnd() != NULL) { + editConsole.SetReadOnly(FALSE); + editConsole.SetSel(0, -1); + editConsole.ReplaceSel("", FALSE); + editConsole.SetReadOnly(TRUE); + } + propogateCommand = false; } - if ( propogateCommand ) { - cmdSystem->BufferCommandText( CMD_EXEC_NOW, str ); + else if (str.CompareNoCase("edit") == 0) { + propogateCommand = false; + } + + if (propogateCommand) { + cmdSystem->BufferCommandText(CMD_EXEC_NOW, str); } Sys_UpdateWindows(W_ALL); @@ -254,8 +680,22 @@ void CConsoleDlg::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized) { CDialog::OnActivate(nState, pWndOther, bMinimized); - if ( nState == WA_ACTIVE || nState == WA_CLICKACTIVE ) - { - editInput.SetFocus(); - } + if (nState == WA_ACTIVE || nState == WA_CLICKACTIVE) { + if (editInput.GetSafeHwnd() != NULL) { + editInput.SetFocus(); + } + } } + +HBRUSH CConsoleDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) +{ + HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor); + + if (pWnd != NULL && pWnd->GetSafeHwnd() == editInput.GetSafeHwnd()) { + pDC->SetBkColor(RGB(0, 0, 0)); + pDC->SetTextColor(ICE_CONSOLE_DEFAULT_COLOR); + return (HBRUSH)blackBrush.GetSafeHandle(); + } + + return hbr; +} \ No newline at end of file diff --git a/neo/engine/tools/radiant/ConsoleDlg.h b/neo/engine/tools/radiant/ConsoleDlg.h index 370acd4c..225c1a2d 100644 --- a/neo/engine/tools/radiant/ConsoleDlg.h +++ b/neo/engine/tools/radiant/ConsoleDlg.h @@ -2,9 +2,9 @@ =========================================================================== IceTech GPL Source Code -Copyright (C) 2026 Justin Marshall +Copyright (C) 2026 Justin Marshall -This file is part of the IceTech GPL Source Code (?IceTech Source Code?). +This file is part of the IceTech GPL Source Code (?IceTech Source Code?). IceTech Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,17 +16,15 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -You should have received a copy of the GNU General Public License -along with IceTech Source Code. If not, see . - -In addition, the IceTech Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the IceTech Source Code. If not, please request a copy in writing from id Software at the address below. - -If you have questions concerning this license or the applicable additional terms, you may contact in writing Justin Marshall, justinmarshall20@gmail.com - =========================================================================== */ #pragma once + #include "afxwin.h" +#include "afxrich.h" + + +#define ICE_CONSOLE_DEFAULT_COLOR RGB(64, 255, 255) // CConsoleDlg dialog @@ -36,31 +34,50 @@ class CConsoleDlg : public CDialog DECLARE_DYNCREATE(CConsoleDlg) public: - CConsoleDlg(CWnd* pParent = NULL); // standard constructor + CConsoleDlg(CWnd* pParent = NULL); virtual ~CConsoleDlg(); -// Dialog Data enum { IDD = IDD_DIALOG_CONSOLE }; protected: - virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + virtual void DoDataExchange(CDataExchange* pDX); + virtual BOOL OnInitDialog(); DECLARE_MESSAGE_MAP() + public: - CEdit editConsole; + CRichEditCtrl editConsole; CEdit editInput; - void AddText(const char *msg); - void SetConsoleText ( const idStr& text ); - void ExecuteCommand ( const idStr& cmd = "" ); - + + void AddText(const char* msg); + void SetConsoleText(const idStr& text); + void ExecuteCommand(const idStr& cmd = ""); + idStr consoleStr; - idStrList consoleHistory; - idStr currentCommand; - int currentHistoryPosition; + idStrList consoleHistory; + idStr currentCommand; + int currentHistoryPosition; bool saveCurrentCommand; afx_msg void OnSize(UINT nType, int cx, int cy); virtual BOOL PreTranslateMessage(MSG* pMsg); afx_msg void OnSetFocus(CWnd* pOldWnd); afx_msg void OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized); -}; + afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor); + +private: + CBrush blackBrush; + + void CreateRichConsoleFromPlaceholder(); + void SetupRichConsoleDefaults(); + + void AppendConsoleTextColored(const char* text); + void AppendConsoleRun(const CString& text, COLORREF color); + void TrimConsoleIfNeeded(); + + bool TryConsumeColorToken(const char* s, int& consumed, COLORREF& color) const; + COLORREF DoomColorForIndex(char c) const; + COLORREF DoomColorForName(const char* name, int& consumed) const; + + CString NormalizeNewlines(const CString& in) const; +}; \ No newline at end of file