#include "xlisp.h"
#include "xlstat.h"
#include "wxlisp.h"
#include "ledit.h"
#include "winutils.h"

/* external variables */
extern HWND hWndFrame, hWndClient;
extern HANDLE hInst, hAccel;
extern LVAL s_menu, sk_allocate, sk_close, sk_install, sk_remove;
extern LVAL s_hardware_objects;
extern int Exiting;
extern int xldebug;
extern int xlisp_mono;

/* global variables */
static char szIVWinClass[]  = "MdiIVWinChild";
static char graphicsSection[] = "Graphics";
extern char *iniFile;
static HCURSOR hSaveCursor;
static HCURSOR hGCCursor, hArrowCursor, hWaitCursor, hCrossCursor,
  hHandCursor, hBrushCursor, hFingerCursor;
static BOOL MouseCaptured = FALSE, MouseMotionOnly;
static HWND MouseWnd = 0;
static void (*MouseAction)(HWND, int, int);
static TEXTMETRIC tm;
static HFONT hGraphFont, hGraphFontUp;

/* buffer variables */
static HBITMAP monoBuffBits = 0, colorBuffBits = 0;
static HDC currentDC = 0, bufferDC = 0;
static int buffering = FALSE;
static int bufflevel = 0;

void StGWCopyToClip(StGWWinInfo *);
long CALLBACK IVWinProc(HWND hWnd, UINT message, WPARAM wParam, LONG lParam);

static HCURSOR get_cursor(unsigned int index);
static void init_msw_cursors(void);
static void init_msw_colors(void);
static unsigned long get_color(unsigned int index);
void init_msw_buffer(void);
static void init_msw_text(void);
static void cleanup_msw_text(void);
static void init_msw_symbols(void);
static void cleanup_msw_symbols(void);
static void StGWPrint(StGWWinInfo *gwinfo);

/**************************************************************************/
/**                                                                      **/
/**                       Internal Data Functions                        **/
/**                                                                      **/
/**************************************************************************/

int StGWWinInfoSize(void) { return(sizeof(StGWWinInfo)); }

void StGWInitWinInfo(LVAL object)
{
  StGWWinInfo *gwinfo = (StGWWinInfo *) StGWObWinInfo(object);

  gwinfo->Object = object;
  gwinfo->initialized = FALSE;
  gwinfo->mouse_x = 0;
  gwinfo->mouse_y = 0;
  gwinfo->canvasWidth = 0;
  gwinfo->canvasHeight = 0;
  gwinfo->hasHscroll = FALSE;
  gwinfo->hasVscroll = FALSE;
  gwinfo->view_h = 0;
  gwinfo->view_v = 0;
  gwinfo->h_scroll_inc[0] = 1; gwinfo->h_scroll_inc[1] = 50;
  gwinfo->v_scroll_inc[0] = 1; gwinfo->v_scroll_inc[1] = 50;
  gwinfo->lineType = 0;
  gwinfo->drawMode = 0;
  gwinfo->backColor = 0;
  gwinfo->drawColor = 1;
  gwinfo->lineWidth = 1;
  gwinfo->window = NULL;
  gwinfo->idleOn = FALSE;
  gwinfo->use_color = FALSE;
  gwinfo->cursor = 0;
  gwinfo->RefCon = 0L;
  gwinfo->drawPen.lopnStyle = PS_SOLID;
  gwinfo->drawPen.lopnWidth.x = 1;
  gwinfo->drawPen.lopnColor = 0;
  gwinfo->rect_offset = 0;
}

void StGWSetRefCon(StGWWinInfo *gwinfo, long x)
{
  if (gwinfo) gwinfo->RefCon = x;
}

long StGWGetRefCon(StGWWinInfo *gwinfo)
{
  return(gwinfo ? gwinfo->RefCon : 0);
}

void StGWSetObject(StGWWinInfo *gwinfo, LVAL x)
{
  if (gwinfo) gwinfo->Object = x;
}

LVAL IViewWindowGetObject(HWND w)
{
  StGWWinInfo *gwinfo = GETGWINFO(w);
  return(gwinfo ? gwinfo->Object : NIL);
}

LVAL StGWGetObject(StGWWinInfo *gwinfo)
{
  return(gwinfo ? gwinfo->Object : NIL);
}

static void SetHardwareState(StGWWinInfo *gwinfo)
{
  HDC hDC;
  HWND w;

  if (gwinfo && (w = gwinfo->window) != 0) {
    hDC = GetDC((HWND) w);
    SetBkMode(hDC, TRANSPARENT);
    SetBkColor(hDC, get_color(gwinfo->backColor));
    SetTextColor(hDC, get_color(gwinfo->drawColor));
    SelectObject(hDC, hGraphFont);
    SetROP2(hDC, gwinfo->drawMode ? R2_NOT : R2_COPYPEN);
    SetPolyFillMode(hDC, WINDING);
    ReleaseDC((HWND) w, hDC);
  }
}

/**************************************************************************/
/**                                                                      **/
/**                       Initialization Functions                       **/
/**                                                                      **/
/**************************************************************************/

BOOL InitApplGraphics(HANDLE hInstance)
{
  WNDCLASS wc;

  /* class structure for the IVIEW window */
  wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
  wc.lpfnWndProc = IVWinProc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = sizeof(LVAL) + sizeof(char *);
  wc.hInstance = hInstance;
  wc.hIcon = LoadIcon(hInstance, "GraphIcon");
  wc.hCursor = (HCURSOR) NULL;
  wc.hbrBackground = GetStockObject(WHITE_BRUSH);
  wc.lpszMenuName = NULL;
  wc. lpszClassName = szIVWinClass;
  if (! RegisterClass(&wc)) return(FALSE);

  if (! InitApplResizeBrush(hInstance)) return(FALSE);
  return(TRUE);
}

BOOL InitInstGraphics(HANDLE hInstance)
{
  hGCCursor = LoadCursor(hInstance, "GCCursor");
  hHandCursor = LoadCursor(hInstance, "HandCursor");
  hFingerCursor = LoadCursor(hInstance, "FingerCursor");
  hBrushCursor = LoadCursor(hInstance, "BrushCursor");
  hArrowCursor = LoadCursor((HWND) NULL, IDC_ARROW);
  hWaitCursor = LoadCursor((HWND) NULL, IDC_WAIT);
  hCrossCursor = LoadCursor((HWND) NULL, IDC_CROSS);
  return(TRUE);
}

void StInitGraphics(void)
{
  init_msw_buffer();
  init_msw_symbols();
  init_msw_colors();
  init_msw_cursors();
  init_msw_text();
}

void MSWGraphCleanup(void)
{
  if (bufferDC) {
    DeleteDC(bufferDC);
    bufferDC = 0;
  }
  if (colorBuffBits) {
    DeleteObject(colorBuffBits);
    colorBuffBits = 0;
  }
  if (monoBuffBits) {
    DeleteObject(monoBuffBits);
    monoBuffBits = 0;
  }
  cleanup_msw_symbols();
  cleanup_msw_text();
}

/**************************************************************************/
/**                                                                      **/
/**         Window Creation, Destruction and Callback Functions          **/
/**                                                                      **/
/**************************************************************************/

HWND IViewWindowNew(LVAL object, int is_GW)
{
  char *title;
  int left, top, width, height, goAway;
  StGWWinInfo *gwinfo;
  HWND wind;
  MDICREATESTRUCT mdicreate;

  StGWGetAllocInfo(object, &title, &left, &top, &width, &height, &goAway);
  if (title == NULL || strlen(title) <= 0) title = "Graph Window";
  width += 2 * GetSystemMetrics(SM_CXFRAME);
  height += 2 * GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CYCAPTION);

  mdicreate.szClass = szIVWinClass;
  mdicreate.szTitle = title;
  mdicreate.hOwner = hInst;
  mdicreate.x = left;
  mdicreate.y = top;
  mdicreate.cx = width;
  mdicreate.cy = height;
  mdicreate.style = WS_MINIMIZE | WS_HSCROLL | WS_VSCROLL;
  mdicreate.lParam = (LONG) object;
  wind = MDICreateWindow(hWndClient, &mdicreate);
  if (! wind) xlfail("allocation Failed");

  gwinfo = (StGWWinInfo *) StGWObWinInfo(object);
  gwinfo->window = wind;
  SetScrollRange(wind, SB_VERT, 0, 0, TRUE);
  SetScrollRange(wind, SB_HORZ, 0, 0, TRUE);
  if (! gwinfo->hasHscroll) gwinfo->canvasWidth = width;
  if (! gwinfo->hasVscroll) gwinfo->canvasHeight = height;
  gwinfo->initialized = FALSE;
  SETGWINFO(wind, gwinfo);
  SETWINOBJECT(wind, object);
  SetHardwareState(gwinfo);
  StGWSetClipRect(gwinfo, FALSE, 0, 0, 0, 0);

  if (is_GW) set_iview_window_address(wind, object);
  else set_iview_address(wind, object);

  return(wind);
}

void StGWRemove(StGWWinInfo *gwinfo)
{
  HWND w;
  
  if (gwinfo == NULL || (w = gwinfo->window) == NULL) return;
  else MDIDestroyWindow(hWndClient, w);
}

void ReleaseMouse(void)
{
  if (MouseCaptured) {
    ReleaseCapture();
    MouseCaptured = FALSE;
    MouseAction = NULL;
  }
  MouseWnd = 0; // indicates mouse is up
}

void CaptureMouse(HWND hWnd)
{
  ReleaseMouse();
  SetCapture(hWnd);
  MouseCaptured = TRUE;
  MouseWnd = hWnd;
}

