/************************************************************************\
 * File Version Information
 * $Header: /Altair32v2/windbg_mem.c 26    11/08/06 9:22p Racini $
 ************************************************************************/
/************************************************************************\
  MITS Altair Emulator
  Debugger memory display child window
  
  Main debugger support for the Solace Sol-20 emulator
  Copyright (c) Jim Battle, 2000, 2001

  Modifications for the Altair32 Emulator
  Copyright (c) 2001-2006 Richard A. Cini

  This subwindow is scrollable vertically. It shows words (byte pairs)
  near the top of the stack, although it can be scrolled to an
  arbitrary memory location.


Change Log:
  2001/11/07  RAC -- Initial changes
  2001/11/22  RAC -- Changed GetMem at stacktop from 256* to <<8
  2001/12/14  RAC -- RELEASE MARKER -- v2.1
  2002/01/02  RAC -- Synched with Solace 3.0
  2002/01/31  RAC -- RELEASE MARKER -- v2.2
  2002/07/07  RAC -- RELEASE MARKER -- v2.3
  2002/08/23  RAC -- RELEASE MARKER -- v2.30.10
  2002/11/15  RAC -- RELEASE MARKER -- v2.40.2100
  2003/04/26  RAC -- RELEASE MARKER -- v2.50.2045
  2004/07/30  RAC -- RELEASE MARKER -- v3.00.0135
  2006/05/12  RAC -- RELEASE MARKER -- v3.10.0200
  2006/11/15  RAC -- RELEASE MARKER -- v3.20.0400
\************************************************************************/
#include <windows.h>
#include <windowsx.h>	// message cracker macros
#include <stdlib.h>
#include <stdio.h>		// C-lib - I/O included in altair32.h
#include <string.h>
#include "altair32.h"
#include "i8080.h"		//Di80 macro
#include "windbg.h"
#include "windbg_sub.h"
#include "extensions.h" // corrections for bad functions in windowsx


static struct tagMEMSTATE {
    int	active;
    int win_x;
    int win_y;
    int chars_x;	// window width  in chars, rounded up
    int chars_y;	// window height in chars, rounded up
    word topaddr;	// first address in window
} memstate;


