/*
 * standard table class.
 *
 * main interface functions.
 *
 * see table.h for interface description
 */

#include <precomp.h>

#include "table.h"
#include "tpriv.h"



/* global tools etc */
extern HANDLE hLibInst;
HANDLE hVertCurs;
HANDLE hNormCurs;
HPEN hpenDotted;
UINT gtab_msgcode;

/* function prototypes */
LRESULT gtab_wndproc(HWND, UINT, WPARAM, LPARAM);
void gtab_createtools(void);
void gtab_deltable(HWND hwnd, lpTable ptab);
lpTable gtab_buildtable(HWND hwnd, DWORD_PTR id);
void gtab_setsize(HWND hwnd, lpTable ptab);
void gtab_newsize(HWND hwnd, lpTable ptab);
void gtab_calcwidths(HWND hwnd, lpTable ptab);
BOOL gtab_alloclinedata(HWND hwnd, HANDLE heap, lpTable ptab);
void gtab_invallines(HWND hwnd, lpTable ptab, int start, int count);
void gtab_append(HWND hwnd, lpTable ptab, int rows, DWORD_PTR id);

/*
 * initialise window class - called from DLL main init
 */
void
gtab_init(void)
{
    WNDCLASS wc;

    gtab_createtools();
    gtab_msgcode = RegisterWindowMessage(TableMessage);

    wc.style = CS_GLOBALCLASS | CS_DBLCLKS;
    wc.lpfnWndProc = gtab_wndproc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = WLTOTAL;
    wc.hInstance = hLibInst;
    wc.hIcon = NULL;
    wc.hCursor = NULL;
    wc.hbrBackground = GetStockObject(WHITE_BRUSH);
    wc.lpszClassName = TableClassName;
    wc.lpszMenuName = NULL;

    RegisterClass(&wc);
}

void
gtab_createtools(void)
{
    hVertCurs = LoadCursor(hLibInst, "VertLine");
    hNormCurs = LoadCursor(NULL, IDC_ARROW);

    hpenDotted = CreatePen(PS_DOT, 1, RGB(0, 0, 0));
}

void
gtab_deltools(void)
{
    DeleteObject(hpenDotted);
}