long CALLBACK IVWinProc(HWND hWnd, UINT message, WPARAM wParam, LONG lParam)
{
  StGWWinInfo *gwinfo;
  static int activating = FALSE;

  switch (message) {
  case WM_CREATE:
    ReleaseMouse();
    return(0);
  case WM_COMMAND:
    switch (GET_WM_COMMAND_ID(wParam, lParam)) {
    case IDC_SHOWWINDOW:
      if (IsIconic(hWnd))
	MDIRestoreWindow(hWndClient, hWnd);
      MDIActivateWindow(hWndClient, hWnd);
      return(0);
    case IDC_HIDEWINDOW:
      if (! IsIconic(hWnd))
	ShowWindow(hWnd, SW_MINIMIZE);
      return(0);
    case IDM_COPY:
      gwinfo = GETGWINFO(hWnd);
      StGWCopyToClip(gwinfo);
      return(0);
    case IDM_PRINT:
      gwinfo  = GETGWINFO(hWnd);
      StGWPrint(gwinfo);
    break;
      break;
    }
    break;
  case WM_SIZE:
    if (! IsIconic(hWnd)) {
      gwinfo = GETGWINFO(hWnd);
      if (gwinfo->hasHscroll) {
	SetScrollRange(hWnd, SB_HORZ, 0, gwinfo->canvasWidth - LOWORD(lParam), TRUE);
      }
      else gwinfo->canvasWidth = LOWORD(lParam);
      if (gwinfo->hasVscroll) {
	SetScrollRange(hWnd, SB_VERT, 0, gwinfo->canvasHeight - HIWORD(lParam), TRUE);
      }
      else gwinfo->canvasHeight = HIWORD(lParam);
      if (gwinfo->hasVscroll || gwinfo->hasHscroll)
	StGWSetScroll(gwinfo, gwinfo->view_h, gwinfo->view_v, TRUE);
      StGWObResize(GETWINOBJECT(hWnd));
    }
    // ### fix slot value
    break;
  case WM_MOVE:
    // ### fix slot value
    break;
  case WM_VSCROLL:
  case WM_HSCROLL:
    {
      int val, hval, vval, *incs;

      gwinfo = GETGWINFO(hWnd);
      val = (message == WM_HSCROLL) ? gwinfo->view_h : gwinfo->view_v;
      incs = (message == WM_HSCROLL) ? gwinfo->h_scroll_inc : gwinfo->v_scroll_inc;
      switch (GET_WM_VSCROLL_CODE(wParam,lParam)) {
      case SB_LINEUP:        val -= incs[0];       break;
      case SB_LINEDOWN:      val += incs[0];       break;
      case SB_PAGEUP:        val -= incs[1];       break;
      case SB_PAGEDOWN:      val += incs[1];       break;
      case SB_TOP:
	{
	  int nMin,nMax;
	  int scroll = (message == WM_HSCROLL) ? SB_HORZ : SB_VERT;
	  GetScrollRange(hWnd, scroll, &nMin, &nMax);
	  val = nMin;
	  break;
	}
      case SB_BOTTOM:
	{
	  int nMin,nMax;
	  int scroll = (message == WM_HSCROLL) ? SB_HORZ : SB_VERT;
	  GetScrollRange(hWnd, scroll, &nMin, &nMax);
	  val = nMax;
	  break;
	}
      case SB_THUMBPOSITION: val = GET_WM_VSCROLL_POS(wParam,lParam); break;
      }
      hval = (message == WM_HSCROLL) ? val: gwinfo->view_h;
      vval = (message == WM_HSCROLL) ? gwinfo->view_v : val;
      StGWSetScroll(gwinfo, hval, vval, TRUE);
    }
    return(0);
  case WM_CHAR:
    StGWObDoKey(GETWINOBJECT(hWnd),
		(unsigned char) wParam,
		GetKeyState(VK_SHIFT),
		GetKeyState(VK_CONTROL));
    return(0);
#ifdef DODO
    /**** These don't make any sense.  Something like sending a symbol
      or a logical key code to the application is needed */
    case WM_KEYDOWN:
     switch (wParam) {
     case VK_UP:
     case VK_DOWN:
     case VK_LEFT:
     case VK_RIGHT:
     case VK_END:
     case VK_HOME:
     case VK_PRIOR:
     case VK_NEXT:
       StGWObDoKey(GETWINOBJECT(hWnd),
		  (unsigned char) wParam,
		   GetKeyState(VK_SHIFT),
		   GetKeyState(VK_CONTROL));
       break;
     }
     return(0);
#endif
  case WM_MOUSEMOVE:
    gwinfo = GETGWINFO(hWnd);
    SetCursor(get_cursor(gwinfo->cursor));
    if (! MouseCaptured) {
      gwinfo->mouse_x = LOWORD(lParam);
      gwinfo->mouse_y = HIWORD(lParam);
      StGWObDoMouse(GETWINOBJECT(hWnd),
		    gwinfo->mouse_x + gwinfo->view_h,
		    gwinfo->mouse_y + gwinfo->view_v,
		    MouseMove,
		    (MouseClickModifier) 0);
    }
    else if (MouseWnd == hWnd && xldebug <= 0) { // ### move to WhileButtonDown?
      if (gwinfo->mouse_x != LOWORD(lParam)
	  || gwinfo->mouse_y != HIWORD(lParam)
	  || ! MouseMotionOnly) {
	gwinfo->mouse_x = LOWORD(lParam);
	gwinfo->mouse_y = HIWORD(lParam);
	if (MouseAction != NULL)
	  (*MouseAction)(hWnd,
			 gwinfo->mouse_x + gwinfo->view_h,
			 gwinfo->mouse_y + gwinfo->view_v);
      }
    }
    return(0);
  case WM_LBUTTONDOWN:
  case WM_MBUTTONDOWN:
  case WM_RBUTTONDOWN:
    {
      int mods = 2 * ((MK_CONTROL & wParam) ? 1 : 0) + ((MK_SHIFT & wParam) ? 1 : 0);
      gwinfo = GETGWINFO(hWnd);
      gwinfo->mouse_x = LOWORD(lParam);
      gwinfo->mouse_y = HIWORD(lParam);
      ReleaseMouse();
      MouseWnd = hWnd; // to indicate button is down
      StGWObDoMouse(GETWINOBJECT(hWnd),
		    gwinfo->mouse_x + gwinfo->view_h,
		    gwinfo->mouse_y + gwinfo->view_v,
		    MouseClick,
		    (MouseClickModifier) mods);

    }
    return(0);
  case WM_LBUTTONUP:
  case WM_MBUTTONUP:
  case WM_RBUTTONUP:
    ReleaseMouse();
    return(0);
  case WM_PAINT:
    {
      PAINTSTRUCT ps;
      int left, top, width, height;

      gwinfo = GETGWINFO(hWnd);
      BeginPaint(hWnd, &ps);
      EndPaint(hWnd, &ps);
      if (! gwinfo->initialized) {
	gwinfo->initialized = TRUE;
	SetHardwareState(gwinfo);
      }
      StGWGetViewRect(gwinfo, &left, &top, &width, &height);
      StGWStartBuffering(gwinfo);
      StGWObRedraw(GETWINOBJECT(hWnd));
      StGWBufferToScreen(gwinfo, left, top, width, height);
      return(0);
    }
  case WM_MDIACTIVATE:
    {
      LVAL menu = slot_value(GETWINOBJECT(hWnd), s_menu);
      activating = GET_WM_MDIACTIVATE_FACTIVATE(hWnd, wParam, lParam);
      if (menu != NIL) {
	if (activating) send_callback_message(menu, sk_install);
	else send_callback_message(menu, sk_remove);
      }
    }
    return(0);
  case WM_NCACTIVATE:
    ReleaseMouse();
    return(DefMDIChildProc(hWnd, message, wParam, lParam));
  case WM_MOUSEACTIVATE:
    if (activating && HTCLIENT == LOWORD(lParam))
      return MA_ACTIVATEANDEAT;
    else
      return MA_ACTIVATE;
  case WM_SETCURSOR:
    activating = FALSE;
    break;
  case WM_CLOSE:
    send_callback_message(GETWINOBJECT(hWnd), sk_close);
    return(0);
  case WM_DESTROY:
    if (! Exiting) {
      gwinfo = GETGWINFO(hWnd);
      if (gwinfo) {
	if (IViewInternalIsLinked(hWnd))
	  IViewUnlinkWindow(hWnd);
	StGWObDoClobber(gwinfo->Object);
	if (gwinfo->FreeMem)
	  (*gwinfo->FreeMem)(hWnd);
	gwinfo->window = NULL;
      }
    }
    return(0);
  }
  return(DefMDIChildProc(hWnd, message, wParam, lParam));
}

void StGWWhileButtonDown(StGWWinInfo *gwinfo, void (*action)(), int motionOnly)
{
  MSG msg;

  if (! gwinfo || ! gwinfo->window) return;
  if (! MouseWnd) return; // checks if mouse is down
  MouseAction = action;
  MouseMotionOnly = motionOnly;
  CaptureMouse((HWND) gwinfo->window);
  while (MouseCaptured) {
    if (PeekMessage(&msg, (HWND) gwinfo->window, 0, 0, PM_REMOVE)) {
      if(! TranslateMDISysAccel(hWndClient, &msg)
	 && ! TranslateAccelerator(hWndFrame, hAccel, &msg)) {
	TTYFlushOutput();
	TranslateMessage(&msg);
	DispatchMessage(&msg);
      }
    }
    else if (! MouseMotionOnly && MouseAction && MouseCaptured) {
      (*MouseAction)(gwinfo->window,
		     gwinfo->mouse_x + gwinfo->view_h,
		     gwinfo->mouse_y + gwinfo->view_v);
      TTYFlushOutput();
    }
  }
  ReleaseMouse(); // should have heppened already
}

void MSWResetGraphics(void)
{
  ReleaseMouse();
  StGWResetBuffer();
}

/**************************************************************************/
/**                                                                      **/
/**                        Idle Action Functions                         **/
/**                                                                      **/
/**************************************************************************/

int MSWAnyIdleActions(void)
{
  LVAL next, object;
  StGWWinInfo *gwinfo;

  for (next = getvalue(s_hardware_objects); consp(next); next = cdr(next)) {
    if (valid_iview_window_address(car(next))) {
      object = car(cdr(cdr(car(next))));
      gwinfo = (StGWWinInfo *) StGWObWinInfo(object);
      if (gwinfo && gwinfo->window
	  && gwinfo->idleOn && ! IsIconic((HWND) gwinfo->window)) {
	return TRUE;
      }
    }
  }
  return FALSE;
}

void MSWDoIdleActions(void)
{
  LVAL next, object;
  StGWWinInfo *gwinfo;

  for (next = getvalue(s_hardware_objects); consp(next); next = cdr(next)) {
    if (valid_iview_window_address(car(next))) {
      object = car(cdr(cdr(car(next))));
      gwinfo = (StGWWinInfo *) StGWObWinInfo(object);
      if (gwinfo && gwinfo->window
	  && gwinfo->idleOn && ! IsIconic((HWND) gwinfo->window)) {
	StGWObDoIdle(object);
      }
    }
  }
}

int StGWIdleOn(StGWWinInfo *gwinfo)
{
  return(gwinfo ? gwinfo->idleOn : FALSE);
}

void StGWSetIdleOn(StGWWinInfo *gwinfo, int on)
{
  if (gwinfo) gwinfo->idleOn = on;
}

/**************************************************************************/
/**                                                                      **/
/**                   Scrolling and Canvas Functions                     **/
/**                                                                      **/
/**************************************************************************/

