/* =========================================================================== Doom 3 GPL Source Code Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?). Doom 3 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. Doom 3 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 Doom 3 Source Code. If not, see . In addition, the Doom 3 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 Doom 3 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 id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #include "../idlib/precompiled.h" #pragma hdrstop #include "DeviceContext.h" #include "Window.h" #include "UserInterfaceLocal.h" #include "SliderWindow.h" #include "EditWindow.h" #define MAXHISTORY 16 bool idEditWindow::ParseInternalVar( const char *_name, idParser *src ) { if ( idStr::Icmp( _name, "maxchars" ) == 0) { maxChars = src->ParseInt(); return true; } //bc if (idStr::Icmp( _name, "doHistoryScroll" ) == 0) { doHistoryScroll = src->ParseBool(); return true; } if (idStr::Icmp( _name, "noscrollbar" ) == 0) { noscrollbar = src->ParseBool(); return true; } if (idStr::Icmp( _name, "decktab" ) == 0) { decktab = src->ParseBool(); return true; } if ( idStr::Icmp( _name, "numeric" ) == 0) { numeric = src->ParseBool(); return true; } if ( idStr::Icmp( _name, "wrap" ) == 0) { wrap = src->ParseBool(); return true; } if ( idStr::Icmp( _name, "readonly" ) == 0) { readonly = src->ParseBool(); return true; } if ( idStr::Icmp( _name, "forceScroll" ) == 0) { forceScroll = src->ParseBool(); return true; } if ( idStr::Icmp( _name, "source" ) == 0) { ParseString( src, sourceFile ); return true; } if ( idStr::Icmp( _name, "password" ) == 0 ) { password = src->ParseBool(); return true; } if ( idStr::Icmp( _name, "cvarMax" ) == 0) { cvarMax = src->ParseInt(); return true; } return idWindow::ParseInternalVar( _name, src ); } idWinVar *idEditWindow::GetWinVarByName( const char *_name, bool fixup, drawWin_t** owner ) { if ( idStr::Icmp( _name, "cvar" ) == 0 ) { return &cvarStr; } if ( idStr::Icmp( _name, "password" ) == 0 ) { return &password; } if ( idStr::Icmp( _name, "liveUpdate" ) == 0 ) { return &liveUpdate; } if ( idStr::Icmp( _name, "cvarGroup" ) == 0 ) { return &cvarGroup; } return idWindow::GetWinVarByName( _name, fixup, owner ); } void idEditWindow::CommonInit() { maxChars = 128; numeric = false; paintOffset = 0; cursorPos = 0; cursorLine = 0; cvarMax = 0; wrap = false; sourceFile = ""; scroller = NULL; sizeBias = 0; lastTextLength = 0; forceScroll = false; password = false; cvar = NULL; liveUpdate = true; readonly = false; cmdIndex = 0; //cmdHistory.SetNum(0, false); cmdHistory.Clear(); scroller = new idSliderWindow(dc, gui); noscrollbar = false; doHistoryScroll = false; decktab = false; } idEditWindow::idEditWindow( idDeviceContext *d, idUserInterfaceLocal *g ) : idWindow(d, g) { dc = d; gui = g; CommonInit(); } idEditWindow::idEditWindow( idUserInterfaceLocal *g ) : idWindow(g) { gui = g; CommonInit(); } idEditWindow::~idEditWindow() { } void idEditWindow::GainFocus() { cursorPos = text.Length(); EnsureCursorVisible(); } void idEditWindow::Draw( int time, float x, float y ) { idVec4 color = foreColor; UpdateCvar( true ); int len = text.Length(); if ( len != lastTextLength ) { scroller->SetValue( 0.0f ); EnsureCursorVisible(); lastTextLength = len; } float scale = textScale; idStr pass; const char* buffer; if ( password ) { const char* temp = text; for ( ; *temp; temp++ ) { pass += "*"; } buffer = pass; } else { buffer = text; } if ( cursorPos > len ) { cursorPos = len; } idRectangle rect = textRect; rect.x -= paintOffset; rect.w += paintOffset; if ( wrap && scroller->GetHigh() > 0.0f ) { float lineHeight = GetMaxCharHeight( ) + 5; rect.y -= scroller->GetValue() * lineHeight; rect.w -= sizeBias; rect.h = ( breaks.Num() + 1 ) * lineHeight; } if ( hover && !noEvents && Contains(gui->CursorX(), gui->CursorY()) ) { color = hoverColor; } else { hover = false; } if ( flags & WIN_FOCUS ) { color = hoverColor; } dc->DrawText( buffer, scale, 0, color, rect, wrap, (flags & WIN_FOCUS) ? cursorPos : -1); } /* ============= idEditWindow::HandleEvent ============= */ const char *idEditWindow::HandleEvent(const sysEvent_t *event, bool *updateVisuals) { static char buffer[ MAX_EDITFIELD ]; const char *ret = ""; int key = event->evValue; if ( event->evType == SE_KEY) { if ( key == K_TAB && decktab) { return "decktab"; } } if ( wrap ) { // need to call this to allow proper focus and capturing on embedded children ret = idWindow::HandleEvent( event, updateVisuals ); if ( ret && *ret ) { return ret; } } if ( ( event->evType != SE_CHAR && event->evType != SE_KEY ) ) { return ret; } idStr::Copynz( buffer, text.c_str(), sizeof( buffer ) ); int len = text.Length(); if ( event->evType == SE_CHAR ) { //bc if not visible, ignore keyboard input. if ( !this->visible ) { return ""; } if ( event->evValue == Sys_GetConsoleKey( false ) || event->evValue == Sys_GetConsoleKey( true ) ) { return ""; } if ( updateVisuals ) { *updateVisuals = true; } if ( maxChars && len > maxChars ) { len = maxChars; } if ( ( key == K_ENTER || key == K_KP_ENTER ) && event->evValue2 ) { RunScript( ON_ACTION ); RunScript( ON_ENTER ); return cmd; } if (key != K_BACKSPACE && key != K_ENTER && key != K_KP_ENTER && (key != 'h' - 'a' + 1)) { //play keystroke sound effect. session->sw->PlayShaderDirectly( "keystroke" ); } if ( key == K_ESCAPE ) { RunScript( ON_ESC ); return cmd; } if ( readonly ) { return ""; } if ( key == 'h' - 'a' + 1 || key == K_BACKSPACE ) { // ctrl-h is backspace if ( cursorPos > 0 ) { //=================================== // BEGIN CTRL+BACKSPACE FUNCTIONALITY if (idKeyInput::IsDown( K_CTRL )) { //Determine where the preceding space or semicolon is. int i; int dividerIndex = -1; int cursorDelta; for (i = cursorPos - 1; i >= 0; i--) { if (text[i] == ' ' || text[i] == ';' || text[i] == '.') { dividerIndex = i; break; } } if (dividerIndex < 0) { //no dividers found. delete everything. this->cursorPos = 0; this->text = ""; return ""; } cursorDelta = cursorPos - dividerIndex - 1; if (cursorDelta <= 0) { //possibly in a block of spaces or semicolons. dividerIndex = -1; for (i = cursorPos - 1; i >= 0; i--) { if (text[i] != ' ' && text[i] != ';' && text[i] != '.') { dividerIndex = i; break; } } cursorDelta = cursorPos - dividerIndex - 1; } memmove( &buffer[ cursorPos - cursorDelta ], &buffer[ cursorPos ], len + cursorDelta - cursorPos); cursorPos -= cursorDelta; } //================ END CTRL+BACKSPACE FUNCTIONALITY else { if ( cursorPos >= len ) { buffer[len - 1] = 0; cursorPos = len - 1; } else { memmove( &buffer[ cursorPos - 1 ], &buffer[ cursorPos ], len + 1 - cursorPos); cursorPos--; } } text = buffer; UpdateCvar( false ); RunScript( ON_ACTION ); } return ""; } // // ignore any non printable chars (except enter when wrap is enabled) // if ( wrap && (key == K_ENTER || key == K_KP_ENTER) ) { } else if ( !idStr::CharIsPrintable( key ) ) { return ""; } if ( numeric ) { if ( ( key < '0' || key > '9' ) && key != '.' ) { return ""; } } if ( dc->GetOverStrike() ) { if ( maxChars && cursorPos >= maxChars ) { return ""; } } else { if ( ( len == MAX_EDITFIELD - 1 ) || ( maxChars && len >= maxChars ) ) { return ""; } if ( cursorPos < len ) { memmove( &buffer[ cursorPos + 1 ], &buffer[ cursorPos ], len + 1 - cursorPos ); } } buffer[ cursorPos ] = key; text = buffer; UpdateCvar( false ); RunScript( ON_ACTION ); if ( cursorPos < len + 1 ) { cursorPos++; } EnsureCursorVisible(); } else if ( event->evType == SE_KEY && event->evValue2 ) { if ( updateVisuals ) { *updateVisuals = true; } if ( key == K_MWHEELDOWN ) { //bc if (noscrollbar) { return ret; } return "scrollnext"; } if ( key == K_MWHEELUP ) { //bc if (noscrollbar) { return ret; } return "scrollprev"; } if ( key == K_DEL ) { if ( readonly ) { return ret; } if ( cursorPos < len ) { //========================================= // BEGIN CTRL+DELETE FUNCTIONALITY if (idKeyInput::IsDown( K_CTRL )) { int i; int dividerIndex; int cursorDelta; dividerIndex = -1; for (i = cursorPos ; i < len; i++) { if (text[i] == ' ' || text[i] == ';' || text[i] == '.') { dividerIndex = i; break; } } if (dividerIndex < 0) { //no dividers found. Delete everything past the cursor. int delta = len - this->cursorPos; memmove( &buffer[cursorPos], &buffer[cursorPos + delta], len - cursorPos); } else { cursorDelta = dividerIndex - this->cursorPos; cursorDelta = Max(cursorDelta, 1); if (cursorDelta <= 1) { dividerIndex = -1; for (i = cursorPos ; i < len; i++) { if (text[i] != ' ' && text[i] != ';' && text[i] != '.') { dividerIndex = i; break; } } cursorDelta = dividerIndex - this->cursorPos; } memmove( &buffer[cursorPos], &buffer[cursorPos + cursorDelta], len - cursorPos); } } // =========================== END CTRL+DELETE FUNCTIONALITY else { memmove( &buffer[cursorPos], &buffer[cursorPos + 1], len - cursorPos); } text = buffer; UpdateCvar( false ); RunScript( ON_ACTION ); } return ret; } if ( key == K_RIGHTARROW || ( tolower( key ) == 'f' && idKeyInput::IsDown( K_CTRL )) || ( tolower( key ) == 'f' && idKeyInput::IsDown( K_ALT ))) { if ( cursorPos < len ) { if ( (idKeyInput::IsDown( K_CTRL ) && key == K_RIGHTARROW) || ( tolower( key ) == 'f' && idKeyInput::IsDown( K_ALT ))) { // skip to next word while( ( cursorPos < len ) && ( buffer[ cursorPos ] != ';' ) ) { cursorPos++; } while( ( cursorPos < len ) && ( buffer[ cursorPos ] == ';' ) ) { cursorPos++; } } else { if ( cursorPos < len ) { cursorPos++; } } } EnsureCursorVisible(); return ret; } if ( key == K_LEFTARROW || ( tolower( key ) == 'b' && idKeyInput::IsDown( K_CTRL )) || ( tolower( key ) == 'b' && idKeyInput::IsDown( K_ALT ))) { if ( (idKeyInput::IsDown( K_CTRL ) && key == K_LEFTARROW) || ( tolower( key ) == 'b' && idKeyInput::IsDown( K_ALT ))) { // skip to previous word while( ( cursorPos > 0 ) && ( buffer[ cursorPos - 1 ] == ';' ) ) { cursorPos--; } while( ( cursorPos > 0 ) && ( buffer[ cursorPos - 1 ] != ';' ) ) { cursorPos--; } } else { if ( cursorPos > 0 ) { cursorPos--; } } EnsureCursorVisible(); return ret; } if ( key == K_HOME || ( tolower( key ) == 'a' && idKeyInput::IsDown( K_CTRL ) )) { if ( idKeyInput::IsDown( K_CTRL ) || cursorLine <= 0 || ( cursorLine >= breaks.Num() ) ) { cursorPos = 0; } else { cursorPos = breaks[cursorLine]; } EnsureCursorVisible(); return ret; } if ( key == K_END || ( tolower( key ) == 'e' && idKeyInput::IsDown( K_CTRL ) )) { if ( idKeyInput::IsDown( K_CTRL ) || (cursorLine < -1) || ( cursorLine >= breaks.Num() - 1 ) ) { cursorPos = len; } else { cursorPos = breaks[cursorLine + 1] - 1; } EnsureCursorVisible(); return ret; } if ( key == K_INS ) { if ( !readonly ) { dc->SetOverStrike( !dc->GetOverStrike() ); } return ret; } if (doHistoryScroll) { if (cmdHistory.Num() > 0 && (key == K_UPARROW || key == K_DOWNARROW || ( tolower( key ) == 'p' && idKeyInput::IsDown( K_CTRL ) ) || ( tolower( key ) == 'n' && idKeyInput::IsDown( K_CTRL ) ))) { if (key == K_UPARROW || ( tolower( key ) == 'p' && idKeyInput::IsDown( K_CTRL ) )) cmdIndex--; else cmdIndex++; if (cmdIndex < 0) cmdIndex = 0; if (cmdIndex > cmdHistory.Num() - 1) cmdIndex = cmdHistory.Num() - 1; text = cmdHistory[cmdIndex].c_str(); cursorPos = cmdHistory[cmdIndex].Length() ; return cmd; } } if ( key == K_DOWNARROW ) { if ( idKeyInput::IsDown( K_CTRL ) ) { scroller->SetValue( scroller->GetValue() + 1.0f ); } else { if ( cursorLine < breaks.Num() - 1 ) { int offset = cursorPos - breaks[cursorLine]; cursorPos = breaks[cursorLine + 1] + offset; EnsureCursorVisible(); } } } if (key == K_UPARROW ) { if ( idKeyInput::IsDown( K_CTRL ) ) { scroller->SetValue( scroller->GetValue() - 1.0f ); } else { if ( cursorLine > 0 ) { int offset = cursorPos - breaks[cursorLine]; cursorPos = breaks[cursorLine - 1] + offset; EnsureCursorVisible(); } } } //BC ctrl+c if (key == 'c' && idKeyInput::IsDown(K_CTRL)) { text = ""; } //bc 2-19-2016 //clear everything after cursor. ctrl+k if (key == 'k' && idKeyInput::IsDown(K_CTRL)) { if (len <= 0 || cursorPos >= len ) { return ret; } int delta = len - this->cursorPos; memmove( &buffer[cursorPos], &buffer[cursorPos + delta], len - cursorPos); text = buffer; return ret; } //bc 2-19-2016 //clear everything before cursor. ctrl+u if (key == 'u' && idKeyInput::IsDown(K_CTRL)) { if (len <= 0 || cursorPos <= 0) { return ret; } memmove( &buffer[ 0 ], &buffer[ cursorPos ], len); cursorPos = 0; text = buffer; return ret; } if ( key == K_ENTER || key == K_KP_ENTER ) { RunScript( ON_ACTION ); RunScript( ON_ENTER ); if (doHistoryScroll) { idStr cleanCommand = text; cleanCommand.StripTrailingWhitespace(); cleanCommand.StripLeading( ' ' ); cleanCommand.ToLower(); if (cleanCommand.Length() > 0) { //remove items if list exceeds max size. if (cmdHistory.Num() > MAXHISTORY) cmdHistory.RemoveIndex( 0 ); cmdHistory.Append( text ); cmdIndex = cmdHistory.Num() ; } } return cmd; } if ( key == K_ESCAPE ) { RunScript( ON_ESC ); if (doHistoryScroll) cmdIndex = cmdHistory.Num(); return cmd; } } else if ( event->evType == SE_KEY && !event->evValue2 ) { if ( key == K_ENTER || key == K_KP_ENTER ) { RunScript( ON_ENTERRELEASE ); return cmd; } else { RunScript( ON_ACTIONRELEASE ); } } return ret; } void idEditWindow::PostParse() { idWindow::PostParse(); if ( maxChars == 0 ) { maxChars = 10; } if ( sourceFile.Length() ) { void *buffer; fileSystem->ReadFile( sourceFile, &buffer ); text = (char *) buffer; fileSystem->FreeFile( buffer ); } InitCvar(); InitScroller(false); EnsureCursorVisible(); flags |= WIN_CANFOCUS; } /* ================ idEditWindow::InitScroller This is the same as in idListWindow ================ */ void idEditWindow::InitScroller( bool horizontal ) { const char *thumbImage = "guis/assets/scrollbar_thumb.tga"; const char *barImage = "guis/assets/scrollbarv.tga"; const char *scrollerName = "_scrollerWinV"; if (horizontal) { barImage = "guis/assets/scrollbarh.tga"; scrollerName = "_scrollerWinH"; } const idMaterial *mat = declManager->FindMaterial( barImage ); mat->SetSort( SS_GUI ); sizeBias = mat->GetImageWidth(); idRectangle scrollRect; if (horizontal) { sizeBias = mat->GetImageHeight(); scrollRect.x = 0; scrollRect.y = (clientRect.h - sizeBias); scrollRect.w = clientRect.w; scrollRect.h = sizeBias; } else { scrollRect.x = (clientRect.w - sizeBias); scrollRect.y = 0; scrollRect.w = sizeBias; scrollRect.h = clientRect.h; } scroller->InitWithDefaults(scrollerName, scrollRect, foreColor, matColor, mat->GetName(), thumbImage, !horizontal, true); InsertChild(scroller, NULL); //bc if (!noscrollbar) { scroller->SetBuddy(this); } } void idEditWindow::HandleBuddyUpdate( idWindow *buddy ) { } void idEditWindow::EnsureCursorVisible() { if ( readonly ) { cursorPos = -1; } else if ( maxChars == 1 ) { cursorPos = 0; } if ( !dc ) { return; } SetFont(); if ( !wrap ) { int cursorX = 0; if ( password ) { cursorX = cursorPos * dc->CharWidth( '*', textScale ); } else { int i = 0; while ( i < text.Length() && i < cursorPos ) { if ( idStr::IsColor( &text[i] ) ) { i += 2; } else { cursorX += dc->CharWidth( text[i], textScale ); i++; } } } int maxWidth = GetMaxCharWidth( ); int left = cursorX - maxWidth; int right = ( cursorX - textRect.w ) + maxWidth; if ( paintOffset > left ) { // When we go past the left side, we want the text to jump 6 characters paintOffset = left - maxWidth * 6; } if ( paintOffset < right) { paintOffset = right; } if ( paintOffset < 0 ) { paintOffset = 0; } scroller->SetRange(0.0f, 0.0f, 1.0f); } else { // Word wrap breaks.Clear(); idRectangle rect = textRect; rect.w -= sizeBias; dc->DrawText(text, textScale, textAlign, colorWhite, rect, true, (flags & WIN_FOCUS) ? cursorPos : -1, true, &breaks ); int fit = textRect.h / (GetMaxCharHeight() + 5); if ( fit < breaks.Num() + 1 ) { scroller->SetRange(0, breaks.Num() + 1 - fit, 1); } else { // The text fits completely in the box scroller->SetRange(0.0f, 0.0f, 1.0f); } if ( forceScroll ) { scroller->SetValue( breaks.Num() - fit ); } else if ( readonly ) { } else { cursorLine = 0; for ( int i = 1; i < breaks.Num(); i++ ) { if ( cursorPos >= breaks[i] ) { cursorLine = i; } else { break; } } int topLine = idMath::FtoiFast( scroller->GetValue() ); if ( cursorLine < topLine ) { scroller->SetValue( cursorLine ); } else if ( cursorLine >= topLine + fit) { scroller->SetValue( ( cursorLine - fit ) + 1 ); } } } } void idEditWindow::Activate(bool activate, idStr &act) { idWindow::Activate(activate, act); if ( activate ) { UpdateCvar( true, true ); EnsureCursorVisible(); } } /* ============ idEditWindow::InitCvar ============ */ void idEditWindow::InitCvar( ) { if ( cvarStr[0] == '\0' ) { if ( text.GetName() == NULL ) { common->Warning( "idEditWindow::InitCvar: gui '%s' window '%s' has an empty cvar string", gui->GetSourceFile(), name.c_str() ); } cvar = NULL; return; } cvar = cvarSystem->Find( cvarStr ); if ( !cvar ) { common->Warning( "idEditWindow::InitCvar: gui '%s' window '%s' references undefined cvar '%s'", gui->GetSourceFile(), name.c_str(), cvarStr.c_str() ); return; } } /* ============ idEditWindow::UpdateCvar ============ */ void idEditWindow::UpdateCvar( bool read, bool force ) { if ( force || liveUpdate ) { if ( cvar ) { if ( read ) { text = cvar->GetString(); } else { cvar->SetString( text ); if ( cvarMax && ( cvar->GetInteger() > cvarMax ) ) { cvar->SetInteger( cvarMax ); } } } } } /* ============ idEditWindow::RunNamedEvent ============ */ void idEditWindow::RunNamedEvent( const char* eventName ) { idStr event, group; if ( !idStr::Cmpn( eventName, "cvar read ", 10 ) ) { event = eventName; group = event.Mid( 10, event.Length() - 10 ); if ( !group.Cmp( cvarGroup ) ) { UpdateCvar( true, true ); } } else if ( !idStr::Cmpn( eventName, "cvar write ", 11 ) ) { event = eventName; group = event.Mid( 11, event.Length() - 11 ); if ( !group.Cmp( cvarGroup ) ) { UpdateCvar( false, true ); } } else if ( !idStr::Cmpn( eventName, "endcursor", 7 ) ) { int len = text.Length(); this->cursorPos = len; } else if ( !idStr::Cmpn( eventName, "getCursorIndex", 14 )) { cvarSystem->SetCVarInteger( "cursorindex", this->cursorPos ); } else if ( !idStr::Cmpn( eventName, "movecursor", 10 )) { this->cursorPos = cvarSystem->GetCVarInteger( "desiredcursorpos" ); } }