LRESULT
gtab_wndproc(
            HWND hwnd,
            UINT msg,
            WPARAM wParam,
            LPARAM lParam
            )
{
    CREATESTRUCT FAR * csp;
    HWND hOwner;
    lpTable ptab;
    HANDLE hHeap;
    lpTableSelection pselect;
    long oldtop;
    long change;

    switch (msg) {

        case WM_CREATE:
            /* create window. set the wnd extra bytes to
             * contain the owner window, a heap and a null table.
             * Owner window is either in lParam or the parent.
             * Then wait for TM_NEWID.
             */
            csp = (CREATESTRUCT FAR *) lParam;
            if (csp->lpCreateParams == NULL) {
                hOwner = GetParent(hwnd);
            } else {
                hOwner = (HWND) csp->lpCreateParams;
            }
            ptab = NULL;
            hHeap = gmem_init();
            SetWindowLongPtr(hwnd, WL_TABLE, (LONG_PTR) ptab);
            SetWindowLongPtr(hwnd, WW_OWNER, (LONG_PTR) hOwner);
            SetWindowLongPtr(hwnd, WW_HEAP, (LONG_PTR) hHeap);

            SetScrollRange(hwnd, SB_VERT, 0, 0, TRUE);
            SetScrollRange(hwnd, SB_HORZ, 0, 0, TRUE);
            break;

        case TM_NEWID:
            /* complete change of table.
             * close old table, discard memory and
             * build new table
             */
            ptab = (lpTable) GetWindowLongPtr(hwnd, WL_TABLE);
            if (ptab != NULL) {
                gtab_sendtq(hwnd, TQ_CLOSE, ptab->hdr.id);
                gtab_deltable(hwnd, ptab);
                SetCursor(hNormCurs);
                SetWindowLongPtr(hwnd, WL_TABLE, 0);
            }
            if ( (ptab = gtab_buildtable(hwnd, (DWORD_PTR)lParam)) != NULL) {
                SetWindowLongPtr(hwnd, WL_TABLE, (LONG_PTR) ptab);
                gtab_setsize(hwnd, ptab);
            } else {
                SetScrollRange(hwnd, SB_VERT, 0, 0, TRUE);
                SetScrollRange(hwnd, SB_HORZ, 0, 0, TRUE);
            }
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case TM_NEWLAYOUT:
            /* change of layout but for same id. no TQ_CLOSE,
             * but otherwise same as TM_NEWID
             */
            ptab = (lpTable) GetWindowLongPtr(hwnd, WL_TABLE);
            if (ptab != NULL) {
                gtab_deltable(hwnd, ptab);
                SetCursor(hNormCurs);
                SetWindowLongPtr(hwnd, WL_TABLE, 0);
            }
            if ( (ptab = gtab_buildtable(hwnd, (DWORD_PTR)lParam)) != NULL) {
                SetWindowLongPtr(hwnd, WL_TABLE, (LONG_PTR) ptab);
                gtab_setsize(hwnd, ptab);
            } else {
                SetScrollRange(hwnd, SB_VERT, 0, 0, TRUE);
                SetScrollRange(hwnd, SB_HORZ, 0, 0, TRUE);
            }
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case TM_REFRESH:
            /* data in table has changed. nrows may have
             * changed. ncols and col types have not changed
             */
            ptab = (lpTable) GetWindowLongPtr(hwnd, WL_TABLE);
            if (ptab != NULL) {
                gtab_newsize(hwnd, ptab);
                gtab_sendtq(hwnd, TQ_SHOWWHITESPACE, (LPARAM) &ptab->show_whitespace);
            }
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case TM_SELECT:
            ptab = (lpTable) GetWindowLongPtr(hwnd, WL_TABLE);
            if (ptab != NULL) {
                pselect = (lpTableSelection) lParam;

                gtab_select(hwnd, ptab, pselect->startrow,
                            pselect->startcell,
                            pselect->nrows,
                            pselect->ncells,
                            TRUE);
                gtab_showsel_middle(hwnd, ptab, pselect->dyRowsFromTop);
            }
            break;

        case TM_GETSELECTION:
            ptab = (lpTable) GetWindowLongPtr(hwnd, WL_TABLE);
            if (ptab != NULL) {
                pselect = (lpTableSelection) lParam;

                *pselect = ptab->select;
            }
            break;

        case TM_PRINT:
            ptab = (lpTable) GetWindowLongPtr(hwnd, WL_TABLE);
            hHeap = (HANDLE) GetWindowLongPtr(hwnd, WW_HEAP);
            if (ptab != NULL) {
                return gtab_print(hwnd, ptab, hHeap, (lpPrintContext) lParam);
            }
            return FALSE;

        case TM_SETTABWIDTH:
            ptab = (lpTable) GetWindowLongPtr(hwnd, WL_TABLE);
            if (!ptab)
                return 0;
            ptab->tabchars = (int)lParam;
            InvalidateRect(hwnd, NULL, FALSE);
            break;

        case TM_TOPROW:

            /* return top row. if wParam is TRUE, set lParam
             * as the new toprow
             */
            ptab = (lpTable) GetWindowLongPtr(hwnd, WL_TABLE);
            if (ptab == NULL) {
                return(0);
            }
            oldtop = ptab->toprow;
            if ((wParam) && (lParam < ptab->hdr.nrows)) {
                change = (long)lParam - ptab->toprow;
                change -= ptab->hdr.fixedrows;
                gtab_dovscroll(hwnd, ptab, change);
            }
            return(oldtop);

        case TM_ENDROW:
            /* return the last visible row in the window */
            ptab = (lpTable) GetWindowLongPtr(hwnd, WL_TABLE);
            if (ptab == NULL) {
                return(0);
            }
            return(ptab->nlines + ptab->toprow - 1);


        case TM_APPEND:
            /* new rows have been added to the end of the
             * table, but the rest of the table has not
             * been changed. Update without forcing redraw of
             * everything.
             * lParam contains the new total nr of rows
             */
            ptab = (lpTable) GetWindowLongPtr(hwnd, WL_TABLE);
            if (ptab != NULL) {
                gtab_append(hwnd, ptab, (int) wParam, (DWORD_PTR)lParam);
                return(TRUE);
            }
            break;

        case WM_SIZE:
            ptab = (lpTable) GetWindowLongPtr(hwnd, WL_TABLE);
            if (ptab != NULL) {
                gtab_setsize(hwnd, ptab);
            }
            break;

        case WM_ERASEBKGND:
            return TRUE;

        case WM_DESTROY:
            ptab = (lpTable) GetWindowLongPtr(hwnd, WL_TABLE);
            if (ptab != NULL) {
                gtab_sendtq(hwnd, TQ_CLOSE, ptab->hdr.id);
                gtab_deltable(hwnd, ptab);
            }
            hHeap = (HANDLE) GetWindowLongPtr(hwnd, WW_HEAP);
            gmem_freeall(hHeap);
            break;

        case WM_SYSCOLORCHANGE:
            InvalidateRect(hwnd, NULL, TRUE);
            break;

        case WM_PAINT:
            gtab_paint(hwnd);
            break;

        case WM_HSCROLL:
            ptab = (lpTable) GetWindowLongPtr(hwnd, WL_TABLE);
            if (ptab != NULL) {
                gtab_msg_hscroll(hwnd, ptab,
                                 GET_SCROLL_OPCODE(wParam, lParam),
                                 GET_SCROLL_POS(wParam, lParam));
            }
            break;

        case WM_VSCROLL:
            ptab = (lpTable) GetWindowLongPtr(hwnd, WL_TABLE);
            if (ptab != NULL) {
                gtab_msg_vscroll(hwnd, ptab,
                                 GET_SCROLL_OPCODE(wParam, lParam),
                                 GET_SCROLL_POS(wParam, lParam));
            }
            break;

        case WM_MOUSEMOVE:
            ptab = (lpTable) GetWindowLongPtr(hwnd, WL_TABLE);
            if (ptab != NULL) {
                gtab_move(hwnd, ptab, (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam));
            } else {
                SetCursor(hNormCurs);
            }
            break;

        case WM_LBUTTONDOWN:
            ptab = (lpTable) GetWindowLongPtr(hwnd, WL_TABLE);
            if (ptab != NULL) {
                gtab_press(hwnd, ptab, (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam));
            }
            break;

        case WM_RBUTTONDOWN:
            ptab = (lpTable) GetWindowLongPtr(hwnd, WL_TABLE);
            if (ptab != NULL) {
                gtab_rightclick(hwnd, ptab, (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam));
            }
            break;

        case WM_LBUTTONUP:
            ptab = (lpTable) GetWindowLongPtr(hwnd, WL_TABLE);
            if (ptab != NULL) {
                gtab_release(hwnd, ptab,
                             (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam));
            }
            break;

        case WM_LBUTTONDBLCLK:
            ptab = (lpTable) GetWindowLongPtr(hwnd, WL_TABLE);
            if (ptab != NULL) {
                gtab_dblclick(hwnd, ptab,
                              (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam));
            }
            break;

        case WM_KEYDOWN:
            /* handle key presses for cursor movement about
             * the table, and return/space for selection.
             * Any key we don't handle is passed to the owner window
             * for him to handle.
             * The table window should have the focus
             */
            ptab = (lpTable) GetWindowLongPtr(hwnd, WL_TABLE);
            if (ptab != NULL) {
                if (gtab_key(hwnd, ptab, (int)wParam) != 0) {
                    /* pass key to owner since
                     * we don't know what to do with it
                     */
                    hOwner = (HANDLE) GetWindowLongPtr(hwnd, WW_OWNER);
                    return(SendMessage(hOwner, WM_KEYDOWN, wParam, lParam));
                } else {
                    return(0);
                }
            }
            break;

#ifdef WM_MOUSEWHEEL
        case WM_MOUSEWHEEL:
            ptab = (lpTable)GetWindowLongPtr(hwnd, WL_TABLE);
            if (ptab != NULL) {
                if (gtab_mousewheel(hwnd,ptab, LOWORD(wParam), (short)HIWORD(wParam))) {
                    // Input was not handled. Need to forward to the owner.
                    hOwner = (HWND)GetWindowLongPtr(hwnd, WW_OWNER);
                    return SendMessage(hOwner, WM_MOUSEWHEEL, wParam, lParam);
                }
            }
            break;
#endif

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

/*
 * send a table-query message to the owner window. returns message
 * value.
 */
INT_PTR
gtab_sendtq(
           HWND hwnd,
           UINT cmd,
           LPARAM lParam
           )
{
    HWND hOwner;

    hOwner = (HANDLE) GetWindowLongPtr(hwnd, WW_OWNER);
    return (SendMessage(hOwner, gtab_msgcode, cmd, lParam));
}

/*
 * free the memory allocated for the array of lines (each containing
 * an array of Cells, each containing an array of chars for the actual
 * data). Called on any occasion that would change the number of visible lines
 */
void
gtab_freelinedata(
                 HANDLE hHeap,
                 lpTable ptab
                 )
{
    int i, j, ncols;
    lpCellData cd;


    ncols = ptab->hdr.ncols;

    /* for each line */
    for (i = 0; i < ptab->nlines; i++) {
        /* for each cell */
        for (j = 0; j < ncols; j++) {
            /* free up the actual text space */
            cd = &ptab->pdata[i].pdata[j];
            gmem_free(hHeap, (LPSTR) cd->ptext, cd->nchars);
            gmem_free(hHeap, (LPSTR) cd->pwzText, cd->nchars);
        }
        /* dealloc array of CellData */
        gmem_free(hHeap, (LPSTR) ptab->pdata[i].pdata,
                  sizeof(CellData) * ncols);
    }
    /* de-alloc array of linedatas */
    gmem_free(hHeap, (LPSTR) ptab->pdata,
              sizeof(LineData) * ptab->nlines);
    ptab->pdata = NULL;
}

/* allocate and init array of linedatas (include cell array
 * and text for each cell)
 */
BOOL
gtab_alloclinedata(
                  HWND hwnd,
                  HANDLE heap,
                  lpTable ptab
                  )
{
    lpLineData pline;
    lpCellData cd;
    int i, j;

    ptab->pdata = (lpLineData) gmem_get(heap,
                                        sizeof(LineData) * ptab->nlines);
    if (ptab->pdata == NULL) {
        return(FALSE);
    }
    for (i = 0; i < ptab->nlines; i++) {
        pline = &ptab->pdata[i];
        pline->linepos.size = ptab->rowheight;
        pline->pdata = (lpCellData) gmem_get(heap,
                                             sizeof(CellData) * ptab->hdr.ncols);
        if (pline->pdata == NULL) {
            return(FALSE);
        }
        for (j = 0; j < ptab->hdr.ncols; j++) {
            cd = &pline->pdata[j];
            cd->props.valid = 0;
            cd->flags = 0;
            cd->nchars = ptab->pcolhdr[j].nchars;
            if (cd->nchars > 0) {
                cd->ptext = gmem_get(heap, cd->nchars);
                if (cd->ptext == NULL) {
                    return(FALSE);
                }
                cd->pwzText = 0;
            }
        }
    }
    return(TRUE);
}

/*
 * free up all table data structures. Called for new layout or new data.
 */
void
gtab_deltable(
             HWND hwnd,
             lpTable ptab
             )
{
    HANDLE hHeap;
    int ncols;

    if (ptab == NULL) {
        return;
    }
    hHeap = (HANDLE) GetWindowLongPtr(hwnd, WW_HEAP);
    ncols = ptab->hdr.ncols;

    if (ptab->pcolhdr != NULL) {
        gmem_free(hHeap, (LPSTR) ptab->pcolhdr,
                  sizeof(ColProps) * ncols);
    }
    if (ptab->pcellpos != NULL) {
        gmem_free(hHeap, (LPSTR) ptab->pcellpos,
                  sizeof(CellPos) * ncols);
    }
    if (ptab->pdata != NULL) {
        gtab_freelinedata(hHeap, ptab);
    }
    gmem_free(hHeap, (LPSTR) ptab, sizeof(Table));
}


/*
 * build up a Table struct (excluding data allocation and
 * anything to do with font or window size).
 * return ptr to this or NULL if error
 */
lpTable
gtab_buildtable(
               HWND hwnd,
               DWORD_PTR id
               )
{
    lpTable ptab;
    HANDLE hHeap;
    int ncols, i;
    ColPropsList cplist;

    hHeap = (HANDLE) GetWindowLongPtr(hwnd, WW_HEAP);
    ptab = (lpTable) gmem_get(hHeap, sizeof(Table));
    if (ptab == NULL) {
        return(NULL);
    }

    // get the tab width. most clients will not support this
    if (gtab_sendtq(hwnd, TQ_TABS, (LPARAM) &ptab->tabchars) == FALSE) {
        ptab->tabchars = TABWIDTH_DEFAULT;
    }

    // get the show whitespace value
    if (gtab_sendtq(hwnd, TQ_SHOWWHITESPACE, (LPARAM) &ptab->show_whitespace) == FALSE) {
        ptab->show_whitespace = FALSE;
    }

    /* get the row/column count from owner window */
    ptab->hdr.id = id;
    ptab->hdr.props.valid = 0;
    ptab->hdr.sendscroll = FALSE;
    if (gtab_sendtq(hwnd, TQ_GETSIZE, (LPARAM) &ptab->hdr) == FALSE) {
        return(NULL);
    }

    ncols = ptab->hdr.ncols;
    ptab->pcolhdr = (lpColProps) gmem_get(hHeap, sizeof(ColProps) * ncols);
    if (ptab->pcolhdr == NULL) {
        /* should prob send TQ_CLOSE at this point */
        return(NULL);
    }

    /* init col properties to default */
    for (i=0; i < ncols; i++) {
        ptab->pcolhdr[i].props.valid = 0;
        ptab->pcolhdr[i].nchars = 0;
    }
    /* get the column props from owner */
    cplist.plist = ptab->pcolhdr;
    cplist.id = id;
    cplist.startcol = 0;
    cplist.ncols = ncols;
    gtab_sendtq(hwnd, TQ_GETCOLPROPS, (LPARAM) &cplist);

    /* init remaining fields */
    ptab->pcellpos = (lpCellPos) gmem_get(hHeap, sizeof(CellPos) * ncols);
    if (ptab->pcellpos == NULL) {
        return(NULL);
    }

    ptab->scrollscale = 1;
    ptab->scroll_dx = 0;
    ptab->toprow = 0;
    ptab->pdata = NULL;
    ptab->nlines = 0;
    ptab->trackmode = TRACK_NONE;

    /* we have to notify owner of the current selection
     * whenever it is changed
     */
    ptab->select.id = id;
    gtab_select(hwnd, ptab, 0, 0, 0, 0, TRUE);

    /* calc ave height/width, cell widths and min height.
     * these change only when cell properties / col count changes -
     * ie only on rebuild-header events
     */
    gtab_calcwidths(hwnd, ptab);
    return(ptab);
}

/* set sizes that are based on window size and scroll pos
 * set:
 *      winwidth
 *      nlines
 *      cellpos start, clip start/end
 * alloc linedata and init
 */
void
gtab_setsize(
            HWND hwnd,
            lpTable ptab
            )
{
    RECT rc;
    int nlines;
    HANDLE heap;
    long change;
    SCROLLINFO si;

    GetClientRect(hwnd, &rc);
    ptab->winwidth = rc.right - rc.left;
    nlines = (rc.bottom - rc.top) / ptab->rowheight;
    /* nlines is the number of whole lines - add one extra
     * for the partial line at the bottom
     */
    nlines += 1;

    /* alloc space for nlines of data - if nlines has changed */
    if (nlines != ptab->nlines) {
        heap = (HANDLE) GetWindowLongPtr(hwnd, WW_HEAP);
        gtab_freelinedata(heap, ptab);
        ptab->nlines = nlines;
        if (!gtab_alloclinedata(hwnd, heap, ptab)) {
            ptab->nlines = 0;
            return;
        }
    }

    si.cbSize = sizeof(si);
    si.fMask = SIF_PAGE|SIF_RANGE;
    si.nMin = 0;

    /* set scroll vertical range */
    si.nMax = ptab->hdr.nrows;
    si.nPage = ptab->nlines;
    if (si.nMax < 0) {
        si.nMax = 0;
        change =  -(ptab->toprow);
    } else if (ptab->toprow > si.nMax) {
        change = si.nMax - ptab->toprow;
    } else {
        change = 0;
    }
    /* the scroll range must be 16-bits for Win3
     * scale until this is true
     */
    ptab->scrollscale = 1;
    while (si.nMax > 32766) {
        ptab->scrollscale *= 16;
        si.nMax /= 16;
        si.nPage /= 16;
    }
    if (!si.nPage)
        si.nPage = 1;

    SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
    gtab_dovscroll(hwnd, ptab, change);

    /* set horz scroll range */
    si.nMax = ptab->rowwidth;
    si.nPage = ptab->winwidth;
    if (si.nMax < 0) {
        si.nMax = 0;
        change = -(ptab->scroll_dx);
    } else if (ptab->scroll_dx > si.nMax) {
        change = si.nMax - ptab->scroll_dx;
    } else {
        change = 0;
    }
    /* horz scroll range will always be < 16 bits */
    SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);
    gtab_dohscroll(hwnd, ptab, change);
}

/* set column widths/height and totals (based on column props)
 * - no assumption of window size (see gtab_setsize)
 * sets avewidth,rowheight,cellpos.size,rowwidth (total of cellpos.size)
 */
void
gtab_calcwidths(
               HWND hwnd,
               lpTable ptab
               )
{
    int i, cxtotal, cx, ave;
    TEXTMETRIC tm = {0};
    TEXTMETRIC tmcol = {0};
    HDC hdc;
    lpProps hdrprops, cellprops;
    HFONT hfont;

    hfont = NULL;  /* eliminate spurious diagnostic, make code worse */
    hdrprops = &ptab->hdr.props;
    hdc = GetDC(hwnd);
    if (hdc)
    {
        GetTextMetrics(hdc, &tm);
        ptab->rowheight = tm.tmHeight + tm.tmExternalLeading;
        if (hdrprops->valid & P_FONT) {
            hfont = SelectObject(hdc, hdrprops->hFont);
        }
        GetTextMetrics(hdc, &tm);
        if (hdrprops->valid & P_FONT) {
            SelectObject(hdc, hfont);
        }
        ReleaseDC(hwnd, hdc);
    }
    else
    {
        // arbitrary, whatever...
        ptab->rowheight = 14;
        tm.tmHeight = 14;
        tm.tmAveCharWidth = 5;
    }

    /* get width and height of average character */
    ptab->avewidth = tm.tmAveCharWidth;
    if (tm.tmHeight + tm.tmExternalLeading < ptab->rowheight - 2 ||
        tm.tmHeight + tm.tmExternalLeading > ptab->rowheight) {
        // fudge so the default FixedSys (and anything of similar size)
        // doesn't vertically clip the System font used for line numbers,
        // filenames, etc.
        ptab->rowheight = tm.tmHeight;
        if (tm.tmExternalLeading)
            ptab->rowheight += tm.tmExternalLeading;
        else
            ptab->rowheight++;
    }
    if (hdrprops->valid & P_HEIGHT) {
        ptab->rowheight = hdrprops->height;
    }

    /* set pixel width of each cell (and add up for row total)
     * based on ave width * nr chars, unless P_WIDTH set
     */
    cxtotal = 0;
    for (i = 0; i < ptab->hdr.ncols; i++) {
        cellprops = &ptab->pcolhdr[i].props;

        if (cellprops->valid & P_WIDTH) {
            cx = cellprops->width;
        } else if (hdrprops->valid & P_WIDTH) {
            cx = hdrprops->width;
        } else {

            if (cellprops->valid & P_FONT) {
                hdc = GetDC(hwnd);
                if (hdc)
                {
                    hfont = SelectObject(hdc, cellprops->hFont);
                    GetTextMetrics(hdc, &tmcol);
                    SelectObject(hdc, hfont);
                    ReleaseDC(hwnd, hdc);
                    ave = tmcol.tmAveCharWidth;
                }
                else
                    ave = 5;        // arbitrary, whatever...
            } else {
                ave = ptab->avewidth;
            }
            /* ave width * nchars */
            cx =  ptab->pcolhdr[i].nchars + 1;
            cx *= ave;
        }
        /* add 2 pixels for box lines */
        cx += 2;
        ptab->pcellpos[i].size = cx;
        cxtotal += cx;
    }
    ptab->rowwidth = cxtotal;
}

/* called when row data + possible nrows changes.
 * other changes are ignored
 */
void
gtab_newsize(
            HWND hwnd,
            lpTable ptab
            )
{
    TableHdr hdr;

    /* get new row count */
    hdr = ptab->hdr;
    gtab_sendtq(hwnd, TQ_GETSIZE, (LPARAM) &hdr);
    if (hdr.nrows != ptab->hdr.nrows) {
        ptab->hdr.nrows = hdr.nrows;
        gtab_setsize(hwnd, ptab);
    }

    gtab_invallines(hwnd, ptab, 0, ptab->nlines);

    InvalidateRect(hwnd, NULL, FALSE);
}

void
gtab_invallines(
               HWND hwnd,
               lpTable ptab,
               int start,
               int count
               )
{
    int i, j;

    for (i = start; i < start + count; i++) {
        for (j = 0; j < ptab->hdr.ncols; j++) {
            ptab->pdata[i].pdata[j].flags = 0;
        }
    }
}

/* new rows have been added to the table. adjust the scroll range and
 * position, and redraw the rows if the end of the table is currently
 * visible.
 * rows = the new total row count.
 */
void
gtab_append(
           HWND hwnd,
           lpTable ptab,
           int rows,
           DWORD_PTR id
           )
{
    long oldrows;
    int line, nupdates;
    RECT rc;
    SCROLLINFO si;


    /* change to the new id */
    ptab->hdr.id = id;
    ptab->select.id = id;

    /* update the header, but remember the old nr of rows
     * so we know where to start updating
     */
    oldrows = ptab->hdr.nrows;

    /* check that the new nr of rows is not smaller. this is
     * illegal at this point and should be ignored
     */
    if (oldrows >= rows) {
        return;
    }

    ptab->hdr.nrows = rows;

    si.cbSize = sizeof(si);
    si.fMask = SIF_PAGE|SIF_RANGE;
    si.nMin = 0;

    /* set the vertical scroll range */
    si.nMax = rows;
    si.nPage = ptab->nlines;
    if (si.nMax < 0) {
        si.nMax = 0;
    }

    /* force the scroll range into 16-bits for win 3.1 */
    ptab->scrollscale = 1;
    while (si.nMax > 32766) {
        ptab->scrollscale *= 16;
        si.nMax /= 16;
        si.nPage /= 16;
    }
    if (!si.nPage)
        si.nPage = 1;

    /* now set the scroll bar range and position */
    SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
    if (si.nMax > 0) {
        SetScrollPos(hwnd, SB_VERT,
                     (int) (ptab->toprow / ptab->scrollscale), TRUE);
    }

    /* calculate which screen lines need to be updated - find what
     * screen line the start of the new section is at
     */
    line = gtab_rowtoline(hwnd, ptab, oldrows);
    if (line == -1) {
        /* not visible -> no more to do */
        return;
    }

    /* how many lines to update - rest of screen or nr of
     * new lines if less than rest of screen
     */
    nupdates = min((ptab->nlines - line), (int)(rows - oldrows));

    /* invalidate the screen line buffers to indicate data
     * needs to be refetch from parent window
     */
    gtab_invallines(hwnd, ptab, line, nupdates);

    /* calculate the region of the screen to be repainted -
     * left and right are same as window. top and bottom
     * need to be calculated from screen line height
     */

    GetClientRect(hwnd, &rc);
    rc.top += line * ptab->rowheight;
    rc.bottom = rc.top + (nupdates * ptab->rowheight);

    /* force a repaint of the updated region */
    InvalidateRect(hwnd, &rc, FALSE);
}