//**** not sure this is needed -- it probably duplicates stuff
static void ResetDCClipRect(HDC hDC, StGWWinInfo *gwinfo)
{
  if (gwinfo->clipped) {
    HRGN rgn;
    rgn = CreateRectRgn(gwinfo->clip_rect.left - gwinfo->view_h,
                        gwinfo->clip_rect.top - gwinfo->view_v,
		        gwinfo->clip_rect.right - gwinfo->view_h,
		        gwinfo->clip_rect.bottom) - gwinfo->view_v;
    SelectClipRgn(hDC, rgn);
    DeleteObject(rgn);
  }
}

int StGWCanvasWidth(StGWWinInfo *gwinfo) { return (gwinfo ? gwinfo->canvasWidth : 0); }
int StGWCanvasHeight(StGWWinInfo *gwinfo) { return (gwinfo ? gwinfo->canvasHeight : 0); }
int StGWHasVscroll(StGWWinInfo *gwinfo) { return (gwinfo ? gwinfo->hasVscroll : FALSE); }
int StGWHasHscroll(StGWWinInfo *gwinfo) { return (gwinfo ? gwinfo->hasHscroll : FALSE); }

void StGWSetHasVscroll(StGWWinInfo *gwinfo, int has, int size)
{
  RECT r;
  HWND w;
  int height;

  if (gwinfo && (w = gwinfo->window) != 0) {
    GetClientRect((HWND) w, &r);
    height = r.bottom - r.top;
    gwinfo->hasVscroll = has;
    gwinfo->canvasHeight = has ? size : height;
    SetScrollRange((HWND) w, SB_VERT, 0, gwinfo->canvasHeight - height, TRUE);
    StGWSetScroll(gwinfo, gwinfo->view_h, gwinfo->view_v, TRUE);
  }
}

void StGWSetHasHscroll(StGWWinInfo *gwinfo, int has, int size)
{
  RECT r;
  HWND w;
  int width;

  if (gwinfo && (w = gwinfo->window) != 0) {
    GetClientRect((HWND) w, &r);
    width = r.right - r.left;
    gwinfo->hasHscroll = has;
    gwinfo->canvasWidth = has ? size : width;
    SetScrollRange((HWND) w, SB_HORZ, 0, gwinfo->canvasWidth - width, TRUE);
    StGWSetScroll(gwinfo, gwinfo->view_h, gwinfo->view_v, TRUE);
  }
}

void StGWSetScroll(StGWWinInfo *gwinfo, int h, int v, int move)
#pragma argsused gwinfo h v
{
  RECT r;
  HWND w;
  int width, height, hmax, vmax;
  HDC hDC;

  if (gwinfo && (w = gwinfo->window) != 0) {
    GetClientRect((HWND) w, &r);
    width = r.right - r.left;
    height = r.bottom - r.top;
    hmax = gwinfo->canvasWidth - width;
    vmax = gwinfo->canvasHeight - height;
    if (hmax < 0) hmax = 0;
    if (vmax < 0) vmax = 0;
    if (h < 0) h = 0; if (h > hmax) h = hmax;
    if (v < 0) v = 0; if (v > vmax) v = vmax;
    gwinfo->view_h = (gwinfo->hasHscroll) ? h : 0;
    gwinfo->view_v = (gwinfo->hasVscroll) ? v : 0;
    if (gwinfo->view_h != GetScrollPos((HWND) w, SB_HORZ))
      SetScrollPos((HWND) w, SB_HORZ, gwinfo->view_h, TRUE);
    if (gwinfo->view_v != GetScrollPos((HWND) w, SB_VERT))
      SetScrollPos((HWND) w, SB_VERT, gwinfo->view_v, TRUE);
    hDC = GET_DC(w);
    SetViewportOrgEx(hDC, -gwinfo->view_h, -gwinfo->view_v, NULL);
    ResetDCClipRect(hDC, gwinfo);
    RELEASE_DC(w, hDC);
    if (currentDC) {
      SetViewportOrgEx(currentDC, -gwinfo->view_h, -gwinfo->view_v, NULL);
      ResetDCClipRect(currentDC, gwinfo);
    }
    InvalidateRect((HWND) w, &r, FALSE);
  }
}

void StGWGetScroll(StGWWinInfo *gwinfo, int *h, int *v)
{
  if (gwinfo) {
    if (h) *h = gwinfo->view_h;
    if (v) *v = gwinfo->view_v;
  }
}

void StGWSetHscrollIncs(StGWWinInfo *gwinfo, int inc, int pageInc)
{
  if (gwinfo) {
    gwinfo->h_scroll_inc[0] = inc;
    gwinfo->h_scroll_inc[1] = pageInc;
  }
}

void StGWGetHscrollIncs(StGWWinInfo *gwinfo, int *inc, int *pageInc)
{
  if (gwinfo) {
    if (inc) *inc = gwinfo->h_scroll_inc[0];
    if (pageInc) *pageInc = gwinfo->h_scroll_inc[1];
  }
}

void StGWSetVscrollIncs(StGWWinInfo *gwinfo, int inc, int pageInc)
{
  if (gwinfo) {
    gwinfo->v_scroll_inc[0] = inc;
    gwinfo->v_scroll_inc[1] = pageInc;
  }
}

void StGWGetVscrollIncs(StGWWinInfo *gwinfo, int *inc, int *pageInc)
{
  if (gwinfo) {
    if (inc) *inc = gwinfo->v_scroll_inc[0];
    if (pageInc) *pageInc = gwinfo->v_scroll_inc[1];
  }
}

void StGWGetViewRect(StGWWinInfo *gwinfo, int *left, int *top, int *width, int *height)
{
  HWND w;
  RECT r;

  if (gwinfo && (w = gwinfo->window) != 0) {
    GetClientRect((HWND) w, &r);
    if (left) *left = r.left + gwinfo->view_h;
    if (top) *top = r.top + gwinfo->view_v;
    if (width) *width = r.right - r.left;
    if (height) *height = r.bottom - r.top;
  }
  else {
    if (left) *left = 0;
    if (top) *top = 0;
    if (width) *width = 1;
    if (height) *height = 1;
  }
}

/**************************************************************************/
/**                                                                      **/
/**                            Cursor Functions                          **/
/**                                                                      **/
/**************************************************************************/

#define NumBasicCursors 9
static int NumCursors;

typedef struct {
  HCURSOR curs;
  int created;
  long refcon;
} cursor_entry;

static cursor_entry *curstab;

static HCURSOR get_cursor(unsigned int index)
{
  if (index < NumCursors) return(curstab[index].curs);
  else return(hArrowCursor);
}

void set_gc_cursor(int set)
{
  if (set) hSaveCursor = SetCursor(hGCCursor);
  else SetCursor(hSaveCursor);
}

static void init_msw_cursors(void)
{
  NumCursors = NumBasicCursors;
  curstab = (cursor_entry *) StCalloc(NumCursors, sizeof(cursor_entry));
  curstab[ARROW_CURSOR].curs = hArrowCursor;
  curstab[WATCH_CURSOR].curs = hWaitCursor;
  curstab[CROSS_CURSOR].curs = hCrossCursor;
  curstab[BRUSH_CURSOR].curs = hBrushCursor;
  curstab[HAND_CURSOR].curs = hHandCursor;
  curstab[FINGER_CURSOR].curs = hFingerCursor;
  curstab[HOUR_GLASS_CURSOR].curs = hWaitCursor;
  curstab[TRASH_BAG_CURSOR].curs = hGCCursor;
  curstab[TRASH_CAN_CURSOR].curs = hGCCursor;
}

void StGWSetCursRefCon(unsigned int index, long rc)
{
  if (index < NumCursors) curstab[index].refcon = rc;
}

long StGWGetCursRefCon(unsigned int index)
{
  if (index < NumCursors) return(curstab[index].refcon);
  else return(0L);
}

void StGWSetCursor(StGWWinInfo *gwinfo, int cursor)
{
  if (gwinfo) gwinfo->cursor = cursor;
}

int StGWCursor(StGWWinInfo *gwinfo)
{
  return(gwinfo ? gwinfo->cursor : 0);
}

static int get_free_cursor_index(void)
{
  char *temp;
  int index;

  for (index = 0;
       index < NumCursors && curstab[index].curs != NULL;
       index++);
  if (index >= NumCursors) {
    temp = realloc((char *) curstab, (NumCursors + 1) * sizeof(cursor_entry));
    if (temp == NULL) return(-1);
    curstab = (cursor_entry *) temp;
    NumCursors++;
    curstab[index].curs = NULL;
    curstab[index].created = FALSE;
    curstab[index].refcon = 0L;
  }
  return(index);
}

static char *make_bitmap_bits(char *image,
			      int width, int height,
			      int cwidth, int cheight,
			      int mask)
{
  HBITMAP hbm = 0, oldbm;
  BITMAP bm;
  HDC bmDC, hDC;
  char *bits = NULL;
  int i, j, size;

  hDC = GetDC(hWndFrame);
  bmDC = CreateCompatibleDC(hDC);
  ReleaseDC(hWndFrame, hDC);

  if (bmDC) {
    hbm = CreateBitmap(cwidth, cheight, 1, 1, NULL);
    if (hbm) {
      oldbm = SelectObject(bmDC, hbm);
      PatBlt(bmDC, 0, 0, cwidth, cheight, WHITENESS);
      if (image) {
	PatBlt(bmDC, 0, 0, cwidth, cheight, mask ? WHITENESS : BLACKNESS);
	PatBlt(bmDC, 0, 0, width, height, WHITENESS);
	for (i = 0; i < height && i < cheight; i++)
	  for (j = 0; j < width && j < cwidth; j++)
	    if (image[i * width + j])
	      SetPixel(bmDC, j, i, 0);
      }
      else PatBlt(bmDC, 0, 0, width, height, BLACKNESS);

      GetObject(hbm, sizeof(BITMAP), (LPSTR) &bm);
      size = (int) bm.bmWidthBytes * bm.bmHeight * bm.bmPlanes;
      bits = StCalloc(size, 1);
      if (bits) GetBitmapBits(hbm, size, bits);
      SelectObject(bmDC, oldbm);
      DeleteObject(hbm);
    }
    DeleteDC(bmDC);
  }
  return(bits);
}

static void free_bitmap_bits(char *bits)
{
  if (bits) StFree(bits);
}

