wine/programs/regedit/hexedit.c
2007-03-12 12:25:31 +01:00

696 lines
19 KiB
C

/*
* Hex Edit control
*
* Copyright 2005 Robert Shearman
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*
* TODO:
* - Selection support
* - Cut, copy and paste
* - Mouse support
*/
#include <stdarg.h>
#include <string.h>
#include <assert.h>
#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winuser.h"
#include "winnls.h"
#include "commctrl.h"
#include "main.h"
/* spaces dividing hex and ASCII */
#define DIV_SPACES 4
typedef struct tagHEXEDIT_INFO
{
HWND hwndSelf;
HFONT hFont;
BOOL bFocus : 1;
BOOL bFocusHex : 1; /* TRUE if focus is on hex, FALSE if focus on ASCII */
BOOL bInsert : 1; /* insert mode if TRUE, overwrite mode if FALSE */
INT nHeight; /* height of text */
INT nCaretPos; /* caret pos in nibbles */
BYTE *pData;
INT cbData;
INT nBytesPerLine; /* bytes of hex to display per line of the control */
INT nScrollPos; /* first visible line */
} HEXEDIT_INFO;
static inline LRESULT HexEdit_SetFont (HEXEDIT_INFO *infoPtr, HFONT hFont, BOOL redraw);
static inline BYTE hexchar_to_byte(TCHAR ch)
{
if (ch >= '0' && ch <= '9')
return ch - '0';
else if (ch >= 'a' && ch <= 'f')
return ch - 'a' + 10;
else if (ch >= 'A' && ch <= 'F')
return ch - 'A' + 10;
else
return -1;
}
static LPTSTR HexEdit_GetLineText(BYTE *pData, LONG cbData, LONG pad)
{
LPTSTR lpszLine = HeapAlloc(GetProcessHeap(), 0,
(cbData * 3 + pad * 3 + DIV_SPACES + cbData + 1) * sizeof(TCHAR));
LONG i;
if (!lpszLine)
return NULL;
for (i = 0; i < cbData; i++)
wsprintf(lpszLine + i*3, TEXT("%02X "), pData[i]);
for (i = 0; i < pad * 3; i++)
lpszLine[cbData * 3 + i] = ' ';
for (i = 0; i < DIV_SPACES; i++)
lpszLine[cbData * 3 + pad * 3 + i] = ' ';
/* attempt an ASCII representation if the characters are printable,
* otherwise display a '.' */
for (i = 0; i < cbData; i++)
{
/* (C1_ALPHA|C1_BLANK|C1_PUNCT|C1_DIGIT|C1_LOWER|C1_UPPER) */
if (isprint(pData[i]))
lpszLine[cbData * 3 + pad * 3 + DIV_SPACES + i] = pData[i];
else
lpszLine[cbData * 3 + pad * 3 + DIV_SPACES + i] = '.';
}
lpszLine[cbData * 3 + pad * 3 + DIV_SPACES + cbData] = 0;
return lpszLine;
}
static void
HexEdit_Paint(HEXEDIT_INFO *infoPtr)
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(infoPtr->hwndSelf, &ps);
INT nXStart, nYStart;
COLORREF clrOldText;
HFONT hOldFont;
BYTE *pData;
INT iMode;
LONG lByteOffset = infoPtr->nScrollPos * infoPtr->nBytesPerLine;
/* Make a gap from the frame */
nXStart = GetSystemMetrics(SM_CXBORDER);
nYStart = GetSystemMetrics(SM_CYBORDER);
if (GetWindowLong(infoPtr->hwndSelf, GWL_STYLE) & WS_DISABLED)
clrOldText = SetTextColor(hdc, GetSysColor(COLOR_GRAYTEXT));
else
clrOldText = SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
iMode = SetBkMode(hdc, TRANSPARENT);
hOldFont = SelectObject(hdc, infoPtr->hFont);
for (pData = infoPtr->pData + lByteOffset; pData < infoPtr->pData + infoPtr->cbData; pData += infoPtr->nBytesPerLine)
{
LPTSTR lpszLine;
LONG nLineLen = min((LONG)((infoPtr->pData + infoPtr->cbData) - pData),
infoPtr->nBytesPerLine);
lpszLine = HexEdit_GetLineText(pData, nLineLen, infoPtr->nBytesPerLine - nLineLen);
/* FIXME: draw hex <-> ASCII mapping highlighted? */
TextOut(hdc, nXStart, nYStart, lpszLine, infoPtr->nBytesPerLine * 3 + DIV_SPACES + nLineLen);
nYStart += infoPtr->nHeight;
HeapFree(GetProcessHeap(), 0, lpszLine);
}
SelectObject(hdc, hOldFont);
SetBkMode(hdc, iMode);
SetTextColor(hdc, clrOldText);
EndPaint(infoPtr->hwndSelf, &ps);
}
static void
HexEdit_UpdateCaret(HEXEDIT_INFO *infoPtr)
{
HDC hdc;
HFONT hOldFont;
SIZE size;
INT nCaretBytePos = infoPtr->nCaretPos/2;
INT nByteLinePos = nCaretBytePos % infoPtr->nBytesPerLine;
INT nLine = nCaretBytePos / infoPtr->nBytesPerLine;
LONG nLineLen = min(infoPtr->cbData - nLine * infoPtr->nBytesPerLine, infoPtr->nBytesPerLine);
LPTSTR lpszLine = HexEdit_GetLineText(infoPtr->pData + nLine * infoPtr->nBytesPerLine, nLineLen, infoPtr->nBytesPerLine - nLineLen);
INT nCharOffset;
/* calculate offset of character caret is on in the line */
if (infoPtr->bFocusHex)
nCharOffset = nByteLinePos*3 + infoPtr->nCaretPos % 2;
else
nCharOffset = infoPtr->nBytesPerLine*3 + DIV_SPACES + nByteLinePos;
hdc = GetDC(infoPtr->hwndSelf);
hOldFont = SelectObject(hdc, infoPtr->hFont);
GetTextExtentPoint32(hdc, lpszLine, nCharOffset, &size);
SelectObject(hdc, hOldFont);
ReleaseDC(infoPtr->hwndSelf, hdc);
if (!nLineLen) size.cx = 0;
HeapFree(GetProcessHeap(), 0, lpszLine);
SetCaretPos(
GetSystemMetrics(SM_CXBORDER) + size.cx,
GetSystemMetrics(SM_CYBORDER) + (nLine - infoPtr->nScrollPos) * infoPtr->nHeight);
}
static void
HexEdit_UpdateScrollbars(HEXEDIT_INFO *infoPtr)
{
RECT rcClient;
INT nLines = infoPtr->cbData / infoPtr->nBytesPerLine;
INT nVisibleLines;
SCROLLINFO si;
GetClientRect(infoPtr->hwndSelf, &rcClient);
InflateRect(&rcClient, -GetSystemMetrics(SM_CXBORDER), -GetSystemMetrics(SM_CYBORDER));
nVisibleLines = (rcClient.bottom - rcClient.top) / infoPtr->nHeight;
si.cbSize = sizeof(si);
si.fMask = SIF_RANGE | SIF_PAGE;
si.nMin = 0;
si.nMax = max(nLines - nVisibleLines, nLines);
si.nPage = nVisibleLines;
SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &si, TRUE);
}
static void
HexEdit_EnsureVisible(HEXEDIT_INFO *infoPtr, INT nCaretPos)
{
INT nLine = nCaretPos / (2 * infoPtr->nBytesPerLine);
SCROLLINFO si;
si.cbSize = sizeof(si);
si.fMask = SIF_POS | SIF_PAGE;
GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &si);
if (nLine < si.nPos)
si.nPos = nLine;
else if (nLine >= si.nPos + si.nPage)
si.nPos = nLine - si.nPage + 1;
else
return;
si.fMask = SIF_POS;
SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &si, FALSE);
SendMessage(infoPtr->hwndSelf, WM_VSCROLL, MAKELONG(SB_THUMBPOSITION, 0), 0);
}
static LRESULT
HexEdit_SetData(HEXEDIT_INFO *infoPtr, INT cbData, const BYTE *pData)
{
HeapFree(GetProcessHeap(), 0, infoPtr->pData);
infoPtr->cbData = 0;
infoPtr->pData = HeapAlloc(GetProcessHeap(), 0, cbData);
if (infoPtr->pData)
{
memcpy(infoPtr->pData, pData, cbData);
infoPtr->cbData = cbData;
infoPtr->nCaretPos = 0;
HexEdit_UpdateScrollbars(infoPtr);
HexEdit_UpdateCaret(infoPtr);
InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
return TRUE;
}
return FALSE;
}
static LRESULT
HexEdit_GetData(HEXEDIT_INFO *infoPtr, INT cbData, BYTE *pData)
{
if (pData)
memcpy(pData, infoPtr->pData, min(cbData, infoPtr->cbData));
return infoPtr->cbData;
}
static inline LRESULT
HexEdit_Char (HEXEDIT_INFO *infoPtr, TCHAR ch)
{
INT nCaretBytePos = infoPtr->nCaretPos/2;
assert(nCaretBytePos >= 0);
/* backspace is special */
if (ch == '\b')
{
if (infoPtr->nCaretPos == 0)
return 0;
/* if at end of byte then delete the whole byte */
if (infoPtr->bFocusHex && (infoPtr->nCaretPos % 2 == 0))
{
memmove(infoPtr->pData + nCaretBytePos - 1,
infoPtr->pData + nCaretBytePos,
infoPtr->cbData - nCaretBytePos);
infoPtr->cbData--;
infoPtr->nCaretPos -= 2; /* backtrack two nibble */
}
else /* blank upper nibble */
{
infoPtr->pData[nCaretBytePos] &= 0x0f;
infoPtr->nCaretPos--; /* backtrack one nibble */
}
}
else
{
if (infoPtr->bFocusHex && hexchar_to_byte(ch) == (BYTE)-1)
{
MessageBeep(MB_ICONWARNING);
return 0;
}
if ((infoPtr->bInsert && (infoPtr->nCaretPos % 2 == 0)) || (nCaretBytePos >= infoPtr->cbData))
{
/* make room for another byte */
infoPtr->cbData++;
infoPtr->pData = HeapReAlloc(GetProcessHeap(), 0, infoPtr->pData, infoPtr->cbData + 1);
if (!infoPtr->pData) return 0;
/* move everything after caret up one byte */
memmove(infoPtr->pData + nCaretBytePos + 1,
infoPtr->pData + nCaretBytePos,
infoPtr->cbData - nCaretBytePos);
/* zero new byte */
infoPtr->pData[nCaretBytePos] = 0x0;
}
/* overwrite a byte */
assert(nCaretBytePos < infoPtr->cbData);
if (infoPtr->bFocusHex)
{
BYTE orig_byte = infoPtr->pData[nCaretBytePos];
BYTE digit = hexchar_to_byte(ch);
if (infoPtr->nCaretPos % 2) /* set low nibble */
infoPtr->pData[nCaretBytePos] = (orig_byte & 0xf0) | digit;
else /* set high nibble */
infoPtr->pData[nCaretBytePos] = (orig_byte & 0x0f) | digit << 4;
infoPtr->nCaretPos++; /* advance one nibble */
}
else
{
infoPtr->pData[nCaretBytePos] = (BYTE)ch;
infoPtr->nCaretPos += 2; /* advance two nibbles */
}
}
HexEdit_UpdateScrollbars(infoPtr);
InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
HexEdit_UpdateCaret(infoPtr);
HexEdit_EnsureVisible(infoPtr, infoPtr->nCaretPos);
return 0;
}
static inline LRESULT
HexEdit_Create (HEXEDIT_INFO *infoPtr, LPCREATESTRUCT lpcs)
{
HexEdit_SetFont(infoPtr, GetStockObject(SYSTEM_FONT), FALSE);
HexEdit_UpdateScrollbars(infoPtr);
return 0;
}
static inline LRESULT
HexEdit_Destroy (HEXEDIT_INFO *infoPtr)
{
HWND hwnd = infoPtr->hwndSelf;
HeapFree(GetProcessHeap(), 0, infoPtr->pData);
/* free info data */
HeapFree(GetProcessHeap(), 0, infoPtr);
SetWindowLongPtr(hwnd, 0, 0);
return 0;
}
static inline LRESULT
HexEdit_EraseBackground (HEXEDIT_INFO *infoPtr, HDC hdc)
{
HBRUSH hBrush, hSolidBrush = NULL;
RECT rc;
if (GetWindowLong(infoPtr->hwndSelf, GWL_STYLE) & WS_DISABLED)
hBrush = hSolidBrush = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
else
{
hBrush = (HBRUSH)SendMessage(GetParent(infoPtr->hwndSelf), WM_CTLCOLOREDIT,
(WPARAM)hdc, (LPARAM)infoPtr->hwndSelf);
if (!hBrush)
hBrush = hSolidBrush = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
}
GetClientRect (infoPtr->hwndSelf, &rc);
FillRect (hdc, &rc, hBrush);
if (hSolidBrush)
DeleteObject(hSolidBrush);
return -1;
}
static inline LRESULT
HexEdit_GetFont (HEXEDIT_INFO *infoPtr)
{
return (LRESULT)infoPtr->hFont;
}
static inline LRESULT
HexEdit_KeyDown (HEXEDIT_INFO *infoPtr, DWORD key, DWORD flags)
{
INT nInc = (infoPtr->bFocusHex) ? 1 : 2;
SCROLLINFO si;
switch (key)
{
case VK_LEFT:
infoPtr->nCaretPos -= nInc;
if (infoPtr->nCaretPos < 0)
infoPtr->nCaretPos = 0;
break;
case VK_RIGHT:
infoPtr->nCaretPos += nInc;
if (infoPtr->nCaretPos > infoPtr->cbData*2)
infoPtr->nCaretPos = infoPtr->cbData*2;
break;
case VK_UP:
if ((infoPtr->nCaretPos - infoPtr->nBytesPerLine*2) >= 0)
infoPtr->nCaretPos -= infoPtr->nBytesPerLine*2;
break;
case VK_DOWN:
if ((infoPtr->nCaretPos + infoPtr->nBytesPerLine*2) <= infoPtr->cbData*2)
infoPtr->nCaretPos += infoPtr->nBytesPerLine*2;
break;
case VK_HOME:
infoPtr->nCaretPos = 0;
break;
case VK_END:
infoPtr->nCaretPos = infoPtr->cbData*2;
break;
case VK_PRIOR: /* page up */
si.cbSize = sizeof(si);
si.fMask = SIF_PAGE;
GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &si);
if ((infoPtr->nCaretPos - (INT)si.nPage*infoPtr->nBytesPerLine*2) >= 0)
infoPtr->nCaretPos -= si.nPage*infoPtr->nBytesPerLine*2;
else
infoPtr->nCaretPos = 0;
break;
case VK_NEXT: /* page down */
si.cbSize = sizeof(si);
si.fMask = SIF_PAGE;
GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &si);
if ((infoPtr->nCaretPos + (INT)si.nPage*infoPtr->nBytesPerLine*2) <= infoPtr->cbData*2)
infoPtr->nCaretPos += si.nPage*infoPtr->nBytesPerLine*2;
else
infoPtr->nCaretPos = infoPtr->cbData*2;
break;
default:
return 0;
}
HexEdit_UpdateCaret(infoPtr);
HexEdit_EnsureVisible(infoPtr, infoPtr->nCaretPos);
return 0;
}
static inline LRESULT
HexEdit_KillFocus (HEXEDIT_INFO *infoPtr, HWND receiveFocus)
{
infoPtr->bFocus = FALSE;
DestroyCaret();
return 0;
}
static inline LRESULT
HexEdit_LButtonDown (HEXEDIT_INFO *infoPtr)
{
SetFocus(infoPtr->hwndSelf);
/* FIXME: hittest and set caret */
return 0;
}
static inline LRESULT HexEdit_NCCreate (HWND hwnd, LPCREATESTRUCT lpcs)
{
HEXEDIT_INFO *infoPtr;
SetWindowLong(hwnd, GWL_EXSTYLE,
lpcs->dwExStyle | WS_EX_CLIENTEDGE);
/* allocate memory for info structure */
infoPtr = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(HEXEDIT_INFO));
SetWindowLongPtr(hwnd, 0, (DWORD_PTR)infoPtr);
/* initialize info structure */
infoPtr->nCaretPos = 0;
infoPtr->hwndSelf = hwnd;
infoPtr->nBytesPerLine = 2;
infoPtr->bFocusHex = TRUE;
infoPtr->bInsert = TRUE;
return DefWindowProc(infoPtr->hwndSelf, WM_NCCREATE, 0, (LPARAM)lpcs);
}
static inline LRESULT
HexEdit_SetFocus (HEXEDIT_INFO *infoPtr, HWND lostFocus)
{
infoPtr->bFocus = TRUE;
CreateCaret(infoPtr->hwndSelf, NULL, 1, infoPtr->nHeight);
HexEdit_UpdateCaret(infoPtr);
ShowCaret(infoPtr->hwndSelf);
return 0;
}
static inline LRESULT
HexEdit_SetFont (HEXEDIT_INFO *infoPtr, HFONT hFont, BOOL redraw)
{
TEXTMETRIC tm;
HDC hdc;
HFONT hOldFont = NULL;
LONG i;
RECT rcClient;
infoPtr->hFont = hFont;
hdc = GetDC(infoPtr->hwndSelf);
if (infoPtr->hFont)
hOldFont = SelectObject(hdc, infoPtr->hFont);
GetTextMetrics(hdc, &tm);
infoPtr->nHeight = tm.tmHeight + tm.tmExternalLeading;
GetClientRect(infoPtr->hwndSelf, &rcClient);
for (i = 0; ; i++)
{
BYTE *pData = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, i);
LPTSTR lpszLine = HexEdit_GetLineText(pData, i, 0);
SIZE size;
GetTextExtentPoint32(hdc, lpszLine, lstrlen(lpszLine), &size);
HeapFree(GetProcessHeap(), 0, lpszLine);
HeapFree(GetProcessHeap(), 0, pData);
if (size.cx > (rcClient.right - rcClient.left))
{
infoPtr->nBytesPerLine = i - 1;
break;
}
}
if (infoPtr->hFont)
SelectObject(hdc, hOldFont);
ReleaseDC (infoPtr->hwndSelf, hdc);
if (redraw)
InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
return 0;
}
static inline LRESULT
HexEdit_VScroll (HEXEDIT_INFO *infoPtr, INT action)
{
SCROLLINFO si;
/* get all scroll bar info */
si.cbSize = sizeof(si);
si.fMask = SIF_ALL;
GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &si);
switch (LOWORD(action))
{
case SB_TOP: /* user pressed the home key */
si.nPos = si.nMin;
break;
case SB_BOTTOM: /* user pressed the end key */
si.nPos = si.nMax;
break;
case SB_LINEUP: /* user clicked the up arrow */
si.nPos -= 1;
break;
case SB_LINEDOWN: /* user clicked the down arrow */
si.nPos += 1;
break;
case SB_PAGEUP: /* user clicked the scroll bar above the scroll thumb */
si.nPos -= si.nPage;
break;
case SB_PAGEDOWN: /* user clicked the scroll bar below the scroll thumb */
si.nPos += si.nPage;
break;
case SB_THUMBTRACK: /* user dragged the scroll thumb */
si.nPos = si.nTrackPos;
break;
default:
break;
}
/* set the position and then retrieve it to let the system handle the
* cases where the position is out of range */
si.fMask = SIF_POS;
SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &si, TRUE);
GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &si);
if (si.nPos != infoPtr->nScrollPos)
{
ScrollWindow(infoPtr->hwndSelf, 0, infoPtr->nHeight * (infoPtr->nScrollPos - si.nPos), NULL, NULL);
infoPtr->nScrollPos = si.nPos;
UpdateWindow(infoPtr->hwndSelf);
/* need to update caret position since it depends on the scroll position */
HexEdit_UpdateCaret(infoPtr);
}
return 0;
}
static LRESULT WINAPI
HexEdit_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
HEXEDIT_INFO *infoPtr = (HEXEDIT_INFO *)GetWindowLongPtr (hwnd, 0);
if (!infoPtr && (uMsg != WM_NCCREATE))
return DefWindowProc(hwnd, uMsg, wParam, lParam);
switch (uMsg)
{
case HEM_SETDATA:
return HexEdit_SetData (infoPtr, (INT)wParam, (const BYTE *)lParam);
case HEM_GETDATA:
return HexEdit_GetData (infoPtr, (INT)wParam, (BYTE *)lParam);
case WM_CHAR:
return HexEdit_Char (infoPtr, (TCHAR)wParam);
case WM_CREATE:
return HexEdit_Create (infoPtr, (LPCREATESTRUCT)lParam);
case WM_DESTROY:
return HexEdit_Destroy (infoPtr);
case WM_ERASEBKGND:
return HexEdit_EraseBackground (infoPtr, (HDC)wParam);
case WM_GETDLGCODE:
return DLGC_WANTCHARS | DLGC_WANTARROWS;
case WM_GETFONT:
return HexEdit_GetFont (infoPtr);
case WM_KEYDOWN:
return HexEdit_KeyDown (infoPtr, wParam, lParam);
case WM_KILLFOCUS:
return HexEdit_KillFocus (infoPtr, (HWND)wParam);
case WM_LBUTTONDOWN:
return HexEdit_LButtonDown (infoPtr);
case WM_NCCREATE:
return HexEdit_NCCreate (hwnd, (LPCREATESTRUCT)lParam);
case WM_PAINT:
HexEdit_Paint(infoPtr);
return 0;
case WM_SETFOCUS:
return HexEdit_SetFocus (infoPtr, (HWND)wParam);
case WM_SETFONT:
return HexEdit_SetFont (infoPtr, (HFONT)wParam, LOWORD(lParam));
case WM_VSCROLL:
return HexEdit_VScroll (infoPtr, (INT)wParam);
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
void HexEdit_Register(void)
{
WNDCLASS wndClass;
ZeroMemory(&wndClass, sizeof(WNDCLASS));
wndClass.style = 0;
wndClass.lpfnWndProc = HexEdit_WindowProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = sizeof(HEXEDIT_INFO *);
wndClass.hCursor = NULL;
wndClass.hbrBackground = NULL;
wndClass.lpszClassName = HEXEDIT_CLASS;
RegisterClass(&wndClass);
}
void HexEdit_Unregister(void)
{
UnregisterClass(HEXEDIT_CLASS, NULL);
}