#include "precompiled.h" #include #include "CConsole.h" #include "Prometheus.h" #include "sysdep/sysdep.h" #include "input.h" #include "Hotkey.h" #include "scripting/ScriptingHost.h" extern bool keys[SDLK_LAST]; CConsole::CConsole() { m_bToggle = false; m_bVisible = false; m_fVisibleFrac = 0.0f; m_szBuffer = new wchar_t[CONSOLE_BUFFER_SIZE]; FlushBuffer(); m_iMsgHistPos = 1; InsertMessage(L"[ 0 A.D. Console v0.12 ] type \"\\info\" for help"); InsertMessage(L""); } CConsole::~CConsole() { m_mapFuncList.clear(); m_deqMsgHistory.clear(); m_deqBufHistory.clear(); delete[] m_szBuffer; } void CConsole::SetSize(float X, float Y, float W, float H) { m_fX = X; m_fY = Y; m_fWidth = W; m_fHeight = H; } void CConsole::ToggleVisible() { m_bToggle = true; m_bVisible = !m_bVisible; } void CConsole::SetVisible( bool visible ) { if( visible != m_bVisible ) m_bToggle = true; m_bVisible = visible; } void CConsole::FlushBuffer(void) { /* Clear the buffer and set the cursor and length to 0 */ memset(m_szBuffer, '\0', sizeof(wchar_t) * CONSOLE_BUFFER_SIZE); m_iBufferPos = m_iBufferLength = 0; } void CConsole::ToLower(wchar_t* szMessage, uint iSize) { uint L = (uint)wcslen(szMessage); if (L <= 0) return; if (iSize && iSize < L) L = iSize; for(uint i = 0; i < L; i++) szMessage[i] = towlower(szMessage[i]); } void CConsole::Trim(wchar_t* szMessage, const wchar_t cChar, uint iSize) { size_t L = wcslen(szMessage); if(!L) return; if (iSize && iSize < L) L = iSize; wchar_t szChar[2] = { cChar, 0 }; /* Find the first point at which szChar does not */ /* exist in the message */ size_t ofs = wcsspn(szMessage, szChar); if(ofs == 0) // no leading chars - we're done return; // move everything chars left, replacing leading cChar chars L -= ofs; memmove(szMessage, szMessage+ofs, L*sizeof(wchar_t)); for(ssize_t i = (ssize_t)L; i >= 0; i--) { szMessage[i] = '\0'; if (szMessage[i - 1] != cChar) break; } } void CConsole::RegisterFunc(fptr F, const wchar_t* szName) { // need to allocate a copy - szName may be a const string literal // (we'll change it - stripping out spaces and converting to lowercase). wchar_t copy[CONSOLE_BUFFER_SIZE]; copy[CONSOLE_BUFFER_SIZE-1] = '\0'; wcsncpy(copy, szName, CONSOLE_BUFFER_SIZE-1); Trim(copy); ToLower(copy); m_mapFuncList.insert(std::pair(copy, F)); } void CConsole::Update(const float DeltaTime) { if(m_bToggle) { const float AnimateTime = .30f; const float Delta = DeltaTime / AnimateTime; if(m_bVisible) { m_fVisibleFrac += Delta; if(m_fVisibleFrac > 1.0f) { m_fVisibleFrac = 1.0f; m_bToggle = false; } } else { m_fVisibleFrac -= Delta; if(m_fVisibleFrac < 0.0f) { m_fVisibleFrac = 0.0f; m_bToggle = false; } } } } //Render Manager. void CConsole::Render() { if (! (m_bVisible || m_bToggle) ) return; // animation: slide in from top of screen const float MaxY = m_fHeight; const float DeltaY = (1.0f - m_fVisibleFrac) * MaxY; glTranslatef(m_fX, m_fY + DeltaY, 0.0f); //Move to window position DrawWindow(); DrawHistory(); DrawBuffer(); } void CConsole::DrawWindow(void) { /* TODO: Add texturing */ glDisable(GL_TEXTURE_2D); glPushMatrix(); /* Draw Background */ /* Set the color to a translucent blue */ glColor4f(0.0f, 0.0f, 0.5f, 0.6f); glBegin(GL_QUADS); glVertex2f(0.0f, 0.0f); glVertex2f(m_fWidth-1.0f, 0.0f); glVertex2f(m_fWidth-1.0f, m_fHeight-1.0f); glVertex2f(0.0f, m_fHeight-1.0f); glEnd(); /* Draw Border */ /* Set the color to a translucent yellow */ glColor4f(0.5f, 0.5f, 0.0f, 0.6f); glBegin(GL_LINE_LOOP); glVertex2f(0.0f, 0.0f); glVertex2f(m_fWidth-1.0f, 0.0f); glVertex2f(m_fWidth-1.0f, m_fHeight-1.0f); glVertex2f(0.0f, m_fHeight-1.0f); glEnd(); if (m_fHeight > m_iFontHeight + 4) { glBegin(GL_LINES); glVertex2f(0.0f, (GLfloat)(m_iFontHeight + 4)); glVertex2f(m_fWidth, (GLfloat)(m_iFontHeight + 4)); glEnd(); } glPopMatrix(); glEnable(GL_TEXTURE_2D); } void CConsole::DrawHistory(void) { int i = 1; std::deque::iterator Iter; //History iterator glPushMatrix(); glColor3f(1.0f, 1.0f, 1.0f); //Set color of text glTranslatef(9.0f, (float)m_iFontOffset, 0.0f); //move away from the border // Draw the text upside-down, because it's aligned with // the GUI (which uses the top-left as (0,0)) glScalef(1.0f, -1.0f, 1.0f); for (Iter = m_deqMsgHistory.begin(); Iter != m_deqMsgHistory.end() && (((i - m_iMsgHistPos + 1) * m_iFontHeight) < m_fHeight); Iter++) { if (i >= m_iMsgHistPos){ glTranslatef(0.0f, -(float)m_iFontHeight, 0.0f); glPushMatrix(); glwprintf(L"%ls", Iter->data()); glPopMatrix(); } i++; } glPopMatrix(); } //Renders the buffer to the screen. void CConsole::DrawBuffer(void) { if (m_fHeight < m_iFontHeight) return; glPushMatrix(); glColor3f(1.0f, 1.0f, 0.0f); glTranslatef(2.0f, (float)m_iFontOffset, 0); glScalef(1.0f, -1.0f, 1.0f); glwprintf(L"]"); glColor3f(1.0f, 1.0f, 1.0f); if (m_iBufferPos==0) DrawCursor(); for (int i = 0; i < m_iBufferLength; i++){ glwprintf(L"%lc", m_szBuffer[i]); if (m_iBufferPos-1==i) DrawCursor(); } glPopMatrix(); } void CConsole::DrawCursor(void) { glPushMatrix(); // Slightly translucent yellow glColor4f(1.0f, 1.0f, 0.0f, 0.8f); // U+FE33: PRESENTATION FORM FOR VERTICAL LOW LINE // (sort of like a | which is aligned to the left of most characters) glwprintf(L"%lc", 0xFE33); // Revert to the standard text colour glColor3f(1.0f, 1.0f, 1.0f); glPopMatrix(); } //Inserts a character into the buffer. void CConsole::InsertChar(const int szChar, const wchar_t cooked ) { static int iHistoryPos = -1; if (!m_bVisible) return; switch (szChar){ case '\r': case '\n': iHistoryPos = -1; m_iMsgHistPos = 1; ProcessBuffer(m_szBuffer); FlushBuffer(); return; case '\t': /* Auto Complete */ return; case '\b': if (IsEmpty() || IsBOB()) return; if (m_iBufferPos == m_iBufferLength) m_szBuffer[m_iBufferPos - 1] = '\0'; else{ for(int j=m_iBufferPos-1; jm_iBufferPos; i--) m_szBuffer[i] = m_szBuffer[i-1]; // move chars to right m_szBuffer[i] = cooked; } m_iBufferPos++; m_iBufferLength++; return; } } void CConsole::InsertMessage(const wchar_t* szMessage, ...) { va_list args; wchar_t szBuffer[CONSOLE_MESSAGE_SIZE]; va_start(args, szMessage); if (vswprintf(szBuffer, CONSOLE_MESSAGE_SIZE, szMessage, args) == -1) { debug_out("Error printfing console message (buffer size exceeded?)\n"); // Make it obvious that the text was trimmed (assuming it was) wcscpy(szBuffer+CONSOLE_MESSAGE_SIZE-4, L"..."); } va_end(args); // Split into lines and add each one individually wchar_t* lineStart = szBuffer; wchar_t* lineEnd; while ( (lineEnd = wcschr(lineStart, '\n')) != NULL) { m_deqMsgHistory.push_front(std::wstring(lineStart, lineEnd)); lineStart = lineEnd+1; } m_deqMsgHistory.push_front(std::wstring(lineStart)); } const wchar_t* CConsole::GetBuffer() { m_szBuffer[m_iBufferLength] = 0; return( m_szBuffer ); } void CConsole::SetBuffer(const wchar_t* szMessage, ...) { va_list args; wchar_t szBuffer[CONSOLE_BUFFER_SIZE]; va_start(args, szMessage); vswprintf(szBuffer, CONSOLE_BUFFER_SIZE, szMessage, args); va_end(args); FlushBuffer(); wcsncpy(m_szBuffer, szMessage, CONSOLE_BUFFER_SIZE); m_iBufferLength = m_iBufferPos = (int)wcslen(m_szBuffer); } void CConsole::ProcessBuffer(const wchar_t* szLine){ if (szLine == NULL) return; if (wcslen(szLine) <= 0) return; assert(wcslen(szLine) < CONSOLE_BUFFER_SIZE); m_deqBufHistory.push_front(szLine); wchar_t szCommand[CONSOLE_BUFFER_SIZE]; memset(szCommand, '\0', sizeof(wchar_t) * CONSOLE_BUFFER_SIZE); std::map::iterator Iter; if (szLine[0] == '\\'){ swscanf(szLine, L"\\%ls", szCommand); Trim(szCommand); ToLower(szCommand); if (!wcscmp(szCommand, L"info")){ InsertMessage(L""); InsertMessage(L"[Information]"); InsertMessage(L" -View commands \"\\commands\""); InsertMessage(L" -Call command \"\\\""); InsertMessage(L" -Say \"\""); InsertMessage(L""); }else if (!wcscmp(szCommand, L"commands")){ InsertMessage(L""); InsertMessage(L"[Commands]"); if (!m_mapFuncList.size()) InsertMessage(L" (none registered)"); for (Iter = m_mapFuncList.begin(); Iter != m_mapFuncList.end(); Iter++) InsertMessage(L" \\%ls", Iter->first.data()); InsertMessage(L""); }else{ Iter = m_mapFuncList.find(szCommand); if (Iter == m_mapFuncList.end()) InsertMessage(L"unknown command <%ls>", szCommand); else Iter->second(); } } else if( szLine[0] == ':' ) { // Process it as JavaScript // Convert Unicode to 8-bit sort-of-ASCII g_ScriptingHost.ExecuteScript( CStr16( szLine + 1 ) ); } else if( szLine[0] == '?' ) { // Process it as JavaScript and display the result jsval rval = g_ScriptingHost.ExecuteScript( CStr16( szLine + 1 ) ); if( rval ) InsertMessage( L"%hs", g_ScriptingHost.ValueToString( rval ).c_str() ); } else InsertMessage(L": %ls", szLine); } #include "sdl.h" extern CConsole* g_Console; extern void Die(int err, const wchar_t* fmt, ...); int conInputHandler(const SDL_Event* ev) { if( ev->type == SDL_HOTKEYDOWN ) { if( ev->user.code == HOTKEY_CONSOLE_TOGGLE ) { g_Console->ToggleVisible(); return( EV_HANDLED ); } else if( ev->user.code == HOTKEY_CONSOLE_COPY ) { clipboard_set( g_Console->GetBuffer() ); } else if( ev->user.code == HOTKEY_CONSOLE_PASTE ) { wchar_t* text = clipboard_get(); if(text) { for(wchar_t* c = text; *c; c++) g_Console->InsertChar(0, *c); clipboard_free(text); } } } if( ev->type != SDL_KEYDOWN) return EV_PASS; SDLKey sym = ev->key.keysym.sym; if(!g_Console->IsActive()) return EV_PASS; // Stop unprintable characters (ctrl+, alt+ and escape), // also prevent ` and/or ~ appearing in console every time it's toggled. // MT: Is this safe? Does any valid character require a ctrl+ or alt+ // key combination? if( ( ev->key.keysym.sym != SDLK_ESCAPE ) && !keys[SDLK_LCTRL] && !keys[SDLK_RCTRL] && !keys[SDLK_LALT] && !keys[SDLK_RALT] && !hotkeys[HOTKEY_CONSOLE_TOGGLE] ) g_Console->InsertChar(sym, (wchar_t)ev->key.keysym.unicode ); return EV_PASS; }