int StGWMakeCursor(int n, char *image, char *mask, int h, int v, long refcon)
{
  int index, cwidth, cheight, i;
  char *imageBits, *maskBits;
  HCURSOR hc = 0;

  index = get_free_cursor_index();
  if (index < 0) return(-1);

  cwidth = GetSystemMetrics(SM_CXCURSOR);
  cheight = GetSystemMetrics(SM_CYCURSOR);
  if (mask && image)
    for (i = 0; i < n * n; i++)
      if (! mask[i]) image[i] = 1;
  imageBits = make_bitmap_bits(image, n, n, cwidth, cheight, FALSE);
  maskBits = make_bitmap_bits(mask, n, n, cwidth, cheight, TRUE);
  if (imageBits && maskBits)
    hc = CreateCursor(hInst, h, v, cwidth, cheight, maskBits, imageBits);
  free_bitmap_bits(imageBits);
  free_bitmap_bits(maskBits);
  if (! hc) return(-1);

  curstab[index].curs = hc;
  curstab[index].created = TRUE;
  curstab[index].refcon = refcon;
  return(index);
}

int StGWMakeResCursor(char *name, int num, long refcon)
{
  int index;
  char *cname = NULL;
  long resnum = 0;
  LVAL arg;

  if (name) return(-1);
  else if (! num) return(-1);
  else {
    arg = xlgetarg();
    if (stringp(arg)) cname = (char *) getstring(arg);
    else if (fixp(arg)) resnum = getfixnum(arg);
    else return(-1);

    index = get_free_cursor_index();
    if (index < 0) return(-1);

    curstab[index].curs = LoadCursor((HINSTANCE) num, cname ? cname : MAKEINTRESOURCE(resnum));
    if (curstab[index].curs) curstab[index].refcon = refcon;
    else return(-1);
  }
  return(index);
}

void StGWFreeCursor(unsigned int index)
{
  if (index < NumCursors && index >= NumBasicCursors) {
    if (curstab[index].curs != NULL && curstab[index].created)
      DestroyCursor(curstab[index].curs);
    curstab[index].curs = 0;
    curstab[index].refcon = 0;
    curstab[index].created = FALSE;
  }
  else xlfail("can't free standard cursor");
}

/**************************************************************************/
/**                                                                      **/
/**                            Color Functions                           **/
/**                                                                      **/
/**************************************************************************/

#define NumBasicColors 8
#define NumRGBColors 256
# define CMULT 255

static int NumColors;

typedef struct {
  unsigned long rgb;
  long refcon;
} ctab_entry;

static ctab_entry *ctable;

static void init_msw_colors(void)
{
  NumColors = NumBasicColors;
  if (StScreenHasColor()) NumColors += NumRGBColors;
  ctable = (ctab_entry *) StCalloc(NumColors, sizeof(ctab_entry));
  
  ctable[0].rgb = RGB(255, 255, 255); // white
  ctable[1].rgb = RGB(  0,   0,   0); // black
  ctable[2].rgb = RGB(255,   0,   0); // red
  ctable[3].rgb = RGB(  0, 255,   0); // green
  ctable[4].rgb = RGB(  0,   0, 255); // blue
  ctable[5].rgb = RGB(  0, 255, 255); // cyan
  ctable[6].rgb = RGB(255,   0, 255); // magenta
  ctable[7].rgb = RGB(255, 255,   0); // yellow
}

void StGWSetColRefCon(unsigned int index, long rc)
{
  if (index < NumColors) ctable[index].refcon = rc;
}

long StGWGetColRefCon(unsigned int index)
{
  if (index < NumColors) return(ctable[index].refcon);
  else return(0L);
}

int StGWMakeColor(double red, double green, double blue, long refcon)
{
  int index;
  
  for (index = NumBasicColors; 
       index < NumColors && StGWGetColRefCon(index) != 0;
       index++);
  if (index >= NumColors) return(-1);
  else {
    StGWSetColRefCon(index, refcon);
    ctable[index].rgb = RGB(CMULT * red, CMULT * green, CMULT * blue);
    return(index);
  }
}

void StGWFreeColor(unsigned int index)
{
  if (index < NumColors && index >= NumBasicColors)
    StGWSetColRefCon(index, 0);
  else xlfail("can't free standard color");
}

static unsigned long get_color(unsigned int index)
{
  if (index < NumColors) return(ctable[index].rgb);
  else return(ctable[1].rgb);
}

int StGWUseColor(StGWWinInfo *gwinfo)
{
  return(gwinfo ? gwinfo->use_color : FALSE);
}

ColorCode StGWDrawColor(StGWWinInfo *gwinfo)
{
  return ((ColorCode) (gwinfo ? gwinfo->drawColor : 1));
}

ColorCode StGWBackColor(StGWWinInfo *gwinfo)
{
  return ((ColorCode) (gwinfo ? gwinfo->backColor : 0));
}

void StGWSetUseColor(StGWWinInfo *gwinfo, int use)
{
  if (gwinfo) {
    gwinfo->use_color = use;
    if (! colorBuffBits) gwinfo->use_color = FALSE;
  }
}

void StGWSetDrawColor(StGWWinInfo *gwinfo, ColorCode color)
{
  HDC hDC;

  if (gwinfo) {
    if (color < NumColors) gwinfo->drawColor = color;
    gwinfo->drawPen.lopnColor = get_color(color);
    if (gwinfo->window) {
      hDC = GetDC((HWND) gwinfo->window);
      SetTextColor(hDC, get_color(color));
      ReleaseDC((HWND) gwinfo->window, hDC);
      if (currentDC) SetTextColor(currentDC, get_color(color));
    }
  }
}

void StGWSetBackColor(StGWWinInfo *gwinfo, ColorCode color)
{
  HDC hDC;

  if (gwinfo) {
    if (color < NumColors) gwinfo->backColor = color;
    if (gwinfo->window) {
      hDC = GetDC((HWND) gwinfo->window);
      SetBkColor(hDC, get_color(color));
      ReleaseDC((HWND) gwinfo->window, hDC);
      if (currentDC) SetBkColor(currentDC, get_color(color));
    }
  }
}

/**************************************************************************/
/**                                                                      **/
/**                        Drawing State Functions                       **/
/**                                                                      **/
/**************************************************************************/

int StGWDrawMode(StGWWinInfo *gwinfo)
{
  return(gwinfo ? gwinfo->drawMode : 0);
}

int StGWLineType(StGWWinInfo *gwinfo)
{
  return(gwinfo ? gwinfo->lineType : 0);
}

void StGWGetLineWidth(StGWWinInfo *gwinfo, int *width)
{
  if (gwinfo && width) *width = gwinfo->lineWidth;
}

void StGWSetDrawMode(StGWWinInfo *gwinfo, int mode)
{
  HDC hDC;

  if (gwinfo) {
    gwinfo->drawMode = mode;
    if (gwinfo->window) {
      hDC = GetDC((HWND) gwinfo->window);
      SetROP2(hDC, gwinfo->drawMode ? R2_NOT : R2_COPYPEN);
      ReleaseDC((HWND) gwinfo->window, hDC);
      if (currentDC)
	SetROP2(currentDC, gwinfo->drawMode ? R2_NOT : R2_COPYPEN);
    }
  }
}

void StGWSetLineType(StGWWinInfo *gwinfo, int type)
{
  if (gwinfo) {
    gwinfo->lineType = type ? 1 : 0;
    gwinfo->drawPen.lopnStyle = type ? PS_DOT : PS_SOLID;
  }
}

void StGWSetLineWidth(StGWWinInfo *gwinfo, int width)
{
  if (gwinfo) {
    gwinfo->lineWidth = (width > 0) ? width : 1;
    gwinfo->rect_offset = width / 2;
    gwinfo->drawPen.lopnWidth.x = (width > 1) ? width : 1;
  }
}

int StGWGetClipRect(StGWWinInfo *gwinfo,
		    int *pleft, int *ptop, int *pwidth, int *pheight)
{
  if (gwinfo == NULL) return(FALSE);
  if (gwinfo->clipped) {
    if (pleft) *pleft = gwinfo->clip_rect.left;
    if (ptop) *ptop = gwinfo->clip_rect.top;
    if (pwidth) *pwidth = gwinfo->clip_rect.right - gwinfo->clip_rect.left;
    if (pheight) *pheight = gwinfo->clip_rect.bottom - gwinfo->clip_rect.top;
  }
  return(gwinfo->clipped);
}

// ### fix
void StGWSetClipRect(StGWWinInfo *gwinfo, int clipping,
		     int left, int top, int width, int height)
{
  HDC hDC;
  HRGN rgn;
  int right, bottom;

  if (gwinfo) {
    if (clipping) {
      right = left + width;
      bottom = top + height;
    }
    else {
      left = 0;
      top = 0;
      right = gwinfo->canvasWidth;
      right = gwinfo->canvasHeight;
    }
    gwinfo->clipped = clipping;
    gwinfo->clip_rect.left = left;
    gwinfo->clip_rect.top = top;
    gwinfo->clip_rect.right = right;
    gwinfo->clip_rect.bottom = bottom;
    left -= gwinfo->view_h;
    top -= gwinfo->view_v;
    right -= gwinfo->view_h;
    bottom -= gwinfo->view_v;
    hDC = GetDC((HWND) gwinfo->window);
    rgn = CreateRectRgn(left, top, right, bottom); /***** error check */
    SelectClipRgn(hDC, rgn);
    ReleaseDC((HWND) gwinfo->window, hDC);
    if (currentDC) SelectClipRgn(currentDC, rgn);
    DeleteObject(rgn);
  }
}

/**************************************************************************/
/**                                                                      **/
/**                           Buffer Functions                           **/
/**                                                                      **/
/**************************************************************************/

void init_msw_buffer(void)
{
  HDC hDC;
  int width, height;

  StGetScreenSize(&width, &height);
  hDC = GetDC(hWndFrame);
  if (StScreenHasColor() && ! xlisp_mono) {
    colorBuffBits = CreateCompatibleBitmap(hDC, width, height);
    if (! colorBuffBits) {
      WarningBox("Can't allocate color buffer");
    }
  }
  ReleaseDC(hWndFrame, hDC);
  monoBuffBits = CreateBitmap(width, height, 1, 1, NULL);
  if (! monoBuffBits) {
    WarningBox("Can't allocate monochrome buffer");
    xexit();
  }
}

