/* vi:set sts=4 sw=4:
 *
 * VIM - Vi IMproved		by Bram Moolenaar
 *				GUI support by Robert Webb
 *
 * Do ":help uganda"  in Vim to read copying and usage conditions.
 * Do ":help credits" in Vim to see a list of people who contributed.
 *
 * Windows GUI.
 *
 * GUI support for Microsoft Windows.  Win32 initially; maybe Win16 later 
 *
 * George V. Reilly <gvr@halcyon.com> wrote the original Win32 GUI.
 * Robert Webb reworked it to use the existing GUI stuff and added menu,
 * scrollbars, etc.
 *
 * Note: Clipboard stuff, for cutting and pasting text to other windows, is in
 * os_win32.c.	(It can also be done from the terminal version).
 */

#include "vim.h"
#include "globals.h"
#include "proto.h"
#include "option.h"

#include <windows.h>
#include <shellapi.h>

#ifdef WIN32
# include <windowsx.h>
#else /* WIN16 */
# include <windowsx.h16>
#endif

#ifdef PROTO
/*
 * Define a few things for generating prototypes.  This is just to avoid
 * syntax errors, the defines do not need to be correct.
 */
# define HINSTANCE  void *
# define HWND	    void *
# define HMENU	    void *
# define UINT	    int	
# define WPARAM	    int
# define LPARAM	    int
typedef int LOGFONT[];
# define ENUMLOGFONT int
# define NEWTEXTMETRIC int
# define VOID	    void
# define CALLBACK
# define DWORD	    int
# define HDROP	    int
# define BOOL	    int
# define LPRECT	    int
# define LRESULT    int
# define WINAPI
# define LPSTR	    int
# define LPWINDOWPOS int
# define RECT	    int
# define LPCREATESTRUCT int
#endif

/* Local variables: */
static HINSTANCE    s_hinst = NULL;
static HWND	    s_hwnd = NULL;
static HWND	    s_textArea = NULL;
static HMENU	    s_menuBar = NULL;
static UINT	    s_menu_id = 0;
static UINT	    s_wait_timer = 0;	/* Timer for get char from user */

static UINT	    s_uMsg = 0;
static WPARAM	    s_wParam = 0;
static LPARAM	    s_lParam = 0;

static int	    s_xpos;
static int	    s_ypos;

static int	    s_timed_out = FALSE;

static const LOGFONT s_lfDefault =
{
    -12, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET,
    OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
    PROOF_QUALITY, FIXED_PITCH | FF_DONTCARE, 
    "Fixedsys"	/* see _ReadVimIni */
};

/* Initialise the "current height" to -12 (same as s_lfDefault) just
 * in case the user specifies a font in "guifont" with no size before a font
 * with an explicit size has been set. This defaults the size to this value
 * (-12 equates to roughly 9pt).
 */
static int current_font_height = -12;

static struct
{
    UINT    key_sym;
    char_u  vim_code0;
    char_u  vim_code1;
} special_keys[] =
{
    {VK_UP,	    'k', 'u'},
    {VK_DOWN,	    'k', 'd'},
    {VK_LEFT,	    'k', 'l'},
    {VK_RIGHT,	    'k', 'r'},

    {VK_F1,	    'k', '1'},
    {VK_F2,	    'k', '2'},
    {VK_F3,	    'k', '3'},
    {VK_F4,	    'k', '4'},
    {VK_F5,	    'k', '5'},
    {VK_F6,	    'k', '6'},
    {VK_F7,	    'k', '7'},
    {VK_F8,	    'k', '8'},
    {VK_F9,	    'k', '9'},
    {VK_F10,	    'k', ';'},

    {VK_F11,	    'F', '1'},
    {VK_F12,	    'F', '2'},
    {VK_F13,	    'F', '3'},
    {VK_F14,	    'F', '4'},
    {VK_F15,	    'F', '5'},
    {VK_F16,	    'F', '6'},
    {VK_F17,	    'F', '7'},
    {VK_F18,	    'F', '8'},
    {VK_F19,	    'F', '9'},
    {VK_F20,	    'F', 'A'},

    {VK_F21,	    'F', 'B'},
    {VK_F22,	    'F', 'C'},
    {VK_F23,	    'F', 'D'},
    {VK_F24,	    'F', 'E'},	    /* winuser.h defines up to F24 */

    {VK_HELP,	    '%', '1'},
    {VK_BACK,	    'k', 'b'},
    {VK_INSERT,	    'k', 'I'},
    {VK_DELETE,	    'k', 'D'},
    {VK_HOME,	    'k', 'h'},
    {VK_END,	    '@', '7'},
    {VK_PRIOR,	    'k', 'P'},
    {VK_NEXT,	    'k', 'N'},
    {VK_PRINT,	    '%', '9'},

    /* Keys that we want to be able to use any modifier with: */
    {VK_SPACE,	    ' ', NUL},
    {VK_TAB,	    TAB, NUL},
    {VK_ESCAPE,	    ESC, NUL},
    {NL,	    NL, NUL},
    {CR,	    CR, NUL},

    /* End of list marker: */
    {0,		    0, 0}
};


#define VIM_NAME	"vim"
#define VIM_CLASS	"Vim"


#ifdef DEBUG
/* Print out the last Windows error message */
    static void
print_windows_error()
{
    LPVOID  lpMsgBuf;

    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
		  NULL, GetLastError(),
		  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		  (LPTSTR) &lpMsgBuf, 0, NULL);
    TRACE("Error: %s\n", lpMsgBuf);
    LocalFree(lpMsgBuf);
}
#endif /* DEBUG */

/*
 * Figure out how high the menu bar is at the moment.
 */
    static int
gui_w32_get_menu_height(fix_window)
    int	    fix_window;	    /* If TRUE, resize window if menu height changed */
{
    static int	old_menu_height = -1;

    RECT    rc1, rc2;
    int	    num;
    int	    menu_height;

    num = GetMenuItemCount(s_menuBar);
    if (num == 0)
	menu_height = 0;
    else
    {
	GetMenuItemRect(s_hwnd, s_menuBar, 0, &rc1);
	GetMenuItemRect(s_hwnd, s_menuBar, num - 1, &rc2);
	menu_height = rc2.bottom - rc1.top + 1;
    }

    if (fix_window && menu_height != old_menu_height)
	gui_set_winsize(FALSE);

    old_menu_height = menu_height;
    return menu_height;
}

/*
 * Cursor blink functions.
 *
 * This is a simple state machine:
 * BLINK_NONE	not blinking at all
 * BLINK_OFF	blinking, cursor is not shown
 * BLINK_ON	blinking, cursor is shown
 */

#define BLINK_NONE  0
#define BLINK_OFF   1
#define BLINK_ON    2

static int		blink_state = BLINK_NONE;
static long_u		blink_waittime = 700;
static long_u		blink_ontime = 400;
static long_u		blink_offtime = 250;
static UINT		blink_timer = 0;

    void
gui_mch_set_blinking(long wait, long on, long off)
{
    blink_waittime = wait;
    blink_ontime = on;
    blink_offtime = off;
}

/* ARGSUSED */
    static VOID CALLBACK
_OnBlinkTimer(
    HWND hwnd,
    UINT uMsg,
    UINT idEvent,
    DWORD dwTime)
{
    MSG msg;

    /*
    TRACE("Got timer event, id %d, blink_timer %d\n", idEvent, blink_timer);
    */
    KillTimer(NULL, idEvent);

    /* Eat spurious WM_TIMER messages */
    while(PeekMessage(&msg, hwnd, WM_TIMER, WM_TIMER, PM_REMOVE))
	;

    if (blink_state == BLINK_ON)
    {
	gui_undraw_cursor();
	blink_state = BLINK_OFF;
	blink_timer = SetTimer(NULL, 0, (UINT)blink_offtime,
						    (TIMERPROC)_OnBlinkTimer);
    }
    else
    {
	gui_update_cursor(TRUE);
	blink_state = BLINK_ON;
	blink_timer = SetTimer(NULL, 0, (UINT)blink_ontime,
						    (TIMERPROC)_OnBlinkTimer);
    }
}

    static void
gui_w32_rm_blink_timer()
{
    MSG msg;

    if (blink_timer != 0)
    {
	KillTimer(NULL, blink_timer);
	/* Eat spurious WM_TIMER messages */
	while(PeekMessage(&msg, s_hwnd, WM_TIMER, WM_TIMER, PM_REMOVE))
	    ;
	blink_timer = 0;
    }
}

/*
 * Stop the cursor blinking.  Show the cursor if it wasn't shown.
 */
    void
gui_mch_stop_blink()
{
    gui_w32_rm_blink_timer();
    if (blink_state == BLINK_OFF)
	gui_update_cursor(TRUE);
    blink_state = BLINK_NONE;
}

/*
 * Start the cursor blinking.  If it was already blinking, this restarts the
 * waiting time and shows the cursor.
 */
    void
