regedit: Use a separate dialog proc function for string data types.
[wine.git] / programs / regedit / hexedit.c
blobd29c916918ec3cab47dc17926f949c93ad822adf
1 /*
2 * Hex Edit control
4 * Copyright 2005 Robert Shearman
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * TODO:
21 * - Selection support
22 * - Cut, copy and paste
23 * - Mouse support
26 #include <stdarg.h>
27 #include <string.h>
28 #include <assert.h>
30 #include "windef.h"
31 #include "winbase.h"
32 #include "wingdi.h"
33 #include "winuser.h"
34 #include "winnls.h"
35 #include "commctrl.h"
37 #include "main.h"
39 /* spaces dividing hex and ASCII */
40 #define DIV_SPACES 4
42 typedef struct tagHEXEDIT_INFO
44 HWND hwndSelf;
45 HFONT hFont;
46 BOOL bFocus : 1;
47 BOOL bFocusHex : 1; /* TRUE if focus is on hex, FALSE if focus on ASCII */
48 BOOL bInsert : 1; /* insert mode if TRUE, overwrite mode if FALSE */
49 INT nHeight; /* height of text */
50 INT nCaretPos; /* caret pos in nibbles */
51 BYTE *pData;
52 INT cbData;
53 INT nBytesPerLine; /* bytes of hex to display per line of the control */
54 INT nScrollPos; /* first visible line */
55 } HEXEDIT_INFO;
57 static inline LRESULT HexEdit_SetFont (HEXEDIT_INFO *infoPtr, HFONT hFont, BOOL redraw);
59 static inline BYTE hexchar_to_byte(WCHAR ch)
61 if (ch >= '0' && ch <= '9')
62 return ch - '0';
63 else if (ch >= 'a' && ch <= 'f')
64 return ch - 'a' + 10;
65 else if (ch >= 'A' && ch <= 'F')
66 return ch - 'A' + 10;
67 else
68 return -1;
71 static LPWSTR HexEdit_GetLineText(int offset, BYTE *pData, LONG cbData, LONG pad)
73 WCHAR *lpszLine = malloc((6 + cbData * 3 + pad * 3 + DIV_SPACES + cbData + 1) * sizeof(WCHAR));
74 LONG i;
76 wsprintfW(lpszLine, L"%04X ", offset);
78 for (i = 0; i < cbData; i++)
79 wsprintfW(lpszLine + 6 + i*3, L"%02X ", pData[offset + i]);
80 for (i = 0; i < pad * 3; i++)
81 lpszLine[6 + cbData * 3 + i] = ' ';
83 for (i = 0; i < DIV_SPACES; i++)
84 lpszLine[6 + cbData * 3 + pad * 3 + i] = ' ';
86 /* attempt an ASCII representation if the characters are printable,
87 * otherwise display a '.' */
88 for (i = 0; i < cbData; i++)
90 /* (C1_ALPHA|C1_BLANK|C1_PUNCT|C1_DIGIT|C1_LOWER|C1_UPPER) */
91 if (iswprint(pData[offset + i]))
92 lpszLine[6 + cbData * 3 + pad * 3 + DIV_SPACES + i] = pData[offset + i];
93 else
94 lpszLine[6 + cbData * 3 + pad * 3 + DIV_SPACES + i] = '.';
96 lpszLine[6 + cbData * 3 + pad * 3 + DIV_SPACES + cbData] = 0;
97 return lpszLine;
100 static void
101 HexEdit_Paint(HEXEDIT_INFO *infoPtr)
103 PAINTSTRUCT ps;
104 HDC hdc = BeginPaint(infoPtr->hwndSelf, &ps);
105 INT nXStart, nYStart;
106 COLORREF clrOldText;
107 HFONT hOldFont;
108 INT iMode;
109 LONG lByteOffset = infoPtr->nScrollPos * infoPtr->nBytesPerLine;
110 int i;
112 /* Make a gap from the frame */
113 nXStart = GetSystemMetrics(SM_CXBORDER);
114 nYStart = GetSystemMetrics(SM_CYBORDER);
116 if (GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_DISABLED)
117 clrOldText = SetTextColor(hdc, GetSysColor(COLOR_GRAYTEXT));
118 else
119 clrOldText = SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
121 iMode = SetBkMode(hdc, TRANSPARENT);
122 hOldFont = SelectObject(hdc, infoPtr->hFont);
124 for (i = lByteOffset; i < infoPtr->cbData; i += infoPtr->nBytesPerLine)
126 LPWSTR lpszLine;
127 LONG nLineLen = min(infoPtr->cbData - i, infoPtr->nBytesPerLine);
129 lpszLine = HexEdit_GetLineText(i, infoPtr->pData, nLineLen, infoPtr->nBytesPerLine - nLineLen);
131 /* FIXME: draw hex <-> ASCII mapping highlighted? */
132 TextOutW(hdc, nXStart, nYStart, lpszLine, lstrlenW(lpszLine));
134 nYStart += infoPtr->nHeight;
135 free(lpszLine);
138 SelectObject(hdc, hOldFont);
139 SetBkMode(hdc, iMode);
140 SetTextColor(hdc, clrOldText);
142 EndPaint(infoPtr->hwndSelf, &ps);
145 static void
146 HexEdit_UpdateCaret(HEXEDIT_INFO *infoPtr)
148 HDC hdc;
149 HFONT hOldFont;
150 SIZE size;
151 INT nCaretBytePos = infoPtr->nCaretPos/2;
152 INT nByteLinePos = nCaretBytePos % infoPtr->nBytesPerLine;
153 INT nLine = nCaretBytePos / infoPtr->nBytesPerLine;
154 LONG nLineLen = min(infoPtr->cbData - nLine * infoPtr->nBytesPerLine, infoPtr->nBytesPerLine);
155 LPWSTR lpszLine = HexEdit_GetLineText(nLine * infoPtr->nBytesPerLine, infoPtr->pData, nLineLen, infoPtr->nBytesPerLine - nLineLen);
156 INT nCharOffset;
158 /* calculate offset of character caret is on in the line */
159 if (infoPtr->bFocusHex)
160 nCharOffset = 6 + nByteLinePos*3 + infoPtr->nCaretPos % 2;
161 else
162 nCharOffset = 6 + infoPtr->nBytesPerLine*3 + DIV_SPACES + nByteLinePos;
164 hdc = GetDC(infoPtr->hwndSelf);
165 hOldFont = SelectObject(hdc, infoPtr->hFont);
167 GetTextExtentPoint32W(hdc, lpszLine, nCharOffset, &size);
169 SelectObject(hdc, hOldFont);
170 ReleaseDC(infoPtr->hwndSelf, hdc);
172 if (!nLineLen) size.cx = 0;
174 free(lpszLine);
176 SetCaretPos(
177 GetSystemMetrics(SM_CXBORDER) + size.cx,
178 GetSystemMetrics(SM_CYBORDER) + (nLine - infoPtr->nScrollPos) * infoPtr->nHeight);
181 static void
182 HexEdit_UpdateScrollbars(HEXEDIT_INFO *infoPtr)
184 RECT rcClient;
185 INT nLines = infoPtr->cbData / infoPtr->nBytesPerLine;
186 INT nVisibleLines;
187 SCROLLINFO si;
189 GetClientRect(infoPtr->hwndSelf, &rcClient);
190 InflateRect(&rcClient, -GetSystemMetrics(SM_CXBORDER), -GetSystemMetrics(SM_CYBORDER));
192 nVisibleLines = (rcClient.bottom - rcClient.top) / infoPtr->nHeight;
193 si.cbSize = sizeof(si);
194 si.fMask = SIF_RANGE | SIF_PAGE;
195 si.nMin = 0;
196 si.nMax = max(nLines - nVisibleLines, nLines);
197 si.nPage = nVisibleLines;
198 SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &si, TRUE);
201 static void
202 HexEdit_EnsureVisible(HEXEDIT_INFO *infoPtr, INT nCaretPos)
204 INT nLine = nCaretPos / (2 * infoPtr->nBytesPerLine);
205 SCROLLINFO si;
207 si.cbSize = sizeof(si);
208 si.fMask = SIF_POS | SIF_PAGE;
209 GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &si);
210 if (nLine < si.nPos)
211 si.nPos = nLine;
212 else if (nLine >= si.nPos + si.nPage)
213 si.nPos = nLine - si.nPage + 1;
214 else
215 return;
216 si.fMask = SIF_POS;
218 SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &si, FALSE);
219 SendMessageW(infoPtr->hwndSelf, WM_VSCROLL, MAKELONG(SB_THUMBPOSITION, 0), 0);
223 static LRESULT
224 HexEdit_SetData(HEXEDIT_INFO *infoPtr, INT cbData, const BYTE *pData)
226 free(infoPtr->pData);
227 infoPtr->cbData = 0;
229 infoPtr->pData = malloc(cbData);
230 memcpy(infoPtr->pData, pData, cbData);
231 infoPtr->cbData = cbData;
233 infoPtr->nCaretPos = 0;
234 HexEdit_UpdateScrollbars(infoPtr);
235 HexEdit_UpdateCaret(infoPtr);
236 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
237 return TRUE;
240 static LRESULT
241 HexEdit_GetData(HEXEDIT_INFO *infoPtr, INT cbData, BYTE *pData)
243 if (pData)
244 memcpy(pData, infoPtr->pData, min(cbData, infoPtr->cbData));
245 return infoPtr->cbData;
248 static inline LRESULT
249 HexEdit_Char (HEXEDIT_INFO *infoPtr, WCHAR ch)
251 INT nCaretBytePos = infoPtr->nCaretPos/2;
253 assert(nCaretBytePos >= 0);
255 /* backspace is special */
256 if (ch == '\b')
258 if (infoPtr->nCaretPos == 0)
259 return 0;
261 /* if at end of byte then delete the whole byte */
262 if (infoPtr->bFocusHex && (infoPtr->nCaretPos % 2 == 0))
264 memmove(infoPtr->pData + nCaretBytePos - 1,
265 infoPtr->pData + nCaretBytePos,
266 infoPtr->cbData - nCaretBytePos);
267 infoPtr->cbData--;
268 infoPtr->nCaretPos -= 2; /* backtrack two nibble */
270 else /* blank upper nibble */
272 infoPtr->pData[nCaretBytePos] &= 0x0f;
273 infoPtr->nCaretPos--; /* backtrack one nibble */
276 else
278 if (infoPtr->bFocusHex && hexchar_to_byte(ch) == (BYTE)-1)
280 MessageBeep(MB_ICONWARNING);
281 return 0;
284 if ((infoPtr->bInsert && (infoPtr->nCaretPos % 2 == 0)) || (nCaretBytePos >= infoPtr->cbData))
286 /* make room for another byte */
287 infoPtr->cbData++;
288 infoPtr->pData = realloc(infoPtr->pData, infoPtr->cbData + 1);
290 /* move everything after caret up one byte */
291 memmove(infoPtr->pData + nCaretBytePos + 1,
292 infoPtr->pData + nCaretBytePos,
293 infoPtr->cbData - nCaretBytePos);
294 /* zero new byte */
295 infoPtr->pData[nCaretBytePos] = 0x0;
298 /* overwrite a byte */
300 assert(nCaretBytePos < infoPtr->cbData);
302 if (infoPtr->bFocusHex)
304 BYTE orig_byte = infoPtr->pData[nCaretBytePos];
305 BYTE digit = hexchar_to_byte(ch);
306 if (infoPtr->nCaretPos % 2) /* set low nibble */
307 infoPtr->pData[nCaretBytePos] = (orig_byte & 0xf0) | digit;
308 else /* set high nibble */
309 infoPtr->pData[nCaretBytePos] = (orig_byte & 0x0f) | digit << 4;
310 infoPtr->nCaretPos++; /* advance one nibble */
312 else
314 infoPtr->pData[nCaretBytePos] = (BYTE)ch;
315 infoPtr->nCaretPos += 2; /* advance two nibbles */
319 HexEdit_UpdateScrollbars(infoPtr);
320 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
321 HexEdit_UpdateCaret(infoPtr);
322 HexEdit_EnsureVisible(infoPtr, infoPtr->nCaretPos);
323 return 0;
326 static inline LRESULT
327 HexEdit_Destroy (HEXEDIT_INFO *infoPtr)
329 HWND hwnd = infoPtr->hwndSelf;
330 free(infoPtr->pData);
331 /* free info data */
332 free(infoPtr);
333 SetWindowLongPtrW(hwnd, 0, 0);
334 return 0;
337 static inline LRESULT
338 HexEdit_GetFont (HEXEDIT_INFO *infoPtr)
340 return (LRESULT)infoPtr->hFont;
343 static inline LRESULT
344 HexEdit_KeyDown (HEXEDIT_INFO *infoPtr, DWORD key, DWORD flags)
346 INT nInc = (infoPtr->bFocusHex) ? 1 : 2;
347 SCROLLINFO si;
349 switch (key)
351 case VK_LEFT:
352 infoPtr->nCaretPos -= nInc;
353 if (infoPtr->nCaretPos < 0)
354 infoPtr->nCaretPos = 0;
355 break;
356 case VK_RIGHT:
357 infoPtr->nCaretPos += nInc;
358 if (infoPtr->nCaretPos > infoPtr->cbData*2)
359 infoPtr->nCaretPos = infoPtr->cbData*2;
360 break;
361 case VK_UP:
362 if ((infoPtr->nCaretPos - infoPtr->nBytesPerLine*2) >= 0)
363 infoPtr->nCaretPos -= infoPtr->nBytesPerLine*2;
364 break;
365 case VK_DOWN:
366 if ((infoPtr->nCaretPos + infoPtr->nBytesPerLine*2) <= infoPtr->cbData*2)
367 infoPtr->nCaretPos += infoPtr->nBytesPerLine*2;
368 break;
369 case VK_HOME:
370 infoPtr->nCaretPos = 0;
371 break;
372 case VK_END:
373 infoPtr->nCaretPos = infoPtr->cbData*2;
374 break;
375 case VK_PRIOR: /* page up */
376 si.cbSize = sizeof(si);
377 si.fMask = SIF_PAGE;
378 GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &si);
379 if ((infoPtr->nCaretPos - (INT)si.nPage*infoPtr->nBytesPerLine*2) >= 0)
380 infoPtr->nCaretPos -= si.nPage*infoPtr->nBytesPerLine*2;
381 else
382 infoPtr->nCaretPos = 0;
383 break;
384 case VK_NEXT: /* page down */
385 si.cbSize = sizeof(si);
386 si.fMask = SIF_PAGE;
387 GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &si);
388 if ((infoPtr->nCaretPos + (INT)si.nPage*infoPtr->nBytesPerLine*2) <= infoPtr->cbData*2)
389 infoPtr->nCaretPos += si.nPage*infoPtr->nBytesPerLine*2;
390 else
391 infoPtr->nCaretPos = infoPtr->cbData*2;
392 break;
393 default:
394 return 0;
397 HexEdit_UpdateCaret(infoPtr);
398 HexEdit_EnsureVisible(infoPtr, infoPtr->nCaretPos);
400 return 0;
404 static inline LRESULT
405 HexEdit_KillFocus (HEXEDIT_INFO *infoPtr, HWND receiveFocus)
407 infoPtr->bFocus = FALSE;
408 DestroyCaret();
410 return 0;
414 static inline LRESULT
415 HexEdit_LButtonDown (HEXEDIT_INFO *infoPtr)
417 SetFocus(infoPtr->hwndSelf);
419 /* FIXME: hittest and set caret */
421 return 0;
425 static inline LRESULT HexEdit_NCCreate (HWND hwnd, LPCREATESTRUCTW lpcs)
427 HEXEDIT_INFO *infoPtr;
428 SetWindowLongW(hwnd, GWL_EXSTYLE,
429 lpcs->dwExStyle | WS_EX_CLIENTEDGE);
431 /* allocate memory for info structure */
432 infoPtr = malloc(sizeof(HEXEDIT_INFO));
433 memset(infoPtr, 0, sizeof(HEXEDIT_INFO));
434 SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
436 /* initialize info structure */
437 infoPtr->nCaretPos = 0;
438 infoPtr->hwndSelf = hwnd;
439 infoPtr->nBytesPerLine = 2;
440 infoPtr->bFocusHex = TRUE;
441 infoPtr->bInsert = TRUE;
443 return DefWindowProcW(infoPtr->hwndSelf, WM_NCCREATE, 0, (LPARAM)lpcs);
446 static inline LRESULT
447 HexEdit_SetFocus (HEXEDIT_INFO *infoPtr, HWND lostFocus)
449 infoPtr->bFocus = TRUE;
451 CreateCaret(infoPtr->hwndSelf, NULL, 1, infoPtr->nHeight);
452 HexEdit_UpdateCaret(infoPtr);
453 ShowCaret(infoPtr->hwndSelf);
455 return 0;
459 static inline LRESULT
460 HexEdit_SetFont (HEXEDIT_INFO *infoPtr, HFONT hFont, BOOL redraw)
462 TEXTMETRICW tm;
463 HDC hdc;
464 HFONT hOldFont = NULL;
465 LONG i;
466 RECT rcClient;
468 infoPtr->hFont = hFont;
470 hdc = GetDC(infoPtr->hwndSelf);
471 if (infoPtr->hFont)
472 hOldFont = SelectObject(hdc, infoPtr->hFont);
474 GetTextMetricsW(hdc, &tm);
475 infoPtr->nHeight = tm.tmHeight + tm.tmExternalLeading;
477 GetClientRect(infoPtr->hwndSelf, &rcClient);
479 for (i = 0; ; i++)
481 BYTE *pData = malloc(i);
482 WCHAR *lpszLine;
483 SIZE size;
485 memset(pData, 0, i);
486 lpszLine = HexEdit_GetLineText(0, pData, i, 0);
487 GetTextExtentPoint32W(hdc, lpszLine, lstrlenW(lpszLine), &size);
488 free(lpszLine);
489 free(pData);
490 if (size.cx > (rcClient.right - rcClient.left))
492 infoPtr->nBytesPerLine = i - 1;
493 break;
497 HexEdit_UpdateScrollbars(infoPtr);
499 if (infoPtr->hFont)
500 SelectObject(hdc, hOldFont);
501 ReleaseDC (infoPtr->hwndSelf, hdc);
503 if (redraw)
504 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
506 return 0;
509 static inline LRESULT
510 HexEdit_VScroll (HEXEDIT_INFO *infoPtr, INT action)
512 SCROLLINFO si;
514 /* get all scroll bar info */
515 si.cbSize = sizeof(si);
516 si.fMask = SIF_ALL;
517 GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &si);
519 switch (LOWORD(action))
521 case SB_TOP: /* user pressed the home key */
522 si.nPos = si.nMin;
523 break;
525 case SB_BOTTOM: /* user pressed the end key */
526 si.nPos = si.nMax;
527 break;
529 case SB_LINEUP: /* user clicked the up arrow */
530 si.nPos -= 1;
531 break;
533 case SB_LINEDOWN: /* user clicked the down arrow */
534 si.nPos += 1;
535 break;
537 case SB_PAGEUP: /* user clicked the scroll bar above the scroll thumb */
538 si.nPos -= si.nPage;
539 break;
541 case SB_PAGEDOWN: /* user clicked the scroll bar below the scroll thumb */
542 si.nPos += si.nPage;
543 break;
545 case SB_THUMBTRACK: /* user dragged the scroll thumb */
546 si.nPos = si.nTrackPos;
547 break;
549 default:
550 break;
553 /* set the position and then retrieve it to let the system handle the
554 * cases where the position is out of range */
555 si.fMask = SIF_POS;
556 SetScrollInfo(infoPtr->hwndSelf, SB_VERT, &si, TRUE);
557 GetScrollInfo(infoPtr->hwndSelf, SB_VERT, &si);
559 if (si.nPos != infoPtr->nScrollPos)
561 ScrollWindow(infoPtr->hwndSelf, 0, infoPtr->nHeight * (infoPtr->nScrollPos - si.nPos), NULL, NULL);
562 infoPtr->nScrollPos = si.nPos;
563 UpdateWindow(infoPtr->hwndSelf);
565 /* need to update caret position since it depends on the scroll position */
566 HexEdit_UpdateCaret(infoPtr);
568 return 0;
572 static LRESULT WINAPI
573 HexEdit_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
575 HEXEDIT_INFO *infoPtr = (HEXEDIT_INFO *)GetWindowLongPtrW(hwnd, 0);
577 if (!infoPtr && (uMsg != WM_NCCREATE))
578 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
580 switch (uMsg)
582 case HEM_SETDATA:
583 return HexEdit_SetData (infoPtr, (INT)wParam, (const BYTE *)lParam);
585 case HEM_GETDATA:
586 return HexEdit_GetData (infoPtr, (INT)wParam, (BYTE *)lParam);
588 case WM_CHAR:
589 return HexEdit_Char (infoPtr, (WCHAR)wParam);
591 case WM_DESTROY:
592 return HexEdit_Destroy (infoPtr);
594 case WM_GETDLGCODE:
595 return DLGC_WANTCHARS | DLGC_WANTARROWS;
597 case WM_GETFONT:
598 return HexEdit_GetFont (infoPtr);
600 case WM_KEYDOWN:
601 return HexEdit_KeyDown (infoPtr, wParam, lParam);
603 case WM_KILLFOCUS:
604 return HexEdit_KillFocus (infoPtr, (HWND)wParam);
606 case WM_LBUTTONDOWN:
607 return HexEdit_LButtonDown (infoPtr);
609 case WM_NCCREATE:
610 return HexEdit_NCCreate (hwnd, (LPCREATESTRUCTW)lParam);
612 case WM_PAINT:
613 HexEdit_Paint(infoPtr);
614 return 0;
616 case WM_SETFOCUS:
617 return HexEdit_SetFocus (infoPtr, (HWND)wParam);
619 case WM_SETFONT:
620 return HexEdit_SetFont (infoPtr, (HFONT)wParam, LOWORD(lParam));
622 case WM_VSCROLL:
623 return HexEdit_VScroll (infoPtr, (INT)wParam);
625 default:
626 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
628 return 0;
631 void HexEdit_Register(void)
633 WNDCLASSW wndClass;
635 ZeroMemory(&wndClass, sizeof(WNDCLASSW));
636 wndClass.style = 0;
637 wndClass.lpfnWndProc = HexEdit_WindowProc;
638 wndClass.cbClsExtra = 0;
639 wndClass.cbWndExtra = sizeof(HEXEDIT_INFO *);
640 wndClass.hCursor = LoadCursorW(0, (const WCHAR *)IDC_IBEAM);
641 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
642 wndClass.lpszClassName = L"HexEdit";
644 RegisterClassW(&wndClass);