#include "stdafx.h" #include "CLCDInput.h" #include "CLCDOutputManager.h" //************************************************************************ // CLCDInput::CLCDInput //************************************************************************ CLCDInput::CLCDInput() : m_hKBHook(nullptr), m_bShowSymbols(true), m_bShowMarker(false), m_lInputTime(0), m_iLinePosition(0), m_iLineCount(0), m_iBreakKeys(KEYS_RETURN), m_lBlinkTimer(0), m_pScrollbar(nullptr) { memset(&m_Marker, 0, sizeof(m_Marker)); // SetScrollbarAlignment(TOP); Reset(); } //************************************************************************ // CLCDInput::~CLCDInput //************************************************************************ CLCDInput::~CLCDInput() { m_vLineOffsets.clear(); } //************************************************************************ // CLCDInput::Initialize //************************************************************************ bool CLCDInput::Initialize() { if(!CLCDTextObject::Initialize()) return false; // m_pParent = pParent; return true; } //************************************************************************ // CLCDInput::Shutdown //************************************************************************ bool CLCDInput::Shutdown() { if(!CLCDTextObject::Shutdown()) return false; return true; } //************************************************************************ // CLCDInput::Update //************************************************************************ void CLCDInput::SetScrollbar(CLCDBar *pScrollbar) { m_pScrollbar = pScrollbar; if(m_pScrollbar) { m_pScrollbar->SetSliderSize(m_iLineCount); m_pScrollbar->SetRange(0, (int)m_vLineOffsets.size()); m_pScrollbar->ScrollTo(m_iLinePosition); } } //************************************************************************ // CLCDInput::GetLastInputTime //************************************************************************ long CLCDInput::GetLastInputTime() { return m_lInputTime; } //************************************************************************ // CLCDInput::Update //************************************************************************ bool CLCDInput::Update() { if(!CLCDTextObject::Update()) return false; if(m_lBlinkTimer + 500 <= (long)GetTickCount()) { m_bShowMarker = !m_bShowMarker; m_lBlinkTimer = GetTickCount(); } return true; } //************************************************************************ // CLCDInput::Draw //************************************************************************ bool CLCDInput::Draw(CLCDGfx *pGfx) { if(!CLCDTextObject::Draw(pGfx)) return false; SelectObject(pGfx->GetHDC(),m_hFont); RECT rBoundary = { 0, 0,0 + GetWidth(), 0 + GetHeight() }; int iLine = m_iLinePosition; int iEndLine = m_iLinePosition + m_iLineCount; int iLen = 0; wchar_t *pcOffset = nullptr; while(iLine < iEndLine && iLine < m_vLineOffsets.size()) { // Calculate the text length if(iLine < m_vLineOffsets.size() -1) { iLen = m_vLineOffsets[iLine+1].iOffset - m_vLineOffsets[iLine].iOffset; // Draw the linebreak marker if(m_bShowSymbols && m_vLineOffsets[iLine+1].bLineBreak) pGfx->DrawFilledRect(m_vLineOffsets[iLine].iWidth+1,rBoundary.top+m_iFontHeight/3,m_iFontHeight/3,m_iFontHeight/3); } else iLen = (int)m_strText.length() - m_vLineOffsets[iLine].iOffset; // Draw the text pcOffset = (wchar_t*)m_strText.c_str() + m_vLineOffsets[iLine].iOffset; DrawTextEx(pGfx->GetHDC(), (LPTSTR)pcOffset, iLen, &rBoundary, m_iTextFormat, &m_dtp); // Draw the input cursor if(m_pInput && m_bShowMarker && m_Marker[0].iLine == iLine) { // insert-mode cursor if(m_bInsert ||m_Marker[0].iXWidth == 1) { pGfx->DrawFilledRect(m_Marker[0].iXLine, m_iFontHeight*(iLine-m_iLinePosition), 1, m_iFontHeight); } // replace-mode cursor else { RECT rMarker = {m_Marker[0].iXLine, m_iFontHeight*(iLine-m_iLinePosition), m_Marker[0].iXLine+m_Marker[0].iXWidth, m_iFontHeight*(iLine-m_iLinePosition+1)}; InvertRect(pGfx->GetHDC(),&rMarker); } } rBoundary.top += m_iFontHeight; rBoundary.bottom += m_iFontHeight; iLine++; } return true; } //************************************************************************ // CLCDInput::ShowSymbols //************************************************************************ void CLCDInput::ShowSymbols(bool bShow) { m_bShowSymbols = bShow; } //************************************************************************ // CLCDInput::SetBreakKeys //************************************************************************ void CLCDInput::SetBreakKeys(int iKeys) { m_iBreakKeys = iKeys != 0; } //************************************************************************ // returns wether the input is currently active //************************************************************************ bool CLCDInput::IsInputActive() { return m_pInput != nullptr; } //************************************************************************ // CLCDInput::OnSizeChanged //************************************************************************ void CLCDInput::OnSizeChanged(SIZE OldSize) { // if the width has changed, offsets need to be recalculated if(GetWidth() != OldSize.cx) OnFontChanged(); // otherwise, just update scrollbar & linecount else if(m_pScrollbar) { m_iLineCount = GetHeight() / m_iFontHeight; m_pScrollbar->SetSliderSize(m_iLineCount); } } //************************************************************************ // CLCDInput::OnFontChanged //************************************************************************ void CLCDInput::OnFontChanged() { if(m_iFontHeight == 0) return; if(m_pScrollbar) m_pScrollbar->SetSliderSize(m_iLineCount); m_iLinePosition = 0; m_iLineCount = GetHeight() / m_iFontHeight; if(m_pScrollbar) m_pScrollbar->SetSliderSize(m_iLineCount); m_Marker[0].iLine = 0; m_Marker[0].iPosition = 0; m_Marker[0].iPosition = (int)m_strText.length(); // Delete all offsets and recalculate them m_vLineOffsets.clear(); // Create a new offset SLineEntry offset; offset.bLineBreak = false; offset.iOffset = 0; m_vLineOffsets.push_back(offset); UpdateOffsets(0); UpdateMarker(); if(m_iLineCount > 0) ScrollToMarker(); } //************************************************************************ // CLCDInput::ActivateInput //************************************************************************ void CLCDInput::ActivateInput() { if(m_pInput) return; CLCDConnection *pLCDCon = CLCDOutputManager::GetInstance()->GetLCDConnection(); pLCDCon->SetAsForeground(1); m_hKBHook = SetWindowsHookEx(WH_KEYBOARD_LL, CLCDInput::KeyboardHook, GetModuleHandle(nullptr), 0); if(!m_hKBHook) return; m_pInput = this; GetKeyboardState(m_acKeyboardState); } //************************************************************************ // CLCDInput::DeactivateInput //************************************************************************ void CLCDInput::DeactivateInput() { if(!m_pInput) return; UnhookWindowsHookEx(m_hKBHook); m_hKBHook = nullptr; m_pInput = nullptr; CLCDConnection *pLCDCon = CLCDOutputManager::GetInstance()->GetLCDConnection(); pLCDCon->SetAsForeground(0); } //************************************************************************ // CLCDInput::KeyboardHook //************************************************************************ CLCDInput* CLCDInput::m_pInput = nullptr; LRESULT CALLBACK CLCDInput::KeyboardHook(int Code, WPARAM wParam, LPARAM lParam) { return m_pInput->ProcessKeyEvent(Code,wParam,lParam); } //************************************************************************ // CLCDInput::ProcessKeyEvent //************************************************************************ LRESULT CLCDInput::ProcessKeyEvent(int Code, WPARAM wParam, LPARAM lParam) { // Event verarbeiten if(Code == HC_ACTION) { KBDLLHOOKSTRUCT *key = (KBDLLHOOKSTRUCT *)(lParam); bool bKeyDown = !(key->flags & LLKHF_UP); bool bToggled = (m_acKeyboardState[key->vkCode] & 0x0F) != 0; if(bKeyDown) bToggled = !bToggled; m_acKeyboardState[key->vkCode] = (bKeyDown?0x80:0x00) | (bToggled?0x01:0x00); if(key->vkCode == VK_LSHIFT || key->vkCode == VK_RSHIFT) m_acKeyboardState[VK_SHIFT] = m_acKeyboardState[key->vkCode]; else if(key->vkCode == VK_LCONTROL || key->vkCode == VK_RCONTROL) m_acKeyboardState[VK_CONTROL] = m_acKeyboardState[key->vkCode]; else if(key->vkCode == VK_LMENU || key->vkCode == VK_RMENU) m_acKeyboardState[VK_MENU] = m_acKeyboardState[key->vkCode]; /* if(bKeyDown) TRACE(L"Key pressed: %i\n",key->vkCode); else TRACE(L"Key released: %i\n",key->vkCode); */ // Only handle Keyup if(bKeyDown) { // Actions with Control/Menu keys if((m_acKeyboardState[VK_LMENU] & 0x80 || m_acKeyboardState[VK_CONTROL] & 0x80) && m_acKeyboardState[VK_SHIFT] & 0x80) { ActivateKeyboardLayout((HKL)HKL_NEXT,0);//KLF_SETFORPROCESS); TRACE(L"Keyboardlayout switched!\n"); return 1; } int res = 0,size = 0,dir = MARKER_HORIZONTAL,scroll = 0; /* if(key->vkCode == VK_DELETE) { dir = MARKER_HOLD; res = -1; if(m_strText[m_Marker[0].iPosition] == '\r') res = -2; if(m_strText.length() >= m_Marker[0].iPosition + -res) { m_strText.erase(m_Marker[0].iPosition,-res); scroll = 1; size = 1; } else { res = 0; } } else */if(key->vkCode == VK_BACK ) { if(m_Marker[0].iPosition != 0) { res = -1; if(m_strText[m_Marker[0].iPosition+res] == '\n') res = -2; m_strText.erase(m_Marker[0].iPosition+res,-res); scroll = 1; size = res; } } // Marker navigation else if (key->vkCode == VK_INSERT) { m_bInsert = !m_bInsert; } else if(key->vkCode == VK_HOME) { res = m_vLineOffsets[m_Marker[0].iLine].iOffset - m_Marker[0].iPosition; scroll = 1; } else if(key->vkCode == VK_END) { if(m_vLineOffsets.size()-1 == m_Marker[0].iLine) res = (int)m_strText.length() - m_Marker[0].iPosition; else res = (m_vLineOffsets[m_Marker[0].iLine+1].iOffset - 1 - m_vLineOffsets[m_Marker[0].iLine+1].bLineBreak) -m_Marker[0].iPosition; scroll = 1; } else if(key->vkCode == VK_UP) { res = -1; dir = MARKER_VERTICAL; } else if(key->vkCode == VK_DOWN) { res = 1; dir = MARKER_VERTICAL; } else if(key->vkCode == VK_LEFT) res = -1; else if(key->vkCode == VK_RIGHT) res = 1; else { #ifdef _UNICODE wchar_t output[4]; #else unsigned char output[2]; #endif if(key->vkCode == VK_RETURN) { bool bCtrlDown = (m_acKeyboardState[VK_CONTROL] & 0x80) != 0; if( bCtrlDown != (m_iBreakKeys == KEYS_RETURN)) { DeactivateInput(); //m_pParent->OnInputFinished(); return 1; } else { res = 2; output[0] = '\r'; output[1] = '\n'; output[2] = 0; } } else { #ifdef _UNICODE res = ToUnicode(key->vkCode,key->scanCode,m_acKeyboardState,output,4,0); #else res = ToAscii( key->vkCode,key->scanCode,m_acKeyboardState,(WORD*)output,0); #endif } if(res <= 0) res = 0; else { if(output[0] != '\r' && output[0] <= 0x001F) return 1; if(m_bInsert || m_strText[m_Marker[0].iPosition] == '\r') m_strText.insert(m_Marker[0].iPosition,(wchar_t*)output,res); else m_strText.replace(m_Marker[0].iPosition,res,(wchar_t*)output); scroll = 1; size = res; } } if(res != 0) { if(dir != MARKER_HOLD) { MoveMarker(dir,res); } UpdateOffsets(size); UpdateMarker(); ScrollToMarker(); m_lInputTime = GetTickCount(); } //WrapLine(); // ---- // Block this KeyEvent } return 1; } return CallNextHookEx(m_hKBHook, Code, wParam, lParam); } //************************************************************************ // CLCDInput::MoveMarker //************************************************************************ void CLCDInput::MoveMarker(int iDir,int iMove,bool bShift) { // Just cursor if(!bShift) { m_lBlinkTimer = GetTickCount(); m_bShowMarker = true; if(iDir == MARKER_HORIZONTAL) { m_Marker[0].iPosition += iMove; } if(iDir == MARKER_VERTICAL) { if(iMove < 0 && m_Marker[0].iLine == 0) return; if(iMove > 0 && m_Marker[0].iLine == m_vLineOffsets.size()-1) return; m_Marker[0].iLine += iMove; int iX = 0,iX2 = 0; SIZE sizeChar = {0,0}; int iBegin = m_vLineOffsets[m_Marker[0].iLine].iOffset; int iLen = 0; if(m_Marker[0].iLine < m_vLineOffsets.size() -1) iLen = m_vLineOffsets[m_Marker[0].iLine+1].iOffset - m_vLineOffsets[m_Marker[0].iLine].iOffset; else iLen = (int)m_strText.length() - m_vLineOffsets[m_Marker[0].iLine].iOffset; HDC hDC = CreateCompatibleDC(nullptr); if(nullptr == hDC) return; SelectObject(hDC, m_hFont); m_Marker[0].iXWidth = 1; m_Marker[0].iPosition = -1; int *piWidths = new int[iLen]; int iMaxChars; int iChar=iBegin; GetTextExtentExPoint(hDC,m_strText.c_str() + iBegin,iLen,GetWidth(),&iMaxChars,piWidths,&sizeChar); for(;iChar= 0 && iChar >= m_Marker[0].iPosition) { m_Marker[0].iXWidth = sizeChar.cx; break; } if(iX >= m_Marker[0].iXLine || (iChar < iBegin+iLen -1 && m_strText[iChar+1] == 10)) { if( m_Marker[0].iXLine - iX2 <= iX - m_Marker[0].iXLine) { m_Marker[0].iPosition = iChar; m_Marker[0].iXLine = iX2; m_Marker[0].iXWidth = sizeChar.cx; break; } else { m_Marker[0].iPosition = iChar+1; m_Marker[0].iXLine = iX; } } } delete[] piWidths; if(m_Marker[0].iPosition == -1) { m_Marker[0].iPosition = iChar; m_Marker[0].iXLine = iX; } DeleteObject(hDC); } for(int i=0;i<2;i++) { if(m_Marker[i].iPosition < 0) m_Marker[i].iPosition = 0; else if(m_Marker[i].iPosition > m_strText.length() ) m_Marker[i].iPosition = (int)m_strText.length(); } if(m_Marker[0].iPosition > 0 && m_strText[m_Marker[0].iPosition-1] == '\r') m_Marker[0].iPosition+= (iDir == MARKER_HORIZONTAL && iMove>0)?1:-1; } } //************************************************************************ // CLCDInput::GetText //************************************************************************ tstring CLCDInput::GetText() { return m_strText; } //************************************************************************ // CLCDInput::Reset //************************************************************************ void CLCDInput::Reset() { m_lInputTime = 0; m_bInsert = true; memset(&m_Marker[0], 0, sizeof(SMarker)); m_strText = L""; m_vLineOffsets.clear(); m_iLinePosition = 0; SLineEntry offset; offset.bLineBreak = false; offset.iOffset = 0; m_vLineOffsets.push_back(offset); if(m_pScrollbar) { m_pScrollbar->ScrollTo(0); m_pScrollbar->SetRange(0,0); m_pScrollbar->SetSliderSize(m_iLineCount); } } //************************************************************************ // CLCDInput::UpdateOffsets //************************************************************************ void CLCDInput::UpdateOffsets(int iModified) { if(m_vLineOffsets.size() == 0 && m_strText.empty()) return; HDC hDC = CreateCompatibleDC(nullptr); if(nullptr == hDC) return; // Reset the marker m_Marker[0].iXLine = 0; m_Marker[0].iXWidth = 1; // Initialize variables int iLen = (int)m_strText.length(); int *piWidths = new int[iLen]; wchar_t *pszText = (wchar_t*)m_strText.c_str(); tstring::size_type pos = 0; int iMaxChars = 0; SIZE sizeWord = {0, 0}; SIZE sizeLine = {0, 0}; SelectObject(hDC, m_hFont); int iLine = -1; // Find the first line to update for(int i = (int)m_vLineOffsets.size()-1; i >= 0; i--) if(m_vLineOffsets[i].iOffset <= m_Marker[0].iPosition) { iLine = i; break; } if(iModified < 0 && iLine-1 >= 0) iLine--; bool bLineClosed = false; // TRACE(L"InputText: Begin Update at #%i\n",iLine); for(;iLine iLen) { // TRACE(L"InputText: Deleted offset #%i\n",iLine); m_vLineOffsets.erase(m_vLineOffsets.begin()+iLine); continue; } sizeLine.cx = 0; sizeWord.cx = 0; iWordOffset = iChar+1; while(iChar= iChar && pos <= iChar + iMaxChars) { iWordOffset = (int)pos + 1; iMaxChars = (int)pos - iChar; } // if the string doesnt fit, try to wrap the last word to the next line else { // find the last space in the line pos = m_strText.rfind(L" ",iChar + iMaxChars); if(pos != tstring::npos && pos >= iChar) iWordOffset = (int)pos +1; else iWordOffset = iChar+iMaxChars; } if(m_Marker[0].iPosition >= iChar && m_Marker[0].iPosition <= iChar + iMaxChars) { if(m_Marker[0].iPosition > iChar) { m_Marker[0].iXLine = piWidths[m_Marker[0].iPosition -1 - iChar]; if(m_strText[m_Marker[0].iPosition -1] == '\n' ) m_Marker[0].iXLine = 0; } if(m_Marker[0].iPosition < iChar + iMaxChars) { if(m_strText[m_Marker[0].iPosition] > 0x001F) m_Marker[0].iXWidth = piWidths[m_Marker[0].iPosition - iChar]-m_Marker[0].iXLine; } } //iChar += iMaxChars; if(m_strText[iChar] == '\n' || sizeLine.cx > GetWidth()) { bLineClosed = true; int iDistance = INFINITE; // Check if a matching offset already exists for(int iLine2 = iLine+1;iLine2 0) { if(m_strText[iChar] == '\n' && iMaxChars >= 2) m_vLineOffsets[iLine].iWidth = piWidths[iMaxChars-2]; else m_vLineOffsets[iLine].iWidth = piWidths[iMaxChars-1]; } else m_vLineOffsets[iLine].iWidth = 0; // TRACE(L"InputText: shifted offsets #%i to end %i position(s)\n",iLine+1,iDistance); for(iLine++;iLine 0) { if(m_strText[iChar-1] == '\n' && iMaxChars >= 2) m_vLineOffsets[iLine].iWidth = piWidths[iMaxChars-2]; else m_vLineOffsets[iLine].iWidth = piWidths[iMaxChars-1]; } else m_vLineOffsets[iLine].iWidth = 0; if(iLine != m_vLineOffsets.size() - 1 && !bLineClosed) { // TRACE(L"InputText: Deleted offsets #%i to #%i\n",iLine+1,m_vLineOffsets.size()-1); m_vLineOffsets.erase(m_vLineOffsets.begin()+iLine+1,m_vLineOffsets.end()); } } finished: delete[] piWidths; DeleteObject(hDC); if(m_pScrollbar) m_pScrollbar->SetRange(0, (int)m_vLineOffsets.size()-1); } //************************************************************************ // CLCDInput::UpdateMarker //************************************************************************ void CLCDInput::UpdateMarker() { // Adjust the markers propertys for(int i = (int)m_vLineOffsets.size()-1;i>= 0;i--) if(m_Marker[0].iPosition >= m_vLineOffsets[i].iOffset) { if(m_Marker[0].iPosition == m_vLineOffsets[i].iOffset) m_Marker[0].iXLine = 0; m_Marker[0].iLine = i; break; } } //************************************************************************ // CLCDInput::ScrollLine //************************************************************************ bool CLCDInput::ScrollLine(bool bDown) { if(bDown && m_iLinePosition + (m_iLineCount-1) < m_vLineOffsets.size() -1) m_iLinePosition++; else if(!bDown && m_iLinePosition > 0) m_iLinePosition--; else return false; if(m_pScrollbar) m_pScrollbar->ScrollTo(m_iLinePosition); return true; } //************************************************************************ // CLCDInput::ScrollToMarker //************************************************************************ void CLCDInput::ScrollToMarker() { if(m_Marker[0].iLine < m_iLinePosition) m_iLinePosition = m_Marker[0].iLine; if(m_Marker[0].iLine > m_iLinePosition + (m_iLineCount-1)) { ScrollLine(); if(m_Marker[0].iLine > m_iLinePosition + (m_iLineCount-1)) m_iLinePosition = (m_Marker[0].iLine / m_iLineCount )*m_iLineCount; } if(m_iLinePosition > m_vLineOffsets.size()-1) m_iLinePosition = (int)m_vLineOffsets.size() -1; if(m_iLinePosition < 0) m_iLinePosition = 0; if(m_pScrollbar) m_pScrollbar->ScrollTo(m_iLinePosition); } //************************************************************************ // CLCDInput::GetLineCount //************************************************************************ int CLCDInput::GetLineCount() { return (int)m_vLineOffsets.size(); } //************************************************************************ // CLCDInput::GetLine //************************************************************************ int CLCDInput::GetLine() { return m_iLinePosition; }