gui_mch_start_blink()
{
    gui_w32_rm_blink_timer();

    /* Only switch blinking on if none of the times is zero */
    if (blink_waittime && blink_ontime && blink_offtime)
    {
	blink_timer = SetTimer(NULL, 0, (UINT)blink_waittime,
						    (TIMERPROC)_OnBlinkTimer);
	blink_state = BLINK_ON;
	gui_update_cursor(TRUE);
    }
}

/*
 * Call-back routines.
 */

    static VOID CALLBACK
_OnTimer(
    HWND hwnd,
    UINT uMsg,
    UINT idEvent,
    DWORD dwTime)
{
    MSG msg;

    /*
    TRACE("Got timer event, id %d, s_wait_timer %d\n", idEvent, s_wait_timer);
    */
    KillTimer(NULL, idEvent);
    s_timed_out = TRUE;

    /* Eat spurious WM_TIMER messages */
    while(PeekMessage(&msg, hwnd, WM_TIMER, WM_TIMER, PM_REMOVE));
    if (idEvent == s_wait_timer)
	s_wait_timer = 0;
}

/*
 * Get this message when the user clicks on the cross in the top right corner
 * of a Windows95 window.
 */
    static void
_OnDestroy(
    HWND hwnd)
{
    preserve_exit();	    /* output IObuff, preserve files and exit */

    FORWARD_WM_DESTROY(hwnd, DefWindowProc);
}

    static void
_OnDropFiles(
    HWND hwnd,
    HDROP hDrop)
{
    char    szFile[_MAX_PATH]; 
    UINT    cFiles = DragQueryFile(hDrop, 0xFFFFFFFF, szFile, _MAX_PATH);
    UINT    i;
    char_u  *fname;
    char_u  string[1];

    /* TRACE("_OnDropFiles: %d files dropped\n", cFiles); */

    /* reset_VIsual(); */
    if (VIsual_active)
    {
	end_visual_mode();
	update_curbuf(NOT_VALID);	/* delete the inversion */
    }

    for (i = 0; i < cFiles; ++i)
    {
	DragQueryFile(hDrop, i, szFile, _MAX_PATH);
	/* TRACE("  dropped %2u: '%s'\n", i, szFile); */

	mch_dirname(IObuff, IOSIZE);
	fname = shorten_fname(szFile, IObuff);
	if (fname == NULL)
	    fname = szFile;
	win_split(0, TRUE);
	(void)do_ecmd(0, fname, NULL, NULL, 0, ECMD_FORCEIT);
    }

    DragFinish(hDrop);

    /* This makes it redraw properly */
    string[0] = Ctrl('L');
    add_to_input_buf(string, 1);
}

    static void
_OnChar(
    HWND hwnd,
    UINT ch,
    int cRepeat)
{
    char_u	string[1];

    /* TRACE("OnChar(%d, %c)\n", ch, ch); */

    string[0] = ch;
    if (string[0] == Ctrl('C'))
    {
	trash_input_buf();
	got_int = TRUE;
    }
    add_to_input_buf(string, 1);
}

    static void
_OnSysChar(
    HWND hwnd,
    UINT ch,
    int cRepeat)
{
    char_u	string[6]; /* Enough for maximum key sequence - see below */
    int		len;
    int		modifiers;

    /* TRACE("OnSysChar(%d, %c)\n", ch, ch); */

    /* OK, we have a character key (given by ch) which was entered with the
     * ALT key pressed. Eg, if the user presses Alt-A, then ch == 'A'. Note
     * that the system distinguishes Alt-a and Alt-A (Alt-Shift-a unless
     * CAPSLOCK is pressed) at this point.
     */
    modifiers = MOD_MASK_ALT;
    if (GetKeyState(VK_SHIFT) & 0x8000)
	modifiers |= MOD_MASK_SHIFT;
    if (GetKeyState(VK_CONTROL) & 0x8000)
	modifiers |= MOD_MASK_CTRL;
    ch = simplify_key(ch, &modifiers);
    if (modifiers & MOD_MASK_ALT)
    {
	ch |= 0x80;
	modifiers &= ~MOD_MASK_ALT;
    }

    len = 0;
    if (modifiers)
    {
	string[len++] = CSI;
	string[len++] = KS_MODIFIER;
	string[len++] = modifiers;
    }
    if (IS_SPECIAL(ch))
    {
	string[len++] = CSI;
	string[len++] = K_SECOND(ch);
	string[len++] = K_THIRD(ch);
    }
    else
	string[len++] = ch;
    add_to_input_buf(string, len);
}

    static void
_OnMouseEvent(
    int button,
    int x,
    int y,
    int repeated_click,
    UINT keyFlags)
{
    int vim_modifiers = 0x0;

    if (keyFlags & MK_SHIFT)
	vim_modifiers |= MOUSE_SHIFT;
    if (keyFlags & MK_CONTROL)
	vim_modifiers |= MOUSE_CTRL;
    if (GetKeyState(VK_MENU) & 0x8000)
	vim_modifiers |= MOUSE_ALT;

    gui_send_mouse_event(button, x, y, repeated_click, vim_modifiers);
}

    static void
_OnMouseButtonDown(
    HWND hwnd,
    BOOL fDoubleClick,
    int x,
    int y,
    UINT keyFlags)
{
    static LONG	s_prevTime = 0;

    LONG    currentTime = GetMessageTime();
    int	    button = -1;
    int	    repeated_click;

    /*
     * While button is down, keep grabbing mouse move events when the mouse
     * goes outside the window
     */
    SetCapture(s_textArea);

    if (s_uMsg == WM_LBUTTONDOWN || s_uMsg == WM_LBUTTONDBLCLK)
	button = MOUSE_LEFT;
    else if (s_uMsg == WM_MBUTTONDOWN || s_uMsg == WM_MBUTTONDBLCLK)
	button = MOUSE_MIDDLE;
    else if (s_uMsg == WM_RBUTTONDOWN || s_uMsg == WM_RBUTTONDBLCLK)
	button = MOUSE_RIGHT;

    if (button >= 0)
    {
	repeated_click = ((int)(currentTime - s_prevTime) < p_mouset);

	/*
	 * TODO: Fix this, it doesn't work:
	 */
	/*
	 * Holding down the left and right buttons simulates pushing the middle
	 * button.  Hmm, this doesn't really work because we have already
	 * passed through the first click as a left or right button click.
	 */
	if (repeated_click &&
	    ((button == MOUSE_LEFT && (keyFlags & MK_RBUTTON)) ||
	     (button == MOUSE_RIGHT && (keyFlags & MK_LBUTTON))))
	{
	    /*
	     * Hmm, gui.c will ignore more than one button down at a time, so
	     * pretend we let go of it first.
	     */
	    gui_send_mouse_event(MOUSE_RELEASE, x, y, FALSE, 0x0);
	    button = MOUSE_MIDDLE;
	    repeated_click = FALSE;
	}

	s_prevTime = currentTime;
	/* TRACE("Button down at x %d, y %d\n", x, y); */
	_OnMouseEvent(button, x, y, repeated_click, keyFlags);
    }
}

    static void
_OnMouseMoveOrRelease(
    HWND hwnd,
    int x,
    int y,
    UINT keyFlags)
{
    int button;

    if (s_uMsg == WM_MOUSEMOVE)
    {
	/*
	 * It's only a MOUSE_DRAG if one or more mouse buttons are being held
	 * down.
	 */
	if (!(keyFlags & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)))
	    return;

	button = MOUSE_DRAG;
	/* TRACE("  move at x %d, y %d\n", x, y); */
    }
    else
    {
	ReleaseCapture();
	button = MOUSE_RELEASE;
	/* TRACE("  up at x %d, y %d\n", x, y); */
    }

    _OnMouseEvent(button, x, y, FALSE, keyFlags);
}

    static void
_OnPaint(
    HWND hwnd)
{
    if (!IsMinimized(hwnd))
    {
	PAINTSTRUCT ps;
	HDC hdc;

	hdc = BeginPaint(hwnd, &ps);
	if (!IsRectEmpty(&ps.rcPaint))
	    gui_redraw(ps.rcPaint.left, ps.rcPaint.top,
		       ps.rcPaint.right - ps.rcPaint.left + 1,
		       ps.rcPaint.bottom - ps.rcPaint.top + 1);
	EndPaint(hwnd, &ps);
    }
}

    static void
_OnSize(
    HWND hwnd,
    UINT state,
    int cx,
    int cy)
{
    if (!IsMinimized(hwnd))
    {
	gui_resize_window(cx, cy);
    
	/* Menu bar may wrap differently now */
	gui_w32_get_menu_height(TRUE);
    }
}

    static void
_OnKillFocus(
    HWND hwnd,
    HWND hwndNewFocus)
{
    clip_mch_lose_focus();
    gui.in_focus = FALSE;
    gui_update_cursor(TRUE);
}

    static void
_OnMenu(
    HWND hwnd,
    int id,
    HWND hwndCtl,
    UINT codeNotify)
{
    MENUITEMINFO    info;

    info.cbSize = sizeof(info);
    info.fMask = MIIM_DATA;
    
    /*
     * The menu id is only 16-bit, so we store the menu pointer in the data
     * field instead.
     */
    GetMenuItemInfo(s_menuBar, id, FALSE, &info);
    gui_menu_cb((GuiMenu *)info.dwItemData);
}

    static void