static void init_dc(StGWWinInfo *gwinfo, HDC bufferDC, int buffer)
{
  HBRUSH hbr;
  RECT r;
  SetBkMode(bufferDC, TRANSPARENT);
  SetBkColor(bufferDC, get_color(gwinfo->backColor));
  SetTextColor(bufferDC, get_color(gwinfo->drawColor));
  SelectObject(bufferDC, hGraphFont);
  SetROP2(bufferDC, gwinfo->drawMode ? R2_NOT : R2_COPYPEN);
  SetPolyFillMode(bufferDC, WINDING);
  if (buffer)
    SetViewportOrgEx(bufferDC, -gwinfo->view_h, -gwinfo->view_v, NULL);
  ResetDCClipRect(bufferDC, gwinfo);
  if (buffer) {
    GetClientRect((HWND) gwinfo->window, &r);
    r.left += gwinfo->view_h; r.top += gwinfo->view_v;
    r.right += gwinfo->view_h; r.bottom += gwinfo->view_v;
  }
  else {
    r.left = r.top = 0;
    r.right = gwinfo->canvasWidth;
    r.bottom = gwinfo->canvasHeight;
  }
  hbr = GET_ERASE_BRUSH(gwinfo);
  FillRect(bufferDC, &r, hbr);
  RELEASE_BRUSH(hbr);
}

void StGWStartBuffering(StGWWinInfo *gwinfo)
{
  HDC hDC;

  if (gwinfo && gwinfo->window) {
    if (! buffering) {
      StGWResetBuffer();
      buffering = TRUE;
      hDC = GetDC((HWND) gwinfo->window);
      bufferDC = CreateCompatibleDC(hDC);
      ReleaseDC((HWND) gwinfo->window, hDC);
      SelectObject(bufferDC, (gwinfo->use_color && colorBuffBits) ? colorBuffBits : monoBuffBits);
      init_dc(gwinfo, bufferDC, TRUE);
      currentDC = bufferDC;
    }
    bufflevel++;
  }
}

void StGWBufferToScreen(StGWWinInfo *gwinfo,
			int left, int top, int width, int height)
{
  HDC hDC;

  if (gwinfo && gwinfo->window) {
    if (--bufflevel <= 0 && buffering) {
      hDC = GetDC((HWND) gwinfo->window);
      if (! gwinfo->use_color) {
	SetBkColor(hDC, get_color(0));
	SetTextColor(hDC, get_color(1));
      }
      BitBlt(hDC, left, top, width, height, currentDC, left, top, SRCCOPY);
      if (! gwinfo->use_color) {
	SetBkColor(hDC, get_color(gwinfo->backColor));
	SetTextColor(hDC, get_color(gwinfo->drawColor));
      }
      ReleaseDC((HWND) gwinfo->window, hDC);
      StGWResetBuffer();
    }
  }
}

void StGWResetBuffer(void)
{
  bufflevel = 0;
  buffering = FALSE;
  currentDC = 0;
  if (bufferDC) {
    DeleteDC(bufferDC);
    bufferDC = 0;
  }
}

// ### fix
void StGWDumpImage(StGWWinInfo *gwinfo, FILEP file, double scale)
{
  xlfail("not supported yet");
}

#ifdef WIN32
static int GetPrinterDC(HDC *pdc)
{
  static PRINTDLG pd = { sizeof (PRINTDLG) } ;

  /* Show the Print dialog box and get printer DC */
  pd.Flags = PD_RETURNDC | PD_NOPAGENUMS | PD_NOSELECTION
    | PD_USEDEVMODECOPIESANDCOLLATE;
  if (!PrintDlg (&pd))
    return FALSE;
  else {
    *pdc = pd.hDC;
    return TRUE;
  }
}

static int PrintEMF(HDC printerDC, HENHMETAFILE hemf)
{
  static DOCINFO di = { sizeof (DOCINFO), TEXT ("EmfView: Printing") } ;
  ENHMETAHEADER emh;
  int play_success, print_success, width, height, dwidth, dheight;
  RECT r;
  double hScale, vScale, scale;

  /* get the metafile header */
  GetEnhMetaFileHeader(hemf, sizeof(emh), &emh);
  
  /* Calculate the scale factor */
  dwidth = GetDeviceCaps(printerDC, HORZRES);
  width = emh.rclBounds.right - emh.rclBounds.left; 
  if (width > 0) hScale = (double) dwidth / width;
  else hScale = 1.0;
  dheight = GetDeviceCaps(printerDC, VERTRES);
  height = emh.rclBounds.bottom - emh.rclBounds.top;
  if (height > 0) vScale = (double) dheight/ height;
  else vScale = 1.0;
  scale = hScale > vScale ? vScale : hScale;
  if (scale > 1.0) scale = floor(scale);

  /* Set the display rectangle */
  /***** add margins? */
  width = scale * width;
  height = scale * height;
  r.left = (dwidth - width) / 2;
  r.top = (dheight - height) / 2;
  r.right = r.left + width;
  r.bottom = r.top + height;

  /* Play the EMF to the printer */
  /**** save current cursor and set up wait cursor */
  print_success = FALSE;
  if ((StartDoc(printerDC, &di) > 0) && (StartPage(printerDC) > 0)) {
    play_success = PlayEnhMetaFile (printerDC, hemf, &r);
    if (EndPage(printerDC) > 0) {
      print_success = TRUE;
      EndDoc(printerDC) ;
    }
  }
  /**** restore original cursor */
  return play_success;
}
#endif /* WIN32 */

static void StGWPrint(StGWWinInfo *gwinfo)
{
#ifdef WIN32
  /***** need to figure out how to disable for win32s */
  if (gwinfo && gwinfo->window) {
    HDC printerDC;
    HENHMETAFILE hEMF;
    int success;

    /* Show the Print dialog box and get printer DC */
    if (! GetPrinterDC(&printerDC))
      return;
    if (printerDC == NULL)
      xlfail("Can't get printer DC");
  
    /* Draw the graph into an enhanced metafile */
    {
      CONTEXT cntxt, *target;
      int jumping, mask;
      LVAL val;

      xlbegin(&cntxt,CF_UNWIND,NIL);
      if (XL_SETJMP(cntxt.c_jmpbuf)) {
	target = xltarget;
	mask = xlmask;
	val = xlvalue;
	jumping = TRUE;
      }
      else {
	target = NULL;
	mask = 0;
	val = NIL;
	jumping = FALSE;

	/* protected code */
	if (buffering) xlfail("already buffering");
	StGWResetBuffer();
	bufferDC = CreateEnhMetaFile(NULL, NULL, NULL, NULL);
	if (bufferDC == NULL) xlfail("can't create meta file");
	init_dc(gwinfo, bufferDC, FALSE);
	currentDC = bufferDC;
	buffering = TRUE;
	bufflevel++;
	StGWObRedraw(GETWINOBJECT(gwinfo->window));
      }
      xlend(&cntxt);

      /* cleanup code */
      if (bufferDC != NULL) hEMF = CloseEnhMetaFile(bufferDC);
      bufferDC = 0;
      StGWResetBuffer();
      if (jumping) DeleteDC(printerDC);
      if (hEMF == NULL) xlfail("failed to close metafile properly");
      if (jumping) xljump(target, mask, val);
    }

    success = PrintEMF(printerDC, hEMF);
    DeleteDC (printerDC);
    DeleteEnhMetaFile(hEMF);
    if (! success) xlfail("printing failed");
  }
#endif /* WIN32 */
}

/**************************************************************************/
/**                                                                      **/
/**                       Basic Drawing Functions                        **/
/**                                                                      **/
/**************************************************************************/

void StGWDrawPoint(StGWWinInfo *gwinfo, int x, int y)
{
  HWND w;
  HDC hDC;
  HPEN oldPen;
  int oldWidth;

  if (gwinfo && (w = gwinfo->window) != 0) {
    oldWidth = gwinfo->drawPen.lopnWidth.x;
    gwinfo->drawPen.lopnWidth.x = 1;
    hDC = GET_DC(w);
    oldPen = SET_PEN(hDC, gwinfo);
    MoveToEx(hDC, x, y, NULL);
    LineTo(hDC, x + 1, y + 1);
    RESTORE_PEN(hDC, oldPen);
    RELEASE_DC(w, hDC);
    gwinfo->drawPen.lopnWidth.x = oldWidth;
  }
}

void StGWDrawLine(StGWWinInfo *gwinfo, int x1, int y1, int x2, int y2)
{
  HWND w;
  HDC hDC;
  HPEN oldPen;

  if (gwinfo && (w = gwinfo->window) != 0) {
    hDC = GET_DC(w);
    oldPen = SET_PEN(hDC, gwinfo);
    MoveToEx(hDC, x1, y1, NULL);
    LineTo(hDC, x2, y2);
    RESTORE_PEN(hDC, oldPen);
    RELEASE_DC(w, hDC);
  }
}

void StGWFrameRect(StGWWinInfo *gwinfo, int left, int top, int width, int height)
{
  HDC hDC;
  HPEN oldPen;
  HBRUSH oldBrush;

  if (gwinfo && gwinfo->window) {
    hDC = GET_DC(gwinfo->window);
    oldPen = SET_PEN(hDC, gwinfo);
    oldBrush = SelectObject(hDC, GetStockObject(NULL_BRUSH));
    if (gwinfo->lineWidth != 1) {
      left += gwinfo->rect_offset;
      top += gwinfo->rect_offset;
      width += 1 - gwinfo->lineWidth;
      height += 1 - gwinfo->lineWidth;
    }
    Rectangle(hDC, left, top, left + width, top + height);
    SelectObject(hDC, oldBrush);
    RESTORE_PEN(hDC, oldPen);
    RELEASE_DC(gwinfo->window, hDC);
  }
}

static void fill_rect(StGWWinInfo *gwinfo, int which,
		      int left, int top, int width, int height)
{
  HDC hDC;
  RECT r;
  HBRUSH hbr;

  if (gwinfo && gwinfo->window) {
    r.left = left; r.top = top;
    r.right = left + width; r.bottom = top + height;
    hDC = GET_DC(gwinfo->window);
    hbr = (which == 'P') ? GET_DRAW_BRUSH(gwinfo) : GET_ERASE_BRUSH(gwinfo);
    FillRect(hDC, &r, hbr);
    RELEASE_BRUSH(hbr);
    RELEASE_DC(gwinfo->window, hDC);
  }
}

void StGWPaintRect(StGWWinInfo *gwinfo,
		   int left, int top, int width, int height)
{
  fill_rect(gwinfo, 'P', left, top, width, height);
}

void StGWEraseRect(StGWWinInfo *gwinfo,
		   int left, int top, int width, int height)
{
  fill_rect(gwinfo, 'E', left, top, width, height);
}

