/*****************************************************************************/ /* TDataEditor.h Copyright (c) Ladislav Zezula 2014 */ /*---------------------------------------------------------------------------*/ /* Description : */ /*---------------------------------------------------------------------------*/ /* Date Ver Who Comment */ /* -------- ---- --- ------- */ /* 18.03.14 1.00 Lad The first version of TDataEditor.cpp */ /*****************************************************************************/ #include "FileTest.h" //----------------------------------------------------------------------------- // Local defines #define DATAEDIT_SPACES_BEFORE_HEXA 2 #define DATAEDIT_SPACES_BEFORE_TEXT 2 #define DATAEDIT_END_OF_LINE 0x7FFFFFFF //----------------------------------------------------------------------------- // Data for DataEditor // Selection mode for MoveCaretTo enum TSelectMode { SmUnchanged = 0, SmStartSelection, SmContinueSelection }; // Internal data structure struct TEditorData { ULONGLONG BaseAddress; // Base address of the data LPBYTE pbEditorDataBegin; // Pointer to the editor data LPBYTE pbEditorDataEnd; // Pointer to the editor data LPTSTR szLineBuffer; // Buffer for formatting single line HFONT hFont; // Currently used font HWND hWndParent; // Parent window HWND hWnd; // Window handle DWORD dwStyles; // Window styles DWORD dwId; // Control ID ULONG ExceptionCode; // Last exception code SIZE_T cbBytesPerLine; // Number of bytes per line size_t nTopIndex; // Index of the top line size_t nLines; // Number of lines TPointerFormat PointerFormat; // Format of the pointer int nPointerSize; // Width of the address, in chars int nAddressWidth; // Width of the address, in chars int nBeginHexaValues; // Start of the hexa values in the line int nEndHexaValues; // End of the hexa values in the line int nBeginTextValues; // Start of the text values in the line int nEndTextValues; // End of the hexa values in the line int nClientCY; // Height of the client area int nClientCX; // Width of the client area int nLeftOrg; // Origin of the left border (0 = default) in pixels int nCharHeight; // Height of one character, in screen pixels int nAveCharWidth; // Average width of one character, in screen pixels int nTabSize; // Size of one tab character, in logical units int nFullVisibleLines; // Number of fully visible lines int nVisibleLines; // Number of visible lines (including the last one, partially visible) size_t nStartSelLine; // Start selection int nStartSelCol; // Start selection size_t nEndSelLine; // End selection int nEndSelCol; // End selection size_t nCaretLine; // Line of the caret int nCaretCol; // Column of the caret int nCaretX; // X-Position of the caret int nCaretY; // Y-Position of the caret bool bWriteException; bool bReadException; bool bHasCapture; // If true if selecting text with the mouse bool bFixedFont; // If true, it means that we use fixed font size bool bHasCaret; }; #define GetEditorData(hWnd) ((TEditorData *)(LONG_PTR)GetWindowLongPtr(hWnd, GWLP_USERDATA)) //----------------------------------------------------------------------------- // Local variables // ASCII to printable characters BYTE AsciiToPrintableTable[256] = { 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E, 0x2E }; const LPTSTR szDataEditClassName = _T("DataEditor"); //----------------------------------------------------------------------------- // Data access functions static void SendExceptionNotification( TEditorData * pData, PVOID ExceptionAddress, ULONG ExceptionCode, BOOL WriteOperation) { DTE_EXCEPTION_DATA Data; // Notify the parent about the exception Data.hdr.hwndFrom = pData->hWnd; Data.hdr.idFrom = pData->dwId; Data.hdr.code = DEN_EXCEPTION; Data.ExceptionAddress = ExceptionAddress; Data.ExceptionCode = ExceptionCode; Data.WriteOperation = WriteOperation; SendMessage(pData->hWndParent, WM_NOTIFY, pData->dwId, (LPARAM)&Data); } static BYTE GuardedReadByte(TEditorData * pData, size_t cbByteOffset) { BYTE OneByte = 0; __try { // Only attempt to read the exception when read exception was not there yet if(pData->bReadException == false) { assert((pData->pbEditorDataBegin + cbByteOffset) < pData->pbEditorDataEnd); OneByte = pData->pbEditorDataBegin[cbByteOffset]; } } __except(EXCEPTION_EXECUTE_HANDLER) { // Notify parent about the exception SendExceptionNotification(pData, pData->pbEditorDataBegin + cbByteOffset, GetExceptionCode(), FALSE); // Remember that we has a read exception pData->ExceptionCode = GetExceptionCode(); pData->bReadException = true; } return OneByte; } static void GuardedWriteBuffer(TEditorData * pData, size_t cbByteOffset, LPCVOID pvBuffer, size_t cbLength) { __try { // Only attempt to read the exception when read exception was not there yet if(pData->bWriteException == false) { assert((pData->pbEditorDataBegin + cbByteOffset) < pData->pbEditorDataEnd); memcpy(pData->pbEditorDataBegin + cbByteOffset, pvBuffer, cbLength); } } __except(EXCEPTION_EXECUTE_HANDLER) { // Notify parent about the exception SendExceptionNotification(pData, pData->pbEditorDataBegin + cbByteOffset, GetExceptionCode(), TRUE); // Remember that we has a write exception pData->ExceptionCode = GetExceptionCode(); pData->bWriteException = true; } } static void PasteTextInternal(TEditorData * pData, LPCSTR szPasteText, size_t PasteOffset, size_t PasteLength) { LPBYTE pbPastePosition = pData->pbEditorDataBegin + PasteOffset; // If the paste position is before the end of data, do it if(pbPastePosition < pData->pbEditorDataEnd) { // Check the length of the data if((pbPastePosition + PasteLength) > pData->pbEditorDataEnd) PasteLength = (size_t)(pData->pbEditorDataEnd - pbPastePosition); // Perform the guarded memcopy GuardedWriteBuffer(pData, PasteOffset, szPasteText, PasteLength); } } //----------------------------------------------------------------------------- // Local functions // The caller needs to free the buffer using HeapFree(GetProcessHeap(), 0, szText) static LPSTR QueryClipboardText(HWND hWnd, size_t * pcchTextLength) { HANDLE hGlobal; LPCSTR szClipboardText; LPSTR szText = NULL; size_t cchText = 0; // Retrieve data from the clipboard if(IsClipboardFormatAvailable(CF_TEXT)) { if(OpenClipboard(hWnd)) { hGlobal = GetClipboardData(CF_TEXT); if(hGlobal != NULL) { szClipboardText = (LPCSTR)GlobalLock(hGlobal); if(szClipboardText != NULL) { cchText = strlen(szClipboardText); szText = (LPSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cchText + 1); if(szText != NULL) memcpy(szText, szClipboardText, cchText); GlobalUnlock(hGlobal); } } CloseClipboard(); } } // Give the result to the caller if(pcchTextLength != NULL) *pcchTextLength = cchText; return szText; } static LPTSTR FormatPointer(TEditorData * pData, LPTSTR Buffer, ULONGLONG LineAddress) { ULONG PointerHigh; ULONG PointerLow = (ULONG)(LineAddress); // Put the high DWORD on 64-bit pointer if(pData->nPointerSize == 8) { // Get the high DWORD pointer PointerHigh = (ULONG)(LineAddress >> 32); // Format the upper DWORD *Buffer++ = HexaAlphabetUpper[(PointerHigh >> 0x1C) & 0x0F]; *Buffer++ = HexaAlphabetUpper[(PointerHigh >> 0x18) & 0x0F]; *Buffer++ = HexaAlphabetUpper[(PointerHigh >> 0x14) & 0x0F]; *Buffer++ = HexaAlphabetUpper[(PointerHigh >> 0x10) & 0x0F]; *Buffer++ = HexaAlphabetUpper[(PointerHigh >> 0x0C) & 0x0F]; *Buffer++ = HexaAlphabetUpper[(PointerHigh >> 0x08) & 0x0F]; *Buffer++ = HexaAlphabetUpper[(PointerHigh >> 0x04) & 0x0F]; *Buffer++ = HexaAlphabetUpper[(PointerHigh >> 0x00) & 0x0F]; // Put the separator *Buffer++ = _T('\''); } // Format the upper DWORD *Buffer++ = HexaAlphabetUpper[(PointerLow >> 0x1C) & 0x0F]; *Buffer++ = HexaAlphabetUpper[(PointerLow >> 0x18) & 0x0F]; *Buffer++ = HexaAlphabetUpper[(PointerLow >> 0x14) & 0x0F]; *Buffer++ = HexaAlphabetUpper[(PointerLow >> 0x10) & 0x0F]; *Buffer++ = HexaAlphabetUpper[(PointerLow >> 0x0C) & 0x0F]; *Buffer++ = HexaAlphabetUpper[(PointerLow >> 0x08) & 0x0F]; *Buffer++ = HexaAlphabetUpper[(PointerLow >> 0x04) & 0x0F]; *Buffer++ = HexaAlphabetUpper[(PointerLow >> 0x00) & 0x0F]; // Put the end of string *Buffer = 0; return Buffer; } static int FormatOneLine(TEditorData * pData, size_t nLineIndex) { ULONGLONG LineAddress; LPBYTE pbDataBegin; LPBYTE pbDataPtr; LPBYTE pbDataEnd; LPTSTR szLineBuffer = pData->szLineBuffer; LPTSTR szLineEnd = szLineBuffer + pData->nEndTextValues; size_t DataOffset = (nLineIndex * pData->cbBytesPerLine); // Get the single line if(nLineIndex <= pData->nLines) { // Copy the address LineAddress = pData->BaseAddress + DataOffset; // Format the pointer szLineBuffer = FormatPointer(pData, szLineBuffer, LineAddress); // Put the spaces *szLineBuffer++ = _T(' '); *szLineBuffer++ = _T(' '); // Put the space between address and hexa values pbDataBegin = pData->pbEditorDataBegin + DataOffset; pbDataEnd = pbDataBegin + pData->cbBytesPerLine; // If there was an exception before, display the exception text pData->bReadException = false; GuardedReadByte(pData, DataOffset); if(pData->bReadException) { StringCchPrintf(szLineBuffer, (szLineEnd - szLineBuffer), _T("Exception %08X when reading address %p"), pData->ExceptionCode, pbDataBegin); szLineBuffer += _tcslen(szLineBuffer); return (int)(szLineBuffer - pData->szLineBuffer); } // Format all data as hexa values for(pbDataPtr = pbDataBegin; pbDataPtr < pbDataEnd; pbDataPtr++) { if(pbDataPtr < pData->pbEditorDataEnd) { *szLineBuffer++ = HexaAlphabetUpper[*pbDataPtr >> 0x04]; *szLineBuffer++ = HexaAlphabetUpper[*pbDataPtr & 0x0F]; *szLineBuffer++ = _T(' '); } else { *szLineBuffer++ = _T(' '); *szLineBuffer++ = _T(' '); *szLineBuffer++ = _T(' '); } } // Put the space between hexa values and text values *szLineBuffer++ = _T(' '); // Put the values as they are for(pbDataPtr = pbDataBegin; pbDataPtr < pbDataEnd; pbDataPtr++) { if(pbDataPtr < pData->pbEditorDataEnd) { *szLineBuffer++ = AsciiToPrintableTable[pbDataPtr[0]]; } else { *szLineBuffer++ = _T(' '); } } } // Finish the line with zero *szLineBuffer = 0; return (int)(szLineBuffer - pData->szLineBuffer); } static int FindPreviousWord(TEditorData * pData, size_t nLine, int nCol) { int nBeginHexaByte; int nCurrentByte; UNREFERENCED_PARAMETER(nLine); // If the caret position is greater than text view, move it to the begin of the text view if(nCol > pData->nBeginTextValues) return pData->nBeginTextValues; // If the caret position is within the hexa view, move it to the hexa byte if(nCol > pData->nBeginHexaValues) { // Get the current byte we are pointing at nCurrentByte = (nCol - pData->nBeginHexaValues) / 3; nBeginHexaByte = pData->nBeginHexaValues + (nCurrentByte * 3); if(nCol > nBeginHexaByte) return nBeginHexaByte; // Move to the previous byte return pData->nBeginHexaValues + (nCurrentByte - 1) * 3; } // We assume that we are before the hexa bytes --> go to position 0 return 0; } static int FindNextWord(TEditorData * pData, size_t nLine, int nCol) { int nCurrentByte; UNREFERENCED_PARAMETER(nLine); // If the caret position is greater than text view, move it to the end if(nCol >= pData->nBeginTextValues) return pData->nEndTextValues; // If the caret position is at the last byte, move it to the text area if(nCol >= pData->nEndHexaValues - 2) return pData->nBeginTextValues; // If the current position is at the hexa values, move to the previous hexa string if(nCol >= pData->nBeginHexaValues) { // Get the current byte we are pointing at nCurrentByte = (nCol - pData->nBeginHexaValues) / 3; return pData->nBeginHexaValues + (nCurrentByte + 1) * 3; } // We assume we are past the begin of the text values return pData->nBeginHexaValues; } static void CreateTextFont(TEditorData * pData) { LOGFONT LogFont; // Create font for all windows ZeroMemory(&LogFont, sizeof(LOGFONT)); LogFont.lfWeight = FW_NORMAL; LogFont.lfCharSet = DEFAULT_CHARSET; LogFont.lfQuality = DEFAULT_QUALITY; LogFont.lfPitchAndFamily = FIXED_PITCH; LogFont.lfHeight = -12; StringCchCopy(LogFont.lfFaceName, _countof(LogFont.lfFaceName), _T("Courier New")); pData->hFont = CreateFontIndirect(&LogFont); // If the font couldn't be created, create default one if(pData->hFont == NULL) { LogFont.lfHeight = -15; StringCchCopy(LogFont.lfFaceName, _countof(LogFont.lfFaceName), _T("Courier")); pData->hFont = CreateFontIndirect(&LogFont); // If even that failed, get the default fixed width font if(pData->hFont == NULL) { pData->hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT); } } // Apply the font to the window SendMessage(pData->hWnd, WM_SETFONT, (WPARAM)pData->hFont, 0); } // Recalculate the view based on new font static void QueryFontDimensions(TEditorData * pData) { TEXTMETRIC TextMetrics; HFONT hOldFont; HDC hdc; // Get the height of one line hdc = GetDC(pData->hWnd); if(hdc != NULL) { hOldFont = (HFONT)SelectObject(hdc, pData->hFont); GetTextMetrics(hdc, &TextMetrics); // Rememember the height of one line pData->nAveCharWidth = TextMetrics.tmAveCharWidth; pData->nCharHeight = TextMetrics.tmHeight; pData->nTabSize = pData->nAveCharWidth * 4; assert(pData->nCharHeight != 0); // Remember if we are using variable pich font. // Note that the TMPF_FIXED_PITCH bit is actually the opposite of what its meaning says pData->bFixedFont = (TextMetrics.tmPitchAndFamily & TMPF_FIXED_PITCH) ? false : true; // Select the old font and SelectObject(hdc, hOldFont); ReleaseDC(pData->hWnd, hdc); } } static void RecalculateView(TEditorData * pData) { SCROLLINFO si; DWORD dwOldStyles; DWORD dwNewStyles; DWORD dwAddStyles; RECT rect; size_t cbEditorData; // The data must already have been created assert(pData != NULL); // Update the client size GetClientRect(pData->hWnd, &rect); pData->nClientCX = rect.right; pData->nClientCY = rect.bottom; pData->nLines = 0; // Update the total number of lines cbEditorData = (size_t)(pData->pbEditorDataEnd - pData->pbEditorDataBegin); if(cbEditorData > 0) pData->nLines = ((cbEditorData - 1) / pData->cbBytesPerLine) + 1; // Update the number of lines, based on new size pData->nFullVisibleLines = (pData->nClientCY / pData->nCharHeight); pData->nVisibleLines = ((pData->nClientCY - 1) / pData->nCharHeight) + 1; // Now check if we need to show or remove vertical scroll bar dwAddStyles = ((pData->dwStyles & DES_VSCROLL) && (pData->nLines > (size_t)pData->nFullVisibleLines)) ? WS_VSCROLL : 0; dwOldStyles = (DWORD)GetWindowLongPtr(pData->hWnd, GWL_STYLE); dwNewStyles = (dwOldStyles & ~WS_VSCROLL) | dwAddStyles; // If the styles changed, apply them if(dwNewStyles != dwOldStyles) { SetWindowLongPtr(pData->hWnd, GWL_STYLE, dwNewStyles); SetWindowPos(pData->hWnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); } // Do we have vertical scrollbar ? if(dwNewStyles & WS_VSCROLL) { // Update scroll bar. Note that this also calls WM_SIZE, // which in turn calls this function. ZeroMemory(&si, sizeof(SCROLLINFO)); si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS; si.nPos = (int)pData->nTopIndex; si.nPage = (pData->nFullVisibleLines > 1) ? (pData->nFullVisibleLines - 1) : 1; si.nMax = (int)((pData->nLines > 1) ? (pData->nLines - 1) : 1); SetScrollInfo(pData->hWnd, SB_VERT, &si, FALSE); } } static void SetBytesPerLine(TEditorData * pData, SIZE_T cbBytesPerLine) { // Don't accept zero number of bytes per line if(cbBytesPerLine <= 0) cbBytesPerLine = 0x10; // Set the width of the pointer switch (pData->PointerFormat) { case PtrPlatformSpecific: pData->nAddressWidth = (pData->nPointerSize == 8) ? 17 : 8; pData->nPointerSize = sizeof(void *); break; case PtrPointer64Bit: pData->nAddressWidth = 17; pData->nPointerSize = 8; break; case PtrPointer32Bit: pData->nAddressWidth = 8; pData->nPointerSize = 4; break; } // Calculate the sizes pData->nBeginHexaValues = (int)(pData->nAddressWidth + DATAEDIT_SPACES_BEFORE_HEXA); pData->nEndHexaValues = (int)(pData->nBeginHexaValues + (cbBytesPerLine * 3) - 1); pData->nBeginTextValues = (int)(pData->nEndHexaValues + DATAEDIT_SPACES_BEFORE_TEXT); pData->nEndTextValues = (INT)(pData->nBeginTextValues + cbBytesPerLine); // Remember the number of bytes per line pData->cbBytesPerLine = cbBytesPerLine; // Reallocate the line buffer if(pData->szLineBuffer != NULL) HeapFree(GetProcessHeap(), 0, pData->szLineBuffer); pData->szLineBuffer = (LPTSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (pData->nEndTextValues + 1) * sizeof(TCHAR)); // Force the view to be recalculated RecalculateView(pData); } // Calculates the X and Y coordinates of the caret // - Does NOT set the caret position // - Does NOT redraw the window // - Returns TRUE if the caret's position actually changed static bool RecalcCaretXY(TEditorData * pData, bool * bNeedRedraw) { HFONT hOldFont; DWORD TextSize = 0; HDC hdc; int nSaveCaretX = pData->nCaretX; int nSaveCaretY = pData->nCaretY; // Calculate the Y-position of the caret pData->nCaretY = 0; if(pData->nLines > 0 && pData->nCaretLine > pData->nTopIndex) pData->nCaretY = ((int)(pData->nCaretLine - pData->nTopIndex) * pData->nCharHeight); // Calculate the X-position of the caret pData->nCaretX = 0; if(pData->nLines > 0 && pData->nCaretCol > 0) { hdc = GetDC(pData->hWnd); if(hdc != NULL) { // Retrieve the n-th line FormatOneLine(pData, pData->nCaretLine); // Calculate the width of the text from the beginning up to the char at the caret pos hOldFont = (HFONT)SelectObject(hdc, pData->hFont); TextSize = GetTabbedTextExtent(hdc, pData->szLineBuffer, pData->nCaretCol, 1, &pData->nTabSize); SelectObject(hdc, hOldFont); ReleaseDC(pData->hWnd, hdc); } // Increment the cursor x-pos by the char position pData->nCaretX += LOWORD(TextSize); } // If the X position od the caret got out of the screen, we have to move the org if(pData->nCaretX > (pData->nLeftOrg + pData->nClientCX)) { pData->nLeftOrg = pData->nCaretX - (pData->nClientCX - 10); *bNeedRedraw = true; } // If the caret went out of the view if(pData->nCaretX < pData->nLeftOrg) { pData->nLeftOrg = pData->nCaretX; *bNeedRedraw = true; } // Add the indent constant pData->nCaretX = (pData->nCaretX - pData->nLeftOrg) + DATAEDIT_TEXT_INDENT; // Return whether the caret's position has been changed or not return (bool)(pData->nCaretX != nSaveCaretX || pData->nCaretY != nSaveCaretY); } // // Recalculates the mouse position to caret line and column // static void MousePosToCaretPos(TEditorData * pData, int nMouseX, int nMouseY, size_t * piMouseLine, int * piMouseCol) { LPTSTR szLineText; HFONT hOldFont; DWORD TextSize; size_t nLineLength = 0; size_t nCharIndex1; size_t nCharIndex2; size_t nCharHalfX; HDC hdc; size_t nCaretLine; int nNextCharTreshold = (pData->nAveCharWidth / 2); int nCaretCol; int nRealWidth; // Adjust the text indent value if(nMouseX < DATAEDIT_TEXT_INDENT) nMouseX = DATAEDIT_TEXT_INDENT; nMouseX = nMouseX + pData->nLeftOrg - DATAEDIT_TEXT_INDENT; // Calculate Y character position if(nMouseY < 0) nCaretLine = pData->nTopIndex - (-nMouseY / pData->nCharHeight) - 1; else nCaretLine = pData->nTopIndex + (nMouseY / pData->nCharHeight); // By default, use average char width to calculate cursor X coordinate nCaretCol = (nMouseX / pData->nAveCharWidth); // Get line text where the caret is if(nCaretLine < pData->nLines) { // Get the text of the line nLineLength = FormatOneLine(pData, nCaretLine); if(nLineLength != 0) { if(pData->bFixedFont == false) { // Set the font into the HDC of the window hdc = GetDC(pData->hWnd); hOldFont = (HFONT)SelectObject(hdc, pData->hFont); // Initialize the search range nCharIndex1 = 0; nCharIndex2 = nLineLength + 1; szLineText = pData->szLineBuffer; // Perform binary search while(nCharIndex1 < (nCharIndex2 - 1)) { // Get character in half of the current string range nCharHalfX = nCharIndex1 + max((nCharIndex2 - nCharIndex1) / 2, 1); // Get size of it TextSize = GetTabbedTextExtent(hdc, szLineText, (int)nCharHalfX, 1, &pData->nTabSize); nRealWidth = LOWORD(TextSize) - nNextCharTreshold; // Choose one of the halves if(nRealWidth < nMouseX) nCharIndex1 = nCharHalfX; else nCharIndex2 = nCharHalfX; } // Get the column nCaretCol = (int)nCharIndex1; // Release the HDC of the window SelectObject(hdc, hOldFont); ReleaseDC(pData->hWnd, hdc); } else { // On a fixed-pitch font, it's simple ... nCaretCol = (nMouseX + nNextCharTreshold) / pData->nAveCharWidth; if(nCaretCol > (int)(nLineLength + 1)) nCaretCol = (int)(nLineLength + 1); } } } *piMouseLine = nCaretLine; *piMouseCol = nCaretCol; } //----------------------------------------------------------------------------- // Caret movement functions inline bool HasSelection(TEditorData * pData) { return (bool)(pData->nStartSelLine != pData->nEndSelLine || pData->nStartSelCol != pData->nEndSelCol); } inline bool IsReversedSelection(TEditorData * pData) { return ((pData->nEndSelLine < pData->nStartSelLine) || (pData->nStartSelLine == pData->nEndSelLine && pData->nEndSelCol < pData->nStartSelCol)); } // This method sets the current selection. // - Does NOT change position of the cursor // - Does NOT redraw the view static bool SetSelection( TEditorData * pData, size_t nStartSelLine, int nStartSelCol, size_t nEndSelLine, int nEndSelCol) { bool bSelectionChanged = false; // Only do selection if there are some lines if(pData->nLines == 0) { nStartSelLine = 0; nStartSelCol = 0; nEndSelLine = 0; nEndSelCol = 0; } // Determine if the selection has actually changed if(nStartSelLine != pData->nStartSelLine || nStartSelCol != pData->nStartSelCol) bSelectionChanged = true; if(nEndSelLine != pData->nEndSelLine || nEndSelCol != pData->nEndSelCol) bSelectionChanged = true; // Set the selection to the lines pData->nStartSelLine = nStartSelLine; pData->nStartSelCol = nStartSelCol; pData->nEndSelLine = nEndSelLine; pData->nEndSelCol = nEndSelCol; return bSelectionChanged; } // Moves the view and the caret // Returns true if the entire view needs to be redrawn. static void MoveTopIndexAndCaretTo( TEditorData * pData, size_t nNewTopIndex, size_t nNewCaretLine, int nNewCaretCol, TSelectMode SelectionMode) { SCROLLINFO si; size_t nNewStartSelLine; size_t nMaxTopIndex; bool bNeedRedraw = false; int nNewStartSelCol; int nPageSize; // We can not do anything if there are no lines if(pData->nLines != 0) { // The page size is the number of fully visible lines // If less than two lines are fully visible, the page size is one line nPageSize = (pData->nFullVisibleLines >= 2) ? pData->nFullVisibleLines : 1; // Check for moving the top index out of bounds nMaxTopIndex = pData->nLines - nPageSize; nNewTopIndex = (nNewTopIndex > nMaxTopIndex) ? nMaxTopIndex : nNewTopIndex; // Check if we are actually changing the top index if(nNewTopIndex != pData->nTopIndex) { pData->nTopIndex = nNewTopIndex; bNeedRedraw = true; } // Check for moving the caret line out of bounds nNewCaretLine = (nNewCaretLine > (pData->nLines - 1)) ? (pData->nLines - 1) : nNewCaretLine; // Check for moving the caret column out of bounds nNewCaretCol = (nNewCaretCol < 0) ? 0 : nNewCaretCol; nNewCaretCol = (nNewCaretCol > (int)pData->nEndTextValues) ? (int)pData->nEndTextValues : nNewCaretCol; // Set the new caret position pData->nCaretLine = nNewCaretLine; pData->nCaretCol = nNewCaretCol; // Update the selection nNewStartSelLine = (SelectionMode == SmStartSelection) ? nNewCaretLine : pData->nStartSelLine; nNewStartSelCol = (SelectionMode == SmStartSelection) ? nNewCaretCol : pData->nStartSelCol; if(SetSelection(pData, nNewStartSelLine, nNewStartSelCol, nNewCaretLine, nNewCaretCol)) bNeedRedraw = true; // If we need to redraw the view, do it if(bNeedRedraw) { // Redraw the view InvalidateRect(pData->hWnd, NULL, FALSE); // Do we have vertical scrollbar ? if(pData->dwStyles & DES_VSCROLL) { ZeroMemory(&si, sizeof(SCROLLINFO)); si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_PAGE | SIF_POS; si.nPos = (int)pData->nTopIndex; si.nPage = nPageSize; SetScrollInfo(pData->hWnd, SB_VERT, &si, TRUE); } } // Recalculate the caret's X and Y position and move the caret. // Move the caret position, if it changed if(pData->bHasCaret && RecalcCaretXY(pData, &bNeedRedraw)) SetCaretPos(pData->nCaretX, pData->nCaretY); } } // Changes the top index, keeps the caret position static void MoveTopIndexTo(TEditorData * pData, size_t nNewTopIndex) { MoveTopIndexAndCaretTo(pData, nNewTopIndex, pData->nCaretLine, pData->nCaretCol, SmContinueSelection); } // Moves a caret to new position. If the caret moves out of the current view, // the view is scrolled to ensure that the caret is visible static void MoveCaret_ScrollView( TEditorData * pData, size_t nNewCaretLine, int nNewCaretCol, TSelectMode SelectionMode) { size_t nNewTopIndex = pData->nTopIndex; int nPageSize = (pData->nFullVisibleLines >= 2) ? pData->nFullVisibleLines : 1; // If the caret goes before the top index, move the top index as well if(nNewCaretLine < nNewTopIndex) nNewTopIndex = nNewCaretLine; // If the new caret line is below the last visible line, move top index down if((nNewTopIndex + nPageSize - 1) < nNewCaretLine && nNewCaretLine <= pData->nLines) nNewTopIndex = (nNewCaretLine - nPageSize + 1); MoveTopIndexAndCaretTo(pData, nNewTopIndex, nNewCaretLine, nNewCaretCol, SelectionMode); } //----------------------------------------------------------------------------- // WM_ message handlers static void OnCreate(HWND hWnd, LPCREATESTRUCT pCreateParams) { TEditorData * pData = new TEditorData; // Pre-init the editor data ZeroMemory(pData, sizeof(TEditorData)); pData->hWndParent = pCreateParams->hwndParent; pData->dwStyles = pCreateParams->style; pData->hWnd = hWnd; pData->dwId = (DWORD)(DWORD_PTR)pCreateParams->hMenu; // Remove the VS_VISIBLE style, as we want it to show the real state pData->dwStyles &= ~WS_VISIBLE; // Remember the editor data SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pData); // We need to recalculate view for new font CreateTextFont(pData); QueryFontDimensions(pData); // Set the default number of bytes per line SetBytesPerLine(pData, 0x10); } static void OnShowWindow(HWND hWnd, WPARAM wParam) { TEditorData * pData = GetEditorData(hWnd); pData->dwStyles = (pData->dwStyles & ~WS_VISIBLE) | (wParam ? WS_VISIBLE : 0); } static void OnSize(HWND hWnd, LPARAM lParam) { TEditorData * pData = GetEditorData(hWnd); pData->nClientCX = GET_X_LPARAM(lParam); pData->nClientCY = GET_Y_LPARAM(lParam); } static void OnSetFocus(HWND hWnd) { TEditorData * pData = GetEditorData(hWnd); // Create the text caret if(CreateCaret(hWnd, NULL, 1, pData->nCharHeight)) { // Force recalculating the caret position, otherwise // the position remains unchanged from previously focused window SetCaretPos(pData->nCaretX, pData->nCaretY); ShowCaret(hWnd); pData->bHasCaret = true; } // Notify the parent that we received focus SendMessage(pData->hWndParent, WM_COMMAND, MAKEWPARAM(pData->dwId, EN_SETFOCUS), (LPARAM)pData->hWnd); } static void OnKillFocus(HWND hWnd) { TEditorData * pData = GetEditorData(hWnd); // If we have capture active, we have to release it if(pData->bHasCapture) { ReleaseCapture(); pData->bHasCapture = false; } // If we have caret, we have to get rid of it if(pData->bHasCaret) { HideCaret(hWnd); DestroyCaret(); pData->bHasCaret = false; } } static LRESULT OnKeyDown(HWND hWnd, WPARAM wParam, LPARAM /* lParam */) { TEditorData * pData = GetEditorData(hWnd); TSelectMode SelectionMode; UINT uKeyCode = (UINT)wParam; size_t nNewCaretLine; size_t nNewTopIndex; int nNewCaretCol = pData->nCaretCol; bool bIsShiftPressed = false; bool bIsCtrlPressed = false; // Do nothing if there are no lines if(pData->nLines == 0) return TRUE; // Get the state of the Shift key if(GetAsyncKeyState(VK_SHIFT) & 0x8000) bIsShiftPressed = true; if(GetAsyncKeyState(VK_CONTROL) & 0x8000) bIsCtrlPressed = true; // Determine the selection mode SelectionMode = bIsShiftPressed ? SmContinueSelection : SmStartSelection; // Perform key-specific action switch(uKeyCode) { case VK_SHIFT: case VK_CONTROL: case VK_MENU: break; case VK_HOME: // Ctrl+Shift+Home: Moves the caret to the first char of the document with selection // Ctrl+Home: Moves the caret to the first char of the document // Shift+Home: Moves the caret to the first char with selection // Home: Moves the caret to the first char nNewTopIndex = bIsCtrlPressed ? 0 : pData->nTopIndex; nNewCaretLine = bIsCtrlPressed ? 0 : pData->nCaretLine; MoveTopIndexAndCaretTo(pData, nNewTopIndex, nNewCaretLine, 0, SelectionMode); break; case VK_PRIOR: // Ctrl+Shift+PageUp: Moves the caret to the top line of the page with selection // Ctrl+PageUp: Moves the caret to the top line of the page // Shift+PageUp: Moves the caret one page up with selection // PageUp: Moves the top index one page up, keeps relative caret position nNewTopIndex = (pData->nTopIndex > (size_t)pData->nFullVisibleLines) ? (pData->nTopIndex - pData->nFullVisibleLines) : 0; nNewTopIndex = (bIsCtrlPressed == false) ? nNewTopIndex : pData->nTopIndex; nNewCaretLine = (pData->nCaretLine > (size_t)pData->nFullVisibleLines) ? (pData->nCaretLine - pData->nFullVisibleLines) : 0; nNewCaretLine = (bIsCtrlPressed == false) ? nNewCaretLine : pData->nTopIndex; MoveTopIndexAndCaretTo(pData, nNewTopIndex, nNewCaretLine, pData->nCaretCol, SelectionMode); break; case VK_UP: // Ctrl+Shift+ArrowUp: Nothing // Ctrl+ArrowUp: Moves the view up by one line, does not change selection or cursor pos // Shift+ArrowUp: Moves the caret one line up with selection // ArrowUp: Moves the caret one line up if(bIsCtrlPressed == false) { nNewCaretLine = (pData->nCaretLine > 0) ? (pData->nCaretLine - 1) : 0; MoveCaret_ScrollView(pData, nNewCaretLine, pData->nCaretCol, SelectionMode); } else if(bIsShiftPressed == false) { nNewTopIndex = (pData->nTopIndex > 0) ? (pData->nTopIndex - 1) : 0; MoveTopIndexAndCaretTo(pData, nNewTopIndex, pData->nCaretLine, pData->nCaretCol, SmContinueSelection); } break; case VK_LEFT: // Ctrl+Shift+ArrowLeft: Move one word left with selection // Ctrl+ArrowLeft: Move one word left // Shift+ArrowLeft: Moves the caret one line left with selection // ArrowLeft: Moves the caret one line left nNewCaretCol = bIsCtrlPressed ? FindPreviousWord(pData, pData->nCaretLine, pData->nCaretCol) : (pData->nCaretCol - 1); MoveCaret_ScrollView(pData, pData->nCaretLine, nNewCaretCol, SelectionMode); break; case VK_RIGHT: // Ctrl+Shift+ArrowRight: Move one word right with selection // Ctrl+ArrowRight: Move one word right // Shift+ArrowRight: Moves the caret one line right with selection // ArrowRight: Moves the caret one line right nNewCaretCol = bIsCtrlPressed ? FindNextWord(pData, pData->nCaretLine, pData->nCaretCol) : (pData->nCaretCol + 1); MoveCaret_ScrollView(pData, pData->nCaretLine, nNewCaretCol, SelectionMode); break; case VK_DOWN: // Ctrl+Shift+ArrowDown: Nothing // Ctrl+ArrowDown: Moves the view down by one line, does not change selection or cursor pos // Shift+ArrowDown: Moves the caret one line down with selection // ArrowDown: Moves the caret one line down if(bIsCtrlPressed == false) { MoveCaret_ScrollView(pData, pData->nCaretLine + 1, pData->nCaretCol, SelectionMode); } else if(bIsShiftPressed == false) { MoveTopIndexAndCaretTo(pData, pData->nTopIndex + 1, pData->nCaretLine, pData->nCaretCol, SmContinueSelection); } break; case VK_NEXT: // Ctrl+Shift+PageDown: Moves the caret to the bottom line of the page with selection // Ctrl+PageDown: Moves the caret to the bottom line of the page // Shift+PageDown: Moves the caret one page down with selection // PageDown: Moves the top index one page down, but caret remains nNewTopIndex = (bIsCtrlPressed == false) ? (pData->nTopIndex + pData->nFullVisibleLines) : pData->nTopIndex; nNewCaretLine = (bIsCtrlPressed == false) ? (pData->nCaretLine + pData->nFullVisibleLines) : (pData->nTopIndex + pData->nFullVisibleLines - 1); MoveTopIndexAndCaretTo(pData, nNewTopIndex, nNewCaretLine, pData->nCaretCol, SelectionMode); break; case VK_END: // Ctrl+Shift+End: Moves the caret to the last char of the document with selection // Ctrl+End: Moves the caret to the last char of the document // Shift+End: Moves the caret to the first char with selection // End: Moves the caret to the last char nNewTopIndex = bIsCtrlPressed ? (pData->nLines - pData->nFullVisibleLines) : pData->nTopIndex; nNewCaretLine = bIsCtrlPressed ? pData->nLines : pData->nCaretLine; MoveTopIndexAndCaretTo(pData, nNewTopIndex, nNewCaretLine, DATAEDIT_END_OF_LINE, SelectionMode); break; case VK_INSERT: if(bIsCtrlPressed) SendMessage(hWnd, WM_COPY, 0, 0); else if(bIsShiftPressed) SendMessage(hWnd, WM_PASTE, 0, 0); return TRUE; case VK_DELETE: if(bIsShiftPressed) { SendMessage(hWnd, WM_CUT, 0, 0); return TRUE; } break; case 'C': case 'c': if(bIsCtrlPressed) SendMessage(hWnd, WM_COPY, 0, 0); break; case 'X': case 'x': if(bIsCtrlPressed) { SendMessage(hWnd, WM_CUT, 0, 0); return TRUE; } break; case 'V': case 'v': if(bIsCtrlPressed) { SendMessage(hWnd, WM_PASTE, 0, 0); return TRUE; } break; } return TRUE; } static LRESULT OnChar(HWND hWnd, WPARAM wParam, LPARAM /* lParam */) { TEditorData * pData = GetEditorData(hWnd); BYTE ByteEntered; BYTE ByteAndMask; BYTE ByteOrMask; BYTE OneByte = (BYTE)wParam; size_t nLineOffset; size_t nNewCaretLine = pData->nCaretLine; int nNewCaretCol; int nByteIndex; int nRemainder; // Get the offset of the 0-th byte in the line nLineOffset = pData->nCaretLine * pData->cbBytesPerLine; // Is the cursor in the hexa part? if(pData->nBeginHexaValues <= pData->nCaretCol && pData->nCaretCol < pData->nEndHexaValues) { // Convert the key to a hexadecimal digit if('a' <= wParam && wParam <= 'z') wParam -= ('a' - 'A'); if('A' <= wParam && wParam <= 'Z') wParam -= ('A' - '9' - 1); if(0x30 <= wParam && wParam < 0x40) { // Get the byte index and remainder nByteIndex = (pData->nCaretCol - pData->nBeginHexaValues) / 3; nRemainder = (pData->nCaretCol - pData->nBeginHexaValues) % 3; // Are we within the range of the data? if((pData->pbEditorDataBegin + nLineOffset + nByteIndex) < pData->pbEditorDataEnd) { // Only if we are in the first upper or lower half of the BYTE if(nRemainder == 0 || nRemainder == 1) { // Read one byte from the mapped data OneByte = GuardedReadByte(pData, nLineOffset + nByteIndex); // Replace the given 4 bits ByteEntered = (BYTE)(wParam - '0'); ByteAndMask = nRemainder ? 0xF0 : 0x0F; ByteOrMask = nRemainder ? ByteEntered : (ByteEntered << 0x04); OneByte = (OneByte & ByteAndMask) | ByteOrMask; // Write the byte back GuardedWriteBuffer(pData, nLineOffset + nByteIndex, &OneByte, 1); } // Determine the next caret position nNewCaretCol = pData->nCaretCol + nRemainder + 1; if(nNewCaretCol >= pData->nEndHexaValues) { nNewCaretCol = pData->nBeginHexaValues; nNewCaretLine++; } // Move the caret MoveCaret_ScrollView(pData, nNewCaretLine, nNewCaretCol, SmStartSelection); } } return 0; } // Are we within the text data range? if(pData->nBeginTextValues <= pData->nCaretCol && pData->nCaretCol < pData->nEndTextValues) { // Get the byte index and remainder nByteIndex = (pData->nCaretCol - pData->nBeginTextValues); // Are we within the range of the data? if((pData->pbEditorDataBegin + nLineOffset + nByteIndex) < pData->pbEditorDataEnd) { // Write the entire byte GuardedWriteBuffer(pData, nLineOffset + nByteIndex, &OneByte, 1); // Determine the next caret position nNewCaretCol = pData->nCaretCol + 1; if(nNewCaretCol >= pData->nEndTextValues) { nNewCaretCol = pData->nBeginTextValues; nNewCaretLine++; } // Move the caret MoveCaret_ScrollView(pData, nNewCaretLine, nNewCaretCol, SmStartSelection); } } return TRUE; } static void OnPaste(HWND hWnd) { TEditorData * pData = GetEditorData(hWnd); size_t nLineOffset = pData->nCaretLine * pData->cbBytesPerLine; size_t cchPasteText = 0; size_t nByteIndex = (size_t)(-1); LPSTR szPasteText; // Is the cursor before the hexa part? if(pData->nCaretCol < pData->nBeginHexaValues) nByteIndex = 0; // Is the cursor within the hexa part? else if(pData->nCaretCol < pData->nEndHexaValues) nByteIndex = (pData->nCaretCol - pData->nBeginHexaValues) / 3; // Is the cursor between the hexa part and the text part? else if(pData->nCaretCol < pData->nBeginTextValues) nByteIndex = pData->cbBytesPerLine; // Is the cursor in the text part? else if(pData->nCaretCol <= pData->nEndTextValues) nByteIndex = (pData->nCaretCol - pData->nBeginTextValues); // Otherwise, put it at after end of the current line else nByteIndex = pData->cbBytesPerLine; // Retrieve the text from the clipboard szPasteText = QueryClipboardText(hWnd, &cchPasteText); if(szPasteText != NULL) { DTE_PASTE_DATA Data; // Notify the parent about the exception Data.hdr.hwndFrom = pData->hWnd; Data.hdr.idFrom = pData->dwId; Data.hdr.code = DEN_PASTE; Data.szPasteText = szPasteText; Data.PasteOffset = nLineOffset + nByteIndex; Data.PasteLength = cchPasteText; Data.bHandled = FALSE; SendMessage(pData->hWndParent, WM_NOTIFY, pData->dwId, (LPARAM)&Data); // If the parent hasn't handled the message, we copy the data to the internal data // Note that the internal paste routine will never extend the data, // and will cut any data that would go beyond the end of the buffer if(Data.bHandled == FALSE) { PasteTextInternal(pData, Data.szPasteText, Data.PasteOffset, Data.PasteLength); InvalidateRect(hWnd, NULL, TRUE); } // Free the text buffer HeapFree(GetProcessHeap(), 0, szPasteText); } } static void OnLButtonDown(HWND hWnd, int nMouseX, int nMouseY) { TEditorData * pData = GetEditorData(hWnd); size_t nCaretLine; int nCaretCol; if(pData->bHasCapture == false) { // Get focus, if we don't have it yet if(GetFocus() != hWnd) SetFocus(hWnd); // Start capture SetCapture(hWnd); pData->bHasCapture = true; // Recalculate the mouse position to character position and save it MousePosToCaretPos(pData, nMouseX, nMouseY, &nCaretLine, &nCaretCol); // Clear the current selection MoveCaret_ScrollView(pData, nCaretLine, nCaretCol, SmStartSelection); } } static void OnMouseMove(HWND hWnd, int nMouseX, int nMouseY) { TEditorData * pData = GetEditorData(hWnd); size_t nCaretLine; int nCaretCol; if(pData->bHasCapture) { // Normalize mouse X position if(nMouseX < 0) nMouseX = 0; // Recalculate the mouse position to character position and save it MousePosToCaretPos(pData, nMouseX, nMouseY, &nCaretLine, &nCaretCol); // Set the caret position MoveCaret_ScrollView(pData, nCaretLine, nCaretCol, SmContinueSelection); } } static void OnMouseWheel(HWND hWnd, UINT uDistance) { TEditorData * pData = GetEditorData(hWnd); size_t nNewTopIndex; // Do nothing if there is mouse capture or if there is more lines than the visible ones if(pData->bHasCapture || pData->nLines == 0) return; // Negative value means that the wheel has been moved down // Positive value means that the wheel has been moved up if((uDistance & 0x8000) == 0) nNewTopIndex = (pData->nTopIndex > 3) ? pData->nTopIndex - 3 : 0; else nNewTopIndex = pData->nTopIndex + 3; MoveTopIndexAndCaretTo(pData, nNewTopIndex, pData->nCaretLine, pData->nCaretCol, SmContinueSelection); } static void OnLButtonUp(HWND hWnd) { TEditorData * pData = GetEditorData(hWnd); if(pData->bHasCapture) { ReleaseCapture(); pData->bHasCapture = false; } } static void OnVertScroll(HWND hWnd, WPARAM wParam) { TEditorData * pData = GetEditorData(hWnd); SCROLLINFO si; switch(LOWORD(wParam)) { case SB_PAGEUP: MoveTopIndexTo(pData, pData->nTopIndex - (pData->nFullVisibleLines - 1)); break; case SB_LINEUP: MoveTopIndexTo(pData, pData->nTopIndex - 1); break; case SB_THUMBTRACK: si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_ALL; GetScrollInfo(hWnd, SB_VERT, &si); MoveTopIndexTo(pData, si.nTrackPos); break; case SB_LINEDOWN: MoveTopIndexTo(pData, pData->nTopIndex + 1); break; case SB_PAGEDOWN: MoveTopIndexTo(pData, pData->nTopIndex + (pData->nFullVisibleLines - 1)); break; } } static void OnPaint(HWND hWnd) { TEditorData * pData = GetEditorData(hWnd); PAINTSTRUCT ps; HGDIOBJ hOldFont; RECT LineRect; RECT TempRect; UINT uOldAlign; HDC hPaintDC; size_t nStartSelLine = pData->nStartSelLine; size_t nEndSelLine = pData->nEndSelLine; size_t nLineIndex = nLineIndex = pData->nTopIndex; int nStartSelCol = pData->nStartSelCol; int nEndSelCol = pData->nEndSelCol; // Get start and end of selection in the proper order if(IsReversedSelection(pData)) { nStartSelLine = pData->nEndSelLine; nStartSelCol = pData->nEndSelCol; nEndSelLine = pData->nStartSelLine; nEndSelCol = pData->nStartSelCol; } // Prepare rectagle for top line LineRect.left = 0; LineRect.top = 0; LineRect.right = pData->nClientCX; LineRect.bottom = pData->nCharHeight; // Start painting BeginPaint(hWnd, &ps); hPaintDC = ps.hdc; hOldFont = SelectObject(hPaintDC, pData->hFont); uOldAlign = SetTextAlign(hPaintDC, TA_LEFT | TA_TOP); // Draw all lines while(LineRect.top < pData->nClientCY) { LPTSTR szLineText; LONG TextSize = 0; int nStartSelection = 0; int nEndSelection = 0; int nLengthToDraw; int nLineLength; int nStartDraw; int x, y; // Reset left position to left edge of the view LineRect.left = 0; // Is this line in the invalidated area? if(IntersectRect(&TempRect, &LineRect, &ps.rcPaint)) { // If the line is within range if(nLineIndex < pData->nLines) { nLineLength = FormatOneLine(pData, nLineIndex); szLineText = pData->szLineBuffer; nStartDraw = 0; // Determine selection if(nLineIndex == nStartSelLine) nStartSelection = nStartSelCol; if(nLineIndex == nEndSelLine) nEndSelection = nEndSelCol; if(nStartSelLine <= nLineIndex && nLineIndex < nEndSelLine) nEndSelection = nLineLength; // Fill the mini-rect between left edge of the view and left edge of the text if(pData->nLeftOrg < DATAEDIT_TEXT_INDENT) { LineRect.right = DATAEDIT_TEXT_INDENT; SetBkColor(hPaintDC, DATAEDIT_COLOR_NORMAL_BG); ExtTextOut(hPaintDC, 0, 0, ETO_OPAQUE, &LineRect, NULL, 0, NULL); LineRect.right = pData->nClientCX; } // Calculate the text position x = DATAEDIT_TEXT_INDENT - pData->nLeftOrg; y = LineRect.top; // Draw the plain text before the selection if(nStartSelection > nStartDraw) { SetTextColor(hPaintDC, DATAEDIT_COLOR_NORMAL_FG); SetBkColor(hPaintDC, DATAEDIT_COLOR_NORMAL_BG); nLengthToDraw = (nStartSelection - nStartDraw); TextSize = TabbedTextOut(hPaintDC, x, y, szLineText, nLengthToDraw, 1, &pData->nTabSize, 0); nStartDraw += nLengthToDraw; szLineText += nLengthToDraw; x += LOWORD(TextSize); } // Draw the selected text if(nEndSelection > nStartDraw) { SetTextColor(hPaintDC, DATAEDIT_COLOR_SELECTION_FG); SetBkColor(hPaintDC, DATAEDIT_COLOR_SELECTION_BG); nLengthToDraw = (nEndSelection - nStartDraw); TextSize = TabbedTextOut(hPaintDC, x, y, szLineText, nLengthToDraw, 1, &pData->nTabSize, 0); nStartDraw += nLengthToDraw; szLineText += nLengthToDraw; x += LOWORD(TextSize); } // Draw the end if(nLineLength > nStartDraw) { SetTextColor(hPaintDC, DATAEDIT_COLOR_NORMAL_FG); SetBkColor(hPaintDC, DATAEDIT_COLOR_NORMAL_BG); nLengthToDraw = (int)(nLineLength - nStartDraw); TextSize = TabbedTextOut(hPaintDC, x, y, szLineText, nLengthToDraw, 1, &pData->nTabSize, 0); x += LOWORD(TextSize); } // Finish the line by filling extra space behind the text if(LineRect.left < LineRect.right) { // Get the current pen position LineRect.left = x; LineRect.right = pData->nClientCX; SetBkColor(hPaintDC, DATAEDIT_COLOR_NORMAL_BG); ExtTextOut(hPaintDC, 0, 0, ETO_OPAQUE, &LineRect, NULL, 0, NULL); } } else { // Paint the entire line as empty filled rectangle SetBkColor(hPaintDC, DATAEDIT_COLOR_NORMAL_BG); ExtTextOut(hPaintDC, 0, 0, ETO_OPAQUE, &LineRect, NULL, 0, NULL); } } // Move the RECT to one line down LineRect.top = LineRect.bottom; LineRect.bottom = LineRect.top + pData->nCharHeight; nLineIndex++; } // Finish painting SetTextAlign(hPaintDC, uOldAlign); SelectObject(hPaintDC, hOldFont); EndPaint(hWnd, &ps); } static void OnDestroy(HWND hWnd) { TEditorData * pData = GetEditorData(hWnd); if(pData != NULL) { // Delete the line buffer if(pData->szLineBuffer != NULL) HeapFree(GetProcessHeap(), 0, pData->szLineBuffer); pData->szLineBuffer = NULL; // Delete the data itself delete pData; } } //----------------------------------------------------------------------------- // Window procedure static LRESULT WINAPI WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) { case WM_CREATE: OnCreate(hWnd, (LPCREATESTRUCT)lParam); break; case WM_SETFONT: return 0; case WM_SHOWWINDOW: OnShowWindow(hWnd, wParam); break; case WM_SIZE: OnSize(hWnd, lParam); break; case WM_SETTEXT: return TRUE; case WM_SETFOCUS: OnSetFocus(hWnd); break; case WM_KILLFOCUS: OnKillFocus(hWnd); break; case WM_GETTEXTLENGTH: return 0; case WM_GETTEXT: return 0; case WM_KEYDOWN: if(OnKeyDown(hWnd, wParam, lParam)) return 0; break; case WM_CHAR: OnChar(hWnd, wParam, lParam); return 1; case WM_PASTE: OnPaste(hWnd); return 1; case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: OnLButtonDown(hWnd, (signed short)LOWORD(lParam), (signed short)HIWORD(lParam)); break; case WM_MOUSEMOVE: OnMouseMove(hWnd, (signed short)LOWORD(lParam), (signed short)HIWORD(lParam)); break; case WM_MOUSEWHEEL: OnMouseWheel(hWnd, HIWORD(wParam)); return 0; case WM_LBUTTONUP: case WM_RBUTTONUP: OnLButtonUp(hWnd); break; case WM_VSCROLL: OnVertScroll(hWnd, wParam); return 0; case WM_PAINT: OnPaint(hWnd); break; case WM_ERASEBKGND: return 1; // All drawing is done on the OnPaint case WM_GETDLGCODE: return (DLGC_WANTARROWS | DLGC_WANTCHARS); case WM_DESTROY: OnDestroy(hWnd); break; } return DefWindowProc(hWnd, uMsg, wParam, lParam); } //----------------------------------------------------------------------------- // Public functions int RegisterDataEditor(HINSTANCE hInst) { WNDCLASSEX wc; DWORD dwErrCode = ERROR_SUCCESS; // Register the class wc.cbSize = sizeof(WNDCLASSEX); if(!GetClassInfoEx(hInst, szDataEditClassName, &wc)) { // Register the class ZeroMemory(&wc, sizeof(WNDCLASSEX)); wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_DBLCLKS; wc.lpfnWndProc = WindowProc; wc.hInstance = hInst; wc.hCursor = LoadCursor(NULL, IDC_IBEAM); wc.hIcon = NULL; wc.hbrBackground = NULL; wc.lpszClassName = szDataEditClassName; if(!RegisterClassEx(&wc)) dwErrCode = GetLastError(); } return dwErrCode; } // Applies the new data int DataEditor_SetData(HWND hWndDataEdit, ULONGLONG BaseAddress, LPVOID pvData, SIZE_T cbData) { TEditorData * pData = GetEditorData(hWndDataEdit); // Verify the validity of the window and data if(pData == NULL) return ERROR_INVALID_PARAMETER; // Apply the new data region pData->BaseAddress = BaseAddress; pData->pbEditorDataBegin = (LPBYTE)pvData; pData->pbEditorDataEnd = (LPBYTE)pvData + cbData; // Move the caret to the position 0, 0 pData->nStartSelLine = pData->nEndSelLine = 0; pData->nStartSelCol = pData->nEndSelCol = 0; // Clear information about an exception pData->bWriteException = false; pData->bReadException = false; // Recalculate the view RecalculateView(pData); InvalidateRect(hWndDataEdit, NULL, TRUE); return ERROR_SUCCESS; } int DataEditor_SetDataFormat(HWND hWndDataEdit, TPointerFormat PointerFormat, SIZE_T cbBytesPerLine) { TEditorData * pData = GetEditorData(hWndDataEdit); // Verify the validity of the window and data if(pData == NULL) return ERROR_INVALID_PARAMETER; // Set the pointer format pData->PointerFormat = PointerFormat; // Apply the byte count SetBytesPerLine(pData, cbBytesPerLine); return ERROR_SUCCESS; }