_OnSetFocus(
    HWND hwnd,
    HWND hwndOldFocus)
{
    gui.in_focus = TRUE;
    gui_update_cursor(TRUE);

    /* reset_modifiers(); */
}

/*
 * Find the scrollbar with the given hwnd.
 */
    static GuiScrollbar *
gui_w32_find_scrollbar(hwnd)
    HWND	hwnd;
{
    WIN		*wp;

    if (gui.bottom_sbar.id == hwnd)
	return &gui.bottom_sbar;
    for (wp = firstwin; wp != NULL; wp = wp->w_next)
    {
	if (wp->w_scrollbars[SBAR_LEFT].id == hwnd)
	    return &wp->w_scrollbars[SBAR_LEFT];
	if (wp->w_scrollbars[SBAR_RIGHT].id == hwnd)
	    return &wp->w_scrollbars[SBAR_RIGHT];
    }
    return NULL;
}

    static void
_OnScroll(
    HWND hwnd,
    HWND hwndCtl,
    UINT code,
    int pos)
{
    GuiScrollbar *sb, *sb_info;
    int		val;
    int		dragging = FALSE;
    SCROLLINFO	si;
    int		c;

    sb = gui_w32_find_scrollbar(hwndCtl);
    if (sb == NULL)
	return;
    else if (sb->wp != NULL)	    /* Left or right scrollbar */
    {
	/*
	 * Careful: need to get scrollbar info out of first (left) scrollbar
	 * for window, but keep real scrollbar too because we must pass it to
	 * gui_drag_scrollbar().
	 */
	sb_info = &sb->wp->w_scrollbars[0];
    }
    else	    /* Bottom scrollbar */
	sb_info = sb;
    val = sb_info->value;

    switch (code)
    {
	case SB_THUMBTRACK:
	    /* TRACE("SB_THUMBTRACK, %d\n", pos); */
	    val = pos;
	    dragging = TRUE;
	    break;
	case SB_LINEDOWN:
	    /* TRACE("SB_LINEDOWN\n"); */
	    val++;
	    break;
	case SB_LINEUP:
	    /* TRACE("SB_LINEUP\n"); */
	    val--;
	    break;
	case SB_PAGEDOWN:
	    /* TRACE("SB_PAGEDOWN\n"); */
	    val += (sb_info->size > 2 ? sb_info->size - 2 : 1);
	    break;
	case SB_PAGEUP:
	    /* TRACE("SB_PAGEUP\n"); */
	    val -= (sb_info->size > 2 ? sb_info->size - 2 : 1);
	    break;
	case SB_TOP:
	    /* TRACE("SB_TOP\n"); */
	    val = 0;
	    break;
	case SB_BOTTOM:
	    /* TRACE("SB_BOTTOM\n"); */
	    val = sb_info->max;
	    break;
	case SB_ENDSCROLL:
	    /*
	     * "pos" only gives us 16-bit data.  In case of large file, use
	     * GetScrollPos() which returns 32-bit.  Unfortunately it is not
	     * valid while the scrollbar is being dragged.
	     */
	    /* TRACE("SB_ENDSCROLL\n"); */
	    val = GetScrollPos(hwndCtl, SB_CTL);
	    break;
	default:
	    /* TRACE("Unknown scrollbar event %d\n", code); */
	    return;
    }

    si.cbSize = sizeof(si);
    si.fMask = SIF_POS;
    si.nPos = val;
    SetScrollInfo(hwndCtl, SB_CTL, &si, TRUE);
    
    /*
     * Scrollbars seem to grab focus and vim doesn't read the input queue until
     * you stop dragging the scrollbar.  We get here each time the scrollbar is
     * dragged another pixel, but as far as the rest of vim goes, it thinks
     * we're just hanging in the call to DispatchMessage() in
     * process_message().  The DispatchMessage() call that hangs was passed a
     * mouse button click event in the scrollbar window. -- webb.
     */
    gui_drag_scrollbar(sb, val, dragging);
    if (code == SB_THUMBTRACK)
    {
	/*
	 * We need to process the messages we just stuck in the queue; if we
	 * don't, we will end up with nothing happening until we release the
	 * thumb, then the screen will scroll wildly.  Really ugly!
	 * This is a hack though, is there no way to stop the scrollbar from
	 * taking over in the first place?
	 */
	c = vgetc();
	if (c == K_SCROLLBAR)
	{
	    if (State & NORMAL)
	    {
		gui_do_scroll();
		setcursor();
	    }
	    else if (State & INSERT)
	    {
		/*
		 * Get some random text inserted sometimes after scrolling in
		 * insert mode, why?  Sometimes you get text that you've
		 * already typed elsewhere! -- webb
		 */
		ins_scroll();
		setcursor();
	    }
	    else if (State & CMDLINE)
	    {
		if (!msg_scrolled)
		{
		    gui_do_scroll();
		    redrawcmdline();
		}
	    }
	    flushbuf();
	}
	else if (c == K_HORIZ_SCROLLBAR)
	{
	    if (State & NORMAL)
	    {
		gui_do_horiz_scroll();
		setcursor();
	    }
	    else if (State & INSERT)
	    {
		ins_horscroll();
		setcursor();
	    }
	    else if (State & CMDLINE)
	    {
		if (!msg_scrolled)
		{
		    gui_do_horiz_scroll();
		    redrawcmdline();
		}
	    }
	    flushbuf();
	}
	else
	{
	    /* Woops!  Shouldn't have got this */
	    vungetc(c);
	}
    }
}

    static void
gui_w32_get_valid_dimensions(
    int w,
    int h,
    int *valid_w,
    int *valid_h)
{
    int	    base_width, base_height;

    base_width = gui_get_base_width()
	+ GetSystemMetrics(SM_CXSIZEFRAME) * 2;
    base_height = gui_get_base_height()
	+ GetSystemMetrics(SM_CYSIZEFRAME) * 2
	+ GetSystemMetrics(SM_CYCAPTION)
	+ gui_w32_get_menu_height(FALSE);
    *valid_w = base_width +
		    ((w - base_width) / gui.char_width) * gui.char_width;
    *valid_h = base_height +
		    ((h - base_height) / gui.char_height) * gui.char_height;
}

/*
 * Even though we have _DuringSizing() which makes the rubber band a valid
 * size, we need this for when the user maximises the window.
 * TODO: Doesn't seem to adjust the width though for some reason.
 */
    static BOOL
_OnWindowPosChanging(
    HWND hwnd,
    LPWINDOWPOS lpwpos)
{
    if (!(lpwpos->flags & SWP_NOSIZE))
    {
	gui_w32_get_valid_dimensions(lpwpos->cx, lpwpos->cy,
				     &lpwpos->cx, &lpwpos->cy);
    }
    return 0;
}

    static int
_DuringSizing(
    HWND hwnd,
    UINT fwSide,
    LPRECT lprc)
{
    int	    w, h;
    int	    valid_w, valid_h;
    int	    w_offset, h_offset;

    w = lprc->right - lprc->left;
    h = lprc->bottom - lprc->top;
    gui_w32_get_valid_dimensions(w, h, &valid_w, &valid_h);
    w_offset = w - valid_w;
    h_offset = h - valid_h;

    if (fwSide == WMSZ_LEFT || fwSide == WMSZ_TOPLEFT
			    || fwSide == WMSZ_BOTTOMLEFT)
	lprc->left += w_offset;
    else if (fwSide == WMSZ_RIGHT || fwSide == WMSZ_TOPRIGHT
			    || fwSide == WMSZ_BOTTOMRIGHT)
	lprc->right -= w_offset;

    if (fwSide == WMSZ_TOP || fwSide == WMSZ_TOPLEFT
			    || fwSide == WMSZ_TOPRIGHT)
	lprc->top += h_offset;
    else if (fwSide == WMSZ_BOTTOM || fwSide == WMSZ_BOTTOMLEFT
			    || fwSide == WMSZ_BOTTOMRIGHT)
	lprc->bottom -= h_offset;
    return TRUE;
}

    static BOOL
_OnCreate (HWND hwnd, LPCREATESTRUCT lpcs)
{
    /* retain window position */
    s_xpos = lpcs->x;
    s_ypos = lpcs->y;

    return 0;
}

    static LRESULT CALLBACK
