diff --git a/changelog.txt b/changelog.txt index 1c26dd6..e760211 100644 --- a/changelog.txt +++ b/changelog.txt @@ -4,6 +4,10 @@ See the end of this file for known issues. DD Mmm 20 - 1.53 +add: console mark mode (selection mode) can be toggled with Ctrl-M + use arrow keys, home/end or page up/down to move the cursor or extend the area when pressing shift + Ctrl-C/enter copies the selected text to the clipboard without color codes and exits mark mode + add: /searchconsole begins a new console search press ctrl-F when the console is down to bring up the command press (shift-)F3 to find the next match going up or down diff --git a/code/client/cl_console.cpp b/code/client/cl_console.cpp index 42fc2b4..01ae362 100644 --- a/code/client/cl_console.cpp +++ b/code/client/cl_console.cpp @@ -37,7 +37,10 @@ static cvar_t* con_drawHelp; X(Border, "4778B2FF", qtrue, "RGBA color of the border") \ X(Arrow, "4778B2FF", qtrue, "RGBA color of backscroll arrows") \ X(Shadow, "000000FF", qtrue, "RGBA color of text shadows") \ + X(MkBG, "BFBFBFFF", qtrue, "RGBA color of the mark background") \ + X(MkShadow, "FFFFFF00", qtrue, "RGBA color of the mark text shadows") \ X(Text, "E2E2E2", qfalse, "RGB color of text") \ + X(MkText, "000000", qfalse, "RGB color of mark text") \ X(CVar, "4778B2", qfalse, "RGB color of variable names") \ X(Cmd, "4FA7BD", qfalse, "RGB color of command names") \ X(Value, "E5BC39", qfalse, "RGB color of variable values") \ @@ -55,6 +58,11 @@ COLOR_LIST( COLOR_LIST_ITEM ) #define CON_TEXTSIZE (256*1024) +#define COLOR_INVALID '\xFF' +#define COLOR_MKTEXT '\xFE' +#define COLOR_MKSHAD '\xFD' +#define COLOR_SHAD '\xFC' + // con_drawHelp flags #define DRAWHELP_ENABLE_BIT 1 #define DRAWHELP_NOTFOUND_BIT 2 @@ -87,17 +95,24 @@ struct console_t { qbool wasActive; // was active before Con_PushConsoleInvisible was called? - char helpText[MAXPRINTMSG]; - int helpX; // char index - float helpY; // top coordinate - int helpWidth; // char count of the longest line - int helpLines; // line count - qbool helpDraw; - float helpXAdjust; + char helpText[MAXPRINTMSG]; + int helpX; // char index + float helpY; // top coordinate + int helpWidth; // char count of the longest line + int helpLines; // line count + qbool helpDraw; + float helpXAdjust; char searchPattern[256]; qbool searchLineIndex; qbool searchStarted; + + qbool markMode; + int markX; // cursor X: offset in the row + int markY; // cursor Y: row index + int markStartX; + int markStartY; + char markText[CON_TEXTSIZE + CON_TEXTSIZE / 100]; // some extra for line endings and the final newline }; static console_t con; @@ -175,6 +190,12 @@ const float* ConsoleColorFromChar( char ccode ) return colValue; if ( ccode == COLOR_HELP ) return colHelp; + if ( ccode == COLOR_MKTEXT ) + return colMkText; + if ( ccode == COLOR_MKSHAD ) + return colMkShadow; + if ( ccode == COLOR_SHAD ) + return colShadow; return ColorFromChar( ccode ); } @@ -201,6 +222,7 @@ void Con_ToggleConsole_f() Con_ClearNotify(); cls.keyCatchers ^= KEYCATCH_CONSOLE; + con.markMode = qfalse; } /* @@ -561,7 +583,7 @@ static void Con_DrawInput() re.SetColor( colText ); SCR_DrawChar( con.xadjust, y, con.cw, con.ch, ']' ); - Field_Draw( &g_consoleField, con.xadjust + con.cw, y, con.cw, con.ch, qtrue ); + Field_Draw( &g_consoleField, con.xadjust + con.cw, y, con.cw, con.ch, qtrue, !Con_IsInMarkMode() ); } @@ -768,20 +790,23 @@ static void Con_DrawSolidConsole( float frac ) SCR_DrawChar( x, scanlines - (SMALLCHAR_HEIGHT * 1.5), SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, Q3_VERSION[i] ); } + const int cw = (int)ceilf( con.cw ); + const int ch = (int)ceilf( con.ch ); + re.SetColor( NULL ); - rows = (scanlines - con.ch) / con.ch; + rows = (scanlines - ch) / ch; con.y = scanlines; - y = scanlines - (con.ch * 3); + y = scanlines - (ch * 3); // draw the console text from the bottom up if (con.display != con.current) { // draw arrows to show the buffer is backscrolled - const int xEnd = ( cls.glconfig.vidWidth - con.xadjust ) / con.cw; + const int xEnd = ( cls.glconfig.vidWidth - con.xadjust ) / cw; re.SetColor( colArrow ); for (x = 0; x < xEnd; x += 4) - SCR_DrawChar( con.xadjust + x * con.cw, y, con.cw, con.ch, '^' ); - y -= con.ch; + SCR_DrawChar( con.xadjust + x * cw, y, cw, ch, '^' ); + y -= ch; --rows; } @@ -790,11 +815,29 @@ static void Con_DrawSolidConsole( float frac ) row--; } - char color = COLOR_WHITE; - re.SetColor( ConsoleColorFromChar( color ) ); + const int markStartX = min( con.markStartX, con.markX ); + const int markStartY = min( con.markStartY, con.markY ); + const int markEndX = max( con.markStartX, con.markX ); + const int markEndY = max( con.markStartY, con.markY ); + qbool drawMark = qfalse; + if (con.markMode) { + // draw the mark area background, if any + const float mhu = ( markEndY - markStartY + 1 ) * ch; // unclipped + const float mh = min( mhu, (float)(con.display - markStartY) * ch ); + drawMark = + markEndX != markStartX || + markEndY != markStartY || + Sys_Milliseconds() % 1000 < 500; + if (drawMark && mh > 0.0f) { + const float mx = con.xadjust + markStartX * cw; + const float my = y - ( con.display - markStartY - 1 ) * ch; + const float mw = ( markEndX - markStartX + 1 ) * cw; + Con_FillRect( mx, my, mw, mh, colMkBG ); + } + } con.rowsVisible = 0; - for (i = 0; i < rows; ++i, --row, y -= con.ch ) + for (i = 0; i < rows; ++i, --row, y -= ch ) { if (row < 0) break; @@ -806,25 +849,36 @@ static void Con_DrawSolidConsole( float frac ) if (y >= 0) con.rowsVisible++; - const short* text = con.text + (row % con.totallines)*con.linewidth; - - re.SetColor( colShadow ); + const short* const text = con.text + (row % con.totallines)*con.linewidth; + const qbool markRow = drawMark && row >= markStartY && row <= markEndY; + + char color = COLOR_INVALID; for (int j = 0; j < con.linewidth; ++j) { - SCR_DrawChar( 1 + con.xadjust + j * con.cw, 1 + y, con.cw, con.ch, (text[j] & 0xFF) ); + char newColor = COLOR_SHAD; + if (markRow && j >= markStartX && j <= markEndX) + newColor = COLOR_MKSHAD; + if (newColor != color) { + color = newColor; + re.SetColor( ConsoleColorFromChar( color ) ); + } + SCR_DrawChar( 1 + con.xadjust + j * cw, 1 + y, cw, ch, (text[j] & 0xFF) ); } if ((row % con.totallines) == con.searchLineIndex) { re.SetColor( colSearch ); SCR_DrawChar( con.xadjust - con.cw, y, con.cw, con.ch, 141 ); } - - re.SetColor( colText ); + + color = COLOR_INVALID; for (int j = 0; j < con.linewidth; ++j) { - if ((text[j] >> 8) != color) { - color = (text[j] >> 8); + char newColor = text[j] >> 8; + if (markRow && j >= markStartX && j <= markEndX) + newColor = COLOR_MKTEXT; + if (newColor != color) { + color = newColor; re.SetColor( ConsoleColorFromChar( color ) ); } - SCR_DrawChar( con.xadjust + j * con.cw, y, con.cw, con.ch, (text[j] & 0xFF) ); + SCR_DrawChar( con.xadjust + j * cw, y, cw, ch, (text[j] & 0xFF) ); } } @@ -1022,3 +1076,151 @@ void Con_ContinueSearch( qbool forward ) } } } + + +static void Con_AutoScrollMarkPosition() +{ + if (con.markY >= con.display) { + con.display = con.markY + 1; + } else if (con.markY < con.display - con.rowsVisible) { + // we need to jump past 1 more line when we're all the way at the bottom + const int extra = con.display == con.current ? 1 : 0; + con.display += con.markY - ( con.display - con.rowsVisible ) - extra; + } +} + + +qbool Con_HandleMarkMode( qbool ctrlDown, qbool shiftDown, int key ) +{ + // ctrl-m = toggle mark mode + if ( ctrlDown && tolower(key) == 'm' ) { + con.markMode = !con.markMode; + if (con.markMode) { + con.markX = 0; + con.markY = con.display - 1; + con.markStartX = 0; + con.markStartY = con.display - 1; + } + return qtrue; + } + + if (!con.markMode) { + return qfalse; + } + + if (key == K_HOME) { + if (ctrlDown) + return qfalse; + con.markX = 0; + if (!shiftDown) { + con.markStartX = con.markX; + con.markStartY = con.markY; + } + Con_AutoScrollMarkPosition(); + return qtrue; + } + + if(key == K_END) { + if (ctrlDown) + return qfalse; + con.markX = con.linewidth - 1; + if (!shiftDown) { + con.markStartX = con.markX; + con.markStartY = con.markY; + } + Con_AutoScrollMarkPosition(); + return qtrue; + } + + if (key == K_LEFTARROW) { + con.markX = max( con.markX - 1, 0 ); + if (!shiftDown) { + con.markStartX = con.markX; + con.markStartY = con.markY; + } + Con_AutoScrollMarkPosition(); + return qtrue; + } + + if (key == K_RIGHTARROW) { + con.markX = min( con.markX + 1, con.linewidth ); + if (!shiftDown) { + con.markStartX = con.markX; + con.markStartY = con.markY; + } + Con_AutoScrollMarkPosition(); + return qtrue; + } + + if (key == K_UPARROW) { + con.markY = max( con.markY - 1, con.totallines - 1 ); + if (!shiftDown) { + con.markStartX = con.markX; + con.markStartY = con.markY; + } + Con_AutoScrollMarkPosition(); + return qtrue; + } + + if (key == K_DOWNARROW) { + con.markY = min( con.markY + 1, con.current - 1 ); + if (!shiftDown) { + con.markStartX = con.markX; + con.markStartY = con.markY; + } + Con_AutoScrollMarkPosition(); + return qtrue; + } + + if (shiftDown && key == K_PGUP) { + con.markY = max( con.markY - 6, con.totallines - 1 ); + Con_AutoScrollMarkPosition(); + return qtrue; + } + + if (shiftDown && key == K_PGDN) { + con.markY = min( con.markY + 6, con.current - 1 ); + Con_AutoScrollMarkPosition(); + return qtrue; + } + + if ((ctrlDown && tolower(key) == 'c') || key == K_ENTER) { + const int markStartX = min( con.markStartX, con.markX ); + const int markStartY = min( con.markStartY, con.markY ); + const int markEndX = max( con.markStartX, con.markX ); + const int markEndY = max( con.markStartY, con.markY ); + char* dst = con.markText; + + for (int y = markStartY; y <= markEndY; ++y) { + if (y > markStartY) { + *dst++ = '\r'; + *dst++ = '\n'; + } + const int row = y % con.totallines; + const short* const text = con.text + row * con.linewidth; + for (int x = markStartX; x <= markEndX; ++x) { + *dst++ = text[x] & 0xFF; + } + } + *dst++ = '\0'; + + Sys_SetClipboardData( con.markText ); + con.markMode = qfalse; + return qtrue; + } + + // let the original scrolling code run... + if (key == K_PGDN || key == K_PGUP || + key == K_MWHEELDOWN || key == K_MWHEELUP) { + return qfalse; + } + + // ...but block everything else + return qtrue; +} + + +qbool Con_IsInMarkMode() +{ + return con.markMode; +} diff --git a/code/client/cl_keys.cpp b/code/client/cl_keys.cpp index fe8b6c6..a1535c6 100644 --- a/code/client/cl_keys.cpp +++ b/code/client/cl_keys.cpp @@ -213,7 +213,7 @@ EDIT FIELDS // handles horizontal scrolling and cursor blinking // position and char sizes are in pixels -void Field_Draw( field_t* edit, int x, int y, int cw, int ch, qbool extColors ) +void Field_Draw( field_t* edit, int x, int y, int cw, int ch, qbool extColors, qbool drawCaret ) { int len; int drawLen; @@ -263,8 +263,8 @@ void Field_Draw( field_t* edit, int x, int y, int cw, int ch, qbool extColors ) firstColor = extColors ? ConsoleColorFromChar( colorCode ) : ColorFromChar( colorCode ); SCR_DrawStringEx( x, y, cw, ch, str, extColors ? DSC_CONSOLE : DSC_NORMAL, qtrue, firstColor ); - if ( (int)( cls.realtime >> 8 ) & 1 ) { - return; // off blink + if ( !drawCaret || Sys_Milliseconds() % 500 < 250 ) { + return; } if ( key_overstrikeMode ) { @@ -469,6 +469,10 @@ CONSOLE LINE EDITING static void Console_Key( int key ) { + if ( Con_HandleMarkMode( keys[K_CTRL].down, keys[K_SHIFT].down, key ) ) { + return; + } + // clear auto-completion buffer when not pressing tab if ( key != K_TAB ) g_consoleField.acOffset = 0; @@ -1227,7 +1231,10 @@ void CL_CharEvent( int key ) { // distribute the key down event to the appropriate handler if ( cls.keyCatchers & KEYCATCH_CONSOLE ) { - Field_CharEvent( &g_consoleField, key ); + if ( !Con_IsInMarkMode() ) + { + Field_CharEvent( &g_consoleField, key ); + } } else if ( cls.keyCatchers & KEYCATCH_UI ) { diff --git a/code/client/client.h b/code/client/client.h index fb0f38a..1233136 100644 --- a/code/client/client.h +++ b/code/client/client.h @@ -445,6 +445,8 @@ void Con_Top(); void Con_Bottom(); void Con_Close(); void Con_ContinueSearch( qbool forward ); +qbool Con_HandleMarkMode( qbool ctrlDown, qbool shiftDown, int key ); +qbool Con_IsInMarkMode(); const float* ConsoleColorFromChar( char ccode ); diff --git a/code/client/keys.h b/code/client/keys.h index 9f6e8d4..7286fe5 100644 --- a/code/client/keys.h +++ b/code/client/keys.h @@ -22,7 +22,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "keycodes.h" -void Field_Draw( field_t* edit, int x, int y, int cw, int ch, qbool extColors ); +void Field_Draw( field_t* edit, int x, int y, int cw, int ch, qbool extColors, qbool drawCaret = qtrue ); extern history_t g_history; extern field_t g_consoleField; diff --git a/code/linux/linux_shared.cpp b/code/linux/linux_shared.cpp index 81da526..65e3976 100644 --- a/code/linux/linux_shared.cpp +++ b/code/linux/linux_shared.cpp @@ -194,10 +194,16 @@ void* QDECL Sys_LoadDll( const char* name, dllSyscall_t *entryPoint, dllSyscall_ } #ifdef DEDICATED -char *Sys_GetClipboardData(void) + +char* Sys_GetClipboardData() { - return NULL; + return NULL; } + +void Sys_SetClipboardData( const char* ) +{ +} + #endif void Sys_Init() diff --git a/code/linux/sdl_core.cpp b/code/linux/sdl_core.cpp index 04b8ef2..0dc040e 100644 --- a/code/linux/sdl_core.cpp +++ b/code/linux/sdl_core.cpp @@ -608,6 +608,12 @@ char* Sys_GetClipboardData() } +void Sys_SetClipboardData( const char* text ) +{ + SDL_SetClipboardText(text); +} + + void Lin_MatchStartAlert() { const int alerts = cl_matchAlerts->integer; diff --git a/code/qcommon/qcommon.h b/code/qcommon/qcommon.h index beb3a98..1bd0d84 100644 --- a/code/qcommon/qcommon.h +++ b/code/qcommon/qcommon.h @@ -1107,6 +1107,7 @@ void QDECL Sys_Error( const char *error, ...); // if it succeeds, returns memory allocated by Z_Malloc // note that this isn't journaled char *Sys_GetClipboardData( void ); +void Sys_SetClipboardData( const char* text ); void Sys_Print( const char *msg ); diff --git a/code/win32/win_main.cpp b/code/win32/win_main.cpp index 85d29bd..e424563 100644 --- a/code/win32/win_main.cpp +++ b/code/win32/win_main.cpp @@ -339,6 +339,26 @@ char *Sys_GetClipboardData( void ) } +void Sys_SetClipboardData( const char* text ) +{ + if ( OpenClipboard( NULL ) ) { + const int l = strlen( text ); + const HGLOBAL hMemory = GlobalAlloc( GMEM_MOVEABLE, l + 1 ); + if ( hMemory ) { + void* const dstMemory = GlobalLock( hMemory ); + if ( dstMemory ) { + strcpy( (char*)dstMemory, text ); + GlobalUnlock( hMemory ); + EmptyClipboard(); + SetClipboardData( CF_TEXT, hMemory ); + } + GlobalFree( hMemory ); + } + CloseClipboard(); + } +} + + /* ========================================================================