static void draw_oval(StGWWinInfo *gwinfo, int which,
		      int left, int top, int width, int height)
{
  HDC hDC;
  HPEN oldPen;
  HBRUSH oldBrush;

  if (gwinfo && gwinfo->window) {
    hDC = GET_DC(gwinfo->window);
    if (which == 'F') {
      oldPen = SET_PEN(hDC, gwinfo);
      oldBrush = SelectObject(hDC, GetStockObject(NULL_BRUSH));
      if (gwinfo->lineWidth != 1) {
	left += gwinfo->rect_offset;
	top += gwinfo->rect_offset;
	width += 1 - gwinfo->lineWidth;
	height += 1 - gwinfo->lineWidth;
      }
    }
    else {
      oldPen = SelectObject(hDC, GetStockObject(NULL_PEN));
      oldBrush = SelectObject(hDC, (which == 'P') ? GET_DRAW_BRUSH(gwinfo) : GET_ERASE_BRUSH(gwinfo));
      width++;  // make up for some deficiency in filling
      height++; // make up for some deficiency in filling
    }
    Ellipse(hDC, left, top, left + width, top + height);
    if (which == 'F') {
      SelectObject(hDC, oldBrush);
      RESTORE_PEN(hDC, oldPen);
    }
    else {
      RESTORE_BRUSH(hDC, oldBrush);
      SelectObject(hDC, oldPen);
    }
    RELEASE_DC(gwinfo->window, hDC);
  }
}

void StGWFrameOval(StGWWinInfo *gwinfo,
		   int left, int top, int width, int height)
{
  draw_oval(gwinfo, 'F', left, top, width, height);
}

void StGWPaintOval(StGWWinInfo *gwinfo,
		   int left, int top, int width, int height)
{
  draw_oval(gwinfo, 'P', left, top, width, height);
}

void StGWEraseOval(StGWWinInfo *gwinfo,
		   int left, int top, int width, int height)
{
  draw_oval(gwinfo, 'E', left, top, width, height);
}

static POINT angle_to_point(double angle,
			    int left, int top, int width, int height)
{
  static double deg2rad = 3.14159 / 180.0;
  double c = cos(deg2rad * angle), s = sin(deg2rad * angle);
  int radius = (width > height) ? width : height;
  POINT pt;

  pt.x = left + width / 2 + radius * c;
  pt.y = top + height / 2 - radius * s;
  return(pt);
}

static void draw_arc(StGWWinInfo *gwinfo, int which,
		     int left, int top, int width, int height,
		     double angle1, double angle2)
{
  HDC hDC;
  HPEN oldPen;
  HBRUSH oldBrush;
  POINT a1, a2;

  a1 = angle_to_point(angle1, left, top, width, height);
  a2 = angle_to_point(angle1 + angle2, left, top, width, height);
  if (gwinfo && gwinfo->window) {
    hDC = GET_DC(gwinfo->window);
    if (which == 'F') {
      oldPen = SET_PEN(hDC, gwinfo);
      oldBrush = SelectObject(hDC, GetStockObject(NULL_BRUSH));
      if (gwinfo->lineWidth != 1) {
	left += gwinfo->rect_offset;
	top += gwinfo->rect_offset;
	width += 1 - gwinfo->lineWidth;
	height += 1 - gwinfo->lineWidth;
      }
    }
    else {
      oldPen = SelectObject(hDC, GetStockObject(NULL_PEN));
      oldBrush = SelectObject(hDC, (which == 'P') ? GET_DRAW_BRUSH(gwinfo) : GET_ERASE_BRUSH(gwinfo));
      width++;  // make up for some deficiency in filling
      height++; // make up for some deficiency in filling
    }
    if (which == 'F')
      Arc(hDC, left, top, left + width, top + height, a1.x, a1.y, a2.x, a2.y);
    else
      Pie(hDC, left, top, left + width, top + height, a1.x, a1.y, a2.x, a2.y);
    if (which == 'F') {
      SelectObject(hDC, oldBrush);
      RESTORE_PEN(hDC, oldPen);
    }
    else {
      RESTORE_BRUSH(hDC, oldBrush);
      SelectObject(hDC, oldPen);
    }
    RELEASE_DC(gwinfo->window, hDC);
  }
}

void StGWFrameArc(StGWWinInfo *gwinfo,
		  int left, int top, int width, int height,
		  double angle1, double angle2)
{
  draw_arc(gwinfo, 'F', left, top, width, height, angle1, angle2);
}

void StGWPaintArc(StGWWinInfo *gwinfo,
		  int left, int top, int width, int height,
		  double angle1, double angle2)
{
  draw_arc(gwinfo, 'P', left, top, width, height, angle1, angle2);
}

void StGWEraseArc(StGWWinInfo *gwinfo,
		  int left, int top, int width, int height,
		  double angle1, double angle2)
{
  draw_arc(gwinfo, 'E', left, top, width, height, angle1, angle2);
}

static void draw_poly(StGWWinInfo *gwinfo, int which, int n, short *p,
		      int from_origin)
{
  HDC hDC;
  HPEN oldPen;
  HBRUSH oldBrush;
  int i;
  POINT *P;

  if (! from_origin) {
    for (i = 1; i < n; i++) {
      p[2 * i] += p[2 * (i - 1)];
      p[2 * i + 1] += p[2 * (i - 1) + 1];
    }
  }

  if (gwinfo && gwinfo->window) {
    P = (POINT *) ((n > 0) ? StCalloc(n, sizeof(POINT)) : NULL);
    for (i = 0; i < n; i++) {
      P[i].x = p[2 * i];
      P[i].y = p[2 * i + 1];
    }

    hDC = GET_DC(gwinfo->window);
    if (which == 'F') {
      oldPen = SET_PEN(hDC, gwinfo);
      oldBrush = SelectObject(hDC, GetStockObject(NULL_BRUSH));
    }
    else {
      oldPen = SelectObject(hDC, GetStockObject(NULL_PEN));
      oldBrush = SelectObject(hDC, (which == 'P') ? GET_DRAW_BRUSH(gwinfo) : GET_ERASE_BRUSH(gwinfo));
    }
    if (which == 'F') Polyline(hDC, P, n);
    else Polygon(hDC, P, n);
    if (which == 'F') {
      SelectObject(hDC, oldBrush);
      RESTORE_PEN(hDC, oldPen);
    }
    else {
      RESTORE_BRUSH(hDC, oldBrush);
      SelectObject(hDC, oldPen);
    }
    RELEASE_DC(gwinfo->window, hDC);
    StFree(P);
  }
}

void StGWFramePoly(StGWWinInfo *gwinfo, int n, short *p, int from_origin)
{
  draw_poly(gwinfo, 'F', n, p, from_origin);
}

void StGWPaintPoly(StGWWinInfo *gwinfo, int n, short *p, int from_origin)
{
  draw_poly(gwinfo, 'P', n, p, from_origin);
}

void StGWErasePoly(StGWWinInfo *gwinfo, int n, short *p, int from_origin)
{
  draw_poly(gwinfo, 'E', n, p, from_origin);
}

/**************************************************************************/
/**                                                                      **/
/**                           Text Functions                             **/
/**                                                                      **/
/**************************************************************************/

static HDC charDC = 0, upcharDC = 0;
static HBITMAP charBits = 0, upcharBits = 0;
static int charSize = 0;

static void init_msw_text(void)
{
  HFONT oldFont;
  HDC hDC;
  LOGFONT lf;
  POINT pt;

  memset(&lf, 0, sizeof(LOGFONT));
  GetPrivateProfileString(graphicsSection, "Font", "Courier New",
                          lf.lfFaceName, LF_FACESIZE, iniFile);
  pt.y = -GetPrivateProfileInt(graphicsSection, "FontSize", 11, iniFile);

  hDC = GetDC(hWndFrame);
  
  /* convert specified point size to pixels */
  pt.y = (pt.y * GetDeviceCaps(hDC, LOGPIXELSY)) / 72;
  DPtoLP(hDC, &pt, 1);
  lf.lfHeight = pt.y;

  hGraphFont = CreateFontIndirect(&lf);
  oldFont = SelectObject(hDC, hGraphFont);
  GetTextMetrics(hDC, &tm);
  SelectObject(hDC, oldFont);

  if (tm.tmPitchAndFamily & TMPF_TRUETYPE) {
    /* rotate 90 degrees for drawing up */
    lf.lfEscapement = lf.lfOrientation = 900;
    hGraphFontUp = CreateFontIndirect(&lf);
  }
  else hGraphFontUp = NULL;

  charDC = CreateCompatibleDC(hDC);
  upcharDC = CreateCompatibleDC(hDC);
  ReleaseDC(hWndFrame, hDC);

  charSize = (tm.tmHeight >  tm.tmAveCharWidth) ? tm.tmHeight :  tm.tmAveCharWidth;
  charBits = CreateBitmap(charSize, charSize, 1, 1, NULL);
  upcharBits = CreateBitmap(charSize, charSize, 1, 1, NULL);
  SelectObject(charDC, charBits);
  SelectObject(charDC, hGraphFont);
  SelectObject(upcharDC, upcharBits);
  SelectObject(upcharDC, hGraphFont);
}

static void cleanup_msw_text(void)
{
  if (charDC) {
    DeleteDC(charDC);
    charDC = 0;
  }
  if (upcharDC) {
    DeleteDC(upcharDC);
    upcharDC = 0;
  }
  if (charBits) {
    DeleteObject(charBits);
    charBits = 0;
  }
  if (upcharBits) {
    DeleteObject(upcharBits);
    upcharBits = 0;
  }
  if (hGraphFont)
    DeleteObject(hGraphFont);
  if (hGraphFontUp)
    DeleteObject(hGraphFontUp);
}

static void draw_char(char ch, int x, int y, HDC hDC, int mode)
{
  if (mode) {
    PatBlt(charDC, 0, 0, charSize, charSize, WHITENESS);
    TextOut(charDC, 0, 0, &ch, 1);
    BitBlt(hDC, x, y, charSize, charSize, charDC, 0, 0,  0x990066L);
  }
  else {
    int bkm = SetBkMode(hDC, TRANSPARENT);
    TextOut(hDC, x, y, &ch, 1);
    SetBkMode(hDC, bkm);
  }
}

static void draw_char_up(char ch, int x, int y, HDC hDC, int mode)
{
  if (hGraphFontUp) {
    HFONT hfontOld;
    /* offsets of 1 seem to be needed to mach bitmap results */
    x++;
    y++;
    if (mode) {
      hfontOld = SelectObject(charDC, hGraphFontUp);
      PatBlt(charDC, 0, 0, charSize, charSize, WHITENESS);
      TextOut(charDC, 0, tm.tmAveCharWidth, &ch, 1);
      BitBlt(hDC, x, y, charSize, charSize, charDC, 0, 0,  0x990066L);
      SelectObject(charDC, hfontOld);
    }
    else {
      hfontOld = SelectObject(hDC, hGraphFontUp);
      draw_char(ch, x, y, hDC, mode);
      SelectObject(hDC, hfontOld);
    }
  }
  else {
    int i, j;
    HDC targetDC;
    COLORREF dc;

    targetDC = mode ? upcharDC : hDC;
    dc = mode ? 0 : GetTextColor(hDC);
    PatBlt(charDC, 0, 0, charSize, charSize, WHITENESS);
    TextOut(charDC, 0, 0, &ch, 1);
    if (mode)
      PatBlt(upcharDC, 0, 0, charSize, charSize, WHITENESS);
    for (i = 0; i < tm.tmAveCharWidth; i++)
      for (j = 0; j < tm.tmHeight; j++)
	if (GetPixel(charDC, i, j) == 0)
	  SetPixel(targetDC, x + j, y + tm.tmAveCharWidth - i, dc);
    if (mode)
      BitBlt(hDC, x, y, charSize, charSize, upcharDC, 0, 0,  0x990066L);
  }
}