_WndProc(
    HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam)
{
    /*
    TRACE("WndProc: hwnd = %08x, msg = %x, wParam = %x, lParam = %x\n",
	  hwnd, uMsg, wParam, lParam);
    */

    s_uMsg = uMsg;
    s_wParam = wParam;
    s_lParam = lParam;

    switch (uMsg)
    {
	/* HANDLE_MSG(hwnd, WM_ACTIVATE,    _OnActivate); */
	HANDLE_MSG(hwnd, WM_CHAR,	_OnChar);
	HANDLE_MSG(hwnd, WM_SYSCHAR,	_OnSysChar);
	/* HANDLE_MSG(hwnd, WM_CLOSE,	    _OnClose); */
	/* HANDLE_MSG(hwnd, WM_COMMAND,	_OnCommand); */
	HANDLE_MSG(hwnd, WM_DESTROY,	_OnDestroy);
	HANDLE_MSG(hwnd, WM_DROPFILES,	_OnDropFiles);
	HANDLE_MSG(hwnd, WM_HSCROLL,	_OnScroll);
	HANDLE_MSG(hwnd, WM_KILLFOCUS,	_OnKillFocus);
	HANDLE_MSG(hwnd, WM_COMMAND,	_OnMenu);
	/* HANDLE_MSG(hwnd, WM_MOVE,	    _OnMove); */
	/* HANDLE_MSG(hwnd, WM_NCACTIVATE,  _OnNCActivate); */
	HANDLE_MSG(hwnd, WM_SETFOCUS,	_OnSetFocus);
	HANDLE_MSG(hwnd, WM_SIZE,	_OnSize);
	/* HANDLE_MSG(hwnd, WM_SYSCOMMAND,  _OnSysCommand); */
	/* HANDLE_MSG(hwnd, WM_SYSKEYDOWN,  _OnAltKey); */
	/* HANDLE_MSG(hwnd, WM_SYSKEYUP,    _OnAltKey); */
	HANDLE_MSG(hwnd, WM_VSCROLL,	_OnScroll);
	HANDLE_MSG(hwnd, WM_WINDOWPOSCHANGING,	_OnWindowPosChanging);

    case WM_CREATE:	/* HANDLE_MSG doesn't seem to handle this one */
	return _OnCreate (hwnd, (LPCREATESTRUCT)lParam);

    case WM_SIZING:	/* HANDLE_MSG doesn't seem to handle this one */
	return _DuringSizing(hwnd, wParam, (LPRECT)lParam);

    default:
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}

    static LRESULT CALLBACK
_TextAreaWndProc(
    HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam)
{
    /*
    TRACE("TextAreaWndProc: hwnd = %08x, msg = %x, wParam = %x, lParam = %x\n",
	  hwnd, uMsg, wParam, lParam);
    */

    s_uMsg = uMsg;
    s_wParam = wParam;
    s_lParam = lParam;

    switch (uMsg)
    {
	HANDLE_MSG(hwnd, WM_LBUTTONDBLCLK,_OnMouseButtonDown);
	HANDLE_MSG(hwnd, WM_LBUTTONDOWN,_OnMouseButtonDown);
	HANDLE_MSG(hwnd, WM_LBUTTONUP,	_OnMouseMoveOrRelease);
	HANDLE_MSG(hwnd, WM_MBUTTONDBLCLK,_OnMouseButtonDown);
	HANDLE_MSG(hwnd, WM_MBUTTONDOWN,_OnMouseButtonDown);
	HANDLE_MSG(hwnd, WM_MBUTTONUP,	_OnMouseMoveOrRelease);
	HANDLE_MSG(hwnd, WM_MOUSEMOVE,	_OnMouseMoveOrRelease);
	HANDLE_MSG(hwnd, WM_PAINT,	_OnPaint);
	HANDLE_MSG(hwnd, WM_RBUTTONDBLCLK,_OnMouseButtonDown);
	HANDLE_MSG(hwnd, WM_RBUTTONDOWN,_OnMouseButtonDown);
	HANDLE_MSG(hwnd, WM_RBUTTONUP,	_OnMouseMoveOrRelease);

    default:
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}


/*
 * End of call-back routines
 */

/*
 * Parse the GUI related command-line arguments.  Any arguments used are
 * deleted from argv, and *argc is decremented accordingly.  This is called
 * when vim is started, whether or not the GUI has been started.
 */
    void
gui_mch_prepare(argc, argv)
    int	    *argc;
    char    **argv;
{
    /* We don't have any command line arguments for the Windows GUI yet */
}

/*
 * Initialise the GUI.	Create all the windows, set up all the call-backs
 * etc.
 */
    int
gui_mch_init()
{
    const char szVimWndClass[] = VIM_CLASS;
    const char szTextAreaClass[] = "VimTextArea";
    WNDCLASS wndclass;
    
    gui.scrollbar_width = GetSystemMetrics(SM_CXVSCROLL);
    gui.scrollbar_height = GetSystemMetrics(SM_CYHSCROLL);
    gui.menu_height = 0;	/* Windows takes care of this */
    gui.border_width = 0;

    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = _WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = s_hinst;
    wndclass.hIcon = LoadIcon(wndclass.hInstance, "IDR_VIM");
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = GetStockBrush(LTGRAY_BRUSH);
    wndclass.lpszMenuName = NULL;
    wndclass.lpszClassName = szVimWndClass;

    if (RegisterClass(&wndclass) == 0)
	return FAIL;

    s_hwnd = CreateWindow(
	szVimWndClass, "Vim W32 GUI",
	WS_OVERLAPPEDWINDOW,
	CW_USEDEFAULT, CW_USEDEFAULT,
	100,				/* Any value will do */
	100,				/* Any value will do */
	NULL, NULL,
	s_hinst, NULL);

    if (s_hwnd == NULL)
	return FAIL;

    /* Create the text area window */
    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = _TextAreaWndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = s_hinst;
    wndclass.hIcon = NULL;
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = NULL;
    wndclass.lpszMenuName = NULL;
    wndclass.lpszClassName = szTextAreaClass;

    if (RegisterClass(&wndclass) == 0)
	return FAIL;

    s_textArea = CreateWindow(
	szTextAreaClass, "Vim text area",
	WS_CHILD | WS_VISIBLE, 0, 0,
	100,				/* Any value will do for now */
	100,				/* Any value will do for now */
	s_hwnd, NULL,
	s_hinst, NULL);

    if (s_textArea == NULL)
	return FAIL;

    s_menuBar = CreateMenu();

    DragAcceptFiles(s_hwnd, TRUE);

    /* Do we need to bother with this? */
    /* m_fMouseAvail = GetSystemMetrics(SM_MOUSEPRESENT); */

    /*
     * We no longer read resources from vim.ini.  Use the .vimrc file!
     */

    /* Get the colors from the "Normal" group (set in syntax.c or in a vimrc
     * file). */
    highlight_gui_started();
    set_normal_colors();

    /*
     * Check that none of the colors are the same as the background color.
     * Then store the current values as the defaults.
     */
    gui_check_colors();
    gui.def_norm_pixel = gui.norm_pixel;
    gui.def_back_pixel = gui.back_pixel;

    /*
     * Start out by adding the configured border width into the border offset
     */
    gui.border_offset = gui.border_width;

    return OK;
}

/*
 * Called when the foreground or background color has been changed.
 */
    void
gui_mch_new_colors()
{
    /* nothing to do? */
}

/*
 * Open the GUI window which was created by a call to gui_mch_init().
 */
    int
gui_mch_open()
{
    /* Actually open the window */
    ShowWindow(s_hwnd, SW_SHOWNORMAL);

    return OK;
}

    void
gui_mch_exit()
{
    /* Can't think of anything to do here */
}

/*
 * Set the size of the window to the given width and height in pixels.
 */
    void
gui_mch_set_winsize(width, height, min_width, min_height,
		    base_width, base_height)
    int	    width;
    int	    height;
    int	    min_width;
    int	    min_height;
    int	    base_width;
    int	    base_height;
{
    RECT    rect;
    int     new_x;
    int     new_y;

    /* try yo keep window completely on screen */
    GetClientRect (GetDesktopWindow (), &rect);
    new_x = width + GetSystemMetrics(SM_CXSIZEFRAME) * 2;
    new_y = height + GetSystemMetrics(SM_CYSIZEFRAME) * 2
			+ GetSystemMetrics(SM_CYCAPTION)
			+ gui_w32_get_menu_height(FALSE);
    if (s_xpos + new_x > rect.right)
    {
	s_xpos = rect.right - new_x;
	if (s_xpos < 0)
	    s_xpos = 0;
    }
    if (s_ypos + new_y > rect.bottom)
    {
	s_ypos = rect.bottom - new_y;
	if (s_ypos < 0)
	    s_ypos = 0;
    }

    /* set window position */
    SetWindowPos(s_hwnd, NULL, s_xpos, s_ypos, new_x, new_y,
		 SWP_NOZORDER | SWP_NOACTIVATE);
    
    /* Menu may wrap differently now */
    gui_w32_get_menu_height(TRUE);
}

    void
gui_mch_get_screen_dimensions(screen_w, screen_h)
    int	    *screen_w;
    int	    *screen_h;
{
    *screen_w = GetSystemMetrics(SM_CXSCREEN)
	      - GetSystemMetrics(SM_CXSIZEFRAME) * 2;
    *screen_h = GetSystemMetrics(SM_CYSCREEN)
	      - GetSystemMetrics(SM_CYSIZEFRAME) * 2
	      - GetSystemMetrics(SM_CYCAPTION)
	      - gui_w32_get_menu_height();
}

    void
gui_mch_set_text_area_pos(x, y, w, h)
    int	    x;
    int	    y;
    int	    w;
    int	    h;
{
    SetWindowPos(s_textArea, NULL, x, y, w, h, SWP_NOZORDER | SWP_NOACTIVATE);
}


/*
 * Scrollbar stuff:
 */

    void
gui_mch_enable_scrollbar(sb, flag)
    GuiScrollbar    *sb;
    int		    flag;
{
    ShowScrollBar(sb->id, SB_CTL, flag);
}

    void
gui_mch_set_scrollbar_thumb(sb, val, size, max)
    GuiScrollbar    *sb;
    int		    val;
    int		    size;
    int		    max;
{
    SCROLLINFO	info;

    info.cbSize = sizeof(info);
    info.fMask = SIF_POS | SIF_RANGE | SIF_PAGE;
    info.nPos = val;
    info.nMin = 0;
    info.nMax = max;
    info.nPage = size;
    SetScrollInfo(sb->id, SB_CTL, &info, TRUE);
}

    void
gui_mch_set_scrollbar_pos(sb, x, y, w, h)
    GuiScrollbar    *sb;
    int		    x;
    int		    y;
    int		    w;
    int		    h;
{
    SetWindowPos(sb->id, NULL, x, y, w, h, SWP_NOZORDER | SWP_NOACTIVATE);
}

    void
gui_mch_create_scrollbar(sb, orient)
    GuiScrollbar    *sb;
    int		    orient;	/* SBAR_VERT or SBAR_HORIZ */
{
    sb->id = CreateWindow(
	"SCROLLBAR", "Scrollbar",
	WS_CHILD | ((orient == SBAR_VERT) ? SBS_VERT : SBS_HORZ), 0, 0,
	10,				/* Any value will do for now */
	10,				/* Any value will do for now */
	s_hwnd, NULL,
	s_hinst, NULL);
}

    void
gui_mch_destroy_scrollbar(sb)
    GuiScrollbar    *sb;
{
    DestroyWindow(sb->id);
}

/*
 * Get the character size of a font, by measuring the size of four characters.
 */
    static void
GetFontSize(GuiFont font)
{
    static const char ach[] = {'W', 'f', 'g', 'M'};

    HWND    hwnd = GetDesktopWindow();
    HDC	    hdc = GetWindowDC(hwnd);
    HFONT   hfntOld = SelectFont(hdc, (HFONT)font);
    SIZE    siz;
    
    GetTextExtentPoint(hdc, ach, sizeof(ach), &siz);

    gui.char_width = siz.cx / sizeof(ach);
    /*
     * Make characters one pixel higher, so that italic and bold fonts don't
     * draw off the bottom of their character space.  Also means that we can
     * underline an underscore for normal text.
     */
    gui.char_height = siz.cy + 1;

    /* TRACE("GetFontSize: h %d, w %d\n", gui.char_height, gui.char_width); */

    SelectFont(hdc, hfntOld);

    ReleaseDC(hwnd, hdc);
}

    static GuiFont
get_font_handle(lf)
    LOGFONT *lf;
{
    HFONT   *font = NULL;

    /* Load the font */
    font = CreateFontIndirect(lf);

    if (font == NULL)
	return (GuiFont)0;

    return (GuiFont)font;
}

/* Convert a string representing a point size into pixels. The string should
 * be a positive decimal number, with an optional decimal point (eg, "12", or
 * "10.5"). The pixel value is returned, and a pointer to the next unconverted
 * character is stored in *end. The flag "vertical" says whether this
 * calculation is for a vertical (height) size or a horizontal (width) one.
 */
    static int
points_to_pixels (char_u *str, char_u **end, int vertical)
{
    int pixels;
    int points = 0;
    int divisor = 0;

    HWND hwnd;
    HDC hdc;

    while (*str)
    {
	if (*str == '.' && divisor == 0)
	{
	    /* Start keeping a divisor, for later */
	    divisor = 1;
	    continue;
	}

	if (!isdigit(*str))
	    break;

	points *= 10;
	points += *str - '0';
	divisor *= 10;

	++str;
    }

    if (divisor == 0)
	divisor = 1;

    hwnd = GetDesktopWindow();
    hdc = GetWindowDC(hwnd);

    pixels = MulDiv(points,
		    GetDeviceCaps(hdc, vertical ? LOGPIXELSY : LOGPIXELSX),
		    72 * divisor);

    ReleaseDC(hwnd, hdc);

    *end = str;
    return pixels;
}

    static int CALLBACK
font_enumproc (elf, ntm, type, lparam)
    ENUMLOGFONT *elf;
    NEWTEXTMETRIC *ntm;
    int type;
    LPARAM lparam;
{
    /* Return value:
     *	  0 = terminate now (monospace & ANSI)
     *	  1 = continue, still no luck...
     *	  2 = continue, but we have an acceptable LOGFONT
     *	      (monospace, not ANSI)
     * We use these values, as EnumFontFamilies returns 1 if the
     * callback function is never called. So, we check the return as
     * 0 = perfect, 2 = OK, 1 = no good...
     * It's not pretty, but it works!
     */

    LOGFONT *lf = (LOGFONT *)(lparam);

    /* Ignore non-monospace fonts without further ado */
    if ((ntm->tmPitchAndFamily & 1) != 0)
	return 1;

    /* Remember this LOGFONT as a "possible" */
    *lf = elf->elfLogFont;

    /* Terminate the scan as soon as we find an ANSI font */
    if (lf->lfCharSet == ANSI_CHARSET)
	return 0;

    /* Continue the scan - we have a non-ANSI font */
    return 2;
}

    static int
init_logfont (lf, height)
    LOGFONT *lf;
    int height;
{
    int n;
    HWND hwnd = GetDesktopWindow();
    HDC hdc = GetWindowDC(hwnd);

    n = EnumFontFamilies (hdc,
			  lf->lfFaceName,
			  (FONTENUMPROC) font_enumproc,
			  (LPARAM)(lf));

    ReleaseDC (hwnd, hdc);

    /* If we couldn't find a useable font, return failure */
    if (n == 1)
	return 0;

    /* Tidy up the rest of the LOGFONT structure. We set to a basic
     * font - get_logfont() sets bold, italic, etc based on the user's
     * input.
     */
    lf->lfHeight = current_font_height;
    lf->lfWidth = 0;
    lf->lfItalic = FALSE;
    lf->lfUnderline = FALSE;
    lf->lfStrikeOut = FALSE;
    lf->lfWeight = FW_NORMAL;

    /* Return success */
    return 1;
}

    static int
get_logfont(lf, name)
    LOGFONT *lf;
    char_u  *name;
{
    char_u  *p;

    if (name == NULL)
    {
	*lf = s_lfDefault;
	return 1;
    }

    /*
     * Split name up, it could be <name>:h<height>:w<width> etc.
     */
    for (p = name; *p && *p != ':'; p++)
	;
    if (p - name + 1 > LF_FACESIZE)
	return 0;			/* Name too long */
    if (p != name)
    {
	/* Copy name to lf->lfFaceName, replacing underscores with spaces */
	int	i;
	int	n = p - name;
	char	*dest = lf->lfFaceName;

	for (i = 0; i < n; ++i)
	{
	    if (name[i] == '_')
		dest[i] = ' ';
	    else
		dest[i] = name[i];
	}

	dest[i] = '\0';
    }

    /* First set defaults */
    lf->lfHeight = -12;
    lf->lfWidth = 0;
    lf->lfWeight = FW_NORMAL;
    lf->lfItalic = FALSE;
    lf->lfUnderline = FALSE;
    lf->lfStrikeOut = FALSE;

    if (!init_logfont (lf))
	return 0;

    while (*p == ':')
	p++;

    /* Set the values found after ':' */
    while (*p)
    {
	switch (*p++)
	{
	    case 'h':
		lf->lfHeight = - points_to_pixels(p, &p, TRUE);
		break;
	    case 'w':
		lf->lfWidth = points_to_pixels(p, &p, FALSE);
		break;
	    case 'b':
		lf->lfWeight = FW_BOLD;
		break;
	    case 'i':
		lf->lfItalic = TRUE;
		break;
	    case 'u':
		lf->lfUnderline = TRUE;
		break;
	    case 's':
		lf->lfStrikeOut = TRUE;
		break;
	    default:
		sprintf((char *)IObuff,
			"Illegal char '%c' in font name \"%s\"",
			p[-1], name);
		EMSG(IObuff);
		break;
	}
	while (*p == ':')
	    p++;
    }

    return 1;
}

/*
 * Initialise vim to use the font with the given name.	Return FAIL if the font
 * could not be loaded, OK otherwise.
 */
    int
gui_mch_init_font(font_name)
    char_u	*font_name;
{
    LOGFONT lf;
    GuiFont font = (GuiFont)0;

    /* Load the font */
    if (get_logfont(&lf, font_name))
	font = get_font_handle(&lf);
    if (font == (GuiFont)0)
	return FAIL;
    if (font_name == NULL)
	font_name = "?";
    if (gui.norm_font != (GuiFont)0)
	gui_mch_free_font(gui.norm_font);
    gui.norm_font = font;
    current_font_height = lf.lfHeight;
    GetFontSize(font);
    hl_set_font_name(font_name);

    if (!lf.lfItalic)
    {
	lf.lfItalic = TRUE;
	gui.ital_font = get_font_handle(&lf);
	lf.lfItalic = FALSE;
    }
    if (lf.lfWeight < FW_BOLD)
    {
	lf.lfWeight = FW_BOLD;
	gui.bold_font = get_font_handle(&lf);
	if (!lf.lfItalic)
	{
	    lf.lfItalic = TRUE;
	    gui.boldital_font = get_font_handle(&lf);
	}
    }

    return OK;
}

    GuiFont
gui_mch_get_font(name, giveErrorIfMissing)
    char_u	*name;
    int		giveErrorIfMissing;
{
    LOGFONT	lf;
    GuiFont	font;

    get_logfont(&lf, name);
    font = get_font_handle(&lf);
    if (font == (GuiFont)0 && giveErrorIfMissing)
	EMSG2("Unknown font: %s", name);
    return font;
}

/*
 * Set the current text font.
 */
    void
gui_mch_set_font(font)
    GuiFont font;
{
    gui.currFont = font;
}

/*
 * Return TRUE if the two fonts given are equivalent.
 */
    int
gui_mch_same_font(f1, f2)
    GuiFont f1;
    GuiFont f2;
{
    return f1 == f2;
}

    void
gui_mch_free_font(font)
    GuiFont font;
{
    DeleteObject((HFONT)font);
}

    static int
hex_digit(c)
    int	    c;
{
    if (isdigit(c))
	return c - '0';
    c = TO_LOWER(c);
    if (c >= 'a' && c <= 'f')
	return c - 'a' + 10;
    return -1000;
}

/*
 * Return the Pixel value (color) for the given color name.
 * Return -1 for error.
 */
    GuiColor
gui_mch_get_color(name)
    char_u  *name;
{
    typedef struct GuiColorTable
    {
	char	    *name;
	COLORREF    color;
    } GuiColorTable;

    static GuiColorTable table[] =
    {
	{"Black",	    RGB(0x00, 0x00, 0x00)},
	{"DarkBlue",	    RGB(0x00, 0x00, 0x80)},
	{"DarkGreen",	    RGB(0x00, 0x80, 0x00)},
	{"DarkCyan",	    RGB(0x00, 0x80, 0x80)},
	{"DarkRed",	    RGB(0x80, 0x00, 0x00)},
	{"DarkMagenta",	    RGB(0x80, 0x00, 0x80)},
	{"Brown",	    RGB(0x80, 0x40, 0x40)},
	{"Gray",	    RGB(0xC0, 0xC0, 0xC0)},
	{"Grey",	    RGB(0xC0, 0xC0, 0xC0)},
	{"LightGray",	    RGB(0xC0, 0xC0, 0xC0)},
	{"LightGrey",	    RGB(0xC0, 0xC0, 0xC0)},
	{"DarkGray",	    RGB(0x80, 0x80, 0x80)},
	{"DarkGrey",	    RGB(0x80, 0x80, 0x80)},
	{"Blue",	    RGB(0x00, 0x00, 0xFF)},
	{"LightBlue",	    RGB(0x00, 0x00, 0xFF)},
	{"Green",	    RGB(0x00, 0xFF, 0x00)},
	{"LightGreen",	    RGB(0x00, 0xFF, 0x00)},
	{"Cyan",	    RGB(0x00, 0xFF, 0xFF)},
	{"LightCyan",	    RGB(0x00, 0xFF, 0xFF)},
	{"Red",		    RGB(0xFF, 0x00, 0x00)},
	{"LightRed",	    RGB(0xFF, 0x00, 0x00)},
	{"Magenta",	    RGB(0xFF, 0x00, 0xFF)},
	{"LightMagenta",    RGB(0xFF, 0x00, 0xFF)},
	{"Yellow",	    RGB(0xFF, 0xFF, 0x00)},
	{"White",	    RGB(0xFF, 0xFF, 0xFF)},
	{"SeaGreen",	    RGB(0x2E, 0x8B, 0x57)},
	{"Orange",	    RGB(0xFF, 0xA5, 0x00)},
	{"Purple",	    RGB(0xA0, 0x20, 0xF0)},
	{"SlateBlue",	    RGB(0x6A, 0x5A, 0xCD)},
    };

    int		    r, g, b;
    int		    i;

    if (!gui.in_use)		    /* can't do this when GUI not running */
	return (GuiColor)-1;

    if (name[0] == '#' && strlen(name) == 7)
    {
	/* Name is in "#rrggbb" format */
	r = hex_digit(name[1]) * 16 + hex_digit(name[2]);
	g = hex_digit(name[3]) * 16 + hex_digit(name[4]);
	b = hex_digit(name[5]) * 16 + hex_digit(name[6]);
	if (r < 0 || g < 0 || b < 0)
	    return (GuiColor)-1;
	return RGB(r, g, b);
    }
    else
    {
	/* Check if the name is one of the colors we know */
	for (i = 0; i < sizeof(table) / sizeof(table[0]); i++)
	    if (STRICMP(name, table[i].name) == 0)
		return table[i].color;
    }

    /*
     * Last attempt. Look in the file "$VIM/rgb.txt".
     */
    {
#define LINE_LEN 100
	FILE	*fd;
	char	line[LINE_LEN];
	char_u	*fname;

	fname = expand_env_save((char_u *)"$VIM/rgb.txt");
	if (fname == NULL)
	    return (GuiColor)-1;

	fd = fopen((char *)fname, "rt");
	vim_free(fname);
	if (fd == NULL)
	    return (GuiColor)-1;

	while (!feof(fd))
	{
	    int	    len;
	    int	    pos;
	    char    *color;

	    fgets(line, LINE_LEN, fd);
	    len = strlen(line);

	    if (len <= 1 || line[len-1] != '\n')
		continue;

	    line[len-1] = '\0';

	    i = sscanf(line, "%d %d %d %n", &r, &g, &b, &pos);
	    if (i != 3)
		continue;

	    color = line + pos;

	    if (STRICMP(color, name) == 0)
	    {
		fclose(fd);
		return (GuiColor) RGB(r,g,b);
	    }
	}

	fclose(fd);
    }

    return (GuiColor)-1;
}

/*
 * Set the current text foreground color.
 */
    void
gui_mch_set_fg_color(color)
    GuiColor	color;
{
    gui.currFgColor = color;
}

/*
 * Set the current text background color.
 */
    void
gui_mch_set_bg_color(color)
    GuiColor	color;
{
    gui.currBgColor = color;
}

    void
gui_mch_draw_string(row, col, s, len, flags)
    int	    row;
    int	    col;
    char_u  *s;
    int	    len;
    int	    flags;
{
    static int *padding = NULL;
    static int pad_size = 0;

    HDC	    hdc = GetWindowDC(s_textArea);
    HPEN    hpen, old_pen;
    HFONT   old_font;
    int	    y;
    int	    i;

#if 1
    /*
     * Italic and bold text seems to have an extra row of pixels at the bottom
     * (below where the bottom of the character should be).  If we draw the
     * characters with a solid background, the top row of pixels in the
     * character below will be overwritten.  We can fix this by filling in the
     * background ourselves, to the correct character proportions, and then
     * writing the character in transparent mode.  Still have a problem when
     * the character is "_", which gets written on to the character below.
     * New fix: set gui.char_ascent to -1.  This shifts all characters up one
     * pixel in their slots, which fixes the problem with the bottom row of
     * pixels.	We still need this code because otherwise the top row of pixels
     * becomes a problem. - webb.
     */
    HBRUSH  hbr;
    RECT    rc;

    if (!(flags & DRAW_TRANSP))
    {
	/*
	 * Clear background first.
	 * Note: FillRect() excludes right and bottom of rectangle.
	 */
	rc.left = FILL_X(col);
	rc.top = FILL_Y(row);
	rc.right = FILL_X(col + len);	    /* Add +1 to erase fake bold? */
	rc.bottom = FILL_Y(row + 1);
	hbr = CreateSolidBrush(gui.currBgColor);
	FillRect(hdc, &rc, hbr);
	DeleteBrush(hbr);

	SetBkMode(hdc, TRANSPARENT);
    }
#else
    /*
     * The alternative would be to write the characters in opaque mode, but
     * when the text is not exactly the same proportions as normal text, too
     * big or too little a rectangle gets drawn for the background.
     */
    SetBkMode(hdc, OPAQUE);
    SetBkColor(hdc, gui.currBgColor);
#endif
    SetTextColor(hdc, gui.currFgColor);
    old_font = SelectFont(hdc, gui.currFont);

    if (pad_size != Columns || padding == NULL || padding[0] != gui.char_width)
    {
	vim_free(padding);
	pad_size = Columns;
	padding = (int *)alloc(pad_size * sizeof(int));
	if (padding != NULL)
	    for (i = 0; i < pad_size; i++)
		padding[i] = gui.char_width;
    }

    /*
     * We have to provide the padding argument because italic and bold versions
     * of fixed-width fonts are often one pixel or so wider than their normal
     * versions.  Who knows why?!
     */
    ExtTextOut(hdc, TEXT_X(col), TEXT_Y(row), 0, NULL, (char *)s, len, padding);

    if (flags & DRAW_BOLD)
	ExtTextOut(hdc, TEXT_X(col), TEXT_Y(row), 0, NULL, (char *)s,
								len, padding);

    if (flags & DRAW_UNDERL)
    {
	/*
	 * Note: LineTo() excludes the last pixel in the line.
	 */
	hpen = CreatePen(PS_SOLID, 1, gui.currFgColor);
	old_pen = SelectObject(hdc, hpen);
	y = FILL_Y(row + 1) - 1;
	MoveToEx(hdc, FILL_X(col), y, NULL);
	LineTo(hdc, FILL_X(col + len), y);
	DeleteObject(hpen);
	SelectObject(hdc, old_pen);
    }
    SelectFont(hdc, old_font);
    ReleaseDC(s_textArea, hdc);
}

/*
 * Return OK if the key with the termcap name "name" is supported.
 */
    int
gui_mch_haskey(name)
    char_u  *name;
{
    int i;

    for (i = 0; special_keys[i].vim_code1 != NUL; i++)
	if (name[0] == special_keys[i].vim_code0 &&
					 name[1] == special_keys[i].vim_code1)
	    return OK;
    return FAIL;
}

    void
gui_mch_beep()
{
    MessageBeep(MB_OK);
}

    void
gui_mch_flash()
{
    HDC	    hdc = GetWindowDC(s_textArea);
    RECT    rc;

    /*
     * Note: InvertRect() excludes right and bottom of rectangle.
     */
    rc.left = 0;
    rc.top = 0;
    rc.right = gui.num_cols * gui.char_width;
    rc.bottom = gui.num_rows * gui.char_height;
    InvertRect(hdc, &rc);

    ui_delay(20L, TRUE);	/* wait 1/50 of a second */

    InvertRect(hdc, &rc);
    ReleaseDC(s_textArea, hdc);
}

/*
 * Invert a rectangle from row r, column c, for nr rows and nc columns.
 */
    void
gui_mch_invert_rectangle(r, c, nr, nc)
    int	    r;
    int	    c;
    int	    nr;
    int	    nc;
{
    HDC	    hdc = GetWindowDC(s_textArea);
    RECT    rc;

    /*
     * Note: InvertRect() excludes right and bottom of rectangle.
     */
    rc.left = FILL_X(c);
    rc.top = FILL_Y(r);
    rc.right = rc.left + nc * gui.char_width;
    rc.bottom = rc.top + nr * gui.char_height;
    InvertRect(hdc, &rc);
    ReleaseDC(s_textArea, hdc);
}

/*
 * Iconify the GUI window.
 */
    void
gui_mch_iconify()
{
    ShowWindow(s_hwnd, SW_MINIMIZE);
}

/*
 * Set the window title
 */
    void
gui_mch_settitle(title, icon)
    char_u  *title;
    char_u  *icon;
{
    SetWindowText(s_hwnd, (LPCSTR)title);
}

/*
 * Draw a cursor without focus.
 */
    void
gui_mch_draw_hollow_cursor(color)
    GuiColor	color;
{
    HDC	    hdc = GetWindowDC(s_textArea);
    HBRUSH  hbr;
    RECT    rc;

    /*
     * Note: FrameRect() excludes right and bottom of rectangle.
     */
    rc.left = FILL_X(gui.col);
    rc.top = FILL_Y(gui.row);
    rc.right = rc.left + gui.char_width;
    rc.bottom = rc.top + gui.char_height;
    hbr = CreateSolidBrush(color);
    FrameRect(hdc, &rc, hbr);
    DeleteBrush(hbr);
    ReleaseDC(s_textArea, hdc);
}

/*
 * Draw part of a cursor, "w" pixels wide, and "h" pixels high, using
 * color "color".
 */
    void
gui_mch_draw_part_cursor(w, h, color)
    int		w;
    int		h;
    GuiColor	color;
{
    HDC		hdc = GetWindowDC(s_textArea);
    HBRUSH	hbr;
    RECT	rc;

    /*
     * Note: FillRect() excludes right and bottom of rectangle.
     */
    rc.left =
#ifdef RIGHTLEFT
		/* vertical line should be on the right of current point */
		State != CMDLINE && curwin->w_p_rl ?  FILL_X(gui.col + 1) - w :
#endif
		    FILL_X(gui.col);
    rc.top = FILL_Y(gui.row) + gui.char_height - h;
    rc.right = rc.left + w;
    rc.bottom = rc.top + h;
    hbr = CreateSolidBrush(color);
    FillRect(hdc, &rc, hbr);
    DeleteBrush(hbr);
    ReleaseDC(s_textArea, hdc);
}

/*
 * Process a single Windows message.  If one is not available we hang until one
 * is.
 */
    static void
process_message()
{
    MSG	    msg;
    UINT    vk;		/* Virtual key */
    char_u  string[3];
    int	    i;
    int	    modifiers = 0;
    int	    key;

    GetMessage(&msg, NULL, 0, 0);

    /*
     * Check if it's a special key that we recognise.  If not, call
     * TranslateMessage().
     */
    if (msg.message == WM_KEYDOWN || msg.message == WM_SYSKEYDOWN)
    {
	vk = (int) msg.wParam;
	for (i = 0; special_keys[i].key_sym != 0; i++)
	{
	    if (special_keys[i].key_sym == vk)
	    {
		if (GetKeyState(VK_SHIFT) & 0x8000)
		    modifiers |= MOD_MASK_SHIFT;
		/*
		 * Don't use caps-lock as shift, because these are special keys
		 * being considered here, and we only want letters to get
		 * shifted -- webb
		 */
		/*
		if (GetKeyState(VK_CAPITAL) & 0x0001)
		    modifiers ^= MOD_MASK_SHIFT;
		*/
		if (GetKeyState(VK_CONTROL) & 0x8000)
		    modifiers |= MOD_MASK_CTRL;
		if (GetKeyState(VK_MENU) & 0x8000)
		    modifiers |= MOD_MASK_ALT;

		if (special_keys[i].vim_code1 == NUL)
		    key = special_keys[i].vim_code0;
		else
		    key = TO_SPECIAL(special_keys[i].vim_code0,
						   special_keys[i].vim_code1);
		key = simplify_key(key, &modifiers);

		if (modifiers)
		{
			string[0] = CSI;
			string[1] = KS_MODIFIER;
			string[2] = modifiers;
			add_to_input_buf(string, 3);
		}

		if (IS_SPECIAL(key))
		{
		    string[0] = CSI;
		    string[1] = K_SECOND(key);
		    string[2] = K_THIRD(key);
		    add_to_input_buf(string, 3);
		}
		else
		{
		    string[0] = key;
		    add_to_input_buf(string, 1);
		}
		break;
	    }
	}
	if (special_keys[i].key_sym == 0)
	{
	    /* Some keys need C-S- where they should only need C- */
	    if (GetKeyState(VK_CONTROL) & 0x8000)
	    {
		if (vk == '6')
		{
		    string[0] = Ctrl('^');
		    add_to_input_buf(string, 1);
		}
		else if (vk == '-')	/* Why doesn't this work? */
		{
		    string[0] = Ctrl('_');
		    add_to_input_buf(string, 1);
		}
		else
		    TranslateMessage(&msg);
	    }
	    else
		TranslateMessage(&msg);
	}
    }

    DispatchMessage(&msg);
}

/*
 * Catch up with any queued events.  This may put keyboard input into the
 * input buffer, call resize call-backs, trigger timers etc.  If there is
 * nothing in the event queue (& no timers pending), then we return
 * immediately.
 */
    void
gui_mch_update()
{
    MSG	    msg;

    while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE) && !is_input_buf_full())
	process_message();
}

