Files
ladislav-zezula-FileTest/TDataEditor.cpp
2024-12-29 19:01:43 +01:00

1708 lines
60 KiB
C++

/*****************************************************************************/
/* 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;
}