void StGWDrawString(StGWWinInfo *gwinfo, char *s, int x, int y)
{
  HDC hDC;
  HWND w;

  if (s && gwinfo && (w = gwinfo->window) != 0) {
    hDC = GET_DC(w);
    y -= tm.tmAscent;
    SelectObject(hDC, hGraphFont); // **** why is this needed??
    if (gwinfo->drawMode) {
      SetBkColor(hDC, get_color(0));
      SetTextColor(hDC, get_color(1));
      for (; *s != '\0'; s++, x += tm.tmAveCharWidth)
	draw_char(*s, x, y, hDC, gwinfo->drawMode);
      SetBkColor(hDC, get_color(gwinfo->backColor));
      SetTextColor(hDC, get_color(gwinfo->drawColor));
    }
    else {
      int bkm = SetBkMode(hDC, TRANSPARENT);
      TextOut(hDC, x, y, s, strlen(s));
      SetBkMode(hDC, bkm);
    }
    RELEASE_DC(w, hDC);
  }
}

void StGWDrawStringUp(StGWWinInfo *gwinfo, char *s, int x, int y)
{
  HDC hDC;
  HWND w;

  if (s && gwinfo && (w = gwinfo->window) != 0) {
    hDC = GET_DC(w);
    x -= tm.tmAscent;
    if (gwinfo->drawMode || hGraphFontUp == NULL)
      y -= tm.tmAveCharWidth;
    if (gwinfo->drawMode) {
      SetBkColor(hDC, get_color(0));
      SetTextColor(hDC, get_color(1));
    }
    for (; *s != '\0'; s++, y -= tm.tmAveCharWidth)
      draw_char_up(*s, x, y, hDC, gwinfo->drawMode);
    if (gwinfo->drawMode) {
      SetBkColor(hDC, get_color(gwinfo->backColor));
      SetTextColor(hDC, get_color(gwinfo->drawColor));
    }
    RELEASE_DC(w, hDC);
  }
}

int StGWTextAscent(StGWWinInfo *gwinfo)
#pragma argsused
{
  return(tm.tmAscent);
}

int StGWTextDescent(StGWWinInfo *gwinfo)
#pragma argsused
{
  return(tm.tmDescent);
}

int StGWTextWidth(StGWWinInfo *gwinfo, char *text)
#pragma argsused text
{
  return(text ? strlen(text) * tm.tmAveCharWidth : 0);
}

void StGWDrawText(StGWWinInfo *gwinfo,
		  char *text,
		  int x, int y, int h, int v)
{
  int FontAscent, string_width;

  if (text && gwinfo) {
    FontAscent = StGWTextAscent(gwinfo);
    string_width = StGWTextWidth(gwinfo, text);
    if (v == 1) y += FontAscent;
    if (h == 1) x -= string_width / 2;
    if (h == 2) x -= string_width;
    StGWDrawString(gwinfo, text, x, y);
  }
}

void StGWDrawTextUp(StGWWinInfo *gwinfo,
		    char *text,
		    int x, int y, int h, int v)
{
  int FontAscent, string_width;

  if (text && gwinfo) {
    FontAscent = StGWTextAscent(gwinfo);
    string_width = StGWTextWidth(gwinfo, text);
    if (v == 1) x += FontAscent;
    if (h == 1) y += string_width / 2;
    if (h == 2) y += string_width;
    StGWDrawStringUp(gwinfo, text, x, y);
  }
}

/**************************************************************************/
/**                                                                      **/
/**                          Symbol Functions                            **/
/**                                                                      **/
/**************************************************************************/

#define NUMSYMBOLS 18
#define SYMROWS 5

typedef struct {
  HBITMAP map;
  HDC hDC;
  int left, top, width, height;
  long refcon;
} Symbol;

static Symbol Symbols[NUMSYMBOLS];

void StGWSetSymRefCon(unsigned int index, long rc)
{
  if (index < NUMSYMBOLS) Symbols[index].refcon = rc;
}

long StGWGetSymRefCon(unsigned int index)
{	
  if (index < NUMSYMBOLS) return(Symbols[index].refcon);
  else return(0);
}

static void InitSymbol(int sym, int left, int top, int width, int height)
{
  HDC hDC;

  hDC = GetDC(hWndFrame);
  Symbols[sym].hDC = CreateCompatibleDC(hDC);
  ReleaseDC(hWndFrame, hDC);

  Symbols[sym].map = CreateBitmap(width, height, 1, 1, NULL);
  SelectObject(Symbols[sym].hDC, Symbols[sym].map);
  PatBlt(Symbols[sym].hDC, 0, 0, width, height, WHITENESS);
  Symbols[sym].left = left;
  Symbols[sym].top = top;
  Symbols[sym].width = width;
  Symbols[sym].height = height;
}

void StGWGetSymbolSize(int sym, int *pwidth, int *pheight)
{
  if (pwidth) *pwidth = Symbols[sym].width;
  if (pheight) *pheight = Symbols[sym].height;
}

static void SetSymbolData(int sym, int row,
			  int bit0, int bit1, int bit2, int bit3, int bit4)
{
  if (bit0) SetPixel(Symbols[sym].hDC, row, 0, 0);
  if (bit1) SetPixel(Symbols[sym].hDC, row, 1, 0);
  if (bit2) SetPixel(Symbols[sym].hDC, row, 2, 0);
  if (bit3) SetPixel(Symbols[sym].hDC, row, 3, 0);
  if (bit4) SetPixel(Symbols[sym].hDC, row, 4, 0);
}

static void init_msw_symbols(void)
{
  InitSymbol(0, 0, 0, 2, 2);
  SetSymbolData(0, 0, 1, 0, 0, 0, 0);
  SetSymbolData(0, 1, 0, 0, 0, 0, 0);
  SetSymbolData(0, 2, 0, 0, 0, 0, 0);
  SetSymbolData(0, 3, 0, 0, 0, 0, 0);
  SetSymbolData(0, 4, 0, 0, 0, 0, 0);

  InitSymbol(1, 0, 0, 2, 2);
  SetSymbolData(1, 0, 1, 1, 0, 0, 0);
  SetSymbolData(1, 1, 0, 0, 0, 0, 0);
  SetSymbolData(1, 2, 0, 0, 0, 0, 0);
  SetSymbolData(1, 3, 0, 0, 0, 0, 0);
  SetSymbolData(1, 4, 0, 0, 0, 0, 0);

  InitSymbol(2, 0, 0, 2, 2);
  SetSymbolData(2, 0, 1, 1, 0, 0, 0);
  SetSymbolData(2, 1, 1, 0, 0, 0, 0);
  SetSymbolData(2, 2, 0, 0, 0, 0, 0);
  SetSymbolData(2, 3, 0, 0, 0, 0, 0);
  SetSymbolData(2, 4, 0, 0, 0, 0, 0);

  InitSymbol(3, 0, 0, 2, 2);
  SetSymbolData(3, 0, 1, 1, 0, 0, 0);
  SetSymbolData(3, 1, 1, 1, 0, 0, 0);
  SetSymbolData(3, 2, 0, 0, 0, 0, 0);
  SetSymbolData(3, 3, 0, 0, 0, 0, 0);
  SetSymbolData(3, 4, 0, 0, 0, 0, 0);

  InitSymbol(4, 2, 2, 4, 4);
  SetSymbolData(4, 0, 0, 1, 1, 0, 0);
  SetSymbolData(4, 1, 1, 0, 0, 1, 0);
  SetSymbolData(4, 2, 1, 0, 0, 1, 0);
  SetSymbolData(4, 3, 0, 1, 1, 0, 0);
  SetSymbolData(4, 4, 0, 0, 0, 0, 0);

  InitSymbol(5, 2, 2, 4, 4);
  SetSymbolData(5, 0, 0, 1, 1, 0, 0);
  SetSymbolData(5, 1, 1, 1, 1, 1, 0);
  SetSymbolData(5, 2, 1, 1, 1, 1, 0);
  SetSymbolData(5, 3, 0, 1, 1, 0, 0);
  SetSymbolData(5, 4, 0, 0, 0, 0, 0);

  InitSymbol(6, 3, 3, 5, 5);
  SetSymbolData(6, 0, 0, 0, 1, 0, 0);
  SetSymbolData(6, 1, 0, 1, 0, 1, 0);
  SetSymbolData(6, 2, 1, 0, 0, 0, 1);
  SetSymbolData(6, 3, 0, 1, 0, 1, 0);
  SetSymbolData(6, 4, 0, 0, 1, 0, 0);

  InitSymbol(7, 3, 3, 5, 5);
  SetSymbolData(7, 0, 0, 0, 1, 0, 0);
  SetSymbolData(7, 1, 0, 1, 1, 1, 0);
  SetSymbolData(7, 2, 1, 1, 1, 1, 1);
  SetSymbolData(7, 3, 0, 1, 1, 1, 0);
  SetSymbolData(7, 4, 0, 0, 1, 0, 0);

  InitSymbol(8, 3, 3, 5, 5);
  SetSymbolData(8, 0, 0, 0, 1, 0, 0);
  SetSymbolData(8, 1, 0, 0, 1, 0, 0);
  SetSymbolData(8, 2, 1, 1, 1, 1, 1);
  SetSymbolData(8, 3, 0, 0, 1, 0, 0);
  SetSymbolData(8, 4, 0, 0, 1, 0, 0);

  InitSymbol(9, 3, 3, 5, 5);
  SetSymbolData(9, 0, 0, 1, 1, 1, 0);
  SetSymbolData(9, 1, 1, 0, 1, 0, 1);
  SetSymbolData(9, 2, 1, 1, 1, 1, 1);
  SetSymbolData(9, 3, 1, 0, 1, 0, 1);
  SetSymbolData(9, 4, 0, 1, 1, 1, 0);

  InitSymbol(10, 2, 2, 4, 4);
  SetSymbolData(10, 0, 1, 1, 1, 1, 0);
  SetSymbolData(10, 1, 1, 0, 0, 1, 0);
  SetSymbolData(10, 2, 1, 0, 0, 1, 0);
  SetSymbolData(10, 3, 1, 1, 1, 1, 0);
  SetSymbolData(10, 4, 0, 0, 0, 0, 0);

  InitSymbol(11, 2, 2, 4, 4);
  SetSymbolData(11, 0, 1, 1, 1, 1, 0);
  SetSymbolData(11, 1, 1, 1, 1, 1, 0);
  SetSymbolData(11, 2, 1, 1, 1, 1, 0);
  SetSymbolData(11, 3, 1, 1, 1, 1, 0);
  SetSymbolData(11, 4, 0, 0, 0, 0, 0);

  InitSymbol(12, 3, 3, 5, 5);
  SetSymbolData(12, 0, 0, 1, 1, 1, 0);
  SetSymbolData(12, 1, 1, 0, 0, 0, 1);
  SetSymbolData(12, 2, 1, 0, 0, 0, 1);
  SetSymbolData(12, 3, 0, 1, 0, 1, 0);
  SetSymbolData(12, 4, 0, 0, 1, 0, 0);

  InitSymbol(13, 3, 3, 5, 5);
  SetSymbolData(13, 0, 0, 1, 1, 1, 0);
  SetSymbolData(13, 1, 1, 1, 1, 1, 1);
  SetSymbolData(13, 2, 1, 1, 1, 1, 1);
  SetSymbolData(13, 3, 0, 1, 1, 1, 0);
  SetSymbolData(13, 4, 0, 0, 1, 0, 0);

  InitSymbol(14, 3, 3, 5, 5);
  SetSymbolData(14, 0, 0, 0, 1, 0, 0);
  SetSymbolData(14, 1, 0, 1, 0, 1, 0);
  SetSymbolData(14, 2, 1, 0, 0, 0, 1);
  SetSymbolData(14, 3, 1, 0, 0, 0, 1);
  SetSymbolData(14, 4, 0, 1, 1, 1, 0);

  InitSymbol(15, 3, 3, 5, 5);
  SetSymbolData(15, 0, 0, 0, 1, 0, 0);
  SetSymbolData(15, 1, 0, 1, 1, 1, 0);
  SetSymbolData(15, 2, 1, 1, 1, 1, 1);
  SetSymbolData(15, 3, 1, 1, 1, 1, 1);
  SetSymbolData(15, 4, 0, 1, 1, 1, 0);

  InitSymbol(16, 3, 3, 5, 5);
  SetSymbolData(16, 0, 1, 0, 0, 0, 1);
  SetSymbolData(16, 1, 0, 1, 0, 1, 0);
  SetSymbolData(16, 2, 0, 0, 1, 0, 0);
  SetSymbolData(16, 3, 0, 1, 0, 1, 0);
  SetSymbolData(16, 4, 1, 0, 0, 0, 1);

  InitSymbol(17, 3, 3, 5, 5);
  SetSymbolData(17, 0, 1, 1, 0, 1, 1);
  SetSymbolData(17, 1, 1, 1, 0, 1, 1);
  SetSymbolData(17, 2, 0, 0, 1, 0, 0);
  SetSymbolData(17, 3, 1, 1, 0, 1, 1);
  SetSymbolData(17, 4, 1, 1, 0, 1, 1);
}