/*
 * GUI input routine called by gui_wait_for_chars().  Waits for a character
 * from the keyboard.
 *  wtime == -1	    Wait forever.
 *  wtime == 0	    This should never happen.
 *  wtime > 0	    Wait wtime milliseconds for a character.
 * Returns OK if a character was found to be available within the given time,
 * or FAIL otherwise.
 */
    int
gui_mch_wait_for_chars(wtime)
    int	    wtime;
{
    MSG		msg;
    int		focus;

    s_timed_out = FALSE;

    if (wtime > 0)
	s_wait_timer = SetTimer(NULL, 0, (UINT)wtime, (TIMERPROC)_OnTimer);

    focus = gui.in_focus;
    while (!s_timed_out)
    {
	/* Stop or start blinking when focus changes */
	if (gui.in_focus != focus)
	{
	    if (gui.in_focus)
		gui_mch_start_blink();
	    else
		gui_mch_stop_blink();
	    focus = gui.in_focus;
	}

	/*
	 * Don't use gui_mch_update() because then we will spin-lock until a
	 * char arrives, instead we use GetMessage() to hang until an
	 * event arrives.  No need to check for input_buf_full because we are
	 * returning as soon as it contains a single char -- webb
	 */
	process_message();

	if (!is_input_buf_empty())
	{
	    if (s_wait_timer != 0 && !s_timed_out)
	    {
		KillTimer(NULL, s_wait_timer);

		/* Eat spurious WM_TIMER messages */
		while(PeekMessage(&msg, s_hwnd, WM_TIMER, WM_TIMER, PM_REMOVE));
		s_wait_timer = 0;
	    }
	    return OK;
	}
    }
    return FAIL;
}