static BOOL
OnCreateMem(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
{
    // set range for vertical scrollbar; runs from 0000 to FFFF
    SetScrollRange(hwnd, SB_VERT, 0x0000, 0xFFFF, FALSE);
    SetScrollPos(hwnd, SB_VERT, (int)I80Regs.nPC.W, TRUE);

    return TRUE;
}

// handle window resize events
static void
OnSizeMem(HWND hwnd, UINT state, int cx, int cy)
{
    memstate.win_x = cx;
    memstate.win_y = cy;

    memstate.chars_x = (cx + dbgstate.fix_x - 1) / dbgstate.fix_x;
    memstate.chars_y = (cy + dbgstate.fix_y - 1) / dbgstate.fix_y;
}

static void
OnPaintMem(HWND hwnd)
{
    PAINTSTRUCT	ps;
    HDC hdc;
    COLORREF oldcolor;
    HBRUSH newbrush, oldbrush;
    HPEN oldpen, newpen;
    int i, saved;
    int addr;
    const int xoff = 3;	// horizontal pixel offset within window

    hdc = BeginPaint(hwnd, &ps);
    saved = SaveDC(hdc);

    if (!memstate.active) {

	// gray out the surface
	GrayOutWindow(hwnd, hdc);

    } else {

	// in order to reduce flashing on single step operation, when
	// we create the window we tell windows not to automatically
	// erase invalidated regions to the background color.  instead,
	// we just draw opaque text.  however, we have to be careful to
	// erase the border too.  that is what we do here.
	COLORREF bg = RGB(0xFF, 0xFF, 0xFF);
	newbrush = CreateSolidBrush(bg);
	newpen   = CreatePen(PS_SOLID, 1, bg);
	oldbrush = SelectObject(hdc, newbrush);
	oldpen   = SelectObject(hdc, newpen);
	Rectangle(hdc, 0,0, xoff, memstate.win_y);					// erase left strip
	Rectangle(hdc, xoff + 10*dbgstate.fix_x, 0, memstate.win_x, memstate.win_y);	// erase bottom strip
	SelectObject(hdc, oldbrush);
	SelectObject(hdc, oldpen);
	DeleteObject(newbrush);
	DeleteObject(newpen);

	SelectFont(hdc, dbgstate.fixed_font);

	// make sure display has same even/odd alignment as SP
	if ((memstate.topaddr ^ I80Regs.nSP.W) & 1) {
	    memstate.topaddr ^= 1;
	    InvalidateRect(dbgstate.hMemWnd, NULL, TRUE);
	}

	addr = memstate.topaddr;

	for(i=0; i<memstate.chars_y; i++) {

	    char membuf[80];
	    int cursp = (addr == I80Regs.nSP.W);
	    int len;
	    int val = Di80((ushort)addr) + 256*Di80((word)(addr+1));

	    len = sprintf(membuf, "%04X: %04X", (addr & 0xFFFF), val);

	    if (cursp) {
		COLORREF bg = RGB(0xFF, 0xFF, 0x00);
		oldcolor = SetBkColor(hdc, bg);
		newbrush = CreateSolidBrush(bg);
		newpen   = CreatePen(PS_SOLID, 1, bg);
		oldbrush = SelectObject(hdc, newbrush);
		oldpen   = SelectObject(hdc, newpen);
		Rectangle(hdc, 0, i*dbgstate.fix_y,			// l, t
			  memstate.win_x, (i+1)*dbgstate.fix_y);	// r, b
	    }

	    TextOut(hdc, xoff, i*dbgstate.fix_y, membuf, len);

	    if (cursp) {
		SetBkColor(hdc, oldcolor);
		SelectObject(hdc, oldbrush);
		SelectObject(hdc, oldpen);
		DeleteObject(newbrush);
		DeleteObject(newpen);
	    }

	    addr += 2;

	} // for i
    }

    RestoreDC(hdc, saved);
    EndPaint(hwnd, &ps);
}

static void
OnKeyDownMem(HWND hwnd, UINT vk, BOOL down, int repeat, UINT flags)
{
    switch (vk) {

	case VK_UP:    MemWindowAction(WA_LINEUP, 0);          return;
	case VK_DOWN:  MemWindowAction(WA_LINEDOWN, 0);        return; 
	case VK_PRIOR: MemWindowAction(WA_PAGEUP, 0);          return;
	case VK_NEXT:  MemWindowAction(WA_PAGEDOWN, 0);        return;
	case VK_HOME:  MemWindowAction(WA_GOTO, I80Regs.nSP.W); return;

	case VK_F5:
	case VK_F6:
	case VK_F7:
	    handle_fkey(vk);
	    return;

	default: break;
    }

    // use default message handler
    FORWARD_WM_KEYDOWN(hwnd, vk, repeat, flags, DefWindowProc);
}

static void
OnVScrollMem(HWND hwnd, HWND hctl, UINT code, int pos)
{
    word loc = (pos & 0xFFFF);	// although passed as int, really signed short

    SetFocus(hwnd);	// make keyboard input go to this subwin

    switch (code) {

	case SB_LINEUP:   MemWindowAction(WA_LINEUP,   0); break;
	case SB_LINEDOWN: MemWindowAction(WA_LINEDOWN, 0); break;
	case SB_PAGEUP:   MemWindowAction(WA_PAGEUP,   0); break;
	case SB_PAGEDOWN: MemWindowAction(WA_PAGEDOWN, 0); break;

	case SB_THUMBTRACK:
	    {
		// show current value in real time, centered
		HDC hdc = GetDC(hwnd);
		COLORREF bg       = RGB(0x00, 0xFF, 0xFF);
		COLORREF oldcolor = SetBkColor(hdc, bg);
		char buf[10];
		int len = sprintf(buf, " %04X  ", (loc & 0xFFFF));

		TextOut(hdc, dbgstate.fix_x * (memstate.chars_x-4)/2,
			     dbgstate.fix_y * (memstate.chars_y)  /2,
			     buf, len);

		SetBkColor(hdc, oldcolor);
		ReleaseDC(hwnd, hdc);
	    }
	    return;

	case SB_THUMBPOSITION:
	    // align to the SP even/oddness
	    loc = (I80Regs.nSP.W & 1) ? (loc | 0x0001) : (loc & 0xFFFE);
	    SetScrollPos(hwnd, SB_VERT, (int)loc, TRUE);
	    MemWindowAction(WA_GOTO, loc);
	    return;

	default:
	    return;
    }

    // get current top line and set scrollbar position
    SetScrollPos(hwnd, SB_VERT, memstate.topaddr, TRUE);
}

// just set focus
static void
OnLButtonUpMem(HWND hWnd, int xClient, int yClient, UINT keyflags)
{
    SetFocus(hWnd);
}

void
MemWindowAction(win_action_t action, word addr)
{
    int newtop, linediff;
    int newbottom;

    switch (action) {

    case WA_INIT:
	memstate.topaddr = 0x0000;
	memstate.active  = FALSE;
	return;

    case WA_DISABLE:
    case WA_ENABLE:
	memstate.active = (action == WA_ENABLE);
	// hide/show the scroll bar
	ShowScrollBar(dbgstate.hMemWnd, SB_VERT, memstate.active);
	// force redraw
	InvalidateRect(dbgstate.hMemWnd, NULL, TRUE);
	return;

    case WA_LINEUP:
	// make current 1st line the 2nd line
	newtop = memstate.topaddr - 2;
	break;

    case WA_PAGEUP:
	// make current 1st line the last line
	newtop = memstate.topaddr - 2*(memstate.chars_y-2);
	break;

    case WA_LINEDOWN:
	// make current 2nd line the top line
	newtop = memstate.topaddr + 2;
	break;

    case WA_PAGEDOWN:
	// make current end of list the first line
	newtop = memstate.topaddr + 2*(memstate.chars_y-2);
	break;

    case WA_GOTO:
	// if the indicated address is already in the window,
	// we just redraw the affected lines, vs recentering
	// and drawing the whole window
	if ((addr >= memstate.topaddr) &&
	    (addr <= memstate.topaddr + 2*(memstate.chars_y-1))) {
	    // the indicated line is already in the window, but
	    // may need to redraw to indicate current position
	    InvalidateRect(dbgstate.hMemWnd, NULL, TRUE);
	    SetScrollPos(dbgstate.hMemWnd, SB_VERT, (int)memstate.topaddr, TRUE);
	    return;
	}
	// go to indicated address, centering it
	newtop = addr - 2*(memstate.chars_y/2);
	break;

    default:
	return;
    }

    // don't scroll top earlier than 0
    if (newtop < 0)
	newtop &= 1;

    // don't scroll bottom later than 64K-1
    newbottom = newtop + 2*memstate.chars_y;
    if (newbottom > 65535)
	newtop -= (newbottom-65536);

    linediff = (memstate.topaddr - newtop)/2;

    memstate.topaddr = newtop;

    // move vertical scroll bar to indicated position
    SetScrollPos(dbgstate.hMemWnd, SB_VERT, (int)memstate.topaddr, TRUE);

    ScrollWindow(dbgstate.hMemWnd,
		    0, (linediff * dbgstate.fix_y),	// X,Y scroll
		    NULL, NULL);
}

static void
OnDestroyMem(HWND hwnd)
{
    dbgstate.hMemWnd = NULL;
}

// main window handler
static LRESULT CALLBACK
WndProcMem(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
    switch (iMsg) {
	HANDLE_MSG(hWnd, WM_CREATE,     OnCreateMem);
	HANDLE_MSG(hWnd, WM_SIZE,       OnSizeMem);
	HANDLE_MSG(hWnd, WM_PAINT,      OnPaintMem);
	HANDLE_MSG(hWnd, WM_KEYDOWN,    OnKeyDownMem);
	HANDLE_MSG(hWnd, WM_VSCROLL,    OnVScrollMem);
	HANDLE_MSG(hWnd, WM_DESTROY,    OnDestroyMem);
	HANDLE_MSG(hWnd, WM_LBUTTONUP,  OnLButtonUpMem);

	case WM_CHAR:	// forward to cmd edit window
	    windbg_cmd_fwdchar(wParam, lParam);
	    break;

	case WM_GETDLGCODE:
	    return DLGC_WANTALLKEYS /* | DLGC_WANTCHARS to get WM_CHAR messages */;
    }

    return DefWindowProc(hWnd, iMsg, wParam, lParam);
}

void
RegisterMemClass(void)
{
    WNDCLASSEX  wndclass;

    wndclass.cbSize        = sizeof(wndclass);
    wndclass.style         = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    wndclass.lpfnWndProc   = WndProcMem;
    wndclass.cbClsExtra    = 0;
    wndclass.cbWndExtra    = 0;
    wndclass.hInstance     = winstate.hInst;
    wndclass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hCursor       = NULL; //LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = NULL; // (HBRUSH)(COLOR_WINDOW + 1);
    wndclass.lpszMenuName  = NULL;
    wndclass.lpszClassName = "solacemem";
    wndclass.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

    RegisterClassEx(&wndclass);
}

HWND
CreateMemWindow(HWND hwnd)
{
    DWORD winstyle = WS_CHILD | WS_VSCROLL | WS_VISIBLE;
    DWORD extstyle = WS_EX_CLIENTEDGE;
    HWND  hwndmem;

    hwndmem = CreateWindowEx(
		extstyle,		// extended style
		"solacemem",	// window class name
		0,				// window caption
		winstyle,		// window style
		0,0,			// initial x,y position
		0,0,			// initial x,y size
		hwnd,			// parent window handle
		NULL,			// window menu handle
		winstate.hInst,			// program instance handle
		NULL);			// creation parameters

    ASSERT(hwndmem != NULL);

    return hwndmem;
}
/* end of file: windbg_mem.c */