static void cleanup_msw_symbols(void)
{
  int i;

  for (i = 0; i < NUMSYMBOLS; i++) {
    if (Symbols[i].hDC) {
      DeleteDC(Symbols[i].hDC);
      Symbols[i].hDC = 0;
    }
    if (Symbols[i].map) {
      DeleteObject(Symbols[i].map);
      Symbols[i].map = 0;
    }
  }
}

void StGWDrawSymbol(StGWWinInfo *gwinfo, int sym, int x, int y)
{
  HDC hDC;
  if (gwinfo && gwinfo->window && sym >= 0 && sym < NUMSYMBOLS) {
    hDC = GET_DC(gwinfo->window);
    x -= Symbols[sym].left;
    y -= Symbols[sym].top;
    if (gwinfo->drawMode) {
      SetBkColor(hDC, get_color(0));
      SetTextColor(hDC, get_color(1));
      BitBlt(hDC, x, y, Symbols[sym].width, Symbols[sym].height,
	     Symbols[sym].hDC, 0, 0,  0x990066L);
      SetBkColor(hDC, get_color(gwinfo->backColor));
      SetTextColor(hDC, get_color(gwinfo->drawColor));
    }
    else if (! buffering || gwinfo->use_color || gwinfo->backColor == 0)
      BitBlt(hDC, x, y, Symbols[sym].width, Symbols[sym].height,
	     Symbols[sym].hDC, 0, 0, SRCCOPY);
    else
      BitBlt(hDC, x, y, Symbols[sym].width, Symbols[sym].height,
	     Symbols[sym].hDC, 0, 0, NOTSRCCOPY);
    RELEASE_DC(gwinfo->window, hDC);
  }
}

void StGWReplaceSymbol(StGWWinInfo *gwinfo,
		       unsigned oldsym, unsigned newsym,
		       int x, int y)
{
  int oldwidth, oldheight, newwidth, newheight;
  
  if (oldsym < NUMSYMBOLS && newsym < NUMSYMBOLS) {
    StGWGetSymbolSize(oldsym, &oldwidth, &oldheight);
    StGWGetSymbolSize(newsym, &newwidth, &newheight);
    if (oldwidth > newwidth || oldheight > newheight)
      StGWEraseRect(gwinfo,
		    x - Symbols[oldsym].left, y - Symbols[oldsym].top,
		    oldwidth, oldheight);
    StGWDrawSymbol(gwinfo, newsym, x, y);
  }
}

/**************************************************************************/
/**                                                                      **/
/**                       Miscellaneous Functions                        **/
/**                                                                      **/
/**************************************************************************/

void StGWSetFreeMem(StGWWinInfo *gwinfo, void (*FreeMem)(HWND))
{
  if (gwinfo) gwinfo->FreeMem = FreeMem;
}

void StGWShowWindow(StGWWinInfo *gwinfo)
{
  HWND w;

  if (gwinfo && (w = gwinfo->window) != 0) {
    StShowWindow(w);
    if (! gwinfo->initialized) StGWInitialDraw(gwinfo);
  }
}

void StGWSetSize(StGWWinInfo *gwinfo, int width, int height, int frame)
{
  if (gwinfo && gwinfo->window)
    StWSetSize(gwinfo->window, width, height, frame);
}

StGWWinInfo *IViewWindowWinInfo(HWND w)
{
  return(w ? (StGWWinInfo *) GETGWINFO(w) : NULL);
}

void StGWInitialDraw(StGWWinInfo *gwinfo)
{
  int left, top, width, height;

  if (gwinfo) {
    gwinfo->initialized = TRUE;
    SetHardwareState(gwinfo);
    StGWObResize(gwinfo->Object);
    StGWGetViewRect(gwinfo, &left, &top, &width, &height);
    StGWStartBuffering(gwinfo);
    StGWObRedraw(gwinfo->Object);
    StGWBufferToScreen(gwinfo, left, top, width, height);
  }
}

void StGWReverseColors(StGWWinInfo *gwinfo)
{
  ColorCode backColor, drawColor;
  LVAL object;

  object = StGWGetObject(gwinfo);
  backColor = StGWBackColor(gwinfo);
  drawColor = StGWDrawColor(gwinfo);
  if (backColor != drawColor) {
    StGWSetBackColor(gwinfo, drawColor);
    StGWSetDrawColor(gwinfo, backColor);
    StGWObRedraw(object);
  }
}

void StGWDrawBitmap(StGWWinInfo *gwinfo,
		    int left, int top, int width, int height,
		    char *image)
{
  HBITMAP hbm, oldbm;
  HDC bmDC, hDC;
  int i, j;

  if (gwinfo && gwinfo->window) {
    hDC = GET_DC(gwinfo->window);
    bmDC = CreateCompatibleDC(hDC);
    if (bmDC) {
      hbm = CreateBitmap(width, height, 1, 1, NULL);
      if (hbm) {
	oldbm = SelectObject(bmDC, hbm);
	PatBlt(bmDC, 0, 0, width, height, WHITENESS);
	for (i = 0; i < height; i++)
	  for (j = 0; j < width; j++)
	    if (image[i * width + j])
	      SetPixel(bmDC, j, i, 0);
	if (gwinfo->drawMode) {
	  SetBkColor(hDC, get_color(0));
	  SetTextColor(hDC, get_color(1));
	  BitBlt(hDC, left, top, width, height, bmDC, 0, 0,  0x990066L);
	  SetBkColor(hDC, get_color(gwinfo->backColor));
	  SetTextColor(hDC, get_color(gwinfo->drawColor));
	}
	else BitBlt(hDC, left, top, width, height, bmDC, 0, 0, SRCCOPY);
	SelectObject(bmDC, oldbm);
	DeleteObject(hbm);
      }
      DeleteDC(bmDC);
    }
    RELEASE_DC(gwinfo->window, hDC);
  }
}

void StGWCopyToClip(StGWWinInfo *gwinfo)
{
  HDC hDC, hDCMem;
  HBITMAP hBM, hOldBM;
  int left, top, width, height;

  if (gwinfo && gwinfo->window) {
    StGWGetViewRect(gwinfo, &left, &top, &width, &height);
    StGWStartBuffering(gwinfo);
    StGWObRedraw(GETWINOBJECT(gwinfo->window));

    hDC = GetDC((HWND) gwinfo->window);
    hDCMem = CreateCompatibleDC(hDC);
    hBM = CreateCompatibleBitmap(hDC, width, height);
    if (hBM) {
      hOldBM = SelectObject(hDCMem, hBM);

      if (! gwinfo->use_color) {
        SetBkColor(hDC, get_color(0));
        SetTextColor(hDC, get_color(1));
      }
      BitBlt(hDCMem, left, top, width, height, currentDC, left, top, SRCCOPY);
      if (! gwinfo->use_color) {
        SetBkColor(hDC, get_color(gwinfo->backColor));
        SetTextColor(hDC, get_color(gwinfo->drawColor));
      }

      OpenClipboard(gwinfo->window);
      EmptyClipboard();
      SetClipboardData(CF_BITMAP, hBM);
      CloseClipboard();

      SelectObject(hDCMem, hOldBM);
    }
    else SysBeep(10);

    DeleteDC(hDCMem);
    ReleaseDC((HWND) gwinfo->window, hDC);
    StGWResetBuffer();
  }
}


syntax highlighted by Code2HTML, v. 0.9.1