/*
 * Output routines.
 */

/* Flush any output to the screen */
    void
gui_mch_flush()
{
    /* Is anything needed here? */
}

    static void
clear_rect(rcp)
    RECT    *rcp;
{
    HDC	    hdc = GetWindowDC(s_textArea);
    HBRUSH  hbr;

    hbr = CreateSolidBrush(gui.back_pixel);
    FillRect(hdc, rcp, hbr);
    DeleteBrush(hbr);
    ReleaseDC(s_textArea, hdc);
}

/*
 * Clear a rectangular region of the screen from text pos (row1, col1) to
 * (row2, col2) inclusive.
 */
    void
gui_mch_clear_block(row1, col1, row2, col2)
    int	    row1;
    int	    col1;
    int	    row2;
    int	    col2;
{
    RECT    rc;

    /*
     * Clear one extra pixel at the right, for when bold characters have
     * spilled over to the next column.  Note: FillRect() excludes right and
     * bottom of rectangle.
     * Can this ever erase part of the next character? - webb
     */
    rc.left = FILL_X(col1);
    rc.top = FILL_Y(row1);
    rc.right = FILL_X(col2 + 1) + 1;
    rc.bottom = FILL_Y(row2 + 1);
    clear_rect(&rc);
}

/*
 * Clear the whole text window.
 */
    void
