Logo Search packages:      
Sourcecode: vile version File versions

ntwinio.c

/*
 * Uses the Win32 screen API.
 *
 * $Header: /usr/build/vile/vile/RCS/ntwinio.c,v 1.133 2002/11/05 20:28:02 tom Exp $
 * Written by T.E.Dickey for vile (october 1997).
 * -- improvements by Clark Morgan (see w32cbrd.c, w32pipe.c).
 */

#include "estruct.h"          /* includes <windows.h> */

#include <windowsx.h>
#include <commdlg.h>
#include <shellapi.h>
#include <stdlib.h>
#include <math.h>

#include "edef.h"
#include "pscreen.h"
#include "patchlev.h"
#include "winvile.h"
#include "nefsms.h"
#include "nefunc.h"

#define MIN_ROWS MINWLNS
#define MIN_COLS 15

#define FIXME_POSCHANGING 1   /* this doesn't seem to help */

#define MAX_CURSOR_STYLE 2

#if OPT_TRACE
#define IGN_PROC(tag,name) \
      case name: \
            TRACE((tag #name " (ignored)\n")); \
            break;

#define DEF_PROC(tag,name) \
      case name: \
            TRACE((tag #name " (%s)\n", which_window(hWnd))); \
            return (DefWindowProc(hWnd, message, wParam, lParam))
#else
#define IGN_PROC(tag,name) case name: break;
#define DEF_PROC(tag,name)    /*nothing */
#endif

#define MAIN_CLASS "VileMain"
#define TEXT_CLASS "VileText"
#define SCRL_CLASS "SCROLLBAR"
#define GRIP_CLASS "VileResize"

#define MY_APPLE "Vile Application"

#define MY_FONT  SYSTEM_FIXED_FONT  /* or ANSI_FIXED_FONT           */

#define NROW      128         /* Max Screen size.             */
#define NCOL    256           /* Edit if you want to.         */
#define     NPAUSE      200         /* # times thru update to pause */
#define NOKYMAP (-1)
#define KYREDIR (-2)          /* sent keystroke elsewhere.    */

#define SetCols(value) term.cols = cur_win->cols = value
#define SetRows(value) term.rows = cur_win->rows = value

#define RowToPixel(n) ((n) * nLineHeight)
#define ColToPixel(n) ((n) * nCharWidth)

#define PixelToRow(n) ((n) / nLineHeight)
#define PixelToCol(n) ((n) / nCharWidth)

#define RectToCols(rect) (PixelToCol(rect.right - rect.left - SbWidth))
#define RectToRows(rect) (PixelToRow(rect.bottom - rect.top))

#if OPT_SCROLLBARS
#define SbWidth   nLineHeight
#else
#define SbWidth   0
#endif

static DWORD default_bcolor;
static DWORD default_fcolor;
static HANDLE hAccTable;      /* handle to accelerator table */
static HANDLE vile_hinstance;
static HCURSOR arrow_cursor;
static HCURSOR hglass_cursor;
static HMENU vile_menu, popup_menu;
static LOGFONT vile_logfont;
static UINT nIDTimer = 0;
static int ac_active = FALSE; /* AutoColor active? */
static int caret_disabled = TRUE;
static int caret_exists = 0;
static int caret_visible = 0;
static int desired_wdw_state;
static int dont_update_sb = FALSE;
static int enable_popup = TRUE;
static int font_resize_in_progress;
static int gui_resize_in_progress;
static int initialized = FALSE;     /* winvile open for business */
static int mouse_captured = 0;
static int nCharWidth = 8;
static int nLineHeight = 10;
static int vile_in_getfkey = 0;
static int vile_resizing = FALSE;   /* rely on repaint_window if true */
static int icursor;           /* T -> enable insertion cursor   */
static int icursor_style;     /* 1 -> cmdmode = block,
                         *      insmode = vertical bar
                         * 2 -> cmdmode = vertical bar
                         *      insmode = block
                         */

#ifdef VILE_OLE
static OLEAUTO_OPTIONS oa_opts;
static int redirect_keys;
#endif

static int nfcolor = -1;      /* normal foreground color */
static int nbcolor = -1;      /* normal background color */
static int crow = -1;         /* current row */
static int ccol = -1;         /* current col */

/* ansi to ibm color translation table */
static const char *initpalettestr = "0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15";
/* black, red, green, yellow, blue, magenta, cyan, white   */

static int cur_pos = 0;
static VIDEO_ATTR cur_atr = 0;

typedef struct {
    HWND w;
    RECT r;
    int shown;
} SBDATA;

static struct gui_info {
    HWND main_hwnd;           /* the top-level window */
    HWND text_hwnd;           /* the enclosed text area */
    int closed;
    int cols;
    int rows;
    RECT xy_limit;
    int x_border;
    int y_border;
    int y_titles;       /* height of caption/titles */
    int nscrollbars;
    int maxscrollbars;
    SBDATA *scrollbars;
    SBDATA size_box;
    SBDATA size_grip;
} only_window, *cur_win = &only_window;
/* *INDENT-OFF* */
static struct my_font {
    int used;
    HFONT font;
    VIDEO_ATTR attr;
} MyFonts[] = {
    { FALSE, 0, 0 },
    { FALSE, 0, VABOLD },
    { FALSE, 0, VAUL },
    { FALSE, 0, VAITAL },
};
/* *INDENT-ON* */

#if OPT_SCROLLBARS
static int check_scrollbar_allocs(void);
static void update_scrollbar_sizes(void);
#endif

#if OPT_TRACE
static char *
which_window(HWND hwnd)
{
    if (hwnd == 0) {
      return "NULL";
    }
    if (hwnd == cur_win->main_hwnd) {
      return "main";
    } else if (hwnd == cur_win->text_hwnd) {
      return "text";
    } else {
      int n;
      static char temp[20];
      sprintf(temp, "%#lx", hwnd);
      for (n = 0; n < cur_win->nscrollbars; n++) {
          if (hwnd == cur_win->scrollbars[n].w) {
            sprintf(temp, "sb%d", n);
            break;
          }
      }
      return temp;
    }
}

static char *
message2s(unsigned code)
{
    static struct {
      WORD code;
      char *name;
    } table[] = {
      /* *INDENT-OFF* */
      { WM_ACTIVATE,          "WM_ACTIVATE" },
      { WM_ACTIVATEAPP, "WM_ACTIVATEAPP" },
      { WM_CANCELMODE,  "WM_CANCELMODE" },
      { WM_CAPTURECHANGED,    "WM_CAPTURECHANGED" },
      { WM_CLOSE,       "WM_CLOSE" },
      { WM_CONTEXTMENU, "WM_CONTEXTMENU" },
      { WM_CREATE,            "WM_CREATE" },
      { WM_CTLCOLORSCROLLBAR, "WM_CTLCOLORSCROLLBAR" },
      { WM_DROPFILES,         "WM_DROPFILES" },
      { WM_ENABLE,            "WM_ENABLE" },
      { WM_ENTERIDLE,         "WM_ENTERIDLE" },
      { WM_ENTERMENULOOP,     "WM_ENTERMENULOOP" },
      { WM_ENTERSIZEMOVE,     "WM_ENTERSIZEMOVE" },
      { WM_ERASEBKGND,  "WM_ERASEBKGND" },
      { WM_EXITMENULOOP,      "WM_EXITMENULOOP" },
      { WM_EXITSIZEMOVE,      "WM_EXITSIZEMOVE" },
      { WM_GETMINMAXINFO,     "WM_GETMINMAXINFO" },
      { WM_GETTEXT,           "WM_GETTEXT" },
      { WM_IME_NOTIFY,  "WM_IME_NOTIFY" },
      { WM_IME_SETCONTEXT,    "WM_IME_SETCONTEXT" },
      { WM_INITMENU,          "WM_INITMENU" },
      { WM_INITMENUPOPUP,     "WM_INITMENUPOPUP" },
      { WM_KEYDOWN,           "WM_KEYDOWN" },
      { WM_KEYUP,       "WM_KEYUP" },
      { WM_KILLFOCUS,         "WM_KILLFOCUS" },
      { WM_MENUSELECT,  "WM_MENUSELECT" },
      { WM_MOUSEACTIVATE,     "WM_MOUSEACTIVATE" },
      { WM_MOUSEMOVE,         "WM_MOUSEMOVE" },
      { WM_MOVE,        "WM_MOVE" },
      { WM_MOVING,            "WM_MOVING" },
      { WM_NCACTIVATE,  "WM_NCACTIVATE" },
      { WM_NCCALCSIZE,  "WM_NCCALCSIZE" },
      { WM_NCCREATE,          "WM_NCCREATE" },
      { WM_NCHITTEST,         "WM_NCHITTEST" },
      { WM_NCLBUTTONDOWN,     "WM_NCLBUTTONDOWN" },
      { WM_NCMOUSEMOVE, "WM_NCMOUSEMOVE" },
      { WM_NCPAINT,           "WM_NCPAINT" },
      { WM_PAINT,       "WM_PAINT" },
      { WM_PARENTNOTIFY,      "WM_PARENTNOTIFY" },
      { WM_QUERYNEWPALETTE,   "WM_QUERYNEWPALETTE" },
      { WM_RBUTTONUP,         "WM_RBUTTONUP" },
      { WM_SETCURSOR,         "WM_SETCURSOR" },
      { WM_SETFOCUS,          "WM_SETFOCUS" },
      { WM_SETTEXT,           "WM_SETTEXT" },
      { WM_SHOWWINDOW,  "WM_SHOWWINDOW" },
      { WM_SIZE,        "WM_SIZE" },
      { WM_SIZING,            "WM_SIZING" },
      { WM_STYLECHANGED,      "WM_STYLECHANGED" },
      { WM_STYLECHANGING,     "WM_STYLECHANGING" },
      { WM_SYSCOMMAND,  "WM_SYSCOMMAND" },
      { WM_WINDOWPOSCHANGED,  "WM_WINDOWPOSCHANGED" },
      { WM_WINDOWPOSCHANGING, "WM_WINDOWPOSCHANGING" },
      /* custom ntwinio WINDOWS messages */
      { WM_WVILE_CURSOR_ON,   "WM_WVILE_CURSOR_ON" },
      { WM_WVILE_CURSOR_OFF,  "WM_WVILE_CURSOR_OFF" },
      /* *INDENT-ON* */

    };
    size_t n;
    static char temp[20];

    for (n = 0; n < TABLESIZE(table); n++) {
      if (table[n].code == code) {
          return table[n].name;
      }
    }
    sprintf(temp, "%#x", code);
    return temp;
}

static char *
syscommand2s(unsigned code)
{
    static struct {
      WORD code;
      char *name;
    } table[] = {
      /* *INDENT-OFF* */
      { SC_SIZE,        "SC_SIZE" },
      { SC_MOVE,        "SC_MOVE" },
      { SC_MINIMIZE,          "SC_MINIMIZE" },
      { SC_MAXIMIZE,          "SC_MAXIMIZE" },
      { SC_NEXTWINDOW,  "SC_NEXTWINDOW" },
      { SC_PREVWINDOW,  "SC_PREVWINDOW" },
      { SC_CLOSE,       "SC_CLOSE" },
      { SC_VSCROLL,           "SC_VSCROLL" },
      { SC_HSCROLL,           "SC_HSCROLL" },
      { SC_MOUSEMENU,         "SC_MOUSEMENU" },
      { SC_KEYMENU,           "SC_KEYMENU" },
      { SC_ARRANGE,           "SC_ARRANGE" },
      { SC_RESTORE,           "SC_RESTORE" },
      { SC_TASKLIST,          "SC_TASKLIST" },
      { SC_SCREENSAVE,  "SC_SCREENSAVE" },
      { SC_HOTKEY,            "SC_HOTKEY" },
      /* *INDENT-ON* */

    };
    size_t n;
    static char temp[80];
    unsigned remainder = code & 0xf;

    for (n = 0; n < TABLESIZE(table); n++) {
      if (table[n].code == (code - remainder)) {
          sprintf(temp, "%s+%X", table[n].name, remainder);
          return temp;
      }
    }
    sprintf(temp, "%#x", code);
    return temp;
}

static void
TraceWindowRect(HWND hwnd)
{
    RECT wrect;
    int rows;
    int cols;
    GetWindowRect(hwnd, &wrect);
    cols = RectToCols(wrect);
    rows = RectToRows(wrect);
    TRACE(("... (%3d,%3d) (%3d,%3d), window is %dx%d cells (%dx%d pixels)%s\n",
         wrect.top, wrect.left,
         wrect.bottom, wrect.right,
         rows,
         cols,
         RowToPixel(rows),
         ColToPixel(cols),
         IsZoomed(hwnd) ? " zoomed" : ""
        ));
}

static void
TraceClientRect(HWND hwnd)
{
    RECT crect;
    int rows;
    int cols;
    GetClientRect(hwnd, &crect);
    cols = RectToCols(crect);
    rows = RectToRows(crect);
    TRACE(("... (%3d,%3d) (%3d,%3d), client is %dx%d cells (%dx%d pixels)\n",
         crect.top, crect.left,
         crect.bottom, crect.right,
         rows,
         cols,
         RowToPixel(rows),
         ColToPixel(cols)
        ));
}

#else
#define TraceWindowRect(hwnd) /* nothing */
#define TraceClientRect(hwnd) /* nothing */
#endif

static HBRUSH
Background(HDC hdc)
{
    TRACE(("Background %#06x\n", GetBkColor(hdc)));
    return CreateSolidBrush(GetBkColor(hdc));
}

void
gui_resize(int cols, int rows)
{
    RECT crect;
    RECT wrect;
    int main_wide;
    int main_high;
    int text_wide;
    int text_high;

    /*
     * There's an undesirable feedback loop between gui_resize() and
     * ResizeClient() when the user changes the font.  Here's how it
     * goes:
     *
     * - user changes font
     * - use_font() is eventually called
     * - use_font() calls gui_resize()
     * - gui_resize calls MoveWindow(cur_win->main_hwnd, ...) which triggers
     *       a call to MainWndProc(), which calls ResizeClient()
     * - ResizeClient() calls gui_resize()
     *
     * ResizeClient(), unfortunately, recomputes the editor's rows and
     * cols and sometimes gets it wrong, especially when the number of
     * rows exceeds a magic value (e.g., 37 rows on a 1024 x 768
     * screen).
     *
     * To stop the feedback loop, check to see if an instance of
     * gui_resize() is active, and if so, exit.
     *
     * Note:  even though "gui_resize_in_progress" breaks the
     * aforementioned feedback loop, bear in mind that it's still not
     * possible to define a screen geometry that's greater than the
     * dimensions of the current desktop.  This restriction is enforced by
     * Windows when the first ShowWindow() call is made from
     * winvile_start().
     */
    if (gui_resize_in_progress && font_resize_in_progress)
      return;
    gui_resize_in_progress = TRUE;

    TRACE(("gui_resize(%d x %d)\n", rows, cols));
    TraceWindowRect(cur_win->main_hwnd);
    TraceClientRect(cur_win->main_hwnd);

    GetClientRect(cur_win->main_hwnd, &crect);
    GetWindowRect(cur_win->main_hwnd, &wrect);

    text_wide = ColToPixel(cols);
    text_high = RowToPixel(rows);
    wrect.right += text_wide - crect.right;
    wrect.bottom += text_high - crect.bottom;
#if FIXME_POSCHANGING
    main_wide = text_wide + (2 * cur_win->x_border) + SbWidth;
    main_high = text_high + (2 * cur_win->y_border) + cur_win->y_titles;
#else
    main_wide = wrect.right - wrect.left + SbWidth;
    main_high = wrect.bottom - wrect.top;
#endif

    TRACE(("... gui_resize -> (%d,%d) (%d,%d) main %dx%d, text %dx%d\n",
         wrect.top,
         wrect.left,
         wrect.bottom,
         wrect.right,
         main_high,
         main_wide,
         text_high,
         text_wide));

    MoveWindow(cur_win->main_hwnd,
             wrect.left,
             wrect.top,
             main_wide,
             main_high,
             TRUE);
    MoveWindow(cur_win->text_hwnd,
             0,
             0,
             text_wide,
             text_high,
             TRUE);

    TraceWindowRect(cur_win->main_hwnd);
    TraceClientRect(cur_win->main_hwnd);
#if OPT_SCROLLBARS
    update_scrollbar_sizes();
#endif
    TRACE(("... gui_resize finish\n"));
    gui_resize_in_progress = FALSE;
}

static int
AdjustedHeight(int high)
{
    int extra = cur_win->y_titles + (2 * cur_win->y_border);
    int rows;
    if (high > cur_win->xy_limit.bottom)
      high = cur_win->xy_limit.bottom;
    rows = (high - extra) / nLineHeight;
    if (rows < MIN_ROWS)
      rows = MIN_ROWS;
    return (rows * nLineHeight) + extra;
}

static int
AdjustedWidth(int wide)
{
    int extra = SbWidth + (2 * cur_win->x_border);
    int cols;
    if (wide > cur_win->xy_limit.right)
      wide = cur_win->xy_limit.right;
    cols = (wide - extra) / nCharWidth;
    if (cols < MIN_COLS)
      cols = MIN_COLS;
    return (cols * nCharWidth) + extra;
}

#if FIXME_POSCHANGING
static int
AdjustPosChanging(HWND hwnd, WINDOWPOS * pos)
{
    if (!(pos->flags & SWP_NOSIZE)) {
      int wide = AdjustedWidth(pos->cx);
      int high = AdjustedHeight(pos->cy);

      TRACE(("...%s position %d,%d, resize from %d,%d to %d,%d\n",
             IsZoomed(hwnd) ? " zoomed" : "",
             pos->y, pos->x,
             pos->cy, pos->cx, high, wide));
      pos->cx = wide;
      pos->cy = high;
    }
    return 0;
}
#endif

static HWND
sizing_window(void)
{
    RECT crect;
    int szw = 140, szh = 22, szx, szy;

    GetClientRect(cur_win->main_hwnd, &crect);
    szx = crect.right / 2 - szw / 2;
    szy = crect.bottom / 2 - szh / 2;
    if (szx < 0 || szy < 0)
      szx = szy = CW_USEDEFAULT;
    return (CreateWindow("STATIC",
                   "",
                   WS_CHILD | WS_VISIBLE | SS_CENTER,
                   szx,
                   szy,
                   szw,
                   szh,
                   cur_win->main_hwnd,
                   (HMENU) 0,
                   vile_hinstance,
                   (LPVOID) 0));
}

/*
 * Handle WM_SIZING, forcing the screen size to stay in multiples of a
 * character cell.
 */
static int
AdjustResizing(HWND hwnd, WPARAM fwSide, RECT * rect)
{
    int wide = rect->right - rect->left;
    int high = rect->bottom - rect->top;
    int adjX = wide - AdjustedWidth(wide);
    int adjY = high - AdjustedHeight(high);

    TRACE(("AdjustResizing now (%d,%d) (%d,%d) (%dx%d pixels)\n",
         rect->top, rect->left, rect->bottom, rect->right,
         rect->bottom - rect->top,
         rect->right - rect->left));

    TraceWindowRect(hwnd);
    TraceClientRect(hwnd);

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

    if (fwSide == WMSZ_TOP
      || fwSide == WMSZ_TOPLEFT
      || fwSide == WMSZ_TOPRIGHT)
      rect->top += adjY;
    else if (fwSide == WMSZ_BOTTOM
           || fwSide == WMSZ_BOTTOMLEFT
           || fwSide == WMSZ_BOTTOMRIGHT)
      rect->bottom -= adjY;

    TRACE(("... AdjustResizing (%d,%d) (%d,%d) adjY:%d, adjX:%d\n",
         rect->top, rect->left, rect->bottom, rect->right,
         adjY, adjX));

    return TRUE;
}

static void
ResizeClient(void)
{
    int h, w;
    RECT crect;

    if (cur_win->closed) {
      TRACE(("ResizeClient ignored (closed)\n"));
      return;
    }

    /*
     * See comments in gui_resize() for an explanation of
     * gui_resize_in_progress.
     */
    if (gui_resize_in_progress && font_resize_in_progress)
      return;
    TRACE(("ResizeClient begin, currently %dx%d\n", term.rows, term.cols));
    TraceWindowRect(cur_win->main_hwnd);
    TraceClientRect(cur_win->main_hwnd);
    GetClientRect(cur_win->main_hwnd, &crect);

    h = RectToRows(crect);
    w = RectToCols(crect);

    /*
     * The WM_WINDOWPOSCHANGING message is supposed to allow modification
     * to keep a window in bounds.  But it doesn't work.  This does (by
     * forcing the calls on MoveWindow to have a "good" value).
     */
    if (h < MIN_ROWS)
      h = MIN_ROWS;

    if (w < MIN_COLS)
      w = MIN_COLS;

    if ((h > 1 && h != term.rows) || (w > 1 && w != term.cols)) {
      TRACE(("...ResizeClient %dx%d\n", h, w));
      vile_resizing = TRUE;
      newscreensize(h, w);
      SetRows(h);
      SetCols(w);
#if OPT_SCROLLBARS
      if (check_scrollbar_allocs() == TRUE)     /* no allocation failure */
          update_scrollbar_sizes();
#endif
      vile_resizing = FALSE;
      TRACE(("...ResizeClient %dx%d\n", h, w));
    } else {
      TRACE(("ResizeClient ignored (h=%d, w=%d, vs %d x %d)\n",
             h, w, term.rows, term.cols));
    }

    gui_resize(w, h);
    TRACE(("...ResizeClient finish\n"));
}

static int RedValue[NCOLORS], GreenValue[NCOLORS], BlueValue[NCOLORS];

/*
 * Ask for a palette setting string.
 * Format is "<entry> <red> <green> <blue>"
 */
int
SetRGBPalette(int f, int n)
{
#define RGB_DATA(name) { name, 0 }
    static struct {
      char *name;
      int value;
    } table[3] = {
      RGB_DATA("red"), RGB_DATA("green"), RGB_DATA("blue")
    };

    char *next;
    char prompt[NLINE + 1];
    char tstring[NLINE + 1];  /* string to add */
    const FSM_CHOICES *fp = name_to_choices("fcolor");
    int code;
    int count = 0;
    int status;               /* status return code */
    long value;

    *tstring = EOS;
    status = kbd_string("Color: ", tstring, sizeof(tstring), ' ',
                  KBD_NORMAL, fsm_complete);
    if (status != TRUE)
      return (status);

    code = choice_to_code(fp, tstring, strlen(tstring));
    if (code < 0) {
      next = 0;
      code = strtol(tstring, &next, 0);
      if (code < 0 || code >= NCOLORS)
          return FALSE;
    }

    /* ask for setting string */
    while (count < 3) {
      *tstring = EOS;
      lsprintf(prompt, "Palette value (%s): ", table[count].name);
      status = mlreply(prompt, tstring, sizeof(tstring));
      if (status != TRUE)
          return (status);
      next = 0;
      value = strtol(tstring, &next, 0);
      if (next == 0 || *next)
          return FALSE;
      table[count++].value = value & 255;
    }

    RedValue[code] = table[0].value;
    GreenValue[code] = table[1].value;
    BlueValue[code] = table[2].value;

    set_winflags(TRUE, WFHARD | WFCOLR);
    vile_refresh(FALSE, 1);
    return (TRUE);
}

/*
 * Set default color map palette
 */
int
ResetRGBPalette(int f, int n)
{
    int code;
    int red, green, blue;
    int first = f ? n : 0;
    int last = f ? n + 1 : NCOLORS;

    for (code = first; code < last && code < NCOLORS; code++) {
      red = green = blue = 0;
      if (code & 1)
          red = rgb_normal;
      if (code & 2)
          green = rgb_normal;
      if (code & 4)
          blue = rgb_normal;
      if (code & 8) {
          if (red)
            red = rgb_bright;
          if (green)
            green = rgb_bright;
          if (blue)
            blue = rgb_bright;
          if (code == 8) {
            red = rgb_gray;
            green = rgb_gray;
            blue = rgb_gray;
          }
      }
      RedValue[code] = red;
      GreenValue[code] = green;
      BlueValue[code] = blue;
    }
    set_winflags(TRUE, WFHARD | WFCOLR);
    vile_refresh(FALSE, 1);
    return TRUE;
}

static COLORREF
color_of(int code)
{
    code = ctrans[code & (NCOLORS - 1)];

    return PALETTERGB(RedValue[code], GreenValue[code], BlueValue[code]);
}

/*
 * Translate the attribute into indices into the color table.
 */
static int
attr_to_colors(VIDEO_ATTR attr, int *fcolor, int *bcolor)
{
    int ninvert = FALSE;      /* normal colors inverted? */

    *fcolor = nfcolor;
    *bcolor = nbcolor;

    attr &= (VASPCOL | VACOLOR | VABOLD | VAITAL | VASEL | VAREV);
    if (attr) {
      ninvert = ((attr & (VASEL | VAREV)) == VASEL
               || (attr & (VASEL | VAREV)) == VAREV);

      if (attr & VASPCOL)
          *fcolor = (VCOLORNUM(attr) & (NCOLORS - 1));
      else if (attr & VACOLOR)
          *fcolor = ((VCOLORNUM(attr)) & (NCOLORS - 1));

      if (ninvert) {
          int temp = *bcolor;
          *bcolor = *fcolor;
          *fcolor = temp;
      }
      TRACE2(("attr_to_colors(%04x) fg=%2d, bg=%2d %s\n",
            attr, *fcolor, *bcolor, ninvert ? "INVERT" : ""));
    }
    return ninvert;
}

static int
fake_color(int current, int nominal)
{
    int x = nominal;
    if (current == ENUM_FCOLOR) {
      int r = GetRValue(nominal) ^ 0x40;
      int g = GetGValue(nominal) ^ 0x40;
      int b = GetBValue(nominal) ^ 0x40;
      x = PALETTERGB(r, g, b);
    }
    TRACE2(("fake_color(%d, %#x) = %#x\n", current, nominal, x));
    nominal = x;
    return nominal;
}

/*
 * Provide an array of inter-character spacing, needed to force bold and italic fonts
 * to align with the normal font.
 */
static INT *
intercharacter(int cols)
{
    if (cols == 0 || nCharWidth == 0)
      return 0;
    else {
      static INT *result;
      static unsigned length;

      if (++cols >= (int) length)
          result = typereallocn(INT, result, length = cols);
      while (--cols >= 0)
          result[cols] = nCharWidth;
      return result;
    }
}

static HFONT
GetMyFont(VIDEO_ATTR attr)
{
    unsigned n;

    if ((attr & (VAUL | VABOLD | VAITAL)) != 0) {
      for (n = 1; n < TABLESIZE(MyFonts); ++n) {
          if ((attr & MyFonts[n].attr) == MyFonts[n].attr)
            break;
      }
    } else {
      n = 0;
    }

    if (n >= TABLESIZE(MyFonts)) {
      return GetMyFont(VAUL);
    } else if (!MyFonts[n].used) {
      LOGFONT logfont = vile_logfont;
      logfont.lfItalic = (BYTE) ((attr & VAITAL) != 0);
      logfont.lfUnderline = (BYTE) ((attr & VAUL) != 0);
      if (attr & VABOLD)
          logfont.lfWeight = FW_SEMIBOLD;
      if ((MyFonts[n].font = CreateFontIndirect(&logfont)) != 0) {
          MyFonts[n].used = TRUE;
      } else if ((attr & (VABOLD | VAITAL)) == (VABOLD | VAITAL)) {
          return GetMyFont(VABOLD);
      } else if ((attr & VAUL) != 0) {
          return GetMyFont(VAUL);
      } else {
          n = 0;
      }
    } else if (MyFonts[n].font == 0) {
      n = 0;                  /* live with previous failure */
    }

    return MyFonts[n].font;
}

static void
SetMyFont(HFONT font, LOGFONT * lf)
{
    unsigned n;

    for (n = 0; n < TABLESIZE(MyFonts); ++n) {
      if (MyFonts[n].used) {
          DeleteObject(MyFonts[n].font);
          MyFonts[n].used = FALSE;
      }
    }
    MyFonts[0].font = font;
    MyFonts[0].used = TRUE;
    if (lf != 0 && lf != &vile_logfont)
      vile_logfont = *lf;
}

static HDC
get_DC_with_Font(HFONT font)
{
    HDC hDC = GetDC(cur_win->text_hwnd);
    if (hDC != 0) {
      if (SelectObject(hDC, font) == 0)
          ReleaseDC(cur_win->text_hwnd, hDC);
    }
    return hDC;
}

static void
nt_set_colors(HDC hdc, VIDEO_ATTR attr)
{
    int fcolor;
    int bcolor;
    int ninvert;

    SelectObject(hdc, GetMyFont(attr));
#ifdef GVAL_VIDEO
    attr ^= global_g_val(GVAL_VIDEO);
#endif
    ninvert = attr_to_colors(attr, &fcolor, &bcolor);

    if (fcolor < 0)
      fcolor = ninvert
          ? fake_color(fcolor, default_fcolor)
          : fake_color(fcolor, default_bcolor);
    else
      fcolor = color_of(fcolor);

    if (bcolor < 0)
      bcolor = ninvert
          ? fake_color(bcolor, default_bcolor)
          : fake_color(bcolor, default_fcolor);
    else
      bcolor = color_of(bcolor);

    SetTextColor(hdc, fcolor);
    SetBkColor(hdc, bcolor);
}

static int
fhide_cursor(void)
{
    TRACE(("fhide_cursor pos %#lx,%#lx (visible:%d, exists:%d)\n", ttrow,
         ttcol, caret_visible, caret_exists));
    if (caret_visible) {
      HideCaret(cur_win->text_hwnd);
      caret_visible = 0;
    }
    if (caret_exists) {
      DestroyCaret();
      caret_exists = 0;
    }
    return 0;
}

static void
fshow_cursor(void)
{
    int x, y, width;
    POINT z;

    if (caret_disabled        /* reject display during font-dialog */
      || ttrow > term.rows    /* reject bogus position in init */
      || ttcol > term.cols)
      return;

    TRACE(("fshow_cursor pos %#lx,%#lx (visible:%d, exists:%d)\n", ttrow,
         ttcol, caret_visible, caret_exists));
    x = ColToPixel(ttcol) + 1;
    y = RowToPixel(ttrow) + 1;
    if (caret_exists) {
      GetCaretPos(&z);
      if (x != z.x
          || y != z.y)
          fhide_cursor();
    }

    if (!caret_exists) {
      width = nCharWidth;     /* assume block cursor */
#if OPT_ICURSOR
      if (icursor) {
          int vertbar = FALSE;

          if (insertmode) {
            if (icursor_style == 1)
                vertbar = TRUE;
          } else {            /* command mode */
            if (icursor_style == 2)
                vertbar = TRUE;
          }
          if (vertbar) {
            if (width < 5)
                width = 2;
            else
                width = 3;
          }
      }
#endif
      TRACE(("...CreateCaret(%d,%d)\n", ttrow, ttcol));
      CreateCaret(cur_win->text_hwnd, (HBITMAP) 0, width, nLineHeight);
      caret_exists = 1;
      SetCaretPos(x, y);
      ShowCaret(cur_win->text_hwnd);
      caret_visible = 1;
    }
}

#if OPT_ICURSOR
static void
nticursor(int unused)
{
    if (icursor && caret_visible) {
      fhide_cursor();         /* Kill the old caret        */
      fshow_cursor();         /* And bring it back to life */
    }
}
#endif

static void
get_borders(void)
{
    SystemParametersInfo(SPI_GETWORKAREA, 0, &cur_win->xy_limit, 0);
    TRACE(("WORKAREA: %d,%d %d,%d\n",
         cur_win->xy_limit.top,
         cur_win->xy_limit.left,
         cur_win->xy_limit.right,
         cur_win->xy_limit.bottom));
    cur_win->x_border = GetSystemMetrics(SM_CXSIZEFRAME);
    cur_win->y_border = GetSystemMetrics(SM_CYSIZEFRAME);
    cur_win->y_titles = GetSystemMetrics(SM_CYCAPTION);
    TRACE(("X border: %d, Y border: %d\n", cur_win->x_border, cur_win->y_border));
    TRACE(("CYFRAME:   %d\n", GetSystemMetrics(SM_CYFRAME)));
    TRACE(("CYCAPTION: %d\n", cur_win->y_titles));
}

/*
 * Note 1: lpntm is a pointer to a TEXTMETRIC struct if FontType does not
 * have TRUETYPE_FONTTYPE set, but we only need the tmPitchAndFamily member,
 * which has the same alignment as in NEWTEXTMETRIC.
 *
 * Note 2: enumerate_fonts() is an instance of FONTENUMPROC.  The parameter
 * types specified for this function do not match the windows docu I
 * have in my possession (MSDN JAN 1998), but they do match the
 * declarations in wingdi.h .  When in doubt, use the source :-) .
 */
static int CALLBACK
enumerate_fonts(
               const LOGFONT * lpelf,
               const TEXTMETRIC * lpntm,
               DWORD FontType,
               LPARAM lParam)
{
    int code = 2;
    const LOGFONT *src = lpelf;
    LOGFONT *dst = ((LOGFONT *) lParam);

    if ((src->lfPitchAndFamily & 3) != FIXED_PITCH) {
      code = 1;
    } else {
      *dst = *src;
      if (src->lfCharSet == ANSI_CHARSET) {
          code = 0;
          TRACE(("Found good font:%s\n", lpelf->lfFaceName));
      }
      TRACE(("Found ok font:%s\n", lpelf->lfFaceName));
      TRACE(("Pitch/Family:   %#x\n", dst->lfPitchAndFamily));
    }

    return code;
}

static int
is_fixed_pitch(HFONT font)
{
    BOOL ok;
    HDC hDC;
    TEXTMETRIC metrics;

    hDC = get_DC_with_Font(font);
    ok = GetTextMetrics(hDC, &metrics);
    ReleaseDC(cur_win->text_hwnd, hDC);

    if (ok)
      ok = ((metrics.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0);

    TRACE(("is_fixed_pitch: %d\n", ok));
    TRACE(("Ave Text width: %d\n", metrics.tmAveCharWidth));
    TRACE(("Max Text width: %d\n", metrics.tmMaxCharWidth));
    TRACE(("Pitch/Family:   %#x\n", metrics.tmPitchAndFamily));

    return (ok);
}

static int
new_font(LOGFONT * lf)
{
    HFONT font = CreateFontIndirect(lf);

    if (font != 0) {
      if (is_fixed_pitch(font)) {
          SetMyFont(font, lf);
          TRACE(("created new font\n"));
          return TRUE;
      } else
          DeleteObject(font);
    }
    return FALSE;
}

static void
get_font(LOGFONT * lf)
{
    HDC hDC;

    SetMyFont(GetStockObject(MY_FONT), 0);
    hDC = GetDC(cur_win->text_hwnd);
    if (EnumFontFamilies(hDC, NULL, enumerate_fonts, (LPARAM) lf) <= 0) {
      TRACE(("Creating Pitch/Family: %#x\n", lf->lfPitchAndFamily));
      new_font(lf);
    }
    ReleaseDC(cur_win->text_hwnd, hDC);
}

static void
use_font(HFONT my_font)
{
    HDC hDC;
    TEXTMETRIC textmetric;
    int oLineHeight = nLineHeight;
    int oCharWidth = nCharWidth;

    hDC = get_DC_with_Font(my_font);
    GetTextMetrics(hDC, &textmetric);
    ReleaseDC(cur_win->text_hwnd, hDC);

    TRACE(("Text height:    %d\n", textmetric.tmHeight));
    TRACE(("Ave Text width: %d\n", textmetric.tmAveCharWidth));
    TRACE(("Max Text width: %d\n", textmetric.tmMaxCharWidth));
    TRACE(("Pitch/Family:   %#x\n", textmetric.tmPitchAndFamily));

    /*
     * We'll use the average text-width, since some fonts (e.g., Courier
     * New) have a bogus max text-width.
     */
    nLineHeight = textmetric.tmExternalLeading + textmetric.tmHeight;
    nCharWidth = textmetric.tmAveCharWidth;
    get_borders();

    font_resize_in_progress = (oLineHeight != nLineHeight)
      || (oCharWidth != nCharWidth);

    gui_resize(term.cols, term.rows);

    font_resize_in_progress = FALSE;
}

static void
set_font(void)
{
    HDC hDC;
    CHOOSEFONT choose;

    TRACE(("set_font\n"));
    fhide_cursor();
    caret_disabled = TRUE;
    memset(&choose, 0, sizeof(choose));
    choose.lStructSize = sizeof(choose);
    choose.hwndOwner = cur_win->text_hwnd;
    choose.Flags = CF_SCREENFONTS
      | CF_FIXEDPITCHONLY
      | CF_FORCEFONTEXIST
      | CF_NOSCRIPTSEL
      | CF_INITTOLOGFONTSTRUCT;
    choose.lpLogFont = &vile_logfont;

    hDC = get_DC_with_Font(GetMyFont(0));
    GetTextFace(hDC, sizeof(vile_logfont.lfFaceName), vile_logfont.lfFaceName);
    ReleaseDC(cur_win->text_hwnd, hDC);

    vile_logfont.lfPitchAndFamily = FIXED_PITCH | FF_MODERN;
    vile_logfont.lfCharSet = ANSI_CHARSET;
    TRACE(("LOGFONT Facename:%s\n", vile_logfont.lfFaceName));

    if (ChooseFont(&choose)) {
      TRACE(("ChooseFont '%s'\n", vile_logfont.lfFaceName));
      if (new_font(&vile_logfont)) {
          int saverow = ttrow;
          int savecol = ttcol;
          mlwrite("[Set font to %s]", vile_logfont.lfFaceName);
          movecursor(saverow, savecol);
          use_font(GetMyFont(0));
          vile_refresh(FALSE, 0);
          update(FALSE);
      } else {
          mlforce("[Cannot create font]");
      }
    } else {
      mlforce("[No font selected]");
    }

    caret_disabled = FALSE;
    fshow_cursor();
    TRACE(("...set_font, LOGFONT Facename:%s\n", vile_logfont.lfFaceName));
}

static int
last_w32_error(int use_msg_box)
{
    if (use_msg_box)
      disp_win32_error(W32_SYS_ERROR, winvile_hwnd());
    else {
      char *msg = NULL;

      fmt_win32_error(W32_SYS_ERROR, &msg, 0);
      mlforce(msg);
      LocalFree(msg);
    }
    return (FALSE);
}

/*
 * Set font from string specification.  See the function parse_font_str()
 * in file w32misc.c for acceptable font string syntax.
 *
 * Prerequistes before calling this function:
 *
 *    winvile's windows and default font created.
 *
 * Set use_mb (Boolean):
 *    T -> errors reported via MessageBox.
 *    F -> errors reported via message line.
 *
 * Returns: T -> all is well, F -> failure.
 */
int
ntwinio_font_frm_str(
                  const char *fontstr,
                  int use_mb)
{
    int face_specified;
    HDC hdc;
    HFONT hfont;
    HWND hwnd;
    LOGFONT logfont;
    char *msg, font_mapper_face[LF_FACESIZE + 1];
    TEXTMETRIC metrics;
    FONTSTR_OPTIONS str_rslts;

    if (!parse_font_str(fontstr, &str_rslts)) {
      msg = "Font syntax invalid";
      if (use_mb)
          MessageBox(winvile_hwnd(), msg, prognam, MB_ICONSTOP | MB_OK);
      else
          mlforce(msg);
      return (FALSE);
    }
    hwnd = cur_win->text_hwnd;
    face_specified = (str_rslts.face[0] != '\0');
    if ((hdc = get_DC_with_Font(GetMyFont(0))) == 0) {
      (void) last_w32_error(use_mb);
      return (FALSE);
    }
    if (!face_specified) {
      /* user didn't specify a face name, get current name. */

      if ((GetTextFace(hdc, sizeof(str_rslts.face), str_rslts.face)) == 0) {
          (void) last_w32_error(use_mb);
          ReleaseDC(hwnd, hdc);
          return (FALSE);
      }
    }

    /* Build up LOGFONT data structure. */
    memset(&logfont, 0, sizeof(logfont));
    logfont.lfWeight = (str_rslts.bold) ? FW_BOLD : FW_NORMAL;
    logfont.lfHeight = -MulDiv(str_rslts.size,
                         GetDeviceCaps(hdc, LOGPIXELSY),
                         72);
    if (str_rslts.italic)
      logfont.lfItalic = TRUE;
    logfont.lfCharSet = DEFAULT_CHARSET;
    logfont.lfPitchAndFamily = FIXED_PITCH | FF_MODERN;
    vl_strncpy(logfont.lfFaceName, str_rslts.face, LF_FACESIZE);
    logfont.lfFaceName[LF_FACESIZE - 1] = '\0';
    if (!((hfont = CreateFontIndirect(&logfont)) != 0 && SelectObject(hdc, hfont))) {
      (void) last_w32_error(use_mb);
      if (hfont)
          DeleteObject(hfont);
      ReleaseDC(hwnd, hdc);
      return (FALSE);
    }

    /*
     * The font mapper will substitute some other font for a font request
     * that cannot exactly be met.  Ck first to see if the face name
     * matches the user chosen face (if applicatble).
     */
    if (face_specified) {
      if ((GetTextFace(hdc, sizeof(font_mapper_face), font_mapper_face))
          == 0) {
          (void) last_w32_error(use_mb);
          ReleaseDC(hwnd, hdc);
          DeleteObject(hfont);
          return (FALSE);
      }
      if (stricmp(font_mapper_face, str_rslts.face) != 0) {
          ReleaseDC(hwnd, hdc);
          DeleteObject(hfont);
          msg = "Font face unknown or size/style unsupported";
          if (use_mb)
            MessageBox(winvile_hwnd(), msg, prognam, MB_ICONSTOP | MB_OK);
          else
            mlforce(msg);
          return (FALSE);
      }
    }

    /* Next, font must be fixed pitch. */
    if (!GetTextMetrics(hdc, &metrics)) {
      (void) last_w32_error(use_mb);
      ReleaseDC(hwnd, hdc);
      DeleteObject(hfont);
      return (FALSE);
    }
    if ((metrics.tmPitchAndFamily & TMPF_FIXED_PITCH) != 0) {
      /* Misnamed constant! */

      ReleaseDC(hwnd, hdc);
      DeleteObject(hfont);
      msg = "Font not fixed pitch";
      if (use_mb)
          MessageBox(winvile_hwnd(), msg, prognam, MB_ICONSTOP | MB_OK);
      else
          mlforce(msg);
      return (FALSE);
    }
    ReleaseDC(hwnd, hdc);     /* finally done with this */
    SetMyFont(hfont, &logfont);
    use_font(GetMyFont(0));
    vile_refresh(FALSE, 0);
    return (TRUE);
}

char *
ntwinio_current_font(void)
{
    static char *buf;
    HDC hdc;
    HWND hwnd;
    LONG size;
    char *style;

    if (!buf) {
      buf = castalloc(char,
                  sizeof("bold-italic") +
                  LF_FACESIZE +
                  16);  /* space for delimiters and point size */
      if (!buf)
          return ("out of memory");
    }
    hwnd = cur_win->text_hwnd;
    if ((hdc = get_DC_with_Font(GetMyFont(0))) == 0) {
      char *msg = NULL;
      fmt_win32_error(W32_SYS_ERROR, &msg, 0);
      return (msg);
      /* "msg" leaks here, but this code path should never be taken. */
    }
    if (vile_logfont.lfWeight == FW_BOLD && vile_logfont.lfItalic)
      style = "bold-italic";
    else if (vile_logfont.lfWeight == FW_BOLD)
      style = "bold";
    else if (vile_logfont.lfItalic)
      style = "italic";
    else
      style = NULL;
    size = MulDiv(labs(vile_logfont.lfHeight),
              72,
              GetDeviceCaps(hdc, LOGPIXELSY));
    sprintf(buf,
          "%s,%ld%s%s",
          vile_logfont.lfFaceName,
          size,
          (style) ? "," : "",
          (style) ? style : "");
    ReleaseDC(hwnd, hdc);
    return (buf);
}

#if OPT_TITLE
static void
nttitle(const char *title)
{                       /* set the current window title */
    SetWindowText(winvile_hwnd(), title);
}
#endif

static void
scflush(void)
{
    if (cur_pos && !vile_resizing) {
      HDC hdc;

      TRACE(("PUTC:flush %2d (%2d,%2d) (%.*s)\n", cur_pos, crow, ccol,
             cur_pos, &CELL_TEXT(crow, ccol)));

      hdc = GetDC(cur_win->text_hwnd);
      nt_set_colors(hdc, cur_atr);

      ExtTextOut(hdc,
               ColToPixel(ccol),
               RowToPixel(crow),
               0,
               (RECT *) 0,
               &CELL_TEXT(crow, ccol), cur_pos,
               intercharacter(cur_pos));

      ReleaseDC(cur_win->text_hwnd, hdc);
    }
    ccol = ccol + cur_pos;
    cur_pos = 0;
}

#if OPT_COLOR
static void
ntfcol(int color)
{                       /* set the current output color */
    scflush();
    nfcolor = color;
}

static void
ntbcol(int color)
{                       /* set the current background color */
    scflush();
    nbcolor = color;
}
#endif

static void
ntflush(void)
{
    scflush();
    SetCaretPos(ColToPixel(ccol), RowToPixel(crow));
}

static void
ntmove(int row, int col)
{
    scflush();
    crow = (short) row;
    ccol = (short) col;
}

/* erase to the end of the line */
static void
nteeol(void)
{
    HDC hDC;
    HBRUSH brush;
    RECT rect;
    int x;

    scflush();

    TRACE(("NTEEOL %d,%d, atr %#x\n", crow, ccol, cur_atr));
    for (x = ccol; x < term.cols; x++) {
      CELL_TEXT(crow, x) = ' ';
      CELL_ATTR(crow, x) = cur_atr;
    }

    GetClientRect(cur_win->text_hwnd, &rect);
    rect.left = ColToPixel(ccol);
    rect.top = RowToPixel(crow);
    rect.right = ColToPixel(term.cols);
    rect.bottom = RowToPixel(crow + 1);

    hDC = GetDC(cur_win->text_hwnd);
    nt_set_colors(hDC, cur_atr);
    brush = Background(hDC);
    FillRect(hDC, &rect, brush);
    DeleteObject(brush);
    ReleaseDC(cur_win->text_hwnd, hDC);
}

#if   OPT_FLASH
static void
flash_display(void)
{
    RECT rect;
    HDC hDC;

    GetClientRect(cur_win->text_hwnd, &rect);
    hDC = GetDC(cur_win->text_hwnd);
    InvertRect(hDC, &rect);
    Sleep(100);
    InvertRect(hDC, &rect);
    ReleaseDC(cur_win->text_hwnd, hDC);
}
#endif

static void
ntbeep(void)
{
#if   OPT_FLASH
    if (global_g_val(GMDFLASH)) {
      flash_display();
      return;
    }
#endif
    MessageBeep(0xffffffff);
}

/*
 * Move 'n' lines starting at 'from' to 'to'
 *
 * OPT_PRETTIER_SCROLL is prettier but slower -- it scrolls a line at a time
 *    instead of all at once.
 */

/* move howmany lines starting at from to to */
static void
ntscroll(int from, int to, int n)
{
    HDC hDC;
    HBRUSH brush;
    RECT region;
    RECT tofill;

    scflush();
    if (to == from)
      return;
#if OPT_PRETTIER_SCROLL
    if (absol(from - to) > 1) {
      ntscroll(from, (from < to) ? to - 1 : to + 1, n);
      if (from < to)
          from = to - 1;
      else
          from = to + 1;
    }
#endif

    region.left = 0;
    region.right = (SHORT) ColToPixel(term.cols);

    if (from > to) {
      region.top = (SHORT) RowToPixel(to);
      region.bottom = (SHORT) RowToPixel(from + n);
    } else {
      region.top = (SHORT) RowToPixel(from);
      region.bottom = (SHORT) RowToPixel(to + n);
    }

    TRACE(("ScrollWindowEx from=%d, to=%d, n=%d  (%d,%d)/(%d,%d)\n",
         from, to, n,
         region.left, region.top,
         region.right, region.bottom));

    ScrollWindowEx(
                  cur_win->text_hwnd,     /* handle of window to scroll */
                  0,    /* amount of horizontal scrolling */
                  RowToPixel(to - from),  /* amount of vertical scrolling */
                  &region,    /* address of structure with scroll rectangle */
                  &region,    /* address of structure with clip rectangle */
                  NULL, /* handle of update region */
                  &tofill,    /* invalidated rectangle */
                  0           /* scrolling flags */
      );

    /* Erase invalidated rectangle */
    TRACE(("ntscroll tofill: (%d,%d)/(%d,%d)\n",
         tofill.left, tofill.top,
         tofill.right, tofill.bottom));
    hDC = GetDC(cur_win->text_hwnd);
    nt_set_colors(hDC, cur_atr);
    brush = Background(hDC);
    FillRect(hDC, &tofill, brush);
    DeleteObject(brush);
    ReleaseDC(cur_win->text_hwnd, hDC);

    /*
     * force text_hwnd's WindProc to service a WM_PAINT message -- gives
     * faster screen updates.
     */
    UpdateWindow(cur_win->text_hwnd);
}

/*
 * vile very rarely generates any of the ASCII printing control characters
 * except for a few hand coded routines but we have to support them anyway.
 */

/* put a character at the current position in the current colors */
static void
ntputc(int ch)
{
    /* This is an optimization for the most common case. */
    if (ch >= ' ') {
      CELL_TEXT(crow, ccol + cur_pos) = (char) ch;
      CELL_ATTR(crow, ccol + cur_pos) = cur_atr;
      cur_pos++;
    } else {

      switch (ch) {

      case '\b':
          scflush();
          if (ccol)
            ccol--;
          break;

      case '\a':
          ntbeep();
          break;

      case '\t':
          scflush();
          do {
            CELL_TEXT(crow, ccol + cur_pos) = ' ';
            CELL_ATTR(crow, ccol + cur_pos) = cur_atr;
            cur_pos++;
          } while ((ccol + cur_pos) % 8 != 0);
          break;

      case '\r':
          scflush();
          ccol = 0;
          break;

      case '\n':
          scflush();
          if (crow < term.rows - 1)
            crow++;
          else
            ntscroll(1, 0, term.rows - 1);
          break;

      default:
          CELL_TEXT(crow, ccol + cur_pos) = (char) ch;
          CELL_ATTR(crow, ccol + cur_pos) = cur_atr;
          cur_pos++;
          break;
      }
    }
}

static void
nteeop(void)
{
    HDC hDC;
    HBRUSH brush;
    RECT rect;
    int y, x;

    scflush();

    TRACE(("NTEEOP %d,%d, atr %#x\n", crow, ccol, cur_atr));
    for (y = crow; y < term.rows; y++) {
      for (x = 0; x < term.cols; x++) {
          CELL_TEXT(y, x) = ' ';
          CELL_ATTR(y, x) = cur_atr;
      }
    }

    rect.left = ColToPixel(ccol);
    rect.top = RowToPixel(crow);
    rect.right = ColToPixel(term.cols);
    rect.bottom = RowToPixel(term.rows);

    if (!vile_resizing) {
      hDC = GetDC(cur_win->text_hwnd);
      nt_set_colors(hDC, cur_atr);
      brush = Background(hDC);
      FillRect(hDC, &rect, brush);
      DeleteObject(brush);
      ReleaseDC(cur_win->text_hwnd, hDC);
    }
}

static void
ntrev(UINT reverse)
{                       /* change reverse video state */
    scflush();
    cur_atr = (VIDEO_ATTR) reverse;
}

static int
ntcres(const char *res)
{                       /* change screen resolution */
    scflush();
    return 0;
}

static void
ntopen(void)
{
    TRACE(("ntopen\n"));

    set_colors(NCOLORS);
    set_palette(initpalettestr);
    ResetRGBPalette(FALSE, 1);
}

static int old_title_set = 0;
static char old_title[256];
static int orig_title_set = 0;
static char orig_title[256];

static void
ntclose(void)
{
    TRACE(("ntclose\n"));

    scflush();
    ntmove(term.rows - 1, 0);
    nteeol();
    ntflush();
}

static void
ntkopen(void)
{                       /* open the keyboard */
}

static void
ntkclose(void)
{                       /* close the keyboard */
}

static struct keyxlate_struct {
    int windows;
    int vile;
} keyxlate[] = {
    /* *INDENT-OFF* */
    { VK_NEXT,          KEY_Next },
    { VK_PRIOR,         KEY_Prior },
    { VK_END,           KEY_End },
    { VK_HOME,          KEY_Home },
    { VK_LEFT,          KEY_Left },
    { VK_RIGHT,         KEY_Right },
    { VK_UP,            KEY_Up },
    { VK_DOWN,          KEY_Down },
    { VK_INSERT,  KEY_Insert },
    { VK_DELETE,  KEY_Delete },
    { VK_HELP,          KEY_Help },
    { VK_SELECT,  KEY_Select },
#if 0
    /* Merely pressing the Alt key generates a VK_MENU key event. */
    { VK_MENU,          KEY_Menu },
#endif
    { VK_F1,            KEY_F1 },
    { VK_F2,            KEY_F2 },
    { VK_F3,            KEY_F3 },
    { VK_F4,            KEY_F4 },
    { VK_F5,            KEY_F5 },
    { VK_F6,            KEY_F6 },
    { VK_F7,            KEY_F7 },
    { VK_F8,            KEY_F8 },
    { VK_F9,            KEY_F9 },
    { VK_F10,           KEY_F10 },
    { VK_F11,           KEY_F11 },
    { VK_F12,           KEY_F12 },
    { VK_F13,           KEY_F13 },
    { VK_F14,           KEY_F14 },
    { VK_F15,           KEY_F15 },
    { VK_F16,           KEY_F16 },
    { VK_F17,           KEY_F17 },
    { VK_F18,           KEY_F18 },
    { VK_F19,           KEY_F19 },
    { VK_F20,           KEY_F20 },
    /* Allow ^-6 to invoke the alternate-buffer command, a la Unix.  */
    { '6',        '6' },
    /* Support recognition of ^@ */
    { '2',        '2' },
    /* *INDENT-ON* */

};

static int savedChar;
static int saveCount = 0;

static int
decode_key_event(KEY_EVENT_RECORD * irp)
{
    int i, key;
    struct keyxlate_struct *keyp;

    if ((key = (UCHAR) irp->uChar.AsciiChar) != 0)
      return key;

#if VILE_OLE
    if (redirect_keys &&
      oleauto_redirected_key(irp->wVirtualKeyCode, irp->dwControlKeyState)) {
      return (KYREDIR); /* Key sent to another window. */
    }
#endif

    key = NOKYMAP;
    for (i = 0, keyp = keyxlate; i < TABLESIZE(keyxlate); i++, keyp++) {
      if (keyp->windows == irp->wVirtualKeyCode) {
          DWORD state = irp->dwControlKeyState;

          /*
           * There are a few special keys that we must deal with here on an
           * ad hoc basis:
           *
           * ALT+F4 - This should _never_ be remapped by any user (nor
           * messed with by vile).
           * 
           * SHIFT+6 - This is actually '^^' -- leave it alone. 
           */
          if ((keyp->windows == VK_F4
             && (state & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)))
            || (keyp->windows == '6' && (state & SHIFT_PRESSED))) {
            TRACE(("decode_key_event - special\n"));
            break;
          }

          /*
           * Add the modifiers that we recognize.  Specifically, we don't
           * care about ENHANCED_KEY, since we already have portable
           * pageup/pagedown and arrow key bindings that would be lost if we
           * used the Win32-only definition.
           */
          if (state &
            (LEFT_CTRL_PRESSED
             | RIGHT_CTRL_PRESSED
             | LEFT_ALT_PRESSED
             | RIGHT_ALT_PRESSED
             | SHIFT_PRESSED)) {
            key = mod_KEY | keyp->vile;
            if (state & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))
                key |= mod_CTRL;
            if (state & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED))
                key |= mod_ALT;
            if (state & SHIFT_PRESSED)
                key |= mod_SHIFT;
            if (keyxlate[i].vile == '2') {
                if ((key & mod_CTRL) && ((key & mod_ALT) == 0)) {
                  /* either ^2 or ^@, => nul char */

                  key = 0;
                } else if ((key & mod_SHIFT) &&
                         ((key & (mod_ALT | mod_CTRL)) == 0)) {
                  /*
                   * this will be mapped to '@' if we let Windows do
                   * the translation
                   */

                  key = NOKYMAP;
                }
            }
          } else {
            key = keyp->vile;
          }
          TRACE(("decode_key_event %#x (%#x) -> %#x\n",
               irp->wVirtualKeyCode, state, key));
          break;
      }
    }

    return key;
}

static int
get_keyboard_state(void)
{
    int result = 0;

    if (GetKeyState(VK_CONTROL) < 0)
      result |= (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED);
    if (GetKeyState(VK_MENU) < 0)
      result |= (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED);
    if (GetKeyState(VK_SHIFT) < 0)
      result |= SHIFT_PRESSED;
    return result;
}

static void
enable_popup_menu(void)
{
    enable_popup = !enable_popup;
    set_global_g_val(GMDPOPUPMENU, enable_popup);
    CheckMenuItem(vile_menu,
              IDM_MENU,
              MF_BYCOMMAND | (enable_popup ? MF_CHECKED : MF_UNCHECKED));
    DrawMenuBar(cur_win->main_hwnd);
}

typedef struct popup_menu_info_struct {
    int menu_id;
    char *menu_name;
    char *vile_cmdname;

} POPUP_MENU_INFO;

static POPUP_MENU_INFO popup_menu_tbl[] =
{
    /* *INDENT-OFF* */
    {IDM_OPEN,          "&Open...", "winopen"},
    {IDM_SAVE_AS, "&Save As...",    "winsave"},
    {IDM_UNDO,          "&Undo",    "undo-changes-backward"},
    {IDM_REDO,          "&Redo",    "redo-changes-forward"},
    {IDM_CUT,           "Cu&t",           "cut-to-clipboard"},
    {IDM_COPY,          "&Copy",    "copy-unnamed-reg-to-clipboard"},
    {IDM_PASTE,         "&Paste",   "paste-from-clipboard"},
    {IDM_DELETE,  "&Delete",  "delete-text-selection"},
    {IDM_SELECT_ALL,    "Select &All",    "select-all"},
    /* *INDENT-ON* */

};

static void
invoke_popup_menu(MSG msg)
{
    char accel[NSTRING], buf[NSTRING * 2];
    AREGION ar;
    BUFFER *bp;
    const CMDFUNC *cmd;
    int i, limit, flag, seltxt, samebuf;
    POINT point;
    POPUP_MENU_INFO *ptbl;

    TRACE(("invoke_popup_menu\n"));
    if (popup_menu == NULL) {
      popup_menu = LoadMenu(vile_hinstance,
                        "WinvilePopMenu");
      popup_menu = GetSubMenu(popup_menu, 0);
    }
    CheckMenuItem(popup_menu, IDM_MENU, MF_BYCOMMAND | MF_CHECKED);

    /* add accelerators (key bindings) to RMB */
    limit = sizeof(popup_menu_tbl) / sizeof(popup_menu_tbl[0]);
    for (i = 0, ptbl = popup_menu_tbl; i < limit; i++, ptbl++) {
      if ((cmd = engl2fnc(ptbl->vile_cmdname)) != NULL) {

          if (fnc2kcod(cmd) != -1) {
            /* cmd has a binding */

            (void) kcod2prc(fnc2kcod(cmd), accel);
            sprintf(buf, "%s\t%s", ptbl->menu_name, accel);
          } else {
            /* rewrite menu text to clear out possible unmapped bindings */

            vl_strncpy(buf, ptbl->menu_name, sizeof(buf));
          }
          ModifyMenu(popup_menu,
                   ptbl->menu_id,
                   MF_BYCOMMAND | MF_STRING,
                   ptbl->menu_id,
                   buf);
      }
    }

    /* Can't undo/redo if not applicable. */
    flag = (undo_ok())? MF_ENABLED : MF_GRAYED;
    EnableMenuItem(popup_menu, IDM_UNDO, MF_BYCOMMAND | flag);
    flag = (redo_ok())? MF_ENABLED : MF_GRAYED;
    EnableMenuItem(popup_menu, IDM_REDO, MF_BYCOMMAND | flag);

    /* Can't "select all" in empty buffer. */
    flag = (curbp->b_bytecount > 0) ? MF_ENABLED : MF_GRAYED;
    EnableMenuItem(popup_menu, IDM_SELECT_ALL, MF_BYCOMMAND | flag);

    /* Can't paste data from clipboard if not TEXT. */
    flag = (IsClipboardFormatAvailable(CF_TEXT)) ? MF_ENABLED : MF_GRAYED;
    EnableMenuItem(popup_menu, IDM_PASTE, MF_BYCOMMAND | flag);

    /* Do we have a text selection? */
    if ((bp = get_selection_buffer_and_region(&ar)) != NULL)
      seltxt = MF_ENABLED;
    else
      seltxt = MF_GRAYED;

    /* Is the text selection in the current buffer? */
    if ((seltxt == MF_ENABLED) && (bp == curbp))
      samebuf = MF_ENABLED;
    else
      samebuf = MF_GRAYED;

    /* Modify menu items that depend on seltxt and samebuf flags. */
    EnableMenuItem(popup_menu, IDM_COPY, MF_BYCOMMAND | seltxt);
    EnableMenuItem(popup_menu, IDM_CUT, MF_BYCOMMAND | samebuf);
    EnableMenuItem(popup_menu, IDM_DELETE, MF_BYCOMMAND | samebuf);

    /* Show popup menu. */
    point.x = LOWORD(msg.lParam);
    point.y = HIWORD(msg.lParam);
    ClientToScreen(msg.hwnd, &point);
    TrackPopupMenu(popup_menu,
               0,
               point.x,
               point.y,
               0,
               msg.hwnd,
               NULL);
    TRACE(("...invoke_popup_menu\n"));
}

static BOOL CALLBACK
AboutBoxProc(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
    char buf[512];
    HWND hwnd;

    switch (iMsg) {
    case WM_INITDIALOG:
      /* set about box dialog title */
      sprintf(buf, "About %s", prognam);
      SetWindowText(hDlg, buf);

      /* announce program version */
      hwnd = GetDlgItem(hDlg, IDM_ABOUT_PROGNAME);
      sprintf(buf, "%s\n%s%s", prognam, version, PATCHLEVEL);
      SetWindowText(hwnd, buf);

      /* talk about copyright */
      hwnd = GetDlgItem(hDlg, IDM_ABOUT_COPYRIGHT);
      sprintf(buf,
            "\nCopyright \xA9 Thomas Dickey 1997-2002\n\n"
            "%s is free software, distributed under the terms of the GNU "
            "Public License (see COPYING).",
            prognam);
      SetWindowText(hwnd, buf);
      w32_center_window(hDlg, cur_win->main_hwnd);
      return (TRUE);
    case WM_COMMAND:
      switch (LOWORD(wParam)) {
      case IDOK:
      case IDCANCEL:
          EndDialog(hDlg, 0);
          return (TRUE);
      }
      break;
    }
    return (FALSE);
}

static int
handle_builtin_menu(WPARAM code)
{
    int result = TRUE;

    TRACE(("handle_builtin_menu code=%#x\n", code));
    switch (LOWORD(code)) {
    case IDM_ABOUT:
      DialogBox(vile_hinstance, "AboutBox", cur_win->main_hwnd, AboutBoxProc);
      break;
    case IDM_OPEN:
      winopen_dir(NULL);
      update(FALSE);
      break;
    case IDM_PRINT:
      winprint(0, 0);
      update(FALSE);
      break;
    case IDM_PAGE_SETUP:
      winpg_setup(0, 0);
      update(FALSE);
      break;
    case IDM_FAVORITES:
      winopen_dir(get_favorites());
      update(FALSE);
      break;
    case IDM_SAVE_AS:
      winsave_dir(NULL);
      update(FALSE);
      break;
    case IDM_SELECT_ALL:
      sel_all(0, 0);
      update(FALSE);
      break;
    case IDM_FONT:
      set_font();
      break;
    case IDM_MENU:
      enable_popup_menu();
      update(FALSE);
      break;
    case IDM_PASTE:
      /* it's okay to paste into minibuffer if reading msg line data */
      if ((!reading_msg_line) && b_val(curbp, MDVIEW))
          return rdonly();
      mayneedundo();
      fhide_cursor();
      cbrdpaste(FALSE, FALSE);
      update(FALSE);
      fshow_cursor();
      break;
    case IDM_REDO:
      forwredo(FALSE, FALSE);
      update(FALSE);
      break;
    case IDM_UNDO:
      backundo(FALSE, FALSE);
      update(FALSE);
      break;
    case IDM_COPY:
      /*
       * Copy selected text to clipboard.  Start by first yanking selected
       * text to unnamed register.  In most cases this won't be necessary,
       * especially if mouse is used to navigate and make selections.
       * However, there are cases where the user might select some text
       * (causes automatic yank to unnamed register), do something to
       * clobber the unnamed register, and then use the menu system to
       * attempt to copy selected text.
       */

      sel_yank(0);
      cbrdcpy_unnamed(FALSE, FALSE);
      update(FALSE);
      break;
    case IDM_CUT:
    case IDM_DELETE:
      if (b_val(curbp, MDVIEW))
          return rdonly();
      mayneedundo();
      fhide_cursor();
      w32_del_selection(LOWORD(code) == IDM_CUT);
      update(FALSE);
      fshow_cursor();
      break;
    default:
      result = FALSE;
    }
    TRACE(("...handle_builtin_menu code ->%d\n", result));
    return result;
}

static void
GetMousePos(POINT * result)
{
    DWORD dword;
    POINTS points;

    dword = GetMessagePos();
    points = MAKEPOINTS(dword);
    POINTSTOPOINT((*result), points);
    ScreenToClient(cur_win->main_hwnd, result);
    result->x /= nCharWidth;
    result->y /= nLineHeight;
}

// Get current mouse position.
// If the mouse is above/below the current window
// then scroll the window down/up proportinally
// to the distance above/below.
// This function is called from WM_TIMER when
// the mouse is captured and a wipe selection is
// active.
static void
AutoScroll(WINDOW *wp)
{
#define DVSR    10
#define INCR    6
#define TRIGGER (DVSR + INCR)

    POINT current;
    int Scroll = 0;
    static int ScrollCount = 0, Throttle = INCR;
    GetMousePos(&current);

    if (wp == 0)
      return;

    // Determine if we are above or below the window,
    // and if so, how far...
    if (current.y < wp->w_toprow) {
      // Above the window
      // Scroll = wp->w_toprow - current.y;
      Scroll = 1;
    }
    if (current.y > mode_row(wp)) {
      // Below
      // Scroll = current.y - mode_row(wp);
      // Scroll *= -1;
      Scroll = -1;
    }

    TRACE(("AutoScroll:(%d, %d, %d)\n", Scroll, ScrollCount, Throttle));
    if (Scroll) {
      int row;
      if (Scroll > 0) {
          row = wp->w_toprow;
      } else {
          row = mode_row(wp) - 1;
      }

      // Scroll the pre-determined amount, ensuring at least one line of
      // window movement per timer tick.  Note also that ScrollScale is
      // signed, so it will be negative if we want to scroll down.
      mvupwind(TRUE, Scroll * max(ScrollCount, TRIGGER) / (Throttle + DVSR));

      // Set the cursor. Column doesn't really matter, it will
      // get updated as soon as we get back into the window...
      setcursor(row, 0) && sel_extend(TRUE, TRUE);
      (void) update(TRUE);
      ScrollCount++;
      if (ScrollCount > TRIGGER && Throttle > 0 && ScrollCount % INCR == 0)
          Throttle--;
    } else {
      // Reset counters
      Throttle = INCR;
      ScrollCount = 0;
    }
#undef DVSR
#undef INCR
#undef TRIGGER
}

static int
MouseClickSetPos(POINT * result, int *onmode)
{
    WINDOW *wp;

    GetMousePos(result);

    TRACE(("GETC:setcursor(%d, %d)\n", result->y, result->x));

    /*
     * If we're getting a button-down in a window, allow it to maybe begin
     * a selection (if the mouse has actually moved).  A button-down on
     * its modeline will allow resizing the window.
     */
    *onmode = FALSE;
    if ((wp = row2window(result->y)) != 0) {
      if (result->y == mode_row(wp)) {
          *onmode = TRUE;
          return TRUE;
      }
      return setcursor(result->y, result->x);
    }
    return FALSE;
}

/*
 * Shrink a window by dragging the modeline
 */
static int
adjust_window(WINDOW *wp, POINT * current, POINT * latest)
{
    if (latest->y == mode_row(wp)) {
      if (current->y != latest->y) {
          WINDOW *save_wp = curwp;
          set_curwp(wp);
          shrinkwind(FALSE, latest->y - current->y);
          set_curwp(save_wp);
          update(TRUE);
      }
      *latest = *current;
      return TRUE;
    }
    return FALSE;
}

/*
 * FUNCTION
 *   mousemove(int    *sel_pending,
 *             int    onmode,
 *             POINT  *first,
 *             POINT  *latest,
 *             MARK   *lmbdn_mark,
 *             WINDOW *wp)
 *
 *   sel_pending - Boolean, T -> client has recorded a left mouse button (LMB)
 *                 click, and so, a selection is pending.
 *
 *   onmode      - Boolean, T -> left mouse button was initially clicked
 *                 down on a mode line.
 *
 *   first       - editor row/col coordinates where LMB was initially recorded.
 *
 *   latest      - during the mouse move, assuming the LMB is still down,
 *                 "latest" represents the editor's current row/col
 *                 position w/in the same window where "first" was recorded.
 *
 *   lmbdn_mark  - editor MARK when LMB was initially recorded.
 *
 *   wp          - pointer to editor window where LMB was initially recorded.
 *
 * DESCRIPTION
 *   Using several state variables, this function handles all the semantics
 *   of a left mouse button "MOVE" event.  The semantics are as follows:
 *
 *   1) This function will not be called unless that LMB is down (enforced
 *      by client).
 *   2) if the LMB was initially clicked down on a mode line, then the only
 *      operation that can occur is window resizing.
 *   3) if not #2 above, then a LMB move within the current editor window
 *      selects a region of text.  Later, when the user releases the LMB, that
 *      text is yanked to the unnamed register (the yank code is not handled
 *      in this function).
 *
 * RETURNS
 *   None
 */
static void
mousemove(int *sel_pending,
        int onmode,
        POINT * first,
        POINT * latest,
        MARK *lmbdn_mark,
        WINDOW *wp)
{
    POINT current;
    int dummy;

    fhide_cursor();
    GetMousePos(&current);
    TRACE(("GETC:MOUSEMOVE (%d,%d)\n", current.x, current.y));

    /* If on mode line, move window. */
    if (onmode) {
      if (!adjust_window(wp, &current, latest)) {
          /*
           * left mouse button still down, but cursor moved off mode
           * line.  Update latest to keep track of cursor in case
           * it wanders back on the mode line.
           */

          *latest = current;
      }
      return;
    }

    if (*sel_pending) {
      /*
       * Selection pending.  If the mouse has moved at least one char,
       * start a selection.
       */

      if (MouseClickSetPos(latest, &dummy)) {
          /* ignore mouse jitter */

          if (latest->x != first->x || latest->y != first->y) {
            *sel_pending = FALSE;
            DOT = *lmbdn_mark;
            (void) sel_begin();
            (void) update(TRUE);
          } else
            return;
      }
    }

    if (wp != row2window(current.y)) {
      /* mouse moved into a different editor window. */

      return;
    }
    if (!setcursor(current.y, current.x))
      return;
    if (get_keyboard_state() & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))
      (void) sel_setshape(RECTANGLE);
    if (!sel_extend(TRUE, TRUE))
      return;
    (void) update(TRUE);
}

#if OPT_SCROLLBARS
static int
check_scrollbar_allocs(void)
{
    int newmax = cur_win->rows / 2;
    int oldmax = cur_win->maxscrollbars;

    TRACE(("check_scrollbar_allocs %d > %d ?\n", oldmax, newmax));
    if (newmax > oldmax) {
      GROW(cur_win->scrollbars, SBDATA, oldmax, newmax);
      cur_win->maxscrollbars = newmax;
      TRACE(("GROW scrollbars=%p, oldmax=%d, newmax=%d\n",
             cur_win->scrollbars, oldmax, newmax));
    }
    return TRUE;
}

static SBDATA
new_scrollbar(void)
{
    SBDATA result;

    result.shown = FALSE;
    result.r.top = -1;
    result.r.left = -1;
    result.r.bottom = -1;
    result.r.right = -1;

    result.w = CreateWindow(
                         SCRL_CLASS,
                         "scrollbar",
                         WS_CHILD | SBS_VERT | WS_CLIPSIBLINGS,
                         0,   /* x */
                         0,   /* y */
                         SbWidth,         /* width */
                         1,   /* height */
                         cur_win->main_hwnd,
                         (HMENU) 0,
                         vile_hinstance,
                         (LPVOID) 0
      );
    return result;
}

static int
show_scrollbar(int n, int flag)
{
    if (cur_win->scrollbars[n].shown != flag) {
      TRACE(("%s_scrollbar %s\n",
             flag ? "show" : "hide",
             which_window(cur_win->scrollbars[n].w)));
      ShowScrollBar(cur_win->scrollbars[n].w, SB_CTL, flag);
      cur_win->scrollbars[n].shown = flag;
      return TRUE;
    }
    return FALSE;
}

static void
set_scrollbar_range(int n, WINDOW *wp)
{
    SCROLLINFO info;
    int lnum, lcnt;

    lnum = line_no(wp->w_bufp, wp->w_dot.l);
    lnum = max(lnum, 1);

    lcnt = vl_line_count(wp->w_bufp);
    lcnt = max(lcnt, 1) + wp->w_ntrows - 1;

    TRACE(("set_scrollbar_range(%d, %s) %d:%d\n",
         n, wp->w_bufp->b_bname, lnum, lcnt));

    info.cbSize = sizeof(info);
    info.fMask = SIF_POS | SIF_RANGE | SIF_PAGE;
    info.nPos = lnum - 1;
    info.nMin = 0;
    info.nMax = lcnt - 1;
    info.nPage = wp->w_ntrows;
    SetScrollInfo(cur_win->scrollbars[n].w, SB_CTL, &info, TRUE);
}

/*
 * All we want to do here is to paint the gap between the real resize-grip and
 * the borders of the dummy window we're surrounding it with.  That's because
 * the resize-grip itself isn't resizable.
 */
LONG FAR PASCAL
GripWndProc(
             HWND hWnd,
             UINT message,
             WPARAM wParam,
             LONG lParam)
{
    PAINTSTRUCT ps;
    HBRUSH brush;

    TRACE(("GRIP:%s\n", message2s(message)));

    switch (message) {
    case WM_PAINT:
      BeginPaint(hWnd, &ps);
      TRACE(("...painting (%d,%d) (%d,%d)\n",
             ps.rcPaint.top,
             ps.rcPaint.left,
             ps.rcPaint.bottom,
             ps.rcPaint.right));
      brush = CreateSolidBrush(GetSysColor(COLOR_SCROLLBAR));
      SelectObject(ps.hdc, brush);
      Rectangle(ps.hdc,
              ps.rcPaint.left,
              ps.rcPaint.top,
              ps.rcPaint.right,
              ps.rcPaint.bottom);
      DeleteObject(brush);
      EndPaint(hWnd, &ps);
      return (0);
    }
    return (DefWindowProc(hWnd, message, wParam, lParam));
}

static void
update_scrollbar_sizes(void)
{
    RECT crect;
    register WINDOW *wp;
    int i, top, left;
    int newsbcnt;
    int oldsbcnt = cur_win->nscrollbars;

    TRACE(("update_scrollbar_sizes\n"));

    i = 0;
    for_each_visible_window(wp)
      i++;
    newsbcnt = i;

    for (i = cur_win->nscrollbars + 1; i <= newsbcnt; i++) {
      if (cur_win->scrollbars[i].w == NULL) {
          cur_win->scrollbars[i] = new_scrollbar();
          TRACE(("... created sb%d=%#x\n", i, cur_win->scrollbars[i].w));
      }
    }
    cur_win->nscrollbars = newsbcnt;

    /* Set sizes and positions on scrollbars and sliders */
    i = 0;
    GetClientRect(cur_win->main_hwnd, &crect);
    top = crect.top;
    left = crect.right - SbWidth;
    for_each_visible_window(wp) {
      int high = RowToPixel(wp->w_ntrows + 1);
      int wide = SbWidth;
      if (show_scrollbar(i, TRUE)
          || cur_win->scrollbars[i].r.top != top
          || cur_win->scrollbars[i].r.left != left
          || cur_win->scrollbars[i].r.bottom != high
          || cur_win->scrollbars[i].r.right != wide) {
          MoveWindow(cur_win->scrollbars[i].w,
                   cur_win->scrollbars[i].r.left = left,
                   cur_win->scrollbars[i].r.top = top,
                   cur_win->scrollbars[i].r.right = wide,
                   cur_win->scrollbars[i].r.bottom = high,
                   TRUE);
          TRACE(("... adjusted %s to (%d,%d) (%d,%d)\n",
               which_window(cur_win->scrollbars[i].w),
               left,
               top,
               SbWidth,
               high));
      }
      if (cur_win->nscrollbars == i + 1)
          set_scrollbar_range(i, wp);
      i++;
      top += high;
    }

    while (i < oldsbcnt) {
      (void) show_scrollbar(i, FALSE);
      i++;
    }

    if (cur_win->size_box.w == 0) {
      cur_win->size_box.w = CreateWindow(
                                    GRIP_CLASS,
                                    "sizebox",
                                    WS_CHILD
                                    | WS_VISIBLE
                                    | WS_CLIPSIBLINGS,
                                    cur_win->size_box.r.left = left,
                                    cur_win->size_box.r.top = top,
                                    cur_win->size_box.r.right =
                                    SbWidth + 1,
                                    cur_win->size_box.r.bottom = nLineHeight,
                                    cur_win->main_hwnd,
                                    (HMENU) 0,
                                    vile_hinstance,
                                    (LPVOID) 0
          );
      cur_win->size_grip.w = CreateWindow(
                                     "SCROLLBAR",
                                     "sizebox",
                                     WS_CHILD
                                     | WS_VISIBLE
                                     | SB_CTL
                                     | SBS_SIZEGRIP
                                     | WS_CLIPSIBLINGS
                                     | SBS_SIZEBOXBOTTOMRIGHTALIGN,
                                     cur_win->size_box.r.left = 0,
                                     cur_win->size_box.r.top = 0,
                                     cur_win->size_box.r.right = SbWidth,
                                     cur_win->size_box.r.bottom = nLineHeight,
                                     cur_win->size_box.w,
                                     (HMENU) 0,
                                     vile_hinstance,
                                     (LPVOID) 0
          );
      TRACE(("... made SIZEGRIP %x at %d,%d\n", cur_win->size_box.w, left, top));
    } else {
      int ok;

      if (cur_win->size_box.r.left != left
          || cur_win->size_box.r.top != top
          || cur_win->size_box.r.right != SbWidth
          || cur_win->size_box.r.bottom != nLineHeight) {
          ok = MoveWindow(cur_win->size_box.w,
                      cur_win->size_box.r.left = left,
                      cur_win->size_box.r.top = top,
                      cur_win->size_box.r.right = SbWidth,
                      cur_win->size_box.r.bottom = nLineHeight,
                      TRUE);
          TRACE(("... move SIZE_BOX %d:%x to %d,%d\n",
               ok, cur_win->size_box.w, left, top));
      }

      left = 0;
      top = 0;
      if (cur_win->size_grip.r.left != left
          || cur_win->size_grip.r.top != top
          || cur_win->size_grip.r.right != SbWidth
          || cur_win->size_grip.r.bottom != nLineHeight) {
          ok = MoveWindow(cur_win->size_grip.w,
                      cur_win->size_grip.r.left = left,
                      cur_win->size_grip.r.top = top,
                      cur_win->size_grip.r.right = SbWidth,
                      cur_win->size_grip.r.bottom = nLineHeight,
                      TRUE);
          TRACE(("... move SIZEGRIP %d:%x to %d,%d\n",
               ok, cur_win->size_grip.w, left, top));
      }
    }
}

void
gui_update_scrollbar(WINDOW *uwp)
{
    WINDOW *wp;
    int i;

    TRACE(("gui_update_scrollbar uwp=%p %s\n", uwp, uwp->w_bufp->b_bname));
    if (dont_update_sb)
      return;

    i = 0;
    for_each_visible_window(wp) {
      TRACE(("wp=%p name='%s'\n", wp, wp->w_bufp->b_bname));
      if (wp == uwp)
          break;
      i++;
    }

    TRACE(("i=%d, nscrollbars=%d\n", i, cur_win->nscrollbars));
    if (i >= cur_win->nscrollbars || (wp->w_flag & WFSBAR)) {
      /*
       * update_scrollbar_sizes will recursively invoke gui_update_scrollbar,
       * but with WFSBAR disabled.
       */
      update_scrollbar_sizes();
      return;
    }

    set_scrollbar_range(i, wp);
}

static int
find_scrollbar(HWND hWnd)
{
    WINDOW *wp;
    int i = 0;

    TRACE((T_CALLED "find_scrollbar(hWnd=%p)\n", hWnd));

    for_each_visible_window(wp) {
      if (cur_win->scrollbars[i].w == hWnd
          || cur_win->main_hwnd == hWnd) {
          set_curwp(wp);
          if (wp->w_bufp != curbp) {
            swbuffer(wp->w_bufp);
          }
          returnCode(i);
      }
      i++;
    }
    returnCode(-1);
}

/*
 * M$ is only capable of delivering an unsigned 16-bit number for nPos.  Infer
 * the "right" value by minimizing the distance we travel in a given jump.
 */
static int
fix_scrollbar_tracking(int nPos)
{
    int this_line = line_no(curwp->w_bufp, curwp->w_line.l);
    int last_line = line_no(curwp->w_bufp, lback(buf_head(curwp->w_bufp)));
    int diff_line = 0x10000;
    int test_line;

    for (test_line = nPos; test_line < last_line; test_line += 0x10000) {
      int check = absol(this_line - test_line);
      if (check < diff_line) {
          diff_line = check;
          nPos = test_line;
      }
    }
    return nPos;
}

static void
handle_scrollbar(HWND hWnd, int msg, int nPos)
{
    int this_line;
    int snum = find_scrollbar(hWnd);
    int upd = TRUE;           /* do an update() if T */

    TRACE(("handle_scrollbar msg=%d, nPos=%d\n", msg, nPos));

    if (snum < 0) {
      TRACE(("...could not find window for %s\n", which_window(hWnd)));
      return;
    }

    fhide_cursor();
    switch (msg) {
    case SB_BOTTOM:
      TRACE(("-> SB_BOTTOM\n"));
      gotoline(FALSE, 1);
      break;
    case SB_LINEDOWN:
      TRACE(("-> SB_LINEDOWN\n"));
      mvdnwind(FALSE, 1);
      break;
    case SB_LINEUP:
      TRACE(("-> SB_LINEUP\n"));
      mvupwind(FALSE, 1);
      break;
    case SB_PAGEDOWN:
      TRACE(("-> SB_PAGEDOWN\n"));
      forwpage(FALSE, 1);
      break;
    case SB_PAGEUP:
      TRACE(("-> SB_PAGEUP\n"));
      backpage(FALSE, 1);
      break;
    case SB_THUMBPOSITION:
      TRACE(("-> SB_THUMBPOSITION: %d\n", nPos));
      gotoline(TRUE, fix_scrollbar_tracking(nPos) + 1);
      break;
    case SB_THUMBTRACK:
      TRACE(("-> SB_THUMBTRACK: %d\n", nPos));
      /*
       * M$ is only capable of delivering an unsigned 16-bit number for nPos.
       * Infer the "right" value by minimizing the distance we travel in a
       * given jump.
       */
      this_line = line_no(curwp->w_bufp, curwp->w_line.l);
      mvupwind(TRUE, this_line - fix_scrollbar_tracking(nPos));
      break;
    case SB_TOP:
      TRACE(("-> SB_TOP\n"));
      gotoline(TRUE, 1);
      break;
    case SB_ENDSCROLL:
      TRACE(("-> SB_ENDSCROLL\n"));
      /* Fall through */
    default:
      upd = FALSE;
      break;
    }
    if (upd) {
      (void) update(TRUE);
      set_scrollbar_range(snum, curwp);
    }
    fshow_cursor();
}
#endif /* OPT_SCROLLBARS */

static int
ntgetch(void)
{
    static DWORD lastclick = 0;
    static int clicks = 0, onmode;
    // Save the timer ID so we can kill it.

    DWORD thisclick;
    int buttondown = FALSE;
    int sel_pending = FALSE;  /* Selection pending */
    MARK lmbdn_mark;          /* left mouse button down here */
    MARK last_dot = nullmark; /* remember where dot was before selection */
    WINDOW *last_win = 0;     /* remember which window dot was in */
    int selecting = FALSE;    /* toggle between cut and paste */
    int result = -1;
    KEY_EVENT_RECORD ker;
    MSG msg;
    POINT first;
    POINT latest;
    UINT clicktime;
    WINDOW *that_wp = 0;
#ifdef VAL_AUTOCOLOR
    int milli_ac, orig_milli_ac;
#endif

    if (saveCount > 0) {
      saveCount--;
      return savedChar;
    }
    clicktime = GetDoubleClickTime();
#ifdef VAL_AUTOCOLOR
    orig_milli_ac = global_b_val(VAL_AUTOCOLOR);
#endif
    vile_in_getfkey = 1;
    if (GetFocus() == cur_win->main_hwnd) {
      fshow_cursor();
    } else {
      fhide_cursor();
    }
    while (result < 0) {
#ifdef VAL_AUTOCOLOR
      milli_ac = orig_milli_ac;
      while (milli_ac > 0) {
          if (PeekMessage(&msg, (HWND) 0, 0, 0, PM_NOREMOVE)) {
            if (msg.message != 0x118)
                break;  /* A meaningful event--process it. */
            else {
                /*
                 * This is the undocumented (as near as I can tell)
                 * 0x118 event that seems to occur whenever the caret
                 * blinks.  This is a background "noise" event that can
                 * be ignored once dispatched.
                 */

                if (GetMessage(&msg, (HWND) 0, 0, 0) != TRUE) {
                  PostQuitMessage(1);
                  quit(TRUE, 1);
                } else
                  DispatchMessage(&msg);
            }
          }
          Sleep(20);          /* sleep a bit, but be responsive to all events */
          milli_ac -= 20;
      }
      if (orig_milli_ac && milli_ac <= 0) {
          ac_active = TRUE;
          autocolor();
          ac_active = FALSE;
      }
#endif
      if (GetMessage(&msg, (HWND) 0, 0, 0) != TRUE) {
          PostQuitMessage(1);
          TRACE(("GETC:no message\n"));
          quit(TRUE, 1);
      }

      if (TranslateAccelerator(cur_win->main_hwnd, hAccTable, &msg)) {
          TRACE(("GETC:no accelerator\n"));
          continue;
      }

      TranslateMessage(&msg);

      switch (msg.message) {
      case WM_DESTROY:
          TRACE(("GETC:DESTROY\n"));
          PostQuitMessage(0);
          continue;

      case WM_CHAR:
          TRACE(("GETC:CHAR:%#x\n", msg.wParam));
          result = msg.wParam;
          if (result == ESC) {
            sel_release();
            (void) update(TRUE);
          }
          break;

      case WM_KEYDOWN:
      case WM_SYSKEYDOWN:
          ker.uChar.AsciiChar = 0;
          ker.wVirtualKeyCode = (SHORT) msg.wParam;
          ker.dwControlKeyState = get_keyboard_state();
          result = decode_key_event(&ker);
          TRACE(("GETC:%sKEYDOWN:%#x ->%#x\n",
               (msg.message == WM_SYSKEYDOWN) ? "SYS" : "",
               msg.wParam, result));
          if (result == NOKYMAP) {
            DispatchMessage(&msg);
            result = -1;
#ifdef VILE_OLE
          } else if (result == KYREDIR) {
            result = -1;      /* keystroke sent elsewhere */
#endif
          } else if (result == (int) msg.wParam) {
            result = -1;      /* we'll get a WM_CHAR next */
          }
          break;

      case WM_TIMER:
          TRACE(("Timer fired\n"));
          {
            if (msg.wParam == nIDTimer) {
                /*
                 * perform auto-scroll if the mouse cursor
                 * is above/below the current window.
                 */

                AutoScroll(that_wp);
            } else {
                DispatchMessage(&msg);
            }
          }
          break;
      case WM_LBUTTONDOWN:
          TRACE(("GETC:LBUTTONDOWN %s\n", which_window(msg.hwnd)));
          if (msg.hwnd == cur_win->text_hwnd) {
            /* Clear current selection, a la notepad. */
            sel_release();
            selecting = FALSE;
            last_dot = DOT;
            last_win = curwp;
            /* Allow click to change window focus. */
            if (MouseClickSetPos(&first, &onmode)) {
                fhide_cursor();
                sel_pending = buttondown = TRUE;
                lmbdn_mark = DOT;
                latest = first;
                that_wp = row2window(first.y);
                (void) update(TRUE);      /* for wdw change */

                /* we _will_ own the mouse, if win32 API cooperates */
                SetCapture(cur_win->text_hwnd);
                mouse_captured = TRUE;

                /* Set a 25 msec timer for handling auto-scroll. */
                nIDTimer = SetTimer(cur_win->text_hwnd, 1, 25, 0);
                if (nIDTimer == 0) {
                  /* timer resources exhausted */

                  disp_win32_error(W32_SYS_ERROR, cur_win->text_hwnd);
                  ReleaseCapture();
                  mouse_captured = FALSE;
                } else {
                  AutoScroll(that_wp);
                }
            }
          } else {
            DispatchMessage(&msg);
          }
          break;

      case WM_LBUTTONUP:
          TRACE(("GETC:LBUTTONUP %s\n", which_window(msg.hwnd)));

          /* If we captured the mouse, then we will release it. */
          if (mouse_captured) {
            mouse_captured = FALSE;
            ReleaseCapture();
            KillTimer(cur_win->text_hwnd, nIDTimer);
            nIDTimer = 0;
          }
          if (msg.hwnd == cur_win->text_hwnd) {
            fhide_cursor();
            sel_pending = FALSE;
            thisclick = GetTickCount();
            TRACE(("CLICK %d/%d\n", lastclick, thisclick));
            if (thisclick - lastclick < clicktime) {
                clicks++;
                TRACE(("MOUSE CLICKS %d\n", clicks));
            } else {
                clicks = 0;
            }
            lastclick = thisclick;

            switch (clicks) {
            case 1:
                on_double_click();
                selecting = TRUE;
                break;
            case 2:
                on_triple_click();
                selecting = TRUE;
                break;
            }

            if (buttondown) {
                int dummy;

                /*
                 * Update editor's current cursor position, if that
                 * position is still within cur_win->text_hwnd .
                 */
                (void) MouseClickSetPos(&latest, &dummy);

                if (!onmode) {
                  /*
                   * The LMB is down and non-modeline mouse movement
                   * just terminated within a vile window.  Note
                   * however that, due to autoscroll, the cursor
                   * might not now be positioned within
                   * cur_win->main_hwnd--but that doesn't matter.
                   * If the user selected at least one char of
                   * text, yank it to the unnamed register.
                   */

                  sel_yank(0);
                }
                if (selecting) {
                  if (win2index(last_win) >= 0) {
                      set_curwp(last_win);
                      restore_dot(last_dot);
                  }
                  last_win = 0;
                  last_dot = nullmark;
                }
            }
            onmode = buttondown = FALSE;
            (void) update(TRUE);
            fshow_cursor();
          } else {
            DispatchMessage(&msg);
          }
          break;

          /*
           * define _WIN32_WINNT=0x400 (or higher) to include WM_MOUSEWHEEL
           * code below.  Note that WM_MOUSEWHEEL is really only required
           * to support a mouse driver that doesn't emit WM_VMSCROLL
           * messages when the wheel mouse rotates.  Examples:
           *
           *    MS Intellimouse driver     -> emits WM_VMSCROLL
           *    Logitech mousewheel driver -> emits WM_VMSCROLL
           *    MS PS/2 compatible driver  -> emits WM_MOUSEWHEEL
           *   
           * The latter driver is often installed when PNP can't
           * distinguish the native HW.
           */
#ifdef WM_MOUSEWHEEL
#if OPT_SCROLLBARS
      case WM_MOUSEWHEEL:
          {
            int c;

            fhide_cursor();
            if ((short) HIWORD(msg.wParam) > 0) {
                c = (HIWORD(msg.wParam) / WHEEL_DELTA);
                mvupwind(TRUE, c * 3);
            } else {
                c = (-((short) HIWORD(msg.wParam)) / WHEEL_DELTA);
                mvdnwind(TRUE, c * 3);
            }
            update(TRUE);
            fshow_cursor();
          }
          break;
#endif
#endif

      case WM_MBUTTONDOWN:
          TRACE(("GETC:MBUTTONDOWN %s\n", which_window(msg.hwnd)));
          if (msg.hwnd == cur_win->text_hwnd) {
            if (MouseClickSetPos(&latest, &onmode)
                && !onmode) {
                sel_yank(0);
                sel_release();
                paste_selection();
            }
            (void) update(TRUE);
          } else {
            DispatchMessage(&msg);
          }
          break;

      case WM_RBUTTONDOWN:
          TRACE(("GETC:RBUTTONDOWN %s\n", which_window(msg.hwnd)));
          if (msg.hwnd == cur_win->text_hwnd) {
            if (enable_popup) {
                invoke_popup_menu(msg);
            } else {
                if (selecting) {
                  sel_yank(0);
                  cbrdcpy_unnamed(FALSE, 1);
                  selecting = FALSE;
                  sel_release();
                } else {
                  mayneedundo();
                  cbrdpaste(FALSE, 1);
                }
                (void) update(TRUE);
            }
          } else {
            DispatchMessage(&msg);
          }
          break;

      case WM_COMMAND:
          TRACE(("GETC:WM_COMMAND, popup:%d, wParam:%#x\n", enable_popup, msg.wParam));
          if (enable_popup) {
            handle_builtin_menu(msg.wParam);
          } else {
            DispatchMessage(&msg);
          }
          break;

      case WM_MOUSEMOVE:
          if (buttondown) {
            mousemove(&sel_pending,
                    onmode,
                    &first,
                    &latest,
                    &lmbdn_mark,
                    that_wp);
            selecting = !sel_pending;
          } else {
            DispatchMessage(&msg);
          }
          break;

      default:
          TRACE(("GETC:default(%s)\n", message2s(msg.message)));
          /* FALLTHRU */
      case WM_KEYUP:          /* FALLTHRU */
      case WM_NCHITTEST:      /* FALLTHRU */
      case WM_NCMOUSEMOVE:    /* FALLTHRU */
      case WM_NCLBUTTONDOWN:  /* FALLTHRU */
      case WM_PAINT:          /* FALLTHRU */
      case WM_NCACTIVATE:     /* FALLTHRU */
      case WM_SETCURSOR:      /* FALLTHRU */
      case 0x118:
          DispatchMessage(&msg);
          break;
      }
    }
    fhide_cursor();
    vile_in_getfkey = 0;

    TRACE(("...ntgetch %#x\n", result));
    return result;
}

static int
nttypahead(void)
{
    MSG msg;

#ifdef VAL_AUTOCOLOR
    if (ac_active) {
      /*
       * Came here during an autocolor operation.  Do nothing, in an
       * attempt to avoid a keyboard lockup (editor loop) that occurs on
       * rare occasions (not reproducible).
       */

      return (0);
    }
#endif
    if (saveCount > 0)
      return (1);
    if (PeekMessage(&msg, (HWND) 0, WM_KEYDOWN, WM_KEYDOWN, PM_NOREMOVE))
      return (1);
    if (PeekMessage(&msg, (HWND) 0, WM_SYSKEYDOWN, WM_SYSKEYDOWN, PM_NOREMOVE))
      return (1);
    return (PeekMessage(&msg, (HWND) 0, WM_CHAR, WM_CHAR, PM_NOREMOVE));
}

static void
repaint_window(HWND hWnd)
{
    PAINTSTRUCT ps;
    HBRUSH brush;
    int x0, y0, x1, y1;
    int row, col;

    BeginPaint(hWnd, &ps);
    TRACE(("repaint_window (erase:%d) %s\n", ps.fErase, which_window(hWnd)));
    nt_set_colors(ps.hdc, cur_atr);
    brush = Background(ps.hdc);

    TRACE(("...painting (%d,%d) (%d,%d)\n",
         ps.rcPaint.top,
         ps.rcPaint.left,
         ps.rcPaint.bottom,
         ps.rcPaint.right));

    y0 = (ps.rcPaint.top) / nLineHeight;
    x0 = (ps.rcPaint.left) / nCharWidth;
    y1 = (ps.rcPaint.bottom + nLineHeight) / nLineHeight;
    x1 = (ps.rcPaint.right + nCharWidth) / nCharWidth;

    if (y0 < 0)
      y0 = 0;
    if (x0 < 0)
      x0 = 0;
    if (y1 > term.maxrows)
      y1 = term.maxrows;
    if (x1 > term.maxcols)
      x1 = term.maxcols;

    TRACE(("...erase %d\n", ps.fErase));
    TRACE(("...cells (%d,%d) - (%d,%d)\n", y0, x0, y1, x1));
    TRACE(("...top:    %d\n", RowToPixel(y0) - ps.rcPaint.top));
    TRACE(("...left:   %d\n", ColToPixel(x0) - ps.rcPaint.left));
    TRACE(("...bottom: %d\n", RowToPixel(y1) - ps.rcPaint.bottom));
    TRACE(("...right:  %d\n", ColToPixel(x1) - ps.rcPaint.right));

    for (row = y0; row < y1; row++) {
      if (pscreen != 0
          && pscreen[row]->v_text != 0
          && pscreen[row]->v_attrs != 0) {
          int old_col = x0;
          VIDEO_ATTR old_atr = CELL_ATTR(row, old_col);
          VIDEO_ATTR new_atr;

          for (col = x0 + 1; col < x1; col++) {
            new_atr = CELL_ATTR(row, col);
            if (new_atr != old_atr) {
                nt_set_colors(ps.hdc, old_atr);
                TRACE2(("ExtTextOut [%3d,%3d]%.*s\n", row, old_col, col -
                      old_col, &CELL_TEXT(row, old_col)));
                ExtTextOut(ps.hdc,
                         ColToPixel(old_col),
                         RowToPixel(row),
                         0,
                         (RECT *) 0,
                         &CELL_TEXT(row, old_col),
                         col - old_col,
                         intercharacter(col));
                old_atr = new_atr;
                old_col = col;
            }
          }
          if (old_col < x1) {
            nt_set_colors(ps.hdc, old_atr);
            TRACE2(("ExtTextOut [%3d,%3d]%.*s\n", row, old_col, x1 -
                  old_col, &CELL_TEXT(row, old_col)));
            ExtTextOut(ps.hdc,
                     ColToPixel(old_col),
                     RowToPixel(row),
                     0,
                     (RECT *) 0,
                     &CELL_TEXT(row, old_col),
                     x1 - old_col,
                     intercharacter(x1));
          }
      }
    }
    DeleteObject(brush);

    TRACE(("...repaint_window\n"));
    EndPaint(hWnd, &ps);
}

static int
we_are_at_home(void)
{
    return TRUE;
}

static void
receive_dropped_files(HDROP hDrop)
{
    char name[NFILEN];
    UINT inx = 0xFFFFFFFF;
    UINT limit = DragQueryFile(hDrop, inx, name, sizeof(name));
    BUFFER *bp = 0;

    TRACE((T_CALLED "receive_dropped_files(hDrop=%p) %d dropped files\n",
         hDrop, limit));

    while (++inx < limit) {
      DragQueryFile(hDrop, inx, name, sizeof(name));
      TRACE(("...'%s'\n", name));
      if ((bp = getfile2bp(name, FALSE, FALSE)) != 0)
          bp->b_flag |= BFARGS;     /* treat this as an argument */
    }
    if (bp != 0) {
      if (swbuffer(bp)) {     /* editor switches to 1st buffer */
          set_directory_from_file(bp);
      }
      update(TRUE);
    }
    DragFinish(hDrop);
    returnVoid();
}

static void
HandleClose(HWND hWnd)
{
    quit(FALSE, 1);
}

LONG FAR PASCAL
TextWndProc(
             HWND hWnd,
             UINT message,
             WPARAM wParam,
             LONG lParam)
{
    TRACE(("TEXT:%s, %s\n", message2s(message), which_window(hWnd)));

    switch (message) {
    case WM_PAINT:
      if ((hWnd == cur_win->main_hwnd
           || hWnd == cur_win->text_hwnd)
          && GetUpdateRect(hWnd, (LPRECT) 0, FALSE)) {
          repaint_window(hWnd);
      } else {
          /* scrollbars, etc. */
          TRACE(("...repaint %s\n", which_window(hWnd)));
          return (DefWindowProc(hWnd, message, wParam, lParam));
      }
      break;
    default:
      return (DefWindowProc(hWnd, message, wParam, lParam));

      IGN_PROC("TEXT:", WM_ERASEBKGND);
    }
    return (0);
}

LONG FAR PASCAL
MainWndProc(
             HWND hWnd,
             UINT message,
             WPARAM wParam,
             LONG lParam)
{
    static int resize_pending;      /* a resize, not a move */
    static HWND resize_hwnd;
    static int resize_wdw_up;

    TRACE(("MAIN:%s, %s\n", message2s(message), which_window(hWnd)));

    switch (message) {
      HANDLE_MSG(hWnd, WM_CLOSE, HandleClose);
    case WM_COMMAND:
#if FIXME
      switch (wParam) {
      case IDC_button:
          wParam = IDC_button_x;
          PostMessage(hWnd, message, wParam, lParam);
          break;
      }
#endif
      break;

    case WM_SETFOCUS:
      fshow_cursor();
      break;
    case WM_WVILE_CURSOR_ON:
      if (GetFocus() == hWnd)
          fshow_cursor();
      break;

    case WM_KILLFOCUS:
    case WM_WVILE_CURSOR_OFF:
      fhide_cursor();
      break;

    case WM_DESTROY:
      PostQuitMessage(0);
      break;

    case WM_WINDOWPOSCHANGED:
      ResizeClient();
      return (DefWindowProc(hWnd, message, wParam, lParam));

    case WM_WINDOWPOSCHANGING:
#if FIXME_POSCHANGING
      if (wheadp != 0)
          return AdjustPosChanging(hWnd, (LPWINDOWPOS) lParam);
#endif
      return (DefWindowProc(hWnd, message, wParam, lParam));

    case WM_SIZING:
      if (initialized) {
          char buf[32];
          static int frame_w, frame_h;

          if (is_winnt()) {
            /*
             * Win9x/ME GDI doesn't support supplementary GDI activity
             * while a window resize is in progress.
             */

            if (!resize_hwnd)
                resize_hwnd = sizing_window();
            if (resize_hwnd) {
                if (!resize_wdw_up) {
                  RECT crect, wrect;

                  resize_wdw_up = TRUE;
                  ShowWindow(resize_hwnd, SW_SHOWNORMAL);
                  sprintf(buf, "%d cols X %d rows", term.cols, term.rows);

                  /* compute real estate consumed by main window frame */
                  GetClientRect(cur_win->main_hwnd, &crect);
                  GetWindowRect(cur_win->main_hwnd, &wrect);
                  frame_w = (wrect.right - wrect.left) - crect.right;
                  frame_h = (wrect.bottom - wrect.top) - crect.bottom;
                } else {
                  RECT newrect;
                  int h, w;

                  /*
                   * The rectangle passed in with this message includes
                   * the outer frame, which is worthless for editing
                   * text.
                   */
                  newrect = *((RECT *) lParam);
                  newrect.right -= frame_w;
                  newrect.bottom -= frame_h;
                  h = RectToRows(newrect);
                  w = RectToCols(newrect);
                  sprintf(buf, "%d cols X %d rows", w, h);
                }
                SetWindowText(resize_hwnd, buf);
            }
          }
      }
      return AdjustResizing(hWnd, wParam, (LPRECT) lParam);

    case WM_EXITSIZEMOVE:
      if (resize_hwnd && resize_wdw_up) {
          resize_wdw_up = FALSE;
          SendMessage(resize_hwnd, WM_CLOSE, 0, 0);
          resize_hwnd = NULL;
      }
      ResizeClient();
      if (resize_pending) {
          if (initialized) {
            mlwrite("columns: %d, rows: %d", term.cols, term.rows);
            update(TRUE);     /* Force cursor out of message line */
          }
          resize_pending = FALSE;
      }
      return (DefWindowProc(hWnd, message, wParam, lParam));

    case WM_DROPFILES:
      receive_dropped_files((HDROP) wParam);
      return 0;

    case WM_SYSCOMMAND:
      TRACE(("MAIN:WM_SYSCOMMAND %s at %d,%d\n",
             syscommand2s(LOWORD(wParam)),
             HIWORD(lParam),
             LOWORD(lParam)));
      handle_builtin_menu(wParam);
      return (DefWindowProc(hWnd, message, wParam, lParam));

#if OPT_SCROLLBARS
    case WM_VSCROLL:
      handle_scrollbar((HWND) lParam, LOWORD(wParam), HIWORD(wParam));
      return (0);
#endif

    case WM_SIZE:
      if (initialized)
          resize_pending = TRUE;
      /* FALL THROUGH */
    default:
      return (TextWndProc(hWnd, message, wParam, lParam));

      IGN_PROC("MAIN:", WM_ERASEBKGND);
    }
    return (1);
}

static BOOL
InitInstance(HINSTANCE hInstance)
{
    WNDCLASS wc;

    hglass_cursor = LoadCursor((HINSTANCE) 0, IDC_WAIT);
    arrow_cursor = LoadCursor((HINSTANCE) 0, IDC_ARROW);

    default_bcolor = GetSysColor(COLOR_WINDOWTEXT + 1);
    default_fcolor = GetSysColor(COLOR_WINDOW + 1);

    ZeroMemory(&wc, sizeof(&wc));
    wc.style = CS_VREDRAW | CS_HREDRAW;
    wc.lpfnWndProc = (WNDPROC) MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(hInstance, "VilewIcon");
    wc.hCursor = arrow_cursor;
    wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
    wc.lpszMenuName = "VileMenu";
    wc.lpszClassName = MAIN_CLASS;

    if (!RegisterClass(&wc))
      return FALSE;

    TRACE(("Registered(%s)\n", MAIN_CLASS));

    vile_hinstance = hInstance;
    hAccTable = LoadAccelerators(vile_hinstance, "VileAcc");

    cur_win->main_hwnd = CreateWindow(
                               MAIN_CLASS,
                               MY_APPLE,
                               WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
                               CW_USEDEFAULT,
                               CW_USEDEFAULT,
                               1,
                               1,
                               (HWND) 0,
                               (HMENU) 0,
                               hInstance,
                               (LPVOID) 0
      );
    TRACE(("CreateWindow(main) -> %#lx\n", cur_win->main_hwnd));
    if (!cur_win->main_hwnd)
      return (FALSE);

    ZeroMemory(&wc, sizeof(&wc));
    wc.style = CS_VREDRAW | CS_HREDRAW;
    wc.lpfnWndProc = (WNDPROC) TextWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = 0;
    wc.hCursor = arrow_cursor;
    wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
    wc.lpszMenuName = 0;
    wc.lpszClassName = TEXT_CLASS;

    if (!RegisterClass(&wc))
      return FALSE;

    TRACE(("Registered(%s)\n", TEXT_CLASS));

    cur_win->text_hwnd = CreateWindow(
                               TEXT_CLASS,
                               "text",
                               WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS,
                               CW_USEDEFAULT,
                               CW_USEDEFAULT,
                               1,
                               1,
                               cur_win->main_hwnd,
                               (HMENU) 0,
                               hInstance,
                               (LPVOID) 0
      );
    TRACE(("CreateWindow(text) -> %#lx\n", cur_win->text_hwnd));
    if (!cur_win->text_hwnd)
      return (FALSE);

    /*
     * Register the GRIP_CLASS now also, otherwise it won't succeed when
     * we create the first scrollbars, until we resize the window.
     */
#if OPT_SCROLLBARS
    ZeroMemory(&wc, sizeof(&wc));
    wc.style = CS_VREDRAW | CS_HREDRAW;
    wc.lpfnWndProc = (WNDPROC) GripWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = vile_hinstance;
    wc.hCursor = arrow_cursor;
    wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
    wc.lpszMenuName = 0;
    wc.lpszClassName = GRIP_CLASS;

    if (!RegisterClass(&wc)) {
      TRACE(("could not register class %s:%#x\n", GRIP_CLASS, GetLastError()));
      return (FALSE);
    }
    TRACE(("Registered(%s)\n", GRIP_CLASS));
#endif

    cur_win->nscrollbars = -1;

    /* Insert Winvile's menu items in the system menu. */
    vile_menu = GetSystemMenu(cur_win->main_hwnd, FALSE);
    AppendMenu(vile_menu, MF_SEPARATOR, 0, NULL);
    AppendMenu(vile_menu, MF_STRING, IDM_OPEN, "&Open...");
    AppendMenu(vile_menu, MF_STRING, IDM_SAVE_AS, "&Save As...");
    AppendMenu(vile_menu, MF_STRING, IDM_FAVORITES, "Fa&vorites...");
    AppendMenu(vile_menu, MF_STRING, IDM_FONT, "&Font...");
    AppendMenu(vile_menu, MF_STRING, IDM_ABOUT, "&About...");
    AppendMenu(vile_menu, MF_SEPARATOR, 0, NULL);
    AppendMenu(vile_menu, MF_STRING, IDM_PAGE_SETUP, "Page Set&up...");
    AppendMenu(vile_menu, MF_STRING, IDM_PRINT, "&Print...");
    AppendMenu(vile_menu, MF_SEPARATOR, 0, NULL);
    AppendMenu(vile_menu, MF_STRING | MF_CHECKED, IDM_MENU, "&Menu");

#if OPT_SCROLLBARS
    if (check_scrollbar_allocs() != TRUE)
      return (FALSE);
#endif

    get_font(&vile_logfont);
    use_font(GetMyFont(0));

    DragAcceptFiles(cur_win->main_hwnd, TRUE);

    return (TRUE);
}

int WINAPI
WinMain(
         HINSTANCE hInstance,
         HINSTANCE hPrevInstance,
         LPSTR lpCmdLine,
         int nCmdShow)
{
    int argc;
    int n;
    int maxargs = (strlen(lpCmdLine) + 1) / 2;
    char **argv = typeallocn(char *, maxargs + 10);
    char *ptr, *fontstr;
#ifdef VILE_OLE
    int oa_invoke, oa_reg;

    memset(&oa_opts, 0, sizeof(oa_opts));
    oa_invoke = oa_reg = FALSE;
#endif

    TRACE(("Starting ntvile, CmdLine:%s\n", lpCmdLine));

    argv[argc = 0] = "VILE";

    if (ffaccess(lpCmdLine, FL_READABLE)) {
      argv[++argc] = lpCmdLine;
    } else {
      for (ptr = lpCmdLine; *ptr != '\0';) {
          char delim = ' ';

          while (*ptr == ' ')
            ptr++;

          if (*ptr == '"') {
            delim = *ptr++;
          }
          if (*argv[argc]) {
            TRACE(("argv[%d]:%s\n", argc, argv[argc]));
            ++argc;
          }
          argv[argc] = ptr;
          if (argc + 1 >= maxargs) {
            break;
          }
          while (*ptr != delim && *ptr != '\0')
            ptr++;
          if (*ptr == delim)
            *ptr++ = '\0';
      }
    }
    if (*argv[argc]) {
      TRACE(("argv[%d]:%s\n", argc, argv[argc]));
      ++argc;
    }
    fontstr = argv[argc] = 0;

    /*
     * If our working directory is ${HOMEDRIVE}${HOME} and we're given a
     * filename, try to set the working directory based on the last one.
     * Drag/drop would only do this for us if we registered for each file
     * type; otherwise it's useful when we have the window already open.
     */
    if (argc >= 2
      && we_are_at_home()
      && ffaccess(argv[argc - 1], FL_READABLE))
      cd_on_open = -1;

    SetCols(80);
    SetRows(24);

    /*
     * Get screen size and OLE options, if any.  Parsing logic is
     * messy, but must remain that way to handle the various command
     * line options available with and without OLE automation.
     */
    for (n = 1; n < argc; n++) {
      int m = n, eat = 0;
      if (n + 1 < argc) {
          if (strcmp(argv[n], "-geometry") == 0) {
            char *src = argv[n + 1];
            char *dst = 0;
            int value = strtol(src, &dst, 0);
            if (dst != src) {
                if (value > 2)
                  SetCols(value);
                if (*dst++ == 'x') {
                  src = dst;
                  value = strtol(src, &dst, 0);
                  if (value > 2) {
                      SetRows(value);
#ifdef VILE_OLE
                      oa_opts.cols = term.cols;
                      oa_opts.rows = term.rows;
#endif
                  }
                }
                eat = 2;
            }
          } else if (strcmp(argv[n], "-font") == 0 ||
                   strcmp(argv[n], "-fn") == 0) {
            fontstr = argv[n + 1];
            eat = 2;
          }
      }
#ifdef VILE_OLE
      if (eat == 0) {
          /* No valid options seen yet. */

          if (argv[n][0] == '-' && argv[n][1] == 'O') {
            int which = argv[n][2];

            if (which == 'r') {
                /*
                 * Flag OLE registration request,
                 * but don't eat argument.  Instead,
                 * registration will be carried out
                 * in main.c, so that the regular
                 * cmdline parser has an opportunity
                 * to flag misspelled OLE options.
                 *
                 * Ex:  winvile -Or -multiple
                 */

                oa_reg = TRUE;
            } else if (which == 'u')
                ExitProgram(oleauto_unregister());
            else if (which == 'a') {
                oa_invoke = TRUE;
                eat = 1;
            }
          } else if (strcmp(argv[n], "-invisible") == 0) {
            oa_opts.invisible = TRUE;
            eat = 1;
          } else if (strcmp(argv[n], "-multiple") == 0) {
            oa_opts.multiple = TRUE;
            eat = 1;
          }
      }
#endif
      if (eat) {
          while (m + eat <= argc) {
            argv[m] = argv[m + eat];
            m++;
          }
          n--;
          argc -= eat;
      }
    }

#ifdef VILE_OLE
    if (oa_reg && oa_invoke) {
      /* tsk tsk */

      MessageBox(cur_win->main_hwnd,
               "-Oa and -Or are mutually exclusive",
               prognam,
               MB_OK | MB_ICONSTOP);
      ExitProgram(BADEXIT);
    }
    if (oa_reg) {
      /*
       * The main program's cmd line parser will eventually cause
       * OLE autoamation registration to occur, at which point
       * winvile exits.  So don't show a window.
       */

      nCmdShow = SW_HIDE;
    }
    if (oa_opts.invisible)
      nCmdShow = SW_HIDE;
#endif
    desired_wdw_state = nCmdShow;

    if (!InitInstance(hInstance))
      return (FALSE);

    /*
     * Vile window created and default font set.  It's now kosher to set
     * the font from a cmdline switch.
     */
    if (fontstr) {
      int success = ntwinio_font_frm_str(fontstr, TRUE);

#ifdef VILE_OLE
      if (oa_reg) {
          if (!success) {
            /*
             * That's it, game over -- crummy font spec detected during
             * OLE registration.
             */

            ExitProgram(BADEXIT);
          } else
            oa_opts.fontstr = fontstr;
      }
#else
      (void) success;
#endif
      /* Regardless of success or failure, continue with new/default font. */
    }
#ifdef VILE_OLE
    if (oa_invoke) {
      /* Intialize OLE Automation */

      if (!oleauto_init(&oa_opts))
          ExitProgram(BADEXIT);
    }
#endif

    return MainProgram(argc, argv);
}

/* return winvile's main window handle */
void *
winvile_hwnd(void)
{
    return (cur_win->main_hwnd);
}

/*
 * To prevent drawing winvile's main frame twice during startup (once for
 * the default font/geometry and then again when the user specifies
 * his/her preferences), this entry point is provided to do the job just
 * once.  Called from main.c .
 */
void
winvile_start(void)
{
    RECT desktop, vile, tray;
    int moved_window = 0;
    HWND tray_hwnd, desktop_hwnd;

    enable_popup = global_g_val(GMDPOPUPMENU);

    /*
     * Before displaying the main window, see if its bottom border lies
     * beneath the Win95/NT taskbar.  If so, move the frame up out of the
     * way (hope this code works for Win98, too :-) ).
     */
    tray_hwnd = FindWindow("Shell_TrayWnd", NULL);
    desktop_hwnd = GetDesktopWindow();
    GetWindowRect(desktop_hwnd, &desktop);
    GetWindowRect(cur_win->main_hwnd, &vile);
    if (tray_hwnd != NULL) {
      GetWindowRect(tray_hwnd, &tray);
      if (vile.bottom > tray.top) {
          /*
           * Could be a conflict...but only if the taskbar is parked
           * horizontally, at bottom of screen.
           */

          if ((tray.bottom + 10 >= desktop.bottom) &&
            (tray.right - tray.left + 10 >= desktop.right - desktop.left)) {
            int diff = vile.bottom - tray.top + 5;

            if (vile.top - diff >= desktop.top) {
                /* editor's title bar won't be shifted offscreen. */

                MoveWindow(cur_win->main_hwnd,
                         vile.left,
                         vile.top - diff,
                         vile.right - vile.left,
                         vile.bottom - vile.top,
                         FALSE);
                moved_window = 1;
            }
          }
      }
    }
    if (!moved_window) {
      /*
       * no conflict with the task bar...but is the editor's bottom edge
       * below the deskop rect?
       */

      if (vile.bottom > desktop.bottom) {
          int diff = vile.bottom - desktop.bottom + 5;

          if (vile.top - diff >= desktop.top) {
            /* editor's title bar won't be shifted offscreen. */

            MoveWindow(cur_win->main_hwnd,
                     vile.left,
                     vile.top - diff,
                     vile.right - vile.left,
                     vile.bottom - vile.top,
                     FALSE);
          }
      }
    }
    ShowWindow(cur_win->main_hwnd, desired_wdw_state);
    UpdateWindow(cur_win->main_hwnd);
    caret_disabled = FALSE;
    fshow_cursor();
    initialized = TRUE;
}

/*
 * Unfortunately, winvile cannot use vile's standard cursor visible/invisible
 * API.  Instead, winvile manages its cursor state internally (and does a
 * pretty good job, at that).  However, there are times when the editor
 * makes display changes that require an external "override".
 *
 * Set queue_change (Boolean) true to defer cursor change until winvile next
 * reads its message queue).
 *
 * Returns:  Boolean (previous cursor visibility state).
 */
int
winvile_cursor_state(
                  int visible,      /* Boolean, T -> cursor on */
                  int queue_change)
{
    int rc = caret_visible;

    if (!queue_change) {
      if (visible) {
          /* don't set turn on cursor unless editor has focus */
          if (GetFocus() == winvile_hwnd())
            fshow_cursor();
      } else
          fhide_cursor();
    } else {
      PostMessage(winvile_hwnd(),
                (visible) ? WM_WVILE_CURSOR_ON : WM_WVILE_CURSOR_OFF,
                0,
                0);
    }
    return (rc);
}

/*
 * winvile doesn't exit via a traditional event loop.  provide a hook
 * to cleanup allocated resources.
 */
void
winvile_cleanup(void)
{
    if (cur_win->main_hwnd)
      DragAcceptFiles(cur_win->main_hwnd, FALSE);
    if (popup_menu) {
      DestroyMenu(popup_menu);
      popup_menu = NULL;      /* be re-entrant */
    }
}

#ifdef VILE_OLE
void
ntwinio_oleauto_reg(void)
{
    /* Pound a bunch of OLE registration data into the registry & exit. */

    ExitProgram(oleauto_register(&oa_opts));
}

void
ntwinio_redirect_hwnd(int redirect)
{
    redirect_keys = redirect;
}
#endif

/*
 * Handle changes to "popup-menu" global setting.
 */
int
chgd_popupmenu(VALARGS * args, int glob_vals, int testing)
{
    if (!testing)
      enable_popup_menu();
    return TRUE;
}

#if OPT_ICURSOR

/* supported syntax is described in chgd_icursor() */
static int
parse_icursor_string(char *str)
{
    int failed, rc = TRUE;
    ULONG style;

    style = vl_atoul(str, 10, &failed);
    if (failed || style > MAX_CURSOR_STYLE)
      rc = FALSE;
    else {
      icursor_style = style;
      icursor = (style > 0);
    }
    return (rc);
}

/*
 * user changed icursor mode
 *
 * Insertion cursor mode is a string that specifies one of three values:
 *
 *     0 -> disable insertion cursor
 *     1 -> insert mode displays vertical bar, cmd mode displays block cursor
 *     2 -> cmd mode displays vertical bar, insert mode displays block cursor
 */
int
chgd_icursor(VALARGS * args, int glob_vals, int testing)
{
    int rc = TRUE;

    if (!testing) {
      char *val = args->global->vp->p;

      if (!parse_icursor_string(val)) {
          mlforce("[invalid icursor syntax]");
          rc = FALSE;
      } else {
          if (caret_visible) {
            fhide_cursor();   /* Kill the old caret        */
            fshow_cursor();   /* And bring it back to life */
          }
      }
    }
    return (rc);
}
#endif /* OPT_ICURSOR */

/*
 * Split the version-message to allow us to format with tabs, so the
 * proportional font doesn't look ugly.
 */
static size_t
option_size(const char *option)
{
    if (*option == '-') {
      const char *next = skip_ctext(option);
      if (next[0] == ' '
          && next[1] != ' '
          && next[1] != 0) {
          next = skip_ctext(next + 1);
          return next - option;
      }
      return 14;        /* use embedded blanks to fix the tabs ... */
    }
    return 0;
}

void
gui_version(char *program)
{
    ShowWindow(cur_win->main_hwnd, SW_HIDE);
    MessageBox(cur_win->main_hwnd, getversion(), prognam, MB_OK | MB_ICONSTOP);
}

void
gui_usage(char *program, const char *const *options, size_t length)
{
    char *buf, *s;
    size_t need, n;
    const char *fmt1 = "%s\n\nOptions:\n";
    const char *fmt2 = "    %s\t%s\n";
    const char *fmt3 = "%s\n";

    /*
     * Hide the (partly-constructed) main window.  It'll flash (FIXME).
     */
    ShowWindow(cur_win->main_hwnd, SW_HIDE);

    need = strlen(fmt1) + strlen(prognam);
    for (n = 0; n < length; n++) {
      if (option_size(options[n]))
          need += strlen(fmt2) + strlen(options[n]);
      else
          need += strlen(fmt3) + strlen(options[n]);
    }
    buf = malloc(need);

    s = lsprintf(buf, fmt1, prognam);
    for (n = 0; n < length; n++) {
      char temp[80];
      if ((need = option_size(options[n])) != 0) {
          strncpy(temp, options[n], need);
          temp[need] = EOS;
          s = lsprintf(s, fmt2, temp, skip_cblanks(options[n] + need));
      } else {
          s = lsprintf(s, fmt3, options[n]);
      }
    }

    MessageBox(cur_win->main_hwnd, buf, prognam, MB_OK | MB_ICONSTOP);
}

/*
 * Standard terminal interface dispatch table. None of the fields point into
 * "termio" code.
 */

TERM term =
{
    NROW,
    NROW,
    NCOL,
    NCOL,
    NPAUSE,
    ntopen,
    ntclose,
    ntkopen,
    ntkclose,
    ntgetch,
    ntputc,
    nttypahead,
    ntflush,
    ntmove,
    nteeol,
    nteeop,
    ntbeep,
    ntrev,
    ntcres,
#if OPT_COLOR
    ntfcol,
    ntbcol,
    set_ctrans,
#else
    nullterm_setfore,
    nullterm_setback,
    nullterm_setpal,
#endif
    nullterm_setccol,
    ntscroll,
    nullterm_pflush,
#if OPT_ICURSOR
    nticursor,
#else
    nullterm_icursor,
#endif
#if OPT_TITLE
    nttitle,
#else
    nullterm_settitle,
#endif
    nullterm_watchfd,
    nullterm_unwatchfd,
    nullterm_cursorvis,
};

Generated by  Doxygen 1.6.0   Back to index