gui_mch_clear_all()
{
    RECT    rc;

    rc.left = 0;
    rc.top = 0;
    rc.right = Columns * gui.char_width + 2 * gui.border_width;
    rc.bottom = Rows * gui.char_height + 2 * gui.border_width;
    clear_rect(&rc);
}

/*
 * Delete the given number of lines from the given row, scrolling up any
 * text further down within the scroll region.
 */
    void
gui_mch_delete_lines(row, num_lines)
    int	    row;
    int	    num_lines;
{
    if (num_lines <= 0)
	return;

    if (row + num_lines > gui.scroll_region_bot)
    {
	/* Scrolled out of region, just blank the lines out */
	gui_clear_block(row, 0, gui.scroll_region_bot, Columns - 1);
    }
    else
    {
	RECT	rc;

	rc.left = FILL_X(0);
	rc.right = FILL_X(Columns);
	rc.top = FILL_Y(row);
	rc.bottom = FILL_Y(gui.scroll_region_bot + 1);
	ScrollWindowEx(s_textArea, 0, -num_lines * gui.char_height,
		       &rc, &rc, NULL, NULL, SW_INVALIDATE);

	/* Update gui.cursor_row if the cursor scrolled or copied over */
	if (gui.cursor_row >= row)
	{
	    if (gui.cursor_row < row + num_lines)
		INVALIDATE_CURSOR();
	    else if (gui.cursor_row <= gui.scroll_region_bot)
		gui.cursor_row -= num_lines;
	}
	UpdateWindow(s_textArea);

	gui_clear_block(gui.scroll_region_bot - num_lines + 1, 0,
	    gui.scroll_region_bot, Columns - 1);
    }
}

/*
 * Insert the given number of lines before the given row, scrolling down any
 * following text within the scroll region.
 */
    void
gui_mch_insert_lines(row, num_lines)
    int	    row;
    int	    num_lines;
{
    if (num_lines <= 0)
	return;

    if (row + num_lines > gui.scroll_region_bot)
    {
	/* Scrolled out of region, just blank the lines out */
	gui_clear_block(row, 0, gui.scroll_region_bot, Columns - 1);
    }
    else
    {
	RECT	rc;

	rc.left = FILL_X(0);
	rc.right = FILL_X(Columns);
	rc.top = FILL_Y(row);
	rc.bottom = FILL_Y(gui.scroll_region_bot + 1);
	ScrollWindowEx(s_textArea, 0, num_lines * gui.char_height,
		       &rc, &rc, NULL, NULL, SW_INVALIDATE);

	/* Update gui.cursor_row if the cursor scrolled or copied over */
	if (gui.cursor_row >= gui.row)
	{
	    if (gui.cursor_row <= gui.scroll_region_bot - num_lines)
		gui.cursor_row += num_lines;
	    else if (gui.cursor_row <= gui.scroll_region_bot)
		INVALIDATE_CURSOR();
	}
	UpdateWindow(s_textArea);

	gui_clear_block(row, 0, row + num_lines - 1, Columns - 1);
    }
}


/*
 * Menu stuff.
 */

    void
gui_mch_enable_menu(flag)
    int	    flag;
{
    SetMenu(s_hwnd, flag ? s_menuBar : NULL);
}

    void
gui_mch_set_menu_pos(x, y, w, h)
    int	    x;
    int	    y;
    int	    w;
    int	    h;
{
    /* It will be in the right place anyway */
}

/*
 * Add a sub menu to the menu bar.
 */
    void
gui_mch_add_menu(menu, parent)
    GuiMenu *menu;
    GuiMenu *parent;
{
    MENUITEMINFO    info;

    menu->submenu_id = CreatePopupMenu();

    info.cbSize = sizeof(info);
    info.fMask = MIIM_DATA | MIIM_TYPE | MIIM_ID | MIIM_SUBMENU;
    info.dwItemData = (DWORD)menu;
    info.wID = menu->id = s_menu_id++;
    info.fType = MFT_STRING;
    info.dwTypeData = (LPTSTR)menu->name;
    info.cch = STRLEN(menu->name);
    info.hSubMenu = menu->submenu_id;
    InsertMenuItem((parent == NULL) ? s_menuBar : parent->submenu_id,
		   (UINT)99999, TRUE, &info);

    /*
     * The "Help" menu is a special case, and should be placed at the far right
     * hand side of the menu-bar.
     */
    if (parent == NULL && STRCMP((char *)menu->name, "Help") == 0)
    {
	/* TODO: Make this the help menu */
    }

    /* Fix window size if menu wrapped */
    if (parent == NULL)
	gui_w32_get_menu_height(TRUE);
}

/*
 * Add a menu item to a menu
 */
    void
gui_mch_add_menu_item(menu, parent)
    GuiMenu *menu;
    GuiMenu *parent;
{
    MENUITEMINFO    info;

    info.cbSize = sizeof(info);
    info.fMask = MIIM_DATA | MIIM_TYPE | MIIM_ID;
    info.dwItemData = (DWORD)menu;
    info.wID = menu->id = s_menu_id++;
    info.fType = MFT_STRING;
    info.dwTypeData = (LPTSTR)menu->name;
    info.cch = STRLEN(menu->name);
    InsertMenuItem(parent->submenu_id, (UINT)99999, TRUE, &info);

    menu->submenu_id = NULL;
}

/*
 * Destroy the machine specific menu widget.
 */
    void
gui_mch_destroy_menu(menu)
    GuiMenu *menu;
{
    RemoveMenu(s_menuBar, menu->id, MF_BYCOMMAND);
    if (menu->submenu_id != NULL)
	DestroyMenu(menu->submenu_id);
}

/*
 * Make a menu either grey or not grey.
 */
    void
gui_mch_menu_grey(menu, grey)
    GuiMenu *menu;
    int	    grey;
{
    if (grey)
	EnableMenuItem(s_menuBar, menu->id, MF_BYCOMMAND | MF_GRAYED);
    else
	EnableMenuItem(s_menuBar, menu->id, MF_BYCOMMAND | MF_ENABLED);
}

/*
 * Make menu item hidden or not hidden
 */
    void
gui_mch_menu_hidden(menu, hidden)
    GuiMenu *menu;
    int	    hidden;
{
    /*
     * This doesn't do what we want.  Hmm, just grey the menu items for now.
     */
    /*
    if (hidden)
	EnableMenuItem(s_menuBar, menu->id, MF_BYCOMMAND | MF_DISABLED);
    else
	EnableMenuItem(s_menuBar, menu->id, MF_BYCOMMAND | MF_ENABLED);
    */
    gui_mch_menu_grey(menu, hidden);
}

/*
 * This is called after setting all the menus to grey/hidden or not.
 */
    void
gui_mch_draw_menubar()
{
    DrawMenuBar(s_hwnd);
}

/* cproto doesn't create a prototype for main() */
int main __PARMS((int argc, char **argv));

#ifndef PROTO
void 
#ifdef VIMDLL
_export
#endif
SaveInst(HINSTANCE hInst)
{
    s_hinst = hInst;
}
#endif
